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

ThreeJS中如何实现两条运行时动态生成的直线重合

需求说明
  • 场景包含两条运行时动态创建的直线,位置为动态传入值
  • 需要实现触发操作后,其中一条直线与另一条直线完全重合的效果
  • 效果参考:
    直线重合效果参考
  • 现有逻辑绑定Q键触发重合方法,但运行后无法达到预期效果
原有问题代码
var camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

camera.position.z = 15;

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("app").appendChild(renderer.domElement);

let point1 = new THREE.Vector3(14, 25, -159);
let point2 = new THREE.Vector3(-5, 2, 65);
let rightLine = createLine(point1, point2);

let point1LineTwo = new THREE.Vector3(-45, 11, -4);
let point2LineTwo = new THREE.Vector3(-26, -8, -30);
let leftLine = createLine(point1LineTwo, point2LineTwo);

function createLine(point1, point2) {
  
  const linePoints = [];

  linePoints.push(new THREE.Vector3(point1.x, point1.y, point1.z));
  linePoints.push(new THREE.Vector3(point2.x, point2.y, point2.z));
  let lineGeometry = new THREE.BufferGeometry().setFromPoints(linePoints);

  var lineMaterial = new THREE.LineBasicMaterial({
    color: 0xff5555,
    linewidth: 2,
  });

  let line = new THREE.Line(lineGeometry, lineMaterial);

  scene.add(line);

  return line;
}

function makeCoincident() {
  let rightLineVector = new THREE.Vector3();
  const positions = rightLine.geometry.attributes.position.array;
  rightLineVector.x = positions[3] - positions[0];
  rightLineVector.y = positions[4] - positions[1];
  rightLineVector.z = positions[5] - positions[2];

  let leftLineVector = new THREE.Vector3();

  const lineLeftPosition = leftLine.geometry.attributes.position.array;
  leftLineVector.x = lineLeftPosition[3] - lineLeftPosition[0];
  leftLineVector.y = lineLeftPosition[4] - lineLeftPosition[1];
  leftLineVector.z = lineLeftPosition[5] - lineLeftPosition[2];

  //Calulate angle Between leftLineVector and rightLineVector
  let angle = leftLineVector.clone().angleTo(rightLineVector);

  //calculate cross prduct of lineOneVector and lineTwoVector
  let crossPoductVector = new THREE.Vector3();
  crossPoductVector.crossVectors(leftLineVector, rightLineVector);
  crossPoductVector.normalize();

  rightLineVector.applyAxisAngle(crossPoductVector.clone(), angle);

  //align  right line on left line
  var axis = new THREE.Vector3(0, 1, 0);
  rightLine.quaternion.setFromUnitVectors(
    axis,
    rightLineVector.clone().normalize()
  );
}

window.addEventListener("keydown", function (event) {
  switch (event.keyCode) {
    case 81: // Q
      makeCoincident();
      break;

    default:
  }
});
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();
原有逻辑错误点
  • 仅计算了方向旋转,没有做位置对齐,即使方向平行,两条直线位置不同也无法重合
  • 旋转逻辑错误:使用固定(0,1,0)轴作为旋转参考,没有基于直线本身的初始方向计算旋转差
  • 旋转时未固定旋转锚点:直线几何体的点未对齐到局部原点,旋转时会绕世界坐标原点偏移,导致直线位置错乱
  • 未处理方向反向、长度匹配的边界情况
修正后实现方案

实现逻辑分为三步:

  1. 提取两条直线的端点、方向向量、长度信息,重置待移动直线的变换状态避免累计误差
  2. 计算旋转四元数,将待移动直线的方向对齐到目标直线方向
  3. 平移待移动直线,将其锚点(第一个端点)对齐到目标直线的对应端点,可选缩放长度实现完全重合

修正后的makeCoincident方法代码:

function makeCoincident() {
  // 提取右线(待移动线)的端点、方向、长度
  const rightPosArr = rightLine.geometry.attributes.position.array;
  const rStart = new THREE.Vector3(rightPosArr[0], rightPosArr[1], rightPosArr[2]);
  const rEnd = new THREE.Vector3(rightPosArr[3], rightPosArr[4], rightPosArr[5]);
  const rDir = rEnd.clone().sub(rStart).normalize();
  const rLen = rStart.distanceTo(rEnd);

  // 提取左线(目标线)的端点、方向、长度
  const leftPosArr = leftLine.geometry.attributes.position.array;
  const lStart = new THREE.Vector3(leftPosArr[0], leftPosArr[1], leftPosArr[2]);
  const lEnd = new THREE.Vector3(leftPosArr[3], leftPosArr[4], leftPosArr[5]);
  const lDir = lEnd.clone().sub(lStart).normalize();
  const lLen = lStart.distanceTo(lEnd);

  // 1. 重置右线变换,清除之前的旋转/平移/缩放
  rightLine.quaternion.identity();
  rightLine.position.set(0, 0, 0);
  rightLine.scale.set(1, 1, 1);

  // 2. 平移几何体,让右线起点对齐到局部原点(作为旋转/缩放锚点)
  rightLine.geometry.translate(-rStart.x, -rStart.y, -rStart.z);

  // 3. 计算旋转四元数,对齐方向
  const rotateQuat = new THREE.Quaternion().setFromUnitVectors(rDir, lDir);
  rightLine.quaternion.copy(rotateQuat);

  // 4. (可选)缩放长度匹配目标线,不需要等长重合可删除该段
  const scaleRatio = lLen / rLen;
  rightLine.scale.set(scaleRatio, scaleRatio, scaleRatio);

  // 5. 移动右线位置,让起点和左线起点重合
  rightLine.position.copy(lStart);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 03:03:49