判断是否相交
一般的写法
参考如下:
bool IsSegmentsIntersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
Vector2 ab = b - a;
Vector2 ac = c - a;
Vector2 ad = d - a;
Vector2 cd = d - c;
Vector2 ca = a - c;
Vector2 cb = b - c;
float crossAB_AC = CrossProduct(ab, ac);
float crossAB_AD = CrossProduct(ab, ad);
float crossCD_CA = CrossProduct(cd, ca);
float crossCD_CB = CrossProduct(cd, cb);
// Check general case
if (crossAB_AC * crossAB_AD < 0 && crossCD_CA * crossCD_CB < 0) return true;
// Check special cases where points are collinear
if (Mathf.Approximately(crossAB_AC, 0) && IsPointOnSegment(c, a, b)) return true;
if (Mathf.Approximately(crossAB_AD, 0) && IsPointOnSegment(d, a, b)) return true;
if (Mathf.Approximately(crossCD_CA, 0) && IsPointOnSegment(a, c, d)) return true;
if (Mathf.Approximately(crossCD_CB, 0) && IsPointOnSegment(b, c, d)) return true;
return false;
float CrossProduct(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; }
bool IsPointOnSegment(Vector2 p, Vector2 a, Vector2 b) { return Mathf.Min(a.x, b.x) <= p.x && p.x <= Mathf.Max(a.x, b.x) && Mathf.Min(a.y, b.y) <= p.y && p.y <= Mathf.Max(a.y, b.y); }
}
优化写法一
但是有些时候可能我们需要判断大量的线段互相之间是否相交,而且大部分情况是不相交的,我门希望能够快速判断出不相交的结果,这时候需要使用包围盒来提升速度,代码参考如下:
bool IsSegmentsIntersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
if (!BoundingBoxCheck(a, b, c, d)) return false;
Vector2 ab = b - a;
Vector2 ac = c - a;
Vector2 ad = d - a;
Vector2 cd = d - c;
Vector2 ca = a - c;
Vector2 cb = b - c;
float crossAB_AC = CrossProduct(ab, ac);
float crossAB_AD = CrossProduct(ab, ad);
float crossCD_CA = CrossProduct(cd, ca);
float crossCD_CB = CrossProduct(cd, cb);
if (crossAB_AC * crossAB_AD < 0 && crossCD_CA * crossCD_CB < 0)return true;
if (Mathf.Approximately(crossAB_AC, 0) && IsPointOnSegment(c, a, b)) return true;
if (Mathf.Approximately(crossAB_AD, 0) && IsPointOnSegment(d, a, b)) return true;
if (Mathf.Approximately(crossCD_CA, 0) && IsPointOnSegment(a, c, d)) return true;
if (Mathf.Approximately(crossCD_CB, 0) && IsPointOnSegment(b, c, d)) return true;
return false;
bool BoundingBoxCheck(Vector2 a, Vector2 b, Vector2 c, Vector2 d) { return Mathf.Max(a.x, b.x) >= Mathf.Min(c.x, d.x) && Mathf.Max(c.x, d.x) >= Mathf.Min(a.x, b.x) && Mathf.Max(a.y, b.y) >= Mathf.Min(c.y, d.y) && Mathf.Max(c.y, d.y) >= Mathf.Min(a.y, b.y); }
float CrossProduct(Vector2 a, Vector2 b) { return a.x * b.y - a.y * b.x; }
bool IsPointOnSegment(Vector2 p, Vector2 a, Vector2 b) { return Mathf.Min(a.x, b.x) <= p.x && p.x <= Mathf.Max(a.x, b.x) && Mathf.Min(a.y, b.y) <= p.y && p.y <= Mathf.Max(a.y, b.y); }
}
优化写法二
下面是快速判断不相交的另一种写法,思想上差不多,代码稍微简洁一点儿:
bool IsSegmentsIntersect(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4)
{
//首先检查以两条线段为对角线的两个矩形是否相交
//对于线段p1p2和线段p3p4,分别求出两条线段在x和y轴方向上的最大最小值
float maxX1 = Mathf.Max(p1.x, p2.x);
float minX1 = Mathf.Min(p1.x, p2.x);
float maxY1 = Mathf.Max(p1.y, p2.y);
float minY1 = Mathf.Min(p1.y, p2.y);
float maxX2 = Mathf.Max(p3.x, p4.x);
float minX2 = Mathf.Min(p3.x, p4.x);
float maxY2 = Mathf.Max(p3.y, p4.y);
float minY2 = Mathf.Min(p3.y, p4.y);
//如果满足以下两个条件,则可能相交,否则一定不相交
//线段p1p2的最大x值大于等于线段p3p4的最小x值
//线段p1p2的最小x值小于等于线段p3p4的最大x值
//线段p1p2的最大y值大于等于线段p3p4的最小x值
//线段p1p2的最小y值小于等于线段p3p4的最大x值
if (maxX1 < minX2 || maxX2 < minX1 || maxY1 < minY2 || maxY2 < minY1) return false;
//如果向量p1-p3和p2-p3的叉乘结果与向量p2-p1和p4-p1的叉乘结果异号,则说明点p3和p4在'直线'p1p2的两侧。
//同理判断点p1和p2在'直线'p3p4的两侧
float crossProduct1 = (p3.x - p1.x) * (p2.y - p1.y) - (p3.y - p1.y) * (p2.x - p1.x);
float crossProduct2 = (p4.x - p1.x) * (p2.y - p1.y) - (p4.y - p1.y) * (p2.x - p1.x);
float crossProduct3 = (p1.x - p3.x) * (p4.y - p3.y) - (p1.y - p3.y) * (p4.x - p3.x);
float crossProduct4 = (p2.x - p3.x) * (p4.y - p3.y) - (p2.y - p3.y) * (p4.x - p3.x);
return crossProduct1 * crossProduct2 <= 0 && crossProduct3 * crossProduct4 <= 0;
}
计算交点位置
如果已知两个二维线段必定相交,可通过以下方法计算其交点位置。
代码参考如下:
Vector2 GetIntersectionPoint(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4)
{
float x1 = p1.x, y1 = p1.y;
float x2 = p2.x, y2 = p2.y;
float x3 = p3.x, y3 = p3.y;
float x4 = p4.x, y4 = p4.y;
float denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
float t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denominator;
return new Vector2(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
}