/// <summary> /// Describes whether validate polygon /// </summary> /// <param name="polygon">The polygon</param> /// <returns>The bool</returns> private static bool ValidatePolygon(Vertices polygon) { PolygonError errorCode = polygon.CheckPolygon(); if (errorCode == PolygonError.InvalidAmountOfVertices || errorCode == PolygonError.AreaTooSmall || errorCode == PolygonError.SideTooSmall || errorCode == PolygonError.NotSimple) { return(false); } if ( errorCode == PolygonError .NotCounterClockWise) //NotCounterCloseWise is the last check in CheckPolygon(), thus we don't need to call ValidatePolygon again. { polygon.Reverse(); } if (errorCode == PolygonError.NotConvex) { polygon = GiftWrap.GetConvexHull(polygon); return(ValidatePolygon(polygon)); } return(true); }
public override void Update(GameSettings settings, GameTime gameTime) { DrawString("Use the mouse to create a polygon."); DrawString("Simple: " + _vertices.IsSimple()); DrawString("Convex: " + _vertices.IsConvex()); DrawString("CCW: " + _vertices.IsCounterClockWise()); DrawString("Area: " + _vertices.GetArea()); PolygonError returnCode = _vertices.CheckPolygon(); if (returnCode == PolygonError.NoError) DrawString("Polygon is supported in Velcro Physics"); else DrawString("Polygon is NOT supported in Velcro Physics. Reason: " + returnCode); DebugView.BeginCustomDraw(ref GameInstance.Projection, ref GameInstance.View); for (int i = 0; i < _vertices.Count; i++) { Vector2 currentVertex = _vertices[i]; Vector2 nextVertex = _vertices.NextVertex(i); DebugView.DrawPoint(currentVertex, 0.1f, Color.Yellow); DebugView.DrawSegment(currentVertex, nextVertex, Color.Red); } DebugView.DrawPoint(_vertices.GetCentroid(), 0.1f, Color.Green); AABB aabb = _vertices.GetAABB(); DebugView.DrawAABB(ref aabb, Color.HotPink); DebugView.EndCustomDraw(); base.Update(settings, gameTime); }
private static bool ValidatePolygon(Vertices polygon) { PolygonError polygonError = polygon.CheckPolygon(); bool flag = polygonError == PolygonError.InvalidAmountOfVertices || polygonError == PolygonError.AreaTooSmall || polygonError == PolygonError.SideTooSmall || polygonError == PolygonError.NotSimple; bool result; if (flag) { result = false; } else { bool flag2 = polygonError == PolygonError.NotCounterClockWise; if (flag2) { polygon.Reverse(); } bool flag3 = polygonError == PolygonError.NotConvex; if (flag3) { polygon = GiftWrap.GetConvexHull(polygon); result = Triangulate.ValidatePolygon(polygon); } else { result = true; } } return(result); }
public static string GetErrorString(PolygonError error) { StringBuilder sb = new StringBuilder(256); if (error == PolygonError.None) { sb.AppendFormat("No errors.\n"); } else { if ((error & PolygonError.NotEnoughVertices) == PolygonError.NotEnoughVertices) { sb.AppendFormat("NotEnoughVertices: must have between 3 and {0} vertices.\n", kMaxPolygonVertices); } if ((error & PolygonError.NotConvex) == PolygonError.NotConvex) { sb.AppendFormat("NotConvex: Polygon is not convex.\n"); } if ((error & PolygonError.NotSimple) == PolygonError.NotSimple) { sb.AppendFormat("NotSimple: Polygon is not simple (i.e. it intersects itself).\n"); } if ((error & PolygonError.AreaTooSmall) == PolygonError.AreaTooSmall) { sb.AppendFormat("AreaTooSmall: Polygon's area is too small.\n"); } if ((error & PolygonError.SidesTooCloseToParallel) == PolygonError.SidesTooCloseToParallel) { sb.AppendFormat("SidesTooCloseToParallel: Polygon's sides are too close to parallel.\n"); } if ((error & PolygonError.TooThin) == PolygonError.TooThin) { sb.AppendFormat("TooThin: Polygon is too thin or core shape generation would move edge past centroid.\n"); } if ((error & PolygonError.Degenerate) == PolygonError.Degenerate) { sb.AppendFormat("Degenerate: Polygon is degenerate (contains collinear points or duplicate coincident points).\n"); } if ((error & PolygonError.Unknown) == PolygonError.Unknown) { sb.AppendFormat("Unknown: Unknown Polygon error!.\n"); } } return(sb.ToString()); }
public override void Update(FarseerPhysicsGameSettings settings) { DrawString("Use the mouse to create a polygon."); DrawString("Simple: " + _vertices.IsSimple()); DrawString("Convex: " + _vertices.IsConvex()); DrawString("CCW: " + _vertices.IsCounterClockWise()); DrawString("Area: " + _vertices.GetArea()); PolygonError returnCode = _vertices.CheckPolygon(); if (returnCode == PolygonError.NoError) { DrawString("Polygon is supported in Farseer Physics Engine"); } else { DrawString("Polygon is NOT supported in Farseer Physics Engine. Reason: " + returnCode); } for (int i = 0; i < _vertices.Count; i++) { Vector2 currentVertex = _vertices[i]; Vector2 nextVertex = _vertices.NextVertex(i); DebugView.DrawPoint(currentVertex, 0.1f, Color.Yellow); DebugView.DrawSegment(currentVertex, nextVertex, Color.Red); } DebugView.DrawPoint(_vertices.GetCentroid(), 0.1f, Color.Green); AABB aabb = _vertices.GetAABB(); DebugView.DrawAABB(ref aabb, Color.HotPink); base.Update(settings); }
/// <summary> /// Checks if polygon is valid for use in Box2d engine. /// Last ditch effort to ensure no invalid polygons are /// added to world geometry. /// /// Performs a full check, for simplicity, convexity, /// orientation, minimum angle, and volume. This won't /// be very efficient, and a lot of it is redundant when /// other tools in this section are used. /// /// From Eric Jordan's convex decomposition library /// </summary> /// <param name="printErrors"></param> /// <returns></returns> public PolygonError CheckPolygon() { PolygonError error = PolygonError.None; if (Count < 3 || Count > Point2DList.kMaxPolygonVertices) { error |= PolygonError.NotEnoughVertices; // no other tests will be valid at this point, so just return return(error); } if (IsDegenerate()) { error |= PolygonError.Degenerate; } //bool bIsConvex = IsConvex(); //if (!IsConvex()) //{ // error |= PolygonError.NotConvex; //} if (!IsSimple()) { error |= PolygonError.NotSimple; } if (GetArea() < MathUtil.EPSILON) { error |= PolygonError.AreaTooSmall; } // the following tests don't make sense if the polygon is not simple if ((error & PolygonError.NotSimple) != PolygonError.NotSimple) { bool bReversed = false; WindingOrderType expectedWindingOrder = WindingOrderType.CCW; WindingOrderType reverseWindingOrder = WindingOrderType.CW; if (WindingOrder == reverseWindingOrder) { WindingOrder = expectedWindingOrder; bReversed = true; } //Compute normals Point2D[] normals = new Point2D[Count]; Point2DList vertices = new Point2DList(Count); for (int i = 0; i < Count; ++i) { vertices.Add(new Point2D(this [i].X, this [i].Y)); int i1 = i; int i2 = NextIndex(i); Point2D edge = new Point2D(this [i2].X - this [i1].X, this [i2].Y - this [i1].Y); normals [i] = Point2D.Perpendicular(edge, 1.0); normals [i].Normalize(); } //Required side checks for (int i = 0; i < Count; ++i) { int iminus = PreviousIndex(i); //Parallel sides check double cross = Point2D.Cross(normals [iminus], normals [i]); cross = MathUtil.Clamp(cross, -1.0f, 1.0f); float angle = (float)Math.Asin(cross); if (Math.Abs(angle) <= Point2DList.kAngularSlop) { error |= PolygonError.SidesTooCloseToParallel; break; } // For some reason, the following checks do not seem to work // correctly in all cases - they return false positives. // //Too skinny check - only valid for convex polygons // if (bIsConvex) // { // for (int j = 0; j < Count; ++j) // { // if (j == i || j == NextIndex(i)) // { // continue; // } // Point2D testVector = vertices[j] - vertices[i]; // testVector.Normalize(); // double s = Point2D.Dot(testVector, normals[i]); // if (s >= -Point2DList.kLinearSlop) // { // error |= PolygonError.TooThin; // } // } // Point2D centroid = vertices.GetCentroid(); // Point2D n1 = normals[iminus]; // Point2D n2 = normals[i]; // Point2D v = vertices[i] - centroid; // Point2D d = new Point2D(); // d.X = Point2D.Dot(n1, v); // - toiSlop; // d.Y = Point2D.Dot(n2, v); // - toiSlop; // // Shifting the edge inward by toiSlop should // // not cause the plane to pass the centroid. // if ((d.X < 0.0f) || (d.Y < 0.0f)) // { // error |= PolygonError.TooThin; // } // } } if (bReversed) { WindingOrder = reverseWindingOrder; } } //if (error != PolygonError.None) //{ // Console.WriteLine("Found invalid polygon: {0} {1}\n", Point2DList.GetErrorString(error), this.ToString()); //} return(error); }
// Assumes that points being passed in the list are connected and form a polygon. // Note that some error checking is done for robustness, but for the most part, // we have to rely on the user to feed us "correct" data public bool AddHole(List <TriangulationPoint> points) { if (points == null) { return(false); } //// split our self-intersection sections into their own lists List <Contour> pts = new List <Contour>(); int listIdx = 0; { Contour c = new Contour(this, points, WindingOrderType.Unknown); pts.Add(c); // only constrain the points if we actually HAVE a bounding rect if (MPoints.Count > 1) { // constrain the points to bounding rect int numPoints = pts[listIdx].Count; for (int i = 0; i < numPoints; ++i) { ConstrainPointToBounds(pts[listIdx][i]); } } } while (listIdx < pts.Count) { // simple sanity checking - remove duplicate coincident points before // we check the polygon: fast, simple algorithm that eliminate lots of problems // that only more expensive checks will find pts[listIdx].RemoveDuplicateNeighborPoints(); pts[listIdx].WindingOrder = WindingOrderType.Default; bool bListOk = true; PolygonError err = pts[listIdx].CheckPolygon(); while (bListOk && err != PolygonError.None) { if ((err & PolygonError.NotEnoughVertices) == PolygonError.NotEnoughVertices) { bListOk = false; continue; } if ((err & PolygonError.NotSimple) == PolygonError.NotSimple) { // split the polygons, remove the current list and add the resulting list to the end //List<Point2DList> l = TriangulationUtil.SplitSelfIntersectingPolygon(pts[listIdx], pts[listIdx].Epsilon); IEnumerable <Point2DList> l = PolygonUtil.SplitComplexPolygon(pts[listIdx], pts[listIdx].Epsilon); pts.RemoveAt(listIdx); foreach (Point2DList newList in l) { Contour c = new Contour(this); c.AddRange(newList); pts.Add(c); } err = pts[listIdx].CheckPolygon(); continue; } if ((err & PolygonError.Degenerate) == PolygonError.Degenerate) { pts[listIdx].Simplify(Epsilon); err = pts[listIdx].CheckPolygon(); continue; //err &= ~(PolygonError.Degenerate); //if (pts[listIdx].Count < 3) //{ // err |= PolygonError.NotEnoughVertices; // bListOK = false; // continue; //} } if ((err & PolygonError.AreaTooSmall) == PolygonError.AreaTooSmall || (err & PolygonError.SidesTooCloseToParallel) == PolygonError.SidesTooCloseToParallel || (err & PolygonError.TooThin) == PolygonError.TooThin || (err & PolygonError.Unknown) == PolygonError.Unknown) { bListOk = false; } // non-convex polygons are ok //if ((err & PolygonError.NotConvex) == PolygonError.NotConvex) //{ //} } if (!bListOk && pts[listIdx].Count != 2) { pts.RemoveAt(listIdx); } else { ++listIdx; } } bool bOk = true; listIdx = 0; while (listIdx < pts.Count) { int numPoints = pts[listIdx].Count; if (numPoints < 2) { // should not be possible by this point... ++listIdx; bOk = false; continue; } else if (numPoints == 2) { uint constraintCode = TriangulationConstraint.CalculateContraintCode(pts[listIdx][0], pts[listIdx][1]); TriangulationConstraint tc; if (!_constraintMap.TryGetValue(constraintCode, out tc)) { tc = new TriangulationConstraint(pts[listIdx][0], pts[listIdx][1]); AddConstraint(tc); } } else { Contour ph = new Contour(this, pts[listIdx], WindingOrderType.Unknown) { WindingOrder = WindingOrderType.Default, }; _holes.Add(ph); } ++listIdx; } return(bOk); }
public PolygonError CheckPolygon() { PolygonError error = PolygonError.None; if (Count < 3 || Count > Point2DList.kMaxPolygonVertices) { error |= PolygonError.NotEnoughVertices; return(error); } if (IsDegenerate()) { error |= PolygonError.Degenerate; } if (!IsSimple()) { error |= PolygonError.NotSimple; } if (GetArea() < MathUtil.EPSILON) { error |= PolygonError.AreaTooSmall; } if ((error & PolygonError.NotSimple) != PolygonError.NotSimple) { bool bReversed = false; WindingOrderType expectedWindingOrder = WindingOrderType.CCW; WindingOrderType reverseWindingOrder = WindingOrderType.CW; if (WindingOrder == reverseWindingOrder) { WindingOrder = expectedWindingOrder; bReversed = true; } Point2D[] normals = new Point2D[Count]; Point2DList vertices = new Point2DList(Count); for (int i = 0; i < Count; ++i) { vertices.Add(new Point2D(this[i].X, this[i].Y)); int i1 = i; int i2 = NextIndex(i); Point2D edge = new Point2D(this[i2].X - this[i1].X, this[i2].Y - this[i1].Y); normals[i] = Point2D.Perpendicular(edge, 1.0); normals[i].Normalize(); } for (int i = 0; i < Count; ++i) { int iminus = PreviousIndex(i); double cross = Point2D.Cross(normals[iminus], normals[i]); cross = MathUtil.Clamp(cross, -1.0f, 1.0f); float angle = (float)Math.Asin(cross); if (Math.Abs(angle) <= Point2DList.kAngularSlop) { error |= PolygonError.SidesTooCloseToParallel; break; } } if (bReversed) { WindingOrder = reverseWindingOrder; } } return(error); }
public static List <Vertices> ConvexPartition(Vertices vertices, TriangulationAlgorithm algorithm, bool discardAndFixInvalid = true, float tolerance = 0.001f) { if (vertices.Count <= 3) { return new List <Vertices> { vertices } } ; List <Vertices> results; switch (algorithm) { case TriangulationAlgorithm.Earclip: if (Settings.SkipSanityChecks) { Debug.Assert(!vertices.IsCounterClockWise(), "The Earclip algorithm expects the polygon to be clockwise."); } else { if (vertices.IsCounterClockWise()) { Vertices temp = new Vertices(vertices); temp.Reverse(); results = EarclipDecomposer.ConvexPartition(temp, tolerance); } else { results = EarclipDecomposer.ConvexPartition(vertices, tolerance); } } break; case TriangulationAlgorithm.Bayazit: if (Settings.SkipSanityChecks) { Debug.Assert(vertices.IsCounterClockWise(), "The polygon is not counter clockwise. This is needed for Bayazit to work correctly."); } else { if (!vertices.IsCounterClockWise()) { Vertices temp = new Vertices(vertices); temp.Reverse(); results = BayazitDecomposer.ConvexPartition(temp); } else { results = BayazitDecomposer.ConvexPartition(vertices); } } break; case TriangulationAlgorithm.Flipcode: if (Settings.SkipSanityChecks) { Debug.Assert(vertices.IsCounterClockWise(), "The polygon is not counter clockwise. This is needed for Bayazit to work correctly."); } else { if (!vertices.IsCounterClockWise()) { Vertices temp = new Vertices(vertices); temp.Reverse(); results = FlipcodeDecomposer.ConvexPartition(temp); } else { results = FlipcodeDecomposer.ConvexPartition(vertices); } } break; case TriangulationAlgorithm.Seidel: results = SeidelDecomposer.ConvexPartition(vertices, tolerance); break; case TriangulationAlgorithm.SeidelTrapezoids: results = SeidelDecomposer.ConvexPartitionTrapezoid(vertices, tolerance); break; case TriangulationAlgorithm.Delauny: results = CDTDecomposer.ConvexPartition(vertices); break; default: throw new ArgumentOutOfRangeException("algorithm"); } if (discardAndFixInvalid) { for (int i = results.Count - 1; i >= 0; i--) { Vertices polygon = results[i]; PolygonError errorCode = polygon.CheckPolygon(); if (errorCode == PolygonError.InvalidAmountOfVertices || errorCode == PolygonError.AreaTooSmall || errorCode == PolygonError.SideTooSmall || errorCode == PolygonError.NotSimple) { results.RemoveAt(i); } else if (errorCode == PolygonError.NotCounterClockWise) { polygon.Reverse(); } else if (errorCode == PolygonError.NotConvex) { results[i] = GiftWrap.GetConvexHull(polygon); } } } return(results); } }