**
想象一下,你手里拿着一个金字塔模型 ------ 不是埃及那种宏伟的大金字塔,而是一个由四个三角形面组成的迷你四面体。现在有一只小蚂蚁爬到了金字塔附近,你怎么判断这只蚂蚁是在金字塔内部、外部,还是正好站在棱边上呢?这就是计算机图形学里 "点是否在四面体内" 要解决的问题,听起来高深,其实原理就像我们玩捉迷藏时判断小伙伴藏没藏在帐篷里一样。
四面体:三维空间的最小积木
在三维世界里,四面体就像乐高积木里最基础的那块 ------ 它由四个顶点、六条棱和四个三角形面组成。任何复杂的三维模型,都可以看作是无数个小四面体拼接而成的。就像用砖头砌墙,每块砖头(四面体)的位置和状态都清楚了,整个墙(模型)的样子也就确定了。
判断点是否在四面体内,核心思路其实很朴素:如果这个点在四面体的四个面的 "内侧",那么它就一定在四面体内部。就像判断一个人是否在房间里,只要他在每一面墙的内侧,就肯定没跑到屋外去。
平面:空间中的隐形守门员
要理解这个原理,我们得先认识 "平面" 这个三维空间里的隐形守门员。每个三角形面都能确定一个唯一的平面,这个平面就像一张无限大的薄纸,把整个三维空间分成了两部分 ------ 我们可以暂且称为 "正面" 和 "反面"。
对于四面体的每个面来说,四面体的内部始终在平面的同一侧。比如你把四面体的一个面想象成一扇门,门轴是面的一条棱,那么整个四面体就像门内侧的小房间,无论你怎么转动这扇门(平面),小房间都只会在门的一侧。
那么问题来了:如何判断一个点在平面的哪一侧呢?这里就要用到 "向量叉乘" 的小技巧,但我们不用公式,用生活化的方式解释:想象平面上有两个不共线的向量(就像从平面上同一点出发的两只箭头),它们叉乘的结果会产生一个垂直于平面的新向量,这个向量就像一根扎在平面上的指南针,始终指向四面体内部的方向。
裁判的秘密:点与平面的位置关系
当我们有了这个垂直于平面的指南针向量后,就能通过以下步骤判断点的位置:
- 从平面上的一个顶点出发,向目标点引一条向量(想象成从门轴到蚂蚁的一条线)
- 计算这条向量与指南针向量的 "点积"(可以理解为两个向量的相似度)
- 如果结果为正,说明点在平面的指南针指向侧(四面体内部方向)
- 如果结果为负,说明点在平面的另一侧(外部)
- 如果结果为零,说明点正好在平面上(可能在面上、棱上或顶点上)
就像四个守门员分别守着四面体的四个面,只有当点通过了所有守门员的检查(都在内部方向),才能判定它在四面体内部。
用代码实现:让计算机当裁判
现在我们用 JavaScript 把这个过程翻译成计算机能理解的语言。首先我们需要定义四面体的四个顶点和待判断的点,每个点都用 x、y、z 三个坐标表示:
yaml
// 定义四面体的四个顶点 A、B、C、D
const tetrahedron = {
A: { x: 0, y: 0, z: 0 },
B: { x: 1, y: 0, z: 0 },
C: { x: 0, y: 1, z: 0 },
D: { x: 0, y: 0, z: 1 }
};
// 待判断的点 P
const pointP = { x: 0.2, y: 0.2, z: 0.2 };
接下来我们需要一个函数来判断点在平面的哪一侧。这个函数接收平面上的三个点和目标点,返回一个表示位置关系的数值:
ini
function getSide(p1, p2, p3, point) {
// 计算平面的三个边向量
const v1 = { x: p2.x - p1.x, y: p2.y - p1.y, z: p2.z - p1.z };
const v2 = { x: p3.x - p1.x, y: p3.y - p1.y, z: p3.z - p1.z };
// 计算法向量(指南针向量),使用叉乘
const normal = {
x: v1.y * v2.z - v1.z * v2.y,
y: v1.z * v2.x - v1.x * v2.z,
z: v1.x * v2.y - v1.y * v2.x
};
// 计算从平面点到目标点的向量
const vp = { x: point.x - p1.x, y: point.y - p1.y, z: point.z - p1.z };
// 计算点积(判断方向关系)
const dotProduct = normal.x * vp.x + normal.y * vp.y + normal.z * vp.z;
return dotProduct;
}
这个函数的返回值就像裁判的打分:正数表示在四面体内部侧,负数表示在外部,零表示在平面上。
最终判决:点的归属
有了判断单个平面的函数,我们就可以对四面体的四个面逐一检查:
arduino
function isPointInTetrahedron(tetra, point) {
// 检查四个面,注意每个面的三个点顺序要保持一致(都按顺时针或逆时针)
const side1 = getSide(tetra.A, tetra.B, tetra.C, point);
const side2 = getSide(tetra.A, tetra.B, tetra.D, point);
const side3 = getSide(tetra.A, tetra.C, tetra.D, point);
const side4 = getSide(tetra.B, tetra.C, tetra.D, point);
// 判断是否所有面的检查结果都同号(都正或都负,取决于法向量方向)
const allPositive = side1 > 0 && side2 > 0 && side3 > 0 && side4 > 0;
const allNegative = side1 < 0 && side2 < 0 && side3 < 0 && side4 < 0;
// 只要有一个面的结果为零,说明点在面上
const onSurface = side1 === 0 || side2 === 0 || side3 === 0 || side4 === 0;
if (onSurface) {
return "点在四面体的表面上";
} else if (allPositive || allNegative) {
return "点在四面体内部";
} else {
return "点在四面体外部";
}
}
// 测试一下我们的函数
console.log(isPointInTetrahedron(tetrahedron, pointP)); // 应该返回"点在四面体内部"
这里有个小细节:四个面的顶点顺序必须保持一致(都按顺时针或逆时针排列),否则就像四个裁判用了不同的评分标准,结果会混乱。这就像判罚足球比赛时,所有裁判都得用同一套规则才行。
生活中的应用:不止是判断蚂蚁位置
这个看似简单的判断,在计算机图形学里却有大用处。比如 3D 游戏中,判断子弹是否击中了敌人(敌人模型由四面体组成),或者医学影像中判断一个病灶点是否在某个器官内部(器官被分割成四面体网格)。
下次当你玩 3D 游戏时,不妨想想:每一次碰撞检测的背后,都有无数个 "点是否在四面体内" 的判断在默默工作,就像无数个隐形的小裁判在维持着虚拟世界的秩序。
从四面体这个简单的几何体出发,我们触摸到了三维空间的基本逻辑。就像通过一片树叶认识整个森林,理解了点与四面体的关系,你就掌握了探索更复杂三维世界的一把钥匙。也许下一次,你就能用这个知识判断小蚂蚁到底藏没藏在你的金字塔模型里了。