lookAt()機能を備えたTHREE.js太い矢印

Dec 03 2020

「太い矢印」メッシュ、つまり標準のArrow Helperのような矢印を作成したかったのですが、シャフトはのcylinder代わりに作成されましたline


tldr; ArrowHelperのデザインをコピーしないでください。質問の最後にあるエピローグのセクションを参照してください。


そのため、必要に応じてコードをコピーして変更し(コンストラクターとメソッドは不要)、変更を加えたところ、正常に機能するようになりました:-

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =  
//= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

//... START of ARROWMAKER SET of FUNCTIONS

// adapted from https://github.com/mrdoob/three.js/blob/master/src/helpers/ArrowHelper.js
        
    //====================================
    function F_Arrow_Fat_noDoesLookAt_Make ( dir, origin, length,  shaftBaseWidth, shaftTopWidth, color, headLength, headBaseWidth, headTopWidth ) 
    {

        //... dir is assumed to be normalized
        
        var thisArrow = new THREE.Object3D();////SW

        if ( dir            === undefined ) dir             = new THREE.Vector3( 0, 0, 1 );
        if ( origin         === undefined ) origin          = new THREE.Vector3( 0, 0, 0 );
        if ( length         === undefined ) length          = 1;
        if ( shaftBaseWidth     === undefined ) shaftBaseWidth  = 0.02 * length;
        if ( shaftTopWidth  === undefined ) shaftTopWidth   = 0.02 * length;
        if ( color          === undefined ) color           = 0xffff00;
        if ( headLength     === undefined ) headLength      = 0.2 * length;
        if ( headBaseWidth  === undefined ) headBaseWidth   = 0.4 * headLength;
        if ( headTopWidth   === undefined ) headTopWidth    = 0.2 * headLength;//... 0.0 for a point.


        
        /* CylinderBufferGeometry parameters from:-
        // https://threejs.org/docs/index.html#api/en/geometries/CylinderBufferGeometry
            * radiusTop — Radius of the cylinder at the top. Default is 1.
            * radiusBottom — Radius of the cylinder at the bottom. Default is 1.
            * height — Height of the cylinder. Default is 1.
            * radialSegments — Number of segmented faces around the circumference of the cylinder. Default is 8
            * heightSegments — Number of rows of faces along the height of the cylinder. Default is 1.
            * openEnded — A Boolean indicating whether the ends of the cylinder are open or capped. Default is false, meaning capped.
            * thetaStart — Start angle for first segment, default = 0 (three o'clock position).
            * thetaLength — The central angle, often called theta, of the circular sector. The default is 2*Pi, which makes for a complete cylinder.        
        */
        //var shaftGeometry  = new THREE.CylinderBufferGeometry( 0.0, 0.5,    1, 8, 1 );//for strongly tapering, pointed shaft
          var shaftGeometry  = new THREE.CylinderBufferGeometry( 0.1, 0.1,    1, 8, 1 );//shaft is cylindrical
        //shaftGeometry.translate( 0, - 0.5, 0 );
        shaftGeometry.translate( 0, + 0.5, 0 );
    
    //... for partial doesLookAt capability
    //shaftGeometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
    
        var headGeometry = new THREE.CylinderBufferGeometry( 0, 0.5, 1, 5, 1 ); //for strongly tapering, pointed head
        headGeometry.translate( 0, - 0.5, 0 );
    
    //... for partial doesLookAt capability
    //headGeometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
            
        thisArrow.position.copy( origin );

        /*thisArrow.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) );
        thisArrow.line.matrixAutoUpdate = false;
        thisArrow.add( thisArrow.line ); */

        thisArrow.shaft = new THREE.Mesh( shaftGeometry, new THREE.MeshLambertMaterial( { color: color  } ) );
        thisArrow.shaft.matrixAutoUpdate = false;
        thisArrow.add( thisArrow.shaft );
        
        thisArrow.head = new THREE.Mesh( headGeometry, new THREE.MeshLambertMaterial( { color: color } ) );
        thisArrow.head.matrixAutoUpdate = false;
        thisArrow.add( thisArrow.head );

        //thisArrow.setDirection( dir );
        //thisArrow.setLength( length, headLength, headTopWidth );
        
        var arkle = new THREE.AxesHelper (2 * length);
        thisArrow.add (arkle);
                
        F_Arrow_Fat_noDoesLookAt_setDirection( thisArrow, dir                               ) ;////SW
        F_Arrow_Fat_noDoesLookAt_setLength   ( thisArrow, length, headLength, headBaseWidth ) ;////SW
        F_Arrow_Fat_noDoesLookAt_setColor    ( thisArrow, color                             ) ;////SW
                
        scene.add ( thisArrow );
        
        //... this screws up for the F_Arrow_Fat_noDoesLookAt  kind of Arrow
        //thisArrow.lookAt(0,0,0);//...makes the arrow's blue Z axis lookAt Point(x,y,z).
    }
    //... EOFn F_Arrow_Fat_noDoesLookAt_Make().
    

    //=============================================
    function F_Arrow_Fat_noDoesLookAt_setDirection( thisArrow, dir ) 
    {
        // dir is assumed to be normalized
        if ( dir.y > 0.99999 ) 
        {
            thisArrow.quaternion.set( 0, 0, 0, 1 );

        } else if ( dir.y < - 0.99999 ) 
        {
            thisArrow.quaternion.set( 1, 0, 0, 0 );

        } else 
        {
            const _axis = /*@__PURE__*/ new THREE.Vector3();
            
            _axis.set( dir.z, 0, - dir.x ).normalize();

            const radians = Math.acos( dir.y );

            thisArrow.quaternion.setFromAxisAngle( _axis, radians );
        }
    }
    //... EOFn F_Arrow_Fat_noDoesLookAt_setDirection().


    //========================================= 
    function F_Arrow_Fat_noDoesLookAt_setLength( thisArrow, length, headLength, headBaseWidth ) 
    {
        if ( headLength     === undefined ) headLength      = 0.2 * length;
        if ( headBaseWidth  === undefined ) headBaseWidth   = 0.2 * headLength;

        thisArrow.shaft.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458
                                                                                  //x&z the same, y as per length-headLength
    //thisArrow.shaft.position.y = length;//SW ???????
        thisArrow.shaft.updateMatrix();

        thisArrow.head.scale.set( headBaseWidth, headLength, headBaseWidth ); //x&z the same, y as per length
        
        thisArrow.head.position.y = length;
        thisArrow.head.updateMatrix();
    }
    //...EOFn  F_Arrow_Fat_noDoesLookAt_setLength().

    //======================================== 
    function F_Arrow_Fat_noDoesLookAt_setColor( thisArrow, color ) 
    {
        thisArrow.shaft.material.color.set( color );
        thisArrow.head.material.color.set( color );
    }
    //...EOFn  F_Arrow_Fat_noDoesLookAt_setColor().
        
