Мышь / холст X, Y до трех.js World X, Y, Z
Я искал пример, который соответствует моему варианту использования, но не могу его найти. Я пытаюсь преобразовать координаты экранной мыши в координаты 3D-мира с учетом камеры.
решения, которые я нашел, все делают пересечение лучей для достижения выбора объекта.
то, что я пытаюсь сделать, это расположить центр тройки.JS объект в координатах, что мышь в настоящее время"над".
моя камера находится на x:0 и y:0, z:от 500 (хотя это перемещение во время моделирования), и все мои объекты находятся в z = 0 с различными значениями x и y, поэтому мне нужно знать мир X, Y, основанный на предположении, что z = 0 для объекта, который будет следовать за положением мыши.
этот вопрос выглядит как аналогичная проблема, но не имеет решения:получение координат мыши по отношению к трехмерному пространству в трех.js
учитывая положение мыши на экране с диапазоном " верхний левый = 0, 0 / нижний правый = окно.расстояние между, окно.innerHeight", может ли кто-нибудь предоставить решение для перемещения тройки.JS-объект с координатами мыши по z = 0?
8 ответов:
вам не нужно иметь какие-либо объекты в вашей сцене, чтобы сделать это.
вы уже знаете, положение камеры.
используя
vector.unproject( camera )
вы можете получить луч, указывающий в нужном направлении.вам просто нужно расширить этот луч, от положения камеры, пока Z-координата кончика луча не равна нулю.
вы можете сделать это вот так:
var vec = new THREE.Vector3(); // create once and reuse var pos = new THREE.Vector3(); // create once and reuse vec.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 ); vec.unproject( camera ); vec.sub( camera.position ).normalize(); var distance = - camera.position.z / vec.z; pos.copy( camera.position ).add( vec.multiplyScalar( distance ) );
переменная
pos
положение точки в 3D-пространстве, "под мышкой", а в самолетеz=0
.три.Яш Р.95
в р. 58 этот код работает для меня:
var planeZ = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); var mv = new THREE.Vector3( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5 ); var raycaster = projector.pickingRay(mv, camera); var pos = raycaster.ray.intersectPlane(planeZ); console.log("x: " + pos.x + ", y: " + pos.y);
чтобы получить координаты мыши 3d объекта используйте projectVector:
var width = 640, height = 480; var widthHalf = width / 2, heightHalf = height / 2; var projector = new THREE.Projector(); var vector = projector.projectVector( object.matrixWorld.getPosition().clone(), camera ); vector.x = ( vector.x * widthHalf ) + widthHalf; vector.y = - ( vector.y * heightHalf ) + heightHalf;
чтобы получить три.JS 3D координаты, которые относятся к конкретным координатам мыши, используйте противоположный, unprojectVector:
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 );
есть отличный пример здесь. Однако для использования вектора проекта должен быть объект, на который пользователь нажал. пересечения будут представлять собой массив всех объектов в месте расположения мыши, независимо от их глубины.
это работает для меня при использовании
orthographic camera
let vector = new THREE.Vector3(); vector.set( (event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1, 0 ); vector.unproject(camera);
WebGL три.Яш Р.89
ThreeJS медленно косит от проектора.(ООН) Проектвектор и решение с проектором.pickingRay() больше не работает, только что закончил обновление моего собственного кода.. поэтому самая последняя рабочая версия должна быть следующей:
var rayVector = new THREE.Vector3(0, 0, 0.5); var camera = new THREE.PerspectiveCamera(fov,this.offsetWidth/this.offsetHeight,0.1,farFrustum); var raycaster = new THREE.Raycaster(); var scene = new THREE.Scene(); //... function intersectObjects(x, y, planeOnly) { rayVector.set(((x/this.offsetWidth)*2-1), (1-(y/this.offsetHeight)*2), 1).unproject(camera); raycaster.set(camera.position, rayVector.sub(camera.position ).normalize()); var intersects = raycaster.intersectObjects(scene.children); return intersects; }
Ниже приведен класс ES6, который я написал на основе ответа WestLangley,который отлично работает для меня в трех.js r77.
обратите внимание, что он предполагает, что ваш видовой экран рендеринга занимает весь видовой экран браузера.
class CProjectMousePosToXYPlaneHelper { constructor() { this.m_vPos = new THREE.Vector3(); this.m_vDir = new THREE.Vector3(); } Compute( nMouseX, nMouseY, Camera, vOutPos ) { let vPos = this.m_vPos; let vDir = this.m_vDir; vPos.set( -1.0 + 2.0 * nMouseX / window.innerWidth, -1.0 + 2.0 * nMouseY / window.innerHeight, 0.5 ).unproject( Camera ); // Calculate a unit vector from the camera to the projected position vDir.copy( vPos ).sub( Camera.position ).normalize(); // Project onto z=0 let flDistance = -Camera.position.z / vDir.z; vOutPos.copy( Camera.position ).add( vDir.multiplyScalar( flDistance ) ); } }
Вы можете использовать класс, как это:
// Instantiate the helper and output pos once. let Helper = new CProjectMousePosToXYPlaneHelper(); let vProjectedMousePos = new THREE.Vector3(); ... // In your event handler/tick function, do the projection. Helper.Compute( e.clientX, e.clientY, Camera, vProjectedMousePos );
vProjectedMousePos теперь содержит проецируемое положение мыши на плоскости z=0.
вот мой взгляд на создание класса es6 из него. Работаем с тремя.js r83. Метод использования rayCaster происходит от mrdoob здесь: три.JS проектор и лучевые объекты
export default class RaycasterHelper { constructor (camera, scene) { this.camera = camera this.scene = scene this.rayCaster = new THREE.Raycaster() this.tapPos3D = new THREE.Vector3() this.getIntersectsFromTap = this.getIntersectsFromTap.bind(this) } // objects arg below needs to be an array of Three objects in the scene getIntersectsFromTap (tapX, tapY, objects) { this.tapPos3D.set((tapX / window.innerWidth) * 2 - 1, -(tapY / window.innerHeight) * 2 + 1, 0.5) // z = 0.5 important! this.tapPos3D.unproject(this.camera) this.rayCaster.set(this.camera.position, this.tapPos3D.sub(this.camera.position).normalize()) return this.rayCaster.intersectObjects(objects, false) } }
вы бы использовали его так, если бы вы хотели проверить все ваши объекты в сцене для хитов. Я сделал рекурсивный флаг false выше, потому что для моего использования мне это не нужно.
var helper = new RaycasterHelper(camera, scene) var intersects = helper.getIntersectsFromTap(tapX, tapY, this.scene.children) ...
хотя предоставленные ответы могут быть полезны в некоторых сценариях, я вряд ли могу представить себе эти сценарии (возможно, игры или анимации), потому что они вообще не точны (гадание вокруг NDC Z цели?). Вы не можете использовать эти методы для отмены проекции координат экрана на мировые, если вы знаете целевую z-плоскость. Но для большинства сценариев, вы должны знать этот самолет.
например, если вы рисуете сферу по центру (известная точка в пространстве модели) и радиусу - вам нужно получить радиус как Дельта проекции, координаты мыши - но ты не можешь! При всем уважении метод @ WestLangley с targetZ не работает, он дает неправильные результаты (я могу предоставить jsfiddle, если это необходимо). Другой пример-вам нужно установить orbit controls target двойным щелчком мыши, но без "реального" raycasting с объектами сцены (когда вам нечего выбирать).
решение для меня состоит в том, чтобы просто создать виртуальную плоскость в целевой точке вдоль оси z и использовать raycasting с этой плоскостью позже. Целевая точка может быть текущей целью управления орбитой или вершиной объекта, который вам нужно нарисовать шаг за шагом в существующем пространстве модели и т. д. Это работает отлично и просто (пример в typescript):
screenToWorld(v2D: THREE.Vector2, camera: THREE.PerspectiveCamera = null, target: THREE.Vector3 = null): THREE.Vector3 { const self = this; const vNdc = self.toNdc(v2D); return self.ndcToWorld(vNdc, camera, target); } //get normalized device cartesian coordinates (NDC) with center (0, 0) and ranging from (-1, -1) to (1, 1) toNdc(v: THREE.Vector2): THREE.Vector2 { const self = this; const canvasEl = self.renderers.WebGL.domElement; const bounds = canvasEl.getBoundingClientRect(); let x = v.x - bounds.left; let y = v.y - bounds.top; x = (x / bounds.width) * 2 - 1; y = - (y / bounds.height) * 2 + 1; return new THREE.Vector2(x, y); } ndcToWorld(vNdc: THREE.Vector2, camera: THREE.PerspectiveCamera = null, target: THREE.Vector3 = null): THREE.Vector3 { const self = this; if (!camera) { camera = self.camera; } if (!target) { target = self.getTarget(); } const position = camera.position.clone(); const origin = self.scene.position.clone(); const v3D = target.clone(); self.raycaster.setFromCamera(vNdc, camera); const normal = new THREE.Vector3(0, 0, 1); const distance = normal.dot(origin.sub(v3D)); const plane = new THREE.Plane(normal, distance); self.raycaster.ray.intersectPlane(plane, v3D); return v3D; }