views:

29

answers:

2

I've got a PathGeometry (polygon) built up of LineSegments on one PathFigure and I'd like to ensure that it's Convex. I have a method using the CrossProduct to determine whether the geometry is Convex, I was assuming that I could just return back a list of points that make it concave when it's false and remove those points to fill the polygon but it's not working quite right.

Here's the code I've got:

    public static bool IsConvexPolygon(this IList<Point> polygon, out List<Point> concavePoints)
    {
        int n = polygon.Count;
        List<double> result = new List<double>();
        concavePoints = new List<Point>();
        for (int i = 0; i < n; i++)
        {
            result.Add(polygon[i].CrossProduct(polygon[i.RotateNext(n)]));
            if (result.Last() < 0.0)
            {
                concavePoints.Add(polygon[i.RotateNext(n)]);
            }
        }
        return (result.All(d => d >= 0.0));
    }

    public static double CrossProduct(this Point p1, Point p2)
        {
            return (p1.X * p2.Y) - (p1.Y * p2.X);
        }

    public static int RotateNext(this int index, int count)
        {
            return (index + 1) % count;
        }

    public static PointCollection ExtractPoints(this Geometry geometry)
        {
            PointCollection pc = new PointCollection();
            if (geometry is LineGeometry)
            {
                var lg = (LineGeometry)geometry;
                pc.Add(lg.StartPoint);
                pc.Add(lg.EndPoint);
                return pc;
            }
            else if (geometry is PathGeometry)
            {
                var pg = (PathGeometry)geometry;
                if (pg.Figures.Count > 0)
                {
                    List<Point> points;
                    if ((pg.Figures[0].Segments.Count > 0) && (pg.Figures[0].Segments[0] is PolyLineSegment))
                        points = ((PolyLineSegment)pg.Figures[0].Segments[0]).Points.ToList();
                    else
                        points = pg.Figures[0].Segments.Select(seg => (seg as LineSegment).Point).ToList();

                    pc.Add(pg.Figures[0].StartPoint);
                    foreach (Point p in points)
                        pc.Add(p);
                    return pc;
                }
            }
            else if (geometry is RectangleGeometry)
            {
                var rg = (RectangleGeometry)geometry;
                var rect = rg.Rect;
                pc.Add(rect.TopLeft);
                pc.Add(rect.TopRight);
                pc.Add(rect.BottomRight);
                pc.Add(rect.BottomLeft);
                return pc;
            }
            return pc;
        }

public static Geometry CreateGeometryFromPoints(this List<Point> pts)
{
    if (pts.Count < 2)
        return null;

    PathFigure pFig = new PathFigure() { StartPoint = pts[0] };
    for (int i = 1; i < pts.Count; i++)
    {
        pFig.Segments.Add(new LineSegment(pts[i], true));
    }
    pFig.IsClosed = true;

    PathGeometry pg = new PathGeometry(new List<PathFigure>() { pFig });
    return pg;
}
public static Path CreatePolygonFromGeometry(this Geometry geo, Brush fillBrush)
        {
            Path path = new Path() { Stroke = Brushes.Black, StrokeThickness = 1, Fill = fillBrush };
            path.Data = geo;
            return path;
        }

And Here's where I making the check and correcting the polygon:

        List<Point> outstuff;
        if (geo1.ExtractPoints().IsConvexPolygon(out outstuff) == false)
        {
            // Got to fill it in if it's concave
            var newpts = geo1.ExtractPoints().Except(outstuff).ToList();
            var z = newpts.CreateGeometryFromPoints().CreatePolygonFromGeometry(Brushes.Purple);
            z.MouseRightButtonDown += delegate { canvas.Children.Remove(z); };
            canvas.Children.Add(z);
        }

Ultimately I'd like to be able to make my Concave Geometry into a Convex one like this:

alt text

A: 

You cycle through each triplet of adjacent vertices (ABC, BCD, CDE, etc). For each triplet you compute the middle point of the segment linking the first and third vertex (you connect A-C in ABC, B-D in BCD, etc). If the middle point is inside the polygon, you go to the next triplet. If it's outside, you substitute the 2 segments linking the triplet with the one segment linking the extremes (that is, you remove the middle point). You go on until no more substitutions are possible.

If you try it on paper, you get exactly the result you describe.

If I'm not mistaken, you can test if a point belongs to a polygon with Polygon.HitTestCore.

Mau
That should work too, thanks for the help Mau!
softwarequestioneer
A: 

I'd compute the convex hull (also: NTS) and remove any vertexes on the interior of the resulting convex hull polygon (using a point-in-polygon test).

codekaizen
Ah! Thanks for mentioning convex hull. I went with that idea, I found this implementation of that here: http://www.itu.dk/~sestoft/gcsharp/index.html#hull
softwarequestioneer