Мышь / холст 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 51

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