You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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>

关键修改说明

  1. 切线向量计算:通过finish.clone().sub(conePos).normalize()获取从圆锥位置指向终点的单位向量,确保方向精准匹配曲线末端走向。
  2. 圆锥初始朝向修正:默认ConeGeometry的尖端沿Y轴正方向,通过cone.rotateX(Math.PI / 2)将其转为沿Z轴,配合lookAt()方法可正确指向切线方向。
  3. 位置优化:选择points[99](倒数第二个点)作为圆锥位置,避免圆锥完全超出曲线末端,视觉上更自然。

内容的提问来源于stack exchange,提问作者Bilal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 15:55:36