//... END of ARROWMAKER SET of FUNCTIONS
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =  
//= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

これは、構築時に矢印の方向を指定できる固定方向の矢印に対しては問題なく機能します。

しかし今、私は時間の経過とともに矢印の方向を変更する必要があります(移動するターゲットを追跡するため)。現在、Object3D.lookAt()関数は、矢印がObject3Dのy軸に沿っているため十分ではありませんが、lookAt()は、Object3Dのz軸を指定されたターゲット位置に向けます。

実験で、私は以下を使用して途中まで到達しました:-

geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );

シャフトとヘッドの形状(上記のコード抽出では2行がコメントアウトされています)。これにより、円柱メッシュが正しい方向を向くようになります。しかし、問題は、メッシュの形状が正しくなく、ヘッドメッシュがシャフトメッシュからずれていることです。

試行錯誤を繰り返すことで、現在の例で矢印が機能するようにコードを調整できる可能性があります。しかし(クォータニオンについての私の理解が弱いことを考えると)私は、それが(a)すべての状況に適用するのに十分一般的である、または(b)THREE.jsの進化に対して十分に将来性があるとは確信していません。

したがって、この「太い矢印」のlookAt()機能を実現する方法に関する解決策/推奨事項に感謝します。

エピローグ

私の主なポイントは、ヘルパーアローのデザインに従わないことです。

