簡體   English   中英

具有 lookAt() 功能的 THREE.js 粗箭頭

[英]THREE.js Thick Arrow with lookAt() capability

我想制作一個“粗箭頭”網格,即像標准箭頭助手一樣的箭頭,但軸由cylinder而不是line制成。


tldr; 不要復制 Arrow Helper 的設計; 請參閱問題末尾的結語部分。


因此,我根據需要復制並修改了代碼(無需構造函數和方法)並進行了更改,現在它可以正常工作了:-

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

//... 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() function 是不夠的,因為箭頭指向其 Object3D y 軸,而 lookAt() 將 Object3D z 軸定向以查看給定目標 position。

通過實驗,我已經通過使用:-

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

在軸和頭部的幾何形狀上(2 行在上面的代碼摘錄中被注釋掉了)。 這似乎使圓柱體網格指向正確的方向。 但問題是網格形狀錯誤,頭部網格偏離軸網格。

通過反復試驗,我可能能夠調整代碼以使箭頭適用於我目前的示例。 但是(考慮到我對四元數的薄弱理解)我不相信它會 (a) 足夠通用以適用於所有情況,或者 (b) 足以應對 THREE.js 的演變。

因此,我將不勝感激有關如何實現此“粗箭頭”的 lookAt() 功能的任何解決方案/建議。

結語

我的主要觀點是不要遵循 Helper Arrow 的設計。

正如 TheJim01 和其他人的回答所表明的那樣,使用 Object3D.add()“嵌套”function 有一種更簡單的方法。

例如:-

(1) 創建兩個圓柱體網格(用於箭頭軸和箭頭),默認指向 Y 方向; 使幾何長度 = 1.0 以幫助將來重新縮放。

(2) 將網格添加到父 Object3D object。

(3) 使用parent.rotateX(Math.PI/2)將父級繞 X 軸旋轉 +90 度。

(4) 將父級添加到祖父級 object。

(5) 隨后使用grandparent.lookAt(target_point_as_World_position_Vec3_or_x_y_z)

如果父母或祖父母的縮放比例不是(n,n,n) NB lookAt() 將無法正常工作。

父和祖父 object 類型可以是普通THREE.Object3DTHREE.GroupTHREE.Mesh (如果需要,例如通過設置小尺寸或.visibility=false使其不可見)

Arrow Helper 可以動態使用,但前提是在使用 lookAt() 之前將內部方向設置為 (0,0,1)。

您可以將lookAt應用於任何Object3D Object3D.lookAt(... )

您已經發現, lookAt導致形狀指向+Z方向,並且正在對此進行補償。 但是隨着Group的引入,它可以更進一步。 Group也派生自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(...); .

進一步的工作

這只是一個起點。 您需要調整它以適應您希望它如何使用正確的 position 構造箭頭,具有正確的長度等。不過,分組模式應該使lookAt更容易使用。

您所需要的只是對嵌套有更多的了解,這使您可以相對於其父對象放置對象。 如上面的答案所述,您可以使用GroupObject3D ,但您不必這樣做。 您可以將箭頭嵌套在圓柱體上並將圓柱體指向 z 方向,然后使用內置的、不要過於復雜的方法lookAt

盡量不要將矩陣或四元數用於此類簡單的事情,因為這會使事情變得更加困難。 由於 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>

您可以使用鼠標四處滾動(如果它有水平滾動,應該在觸控板上)並單擊以指向箭頭。 我還添加了一些跟蹤點,因此您可以看到 `lookAt'確實可以工作而不會使其過於復雜,那就是指向您單擊包裹球體的點。

有了這個,我肯定經常輸入shaft這個詞。 它開始聽起來很奇怪。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM