three.js Vector3 to 2D screen coordinate with rotated scene

怎甘沉沦 提交于 2019-12-06 09:29:50

In a rendering, each mesh of the scene usually is transformed by the model matrix, the view matrix and the projection matrix.

  • Model matrix:
    The model matrix defines the location, orientation and the relative size of an mesh in the scene. The model matrix transforms the vertex positions from of the mesh to the world space.

  • View matrix:
    The view matrix describes the direction and position from which the scene is looked at. The view matrix transforms from the world space to the view (eye) space. In the coordinate system on the viewport, the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).

  • Projection matrix:
    The projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. The projection matrix transforms from view space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) in the range (-1, -1, -1) to (1, 1, 1) by dividing with the w component of the clip coordinates.

If you want know, where a point from the geometry is seen on the viewport, then you have to do all these transformation and you have to convert from normalized device coordinates (NDC) to window coordinates (pixel).

The transformation by the view matrix and the projection matrix is done by project:

vector.project(camera);

The transformation from normalized device coordinates (NDC) to window coordinates (pixel) is done like this:

vector.x = Math.round( (   vector.x + 1 ) * w / 2 );
vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );

You forgot the transformation with the model matrix, which can be done like this:

var modelMat = cube.matrixWorld;
vector.applyMatrix4(modelMat);


Adapt the function getScreenPosition somehow like this:

function getScreenPosition(position) {
    var vector = new THREE.Vector3( position.x, position.y, position.z );

    // model to world
    var modelMat = cube.matrixWorld;
    vector.applyMatrix4(modelMat);

    // world to view and view to NDC
    vector.project(camera);

    // NDC to pixel
    vector.x = Math.round( (   vector.x + 1 ) * w / 2 );
    vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );

    return vector;
}


