88 votes

Souris / Canvas X, Y to Three.js World X, Y, Z

J'ai cherché un exemple qui corresponde à mon cas d'utilisation, mais je n'en ai pas trouvé. J'essaie de convertir les coordonnées de la souris de l'écran en coordonnées du monde 3D en tenant compte de la caméra.

Les solutions que j'ai trouvées font toutes appel à l'intersection des rayons pour obtenir la sélection des objets.

Ce que j'essaie de faire, c'est de positionner le centre d'un objet Three.js aux coordonnées sur lesquelles la souris se trouve actuellement.

Ma caméra est à x:0, y:0, z:500 (bien qu'elle se déplace pendant la simulation) et tous mes objets sont à z = 0 avec des valeurs x et y variables. J'ai donc besoin de connaître les valeurs X et Y du monde en supposant un z = 0 pour l'objet qui suivra la position de la souris.

Cette question ressemble à un problème similaire mais n'a pas de solution : Obtenir les coordonnées de la souris par rapport à l'espace 3D dans THREE.js

Etant donné la position de la souris sur l'écran avec un intervalle de "top-left = 0, 0 | bottom-right = window.innerWidth, window.innerHeight", quelqu'un peut-il fournir une solution pour déplacer un objet Three.js aux coordonnées de la souris le long de z = 0 ?

162voto

WestLangley Points 28444

Vous n'avez pas besoin d'avoir des objets dans votre scène pour faire cela.

Vous connaissez déjà la position de la caméra.

Utilisation vector.unproject( camera ) vous pouvez obtenir un rayon pointant dans la direction souhaitée.

Il suffit d'étendre ce rayon, à partir de la position de la caméra, jusqu'à ce que la coordonnée z de l'extrémité du rayon soit nulle.

Vous pouvez procéder de la manière suivante :

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 est la position du point dans l'espace 3D, "sous la souris", et dans le plan z=0 .


EDIT : Si vous avez besoin du point "sous la souris" et dans le plan z = targetZ remplacer le calcul de la distance par :

var distance = ( targetZ - camera.position.z ) / vec.z;

three.js r.98

14voto

KTCO Points 1123

Cela a fonctionné pour moi lors de l'utilisation d'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 three.js r.89

8voto

lazymf Points 80

Dans la version 58, ce code fonctionne pour moi :

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

5voto

drone1 Points 51

Voici une classe ES6 que j'ai écrite en me basant sur la réponse de WestLangley, et qui fonctionne parfaitement dans THREE.js r77.

Notez que cela suppose que votre fenêtre de rendu occupe la totalité de la fenêtre du navigateur.

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

Vous pouvez utiliser la classe comme suit :

// 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 contient maintenant la position projetée de la souris sur le plan z=0.

3voto

duhaime Points 494

J'avais un canevas plus petit que ma fenêtre complète, et je devais déterminer les coordonnées mondiales d'un clic :

// get the position of a canvas event in world coords
function getWorldCoords(e) {
  // get x,y coords into canvas where click occurred
  var rect = canvas.getBoundingClientRect(),
      x = e.clientX - rect.left,
      y = e.clientY - rect.top;
  // convert x,y to clip space; coords from top left, clockwise:
  // (-1,1), (1,1), (-1,-1), (1, -1)
  var mouse = new THREE.Vector3();
  mouse.x = ( (x / canvas.clientWidth ) * 2) - 1;
  mouse.y = (-(y / canvas.clientHeight) * 2) + 1;
  mouse.z = 0.5; // set to z position of mesh objects
  // reverse projection from 3D to screen
  mouse.unproject(camera);
  // convert from point to a direction
  mouse.sub(camera.position).normalize();
  // scale the projected ray
  var distance = -camera.position.z / mouse.z,
      scaled = mouse.multiplyScalar(distance),
      coords = camera.position.clone().add(scaled);
  return coords;
}

var canvas = renderer.domElement;
canvas.addEventListener('click', getWorldCoords);

Voici un exemple. Cliquez sur la même région du donut avant et après le glissement et vous constaterez que les coordonnées restent constantes (vérifiez la console du navigateur) :

// three.js boilerplate
var container = document.querySelector('body'),
    w = container.clientWidth,
    h = container.clientHeight,
    scene = new THREE.Scene(),
    camera = new THREE.PerspectiveCamera(75, w/h, 0.001, 100),
    controls = new THREE.MapControls(camera, container),
    renderConfig = {antialias: true, alpha: true},
    renderer = new THREE.WebGLRenderer(renderConfig);
controls.panSpeed = 0.4;
camera.position.set(0, 0, -10);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);

window.addEventListener('resize', function() {
  w = container.clientWidth;
  h = container.clientHeight;
  camera.aspect = w/h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
})

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

// draw some geometries
var geometry = new THREE.TorusGeometry( 10, 3, 16, 100, );
var material = new THREE.MeshNormalMaterial( { color: 0xffff00, } );
var torus = new THREE.Mesh( geometry, material, );
scene.add( torus );

// convert click coords to world space
// get the position of a canvas event in world coords
function getWorldCoords(e) {
  // get x,y coords into canvas where click occurred
  var rect = canvas.getBoundingClientRect(),
      x = e.clientX - rect.left,
      y = e.clientY - rect.top;
  // convert x,y to clip space; coords from top left, clockwise:
  // (-1,1), (1,1), (-1,-1), (1, -1)
  var mouse = new THREE.Vector3();
  mouse.x = ( (x / canvas.clientWidth ) * 2) - 1;
  mouse.y = (-(y / canvas.clientHeight) * 2) + 1;
  mouse.z = 0.0; // set to z position of mesh objects
  // reverse projection from 3D to screen
  mouse.unproject(camera);
  // convert from point to a direction
  mouse.sub(camera.position).normalize();
  // scale the projected ray
  var distance = -camera.position.z / mouse.z,
      scaled = mouse.multiplyScalar(distance),
      coords = camera.position.clone().add(scaled);
  console.log(mouse, coords.x, coords.y, coords.z);
}

var canvas = renderer.domElement;
canvas.addEventListener('click', getWorldCoords);

render();

html,
body {
  width: 100%;
  height: 100%;
  background: #000;
}
body {
  margin: 0;
  overflow: hidden;
}
canvas {
  width: 100%;
  height: 100%;
}

<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js'></script>
<script src=' https://threejs.org/examples/js/controls/MapControls.js'></script>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X