我正在开发一个游戏,需要一个可以计算交点的算法。我已经解决了这个问题,但是我的方法非常恶劣,希望这里有人能提供更优雅的解决方案。
一对点表示它们之间画的线段的端点。给定两对点,绘制的线段是否相交,如果相交,在哪个点上?
例如,称线段为 (A.x, A.y)-(B.x, B.y) 和 (C.x, C.y)-(D.x, D.y)
有人能想到一个解决方案吗?任何语言的解决方案都可以。
编辑: 我应该更清楚地指出的一点是,如果交点超出线段长度,则算法必须返回 false。
我正在开发一个游戏,需要一个可以计算交点的算法。我已经解决了这个问题,但是我的方法非常恶劣,希望这里有人能提供更优雅的解决方案。
一对点表示它们之间画的线段的端点。给定两对点,绘制的线段是否相交,如果相交,在哪个点上?
例如,称线段为 (A.x, A.y)-(B.x, B.y) 和 (C.x, C.y)-(D.x, D.y)
有人能想到一个解决方案吗?任何语言的解决方案都可以。
编辑: 我应该更清楚地指出的一点是,如果交点超出线段长度,则算法必须返回 false。
如果你有机会的话,真的应该检查一下《碰撞检测圣经》——“实时碰撞检测”,如果你计划进行任何非平凡的事情。无论如何,进行一组线交点测试都是很昂贵的。你所做的是在复杂的多边形上使用诸如轴对齐或定向包围盒之类的东西。这将使您能够快速地进行每个“对象”之间的最坏情况O(N ^ 2)碰撞检查。然后,您可以通过只检查彼此靠近的对象的交点来进一步加快速度,例如使用空间树(二元划分或四叉树)。
这样可以剪枝许多碰撞测试。最好的优化是根本不做某件事。只有当包围盒之间发生碰撞时,才进行昂贵的线交点检查,以确定对象是否真正相交。这使您能够将对象数量扩展到一个可接受的范围内,同时保持速度合理。
float x12 = x1 - x2;
float x34 = x3 - x4;
float y12 = y1 - y2;
float y34 = y3 - y4;
float c = x12 * y34 - y12 * x34;
if (fabs(c) < 0.01)
{
// No intersection
return false;
}
else
{
// Intersection
float a = x1 * y2 - y1 * x2;
float b = x3 * y4 - y3 * x4;
float x = (a * x34 - b * x12) / c;
float y = (a * y34 - b * y12) / c;
return true;
}
这些公式来自于:
线线相交 - 维基百科
public struct PointD
{
public double X { get; set; }
public double Y { get; set; }
}
/// <summary>
/// Find the intersection point between two lines.
/// </summary>
/// <param name="IntersectPoint">The intersection point. A <see cref="Esteem.Geometry.PointD">PointD</see> structure.</param>
/// <param name="L1StartPoint">The starting point of first line. A PointD structure.</param>
/// <param name="L1EndPoint">The end point of first line. A PointD structure.</param>
/// <param name="L2StartPoint">The starting point of second line. A PointD structure.</param>
/// <param name="L2EndPoint">The end point of second line. A PointD structure.</param>
/// <param name="L1IntersectPos">The intersection position at first line.</param>
/// <param name="L2IntersectPos">The intersection position at second line.</param>
/// <returns>Returns a boolean. True if there is intersection, false otherwise.</returns>
/// <remarks>The formula is taken from comp.graphics.algorithms Frequently Asked Questions.</remarks>
public static bool LineIntersect(out PointD IntersectPoint, PointD L1StartPoint, PointD L1EndPoint, PointD L2StartPoint, PointD L2EndPoint, out double L1IntersectPos, out double L2IntersectPos)
{
IntersectPoint = new PointD();
double ay_cy, ax_cx, px, py;
double dx_cx = L2EndPoint.X - L2StartPoint.X,
dy_cy = L2EndPoint.Y - L2StartPoint.Y,
bx_ax = L1EndPoint.X - L1StartPoint.X,
by_ay = L1EndPoint.Y - L1StartPoint.Y;
double de = (bx_ax) * (dy_cy) - (by_ay) * (dx_cx);
//double tor = 1.0E-10; //tolerance
L1IntersectPos = 0; L2IntersectPos = 0;
if (Math.Abs(de)<0.01)
return false;
//if (de > -tor && de < tor) return false; //line is in parallel
ax_cx = L1StartPoint.X - L2StartPoint.X;
ay_cy = L1StartPoint.Y - L2StartPoint.Y;
double r = ((ay_cy) * (dx_cx) - (ax_cx) * (dy_cy)) / de;
double s = ((ay_cy) * (bx_ax) - (ax_cx) * (by_ay)) / de;
px = L1StartPoint.X + r * (bx_ax);
py = L1StartPoint.Y + r * (by_ay);
IntersectPoint.X = px; //return the intersection point
IntersectPoint.Y = py; //return the intersection position
L1IntersectPos = r; L2IntersectPos = s;
return true; //indicate there is intersection
}
要检查交点是否不超出线的原始长度,只需确保0<r<1
和0<s<1
一个简单的优化方法可以节省很多时间,就是在进行交点计算之前检查线条的轴对齐边界框。
如果边界框完全不相交,则可以确定没有交点。
当然,这取决于你拥有的数据。如果每次检查都很可能出现交点,那么你可能会浪费时间在一个总是正确的检查上。
/// <summary>
/// Returns the intersection point of the given lines.
/// Returns Empty if the lines do not intersect.
/// Source: http://mathworld.wolfram.com/Line-LineIntersection.html
/// </summary>
public static PointF LineIntersection(PointF v1, PointF v2, PointF v3, PointF v4)
{
float tolerance = 0.000001f;
float a = Det2(v1.X - v2.X, v1.Y - v2.Y, v3.X - v4.X, v3.Y - v4.Y);
if (Math.Abs(a) < float.Epsilon) return PointF.Empty; // Lines are parallel
float d1 = Det2(v1.X, v1.Y, v2.X, v2.Y);
float d2 = Det2(v3.X, v3.Y, v4.X, v4.Y);
float x = Det2(d1, v1.X - v2.X, d2, v3.X - v4.X) / a;
float y = Det2(d1, v1.Y - v2.Y, d2, v3.Y - v4.Y) / a;
if (x < Math.Min(v1.X, v2.X) - tolerance || x > Math.Max(v1.X, v2.X) + tolerance) return PointF.Empty;
if (y < Math.Min(v1.Y, v2.Y) - tolerance || y > Math.Max(v1.Y, v2.Y) + tolerance) return PointF.Empty;
if (x < Math.Min(v3.X, v4.X) - tolerance || x > Math.Max(v3.X, v4.X) + tolerance) return PointF.Empty;
if (y < Math.Min(v3.Y, v4.Y) - tolerance || y > Math.Max(v3.Y, v4.Y) + tolerance) return PointF.Empty;
return new PointF(x, y);
}
/// <summary>
/// Returns the determinant of the 2x2 matrix defined as
/// <list>
/// <item>| x1 x2 |</item>
/// <item>| y1 y2 |</item>
/// </list>
/// </summary>
public static float Det2(float x1, float x2, float y1, float y2)
{
return (x1 * y2 - y1 * x2);
}
(如果我还没有想出如何这样做,我会将此作为评论留下。)
我想补充一下,作为边界框测试的替代/补充,您还可以测试线段中点之间的距离是否大于线段长度的一半。如果是,则这些线段不可能相交。
想象一下每条线段的最小包围圆,然后测试圆形相交。这允许进行预计算(至少对于静态线段和保持其长度的线段),并且是排除许多潜在相交的有效方法。
好的,数学和标量积可以帮助到这里。
- 假设你想要相交线段[AB]和[CD]
- 假设线段的交点是M
当且仅当以下条件满足时,M在线段[ÅB]内:
Vector(AB) . Vector(AM) >= 0
并且
Vector(AB) . Vector(MB) >= 0
其中"."表示标量积:u . v = ux * vx + uy * vy
所以,你的算法是:
- 找到M
- 当且仅当下面4个方程式为真时,M同时在两个线段之内
Vector(AB) . Vector(AM) >= 0
Vector(AB) . Vector(MB) >= 0
Vector(CD) . Vector(CM) >= 0
Vector(CD) . Vector(MD) >= 0
希望这能有所帮助。
private function Loop(e:Event):void
{
var x12:Number = Ball1.x - Ball2.x;
var x34:Number = Ball3.x - Ball4.x;
var y12:Number = Ball1.y - Ball2.y;
var y34:Number = Ball3.y - Ball4.y;
// Det
var c:Number = x12 * y34 - y12 * x34;
if (Math.abs(c) < 0.01)
{
Circle.visible = false;
}
else
{
var a:Number = Ball1.x * Ball2.y - Ball1.y * Ball2.x;
var b:Number = Ball3.x * Ball4.y - Ball3.y * Ball4.x;
var px:Number = (a * x34 - b * x12) / c;
var py:Number = (a * y34 - b * y12) / c;
var Btwn12x:Boolean = (px >= Math.min(Ball1.x, Ball2.x)) && (px <= Math.max(Ball1.x, Ball2.x));
var Btwn12y:Boolean = (py >= Math.min(Ball1.y, Ball2.y)) && (py <= Math.max(Ball1.y, Ball2.y));
var Btwn34x:Boolean = (px >= Math.min(Ball3.x, Ball4.x)) && (px <= Math.max(Ball3.x, Ball4.x));
var Btwn34y:Boolean = (py >= Math.min(Ball3.y, Ball4.y)) && (py <= Math.max(Ball3.y, Ball4.y));
var Btwn12:Boolean = Btwn12x && Btwn12y;
var Btwn34:Boolean = Btwn34x && Btwn34y;
if(Btwn12 && Btwn34)
{
Circle.visible = true;
Circle.x = px;
Circle.y = py;
}
else
{
Circle.visible = false;
}
}
}