/// <summary>
        /// Auxiliary function for _FindOuterPerim.
        /// When called, chooses the next neighbour from the current vertex that should be added to form the outer
        /// perimeter of the polygon represented by the nodes in this class.
        /// </summary>
        /// <param name="currentVertex">Current vertex selected as part of the outer perimeter.</param>
        /// <param name="bearing">Bearing that describes the direction from the current vertex to the previous vertex.</param>
        /// <param name="validNeighbours">A set of valid neighbours </param>
        /// <returns>The next neighbour from the current vertex to make the outer perimeter.</returns>
        private static PolygonSplittingGraphNode _ChooseNextNeighbour(PolygonSplittingGraphNode currentVertex, Vector2 bearing,
                                                                      HashSet <PolygonSplittingGraphNode> validNeighbours)
        {
            float minAngle = 360;
            PolygonSplittingGraphNode currentSolution = validNeighbours.First();

            foreach (PolygonSplittingGraphNode neighbour in validNeighbours)
            {                                                                                //pick the neighbour with the least CCW angle difference
                Vector2 neighbourDirection         = (new Vector2(neighbour.x, neighbour.y) - new Vector2(currentVertex.x, currentVertex.y)).Normalized();
                var     mirroredBearing            = new Vector2(bearing.x, -1 * bearing.y); //MIRROR the y because graphics canvases are ALWAYS MIRRORED, F**K
                var     mirroredNeighbourDirection = new Vector2(neighbourDirection.x, -1 * neighbourDirection.y);
                float   angleDiff = Mathf.Rad2Deg(Mathf.Atan2(mirroredBearing.x * mirroredNeighbourDirection.y - mirroredBearing.y * mirroredNeighbourDirection.x,
                                                              mirroredBearing.x * mirroredNeighbourDirection.x + mirroredBearing.y * mirroredNeighbourDirection.y));
                if (angleDiff <= 0)
                {
                    angleDiff += 360;
                }
                if (!(angleDiff < minAngle))
                {
                    continue;
                }
                minAngle        = angleDiff;
                currentSolution = neighbour;
            }
            return(currentSolution);
        }
Beispiel #2
0
        /// <summary>
        /// A recursive DFS that finds bridges by:
        ///     1. Labelling every node with a number signifying 'when' it was discovered (preorder)
        ///     2. Maintaining, for every node N, its lowest reachable vertex from a subtree with vertex N.
        /// A bridge is confirmed between some node U and some node V if V's lowest reachable vertex is further down the tree
        /// than U.
        /// Credit to: https://www.geeksforgeeks.org/bridge-in-a-graph/
        /// for the algorithm which i pretty much shamelessly stole.
        /// </summary>
        /// <param name="node">Node that this method is visiting.</param>
        private static void _BridgeDFS(PolygonSplittingGraphNode node)
        {
            int visitedID = node.id;

            _visited.Add(visitedID);
            _preorder[visitedID] = _low[visitedID] = ++_time;
            foreach (int neighbourID in node.connectedNodeIDs)
            {
                if (!_visited.Contains(neighbourID))
                {
                    _parent[neighbourID] = visitedID;
                    _BridgeDFS(_allNodes[neighbourID]);
                    _low[visitedID] = Math.Min(_low[visitedID], _low[neighbourID]);
                    if (_low[neighbourID] > _preorder[visitedID])
                    { //FOUND BRIDGE, as lowest vertex reachable from neighbourID is further down the tree than visitedID
                        _bridges[visitedID].Add(neighbourID);
                        _bridges[neighbourID].Add(visitedID);
                    }
                }
                else if (neighbourID != _parent[visitedID])
                {
                    _low[visitedID] = Math.Min(_low[visitedID], _preorder[neighbourID]);
                }
            }
        }
Beispiel #3
0
        /// <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);
        }
