起因:
MOBA游戏技能的矩形碰撞盒,位于施法人物正前方,当人物转向后碰撞盒也需要旋转,此时检查碰撞目标,解决此问题过程中,发现不规则多边形碰撞检查跟这个问题原理相通,顺便就整理了一下(部分图片来源于网络,侵删)。
核心理论:
对于两个凸多边形,当存在直线,使得两个多边形在这条直线上的投影不想交,那么这两个多边形就不想交,这条直线 叫做分离轴。
分离轴这种方式只适用于凸多边形,凹多边形不适应,对于凹多边形碰撞检查,可以将凹多边形裁剪成多个凸多边形进 行检查(可搜索凹多边形切分凸多边形算法)。
实现步骤:
首先,第一步根据分离轴定理,我们需要找这么一条轴,将两个多边形进行投影,然后第二步,检查这两个投影是否有相交。
寻找分离轴,我们能想到最简单的方式就是360度,每个方向上取一条轴进行投影检测,不过通常我们只需要检查与多 边形每条边垂直的轴就可以,比如两个8边形需要检查16条轴。
当两个多边形相离时,存在至少一条或多条直线将两个多边形分隔开,这些直线中必定有一条,与两个多边形中的 某条边相平行(可以作图看一下),反过来,只要能证明与两个多边形中的某条边相平行的一条直线,将这两个多边形 分隔,那这个两个多边形必定相离。
检查两个多边形投影是否相交,需要将两个多边形的每个顶点,在需要检查的分离轴上做投影,找到两个多边形各自顶 点投影的最小点和最大点组成的线段,判断这两条线段是否相交。
简单常规图形的碰撞检查:
圆与圆的碰撞检查,只需要检查两个圆心的距离和两个圆半径和的关系,用分离轴算法相当于两个圆心在xy任意坐标轴 上的投影坐标,再加减各自半径,得出两个圆各自投影线段的最大点和最小点,进行线段相交判断。
常规矩形*常规矩形(AABB,axis-aligned bounding box,即轴对其包围盒)
只需要检查两个矩形各自的长度坐标表范围和宽度坐标表范围是否同时有相交,用分离轴算法相当于就是在这判断投影 相交,而且因为矩形对边平行的原因,每个矩形只需要检查两条轴。
旋转矩形*旋转矩形(OBB ,oriented bounding box,即方向包围盒)
使用分离轴算法,圆的分离轴为0条,只需要检查多边形的分离轴,进行投影时,圆将圆心进行投影,投影点加减半 径,得出圆投影线段的最大点和最小点,以此和多边形投影进行相交判断。
代码实现:
public bool sat(List<Vector3> _polygon1, List<Vector3> _polygon2)
{
bool isIntersect = true;
if (_polygon1.Count >= 3 && _polygon2.Count >= 3)
{
List<Vector2> axisList = new List<Vector2>();
//统计投影轴
Action<List<Vector3>> _addAxis = (_polygon) =>
{
for (int index = 0; index < _polygon.Count; index++)
{
int nextIndex = index + 1 < _polygon.Count ? index + 1 : 0;
Vector2 curPos = _polygon[index];
Vector2 nextPos = _polygon[nextIndex];
Vector2 side = nextPos - curPos;//边向量
axisList.Add(new Vector2(-side.y, side.x));//二维边向量的法向量,只需要x,y坐标交换,然后x,y任一取反
}
};
_addAxis(_polygon1);
_addAxis(_polygon2);
//将多边形每个顶点视为原点为0的向量,求出每个顶点向量在每条分离轴上的投影,投影大小即为坐标
for (int index= 0; index< axisList.Count; index++)
{
Vector2 axis= axisList[index];
float p1VertexMax = float.MinValue;//多边形1的在分离轴上的投影最大值
float p1VertexMin = float.MaxValue;//多边形1的在分离轴上的投影最小值,下同
float p2VertexMax = float.MinValue;
float p2VertexMin = float.MaxValue;
for (int pIndex= 0; pIndex< _polygon1.Count; pIndex++)
{
Vector2 vertex = _polygon1[pIndex];
float prjPos= Vector2.Dot(vertex, axis) / axis.magnitude;//利用点乘求投影
p1VertexMax = prjPos > p1VertexMax ? prjPos : p1VertexMax;
p1VertexMin = prjPos < p1VertexMin ? prjPos : p1VertexMin;
}
for (int pIndex = 0; pIndex < _polygon2.Count; pIndex++)
{
Vector2 vertex = _polygon2[pIndex];
float prjPos = Vector2.Dot(vertex, axis) / axis.magnitude;
p2VertexMax = prjPos > p2VertexMax ? prjPos : p2VertexMax;
p2VertexMin = prjPos < p2VertexMin ? prjPos : p2VertexMin;
}
if (p1VertexMin > p2VertexMax || p2VertexMin > p1VertexMax)//判断两个多边形投影是否相交
{
isIntersect = false;
break;
}
}
}
return isIntersect;
}
测试: