09 · 三维叉积:垂直方向、法线与力矩
1. 结果为什么是一根新向量
三维中,两条不平行的方向确定一个平面。叉积构造垂直于该平面的方向,其长度同时编码面积:
结果同时垂直于 a、b,可以代入点积公式验证。方向由右手定则决定:右手四指从 a 转向 b,拇指指向结果。交换顺序得到相反向量。
import * as THREE from 'three';
const x = new THREE.Vector3(1, 0, 0);
const y = new THREE.Vector3(0, 1, 0);
const z = new THREE.Vector3().crossVectors(x, y);
console.log(z.toArray()); // [0, 0, 1]
console.log(z.dot(x), z.dot(y)); // 0, 0
crossVectors(a,b) 把结果写进接收对象;a.cross(b) 则覆盖 a。结果未必是单位向量,通常要根据用途决定是否归一化。
2. 三角形法线:表面朝向来自顶点顺序
给定三个位置 A、B、C,必须先相减得到从同一起点出发的两条边:
const edgeAB = new THREE.Vector3();
const edgeAC = new THREE.Vector3();
function triangleNormal(a, b, c, out) {
edgeAB.subVectors(b, a);
edgeAC.subVectors(c, a);
out.crossVectors(edgeAB, edgeAC);
if (out.lengthSq() < 1e-16) return false; // 适合单位尺度示例的阈值
out.normalize();
return true;
}
交换 B、C,法线取反。渲染中的正反面判断主要根据投影后绕序等光栅化规则,不是读取 normal attribute 来决定;法线属性主要参与着色,所以只反转法线并不等于反转面的绕序。
原始叉积长度等于三角形面积的两倍。有些平滑法线算法累加相邻三角形的原始叉积再归一化,隐含了面积权重;先把每个面法线归一化再累加则是另一种权重策略。
3. 给角色建立右、上、前基底
假定模型局部 +Z 是前方,参考上方向为世界 +Y。希望得到右手基底 [right, up, forward]:
function makeBasis(forwardInput) {
if (forwardInput.lengthSq() < 1e-12) return null;
const forward = forwardInput.clone().normalize();
const referenceUp = Math.abs(forward.y) > 0.999
? new THREE.Vector3(1, 0, 0)
: new THREE.Vector3(0, 1, 0);
const right = new THREE.Vector3().crossVectors(referenceUp, forward).normalize();
const up = new THREE.Vector3().crossVectors(forward, right).normalize();
return { right, up, forward };
}
forward=(0,0,1) 时,right=(1,0,0),up=(0,1,0)。若模型以 -Z 为前方,应调整映射,不能照搬符号。参考上方向与前方近平行时,叉积非常小,归一化会放大误差,所以要选择备用轴。
这个备用轴分支适合独立构造基底,但穿越阈值可能造成姿态跳变。连续相机运动可保留上一帧基底或使用四元数平滑,不能仅靠一行叉积解决连续性。
4. 推门的力矩:距离和角度都重要
从门轴上的参考点到施力点的位移为 r,力为 F,力矩是:
门把手距离转轴 0.8 米,垂直门面施力 10 N,最大力矩大小为 8 N·m。若力沿 r 指向转轴,叉积为零,即使很用力也不能产生绕轴转动的力矩。
对于只允许绕固定单位轴 u 旋转的门,真正关心的是 u·τ,即该轴上的有符号力矩。它可以同时用点积和叉积表达,而不需要猜“正转还是反转”。完整动力学还需要转动惯量和积分方法,本式只负责瞬时力矩。
5. 判断平行要考虑尺度
单看 cross.length() < 0.001 会受输入长度影响。对于非零向量,可以比较 |a×b|²/(|a|²|b|²),它等于 sin²θ。零长度仍要先单独处理。只要需要方向,先归一化再比较叉积长度也能简化尺度问题。
练习(不附答案)
- 用 A=(0,0,0)、B=(2,0,0)、C=(0,3,0) 求原始法线、单位法线和面积。
- 交换三角形的两个顶点,检查法线和面积分别如何变化。
- 验证 makeBasis 返回的三个向量长度约为 1、两两点积约为 0,并测试朝正上方的输入。
- 门轴为 +Y,r=(0.8,0,0)、F=(0,0,-10),计算轴向力矩及其符号。