摘要

分离轴定理(SAT)是一种高效的凸多边形碰撞检测算法。其核心思想是通过寻找能将两物体投影分离的轴线来判断碰撞:若存在任一轴线使投影不重叠,则物体未碰撞;若所有轴线投影均重叠,则发生碰撞。SAT在游戏中广泛应用,支持任意凸形状的精确检测。2D实现需检查多边形各边法线方向作为分离轴,3D则需增加面法线和边叉积方向检测。文中提供了简洁的C#二维实现,包含向量运算、多边形投影及碰撞检测逻辑,适用于游戏开发中的复杂碰撞场景。算法优势在于比AABB/圆形检测更精确且计算高效,是物理引擎的常用基础技术。

一、什么是分离轴定理(SAT)?

1. 生活中的比喻

想象你有两块形状奇怪的饼干(比如星星形和月亮形),你想知道它们有没有碰在一起。你把它们放在桌子上,从上面看。

你可以用一根直尺(轴)放在桌面上,试着“投影”这两块饼干到直尺上。

如果你能找到某个方向,让两块饼干在直尺上的投影完全不重叠,那它们一定没有碰到。如果无论怎么放直尺,两块饼干的投影总有重叠,那它们一定有碰撞。

这根“直尺”就是“分离轴”,这就是分离轴定理的核心思想。

2. 形象图示

假设有两个多边形A和B:

你把A和B分别“投影”到某个方向的轴上(比如x轴、y轴、斜着的轴)。如果在某个轴上,A的投影区间和B的投影区间没有重叠,那A和B就没有碰撞。如果所有轴上都重叠,那A和B就发生了碰撞。

二、SAT的原理

定理内容:如果两个凸多边形(或凸多面体)没有碰撞,那么一定存在一条轴(分离轴),使得它们在这条轴上的投影不重叠。检测方法:只要找到一个分离轴,证明没碰撞;如果所有可能的分离轴都没有分开它们,说明碰撞了。

常用的分离轴:

两个物体各自的边的法线方向(2D),或面法线/边叉积方向(3D)。

三、在游戏中的实际应用

1. 游戏场景举例

2D平台游戏中,角色和斜坡、箱子等多边形障碍物的碰撞检测。3D游戏中,两个凸多面体(比如刚体、车辆、道具)的精确碰撞检测。

2. 为什么用SAT?

比AABB/圆形更精确:AABB只能检测矩形,圆只能检测圆,SAT可以检测任意凸多边形/多面体。效率高:只需要检测有限个轴(2D最多是两个物体所有边的法线方向),比暴力检测所有点要快很多。

3. 实际流程

以2D为例(3D原理类似):

找分离轴:取两个多边形所有边的法线方向作为分离轴。投影:把两个多边形的所有顶点投影到每个轴上,得到区间。检测重叠:如果有任何一个轴上区间不重叠,立即返回“无碰撞”。全部重叠:如果所有轴都重叠,说明发生了碰撞。

四、形象小结

SAT就像用不同方向的手电筒照射两个物体的影子,只要有一个方向影子不重叠,物体就没碰到。在游戏中,SAT让你能检测任意凸形状的碰撞,比如角色和斜坡、复杂障碍物、3D刚体等。

五、代码/伪代码简要示例

// 伪代码:检测两个凸多边形是否碰撞

foreach (每个多边形的每条边)

{

axis = 这条边的法线方向;

投影A = 把A的所有顶点投影到axis上,得到区间[minA, maxA];

投影B = 把B的所有顶点投影到axis上,得到区间[minB, maxB];

if (maxA < minB || maxB < minA)

return "没有碰撞"; // 找到分离轴

}

return "发生碰撞"; // 所有轴都重叠

六、总结

分离轴定理是检测两个凸形状是否碰撞的高效方法。实际应用:2D/3D游戏中任意凸形状的精确碰撞检测。形象理解:只要能找到一个方向让两个物体的“影子”不重叠,就一定没碰撞。

下面给你一个简洁易懂的C#二维SAT(分离轴定理)碰撞检测实现,适用于任意凸多边形。代码注释详细,便于理解和扩展。

一、核心数据结构

using System;

using System.Collections.Generic;

public struct Vec2

{

public float x, y;

public Vec2(float x, float y) { this.x = x; this.y = y; }

// 向量减法

public static Vec2 operator -(Vec2 a, Vec2 b) => new Vec2(a.x - b.x, a.y - b.y);

// 点积

public float Dot(Vec2 other) => x * other.x + y * other.y;

// 法线(顺时针90度)

public Vec2 Perpendicular() => new Vec2(-y, x);

// 单位化

public Vec2 Normalized()

{

float len = (float)Math.Sqrt(x * x + y * y);

return len > 1e-6 ? new Vec2(x / len, y / len) : new Vec2(0, 0);

}

}

// 凸多边形

public class ConvexPolygon

{

public List vertices; // 顶点按顺时针或逆时针顺序排列

public ConvexPolygon(List vertices)

{

this.vertices = vertices;

}

}

二、SAT碰撞检测主函数

public static class SAT

{

// 检测两个凸多边形是否碰撞

public static bool IsColliding(ConvexPolygon polyA, ConvexPolygon polyB)

{

// 检查A和B所有边的法线方向

return !HasSeparatingAxis(polyA, polyB) && !HasSeparatingAxis(polyB, polyA);

}

// 检查polyA的所有边的法线方向是否为分离轴

private static bool HasSeparatingAxis(ConvexPolygon polyA, ConvexPolygon polyB)

{

int countA = polyA.vertices.Count;

for (int i = 0; i < countA; i++)

{

// 当前边

Vec2 p1 = polyA.vertices[i];

Vec2 p2 = polyA.vertices[(i + 1) % countA];

Vec2 edge = p2 - p1;

Vec2 axis = edge.Perpendicular().Normalized();

// 投影A

ProjectPolygon(polyA, axis, out float minA, out float maxA);

// 投影B

ProjectPolygon(polyB, axis, out float minB, out float maxB);

// 检查投影区间是否分离

if (maxA < minB || maxB < minA)

return true; // 找到分离轴,无碰撞

}

return false; // 没有分离轴

}

// 把多边形投影到轴上,得到最小最大投影值

private static void ProjectPolygon(ConvexPolygon poly, Vec2 axis, out float min, out float max)

{

min = max = poly.vertices[0].Dot(axis);

foreach (var v in poly.vertices)

{

float proj = v.Dot(axis);

if (proj < min) min = proj;

if (proj > max) max = proj;

}

}

}

三、使用示例

class Program

{

static void Main()

{

// 三角形A

var polyA = new ConvexPolygon(new List

{

new Vec2(0, 0),

new Vec2(2, 0),

new Vec2(1, 2)

});

// 四边形B

var polyB = new ConvexPolygon(new List

{

new Vec2(1, 1),

new Vec2(3, 1),

new Vec2(3, 3),

new Vec2(1, 3)

});

bool colliding = SAT.IsColliding(polyA, polyB);

Console.WriteLine("碰撞结果: " + (colliding ? "发生碰撞" : "没有碰撞"));

}

}

输出:

碰撞结果: 发生碰撞

四、说明与扩展

只适用于凸多边形,凹多边形需先分解为凸多边形。可扩展到3D(分离轴为所有面法线和两物体边的叉积方向)。可返回最小穿透向量(MTV)用于物理分离,方法是记录重叠最小的轴。