Beispiel #4
0
        /// <summary>
        /// Final step in partitioning a ConnectedNodeGroup, by converting it into a RectangularPolygon and checking if
        /// the partition contains any holes (AKA the outerPerim of any ConnectedNodeGroups its parent ConnectedNodeGroup
        /// contains).
        /// </summary>
        /// <param name="cycle">Cycle discovered from BFS, AKA partition of ConnectedNodeGroup.></param>
        /// <param name="connectedGroup">The ConnectedNodeGroup <param>cycle</param> was derived from.</param>
        /// <param name="connectedGroups">A list of all ConnectedNodeGroups.</param>
        /// <param name="idsContainedInGroup">The IDs of the ConnectedNodeGroups contained within <param>connectedGroup</param></param>
        /// <param name="nodes"></param>
        /// <param name="innerHoles">Holes extracted from the same ConnectedNodeGroup.</param>
        /// <returns></returns>
        private static ChordlessPolygon _FinalisePartition(List <PolygonSplittingGraphNode> cycle, ConnectedNodeGroup connectedGroup,
                                                           SortedList <int, ConnectedNodeGroup> connectedGroups,
                                                           SortedDictionary <int, HashSet <int> > idsContainedInGroup,
                                                           SortedList <int, PolygonSplittingGraphNode> nodes,
                                                           List <ChordlessPolygon> innerHoles)
        {
            var cycleAsVectorArray = new Vector2[cycle.Count];

            for (int i = 0; i < cycle.Count; i++)
            {
                PolygonSplittingGraphNode node = cycle[i];
                cycleAsVectorArray[i] = new Vector2(node.x, node.y);
            }

            var potentialHoles = new List <List <Vector2> >();

            potentialHoles.AddRange(innerHoles.Select(innerHole => innerHole.outerPerim.ToList()));
            var bridges = new Dictionary <Vector2, HashSet <Vector2> >();

            foreach (int connectedGroupID in idsContainedInGroup[connectedGroup.id])
            {
                ConnectedNodeGroup groupInside = connectedGroups[connectedGroupID];
                potentialHoles.Add(groupInside.outerPerimSimplified.ToList());

                foreach (int nodeID in groupInside.bridges.Keys)
                {
                    if (!cycle.Exists(x => x.id == nodeID))
                    {
                        continue;
                    }
                    var nodeCoord = new Vector2(nodes[nodeID].x, nodes[nodeID].y);
                    foreach (int neighbourID in groupInside.bridges[nodeID])
                    {
                        if (!cycle.Exists(x => x.id == neighbourID))
                        {
                            continue;
                        }
                        var neighbourCoord = new Vector2(nodes[neighbourID].x, nodes[neighbourID].y);
                        if (!bridges.ContainsKey(nodeCoord))
                        {
                            bridges[nodeCoord] = new HashSet <Vector2>();
                        }
                        bridges[nodeCoord].Add(neighbourCoord);
                    }
                }
            }
            return(new ChordlessPolygon(cycleAsVectorArray, potentialHoles.ToArray(), bridges, false));
        }
        /// <summary>
        /// Finds the outer perimeter of the group of nodes in this class by:
        ///     1. Getting the node with the smallest X (and Y if X-value is the same) coordinate.
        ///     2. Defining a bearing as being in the negative Y direction as there are no nodes in that direction.
        ///     3. Out of the valid edges from the current node, picking the one with the least positive CCW angle change
        ///        from the bearing.
        ///     4. Defining a new bearing as being the direction FROM THE NEW node TO THE OLD node.
        ///     5. Repeating until the first node is found again.
        /// As always, the convention for closed loops of Vector2s has list[first] == list[last].
        /// </summary>
        /// <param name="adjMatrix">Adjacency Matrix of nodes.</param>
        /// <returns>List of nodes in CCW order that describe the outer perimeter.</returns>
        private List <PolygonSplittingGraphNode> _FindOuterPerim()
        {
            PolygonSplittingGraphNode startVertex = this._xySortedNodes.First().Key;
            var localOuterPerim = new List <PolygonSplittingGraphNode> {
                startVertex
            };
            Vector2 bearing = Globals.NorthVec2;
            PolygonSplittingGraphNode currentVertex = startVertex;

            do
            {
                var validNeighbours = new HashSet <PolygonSplittingGraphNode>();
                foreach (int neighbourID in currentVertex.connectedNodeIDs.Where(neighbourID => this.nodes.ContainsKey(neighbourID)))
                {
                    validNeighbours.Add(this.nodes[neighbourID]);
                }
                PolygonSplittingGraphNode nextNeighbour = _ChooseNextNeighbour(currentVertex, bearing, validNeighbours);
                localOuterPerim.Add(nextNeighbour);
                bearing       = (new Vector2(currentVertex.x, currentVertex.y) - new Vector2(nextNeighbour.x, nextNeighbour.y)).Normalized();
                currentVertex = nextNeighbour;
            } while (!currentVertex.Equals(startVertex));
            return(localOuterPerim);
        }
 private int _CompareTo(PolygonSplittingGraphNode other)
 {
     return(SortFuncs.SortByXThenYAscending(new Vector2(this.x, this.y), new Vector2(other.x, other.y)));
 }
 private bool _Equals(PolygonSplittingGraphNode other)
 {
     return(this.x == other.x && this.y == other.y);
 }
 /// <summary>
 /// Checks if this class contains the input <param>node</param>.
 /// </summary>
 /// <param name="node"></param>
 /// <returns>True if contained, false otherwise.</returns>
 public bool ContainsNode(PolygonSplittingGraphNode node)
 {
     return(this.nodes.ContainsValue(node));
 }
