我会推荐Habrador - Use math to solve problems in Unity with C# - Chapter 6 Are two triangles in 2D space intersecting?。真的检查一下,因为它在那里解释了更多细节。
基本上它通过经典的 3 个步骤来解决这个问题:
-
第 1 步。 用矩形近似三角形并测试它们是否相交。
-
第 2 步。 检查一个三角形的任何边是否与另一个三角形的任何边相交。
-
第 3 步。 检查一个三角形的任何角是否在另一个三角形内,反之亦然。
首先它使用一些辅助类型
//To store triangle data to get cleaner code
public struct Triangle
{
//Corners of the triangle
public Vector3 p1, p2, p3;
//The 3 line segments that make up this triangle
public LineSegment[] lineSegments;
public Triangle(Vector3 p1, Vector3 p2, Vector3 p3)
{
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
lineSegments = new LineSegment[3];
lineSegments[0] = new LineSegment(p1, p2);
lineSegments[1] = new LineSegment(p2, p3);
lineSegments[2] = new LineSegment(p3, p1);
}
}
//To create a line segment
public struct LineSegment
{
//Start/end coordinates
public Vector3 p1, p2;
public LineSegment(Vector3 p1, Vector3 p2)
{
this.p1 = p1;
this.p2 = p2;
}
}
然后它检查连续的3个步骤
//The triangle-triangle intersection in 2D algorithm
bool IsTriangleTriangleIntersecting(Triangle triangle1, Triangle triangle2)
{
bool isIntersecting = false;
//Step 1. AABB intersection
if (IsIntersectingAABB(triangle1, triangle2))
{
//Step 2. Line segment - triangle intersection
if (AreAnyLineSegmentsIntersecting(triangle1, triangle2))
{
isIntersecting = true;
}
//Step 3. Point in triangle intersection - if one of the triangles is inside the other
else if (AreCornersIntersecting(triangle1, triangle2))
{
isIntersecting = true;
}
}
return isIntersecting;
}
这3个步骤本身的实现
//
// STEP 1 - Intersection: AABB
//
//Approximate the triangles with rectangles and see if they intersect with the AABB intersection algorithm
bool IsIntersectingAABB(Triangle t1, Triangle t2)
{
//Find the size of the bounding box
//Triangle 1
float t1_minX = Mathf.Min(t1.p1.x, Mathf.Min(t1.p2.x, t1.p3.x));
float t1_maxX = Mathf.Max(t1.p1.x, Mathf.Max(t1.p2.x, t1.p3.x));
float t1_minZ = Mathf.Min(t1.p1.z, Mathf.Min(t1.p2.z, t1.p3.z));
float t1_maxZ = Mathf.Max(t1.p1.z, Mathf.Max(t1.p2.z, t1.p3.z));
//Triangle 2
float t2_minX = Mathf.Min(t2.p1.x, Mathf.Min(t2.p2.x, t2.p3.x));
float t2_maxX = Mathf.Max(t2.p1.x, Mathf.Max(t2.p2.x, t2.p3.x));
float t2_minZ = Mathf.Min(t2.p1.z, Mathf.Min(t2.p2.z, t2.p3.z));
float t2_maxZ = Mathf.Max(t2.p1.z, Mathf.Max(t2.p2.z, t2.p3.z));
//Are the rectangles intersecting?
//If the min of one box in one dimension is greater than the max of another box then the boxes are not intersecting
//They have to intersect in 2 dimensions. We have to test if box 1 is to the left or box 2 and vice versa
bool isIntersecting = true;
//X axis
if (t1_minX > t2_maxX)
{
isIntersecting = false;
}
else if (t2_minX > t1_maxX)
{
isIntersecting = false;
}
//Z axis
else if (t1_minZ > t2_maxZ)
{
isIntersecting = false;
}
else if (t2_minZ > t1_maxZ)
{
isIntersecting = false;
}
//Debugging to display the approximated rectangles
//Box 1
Vector3 BB_1_centerPos = new Vector3((t1_maxX - t1_minX) * 0.5f + t1_minX, -0.2f, (t1_maxZ - t1_minZ) * 0.5f + t1_minZ);
box1Obj.transform.position = BB_1_centerPos;
box1Obj.transform.localScale = new Vector3(t1_maxX - t1_minX, 0.1f, t1_maxZ - t1_minZ);
//Box 2
Vector3 BB_2_centerPos = new Vector3((t2_maxX - t2_minX) * 0.5f + t2_minX, -0.2f, (t2_maxZ - t2_minZ) * 0.5f + t2_minZ);
box2Obj.transform.position = BB_2_centerPos;
box2Obj.transform.localScale = new Vector3(t2_maxX - t2_minX, 0.1f, t2_maxZ - t2_minZ);
return isIntersecting;
}
//
// STEP 2 - Intersection: Line segment - triangle
//
//Check if any of the edges that make up one of the triangles is intersecting with any of
//the edges of the other triangle
bool AreAnyLineSegmentsIntersecting(Triangle t1, Triangle t2)
{
bool isIntersecting = false;
//Loop through all edges
for (int i = 0; i < t1.lineSegments.Length; i++)
{
for (int j = 0; j < t2.lineSegments.Length; j++)
{
//The start/end coordinates of the current line segments
Vector3 t1_p1 = t1.lineSegments[i].p1;
Vector3 t1_p2 = t1.lineSegments[i].p2;
Vector3 t2_p1 = t2.lineSegments[j].p1;
Vector3 t2_p2 = t2.lineSegments[j].p2;
//Are they intersecting?
if (AreLineSegmentsIntersecting(t1_p1, t1_p2, t2_p1, t2_p2))
{
isIntersecting = true;
//To stop the outer for loop
i = int.MaxValue - 1;
break;
}
}
}
return isIntersecting;
}
//Check if 2 line segments are intersecting in 2d space
//http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/
//p1 and p2 belong to line 1, p3 and p4 belong to line 2
bool AreLineSegmentsIntersecting(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
{
bool isIntersecting = false;
float denominator = (p4.z - p3.z) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.z - p1.z);
//Make sure the denominator is != 0, if 0 the lines are parallel
if (denominator != 0)
{
float u_a = ((p4.x - p3.x) * (p1.z - p3.z) - (p4.z - p3.z) * (p1.x - p3.x)) / denominator;
float u_b = ((p2.x - p1.x) * (p1.z - p3.z) - (p2.z - p1.z) * (p1.x - p3.x)) / denominator;
//Is intersecting if u_a and u_b are between 0 and 1
if (u_a >= 0 && u_a <= 1 && u_b >= 0 && u_b <= 1)
{
isIntersecting = true;
}
}
return isIntersecting;
}
//
// STEP 3 - Intersection: Point in triangle
//
//There's a possibility that one of the triangles is smaller than the other
//So we have to check if any of the triangle's corners is inside the other triangle
bool AreCornersIntersecting(Triangle t1, Triangle t2)
{
bool isIntersecting = false;
//We only have to test one corner from each triangle
//Triangle 1 in triangle 2
if (IsPointInTriangle(t1.p1, t2.p1, t2.p2, t2.p3))
{
isIntersecting = true;
}
//Triangle 2 in triangle 1
else if (IsPointInTriangle(t2.p1, t1.p1, t1.p2, t1.p3))
{
isIntersecting = true;
}
return isIntersecting;
}
//Is a point p inside a triangle p1-p2-p3?
//From http://totologic.blogspot.se/2014/01/accurate-point-in-triangle-test.html
bool IsPointInTriangle(Vector3 p, Vector3 p1, Vector3 p2, Vector3 p3)
{
bool isWithinTriangle = false;
float denominator = ((p2.z - p3.z) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.z - p3.z));
float a = ((p2.z - p3.z) * (p.x - p3.x) + (p3.x - p2.x) * (p.z - p3.z)) / denominator;
float b = ((p3.z - p1.z) * (p.x - p3.x) + (p1.x - p3.x) * (p.z - p3.z)) / denominator;
float c = 1 - a - b;
//The point is within the triangle if 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1
if (a >= 0f && a <= 1f && b >= 0f && b <= 1f && c >= 0f && c <= 1f)
{
isWithinTriangle = true;
}
return isWithinTriangle;
}
太好了,我决定重新设计并稍微重构一下
/// <summary>
/// To store triangle data to get cleaner code
/// </summary>
[Serializable]
public struct Triangle2D
{
/// <summary>
/// To create a line segment
/// </summary>
private struct LineSegment2D
{
//Start/end coordinates
private readonly Vector2 _p1;
private readonly Vector2 _p2;
public LineSegment2D(Vector2 p1, Vector2 p2)
{
_p1 = p1;
_p2 = p2;
}
/// <summary>
/// Check if 2 line segments are intersecting in 2d space
/// <para>http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/</para>
/// </summary>
public bool IsIntersecting(LineSegment2D other)
{
var p3 = other._p1;
var p4 = other._p2;
var denominator = (p4.y - p3.y) * (_p2.x - _p1.x) - (p4.x - p3.x) * (_p2.y - _p1.y);
//Make sure the denominator is != 0, if 0 the lines are parallel
if (denominator == 0) return false;
var u_a = ((p4.x - p3.x) * (_p1.y - p3.y) - (p4.y - p3.y) * (_p1.x - p3.x)) / denominator;
var u_b = ((_p2.x - _p1.x) * (_p1.y - p3.y) - (_p2.y - _p1.y) * (_p1.x - p3.x)) / denominator;
//Is intersecting if u_a and u_b are between 0 and 1
return 0 <= u_a && u_a <= 1 && 0 <= u_b && u_b <= 1;
}
}
//Corners of the triangle
// Can be edited via the Inspector
[SerializeField] private Vector2 _a;
[SerializeField] private Vector2 _b;
[SerializeField] private Vector2 _c;
//The 3 line segments that make up this triangle
private LineSegment2D[] _lineSegments;
public Triangle2D(Vector2 a, Vector2 b, Vector2 c)
{
_a = a;
_b = b;
_c = c;
_lineSegments = new[]
{
new LineSegment2D(a, b),
new LineSegment2D(b, c),
new LineSegment2D(c, a)
};
}
/// <summary>
/// Is this triangle intersecting the other one?
/// </summary>
public bool IsIntersecting(Triangle2D other)
{
//Step 1. AABB intersection
if (IsIntersectingAABB(other))
{
//Step 2. Line segment - triangle intersection
if (AreAnyLineSegmentsIntersecting(other))
{
return true;
}
//Step 3. Point in triangle intersection - if one of the triangles is inside the other
if (AreCornersIntersecting(other))
{
return true;
}
}
return false;
}
/// <summary>
/// Is a point p inside this triangle
///<para>From http://totologic.blogspot.se/2014/01/accurate-point-in-triangle-test.html</para>
/// </summary>
public bool IsPointInTriangle(Vector2 p)
{
var denominator = (_b.y - _c.y) * (_a.x - _c.x) + (_c.x - _b.x) * (_a.y - _c.y);
var a = ((_b.y - _c.y) * (p.x - _c.x) + (_c.x - _b.x) * (p.y - _c.y)) / denominator;
var b = ((_c.y - _a.y) * (p.x - _c.x) + (_a.x - _c.x) * (p.y - _c.y)) / denominator;
var c = 1 - a - b;
return 0f <= a && a <= 1f && 0f <= b && b <= 1f && 0f <= c && c <= 1f;
}
/// <summary>
/// Approximate the triangles with rectangles and see if they intersect with the AABB intersection algorithm
/// </summary>
private bool IsIntersectingAABB(Triangle2D other)
{
//Find the size of the bounding boxes
// Bounding box Triangle 1
var t1_minX = Mathf.Min(_a.x, _b.x, _c.x);
var t1_maxX = Mathf.Max(_a.x, _b.x, _c.x);
var t1_minY = Mathf.Min(_a.y, _b.y, _c.y);
var t1_maxY = Mathf.Max(_a.y, _b.y, _c.y);
// Bounding box Triangle 2
var t2_minX = Mathf.Min(other._a.x, other._b.x, other._c.x);
var t2_maxX = Mathf.Max(other._a.x, other._b.x, other._c.x);
var t2_minY = Mathf.Min(other._a.y, other._b.y, other._c.y);
var t2_maxY = Mathf.Max(other._a.y, other._b.y, other._c.y);
//Are the rectangles intersecting?
//If the min of one box in one dimension is greater than the max of another box then the boxes are not intersecting
//They have to intersect in 2 dimensions. We have to test if box 1 is to the left or box 2 and vice versa
return t1_minX <= t2_maxX && t2_minX <= t1_maxX && t1_minY <= t2_maxY && t2_minY <= t1_maxY;
}
/// <summary>
/// Check if any of the edges that make up one of the triangles is intersecting with any of the edges of the other triangle
/// </summary>
private bool AreAnyLineSegmentsIntersecting(Triangle2D triangle2)
{
//Loop through all edges
foreach (var line1 in _lineSegments)
{
foreach (var line2 in triangle2._lineSegments)
{
//Are they intersecting?
if (line1.IsIntersecting(line2))
{
return true;
}
}
}
return false;
}
/// <summary>
/// There's a possibility that one of the triangles is smaller than the other
/// <para>So we have to check if any of the triangle's corners is inside the other triangle</para>
/// </summary>
private bool AreCornersIntersecting(Triangle2D other)
{
//We only have to test one corner from each triangle (otherwise the line segments would already intersect anyway)
//Triangle 1 in triangle 2 or //Triangle 2 in triangle 1
return other.IsPointInTriangle(_a) || IsPointInTriangle(other._a);
}
}
最后的用法是例如
// Create the two triangles structs
var triangle1 = new Triangle2D(triangle1[0], triangle1[1], triangle1[2]);
var triangle2 = new Triangle2D(triangle2[0], triangle2[1], triangle2[2]);
bool intersectingOnPlane = triangle1.IsIntersecting(triangle2);