public Node(Node node) { _id = node.ID; position = node.Position; adjacents = node.adjacents; angle = node.angle; }
public EdgeBuilder(Node _root, List<Vector3> _targets, float _edgeWidth, float _segment, float _minAngle, float _minDistance, float _maxDistance, Action<List<Node>, List<Edge>> onFinished) { //m_rootNode = new Node(_root.Position); m_rootNode = _root; m_edgeWidth = _edgeWidth; m_segmentLength = _segment; m_minAngle = _minAngle; m_minDistance = _minDistance; m_maxDistance = _maxDistance; edges = new List<Edge>(); nodes = new List<Node>(); m_nodeGrowCounts = new Dictionary<string, int>(); m_nodeGrowDirections = new Dictionary<string, Vector3>(); m_nodeStack = new Stack<Node>(); m_visitedTargets = new List<Target>(); m_targets = new List<Target>(); if (_targets.Count > 0) { _targets.ForEach((t) => m_targets.Add(new Target(t))); StartBuild(onFinished); } }
void StartBuild(Action<List<Node>, List<Edge>> onFinished) { // Create first edge from root to first target float toClosest; Target initTarget = GetClosestNonVisitedTarget(m_rootNode, out toClosest, false); m_visitedTargets.Add(initTarget); m_currentNode = new Node(initTarget.position); nodes.Add(m_currentNode); m_nodeStack.Push(m_currentNode); m_currentNode.adjacents.Add(m_rootNode.ID); m_rootNode.adjacents.Add(m_currentNode.ID); edges.Add(new Edge(m_rootNode.ID, m_currentNode.ID, m_edgeWidth)); // Get next target and advance to it Target currentTarget = GetClosestNonVisitedTarget(m_currentNode, out toClosest, false); AdvanceUntilClosest(ref m_currentNode, currentTarget, toClosest); // Build other edges BuildEdgesRecursive(); EdgeGraphUtility.FixIntersectingEdges(ref nodes, ref edges); if (onFinished != null) onFinished(nodes, edges); }
public string GetAdjacentNode(Node current) { if (current.ID == node1) return node2; if (current.ID == node2) return node1; return null; }
public static Edge FindEdgeByNodes(Node n1, Node n2, List<Edge> _edges) { if (n1 == null || n2 == null) return null; for (int i = 0; i < _edges.Count; i++) { if (_edges[i] == null) continue; if ((_edges[i].Node1 == n1.ID && _edges[i].Node2 == n2.ID) || (_edges[i].Node1 == n2.ID && _edges[i].Node2 == n1.ID)) { return _edges[i]; } } return null; }
/// <summary> /// Gets closest node to point /// </summary> /// <param name="nodes"></param> /// <param name="point"></param> /// <returns></returns> public static Node GetClosestNode(List<Node> nodes, Node other) { Node closest = null; float toClosest = Mathf.Infinity; float toCurrent = Mathf.Infinity; for (int i = 0; i < nodes.Count; i++) { if (nodes[i] == other) continue; toCurrent = Vector3.Distance(nodes[i].Position, other.Position); if (toCurrent < toClosest) { closest = nodes[i]; toClosest = toCurrent; } } return closest; }
void CombineSubNodes(Node _rootNode, List<Node> _nodes, List<Edge> _edges, float _combineRange, bool ensureRootEdge = true) { if (_combineRange > 0f) { for (int safe = 0; safe < 100; safe++) { bool wasCombined = false; for (int i = 0; i < _nodes.Count; i++) { //Get closest node Node closest = Node.GetClosestNode(_nodes, _nodes[i]); if (closest == _nodes[i] || closest == null) continue; if (Vector3.Distance(_nodes[i].Position, closest.Position) < _combineRange) { _nodes[i] = Node.CombineNodes(_nodes[i], closest, _edges, _nodes); _nodes.Remove(closest); wasCombined = true; } } if (!wasCombined) break; } } if (!ensureRootEdge) return; // Ensure the edge between first node and root node Edge rootEdge = null; for (int i = 0; i < _edges.Count; i++) { if ((_edges[i].Node1 == _rootNode.ID && _edges[i].Node2 == _nodes[0].ID) || (_edges[i].Node2 == _rootNode.ID && _edges[i].Node1 == _nodes[0].ID)) { rootEdge = _edges[i]; } } if (rootEdge == null) { rootEdge = new Edge(_rootNode.ID, _nodes[0].ID, subEdgeWidth); _edges.Add(rootEdge); } }
/// <summary> /// Combines two nodes into one /// </summary> /// <param name="node1">First node, this node is transformed into the combined node</param> /// <param name="node2">Second node</param> /// <param name="edges">List of edges in which the IDs are fixed</param> /// <returns>Combined node</returns> public static Node CombineNodes(Node node1, Node node2, List<Edge> edges, List<Node> nodes) { //Combine node positions node1.position = (node1.position + node2.position) / 2f; //Fix edges for (int i = 0; i < edges.Count; i++) { //Remove edges that end in the node2, which will be removed if (node2.adjacents.Count == 1 && (edges[i].Node1 == node2.ID || edges[i].Node2 == node2.ID)) { edges.RemoveAt(i); i--; continue; } if (edges[i].Node1 == node2.ID) edges[i].Node1 = node1.ID; if (edges[i].Node2 == node2.ID) edges[i].Node2 = node1.ID; } EdgeGraphUtility.CleanUpEdges(ref nodes, ref edges); //Fix node adjacents EdgeGraphUtility.CheckAdjacentNodes(ref nodes, ref edges); return node1; }
void DrawPrimitiveHandles() { if (currentEditorState != GraphEditorState.Primitives) return; if (graph.mainPrimitives != null && graph.mainPrimitives.Count > 0) { Primitive p = null; Node n1 = null; Node n2 = null; for (int i = 0; i < graph.mainPrimitives.Count; i++) { p = graph.mainPrimitives[i]; foreach (var edge in p.edges) { n1 = p.nodes.Find(x => x.ID == edge.Node1); n2 = p.nodes.Find(x => x.ID == edge.Node2); if (n1 != null && n2 != null) { Handles.color = Color.cyan; Color singleSelectColor = new Color(Color.red.r - .3f, Color.red.g - .3f, Color.red.b - .3f); Color multiSelectColor = Color.red; int indexMask = 1 << i; if (singleSelectedPrimitiveIdx == i) Handles.color = singleSelectColor; else if ((selectedPrimitiveMask & indexMask) == indexMask) Handles.color = multiSelectColor; Handles.DrawLine(graph.transform.TransformPoint(n1.Position), graph.transform.TransformPoint(n2.Position)); } } if (debugMode) { for (int j = 0; j < p.nodes.Count; j++) { Vector3 center = graph.transform.TransformPoint(p.nodes[j].Position + p.nodes[j].dirToInside); Handles.color = Color.green; Handles.DrawLine(graph.transform.TransformPoint(p.nodes[j].Position), center); Handles.Label(center, j.ToString(), "box"); } } } if (SinglePrimitiveIsSelected) { // Root index // ---------------------------------------------------------------------------------------- p = graph.mainPrimitives[singleSelectedPrimitiveIdx]; int idx = p.subEdgeRootIndex; if (idx >= p.nodes.Count) { idx = p.nodes.Count - 1; } Handles.color = Color.yellow; int nextIdx = UtilityTools.Helper.GetNextIndex<Node>(p.nodes, idx); Vector3 toNext = (p.nodes[nextIdx].Position - p.nodes[idx].Position).normalized; Vector3 rightNormal = -UtilityTools.MathHelper.LeftSideNormal(toNext); Vector3 pos = graph.transform.TransformPoint(p.nodes[idx].Position); float dist = 3f; //Handles.SphereCap(i, pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * .2f); Handles.Label(pos + rightNormal * dist, "Root", "box"); Handles.DrawLine(pos, pos + rightNormal * dist); // Root node modification if (shiftIsPressed && !controlIsPressed) { subEdgeRootNode = EdgeGraphUtility.GetClosestNode(cursorWorldPosition, graph.mainPrimitives[singleSelectedPrimitiveIdx].nodes, graph.transform); Vector3 nodePos = graph.transform.TransformPoint(subEdgeRootNode.Position); Vector3 arrowDir = (cursorWorldPosition - nodePos).normalized; Vector3 arrowPos = nodePos + arrowDir; arrowPos += arrowDir * HandleUtility.GetHandleSize(arrowPos); Quaternion arrowRot = Quaternion.LookRotation((nodePos - arrowPos).normalized); Handles.ArrowCap(0, arrowPos, arrowRot, HandleUtility.GetHandleSize(arrowPos)); Handles.DrawLine(arrowPos, cursorWorldPosition); } // ---------------------------------------------------------------------------------------- // Create new subedges // ---------------------------------------------------------------------------------------- if (activeSubNode >= 0 && controlIsPressed && !shiftIsPressed && p.subNodes != null && p.subNodes.Count > 0) { Node _selected = p.subNodes[activeSubNode]; float toClosest; Node closestNode = CheckClosestNode(p.subNodes, out toClosest, _selected); float toSelected = Vector3.Distance(_selected.Position, cursorLocalPosition); if (toClosest < toSelected) { Handles.color = Color.green; Handles.DrawLine(graph.transform.TransformPoint(_selected.Position), graph.transform.TransformPoint(closestNode.Position)); // Add the new edge creatingNewSubEdge = new Edge(_selected.ID, closestNode.ID, p.subEdgeWidth); } else creatingNewSubEdge = null; } // ---------------------------------------------------------------------------------------- } } // Subgraphs if (graph.subGraphs != null && graph.subGraphs.Count > 0) { Node n1 = null; Node n2 = null; Primitive p = null; Edge edge = null; for (int i = 0; i < graph.subGraphs.Count; i++) { if (graph.subGraphs[i] == null) continue; Handles.color = new Color(0f, 1f, .5f, 1f); for (int j = 0; j < graph.subGraphs[i].mainPrimitives.Count; j++) { p = graph.subGraphs[i].mainPrimitives[j]; for (int k = 0; k < p.edges.Count; k++) { edge = p.edges[k]; n1 = p.nodes.Find(x => x.ID == edge.Node1); n2 = p.nodes.Find(x => x.ID == edge.Node2); if (n1 != null && n2 != null) { Handles.DrawLine(graph.transform.TransformPoint(n1.Position), graph.transform.TransformPoint(n2.Position)); } } } } } }
public void CutAcuteAngles() { if (type != PrimitiveType.MinimalCycle) return; List<Node> _nodesToRemove = new List<Node>(); List<Node> _newNodes = new List<Node>(); List<Edge> _newEdges = new List<Edge>(); // Old edges are kept in the edges list, but node changes are saved here and refreshed to the edges once all angles are checked Dictionary<string, List<NodePair>> nodesToSwitchInEdges = new Dictionary<string, List<NodePair>>(); for (int i = 0; i < nodes.Count; i++) { Node prevNode = EdgeGraphUtility.GetNode(nodes[i].adjacents[0], ref nodes); Node nextNode = EdgeGraphUtility.GetNode(nodes[i].adjacents[1], ref nodes); Vector3 dirToPrev = (prevNode.Position - nodes[i].Position).normalized; Vector3 dirToNext = (nextNode.Position - nodes[i].Position).normalized; Edge prevEdge = EdgeGraphUtility.FindEdgeByNodes(nodes[i], prevNode, edges); Edge nextEdge = EdgeGraphUtility.FindEdgeByNodes(nodes[i], nextNode, edges); float angle = Vector3.Angle(dirToPrev, dirToNext); if (angle < 45f) { // Move nodes so that the cut side is 1f wide float distanceToMove = .55f / Mathf.Sin(Mathf.Deg2Rad * angle / 2f); float distToPrev = Vector3.Distance(prevNode.Position, nodes[i].Position); float distToNext = Vector3.Distance(nextNode.Position, nodes[i].Position); if (distanceToMove > distToPrev) distanceToMove = distToPrev * .8f; if (distanceToMove > distToNext) distanceToMove = distToNext * .8f; Vector3 newNodePrevPos = (nodes[i].Position + dirToPrev * distanceToMove); Vector3 newNodeNextPos = (nodes[i].Position + dirToNext * distanceToMove); string oldNodeID = nodes[i].ID; // Remove old node _nodesToRemove.Add(nodes[i]); // Make new nodes Node newNodeToPrev = new Node(newNodePrevPos); Node newNodeToNext = new Node(newNodeNextPos); if (!nodesToSwitchInEdges.ContainsKey(prevEdge.ID)) nodesToSwitchInEdges.Add(prevEdge.ID, new List<NodePair>()); nodesToSwitchInEdges[prevEdge.ID].Add(new NodePair(oldNodeID, newNodeToPrev.ID)); if (!nodesToSwitchInEdges.ContainsKey(nextEdge.ID)) nodesToSwitchInEdges.Add(nextEdge.ID, new List<NodePair>()); nodesToSwitchInEdges[nextEdge.ID].Add(new NodePair(oldNodeID, newNodeToNext.ID)); // Add the new edge Edge newEdge = new Edge(newNodeToNext.ID, newNodeToPrev.ID); _newEdges.Add(newEdge); // Add new nodes to the dict _newNodes.Add(newNodeToPrev); _newNodes.Add(newNodeToNext); } } foreach (var n in _nodesToRemove) { for (int i = 0; i < nodes.Count; i++) { if (nodes[i].ID == n.ID) { nodes.RemoveAt(i); i--; } } } foreach (var kvp in nodesToSwitchInEdges) { for (int i = 0; i < edges.Count; i++) { if (edges[i].ID == kvp.Key) { Edge e = edges[i]; for (int j = 0; j < kvp.Value.Count; j++) { NodePair pair = kvp.Value[j]; if (e.Node1 == pair.oldNode) e.Node1 = pair.newNode; if (e.Node2 == pair.oldNode) e.Node2 = pair.newNode; } } } } _newEdges.ForEach((e) => edges.Add(e)); _newNodes.ForEach((n) => nodes.Add(n)); EdgeGraphUtility.CleanUpEdges(ref nodes, ref edges); EdgeGraphUtility.CheckAdjacentNodes(ref nodes, ref edges); }
public static Edge RemoveEdgeAndCleanAdjacents(Node _n0, Node _n1, ref List<Node> _nodes, ref List<Edge> _edges) { Edge e = FindEdgeByNodes(_n0, _n1, _edges); if (e != null && _edges.Contains(e)) { if (_edges.Remove(e)) { CheckAdjacentNodes(ref _nodes, ref _edges); } } return e; }
/// <summary> /// Splits given edge into two edges at given point /// If a node is given, it will be used to split the edges /// Otherwise make a new node /// </summary> public static Node SplitEdge(Edge edge, Vector3 point, List<Node> nodes, List<Edge> edges, Node node = null) { bool isNewNode = false; // Create new node if (node == null) { node = new Node(point); isNewNode = true; } // Get start and end nodes of the edge Node start = Node.GetNode(nodes, edge.Node1); Node end = Node.GetNode(nodes, edge.Node2); // Add new node between start and end start.adjacents.Remove(end.ID); end.adjacents.Remove(start.ID); start.adjacents.Add(node.ID); end.adjacents.Add(node.ID); int startIndex = nodes.IndexOf(start); if (startIndex == -1) { Debug.LogError("Edge::SplitEdge() - StartIndex not found. Are you sure you gave the right node list?"); return null; } if (isNewNode) { if (startIndex == nodes.Count - 1) nodes.Add(node); else nodes.Insert(startIndex + 1, node); } // Remove original edge from edges edges.Remove(edge); // Create new edges Edge e1 = new Edge(start.ID, node.ID, edge.width); Edge e2 = new Edge(node.ID, end.ID, edge.width); edges.Add(e1); edges.Add(e2); return node; }
Node CheckClosestNode(List<Node> nodes, out float toClosest, Node ignore = null) { toClosest = Mathf.Infinity; if (nodes == null || nodes.Count <= 0) return null; Node closestNode = nodes[0]; for (int i = 0; i < nodes.Count; i++) { if (ignore != null && nodes[i] == ignore) continue; Vector3 currentPos = graph.transform.TransformPoint(nodes[i].Position); float toCurrent = Vector3.Distance(currentPos, cursorWorldPosition); Vector3 closestPos = graph.transform.TransformPoint(closestNode.Position); toClosest = Vector3.Distance(closestPos, cursorWorldPosition); if (toCurrent < toClosest) { closestNode = nodes[i]; toClosest = toCurrent; } } return closestNode; }
public static void RemoveNodeAndCleanAdjacents(Node n, ref List<Node> _nodes, ref List<Edge> _edges) { if (_nodes.Remove(n)) { for (int i = 0; i < _nodes.Count; i++) { if (_nodes[i] != n && _nodes[i].adjacents.Contains(n.ID)) _nodes[i].adjacents.Remove(n.ID); } } }
/// <summary> /// Get closest non visited target /// </summary> Target GetClosestNonVisitedTarget(Node node, out float toClosest, bool checkMaxDistance = true) { Vector3 nodePos = node.Position; Target closest = null; float _toClosest = Mathf.Infinity; foreach (var t in m_targets) { float toTarget = Vector3.Distance(nodePos, t.position); if (toTarget < _toClosest && !m_visitedTargets.Contains(t)) { if (checkMaxDistance && toTarget > m_maxDistance) continue; closest = t; _toClosest = Vector3.Distance(nodePos, closest.position); } } toClosest = _toClosest; return closest; }
public static Node GetCounterClockwiseMostAdjacent(Node prev, Node curr, ref List<Node> _nodes) { if (curr.adjacents == null || curr.adjacents.Count == 0) return null; Vector3 dirCurr = Vector3.zero; dirCurr = curr.Position - prev.Position; Node next = EdgeGraphUtility.GetNode(curr.GetAdjacent(prev.ID), ref _nodes); if (next == null) return null; Vector3 dirNext = next.Position - curr.Position; bool currIsConvex = DotPerp(dirNext, dirCurr) <= 0; foreach (var a in curr.adjacents) { var adj = EdgeGraphUtility.GetNode(a, ref _nodes); Vector3 dirAdj = adj.Position - curr.Position; if (currIsConvex) { if (DotPerp(dirCurr, dirAdj) > 0 && DotPerp(dirNext, dirAdj) > 0) { next = adj; dirNext = dirAdj; currIsConvex = DotPerp(dirNext, dirCurr) <= 0; } } else { if (DotPerp(dirCurr, dirAdj) > 0 || DotPerp(dirNext, dirAdj) > 0) { next = adj; dirNext = dirAdj; currIsConvex = DotPerp(dirNext, dirCurr) <= 0; } } } return next; }
void AdvanceUntilClosest(ref Node currentNode, Target closest, float toClosest) { Vector3 currentDir = (closest.position - currentNode.Position).normalized; int counter = 0; float lastToClosest = 0f; while (counter < 100) { if (toClosest > m_minDistance) { currentDir = (closest.position - currentNode.Position).normalized; if (Advance(ref currentNode, currentDir)) m_nodeStack.Push(currentNode); lastToClosest = toClosest; toClosest = Vector3.Distance(currentNode.Position, closest.position); // If we are suddenly going away from the closest node, end if (toClosest > lastToClosest) break; } else break; counter++; } m_visitedTargets.Add(closest); }
void ColonizeSpace() { //Process targets for (int i = 0; i < m_targets.Count; i++) { bool targetRemoved = false; m_targets[i].closestNode = null; Vector3 direction = Vector3.zero; //Find closest node for this target for (int j = 0; j < nodes.Count; j++) { direction = m_targets[i].position - nodes[j].Position; float distance = direction.magnitude; direction.Normalize(); //If min distance is reached, we remove the target if (distance <= m_minDistance) { m_targets.RemoveAt(i); i--; targetRemoved = true; break; } //If target is in range, check if it is closest else if (distance <= m_maxDistance) { if (m_targets[i].closestNode == null) m_targets[i].closestNode = nodes[j]; else if ((m_targets[i].position - m_targets[i].closestNode.Position).magnitude > distance) m_targets[i].closestNode = nodes[j]; } //If target is not removed, set the grow parameters on all the closest nodes that are in range if (!targetRemoved && m_targets[i].closestNode != null) { Vector3 dir = (m_targets[i].position - m_targets[i].closestNode.Position).normalized; string closestID = m_targets[i].closestNode.ID; if (m_nodeGrowDirections.ContainsKey(closestID)) m_nodeGrowDirections[closestID] += dir; else m_nodeGrowDirections.Add(closestID, dir); if (m_nodeGrowCounts.ContainsKey(closestID)) m_nodeGrowCounts[closestID]++; else m_nodeGrowCounts.Add(closestID, 1); } } } //Advance nodes towards nearest targets bool nodeAdded = false; for (int i = 0; i < nodes.Count; i++) { string nodeID = nodes[i].ID; //If at least one target is affecting the current node if (m_nodeGrowCounts.ContainsKey(nodeID) && m_nodeGrowCounts[nodeID] > 0) { Vector3 avgDirection = m_nodeGrowDirections[nodeID] / (float)m_nodeGrowCounts[nodeID]; avgDirection.Normalize(); Node newNode = new Node(nodes[i].Position); nodes.Add(newNode); if (Advance(ref newNode, avgDirection)) { m_nodeGrowCounts[nodeID] = 0; m_nodeGrowDirections[nodeID] = Vector3.zero; nodeAdded = true; } } } //If no nodes were added, we are done if (!nodeAdded) m_isFinished = true; }
void BuildEdges() { float toClosest; Target currentTarget = GetClosestNonVisitedTarget(m_currentNode, out toClosest); //If no target was found, trace back node stack if (currentTarget == null) { for (int i = 0; i < m_nodeStack.Count; i++) { Node n = m_nodeStack.Pop(); currentTarget = GetClosestNonVisitedTarget(n, out toClosest); if (currentTarget != null) { m_currentNode = n; break; } } /// If no target was found but there is still unvisited targets, /// connect one of these targets to the closest node from it if (currentTarget == null && m_visitedTargets.Count < m_targets.Count) { //Empty the stack as we sort of start a new progress m_nodeStack = new Stack<Node>(); Target nonVisitedTarget = null; for (int j = 0; j < m_targets.Count; j++) { if (!m_visitedTargets.Contains(m_targets[j])) { nonVisitedTarget = m_targets[j]; break; } } //Find closest node to this non-visited target if (nonVisitedTarget != null) { Node closest = GetClosestNodeToTarget(nonVisitedTarget); if (closest != null) { m_currentNode = closest; //Get closest target to this node currentTarget = GetClosestNonVisitedTarget(m_currentNode, out toClosest, false); } } } /// If current target is still null, we are finished if (currentTarget == null) { m_isFinished = true; return; } } AdvanceUntilClosest(ref m_currentNode, currentTarget, toClosest); }
/// <summary> /// Advances forward by segmentLength from current node /// </summary> /// <param name="_node"></param> /// <param name="_dir"></param> /// <returns>True if new node was made</returns> bool Advance(ref Node _node, Vector3 _dir) { bool retval = false; Vector3 newPos = _node.Position + _dir * m_segmentLength; if (newPos == _node.Position) return retval; bool addNew = false; if (_node.adjacents.Count > 0) { Node adj = Node.GetNode(nodes, _node.adjacents[0]); if (adj != null) { Vector3 dirToPrevious = (adj.Position - _node.Position).normalized; // If direction stays the same, just move current node float dot = Vector3.Dot(_dir.normalized, dirToPrevious); if (Mathf.Approximately(dot, -1f)) { _node.Position = newPos; } else { // If resulting angle is too sharp, make a 90 degree turn Vector3 referenceRight = Vector3.Cross(Vector3.up, dirToPrevious); // Pick adjacent with which current direction creates the smallest angle float angle = Mathf.Infinity; for (int i = 0; i < _node.adjacents.Count; i++) { adj = Node.GetNode(nodes, _node.adjacents[0]); Vector3 dirToCur = (adj.Position - _node.Position).normalized; float toCur = Vector3.Angle(dirToCur, _dir); if (toCur < angle) { angle = toCur; dirToPrevious = dirToCur; } } if (angle < m_minAngle) { //Check if current direction points left or right from previous and make a 90 degree turn to that direction if (Vector3.Dot(referenceRight, _dir) > 0) // Right _dir = Vector3.Cross(Vector3.up, dirToPrevious); else _dir = Vector3.Cross(dirToPrevious, Vector3.up); newPos = _node.Position + _dir * m_segmentLength; } addNew = true; } } else addNew = true; } else addNew = true; if (addNew) { Node newNode = new Node(newPos); nodes.Add(newNode); newNode.adjacents.Add(_node.ID); _node.adjacents.Add(newNode.ID); edges.Add(new Edge(_node.ID, newNode.ID, m_edgeWidth)); _node = newNode; retval = true; } return retval; }
public Target(Vector3 pos) { position = pos; closestNode = null; }
/// <summary> /// Extracts filament consisting of nodes and edges /// </summary> public static void ExtractFilament(Node _n0, Node _n1, ref List<Node> _nodes, ref List<Edge> _edges, ref List<Primitive> _primitives) { Edge e = EdgeGraphUtility.FindEdgeByNodes(_n0, _n1, _edges); if (e != null && e.isPartOfCycle) { if (_n0.adjacents.Count >= 3) { EdgeGraphUtility.RemoveEdgeAndCleanAdjacents(_n0, _n1, ref _nodes, ref _edges); _n0 = _n1; if (_n0.adjacents.Count == 1) _n1 = EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes); } while (_n0.adjacents.Count == 1) { _n1 = EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes); Edge ee = EdgeGraphUtility.FindEdgeByNodes(_n0, _n1, _edges); if (ee != null && e.isPartOfCycle) { EdgeGraphUtility.RemoveNodeAndCleanAdjacents(_n0, ref _nodes, ref _edges); EdgeGraphUtility.RemoveEdgeAndCleanAdjacents(_n0, _n1, ref _nodes, ref _edges); } else break; } if (_n0.adjacents.Count == 0) { EdgeGraphUtility.RemoveNodeAndCleanAdjacents(_n0, ref _nodes, ref _edges); } } else { Primitive primitive = new Primitive(Primitive.PrimitiveType.Filament); if (_n0.adjacents.Count >= 3) { primitive.nodes.Add(_n0); primitive.edges.Add(e); EdgeGraphUtility.RemoveEdgeAndCleanAdjacents(_n0, _n1, ref _nodes, ref _edges); _n0 = _n1; if (_n0.adjacents.Count == 1) _n1 = EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes); } while (_n0.adjacents.Count == 1) { primitive.nodes.Add(_n0); _n1 = EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes); EdgeGraphUtility.RemoveNodeAndCleanAdjacents(_n0, ref _nodes, ref _edges); Edge _e = EdgeGraphUtility.RemoveEdgeAndCleanAdjacents(_n0, _n1, ref _nodes, ref _edges); if (_e != null) primitive.edges.Add(_e); _n0 = _n1; } primitive.nodes.Add(_n0); if (_n0.adjacents.Count == 0) { EdgeGraphUtility.RemoveNodeAndCleanAdjacents(_n0, ref _nodes, ref _edges); } _primitives.Add(primitive); } }
/// <summary> /// Makes copies of edges and nodes /// New edges will have new IDs and new copies of nodes /// </summary> public static void CopyNodesAndEdges(List<Node> _nodes, List<Edge> _edges, out List<Node> _newNodes, out List<Edge> _newEdges, bool adjacentCheck = true, bool cleanUp = true) { _newNodes = new List<Node>(); _newEdges = new List<Edge>(); //Refresh adjacent nodes if (adjacentCheck) CheckAdjacentNodes(ref _nodes, ref _edges); if (cleanUp) CleanUpEdges(ref _nodes, ref _edges); //Calculate angle between adjacent nodes for (int i = 0; i < _nodes.Count; i++) { Node adj1 = null; Node adj2 = null; if (i == 0) { adj1 = _nodes[_nodes.Count - 1]; adj2 = _nodes[i + 1]; } else if (i == _nodes.Count - 1) { adj1 = _nodes[i - 1]; adj2 = _nodes[0]; } else { adj1 = _nodes[i - 1]; adj2 = _nodes[i + 1]; } Vector3 dirToNode1 = (adj1.Position - _nodes[i].Position).normalized; Vector3 dirToNode2 = (adj2.Position - _nodes[i].Position).normalized; Vector3 referenceForward = -dirToNode1; Vector3 referenceRight = Vector3.Cross(Vector3.up, referenceForward); float angleBetweenAdjs = Vector3.Angle(dirToNode1, dirToNode2); float angle = Vector3.Angle(dirToNode2, referenceForward); //Angles are in counter clockwise order, so positive dot product with right reference means angle is more than 180 degrees float sign = Mathf.Sign(Vector3.Dot(dirToNode2, referenceRight)); //Center position of this node and adjacent nodes //Vector3 center = (adj1.Position + adj2.Position + _nodes[i].Position) / 3f; Vector3 center = _nodes[i].Position + (dirToNode1 + dirToNode2).normalized; Vector3 dirToCenter = (center - _nodes[i].Position).normalized; if (sign > 0) { _nodes[i].Angle = 180f + angle; _nodes[i].dirToInside = -dirToCenter; } else { _nodes[i].Angle = angleBetweenAdjs; _nodes[i].dirToInside = dirToCenter; } } //Save old and new IDs of nodes in order <old, new> Dictionary<string, string> nodeIDPairs = new Dictionary<string, string>(); //Make new nodes and save the link to the old one for (int i = 0; i < _nodes.Count; i++) { Node oldNode = _nodes[i]; Node newNode = new Node(oldNode.Position, oldNode.Angle); newNode.dirToInside = oldNode.dirToInside; if (!nodeIDPairs.ContainsKey(oldNode.ID)) { nodeIDPairs.Add(oldNode.ID, newNode.ID); _newNodes.Add(newNode); } } //Now that we have all the links made, we can refresh adjacents for new nodes for (int i = 0; i < _nodes.Count; i++) { for (int j = 0; j < _nodes[i].adjacents.Count; j++) { string oldAdj = _nodes[i].adjacents[j]; Node newNode = _newNodes.Find(n => n.ID == nodeIDPairs[_nodes[i].ID]); if (nodeIDPairs.ContainsKey(oldAdj)) newNode.adjacents.Add(nodeIDPairs[oldAdj]); else newNode.adjacents.Add(oldAdj); } } //Make new edges for (int i = 0; i < _edges.Count; i++) { Edge e = _edges[i]; if (e == null) continue; //New nodes retrieved by using old node IDs Node n1 = _newNodes.Find(n => n.ID == nodeIDPairs[e.Node1]); Node n2 = _newNodes.Find(n => n.ID == nodeIDPairs[e.Node2]); Edge newE = new Edge(n1.ID, n2.ID, e.Width); _newEdges.Add(newE); } }
// Algorithms from http://www.geometrictools.com/Documentation/MinimalCycleBasis.pdf // The Minimal Cycle Basis for a Planar Graph by David Eberly /// <summary> /// Attempts to find minimal cycles /// </summary> public static void ExtractPrimitive(Node _n0, ref List<Node> _nodes, ref List<Edge> _edges, ref List<Primitive> _primitives) { List<Node> visited = new List<Node>(); List<Node> sequence = new List<Node>(); EdgeGraphUtility.CheckAdjacentNodes(ref _nodes, ref _edges); if (_n0.adjacents.Count == 0) { EdgeGraphUtility.RemoveNodeAndCleanAdjacents(_n0, ref _nodes, ref _edges); return; } sequence.Add(_n0); Node _n1 = GetClockwiseMostAdjacent(null, _n0, ref _nodes); Node prev = _n0; Node curr = _n1; while (curr != null && curr != _n0 && !visited.Contains(curr)) { sequence.Add(curr); visited.Add(curr); Node next = GetCounterClockwiseMostAdjacent(prev, curr, ref _nodes); prev = curr; curr = next; } if (curr == null) { // Filament found, not necessarily rooted at prev ExtractFilament(prev, EdgeGraphUtility.GetNode(prev.adjacents[0], ref _nodes), ref _nodes, ref _edges, ref _primitives); } else if (curr == _n0) { // Minimal cycle found Primitive primitive = new Primitive(Primitive.PrimitiveType.MinimalCycle); primitive.nodes.AddRange(sequence); for (int i = 0; i < sequence.Count; i++) { Node n1; Node n2; if (i == sequence.Count - 1) { n1 = sequence[i]; n2 = sequence[0]; } else { n1 = sequence[i]; n2 = sequence[i + 1]; } Edge e = EdgeGraphUtility.FindEdgeByNodes(n1, n2, _edges); if (e != null) { primitive.edges.Add(e); e.isPartOfCycle = true; } } EdgeGraphUtility.RemoveEdgeAndCleanAdjacents(_n0, _n1, ref _nodes, ref _edges); if (_n0.adjacents.Count == 1) { // Remove the filament rooted at v0 ExtractFilament(_n0, EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes), ref _nodes, ref _edges, ref _primitives); } if (_n1.adjacents.Count == 1) { // Remove the filament rooted at v1 ExtractFilament(_n1, EdgeGraphUtility.GetNode(_n1.adjacents[0], ref _nodes), ref _nodes, ref _edges, ref _primitives); } _primitives.Add(primitive); } else // curr was visited earlier { // A cycle has been found, but is not guaranteed to be a minimal cycle. // This implies v0 is part of a filament // Locate the starting point for the filament by traversing from v0 away from the initial v1 while (_n0.adjacents.Count == 2) { if (_n0.adjacents[0] != _n1.ID) { _n1 = _n0; _n0 = EdgeGraphUtility.GetNode(_n0.adjacents[0], ref _nodes); } else { _n1 = _n0; _n0 = EdgeGraphUtility.GetNode(_n0.adjacents[1], ref _nodes); } } ExtractFilament(_n0, _n1, ref _nodes, ref _edges, ref _primitives); } }
/// Move selected node while dragging void NodeTools() { if (currentEditorState != GraphEditorState.Nodes) return; // Moving nodes if (mouseIsPressed && !shiftIsPressed && !controlIsPressed && activeNode >= 0 && activeNode < graph.nodes.Count) { graph.nodes[activeNode].Position = cursorLocalPosition; } if (mouseWasReleased) { // Node deleting float toClosest; Node closestNode = CheckClosestNode(graph.nodes, out toClosest); if (shiftIsPressed && !controlIsPressed && closestNode != null) { graph.RemoveNode(closestNode.ID); } } if (MouseClickedDown) { // Node adding if (controlIsPressed) { if (shiftIsPressed) { Vector3 splitPointLocal = graph.transform.InverseTransformPoint(EdgeGraphUtility.GetClosestPointOnEdge(cursorWorldPosition, graph.nodes, graph.edges, graph.transform, out closestEdge)); Node newNode = new Node(splitPointLocal); graph.nodes.Add(newNode); graph.edges.Add(new Edge(closestEdge.Node1, newNode.ID, closestEdge.Width)); graph.edges.Add(new Edge(newNode.ID, closestEdge.Node2, closestEdge.Width)); graph.edges.Remove(closestEdge); closestEdge = null; } else { if (connectNewNode) { Node newNode = new Node(cursorLocalPosition); graph.nodes.Add(newNode); float toClosest; Node closestNode = CheckClosestNode(graph.nodes, out toClosest); if (closestNode != null) graph.edges.Add(new Edge(closestNode.ID, newNode.ID)); } else graph.nodes.Add(new Node(cursorLocalPosition)); } } } }