分离轴定理:影子重叠即碰撞

摘要

分离轴定理(SAT)是一种高效的碰撞检测方法,通过判断两个物体在所有方向上的投影(影子)是否重叠来确定是否碰撞。核心思想是:只要存在一个方向使投影不重叠,则物体未碰撞;若所有方向投影均重叠,则发生碰撞。SAT适用于游戏中的凸多边形/多面体检测(如OBB),优势在于可提前终止计算,兼顾效率与通用性。生活中类似“从不同角度观察书本是否重叠”的现象可帮助理解该原理。


一、分离轴定理(SAT)是什么?——“影子重叠”的故事

想象你有两个不规则的物体,比如两块积木。你想知道它们有没有碰到一起。
分离轴定理的核心思想是:

“如果你能找到一个方向(轴),让这两个物体在这个方向上的影子(投影)完全不重叠,那它们就一定没有碰到;如果所有方向上的影子都重叠了,那它们就一定有碰撞。”

形象比喻

  • 把两个物体放在阳光下,阳光从不同方向照射。
  • 你在每个方向上都看它们的影子。
  • 只要有一个方向,两个影子完全分开了,那它们肯定没碰到。
  • 如果无论怎么照,影子都重叠,那它们肯定有交集。

关键点

  • “轴”可以是任意方向,但对于常见的几何体(如盒子、凸多边形),有一套有限的“关键轴”需要检查。
  • 只要找到一个分离轴(影子不重叠),就能断定“不碰撞”。
  • 如果所有轴都重叠,才是“碰撞”。

二、SAT在游戏中的实际应用

1. 碰撞检测

在3D游戏中,常见的碰撞体有AABB、OBB、多边形、凸多面体等。
SAT是检测两个凸多边形/多面体是否相交的通用方法,尤其适合OBB(有向包围盒)和凸多边形。

例子:两个OBB的碰撞检测
  • 先找出所有可能的分离轴(比如两个盒子的主轴和它们主轴的叉积)。
  • 把两个盒子在每个轴上投影,检查投影区间是否重叠。
  • 只要有一个轴不重叠,就不用再算了,直接判定“不碰撞”。
  • 如果所有轴都重叠,判定“碰撞”。
伪代码流程
for axis in all_possible_axes:
    proj1 = project_shape1_on_axis(axis)
    proj2 = project_shape2_on_axis(axis)
    if not overlap(proj1, proj2):
        return False  # 有分离轴,不碰撞
return True  # 所有轴都重叠,碰撞

2. 游戏中的实际场景

  • 角色与障碍物:比如主角和箱子、门等物体的碰撞体都是OBB,SAT可以高效判断是否相交。
  • 物理引擎:如Box2D、Bullet、PhysX等,底层都用SAT做凸多边形/多面体的初步碰撞检测。
  • 射线检测:判断子弹是否击中目标,也可以用SAT原理。

3. 优点

  • 高效:只要找到一个分离轴就能提前结束,适合实时游戏。
  • 通用:适用于任意凸多边形/多面体。

三、再举个生活例子

想象你有两本书,你想知道它们有没有叠在一起。你可以从正上方、正侧面、正前方分别看它们的投影(影子)。

  • 如果有一个方向看过去,两个影子完全分开,那它们肯定没叠在一起。
  • 如果每个方向看过去,影子都重叠,那它们肯定有重叠部分。

四、总结一句话

分离轴定理就是“找影子不重叠的方向”,只要有一个方向影子分开了,物体就没碰到。


我们继续深入,用更具体的例子、动画想象和伪代码,让你彻底掌握SAT的原理和实际用法。


一、二维例子:两个凸多边形的碰撞检测

1. 动画想象

假设有两个不规则的凸多边形(比如两个盾牌),你想知道它们是否碰撞。

  • 你把每个多边形的每一条边都当作“分离轴”。
  • 把两个多边形都投影到这条轴上,看看它们的投影区间(影子)是否重叠。
  • 如果有一条轴上,两个投影区间完全不重叠,说明这两个多边形没有碰撞。
  • 如果所有轴上投影都重叠,说明它们碰撞了。

2. 伪代码(二维凸多边形)

def polygons_collide(poly1, poly2):
    for edge in poly1.edges + poly2.edges:
        axis = edge.normal()  # 取边的法线作为分离轴
        proj1 = project_polygon(poly1, axis)
        proj2 = project_polygon(poly2, axis)
        if not overlap(proj1, proj2):
            return False  # 找到分离轴,不碰撞
    return True  # 所有轴都重叠,碰撞
投影函数
def project_polygon(polygon, axis):
    dots = [vertex.dot(axis) for vertex in polygon.vertices]
    return min(dots), max(dots)
重叠判断
def overlap(proj1, proj2):
    return not (proj1[1] < proj2[0] or proj2[1] < proj1[0])

二、三维例子:两个OBB的碰撞检测

1. 需要检查哪些轴?

对于两个OBB(有向包围盒),需要检查的分离轴有:

  • 盒子A的3个主轴
  • 盒子B的3个主轴
  • 盒子A和B主轴两两叉积,共9个轴

总共15个轴

2. 伪代码(简化版)

axes = []
axes += [A.axis0, A.axis1, A.axis2]
axes += [B.axis0, B.axis1, B.axis2]
for i in range(3):
    for j in range(3):
        axes.append(cross(A.axis[i], B.axis[j]))

for axis in axes:
    projA = project_obb(A, axis)
    projB = project_obb(B, axis)
    if not overlap(projA, projB):
        return False  # 有分离轴,不碰撞
return True  # 所有轴都重叠,碰撞

3. 投影OBB到轴

OBB在某个轴上的投影区间,可以通过中心点在轴上的投影±半长宽高在该轴上的投影之和得到。

def project_obb(obb, axis):
    center_proj = obb.center.dot(axis)
    r = 0
    for i in range(3):
        r += obb.half_extents[i] * abs(obb.axes[i].dot(axis))
    return (center_proj - r, center_proj + r)

三、实际游戏中的优化

  • 只对可能接触的物体用SAT:比如先用AABB快速排除远距离物体,再用SAT精确检测。
  • 提前终止:只要找到一个分离轴就立刻返回,节省大量计算。
  • 缓存轴和投影:减少重复计算。

四、可视化动画想象

你可以想象这样一个动画:

  1. 两个多边形/盒子在屏幕上。
  2. 你拿一根棍子(分离轴),在不同方向上“照射”它们。
  3. 在每个方向上,两个物体都投下影子。
  4. 只要有一次,两个影子完全分开,物体就没碰到。
  5. 如果每次影子都重叠,物体就碰上了。

五、SAT的局限

  • 只适用于凸多边形/多面体,不适用于凹形物体(凹形要先分解成多个凸形)。
  • 对于复杂物体,分离轴数量会增加,计算量也会增加。

六、总结口诀

“找轴投影,影子分离则不碰,影子都重叠才碰撞。”


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值