如何在新直线上创建与原平面-直线夹角一致的同朝向平面
问题背景
- 存在支持用户自由旋转调整朝向的平面网格,有一条固定穿过该平面中心的基准直线,平面旋转完成后,其法向量与该基准直线形成固定夹角
- 另有一条位置与原基准直线完全独立、位置不可修改的新直线,需要在新直线上创建新平面
- 要求新平面朝向逻辑与原平面一致,且新平面法向量与新直线的夹角,和原平面法向量与原基准直线的夹角完全相等
- 直接克隆原平面放置到新直线上无法满足夹角一致的要求
- 调试环境中按下A键即可触发新平面创建操作
实现方案
核心原理
直接克隆原平面复用其世界旋转属性无法满足要求,本质原因是原平面的法向量朝向是相对于原基准直线的参考系生成的,新直线与原基准直线空间方向、位置均独立,直接套用原世界旋转会丢失相对参考关系,自然无法保证夹角一致。
核心解决逻辑是提取原平面相对于自身基准直线的固定旋转偏移,将该偏移关系映射到新直线的参考坐标系下,完全保留相对角度关系,不需要修改两条直线的任何位置参数。
具体步骤
- 第一步:提取原参考系下的固定相对旋转
归一化原基准直线的方向向量记为oldLineDir,归一化原平面的世界空间法向量记为oldNormal,计算从oldLineDir旋转到oldNormal的四元数,记为relativeQuat。这个四元数代表原平面相对于自身基准直线的角度偏移,是不受空间位置、直线绝对方向影响的固定关系,也是需要保留的核心朝向逻辑。 - 第二步:计算参考系对齐旋转
归一化新直线的方向向量记为newLineDir,计算从原基准直线方向oldLineDir旋转到新直线方向newLineDir的四元数,记为alignQuat,这一步是把原参考系的方向基准对齐到新直线上。 - 第三步:生成新平面的最终姿态
按照alignQuat * relativeQuat的四元数乘法顺序计算得到新平面的最终世界旋转四元数(注意四元数乘法顺序不可调换),将新平面放置到新直线上的目标点位,赋值计算得到的旋转即可。 - 校验:两个单位向量的点积等于向量夹角的余弦值,计算新平面法向量与新直线方向的点积、原平面法向量与原基准直线方向的点积,两个值完全相等即证明夹角匹配符合要求。
注意:所有向量计算前必须做归一化处理,否则会出现旋转计算偏差、角度不准的问题。
参考实现代码(基于Three.js)
/** * 生成与原平面相对直线夹角一致的新平面 * @param {THREE.Mesh} oldPlane 原平面网格 * @param {THREE.Line3} oldLine 穿过原平面中心的原基准直线 * @param {THREE.Line3} newLine 待放置平面的新直线 * @param {THREE.Vector3} newPlanePos 新平面在新直线上的放置位置 * @returns {THREE.Mesh} 符合要求的新平面 */ function createAlignedPlane(oldPlane, oldLine, newLine, newPlanePos) { // 预创建临时计算对象,避免重复实例化 const oldLineDir = new THREE.Vector3(); const oldNormal = new THREE.Vector3(); const newLineDir = new THREE.Vector3(); const relativeRot = new THREE.Quaternion(); const alignRot = new THREE.Quaternion(); const finalRot = new THREE.Quaternion(); // 获取归一化的原基准直线方向、原平面法向量 oldLine.direction(oldLineDir).normalize(); oldPlane.getWorldDirection(oldNormal).normalize(); // 计算原平面相对于原基准直线的固定旋转偏移 relativeRot.setFromUnitVectors(oldLineDir, oldNormal); // 获取归一化的新直线方向,计算参考系对齐旋转 newLine.direction(newLineDir).normalize(); alignRot.setFromUnitVectors(oldLineDir, newLineDir); // 组合得到新平面最终旋转 finalRot.copy(alignRot).multiply(relativeRot); // 克隆原平面的几何、材质,重新赋值位置和旋转 const newPlane = oldPlane.clone(); newPlane.position.copy(newPlanePos); newPlane.quaternion.copy(finalRot); return newPlane; }
内容的提问来源于stack exchange,提问作者Yashwant Raut
相关产品推荐
相关产品推荐