TheJim01とsomethinghereの回答が示すように、Object3D.add()の「ネスト」関数を使用するより簡単なアプローチがあります。

例えば:-

(1)デフォルトでY方向を指す2つのシリンダーメッシュ(アローシャフトとアローヘッド用)を作成します。将来の再スケーリングを支援するために、ジオメトリの長さ= 1.0にします。

(2)メッシュを親Object3Dオブジェクトに追加します。

(3)を使用して、親をX軸を中心に+90度回転しparent.rotateX(Math.PI/2)ます。

(4)親を祖父母オブジェクトに追加します。

(5)その後使用しますgrandparent.lookAt(target_point_as_World_position_Vec3_or_x_y_z)

親または祖父母のスケーリングが(n,n,n)。以外の場合、NB lookAt()は正しく機能しません。

親と祖父母オブジェクトの種類はプレーンでもよいTHREE.Object3D、またはTHREE.Group、又はTHREE.Mesh(小さな寸法を設定するかによって必要な場合、例えば不可視製.visibility=false

Arrow Helperは動的に使用できますが、lookAt()を使用する前に内部方向が(0,0,1)に設定されている場合に限ります。

回答

2 TheJim01 Dec 03 2020 at 22:07

どなたlookAtでもお申し込みいただけますObject3D。Object3D.lookAt( ... )

lookAt形状がその+Z方向を向く原因となることをすでに発見しており、それを補っています。ただし、の導入により、さらに一歩進めることができますGroup。Groupsもから派生しObject3Dているため、lookAtメソッドもサポートしています。

let W = window.innerWidth;
let H = window.innerHeight;

const renderer = new THREE.WebGLRenderer({
  antialias: true,
  alpha: true
});
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(10, 10, 50);
camera.lookAt(scene.position);
scene.add(camera);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);

const group = new THREE.Group();
scene.add(group);

const arrowMat = new THREE.MeshLambertMaterial({color:"green"});

const arrowGeo = new THREE.ConeBufferGeometry(2, 5, 32);
const arrowMesh = new THREE.Mesh(arrowGeo, arrowMat);
arrowMesh.rotation.x = Math.PI / 2;
arrowMesh.position.z = 2.5;
group.add(arrowMesh);

const cylinderGeo = new THREE.CylinderBufferGeometry(1, 1, 5, 32);
const cylinderMesh = new THREE.Mesh(cylinderGeo, arrowMat);
cylinderMesh.rotation.x = Math.PI / 2;
cylinderMesh.position.z = -2.5;
group.add(cylinderMesh);

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

function resize() {
  W = window.innerWidth;
  H = window.innerHeight;
  renderer.setSize(W, H);
  camera.aspect = W / H;
  camera.updateProjectionMatrix();
  render();
}

window.addEventListener("resize", resize);

resize();

let rad = 0;

