【问题标题】:Given a list of polygons, construct Polygon with Holes给定一个多边形列表,用孔构造多边形
【发布时间】:2011-06-10 07:06:45
【问题描述】:

对于多边形,点以逆时针方式给出。对于孔,点以顺时针方式给出。那么给定一个多边形列表,如何构造所有带孔的多边形呢?

下面是多边形列表的几个前提:

  1. 一个多边形可以包含多个孔,但一个孔必须包含在多边形内。
  2. 一个洞不能包含一个或多个多边形。
  3. 所有孔都必须位于多边形内部,而不是在多边形外部
  4. 孔的边缘可以碰到多边形。
  5. 并且所有多边形和孔不能与其他多边形/孔相交,尽管它们可以相互接触。
  6. 多边形/孔可以是凸的凹的。

如何设计一种算法来构造带孔的多边形?

我正在寻找一个通用算法,因此欢迎任何 C#、C++ 和 matlab 形式的代码。

编辑:这是我的输入(在 C# 中)

public class PolygonWithHoles
{
   public List<Point> OuterBoundary;
   public List<List<Point>> Holes;
}


//input: a list of point list. If the point list constructs a polygon in Counter clock wise, then it is a general polygon, else it is a holes
public List<PolygonWithHoles> ConstuctPolyHoles(List<List<Point>> Polys)
{
}

【问题讨论】:

  • 我不清楚你想要什么输出。你能举个例子吗?
  • @Erno,添加了示例输入/输出
  • 多边形和/或孔是凸的吗?
  • @Erno,它可以是凸的凹的,有关系吗?
  • 是的,我认为确实如此。因为当它们可以是凹的时,很难确定点列表是孔还是多边形。

标签: c# c++ matlab geometry computational-geometry


【解决方案1】:

由于浮点舍入错误,多边形测试中的点很容易受到攻击。它们通常只适用于非平凡的多边形。

更强大的方法应该基于扫描线算法,其中顶点首先根据它们的 x 和 y 值进行排序。然后扫描(或扫描)线移动,例如。从左到右。然后,该算法通常通过在其左顶点“命中”扫描线时添加一条线并在其右顶点命中该线时将其删除来维护与扫描线相交的线列表。

每次移动扫描线后,更新当前线与扫描线的交点,并根据交点的y值重新排序。每当在排序操作中需要对两条线重新排序时,这意味着它们有一个交点,然后可以记录下来。

找到所有的交点后,就可以可靠地识别轮廓和孔洞了。

以下项目使用这种方法:

还有其他的,以下网站(推广 PolyBoolean 库)比较了最重要的一些:http://www.complex-a5.ru/polyboolean/comp.html

作为警告:我自己已经实现了一个多边形库,支持布尔运算以及孔检测。这个库正在商业产品中使用,我花了几年时间(!)来微调算法,以确保在尽可能短的时间内为任何给定的输入多边形提供正确的结果(其他库可能快几个%,但因一些输入数据而失败)。事实上,单一的方法算法可能无法解决所有可能的问题,所以我不得不实现几个。

祝你好运!

【讨论】:

  • 在给你投票后,我意识到你只是在展示多边形剪辑库——它不是孔检测,也没有回答我的问题。
  • @Graviton,剪裁库检测孔洞作为其算法的一部分。例如,Klaas Holwerda 的 Boolean 库有一个函数detectHoles()(或类似的),它在裁剪过程中被调用,它将多边形分类为孔和外轮廓。通常,您还需要裁剪部分,因为如果多边形是自相交的,则必须在检测孔之前添加新顶点。如果不需要,可以删除算法中的相应阶段。
  • 对此不太确定。在我的输入中,我们已经知道外部边界是逆时针方向的,孔是顺时针方向的。剪辑库是否考虑到这一点?我不这么认为。
  • @Graviton。他们是这样。这就是所谓的填充模式。您需要使用“非零缠绕规则”作为填充模式。但是阅读您的先决条件列表,我意识到我建议的算法是矫枉过正的。我会更新我的答案。
  • @Graviton。实际上,我再次考虑了这一点,并且我坚持我的回答,即您需要基于扫描线的算法。虽然确定顶点是顺时针还是逆时针很简单,但测试一个洞是否在给定的多边形中并非易事,除非您愿意满足 O(n^2) 复杂度。虽然我建议的所有库都不会完全按照您的要求开箱即用,但可以对其进行修改。我自己的库(C++ 和 .NET)开箱即用,但不幸的是,它不是开源的(我将其商业化)。
【解决方案2】:

也许您正在寻找类似以下的简单解决方案:

public sealed class PolygonWithHoles : Shape
{

    #region Constructors
    
    /// <summary>
    /// Instantiates a new instance of a polygon.
    /// </summary>
    public PolygonWithHoles()
    {
    }

    #endregion Constructors

    #region Dynamic Properties
    
    /// <summary>
    /// Holes property
    /// </summary>
    public static readonly DependencyProperty HolesProperty = DependencyProperty.Register(
            "Holes", typeof(List<PointCollection>), typeof(PolygonWithHoles),
            new FrameworkPropertyMetadata(new List<PointCollection>(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// Holes property
    /// </summary>
    public List<PointCollection> Holes
    {
        get
        {
            return (List<PointCollection>)GetValue(HolesProperty);
        }
        set
        {
            SetValue(HolesProperty, value);
        }
    }

    /// <summary>
    /// Points property
    /// </summary>
    public static readonly DependencyProperty PointsProperty = DependencyProperty.Register(
            "Points", typeof(PointCollection), typeof(PolygonWithHoles),
            new FrameworkPropertyMetadata(new PointCollection(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// Points property
    /// </summary>
    public PointCollection Points
    {
        get
        {
            return (PointCollection)GetValue(PointsProperty);
        }
        set
        {
            SetValue(PointsProperty, value);
        }
    }

    /// <summary>
    /// FillRule property
    /// </summary>
    public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register(
        "FillRule",
        typeof(FillRule),
        typeof(PolygonWithHoles),
        new FrameworkPropertyMetadata(
            FillRule.EvenOdd,
            FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender)
        );

    /// <summary>
    /// FillRule property
    /// </summary>
    public FillRule FillRule
    {
        get
        {
            return (FillRule)GetValue(FillRuleProperty);
        }
        set
        {
            SetValue(FillRuleProperty, value);
        }
    }

    #endregion Dynamic Properties

    #region Protected Methods and properties

    /// <summary>
    /// Get the polygon that defines this shape
    /// </summary>
    protected override Geometry DefiningGeometry
    {
        get
        {
            return _polygonGeometry;
        }
    }

    #endregion

    #region Protected Methods

    /// <summary>
    /// Updates DesiredSize of the shape.  Called by parent UIElement during is the first pass of layout.
    /// </summary>
    /// <param name="constraint">Constraint size is an "upper limit" that should not exceed.</param>
    /// <returns>Shape's desired size.</returns>
    protected override Size MeasureOverride(Size constraint)
    {
        CacheDefiningGeometry();

        return base.MeasureOverride(constraint);
    }

    #endregion

    #region Internal Methods

    internal void CacheDefiningGeometry()
    {
        PointCollection pointCollection = Points;
        
        if (pointCollection == null)
        {
            _polygonGeometry = Geometry.Empty;
            return;
        }

        PathFigure pathFigure = new PathFigure();
        if (pointCollection.Count > 0)
        {
            pathFigure.StartPoint = pointCollection[0];

            if (pointCollection.Count > 1)
            {
                Point[] array = new Point[pointCollection.Count - 1];

                for (int i = 1; i < pointCollection.Count; i++)
                {
                    array[i - 1] = pointCollection[i];
                }

                pathFigure.Segments.Add(new PolyLineSegment(array, true));
            }

            pathFigure.IsClosed = true;
        }

        PathGeometry polygonGeometry = new PathGeometry();
        polygonGeometry.Figures.Add(pathFigure);
        polygonGeometry.FillRule = FillRule.Nonzero;

        GeometryGroup geometryGroup = new GeometryGroup();
        // Set FillRule
        geometryGroup.FillRule = FillRule;
        geometryGroup.Children.Add(polygonGeometry);

        // Holes
        List<PointCollection> holesCollection = Holes;
        if (holesCollection != null)
        {
            foreach (PointCollection holePointCollection in holesCollection)
            {
                PathFigure holePathFigure = new PathFigure();

                if (holePointCollection.Count > 0)
                {
                    holePathFigure.StartPoint = holePointCollection[0];

                    if (holePointCollection.Count > 1)
                    {
                        Point[] array = new Point[holePointCollection.Count - 1];

                        for (int i = 1; i < holePointCollection.Count; i++)
                        {
                            array[i - 1] = holePointCollection[i];
                        }

                        holePathFigure.Segments.Add(new PolyLineSegment(array, true));
                    }

                    holePathFigure.IsClosed = true;
                }

                PathGeometry holePolygonGeometry = new PathGeometry();
                holePolygonGeometry.Figures.Add(holePathFigure);
                holePolygonGeometry.FillRule = FillRule.Nonzero;

                geometryGroup.Children.Add(holePolygonGeometry);
            }

        }
        

        _polygonGeometry = geometryGroup;
    }

    #endregion Internal Methods

    #region Private Methods and Members

    private Geometry _polygonGeometry;

    #endregion
}

【讨论】:

    猜你喜欢
    • 2017-12-19
    • 2021-09-01
    • 2017-05-12
    • 2010-09-26
    • 1970-01-01
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多