2010-02-12 15 views
9

的一致子集這個問題是有關:檢測兩個重合線段

但是請注意,一個有趣的子問題在大多數解決方案中完全掩蓋了,即使存在三種子情況,這些問題也只是返回null:

  • 一致,但不重疊
  • 例如,我們可以設計這樣一個C#函數感人只是點和重合
  • 重疊/重合線子段

public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2) 

其中(a1,a2)是一條線段,(b1,b2)是另一條線段。

此功能需要涵蓋大多數實現或解釋所掩蓋的所有奇怪情況。爲了解釋的重合線的古怪,該函數可以返回的PointF的數組:

  • 零結果的點(或空),如果線是平行的或不相交(無限線相交但線段是不相交,或線是平行
  • 一個結果點(包含相交位置),如果他們相交或者如果它們是一致的在一個點
  • 兩個結果的點(對於重疊線段的一部分),如果兩行是重合
+0

我意識到這個問題只是被問到,所以你可以發佈你的答案。您應該將其標記爲已接受的答案。在這個問題上使用較少的對抗性語言也不會有什麼壞處,FWIW。 – tfinniga

+0

@tfinniga:我沒有意識到這是對抗性的,直到我重寫了它,並且讓它聽起來像是一個謎題,而不是一個需求。我的目標不是讓其他人爲我做這些工作,而是要證明沒有其他的實施甚至是工作。 (如果你能證明我錯了,並找到一個非常好的解決方案(現在就是這樣),完美地工作,我會很樂意給你100代表)。 –

+0

謝謝,我認爲這好多了。針對這種共同需求的防彈實施是有價值的,並且改寫的問題更加令人愉快。 – tfinniga

回答

7

聽起來像你有你的解決方案,這是偉大的。我有一些改進它的建議。

該方法有一個主要的可用性問題,因爲它非常容易理解(1)參數意味着什麼,以及(2)結果是什麼意思。這兩個都是你想要使用該方法的小謎題。

我會更傾向於使用類型系統來更清楚地說明這種方法的作用。

我會首先定義一個類型 - 可能是一個結構,特別是如果它是不可變的 - 稱爲LineSegment。一個LineSegment由兩個表示結束點的PointF結構組成。第二,如果需要表示兩個或多個基因座聯合的基因座,我將定義一個抽象基類型「基因座」和派生類型EmptyLocus,PointLocus,LineSegmentLocus和可能的UnionLocus。一個空的軌跡只是一個單獨的點,一個點軌跡只是一個點,等等。

現在你的方法簽名變得更加清晰:

static Locus Intersect(LineSegment l1, LineSegment l2) 

這個方法有兩個線段和計算的點的軌跡就是它們的交集 - 無論是空的,單點,或線段。

請注意,您可以概括此方法。計算線段與線段的交點是棘手的,但是計算線段與點或點與點的交點或任何具有空軌跡的點是容易。將交集擴展到任意位置的聯合並不難。因此,你實際上可以寫:

static Locus Intersect(Locus l1, Locus l2) 

哎,現在它變得清晰,相交可能是在軌跡的擴展方法:

static Locus Intersect(this Locus l1, Locus l2) 

從的PointF到PointLocus和線段添加的隱式轉換到LineSegmentLocus ,你可以說像

var point = new PointF(whatever); 
var lineseg = new LineSegment(somepoint, someotherpoint); 
var intersection = lineseg.Intersect(point); 
if (intersection is EmptyLocus) ... 

使用類型系統很好可以大大提高程序的可讀性。

+1

感謝您的建議和擴展。 –

+0

這是一個偉大的方法Eric,我之前使用枚舉與其他對象結合來提供結果。這是優雅而優越的。謝謝。 –

13
// port of this JavaScript code with some changes: 
    // http://www.kevlindev.com/gui/math/intersection/Intersection.js 
    // found here: 
    // http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/563240#563240 

public class Intersector 
{ 
    static double MyEpsilon = 0.00001; 

    private static float[] OverlapIntervals(float ub1, float ub2) 
    { 
     float l = Math.Min(ub1, ub2); 
     float r = Math.Max(ub1, ub2); 
     float A = Math.Max(0, l); 
     float B = Math.Min(1, r); 
     if (A > B) // no intersection 
      return new float[] { }; 
     else if (A == B) 
      return new float[] { A }; 
     else // if (A < B) 
      return new float[] { A, B }; 
    } 

    // IMPORTANT: a1 and a2 cannot be the same, e.g. a1--a2 is a true segment, not a point 
    // b1/b2 may be the same (b1--b2 is a point) 
    private static PointF[] OneD_Intersection(PointF a1, PointF a2, PointF b1, PointF b2) 
    { 
     //float ua1 = 0.0f; // by definition 
     //float ua2 = 1.0f; // by definition 
     float ub1, ub2; 

     float denomx = a2.X - a1.X; 
     float denomy = a2.Y - a1.Y; 

     if (Math.Abs(denomx) > Math.Abs(denomy)) 
     { 
      ub1 = (b1.X - a1.X)/denomx; 
      ub2 = (b2.X - a1.X)/denomx; 
     } 
     else 
     { 
      ub1 = (b1.Y - a1.Y)/denomy; 
      ub2 = (b2.Y - a1.Y)/denomy; 
     } 

     List<PointF> ret = new List<PointF>(); 
     float[] interval = OverlapIntervals(ub1, ub2); 
     foreach (float f in interval) 
     { 
      float x = a2.X * f + a1.X * (1.0f - f); 
      float y = a2.Y * f + a1.Y * (1.0f - f); 
      PointF p = new PointF(x, y); 
      ret.Add(p); 
     } 
     return ret.ToArray(); 
    } 

    private static bool PointOnLine(PointF p, PointF a1, PointF a2) 
    { 
     float dummyU = 0.0f; 
     double d = DistFromSeg(p, a1, a2, MyEpsilon, ref dummyU); 
     return d < MyEpsilon; 
    } 

    private static double DistFromSeg(PointF p, PointF q0, PointF q1, double radius, ref float u) 
    { 
     // formula here: 
     //http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html 
     // where x0,y0 = p 
     //  x1,y1 = q0 
     //  x2,y2 = q1 
     double dx21 = q1.X - q0.X; 
     double dy21 = q1.Y - q0.Y; 
     double dx10 = q0.X - p.X; 
     double dy10 = q0.Y - p.Y; 
     double segLength = Math.Sqrt(dx21 * dx21 + dy21 * dy21); 
     if (segLength < MyEpsilon) 
      throw new Exception("Expected line segment, not point."); 
     double num = Math.Abs(dx21 * dy10 - dx10 * dy21); 
     double d = num/segLength; 
     return d; 
    } 

    // this is the general case. Really really general 
    public static PointF[] Intersection(PointF a1, PointF a2, PointF b1, PointF b2) 
    { 
     if (a1.Equals(a2) && b1.Equals(b2)) 
     { 
      // both "segments" are points, return either point 
      if (a1.Equals(b1)) 
       return new PointF[] { a1 }; 
      else // both "segments" are different points, return empty set 
       return new PointF[] { }; 
     } 
     else if (b1.Equals(b2)) // b is a point, a is a segment 
     { 
      if (PointOnLine(b1, a1, a2)) 
       return new PointF[] { b1 }; 
      else 
       return new PointF[] { }; 
     } 
     else if (a1.Equals(a2)) // a is a point, b is a segment 
     { 
      if (PointOnLine(a1, b1, b2)) 
       return new PointF[] { a1 }; 
      else 
       return new PointF[] { }; 
     } 

     // at this point we know both a and b are actual segments 

     float ua_t = (b2.X - b1.X) * (a1.Y - b1.Y) - (b2.Y - b1.Y) * (a1.X - b1.X); 
     float ub_t = (a2.X - a1.X) * (a1.Y - b1.Y) - (a2.Y - a1.Y) * (a1.X - b1.X); 
     float u_b = (b2.Y - b1.Y) * (a2.X - a1.X) - (b2.X - b1.X) * (a2.Y - a1.Y); 

     // Infinite lines intersect somewhere 
     if (!(-MyEpsilon < u_b && u_b < MyEpsilon)) // e.g. u_b != 0.0 
     { 
      float ua = ua_t/u_b; 
      float ub = ub_t/u_b; 
      if (0.0f <= ua && ua <= 1.0f && 0.0f <= ub && ub <= 1.0f) 
      { 
       // Intersection 
       return new PointF[] { 
        new PointF(a1.X + ua * (a2.X - a1.X), 
         a1.Y + ua * (a2.Y - a1.Y)) }; 
      } 
      else 
      { 
       // No Intersection 
       return new PointF[] { }; 
      } 
     } 
     else // lines (not just segments) are parallel or the same line 
     { 
      // Coincident 
      // find the common overlapping section of the lines 
      // first find the distance (squared) from one point (a1) to each point 
      if ((-MyEpsilon < ua_t && ua_t < MyEpsilon) 
       || (-MyEpsilon < ub_t && ub_t < MyEpsilon)) 
      { 
       if (a1.Equals(a2)) // danger! 
        return OneD_Intersection(b1, b2, a1, a2); 
       else // safe 
        return OneD_Intersection(a1, a2, b1, b2); 
      } 
      else 
      { 
       // Parallel 
       return new PointF[] { }; 
      } 
     } 
    } 


} 

下面是測試代碼:

public class IntersectTest 
    { 
     public static void PrintPoints(PointF[] pf) 
     { 
      if (pf == null || pf.Length < 1) 
       System.Console.WriteLine("Doesn't intersect"); 
      else if (pf.Length == 1) 
      { 
       System.Console.WriteLine(pf[0]); 
      } 
      else if (pf.Length == 2) 
      { 
       System.Console.WriteLine(pf[0] + " -- " + pf[1]); 
      } 
     } 

     public static void TestIntersect(PointF a1, PointF a2, PointF b1, PointF b2) 
     { 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("Does  " + a1 + " -- " + a2); 
      System.Console.WriteLine("intersect " + b1 + " -- " + b2 + " and if so, where?"); 
      System.Console.WriteLine(""); 
      PointF[] result = Intersect.Intersection(a1, a2, b1, b2); 
      PrintPoints(result); 
     } 

     public static void Main() 
     { 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("line segments intersect"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(100, 100), 
          new PointF(100, 0), 
          new PointF(0, 100)); 
      TestIntersect(new PointF(5, 17), 
          new PointF(100, 100), 
          new PointF(100, 29), 
          new PointF(8, 100)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("just touching points and lines cross"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(25, 25), 
          new PointF(25, 25), 
          new PointF(100, 75)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("parallel"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(0, 100), 
          new PointF(100, 0), 
          new PointF(100, 100)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----"); 
      System.Console.WriteLine("lines cross but segments don't intersect"); 
      TestIntersect(new PointF(50, 50), 
          new PointF(100, 100), 
          new PointF(0, 25), 
          new PointF(25, 0)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("coincident but do not overlap!"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(25, 25), 
          new PointF(75, 75), 
          new PointF(100, 100)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("touching points and coincident!"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(25, 25), 
          new PointF(25, 25), 
          new PointF(100, 100)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine("overlap/coincident"); 
      TestIntersect(new PointF(0, 0), 
          new PointF(75, 75), 
          new PointF(25, 25), 
          new PointF(100, 100)); 
      TestIntersect(new PointF(0, 0), 
          new PointF(100, 100), 
          new PointF(0, 0), 
          new PointF(100, 100)); 
      System.Console.WriteLine("----------------------------------------------------------"); 
      System.Console.WriteLine(""); 

      while (!System.Console.KeyAvailable) { } 
     } 

    } 

,這裏是輸出:

 
---------------------------------------------------------- 
line segments intersect 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=100, Y=100} 
intersect {X=100, Y=0} -- {X=0, Y=100} and if so, where? 

{X=50, Y=50} 
---------------------------------------------------------- 
Does  {X=5, Y=17} -- {X=100, Y=100} 
intersect {X=100, Y=29} -- {X=8, Y=100} and if so, where? 

{X=56.85001, Y=62.30054} 
---------------------------------------------------------- 

---------------------------------------------------------- 
just touching points and lines cross 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=25, Y=25} 
intersect {X=25, Y=25} -- {X=100, Y=75} and if so, where? 

{X=25, Y=25} 
---------------------------------------------------------- 

---------------------------------------------------------- 
parallel 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=0, Y=100} 
intersect {X=100, Y=0} -- {X=100, Y=100} and if so, where? 

Doesn't intersect 
---------------------------------------------------------- 

---- 
lines cross but segments don't intersect 
---------------------------------------------------------- 
Does  {X=50, Y=50} -- {X=100, Y=100} 
intersect {X=0, Y=25} -- {X=25, Y=0} and if so, where? 

Doesn't intersect 
---------------------------------------------------------- 

---------------------------------------------------------- 
coincident but do not overlap! 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=25, Y=25} 
intersect {X=75, Y=75} -- {X=100, Y=100} and if so, where? 

Doesn't intersect 
---------------------------------------------------------- 

---------------------------------------------------------- 
touching points and coincident! 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=25, Y=25} 
intersect {X=25, Y=25} -- {X=100, Y=100} and if so, where? 

{X=25, Y=25} 
---------------------------------------------------------- 

---------------------------------------------------------- 
overlap/coincident 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=75, Y=75} 
intersect {X=25, Y=25} -- {X=100, Y=100} and if so, where? 

{X=25, Y=25} -- {X=75, Y=75} 
---------------------------------------------------------- 
Does  {X=0, Y=0} -- {X=100, Y=100} 
intersect {X=0, Y=0} -- {X=100, Y=100} and if so, where? 

{X=0, Y=0} -- {X=100, Y=100} 
---------------------------------------------------------- 
+0

我只是因爲覺得在這裏提供一個完整的代碼示例違反了網站的精神而下了決心。 OP似乎並不想學習,他們只是想讓別人完成他們的任務。 –

+0

錯......我沒有注意到你也發佈了這個問題= P。我已經刪除了downvote。 –

+0

......不然,我被告知我的10分鐘舊帖子太舊了,無法更改。我已經提出了另一個答案來彌補它。抱歉。 :) –