Beispiel #9
0
        /// <summary>
        /// Partitions a connected node group by:
        ///     1. Repeatedly running BFS on its nodes to find cycles and removing edges for the next BFS until no more
        ///        cycles can be found.
        ///         1.1 Note that the BFS only runs on nodes with two or less VALID EDGES.  Edges are only removed IFF either
        ///             of its vertices has two or less valid edges.  This ensures we do not remove an edge that could be used
        ///             twice, for example two adjacent cycles that share an edge.
        ///     2. The cycle is added to a list of perimeters (AKA a list of nodes) IFF it is NOT a hole.
        /// </summary>
        /// <param name="connectedGroup">ConnectedNodeGroup that is being partitioned.</param>
        /// <param name="idsContainedInGroup">The IDs of the ConnectedNodeGroups contained within <param>connectedGroup</param></param>
        /// <param name="connectedGroups">List of all ConnectedNodeGroups.</param>
        /// <param name="nodes">List of all nodes.</param>
        /// <param name="holes">Holes of the polygon that was made into a PolygonSplittingGraph.</param>
        /// <returns></returns>
        public static List <ChordlessPolygon> PartitionConnectedNodeGroup(ConnectedNodeGroup connectedGroup,
                                                                          SortedDictionary <int, HashSet <int> > idsContainedInGroup,
                                                                          SortedList <int, ConnectedNodeGroup> connectedGroups,
                                                                          SortedList <int, PolygonSplittingGraphNode> nodes,
                                                                          List <Vector2>[] holes)
        {
            if (connectedGroup is null)
            {
                throw new ArgumentNullException(nameof(connectedGroup));
            }
            if (idsContainedInGroup is null)
            {
                throw new ArgumentNullException(nameof(idsContainedInGroup));
            }
            if (connectedGroups is null)
            {
                throw new ArgumentNullException(nameof(connectedGroups));
            }
            if (nodes is null)
            {
                throw new ArgumentNullException(nameof(nodes));
            }
            if (holes is null)
            {
                throw new ArgumentNullException(nameof(holes));
            }

            var outerPerimCycle = new List <PolygonSplittingGraphNode>();
            var polyCycles      = new List <List <PolygonSplittingGraphNode> >();
            var holeCycles      = new List <List <PolygonSplittingGraphNode> >();

            List <List <PolygonSplittingGraphNode> > allFaces = _GetAllFaces(connectedGroup);
            var uniqueFaces = new List <List <Vector2> >();

            foreach (List <PolygonSplittingGraphNode> newFace in allFaces)
            {
                //construct Vector2[] or List<Vector2> describing face perim in Vector2s
                var newFacePerim = new Vector2[newFace.Count];
                for (int i = 0; i < newFace.Count; i++)
                {
                    PolygonSplittingGraphNode node = newFace[i];
                    newFacePerim[i] = new Vector2(node.x, node.y);
                }

                bool newFaceUnique = true;
                foreach (List <Vector2> uniqueFace in uniqueFaces)
                {
                    if (GeometryFuncs.ArePolysIdentical(uniqueFace.ToArray(), newFacePerim))
                    {
                        newFaceUnique = false;
                        break;
                    }
                }

                if (newFaceUnique)
                {
                    uniqueFaces.Add(newFacePerim.ToList());
                    if (IsCycleHole(newFacePerim, holes))
                    {
                        holeCycles.Add(newFace);
                    }
                    else if (IsCycleOuterPerim(newFacePerim, connectedGroup.outerPerimNodes))
                    {
                        outerPerimCycle.AddRange(newFace);
                    }
                    else if (IsCycleComplex(newFacePerim))
                    {
                        continue;
                    }
                    else
                    {
                        polyCycles.Add(newFace);
                    }
                }
            }

            var innerHoles = holeCycles.Select(_FinaliseInnerHole).ToList();
            var partitions = new List <ChordlessPolygon>();

            foreach (List <PolygonSplittingGraphNode> polyCycle in polyCycles)
            {
                partitions.Add(_FinalisePartition(polyCycle, connectedGroup, connectedGroups,
                                                  idsContainedInGroup, nodes, innerHoles));
            }

            if (partitions.Count == 0 && outerPerimCycle.Count > 0) //planar face that represents the outer perim is only relevant IFF there are no other (non-hole) faces
            {
                partitions.Add(_FinalisePartition(outerPerimCycle, connectedGroup, connectedGroups,
                                                  idsContainedInGroup, nodes, innerHoles));
            }
            return(partitions);
        }