我试图定位一个 div,使其始终位于对象的最高点,向左和向上偏移一点,因此它不在顶点的正上方。你可以看到 here .如果 jsfiddle 链接不再有效,请参阅下面的在没有场景旋转的情况下工作 片段。我可以告诉你,它的效果非常好。
但是,对于工作中的项目,我需要旋转场景本身。显然,这搞乱了到 2D 屏幕坐标的转换。你可以查看这个here .您还可以查看下面的不使用场景旋转 片段。如您所见,标签不会在垂直(“y-”)方向更新,但会“水平”更新。这是因为相机的位置确实在 x-z 平面周围发生变化(phi 变化),但它永远不会改变其 y 位置(theta 永远不会改变)——而是当鼠标向上/向下拖动时场景会旋转。如我所写,我需要旋转场景以获得所需的效果。
如果有人能为我指出正确的方向或在任何 html/js/css 片段网站(如 jsfiddle 等)中快速举例,{,s}他将成为救星!
旁注:我尝试(显然没有成功)跳过一些步骤将每个顶点的 x 和 y 坐标转换为“正确旋转”位置(即乘以
sin(sceneRotation)
和cos(sceneRotation)
,但这只会让情况变得更糟。旁注 2:我还花了一个小时旋转场景中的每个对象,但由于这些对象实际上存储在
THREE.Group
中, 它具有完全相同的效果。
在没有场景旋转的情况下工作
点击下面的运行代码段
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>
最佳答案
在渲染中,场景的每个网格通常由模型矩阵、 View 矩阵和投影矩阵进行变换。
模型矩阵:
模型矩阵定义场景中网格的位置、方向和相对大小。模型矩阵将顶点位置从网格变换到世界空间。查看矩阵:
View 矩阵描述了观察场景的方向和位置。 View 矩阵从世界空间转换到 View (眼睛)空间。在视口(viewport)的坐标系中,X 轴指向左侧,Y 轴指向上方,Z 轴指向 View 外(请注意,在右手系统中,Z 轴是 X 轴的叉积轴和 Y 轴)。投影矩阵:
投影矩阵描述了从场景的 3D 点到视口(viewport)的 2D 点的映射。投影矩阵从 View 空间变换到裁剪空间,裁剪空间中的坐标被变换为范围为(-1, -1, -1) 到(1, 1, 1) 的归一化设备坐标(NDC)除以剪辑坐标的 w 分量。
如果您想知道几何体中的一个点在视口(viewport)中的哪个位置,则必须进行所有这些转换,并且必须将归一化设备坐标 (NDC) 转换为窗口坐标(像素)。
View 矩阵和投影矩阵的变换由project
完成:
vector.project(camera);
从归一化设备坐标(NDC)到窗口坐标(像素)的转换是这样完成的:
vector.x = Math.round( ( vector.x + 1 ) * w / 2 );
vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );
你忘记了模型矩阵的转换,可以这样完成:
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);
// 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;
}
查看代码片段:
/* 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/three@0.116/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.116/examples/js/controls/OrbitControls.js"></script>
关于javascript - three.js Vector3 到 2D 屏幕坐标与旋转场景,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46667395/