第2章:向量的点积与叉积
好,咱们今天聊点实在的。向量点积和叉积,这两个东西在动画里到底有什么用?
说实话,我刚开始做前端动画那会儿,也觉得这些数学概念离我很远。直到有一次做一个3D射击游戏,子弹碰撞检测怎么调都不对——嗯,后来才发现,就是没搞懂点积和叉积的几何意义。
点积的几何意义:投影
先说说点积。公式很简单:a · b = |a| |b| cosθ。但咱们不看公式,看它到底在干什么。
点积的本质,就是一个向量在另一个向量方向上的投影长度。
你想想看,如果两个向量方向相同,点积就是它们长度的乘积。如果垂直,点积就是0。如果方向相反,点积就是负数。
我在项目中遇到过这样一个场景:角色要判断自己是否面向某个物体。怎么做?
// 判断角色是否面向目标
function isFacing(character, target) {
const forward = character.forward; // 角色朝向向量
const toTarget = target.position - character.position; // 指向目标的向量
// 归一化
const forwardNorm = normalize(forward);
const toTargetNorm = normalize(toTarget);
// 点积
const dot = dotProduct(forwardNorm, toTargetNorm);
// 如果点积大于0.5,说明夹角小于60度,算作面向目标
return dot > 0.5;
}
核心要点:点积的正负告诉你两个向量是「同向」还是「反向」。点积的大小告诉你「有多同向」。
说白了,点积就是判断「方向一致性」的工具。在动画里,我经常用它来做:
- 光照计算:判断光线与表面法线的夹角,决定亮度
- 碰撞方向判断:物体从哪个方向撞过来
- 视线检测:物体是否在视野范围内
我的小技巧:做投影时,记得先把向量归一化。不然点积的结果会被向量长度干扰,不好判断阈值。
叉积的几何意义:面积与法向量
叉积就更有意思了。它产生的是一个垂直于两个向量所在平面的新向量。
公式:a × b = |a| |b| sinθ · n,其中n是单位法向量。
叉积的长度等于两个向量围成的平行四边形的面积。方向由右手定则决定。
我曾经踩过一个坑:做3D角色旋转时,想计算旋转轴。用叉积算出来的法向量方向总是不对。后来发现,叉积不满足交换律——a × b = -(b × a)。顺序搞反了,方向就反了。
// 计算两个向量的叉积
function crossProduct(a, b) {
return {
x: a.y * b.z - a.z * b.y,
y: a.z * b.x - a.x * b.z,
z: a.x * b.y - a.y * b.x
};
}
// 计算三角形面积(三个顶点)
function triangleArea(p1, p2, p3) {
const v1 = subtract(p2, p1);
const v2 = subtract(p3, p1);
const cross = crossProduct(v1, v2);
return 0.5 * magnitude(cross);
}
注意:叉积只在三维空间中有定义。二维空间里没有叉积,但可以用「伪叉积」——也就是二维向量的行列式,它表示两个向量围成的有向面积。
在动画碰撞检测中的应用
好了,理论说完了。咱们看看实际怎么用。
1. 点积在碰撞检测中的应用
最常见的场景:判断物体是否在另一个物体的前方。
比如做赛车游戏,判断前车是否在正前方:
function isInFront(self, other) {
const forward = self.forward; // 自己的前进方向
const toOther = other.position - self.position;
// 点积大于0,说明在正前方
return dotProduct(forward, toOther) > 0;
}
还有一个经典用法:分离轴定理(SAT)。判断两个凸多边形是否碰撞,就是看它们在所有轴上的投影是否有重叠。而投影,就是用点积算的。
2. 叉积在碰撞检测中的应用
叉积的用处更直接:判断点在多边形的哪一侧。
你想想看,一个多边形有边,每条边对应一个法向量。用叉积可以判断一个点是在边的左侧还是右侧。
// 判断点P在边AB的哪一侧
function pointSide(A, B, P) {
const AB = subtract(B, A);
const AP = subtract(P, A);
const cross = crossProduct(AB, AP);
// cross.z > 0 表示在左侧
// cross.z < 0 表示在右侧
// cross.z = 0 表示在边上
return cross.z;
}
我在做2D物理引擎时,就用这个来判断点是否在凸多边形内部。如果一个点在所有边的同一侧(比如都在左侧),那它就在多边形内部。
3. 综合应用:碰撞响应
碰撞检测只是第一步。检测到碰撞后,还要计算反弹方向。
反弹方向怎么算?用点积算投影,用叉积算法向量。
// 计算反弹方向
function reflect(velocity, normal) {
// 速度在法线方向上的投影
const dot = dotProduct(velocity, normal);
// 反弹:速度 - 2 * 法线方向上的投影分量
return subtract(velocity, multiply(normal, 2 * dot));
}
总结一下:
- 点积 → 投影 → 判断方向、计算分量
- 叉积 → 法向量 → 判断左右、计算面积
- 两者结合 → 碰撞检测 + 碰撞响应
说实话,这些知识我一开始也觉得抽象。但当你真正动手写一个碰撞检测系统时,就会发现——点积和叉积就是你的左右手,缺一个都不行。
我个人习惯是把这些基础运算封装成工具函数,放在一个单独的模块里。这样写动画逻辑时,直接调用就行,不用每次都重新推导公式。
下一章咱们聊聊矩阵变换,那又是另一个有意思的话题了。