2010-11-17 194 views
4

我需要爲GraphicsPath對象創建擴展方法,以確定GraphicsPath是順時針還是逆時針纏繞的。GraphicsPath.IsClockWise()擴展方法

事情是這樣的:

public static bool IsClockwise(GraphicsPath gp) 
    { 
     bool bClockWise = false; 
     PointF[] pts = gp.PathPoints; 

     foreach (PointF pt in pts) 
     { 
      //?? 
     } 

     return bClockWise; 
    } 

注:我只是假設,我們需要在這裏GraphicsPath的點foreach,但如果有更好的方法,歡迎隨時提供。

這樣做的原因是因爲GraphicsPathFillMode確定是否相鄰GraphicsPaths的重疊部分被「填充」(Winding)或「洞」(Alternate)。但是,只有當相鄰的GraphicsPath被纏繞在相同的方向時,這纔是真實的。

Fillmode Winding vs Alternate

如果兩個路徑纏繞在相對的方向,即使是設置在(不想要的)孔FillModeWinding結果。

有趣的是,GraphicsPath的確實有.Reverse()方法,它允許一個改變路徑的方向(這確實解決該問題),但它不可能知道什麼時候需要的GraphicsPath無需知道待.Reversed路徑的方向。

你能用這個擴展方法來幫忙嗎?

+0

我覺得這個問題惡化到不得不找一個內點。對於凸多邊形來說這很容易。 – 2010-11-17 21:03:10

回答

4

我主要把這件事扔到那裏,因爲這個問題引起了我的興趣,這是我沒有專門知識,我想討論。

我拿了Point in Polygon pseudo-code,並試圖讓它適合你的情況。看起來你只是想確定一些東西是順時針還是逆時針纏繞的。這裏有一個簡單的測試和一個非常簡單的(邊緣粗糙的)C#實現。

[Test] 
public void Test_DetermineWindingDirection() 
{ 

    GraphicsPath path = new GraphicsPath(); 

    // Set up points collection 
    PointF[] pts = new[] {new PointF(10, 60), 
        new PointF(50, 110), 
        new PointF(90, 60)}; 
    path.AddLines(pts); 

    foreach(var point in path.PathPoints) 
    { 
     Console.WriteLine("X: {0}, Y: {1}",point.X, point.Y); 
    } 

    WindingDirection windingVal = DetermineWindingDirection(path.PathPoints); 
    Console.WriteLine("Winding value: {0}", windingVal); 
    Assert.AreEqual(WindingDirection.Clockwise, windingVal); 

    path.Reverse(); 
    foreach(var point in path.PathPoints) 
    { 
     Console.WriteLine("X: {0}, Y: {1}",point.X, point.Y); 
    } 

    windingVal = DetermineWindingDirection(path.PathPoints); 
    Console.WriteLine("Winding value: {0}", windingVal); 
    Assert.AreEqual(WindingDirection.CounterClockWise, windingVal); 
} 

public enum WindingDirection 
{ 
    Clockwise, 
    CounterClockWise 
} 

public static WindingDirection DetermineWindingDirection(PointF[] polygon) 
{ 
    // find a point in the middle 
    float middleX = polygon.Average(p => p.X); 
    float middleY = polygon.Average(p => p.Y); 
    var pointInPolygon = new PointF(middleX, middleY); 
    Console.WriteLine("MiddlePoint = {0}", pointInPolygon); 

    double w = 0; 
    var points = polygon.Select(point => 
            { 
             var newPoint = new PointF(point.X - pointInPolygon.X, point.Y - pointInPolygon.Y); 
             Console.WriteLine("New Point: {0}", newPoint); 
             return newPoint; 
            }).ToList(); 

    for (int i = 0; i < points.Count; i++) 
    { 
     var secondPoint = i + 1 == points.Count ? 0 : i + 1; 
     double X = points[i].X; 
     double Xp1 = points[secondPoint].X; 
     double Y = points[i].Y; 
     double Yp1 = points[secondPoint].Y; 

     if (Y * Yp1 < 0) 
     { 
      double r = X + ((Y * (Xp1 - X))/(Y - Yp1)); 
      if (r > 0) 
       if (Y < 0) 
        w = w + 1; 
       else 
        w = w - 1; 
     } 
     else if ((Y == 0) && (X > 0)) 
     { 
      if (Yp1 > 0) 
       w = w + .5; 
      else 
       w = w - .5; 
     } 
     else if ((Yp1 == 0) && (Xp1 > 0)) 
     { 
      if (Y < 0) 
       w = w + .5; 
      else 
       w = w - .5; 
     } 
    } 
    return w > 0 ? WindingDirection.ClockWise : WindingDirection.CounterClockwise; 
} 