-3

這真的很簡單。如果你有兩條線,你可以找到y = mx + b形式的兩個方程。例如:

y = 2x + 5 
y = x - 3 

所以,兩線相交時,Y1 = Y2在同一x座標,所以......

2x + 5 = x - 3 
x + 5 = -3 
x = -8 

當x = -8 Y1 = Y2和你已經找到了交點。這應該是非常簡單的翻譯成代碼。如果沒有交點,則每條線的斜率將爲m,在這種情況下,您甚至不需要執行計算。

+0

這也是很微妙的錯誤:當點在上面時在彼此之下,斜坡是無限的,所有的地獄破壞。 –

+0

當每條線的斜率相等時,它們仍然可以在一點或線段相交,甚至完全不重疊。 –

2

@Jared,很好的問題和很好的答案。

如約瑟夫奧洛克的CGA常見問題解答here中所解釋的,通過將點的位置表示爲單個參數的函數,可以簡化問題。

令R爲一個參數,以指示沿包含AB, 具有以下含義的線P的 位置:

 r=0  P = A 
     r=1  P = B 
     r<0  P is on the backward extension of AB 
     r>1  P is on the forward extension of AB 
     0<r<1 P is interior to AB 

沿着這些線路的思考,對於任何點C(CX,CY )我們計算R如下:

double deltax = bx - ax; 
double deltay = by - ay; 
double l2 = deltax * deltax + deltay * deltay; 
double r = (ay - cy) * (ay - by) - (ax - cx) * (bx - ax)/l2; 

這應該更容易計算的重疊部分。

請注意,我們避免採取平方根,因爲只需要長度的平方。

+0

加上鍊接參考。這對我很有用 – Gnomo