Ratón / Lienzo X, Y a Tres.js Mundo X, Y, Z


He buscado un ejemplo que coincida con mi caso de uso, pero no puedo encontrar uno. Estoy tratando de convertir las coordenadas del ratón de la pantalla en coordenadas del mundo 3D teniendo en cuenta la cámara.

Las soluciones que he encontrado todas hacen intersección de rayos para lograr la selección de objetos.

Lo que estoy tratando de hacer es posicionar el centro de un Tres.js objeto en las coordenadas que el ratón está actualmente "over".

Mi cámara está en x: 0, y:0, z:500 (aunque se moverá durante el simulación) y todos mis objetos están en z = 0 con valores variables de x e y, por lo que necesito saber el mundo X, Y basado en asumir un z = 0 para el objeto que seguirá la posición del ratón.

Esta pregunta parece un problema similar, pero no tiene una solución: Obtener las coordenadas del ratón en relación con el espacio 3D en TRES.js

Dada la posición del ratón en la pantalla con un rango de "arriba-izquierda = 0, 0 | abajo-derecha = ventana.innerWidth, ventana.innerHeight", puede alguien proporcionar una solución para mover un Tres.js objeto de las coordenadas del ratón a lo largo de z = 0?

Author: Community, 2012-10-24

8 answers

No necesita tener ningún objeto en su escena para hacer esto.

Ya conoces la posición de la cámara.

Usando vector.unproject( camera ) puede obtener un rayo apuntando en la dirección que desee.

Solo necesita extender ese rayo, desde la posición de la cámara, hasta que la coordenada z de la punta del rayo sea cero.

Puedes hacer eso así:

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 variable pos es la posición del punto en el espacio 3D, "bajo el ratón", y en el plano z=0.

Tres.js r. 95

 104
Author: WestLangley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-22 12:28:59

En r.58 este código funciona para mí:

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);
 5
Author: lazymf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-02 11:07:38

Para obtener las coordenadas del ratón de un objeto 3d use 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;

Para conseguir los tres.js 3D coordenadas que se refieren a las coordenadas específicas del ratón, utilizar el opuesto, 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 );

Hay un gran ejemplo aquí. Sin embargo, para usar project vector, debe haber un objeto donde el usuario haga clic. las intersecciones serán una matriz de todos los objetos en la ubicación del ratón, independientemente de su profundidad.

 2
Author: BishopZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-24 18:35:16

Esto funcionó para mí cuando uso un 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 tres.js r. 89

 2
Author: KTCO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-02 22:12:23

ThreeJS está segando lentamente lejos del proyector.(Un)ProjectVector y la solución con proyector.pickingRay () ya no funciona, acaba de terminar de actualizar mi propio código.. así que la versión de trabajo más reciente debe ser la siguiente:

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;
}
 1
Author: Pete Kozak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-29 15:23:44

A continuación hay una clase ES6 que escribí basada en la respuesta de WestLangley, que funciona perfectamente para mí en TRES.js r77.

Tenga en cuenta que asume que su vista de renderizado ocupa toda la vista del navegador.

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

Puedes usar la clase así:

// 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 ahora contiene la posición proyectada del ratón en el plano z=0.

 1
Author: drone1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-05 19:35:18

Aquí está mi opinión sobre la creación de una clase es6 a partir de ella. Trabajando con tres.js r83. El método para usar rayCaster viene de mrdoob aquí: Tres.proyector js y objetos 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 usarías así si quisieras cotejar todos tus objetos en la escena en busca de golpes. Hice la bandera recursiva falsa arriba porque para mis usos no necesitaba que lo fuera.

var helper = new RaycasterHelper(camera, scene)
var intersects = helper.getIntersectsFromTap(tapX, tapY, 
this.scene.children)
...
 0
Author: Matt Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:38

Aunque las respuestas proporcionadas pueden ser útiles en algunos escenarios, difícilmente puedo imaginar esos escenarios (tal vez juegos o animaciones) porque no son precisos en absoluto (adivinando alrededor de la NDC z de target?). No puedes usar esos métodos para desproteger las coordenadas de la pantalla a las del mundo si conoces el plano z objetivo. Pero para la mayoría de los escenarios, usted debe conocer este plano.

Por ejemplo, si dibuja la esfera por el centro (punto conocido en el espacio del modelo) y el radio, debe obtener el radio como delta de coordenadas de ratón no proyectadas - ¡pero no puedes! Con el debido respeto el método de @ WestLangley con targetZ no funciona, da resultados incorrectos (puedo proporcionar jsfiddle si es necesario). Otro ejemplo-es necesario establecer los controles de órbita objetivo por doble clic del ratón, pero sin" real " raycasting con objetos de escena (cuando no tiene nada que recoger).

La solución para mí es simplemente crear el plano virtual en el punto objetivo a lo largo del eje z y usar raycasting con este plano después. Punto objetivo puede ser el objetivo actual de los controles de órbita o el vértice del objeto que necesita dibujar paso a paso en el espacio del modelo existente, etc. Esto funciona perfectamente y es simple (ejemplo en 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; 
}
 0
Author: SalientBrain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-16 08:49:41