See the code snippet:

  /* can't see top line; thanks jsfiddle */
    /* "globals" */
    /* ~~~~~~~~~ */
    var PI = Math.PI;
    /* camera stuff */
    var lastX, lastY, r = 60, phi = 0, c = new THREE.Vector3(0, 0, 0);
    /* three.js stuff */
    var scene = new THREE.Scene();
    var w = window.innerWidth, h = window.innerHeight;
    var camera = new THREE.PerspectiveCamera(60, w / h, 1, 1000);
    //camera.position.set(0, 0, -60);
    updateCamera();
    var renderer = new THREE.WebGLRenderer({
        alpha: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.domElement.style.backgroundColor = "#bbbbbb"
    document.body.appendChild(renderer.domElement);

    var label = document.getElementById("label");
    //var controls = new THREE.OrbitControls(camera, renderer.domElement);
    //controls.addEventListener("change", updateLabel);
    document.body.addEventListener("mousedown", handleMouseDown);
    document.body.addEventListener("touchstart", handleTouchStart);

    var geom = new THREE.Geometry();
    geom.vertices.push(new THREE.Vector3(-10, 10, -10));
    geom.vertices.push(new THREE.Vector3(10, 10, -10));
    geom.vertices.push(new THREE.Vector3(10, -10, -10));
    geom.vertices.push(new THREE.Vector3(-10, -10, -10));
    geom.vertices.push(new THREE.Vector3(-10, 10, 10));
    geom.vertices.push(new THREE.Vector3(10, 10, 10));
    geom.vertices.push(new THREE.Vector3(10, -10, 10));
    geom.vertices.push(new THREE.Vector3(-10, -10, 10));
    geom.faces.push(new THREE.Face3(0, 1, 2));
    geom.faces.push(new THREE.Face3(0, 2, 3));
    geom.faces.push(new THREE.Face3(7, 6, 5));
    geom.faces.push(new THREE.Face3(7, 5, 4));
    geom.faces.push(new THREE.Face3(4, 5, 1));
    geom.faces.push(new THREE.Face3(4, 1, 0));
    geom.faces.push(new THREE.Face3(3, 2, 6));
    geom.faces.push(new THREE.Face3(3, 6, 7));
    geom.faces.push(new THREE.Face3(4, 0, 3));
    geom.faces.push(new THREE.Face3(4, 3, 7));
    geom.faces.push(new THREE.Face3(1, 5, 6));
    geom.faces.push(new THREE.Face3(1, 6, 2));
    var mat = new THREE.MeshBasicMaterial({ color: 0x3377dd, transparent: true, opacity: .65, wireframe: false });
    var cube = new THREE.Mesh(geom, mat);
    //cube.translateX(10);
    scene.add(cube);
    var matWire = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
    var cube1 = new THREE.Mesh(geom, matWire);
    //cube1.translateX(10);
    scene.add(cube1);

    render();

    function render() {
        requestAnimationFrame(render);
        renderer.render(scene, camera);
    }

    function getScreenPosition(position, object) {
        var vector = new THREE.Vector3( position.x, position.y, position.z );

        // model to world
        if ( object != null ) {
            var modelMat = cube.matrixWorld;
            vector.applyMatrix4(modelMat);
        }

        // world to view and view to NDC
        vector.project(camera);

        // NDC to pixel
        vector.x = Math.round( (   vector.x + 1 ) * w / 2 );
        vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );

        return vector;
    }

    function updateLabel() {
        var minY = null, x = null,
        	verts = cube.geometry.vertices;
        for (var i = 0, iLen = verts.length; i < iLen; i++) {
            var pos = getScreenPosition(verts[i], cube);
            if (minY === null || pos.y < minY) {
            	minY = pos.y;
                x = pos.x;
            }
        }
        label.style.left = (x - 3) + "px";
        label.style.top = (minY - 28) + "px";
    }

    function handleMouseDown(ev) {
        ev.preventDefault();
        mouseOrTouchDown(ev.pageX, ev.pageY);
    }

    function handleTouchStart(ev) {
        var touches = ev.touches;
        if (touches.length !== 1) {
            return;
        }
        ev.preventDefault();
        mouseOrTouchDown(touches.item(0).pageX, touches.item(0).pageY, true);
    }

    function mouseOrTouchDown(downX, downY, touch) {
        if (touch === undefined) { touch = false; }
        lastX = downX;
        lastY = downY;
        if (touch) {
            document.ontouchmove = handleTouchMove;
            document.addEventListener("touchend", function(ev) {
                document.ontouchmove = null;
            });
            document.addEventListener("touchcancel", function(ev) {
                document.removeEventListener("touchmove", handleTouchMove);
            });
        } else {
            document.addEventListener("mousemove", handleMouseMove);
            document.addEventListener("mouseup", function(ev) {
                document.removeEventListener("mousemove", handleMouseMove);
            });
        }
    }

    function handleMouseMove(ev) {
        ev.preventDefault();
        mouseOrTouchMove(ev.pageX, ev.pageY);
    }

    function handleTouchMove(ev) {
        var touches = ev.touches;
        if (touches.length !== 1) {
            return;
        }
        ev.preventDefault();
        mouseOrTouchMove(touches.item(0).pageX, touches.item(0).pageY);
    }

    function mouseOrTouchMove(x, y) {
        var dx = lastX - x, dy = y - lastY; /* change in {x, y} */

        phi -= dx / 100;
        if (phi > 2 * PI) {
            phi -= 2 * PI;
        } else if (phi < 0) {
            phi += 2 * PI;
        }

        if (phi < PI / 2 || phi > 3 * PI / 2) {
            sign = -1;
        } else {
            sign = 1;
        }
        if (scene.rotation.z + sign * dy / 100 < -PI) {
            scene.rotation.z = -PI;
        } else if (scene.rotation.z + sign * dy / 100 > 0) {
            scene.rotation.z = 0;
        } else {
            scene.rotateZ(sign * dy / 100);
        }

        lastX = x;
        lastY = y;

        updateCamera();
        updateLabel();
    }

    function updateCamera() {
        var z = r * Math.sin(phi); /* new y pos (z-axis) in x-z plane */
        var x = r * Math.cos(phi); /* new x pos (x-axis) in x-z plane */
        camera.position.set(x, 1, z);
        camera.lookAt(c);
    }
body {
    overflow: hidden;
    margin: 0;
}

#label {
    position: absolute;
    left: 20px; top: 20px;
}
<div id="label">label</div>
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!