本文介绍了三.js Vector3 到 2D 屏幕坐标与旋转场景的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图定位一个 div,使其始终位于向左和向上偏移的对象的最高点,因此它不在顶点的正上方.您可以在此处看到.如果 jsfiddle 链接不再有效,请参阅下面的无场景旋转工作片段.我可以告诉你,它的效果非常好.

I am trying to position a div such that it is always at the highest point of an object offset left and up a little so it is not right on top of the vertex. You can see that here. Please see the working with no scene rotation snippet below if the jsfiddle link no longer works. I can tell you, it works fairly marvellously.

但是,对于工作中的项目,我需要旋转场景本身.显然,这会破坏到 2D 屏幕坐标的转换.您可以在此处查看此内容.您也可以查看下面的不使用场景旋转代码段.如您所见,标签不会在垂直(y-")方向更新,但会水平"更新.这是因为相机的位置确实会围绕 x-z 平面发生变化(phi 变化),但它永远不会改变其 y 位置(θ 永远不会变化)——而是在向上/向下拖动鼠标时旋转场景.正如我所写,我需要旋转场景以达到所需的效果.

However, for a project at work, I need to rotate the scene, itself. Apparently, this messes up the conversion to 2D screen coordinates. You can view this here. You can view the not working with scene rotation snippet below as well. As you can see, the label does not update in the vertical ("y-") direction, but it does update "horizontally". This would be because the camera's position does, indeed, change around the x-z plane (phi changes), but it never changes its y-position (theta never changes)--rather the scene is rotated when the mouse is dragged up/down. As I wrote, I need to rotate the scene to achieve the desired affect.

如果有人能指出我正确的方向或在任何 html/js/css 片段站点(如 jsfiddle 等)中做一个简单的例子,{,s}他将是一个救星!

If someone could point me in the right direction or make a quick example in any of the html/js/css snippet sites (like jsfiddle, etc, etc), {,s}he would be a life-saver!

  • side-note:我尝试(显然没有成功)跳过一些圈套将每个顶点的 x 和 y 坐标转换为正确旋转"位置(即,乘以 sin(sceneRotation)cos(sceneRotation),但这只会让情况变得更糟.

  • side-note: I tried (with no success, obviously) to jump through some hoops to convert the x- and y-coordinates for each vertex to a "properly-rotated" position (ie, by multiplying by sin(sceneRotation) and cos(sceneRotation), but that just made it even worse.

side-note 2:我也只花了一个小时旋转场景中的每个对象,但由于这些对象实际上存储在 THREE.Groups 中,它具有完全相同的效果.

side-note 2: I also just spent an hour rotating each object in the scene, but since the objects are actually stored in THREE.Groups, it has the exact same effect.

点击下面的运行代码段

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);
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);

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);
scene.add(cube);
var matWire = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
var cube1 = new THREE.Mesh(geom, matWire);
scene.add(cube1);

render();

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

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

    vector.project(camera);

    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]);
        if (minY === null || pos.y < minY) {
        	minY = pos.y;
            x = pos.x;
        }
    }
    label.style.left = (x - 3) + "px";
    label.style.top = (minY - 28) + "px";
}
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>

点击下面的运行代码段

/* "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) {
    var vector = new THREE.Vector3( position.x, position.y, position.z );

    vector.project(camera);

    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]);
        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>

推荐答案

在渲染中,场景的每个网格通常由模型矩阵、视图矩阵和投影矩阵进行变换.

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.

查看矩阵:
视图矩阵描述了观察场景的方向和位置.视图矩阵从世界空间转换到视图(眼睛)空间.在视口的坐标系中,X 轴指向左侧,Y 轴指向上方,Z 轴指向视图外(注意在右手坐标系中,Z 轴是 X 轴的叉积)轴和 Y 轴).

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).

投影矩阵:
投影矩阵描述了从场景的 3D 点到视口的 2D 点的映射.投影矩阵从视图空间转换到裁剪空间,裁剪空间中的坐标转换为(-1, -1, -1)到(1, 1, 1)范围内的归一化设备坐标(NDC)通过除以剪辑坐标的 w 分量.

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.

如果您想知道在视口上可以看到几何图形中的某个点,那么您必须进行所有这些转换,并且必须将标准化设备坐标 (NDC) 转换为窗口坐标(像素).

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).

视图矩阵和投影矩阵的变换由project完成:

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

vector.project(camera);

从标准化设备坐标(NDC)到窗口坐标(像素)的转换是这样完成的:

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);


像这样调整函数`getScreenPosition`:函数getScreenPosition(位置){var vector = new THREE.Vector3( position.x, position.y, position.z );//模型到世界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://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>

这篇关于三.js Vector3 到 2D 屏幕坐标与旋转场景的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-23 13:19