Mouse / Tela X, Y a Three.js Mondo X, Y, Z

Ho cercato un esempio che corrisponde al mio caso d’uso ma non ne trova uno. Sto cercando di convertire le coordinate dello schermo del mouse in coordinate 3D del mondo tenendo conto della fotocamera.

Soluzioni Ho trovato tutte le intersezioni dei raggi per ottenere la selezione degli oggetti.

Quello che sto cercando di fare è posizionare il centro di un object Three.js alle coordinate che il mouse è attualmente “sopra”.

La mia macchina fotografica è in x: 0, y: 0, z: 500 (anche se si muoverà durante la simulazione) e tutti i miei oggetti sono a z = 0 con valori xey variabili, quindi ho bisogno di conoscere il mondo X, Y basato assumendo az = 0 per l’object che seguirà la posizione del mouse.

Questa domanda sembra un problema simile ma non ha una soluzione: ottenere le coordinate del mouse in relazione allo spazio 3D in THREE.js

Data la posizione del mouse sullo schermo con un intervallo di “top-left = 0, 0 | bottom-right = window.innerWidth, window.innerHeight”, chiunque può fornire una soluzione per spostare un object Three.js sulle coordinate del mouse lungo z = 0?

Non è necessario avere oggetti nella scena per farlo.

Conosci già la posizione della videocamera.

Usando vector.unproject( camera ) puoi ottenere un raggio che punta nella direzione desiderata.

Hai solo bisogno di estendere quel raggio, dalla posizione della telecamera, fino a quando la coordinata z della punta del raggio è zero.

Puoi farlo in questo modo:

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

La variabile pos è la posizione del punto nello spazio 3D, “sotto il mouse” e nel piano z=0 .

three.js r.95

In r.58 questo codice funziona per me:

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

per ottenere le coordinate del mouse di un object 3d utilizzare 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; 

per ottenere le coordinate 3D di three.js che si riferiscono a specifiche coordinate del mouse, utilizzare il contrario, 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 ); 

C’è un grande esempio qui . Tuttavia, per utilizzare il vettore del progetto, deve esserci un object su cui l’utente ha fatto clic. intersects sarà una matrice di tutti gli oggetti nella posizione del mouse, indipendentemente dalla loro profondità.

Questo ha funzionato per me quando si utilizza una 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 three.js r.89

ThreeJS si sta lentamente allontanando dal proiettore. (Un) ProjectVector e la soluzione con projector.pickingRay () non funziona più, ho appena finito di aggiornare il mio codice .. quindi la versione più recente dovrebbe essere la seguente:

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

Di seguito è riportata una class ES6 che ho scritto in base alla risposta di WestLangley, che funziona perfettamente per me in THREE.js r77.

Si noti che si presuppone che la vista di rendering occupi l’intera finestra del browser.

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

Puoi usare la class in questo modo:

 // 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 ora contiene la posizione del mouse proiettata sul piano z = 0.

Ecco il mio modo di creare una class es6. Lavorare con Three.js r83. Il metodo di utilizzo di rayCaster deriva da mrdoob qui: Proiettore Three.js e oggetti Ray

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

Lo useresti in questo modo se volessi controllare tutti gli oggetti nella scena per i colpi. Ho reso falso il flag ricorsivo perché per i miei usi non avevo bisogno che fosse.

 var helper = new RaycasterHelper(camera, scene) var intersects = helper.getIntersectsFromTap(tapX, tapY, this.scene.children) ... 

Sebbene le risposte fornite possano essere utili in alcuni scenari, difficilmente riesco a immaginare quegli scenari (magari giochi o animazioni) perché non sono affatto precisi (indovinando intorno all’objective NDC z?). Non è ansible utilizzare questi metodi per non proiettare le coordinate dello schermo su quelle mondiali se si conosce il bersaglio z-plane. Ma per la maggior parte degli scenari, dovresti conoscere questo piano.

Ad esempio, se si disegna la sfera per centro (punto noto nello spazio modello) e raggio – è necessario ottenere il raggio come delta delle coordinate del mouse non proiettate, ma non è ansible! Con tutto il dovuto rispetto, il metodo di WestLangley con targetZ non funziona, fornisce risultati errati (posso fornire jsfiddle se necessario). Un altro esempio: è necessario impostare i controlli dell’orbita con doppio clic del mouse, ma senza raycasting “reale” con oggetti scena (quando non si ha nulla da raccogliere).

La soluzione per me è solo creare il piano virtuale nel punto di destinazione lungo l’asse z e utilizzare raycasting con questo piano in seguito. Il punto target può essere l’orbita corrente controlla il bersaglio o il vertice dell’object che devi disegnare passo dopo passo nello spazio modello esistente ecc. Funziona perfettamente ed è semplice (esempio in 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 = vx - bounds.left; let y = vy - 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; }