碰撞检测

碰撞在物理学中表现为两粒子或物体间极短的相互作用。 碰撞前后参与物发生速度,动量或能量改变。由能量转移的方式区分为弹性碰撞和非弹性碰撞。弹性碰撞是碰撞前后整个系统的动能不变的碰撞。弹性碰撞的必要条件是动能没有转成其他形式的能量(热能、转动能量),例如原子的碰撞。非弹性碰撞是碰撞后整个系统的部分动能转换成至少其中一碰撞物的内能,使整个系统的动能无法守恒。

在游戏中,为了简化物体之间的碰撞检测运算,通常会对物体创建一个规则的几何外形将其包围。其中,AABB(axis-aligned bounding box)包围盒被称为轴对齐包围盒。在网页中,制作元素拖拽定位的效果也会使用到碰撞检测的原理。


📐 数学公式

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
点与 AABB
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
检测一个点是否在 AABB 内部:
我们只需要检查这个点的坐标是否在 AABB; 分别考虑到每种坐标轴。如果假设 Px, Py 和 Pz 是点的坐标,BminX – BmaxX, BminY – BmaxY, 和 BminZ – BmaxZ 是 AABB 的每一个坐标轴的范围,我们可以使用以下公式计算两者之间的碰撞是否发生:
f(P,B) = (Px ≥ BminX ∧ Px ≤ BmaxX)  (Py ≥ BminY ∧ Py ≤ BmaxY)  (Pz ≥ BminZ ∧ Pz ≤ BmaxZ)



■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
点与球
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
检查是否一个球体包含一个点,我们需要计算点和球体的中心之间的距离。如果这个距离小于或等于球的半径,这个点就在里面。

两个点 AB 之间的欧氏距离(真实距离): 
d = ((Ax-Bx)²+(Ay-By)²+(Az-Bz)²)

球体碰撞检测是:
f(P,S) = Sradius ≥ ((Px-Sx)²+(Py-Sy)²+(Pz-Sz)²)


■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
球与球
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
球体与球体的距离类似于点和球体。我们需要测试是球体的中心之间的距离小于或等于半径的总和。
f(A,B) = ((Ax-Bx)²+(Ay-By)²+(Az-Bz)²) ≤ Aradius + Bradius



■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
球与 AABB
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
测试一个球和一个 AABB 的碰撞是稍微复杂。一个合乎逻辑的方法是,检查 AABB 每个顶点,计算每一个点与球的距离。然而这是大材小用了,测试所有的顶点都是不必要的,因为我们可以侥幸计算 AABB 最近的点 (不一定是一个顶点) 和球体的中心之间的距离,看看它是小于或等于球体的半径。我们可以通过逼近球体的中心和 AABB 的距离得到这个值。

📌 JavaScript

参考代码:index.js

/**
* 判断两个三维空间的球体是否碰撞
* @param  {Object} sphere1  - 球体的x,y,z坐标和半径radius
* @param  {Object} sphere2  - 球体的x,y,z坐标和半径radius
* @return {Boolean} 
*/
export function intersectSpheres(sphere1, sphere2) {
    const distance = Math.sqrt((sphere1.x - sphere2.x) * (sphere1.x - sphere2.x) +
        (sphere1.y - sphere2.y) * (sphere1.y - sphere2.y) +
        (sphere1.z - sphere2.z) * (sphere1.z - sphere2.z));

    return distance < (sphere1.radius + sphere2.radius);
}

/**
* 判断两个二维空间的矩形是否碰撞
* 原理:确定两个矩形任意 4 边之间不再有间隔,存在间隔代表没有发生碰撞。
* @param  {Object} rect1  - 矩形的x,y坐标和长宽值
* @param  {Object} rect2  - 矩形的x,y坐标和长宽值
* @return {Boolean} 
*/
export function intersectBoxes(rect1, rect2) {
    if (rect1.x < rect2.x + rect2.width &amp;&amp;
        rect1.x + rect1.width > rect2.x &amp;&amp;
        rect1.y < rect2.y + rect2.height &amp;&amp;
        rect1.height + rect1.y > rect2.y) 
    {
        return true;
    } else {
        return false;
    }

}

测试:test.js

import { intersectSpheres, intersectBoxes } from './index';

console.log( intersectSpheres({x:5, y:3, z: 3, radius:30}, {x:5, y:3, z: 3, radius:10}) );
/* 输出:true */

console.log( intersectSpheres({x:5, y:3, z: 3, radius:30}, {x:35, y:23, z: 33, radius:10}) );
/* 输出:false */


console.log(intersectBoxes({ x: 5, y: 5, width: 50, height: 50 }, { x: 20, y: 10, width: 10, height: 10 }));
/* 输出:true */

console.log(intersectBoxes({ x: 5, y: 5, width: 50, height: 50 }, { x: 60, y: 60, width: 5, height: 5 }));
/* 输出:false */