'Converting 3D position to 2d screen position [r69!]

I need Three.js code to convert 3D object coordinates to 2d ones in a 'div' element so that I can place text labels where they need to be (without those labels scaling/moving/rotating along with the 3D movement). Unfortunately, all of the examples that I have seen and tried so far seem to be using obsolete functions/techniques. In my case, I believe that I am using r69 of Three.js.

Here is an example of an 'older' technique that just produces errors for me:

Three.js: converting 3d position to 2d screen position

Here is a snippet of some newer code (?) that doesn't provide sufficient context for me to get working, but looks a lot cleaner:

https://github.com/mrdoob/three.js/issues/5533



Solution 1:[1]

I've written for my project the following function; it receives an THREE.Object3D instance and a camera as a parameters and returns the position on the screen.

function toScreenPosition(obj, camera)
{
    var vector = new THREE.Vector3();

    var widthHalf = 0.5*renderer.context.canvas.width;
    var heightHalf = 0.5*renderer.context.canvas.height;

    obj.updateMatrixWorld();
    vector.setFromMatrixPosition(obj.matrixWorld);
    vector.project(camera);

    vector.x = ( vector.x * widthHalf ) + widthHalf;
    vector.y = - ( vector.y * heightHalf ) + heightHalf;

    return { 
        x: vector.x,
        y: vector.y
    };

};

Then I created a THREE.Object3D just to hold the div position (it's attached to a mesh in the scene) and when needed it can easily converted to screen position using the toScreenPosition function and it updates the coordinates of the div element.

var proj = toScreenPosition(divObj, camera);

divElem.style.left = proj.x + 'px';
divElem.style.top = proj.y + 'px';

Here a fiddle with a demo.

Solution 2:[2]

You can convert a 3D position to screen coordinates by using a pattern like so:

var vector = new THREE.Vector3();
var canvas = renderer.domElement;

vector.set( 1, 2, 3 );

// map to normalized device coordinate (NDC) space
vector.project( camera );

// map to 2D screen space
vector.x = Math.round( (   vector.x + 1 ) * canvas.width  / 2 );
vector.y = Math.round( ( - vector.y + 1 ) * canvas.height / 2 );
vector.z = 0;

three.js r.69

Solution 3:[3]

For me this function works (Three.js version 69):

function createVector(x, y, z, camera, width, height) {
        var p = new THREE.Vector3(x, y, z);
        var vector = p.project(camera);

        vector.x = (vector.x + 1) / 2 * width;
        vector.y = -(vector.y - 1) / 2 * height;

        return vector;
    }

Solution 4:[4]

This the supplementary code to the answer of @meirm, where the .getContext() is used in the recent version of THREE.js and the offset of the canvas is added because sometimes a canvas is embedded in another UI.

const toScreenPosition = function(obj, camera){
    let vector = new THREE.Vector3();
    let widthHalf = 0.5 * renderer.getContext().canvas.width;
    let heightHalf = 0.5 * renderer.getContext().canvas.height;
    obj.updateMatrixWorld();
    vector.setFromMatrixPosition(obj.matrixWorld);
    vector.project(camera);
    vector.x = ( vector.x * widthHalf ) + widthHalf + $(scenecanvas).offset().left;
    vector.y = - ( vector.y * heightHalf ) + heightHalf + $(scenecanvas).offset().top;
    return { 
        x: vector.x,
        y: vector.y
    };
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Sarath
Solution 3 Julia Savinkova
Solution 4 Shao-Kui