function animate() {
  rad += 0.05;
  group.lookAt(Math.sin(rad) * 100, Math.cos(rad) * 100, 100);
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow: hidden;
  background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>

ここで重要なのは、コーン/シャフトがその+Z方向を指すように作成されてから、に追加されることGroupです。これは、それらの方向がグループに対してローカルになったことを意味します。グループがlookAt変わると、形はそれに追随します。また、「矢印」の形はグループのローカル+Z方向を指しているため、に指定された位置を指していることも意味しgroup.lookAt(...);ます。

今後の作業

これは出発点にすぎません。これを、正しい位置、正しい長さなどで矢印を作成する方法に適合させる必要があります。それでも、グループ化パターンを使用lookAtすると、作業が簡単になります。

1 somethinghere Dec 04 2020 at 00:37

必要なのは、ネストについてもう少し理解することです。これにより、オブジェクトをその親に対して相対的に配置できます。上記の回答で述べたように、またはを使用できますが、使用する必要はありません。円柱に矢じりを入れ子にして、円柱をz方向に向けてから、組み込みの複雑なものを使用しない方法を使用できます。GroupObject3DlookAt

このような単純なものには行列やクォータニオンを使用しないようにしてください。物事を理解するのが非常に難しくなります。THREE.jsはネストされたフレームを許可するので、それを利用してください!

const renderer = new THREE.WebGLRenderer;
const camera = new THREE.PerspectiveCamera;
const scene = new THREE.Scene;
const mouse = new THREE.Vector2;
const raycaster = new THREE.Raycaster;
const quaternion = new THREE.Quaternion;
const sphere = new THREE.Mesh(
    new THREE.SphereGeometry( 10, 10, 10 ),
    new THREE.MeshBasicMaterial({ transparent: true, opacity: .1 })
);
const arrow = new THREE.Group;
const arrowShaft = new THREE.Mesh(
    // We want to ensure our arrow is completely offset into one direction
    // So the translation ensure every bit of it is in Y+
    new THREE.CylinderGeometry( .1, .3, 3 ).translate( 0, 1.5, 0 ),
    new THREE.MeshBasicMaterial({ color: 'blue' })
);
const arrowPoint = new THREE.Mesh(
    // Same thing, translate to all vertices or +Y
    new THREE.ConeGeometry( 1, 2, 10 ).translate( 0, 1, 0 ),
    new THREE.MeshBasicMaterial({ color: 'red' })
);
const trackerPoint = new THREE.Mesh(
  new THREE.SphereGeometry( .2 ),
  new THREE.MeshBasicMaterial({ color: 'green' })
);
const clickerPoint = new THREE.Mesh(
  trackerPoint.geometry,
  new THREE.MeshBasicMaterial({ color: 'yellow' })
);

camera.position.set( 10, 10, 10 );
camera.lookAt( scene.position );

// Place the point at the top of the shaft
arrowPoint.position.y = 3;
// Point the shaft into the z-direction
arrowShaft.rotation.x = Math.PI / 2;

// Attach the point to the shaft
arrowShaft.add( arrowPoint );
// Add the shaft to the global arrow group
arrow.add( arrowShaft );
// Add the arrow to the scene
scene.add( arrow );
scene.add( sphere );
scene.add( trackerPoint );
scene.add( clickerPoint );

renderer.domElement.addEventListener( 'mousemove', mouseMove );
renderer.domElement.addEventListener( 'click', mouseClick );
renderer.domElement.addEventListener( 'wheel', mouseWheel );

render();

document.body.appendChild( renderer.domElement );

function render(){

    renderer.setSize( innerWidth, innerHeight );
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.render( scene, camera );
    
}
function mouseMove( event ){

    mouse.set(
        event.clientX / event.target.clientWidth * 2 - 1,
        -event.clientY / event.target.clientHeight * 2 + 1
    );

    raycaster.setFromCamera( mouse, camera );
    
    const hit = raycaster.intersectObject( sphere ).shift();

    if( hit ){

      trackerPoint.position.copy( hit.point );
      render();
       
    }
    
    document.body.classList.toggle( 'tracking', !!hit );

}
function mouseClick( event ){
  
    clickerPoint.position.copy( trackerPoint.position );
    arrow.lookAt( trackerPoint.position );
    render();
    
}
function mouseWheel( event ){

    const angle = Math.PI * event.wheelDeltaX / innerWidth;
    
    camera.position.applyQuaternion(
      quaternion.setFromAxisAngle( scene.up, angle )
    );
    camera.lookAt( scene.position );
    render();
    
}
body { padding: 0; margin: 0; }
body.tracking { cursor: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r123/three.min.js"></script>

マウスを使用して回転し(水平スクロールがある場合はトラックパッド上にあるはずです)、クリックして矢印をポイントします。私はまた、あなたが`ルックアット」のことを見ることができるように、いくつかのトラッキングポイントを追加、それを過度に複雑せずに仕事をされており、そのされますが、折り返しの球をクリックした地点を指し。

そしてそれで、私は間違いなくshaftあまりにも頻繁に単語を入力しました。それは奇妙に聞こえ始めています。