82

ユースケースに一致する例を探しましたが、見つかりませんでした。カメラを考慮して、画面のマウス座標を3Dワールド座標に変換しようとしています。

私が見つけた解決策はすべて、オブジェクトの選択を実現するために光線の交差を行います。

私がやろうとしているのは、Three.jsオブジェクトの中心を、マウスが現在「上」にある座標に配置することです。

私のカメラはx:0、y:0、z:500にあり(シミュレーション中に移動しますが)、すべてのオブジェクトはz = 0にあり、xとyの値が変化するため、X、Yベースの世界を知る必要があります。マウスの位置に続くオブジェクトに対してaz=0と仮定した場合。

この質問は同様の問題のように見えますが、解決策はありません。THREE.jsの3D空間に関連するマウスの座標を取得する

画面上のマウスの位置が「左上=0、0|右下=window.innerWidth、window.innerHeight」の範囲である場合、Three.jsオブジェクトをマウスの座標に移動するためのソリューションを提供できる人は誰でもいます。 z = 0に沿って?

4

10 に答える 10

148

これを行うために、シーンにオブジェクトを含める必要はありません。

あなたはすでにカメラの位置を知っています。

を使用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


編集:「マウスの下」で平面内の点が必要な場合はz = targetZ、距離の計算を次のように置き換えます。

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

three.js r.98

于 2012-10-26T17:35:12.973 に答える
7

r.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);
于 2013-07-02T11:07:38.620 に答える
7

これは、を使用するときに私のために働いた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

于 2018-01-02T22:12:23.580 に答える
3

以下は、WestLangleyの返信に基づいて作成したES6クラスです。これは、THREE.jsr77で完全に機能します。

レンダリングビューポートがブラウザのビューポート全体を占めることを前提としていることに注意してください。

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平面上に投影されたマウスの位置が含まれるようになりました。

于 2016-06-05T19:14:13.597 に答える
2

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;

特定のマウス座標に関連するthree.js3D座標を取得するには、反対の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 );

ここに素晴らしい例があります。ただし、プロジェクトベクトルを使用するには、ユーザーがクリックしたオブジェクトが必要です。交差は、深さに関係なく、マウスの位置にあるすべてのオブジェクトの配列になります。

于 2012-10-24T18:18:40.897 に答える
2

ウィンドウ全体よりも小さいキャンバスがあり、クリックの世界座標を決定する必要がありました。

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

これが例です。スライドの前後でドーナツの同じ領域をクリックすると、座標が一定のままであることがわかります(ブラウザコンソールを確認してください)。

// 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>

于 2019-06-02T15:52:48.197 に答える
1

ThreeJSは、Projector。(Un)ProjectVectorから徐々に離れていき、projector.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;
}
于 2014-10-29T15:23:44.307 に答える
0

これが、それからes6クラスを作成する際の私の見解です。Three.jsr83での作業。rayCasterを使用する方法は、ここでmrdoobから取得されます:Three.jsProjectorおよび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)
      }
    }

シーン内のすべてのオブジェクトに対してヒットをチェックする場合は、このように使用します。上記の再帰フラグをfalseにしたのは、私の使用では再帰フラグを必要としなかったためです。

var helper = new RaycasterHelper(camera, scene)
var intersects = helper.getIntersectsFromTap(tapX, tapY, 
this.scene.children)
...
于 2017-04-12T16:58:15.987 に答える
0

提供された回答は一部のシナリオで役立つ場合がありますが、それらのシナリオ(ゲームやアニメーションなど)はまったく正確ではないため(ターゲットのNDC zを推測しますか?)、ほとんど想像できません。ターゲットのz平面がわかっている場合は、これらのメソッドを使用して画面座標をワールド座標に投影解除することはできません。ただし、ほとんどのシナリオでは、この平面を知っている必要があります。

たとえば、球を中心(モデル空間の既知の点)と半径で描画する場合(投影されていないマウス座標のデルタとして半径を取得する必要があります)、できません。敬意を払って、targetZを使用した@WestLangleyのメソッドは機能せず、誤った結果が得られます(必要に応じてjsfiddleを提供できます)。別の例-マウスのダブルクリックでオービットコントロールのターゲットを設定する必要がありますが、シーンオブジェクトを使用した「実際の」レイキャストはありません(選択するものがない場合)。

私にとっての解決策は、z軸に沿ったターゲットポイントに仮想平面を作成し、後でこの平面でレイキャスティングを使用することです。ターゲットポイントは、既存のモデル空間などで段階的に描画する必要があるオブジェクトの現在の軌道コントロールターゲットまたは頂点にすることができます。これは完全に機能し、単純です(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; 
}
于 2018-04-15T21:44:53.907 に答える
0

(別名r3fとreact-three-fiber)を使用している人にとって@react-three/fiber、私はこの議論と、MattRossmanによる関連するコードサンプルが役に立ちました。特に、上記のメソッドを使用した多くの例は、OrbitControlsが機能している場合ではなく、単純な正投影ビュー用です。

ディスカッション:https ://github.com/pmndrs/react-three-fiber/discussions/857

マットの手法を使用した簡単な例:https ://codesandbox.io/s/r3f-mouse-to-world-elh73?file = / src / index.js

より一般化可能な例:https ://codesandbox.io/s/react-three-draggable-cxu37?file = / src / App.js

于 2021-12-24T17:54:30.723 に答える