【原理】[窄相检测]SAT解决凸多边形间的精确[碰撞检测]
·
【从Unity物理系统开始探索游戏物理】专栏-直达
分离轴定理 Separation axis theorem(SAT)是游戏物理中用于凸多边形碰撞检测的高效算法,其核心思想是通过寻找能将两物体投影分离的轴线来判断碰撞:若存在任一轴线使投影不重叠,则物体未碰撞;若所有轴线投影均重叠,则发生碰撞.
解决的问题
SAT主要用于解决凸多边形间的精确碰撞检测问题,相比AABB(轴对齐包围盒)或圆形检测,它能处理更复杂的几何形状(如不规则多边形),且计算效率较高(O(n²)时间复杂度)。其局限性在于仅适用于凸多边形,凹多边形需先分解为凸多边形组合。
算法原理与解决步骤
- 分离轴定义:若两物体未碰撞,必存在一条直线(分离轴)使其投影不重叠。
- 关键步骤:
- 计算法向量:遍历多边形每条边,计算其垂直向量(法向量)作为潜在分离轴。
- 投影检测:将两多边形顶点投影到每条法向量上,得到投影区间。
- 区间重叠判断:若任一轴上投影区间无重叠,则物体未碰撞;否则继续检测,全部通过则判定碰撞。
历史发展
SAT起源于计算几何领域,早期用于机器人路径规划中的碰撞预测。随着游戏物理引擎(如Unity的PhysX)普及,SAT因实现简单、性能适中成为Narrow Phase(精细检测)的常用算法。现代优化结合了Burst Compiler等加速技术。
C#实现示例
2D凸多边形的SAT检测
代码说明:
-
核心逻辑:通过遍历所有边的法向量作为分离轴,检测投影区间重叠。
-
性能优化:使用向量点积计算投影,避免复杂数学运算。
-
扩展性:可结合Unity的Collider组件实现更复杂物理效果。
-
SatCollision.cs
using UnityEngine; using System.Collections.Generic; public class SATCollision : MonoBehaviour { // 检测两个凸多边形是否碰撞 public static bool CheckCollision(Vector2[] polyA, Vector2[] polyB) { // 检查多边形A的边法向量 foreach (Vector2 edge in GetEdges(polyA)) { Vector2 normal = new Vector2(-edge.y, edge.x).normalized; if (!OverlapOnAxis(polyA, polyB, normal)) return false; } // 检查多边形B的边法向量 foreach (Vector2 edge in GetEdges(polyB)) { Vector2 normal = new Vector2(-edge.y, edge.x).normalized; if (!OverlapOnAxis(polyA, polyB, normal)) return false; } return true; } // 获取多边形所有边向量 private static List<Vector2> GetEdges(Vector2[] polygon) { List<Vector2> edges = new List<Vector2>(); for (int i = 0; i < polygon.Length; i++) { Vector2 next = polygon[(i + 1) % polygon.Length]; edges.Add(next - polygon[i]); } return edges; } // 检查两多边形在指定轴上的投影是否重叠 private static bool OverlapOnAxis(Vector2[] polyA, Vector2[] polyB, Vector2 axis) { float minA = float.MaxValue, maxA = float.MinValue; float minB = float.MaxValue, maxB = float.MinValue; // 计算多边形A的投影区间 foreach (Vector2 point in polyA) { float projection = Vector2.Dot(point, axis); minA = Mathf.Min(minA, projection); maxA = Mathf.Max(maxA, projection); } // 计算多边形B的投影区间 foreach (Vector2 point in polyB) { float projection = Vector2.Dot(point, axis); minB = Mathf.Min(minB, projection); maxB = Mathf.Max(maxB, projection); } return maxA >= minB && maxB >= minA; } }
3D凸多边形的SAT检测
代码功能说明:
-
Sat3D扩展至3D空间,增加面法线和边叉积检测
-
测试程序展示两个相交立方体和矩形的检测示例
-
Sat3D.cs
public static class Sat3D { public static bool CheckCollision(Vector3[] verticesA, Vector3[] verticesB, Vector3[] normalsA, Vector3[] normalsB) { // 检查物体A的面法线 foreach (var normal in normalsA) { if (!OverlapOnAxis(verticesA, verticesB, normal)) return false; } // 检查物体B的面法线 foreach (var normal in normalsB) { if (!OverlapOnAxis(verticesA, verticesB, normal)) return false; } // 检查所有边的叉积方向 for (int i = 0; i < normalsA.Length; i++) { for (int j = 0; j < normalsB.Length; j++) { Vector3 axis = Vector3.Cross(normalsA[i], normalsB[j]); if (axis.X == 0 && axis.Y == 0 && axis.Z == 0) continue; if (!OverlapOnAxis(verticesA, verticesB, axis)) return false; } } return true; } private static bool OverlapOnAxis(Vector3[] verticesA, Vector3[] verticesB, Vector3 axis) { float minA = float.MaxValue, maxA = float.MinValue; float minB = float.MaxValue, maxB = float.MinValue; foreach (var vertex in verticesA) { float projection = Vector3.Dot(vertex, axis); minA = Math.Min(minA, projection); maxA = Math.Max(maxA, projection); } foreach (var vertex in verticesB) { float projection = Vector3.Dot(vertex, axis); minB = Math.Min(minB, projection); maxB = Math.Max(maxB, projection); } return maxA >= minB && maxB >= minA; } } -
Program.cs
class Program { static void Main() { // 3D测试用例 Vector3[] cubeA = { new Vector3(0,0,0), new Vector3(1,0,0), new Vector3(1,1,0), new Vector3(0,1,0), new Vector3(0,0,1), new Vector3(1,0,1), new Vector3(1,1,1), new Vector3(0,1,1) }; Vector3[] cubeB = { new Vector3(0.5f,0.5f,0.5f), new Vector3(1.5f,0.5f,0.5f), new Vector3(1.5f,1.5f,0.5f), new Vector3(0.5f,1.5f,0.5f), new Vector3(0.5f,0.5f,1.5f), new Vector3(1.5f,0.5f,1.5f), new Vector3(1.5f,1.5f,1.5f), new Vector3(0.5f,1.5f,1.5f) }; Vector3[] normalsA = { new Vector3(1,0,0), new Vector3(0,1,0), new Vector3(0,0,1), new Vector3(-1,0,0), new Vector3(0,-1,0), new Vector3(0,0,-1) }; bool collision3D = Sat3D.CheckCollision(cubeA, cubeB, normalsA, normalsA); Console.WriteLine($"3D碰撞检测结果: {collision3D}"); } }
应用场景
- 游戏开发:精确检测角色与复杂环境的碰撞。
- 物理模拟:如弹球游戏中多边形物体的交互。
- 机器人导航:障碍物碰撞预测
【从Unity物理系统开始探索游戏物理】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)
更多推荐
所有评论(0)