09 · 三维叉积:垂直方向、法线与力矩

返回目录 · 上一章 · 下一章

1. 结果为什么是一根新向量

三维中,两条不平行的方向确定一个平面。叉积构造垂直于该平面的方向,其长度同时编码面积:

a×b=(aybz−azby, azbx−axbz, axby−aybx) a\times b=(a_yb_z-a_zb_y,\ a_zb_x-a_xb_z,\ a_xb_y-a_yb_x)
∥a×b∥=∥a∥∥b∥sin⁡θ \|a\times b\|=\|a\|\|b\|\sin\theta

结果同时垂直于 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,必须先相减得到从同一起点出发的两条边:

nraw=(B−A)×(C−A),n=nraw/∥nraw∥ n_{raw}=(B-A)\times(C-A),\qquad n=n_{raw}/\|n_{raw}\|
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,力矩是:

τ=r×F \tau=r\times F

门把手距离转轴 0.8 米,垂直门面施力 10 N,最大力矩大小为 8 N·m。若力沿 r 指向转轴,叉积为零,即使很用力也不能产生绕轴转动的力矩。

对于只允许绕固定单位轴 u 旋转的门,真正关心的是 u·τ,即该轴上的有符号力矩。它可以同时用点积和叉积表达,而不需要猜“正转还是反转”。完整动力学还需要转动惯量和积分方法,本式只负责瞬时力矩。

5. 判断平行要考虑尺度

单看 cross.length() < 0.001 会受输入长度影响。对于非零向量,可以比较 |a×b|²/(|a|²|b|²),它等于 sin²θ。零长度仍要先单独处理。只要需要方向,先归一化再比较叉积长度也能简化尺度问题。

练习(不附答案)

  1. 用 A=(0,0,0)、B=(2,0,0)、C=(0,3,0) 求原始法线、单位法线和面积。
  2. 交换三角形的两个顶点,检查法线和面积分别如何变化。
  3. 验证 makeBasis 返回的三个向量长度约为 1、两两点积约为 0,并测试朝正上方的输入。
  4. 门轴为 +Y,r=(0.8,0,0)、F=(0,0,-10),计算轴向力矩及其符号。