[英]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.Object3D
或THREE.Group
或THREE.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
更容易使用。
您所需要的只是对嵌套有更多的了解,这使您可以相对于其父对象放置对象。 如上面的答案所述,您可以使用Group
或Object3D
,但您不必这样做。 您可以将箭头嵌套在圆柱体上并将圆柱体指向 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.