46

There are several excellent stack questions (1, 2) about unprojecting in Three.js, that is how to convert (x,y) mouse coordinates in the browser to the (x,y,z) coordinates in Three.js canvas space. Mostly they follow this pattern:

    var elem = renderer.domElement, 
        boundingRect = elem.getBoundingClientRect(),
        x = (event.clientX - boundingRect.left) * (elem.width / boundingRect.width),
        y = (event.clientY - boundingRect.top) * (elem.height / boundingRect.height);

    var vector = new THREE.Vector3( 
        ( x / WIDTH ) * 2 - 1, 
        - ( y / HEIGHT ) * 2 + 1, 
        0.5 
    );

    projector.unprojectVector( vector, camera );
    var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
    var intersects = ray.intersectObjects( scene.children );

I have been attempting to do the reverse - instead of going from "screen to world" space, to go from "world to screen" space. If I know the position of the object in Three.js, how do I determine its position on the screen?

There does not seem to be any published solution to this problem. Another question about this just showed up on Stack, but the author claims to have solved the problem with a function that is not working for me. Their solution does not use a projected Ray, and I am pretty sure that since 2D to 3D uses unprojectVector(), that the 3D to 2D solution will require projectVector().

There is also this issue opened on Github.

Any help is appreciated.

Community
  • 1
  • 1
BishopZ
  • 6,269
  • 8
  • 45
  • 58

4 Answers4

41

Try with this:

var width = 640, height = 480;
var widthHalf = width / 2, heightHalf = height / 2;

var vector = new THREE.Vector3();
var projector = new THREE.Projector();
projector.projectVector( vector.setFromMatrixPosition( object.matrixWorld ), camera );

vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
mrdoob
  • 19,334
  • 4
  • 63
  • 62
  • 1
    an op on another Stack questions posted this link which also has the solution. Thanks again! https://github.com/mrdoob/three.js/issues/78 – BishopZ Jul 24 '12 at 15:13
  • 2
    getPosition() has been deprecated. Use this instead: var vector = projector.projectVector(new THREE.Vector3().getPositionFromMatrix(object.matrixWorld), camera); – imbrizi Jun 02 '13 at 01:17
  • I think your code is missing vector before getPositionFromMatrix. So vector.getPositionFromMatrix – PCoelho Jul 13 '13 at 02:06
  • Fixed again. Sorry about that. – mrdoob Jul 13 '13 at 11:11
  • It looks like vector.getPositionFromMatrix() is now called vector.setFromMatrixPosition(). http://threejs.org/docs/index.html#Reference/Math/Vector3 – stephband Feb 18 '14 at 17:49
  • @mrdoob this solution not working now :( x: NaN, y: NaN, z: -Infinity always. – Artem Fitiskin Aug 01 '14 at 06:56
  • Working fine for me, I'm on r68, @mrdoob any reason why vector is a `THREE.Vector3()` and not a `THREE.Vector2()`? I'm guessing that for screen coordinates we don't need the `z` value but maybe I'm wrong. Also, this answer seems to cover the instance where you want the screen coordinates of a particular object, but what about a specific vector? For example, if I wanted to get the centroid of a face and know the screen coordinates of that, this wouldn't really be useful right? I guess I should ask a new question... – Rohan Deshpande Nov 13 '14 at 02:42
  • "object is undefined" – João Vilaça Jan 31 '18 at 16:30
35

For modern Three.js (r75), a vector can be projected onto the screen with:

var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;

var pos = object.position.clone();
pos.project(camera);
pos.x = ( pos.x * widthHalf ) + widthHalf;
pos.y = - ( pos.y * heightHalf ) + heightHalf;
dkobozev
  • 2,265
  • 2
  • 21
  • 21
  • 7
    Note that `object.position` might not be what you need, if the object is part of a group, since `object.position` is the local position within a group. Using `object.getWorldPosition()` (or `pos.setFromMatrixPosition()`, as in the other answers) instead will account for relative positioning within groups. – tangle Jun 05 '18 at 12:14
  • 3
    This answer assumes the canvas is the same size as the window. Really one should be using the half canvas width and half canvas height... – duhaime Jul 09 '20 at 20:13
  • 3
    If you want to get the position accurately regardless of the position and size of the containing canvas (which may not be the entire window), you'll want to base these calculations off of `canvas.getBoundingClientRect()` instead of `window` – BallpointBen Dec 17 '20 at 18:19
17

For everyone getting deprecated or warnings logs, the accepted answer is for older Three.js versions. Now it's even easier with:

let pos = new THREE.Vector3();
pos = pos.setFromMatrixPosition(object.matrixWorld);
pos.project(camera);

let widthHalf = canvasWidth / 2;
let heightHalf = canvasHeight / 2;

pos.x = (pos.x * widthHalf) + widthHalf;
pos.y = - (pos.y * heightHalf) + heightHalf;
pos.z = 0;

console.log(pos);
martin
  • 93,354
  • 25
  • 191
  • 226
  • "object" is not defined – João Vilaça Jan 31 '18 at 16:31
  • 1
    @JoãoVilaça `object` should the the `THREE.Object3D` instance you are trying to convert to a screen coordinate (it takes the position of that object) – Ferrybig Jan 16 '19 at 09:44
  • 1
    This is nice, but when the object is behind the camera, `pos.project()` returns a value as if the object is rotated 180 degrees around the camera. – Jespertheend May 15 '20 at 09:56
  • @Jespertheend do you know any solution on this problem? – Marcel Ennix May 28 '20 at 08:52
  • @MarcelEnnix you can check if `pos.z` is between `0` and `1` and assume the object to be off screen when it isn't in this range. Ideally I would have the 2d position approach Infinity when behind the camera but I haven't figured out how to do this yet. – Jespertheend May 28 '20 at 09:32
  • 1
    @Jespertheend Thanks for your answer. I solved it by creating a directionVector from camera to the object and compare it with the lookAt-Vector from the camera. You can see it in action here (browser-mmo): https://g13.ennix.io – Marcel Ennix May 29 '20 at 03:18
1

None of these answers worked for me but they were very close, so I investigated a little bit more and combining some code from those answers plus this article I was able to make it work with the following snippet

const vector = new THREE.Vector3();
const canvas = renderer.domElement; // `renderer` is a THREE.WebGLRenderer

obj.updateMatrixWorld();  // `obj´ is a THREE.Object3D
vector.setFromMatrixPosition(obj.matrixWorld);

vector.project(camera); // `camera` is a THREE.PerspectiveCamera

const x = Math.round((0.5 + vector.x / 2) * (canvas.width / window.devicePixelRatio));
const y = Math.round((0.5 - vector.y / 2) * (canvas.height / window.devicePixelRatio));
adriancho5692
  • 671
  • 6
  • 8