基于ThreeJs优化箭头圆锥体旋转效果
优化Three.js贝塞尔曲线上箭头圆锥体的旋转效果
我使用Three.js的
QuadraticBezierCurve3和ConeGeometry在拾取点与放置点之间绘制箭头,但圆锥体的旋转效果不理想,具体代码如下,恳请告知如何优化箭头函数的可视化效果(圆锥体旋转)。
原代码片段
var camera, scene, renderer; init(); animate(); /** draw arrow */ function drawArrow(pick_pos, place_pos, scene) { /** * Curve */ const start = new THREE.Vector3(); start.add(pick_pos) const finish = new THREE.Vector3(); finish.add(place_pos) let mid = new THREE.Vector3(); mid.add(start); let dist = finish.x + mid.x; mid.x = dist/2; mid.y += 3; const b_curve = new THREE.QuadraticBezierCurve3( start, mid, finish ); const points = b_curve.getPoints( 100 ); const line_geometry = new THREE.BufferGeometry().setFromPoints( points ); const material = new THREE.LineBasicMaterial( { color: 0x0ffffb, linewidth:5 } ); const curve = new THREE.Line( line_geometry, material ); /** * Cone */ const cone_geometry = new THREE.ConeGeometry( 0.2, 1, 8 ); const cone_material = new THREE.MeshBasicMaterial({ color: 0xff00fb }); const cone = new THREE.Mesh( cone_geometry, cone_material ); cone.position.set(points[98].x, points[98].y, points[98].z); cone.rotateX(Math.PI * points[100].x); cone.rotateZ(Math.PI * points[100].z); /** * Arrow */ const arrow = new THREE.Group(); arrow.add(curve); arrow.add(cone); arrow.name = 'arrow'; scene.add(arrow); } /** Create the scene, camera, renderer */ function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x21252d); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000); camera.position.x = 0; camera.position.y = 8; camera.position.z = 10; scene.add(camera); controls = new THREE.OrbitControls(camera, renderer.domElement); const pick = new THREE.Vector3(0, 0, 0); const place = new THREE.Vector3(5, 0, 5); drawArrow(pick, place, scene); window.addEventListener('resize', onWindowResize); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); render(); } function render() { renderer.render(scene, camera); }
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.min.js"></script>
问题分析
原代码中直接用曲线上点的坐标值计算旋转角度的方式完全错误,无法匹配曲线末端的切线方向,导致圆锥朝向完全偏离预期。正确思路是计算曲线末端的切线向量,让圆锥尖端沿切线指向终点。
优化后的完整代码
var camera, scene, renderer; init(); animate(); /** draw arrow */ function drawArrow(pick_pos, place_pos, scene) { /** * Curve */ const start = new THREE.Vector3(); start.add(pick_pos) const finish = new THREE.Vector3(); finish.add(place_pos) let mid = new THREE.Vector3(); mid.add(start); let dist = finish.x + mid.x; mid.x = dist/2; mid.y += 3; const b_curve = new THREE.QuadraticBezierCurve3( start, mid, finish ); const points = b_curve.getPoints(100); const line_geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x0ffffb, linewidth:5 }); const curve = new THREE.Line(line_geometry, material); /** * Cone */ const cone_geometry = new THREE.ConeGeometry(0.2, 1, 8); const cone_material = new THREE.MeshBasicMaterial({ color: 0xff00fb }); const cone = new THREE.Mesh(cone_geometry, cone_material); // 设置圆锥位置到曲线末端附近(避免完全超出曲线) const conePos = points[99]; cone.position.set(conePos.x, conePos.y, conePos.z); // 计算曲线末端的切线方向:从圆锥位置指向终点的单位向量 const direction = finish.clone().sub(conePos).normalize(); // 调整圆锥初始朝向:默认圆锥沿Y轴向上,先旋转90度让尖端沿Z轴,再指向切线方向 cone.rotateX(Math.PI / 2); cone.lookAt(conePos.clone().add(direction)); /** * Arrow */ const arrow = new THREE.Group(); arrow.add(curve); arrow.add(cone); arrow.name = 'arrow'; scene.add(arrow); } /** Create the scene, camera, renderer */ function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x21252d); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000); camera.position.x = 0; camera.position.y = 8; camera.position.z = 10; scene.add(camera); controls = new THREE.OrbitControls(camera, renderer.domElement); const pick = new THREE.Vector3(0, 0, 0); const place = new THREE.Vector3(5, 0, 5); drawArrow(pick, place, scene); window.addEventListener('resize', onWindowResize); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); render(); } function render() { renderer.render(scene, camera); }
<script src="https://cdn.jsdelivr.net/npm/three@0.122.0/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.122.0/examples/js/controls/OrbitControls.min.js"></script>
关键修改说明
- 切线向量计算:通过
finish.clone().sub(conePos).normalize()获取从圆锥位置指向终点的单位向量,确保方向精准匹配曲线末端走向。 - 圆锥初始朝向修正:默认
ConeGeometry的尖端沿Y轴正方向,通过cone.rotateX(Math.PI / 2)将其转为沿Z轴,配合lookAt()方法可正确指向切线方向。 - 位置优化:选择
points[99](倒数第二个点)作为圆锥位置,避免圆锥完全超出曲线末端,视觉上更自然。
内容的提问来源于stack exchange,提问作者Bilal
相关产品推荐
相关产品推荐

