/// <summary> /// Searches through the extension segments and makes a list of new vertices that did not exist previously. /// </summary> /// <param name="polygon">Polygon that new vertices have been found for.</param> /// <param name="extensionSegments">List of extension segments.</param> /// <returns>A list of new vertices.</returns> private static HashSet <Vector2> _GetNewVertices(ChordlessPolygon polygon, List <PolyEdge> extensionSegments) { var newVertices = new HashSet <Vector2>(); var existingVertices = new HashSet <Vector2>(); foreach (Vector2 vertex in polygon.outerPerim) { existingVertices.Add(vertex); } foreach (ImmutableList <Vector2> hole in polygon.holes) { foreach (Vector2 vertex in hole) { existingVertices.Add(vertex); } } foreach (PolyEdge edge in extensionSegments) { if (!existingVertices.Contains(edge.a)) { newVertices.Add(edge.a); } if (!existingVertices.Contains(edge.b)) { newVertices.Add(edge.b); } } return(newVertices); }
/// <summary> /// Cut down the input overextension coordinate to the closest edge. Checks edges from: /// 1. The polygon's perimeter /// 2. The polygon's holes perimeters' /// 3. Extensions that already exist /// </summary> /// <param name="polygon">Input polygon.</param> /// <param name="extensions">Extension edges that already exist.</param> /// <param name="origin">Concave vertex being extended.</param> /// <param name="overextension">Vector2 representing the other side of the extension that is currently too far /// and needs to be cut down.</param> /// <returns>A vector2 representing the other side of where the extension should be, AKA the first (closest) edge it /// hits.</returns> private static Vector2 _GetCutExtension(ChordlessPolygon polygon, List <PolyEdge> extensions, Vector2 origin, Vector2 overextension) { Vector2 cutExtension = overextension; for (int i = 0; i < polygon.outerPerim.Length - 1; i++) { Vector2 perimCoordA = polygon.outerPerim[i]; Vector2 perimCoordB = polygon.outerPerim[i + 1]; if (GeometryFuncs.AreSegmentsParallel(origin, overextension, perimCoordA, perimCoordB) || perimCoordA == origin || perimCoordB == origin) { continue; } Vector2 potentialCutExtension = _CutExtensionWithSegment(origin, overextension, perimCoordA, perimCoordB); if (origin.DistanceSquaredTo(potentialCutExtension) < origin.DistanceSquaredTo(cutExtension)) { cutExtension = potentialCutExtension; } } foreach (ImmutableList <Vector2> hole in polygon.holes) { for (int i = 0; i < hole.Count - 1; i++) { Vector2 perimCoordA = hole[i]; Vector2 perimCoordB = hole[i + 1]; if (GeometryFuncs.AreSegmentsParallel(origin, overextension, perimCoordA, perimCoordB) || perimCoordA == origin || perimCoordB == origin) { continue; } Vector2 potentialCutExtension = _CutExtensionWithSegment(origin, overextension, perimCoordA, perimCoordB); if (origin.DistanceSquaredTo(potentialCutExtension) < origin.DistanceSquaredTo(cutExtension)) { cutExtension = potentialCutExtension; } } } foreach (PolyEdge edge in extensions) { Vector2 extCoordA = edge.a; Vector2 extCoordB = edge.b; if (GeometryFuncs.AreSegmentsParallel(origin, overextension, extCoordA, extCoordB) || extCoordA == origin || extCoordB == origin) { continue; } Vector2 potentialCutExtension = _CutExtensionWithSegment(origin, overextension, extCoordA, extCoordB); if (origin.DistanceSquaredTo(potentialCutExtension) < origin.DistanceSquaredTo(cutExtension)) { cutExtension = potentialCutExtension; } } return(cutExtension); }
/// <summary> /// Creates a ChordlessPolygon with just an outer perimeter and flags it as a hole. /// </summary> /// <param name="cycle"></param> /// <returns>A ChordlessPolygon flagged as a hole, just with an outer perimeter.</returns> private static ChordlessPolygon _FinaliseInnerHole(List <PolygonSplittingGraphNode> cycle) { var cyclePerim = new Vector2[cycle.Count]; for (int i = 0; i < cycle.Count; i++) { PolygonSplittingGraphNode node = cycle[i]; cyclePerim[i] = new Vector2(node.x, node.y); } var holePolygon = new ChordlessPolygon(cyclePerim, Array.Empty <List <Vector2> >(), new Dictionary <Vector2, HashSet <Vector2> >(), true); return(holePolygon); }
/// <summary> /// Given an origin and a direction to extend towards, returns the coordinate that is 'far' enough that it goes /// beyond the bounds of the polygon. /// </summary> /// <param name="polygon">Polygon owning the vertex being extended.</param> /// <param name="origin">Concave vertex being extended.</param> /// <param name="freeDir">Direction that the extension is in.</param> /// <returns>Vector2 representing a coordinate beyond the bounds of the polygon's perimeter.</returns> private static Vector2 _GetOverextendedCoord(ChordlessPolygon polygon, Vector2 origin, Vector2 freeDir) { float furthest = (freeDir.x == 0) ? origin.y : origin.x; for (int i = 0; i < polygon.outerPerim.Length - 1; i++) { Vector2 perimVertex = polygon.outerPerim[i]; if (freeDir.x == 0) { //vertical direction if (freeDir.y > 0 && perimVertex.y > origin.y) { //down if (perimVertex.y > furthest) { furthest = perimVertex.y; } } else if (freeDir.y < 0 && perimVertex.y < origin.y) { //up if (perimVertex.y < furthest) { furthest = perimVertex.y; } } } else if (freeDir.y == 0) { //horizontal direction if (freeDir.x > 0 && perimVertex.x > origin.x) { //right if (perimVertex.x > furthest) { furthest = perimVertex.x; } } else if (freeDir.x < 0 && perimVertex.x < origin.x) { //left if (perimVertex.x < furthest) { furthest = perimVertex.x; } } } } Vector2 overextension = (freeDir.x == 0) ? new Vector2(origin.x, furthest) : new Vector2(furthest, origin.y); return(overextension + freeDir); }
/// <summary> /// Checks polygon vertices (of both its outer perimeters and holes) and returns a list of the concave ones. /// </summary> /// <param name="polygon"></param> /// <returns>List of concave vertices.</returns> public static HashSet <ConcaveVertex> GetConcaveVertices(ChordlessPolygon polygon) { if (polygon is null) { throw new ArgumentNullException(nameof(polygon)); } var concaveVertices = new HashSet <ConcaveVertex>(); concaveVertices.SymmetricExceptWith(_TracePerimeterForConcaveVertices(polygon.outerPerim)); foreach (ImmutableList <Vector2> hole in polygon.holes) { concaveVertices.SymmetricExceptWith(_TracePerimeterForConcaveVertices(hole, true)); } return(concaveVertices); }
private static (List <PolyEdge>, HashSet <Vector2>) _AddBridgesToExtensions(ChordlessPolygon polygon) { var extensions = new List <PolyEdge>(); var alreadyExtended = new HashSet <Vector2>(); foreach (Vector2 bridgeA in polygon.bridges.Keys) { foreach (Vector2 bridgeB in polygon.bridges[bridgeA]) { var bridgeExtension = new PolyEdge(bridgeA, bridgeB); if (!extensions.Contains(bridgeExtension) && !extensions.Contains(bridgeExtension.GetReverseEdge())) { extensions.Add(bridgeExtension); alreadyExtended.Add(bridgeA); alreadyExtended.Add(bridgeB); } } } return(extensions, alreadyExtended); }
/// <summary> /// Gets chordless polygon extensions as a list of PolyEdges. Always fills in bridges first. /// </summary> /// <param name="polygon"></param> /// <param name="concaveVertices">Vertices in polygon that need to be extended.</param> /// <param name="chords"></param> /// <returns>List of PolyEdges representing extensions.</returns> private static List <PolyEdge> _FindVertexExtensions(ChordlessPolygon polygon, HashSet <ConcaveVertex> concaveVertices, List <Chord> chords) { (List <PolyEdge> extensions, HashSet <Vector2> alreadyExtended) = _AddBridgesToExtensions(polygon); foreach (ConcaveVertex concaveVertex in concaveVertices) { if (alreadyExtended.Contains(concaveVertex.vertex)) { continue; } Vector2 freeDir = concaveVertex.GetHorizontalFreeDirection(); if (_IsDirectionChordFree(concaveVertex.vertex, freeDir, concaveVertices, chords)) { //ensure that if we were to extend in this direction we would not create a chord } else { freeDir = concaveVertex.GetVerticalFreeDirection(); } Vector2 overextension = _GetOverextendedCoord(polygon, concaveVertex.vertex, freeDir); Vector2 cutExtension = _GetCutExtension(polygon, extensions, concaveVertex.vertex, overextension); extensions.Add(new PolyEdge(concaveVertex.vertex, cutExtension)); } return(extensions); }
/// <summary> /// Grabs the new vertices in <param>extensionVertices</param> and inserts them into <param>polygon</param>'s existing /// perimeters where they fit, and returns two new Lists (representing outer perim and holes respectively) that /// are the same as the polygon's current perimeters but with the new vertices inserted. /// </summary> /// <param name="polygon">Polygon that new vertices are being inserted into.</param> /// <param name="extensionVertices">New vertices created by extending the polygon's concave vertices to the closest /// edge.</param> /// <returns>Two lists, representing the polygon's outer perim and hole perims respectively, but with the new /// vertices inserted.</returns> private static (List <Vector2>, List <List <Vector2> >) _InsertNewVerticesIntoPerims(ChordlessPolygon polygon, HashSet <Vector2> extensionVertices) { var outerPerimWithNewVertices = new List <Vector2>(); var holesWithNewVertices = new List <List <Vector2> >(); for (int i = 0; i < polygon.outerPerim.Length - 1; i++) { Vector2 thisVertex = polygon.outerPerim[i]; Vector2 nextVertex = polygon.outerPerim[i + 1]; List <Vector2> verticesInBetween = _GetVerticesInBetween(thisVertex, nextVertex, extensionVertices); outerPerimWithNewVertices.Add(thisVertex); IOrderedEnumerable <Vector2> orderedNewVertices = verticesInBetween.OrderBy(x => thisVertex.DistanceSquaredTo(x)); outerPerimWithNewVertices.AddRange(orderedNewVertices); } outerPerimWithNewVertices.Add(polygon.outerPerim[polygon.outerPerim.Length - 1]); foreach (ImmutableList <Vector2> hole in polygon.holes) { var holePerimWithNewVertices = new List <Vector2>(); for (int i = 0; i < hole.Count - 1; i++) { Vector2 thisVertex = hole[i]; Vector2 nextVertex = hole[i + 1]; List <Vector2> verticesInBetween = _GetVerticesInBetween(thisVertex, nextVertex, extensionVertices); holePerimWithNewVertices.Add(thisVertex); IOrderedEnumerable <Vector2> orderedNewVertices = verticesInBetween.OrderBy(x => thisVertex.DistanceSquaredTo(x)); holePerimWithNewVertices.AddRange(orderedNewVertices); } holePerimWithNewVertices.Add(hole[hole.Count - 1]); holesWithNewVertices.Add(holePerimWithNewVertices); } return(outerPerimWithNewVertices, holesWithNewVertices); }