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)轴作为旋转参考,没有基于直线本身的初始方向计算旋转差 - 旋转时未固定旋转锚点:直线几何体的点未对齐到局部原点,旋转时会绕世界坐标原点偏移,导致直线位置错乱
- 未处理方向反向、长度匹配的边界情况
修正后实现方案
实现逻辑分为三步:
- 提取两条直线的端点、方向向量、长度信息,重置待移动直线的变换状态避免累计误差
- 计算旋转四元数,将待移动直线的方向对齐到目标直线方向
- 平移待移动直线,将其锚点(第一个端点)对齐到目标直线的对应端点,可选缩放长度实现完全重合
修正后的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
相关产品推荐
相关产品推荐