,我已經擺在那的不同使得這個特定於您的問題的一點是,我從所有點計算X和Y的平均值,並用其作爲「點面」值。所以,只傳入一些點數,它會在它們中間找到一些東西。

,我也做了這樣的假設V I + 1應該當它到達的點陣列,這樣

if (i + 1 == points.Count) 
    // use points[0] instead 

這並沒有經過優化的邊界環繞,它只是東西可能會回答你的問題。也許你已經自己想出了這個。

在這裏我們希望爲有建設性的批評。 :)

+0

真棒,戴夫!我迫不及待地把這一切都交給了考驗...... – Flipster 2010-11-18 00:19:30

+0

男人,這些都是如此偉大的答案。有沒有什麼辦法可以「接受」他們三個人,因爲他們都可以做到這一點(儘管通過編寫源代碼,你肯定會走得更遠)。 – Flipster 2010-11-18 00:20:19

+0

@FlipScript - 我很高興它有幫助。這真的很有趣。此外,將我的方法轉換爲根據原始問題返回true/false的擴展方法非常簡單。祝你好運! – 2010-11-18 00:27:31

2

不,您將不得不循環點確定收尾順序。網絡上有許多算法,例如。

http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf

我想,當我需要實現它,我用從圖形寶石本本的算法,修改爲地球表面(地理空間應用程序)。關閉我的頭頂我認爲它乘以分量矢量並將它們相加。總額的標誌給了你清盤的順序。

+0

非常有趣,Winwaed。該文看起來很有希望!我沒有花很多時間試圖瞭解這一切,但「軸交叉法纏繞數算法」看起來幾乎像在最後一頁上的僞代碼。 – Flipster 2010-11-17 20:42:55

+0

有沒有想過ViVi + 1的「障礙」是什麼意思?我對這種表示法並不熟悉。 – Flipster 2010-11-17 20:44:18

+0

從上下文來看,我認爲它意味着從Vi到Vi + 1的矢量。我看過相同的,但用箭頭而不是酒吧。 – winwaed 2010-11-17 20:46:58

1

See my post here。我認爲它有一個正是你想要的例子。

+0

謝謝#wageoghe。我卡與.net 3.5,但即使沒有.ZIP的東西,也有一些有趣的代碼位,我需要更仔細(特別是「GreensTheorem」)。謝謝! – Flipster 2010-11-17 21:53:41

+0

如果你在SO上搜索一下,你應該能夠找到你可以在3.5上使用的Zip的實現,因爲你沒有4.0的Zip的訪問權限。我也會看,並嘗試發佈。現在遠離電腦,所以不容易從iPhone發佈等。 – wageoghe 2010-11-17 22:09:35

+0

這是Eric Lippert的一個實現。 http://blogs.msdn.com/b/ericlippert/archive/2009/05/07/zip-me-up.aspx – wageoghe 2010-11-17 22:32:26

0

編輯:

忽略這一切之下。我發現了這個問題。

上述代碼的最後一行應改爲從:

return w > 0 ? WindingDirection.CounterClockWise : WindingDirection.Clockwise; 

return w > 0 ? WindingDirection.ClockWise : WindingDirection.CounterClockwise; 

否則,代碼的偉大工程,我已經接受了這個作爲答案(但我想給道具發現文章的@winwaed用算法)。

---忽略---(出於歷史目的)

喜戴夫,

我不知道發生了什麼(還),但我得到的不正確的響應「順時針「來自逆時針輪廓的功能。

這裏是輪廓,在Inkscape中0.48,其中有一個(新)選項以顯示與小的半箭頭頭部的路徑的方向上顯示中的一個。如您所見,外部路徑逆時針旋轉(即使函數順時針返回)。

Counter Clockwise Winding

我要去看看這個不久...