GraphicsPath.IsClockWise()扩展方法

问题描述:

我需要为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

我主要把这件事扔到那里,因为这个问题引起了我的兴趣,这是我没有专门知识,我想讨论。

我拿了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

不,您将不得不循环点确定收尾顺序。网络上有许多算法,例如。

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

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

编辑:

忽略这一切之下。我发现了这个问题。

上述代码的最后一行应改为从:

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

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

否则,代码的伟大工程,我已经接受了这个作为答案(但我想给道具发现文章的@winwaed用算法)。

---忽略---(出于历史目的)

喜戴夫,

我不知道发生了什么(还),但我得到的不正确的响应“顺时针“来自逆时针轮廓的功能。

这里是轮廓,在Inkscape中0.48,其中有一个(新)选项以显示与小的半箭头头部的路径的方向上显示中的一个。如您所见,外部路径逆时针旋转(即使函数顺时针返回)。

Counter Clockwise Winding

我要去看看这个不久...