/// <summary> /// Returns closest point on all the given edges. Edge positions are changed to world coordinates in reference to refTransform. /// </summary> public static Vector3 GetClosestPointOnEdge(Vector3 point, List<Node> nodes, List<Edge> edges, Transform refTransform, out Edge closestEdge) { Vector3 pointOnEdge = Vector3.zero; closestEdge = edges[0]; float closestDist = Mathf.Infinity; for (int i = 0; i < edges.Count; i++) { Vector2 splitPointXZ; Node node1 = EdgeGraphUtility.GetNode(edges[i].Node1, ref nodes); if (node1 == null) continue; Vector3 n1Pos = node1.Position; n1Pos = refTransform.TransformPoint(n1Pos); Node node2 = EdgeGraphUtility.GetNode(edges[i].Node2, ref nodes); if (node2 == null) continue; Vector3 n2Pos = node2.Position; n2Pos = refTransform.TransformPoint(n2Pos); splitPointXZ = Edge.GetClosestPointOnEdge(point, n1Pos, n2Pos); Vector3 splitPoint = new Vector3(splitPointXZ.x, n1Pos.y, splitPointXZ.y); if (Vector3.Distance(point, splitPoint) < closestDist) { closestEdge = edges[i]; pointOnEdge = splitPoint; closestDist = Vector3.Distance(point, splitPoint); } } return pointOnEdge; }
/// <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); } }
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)); } } } } } }
void DrawEdgeHandles() { if (currentEditorState != GraphEditorState.Edges) return; Handles.color = Color.green * new Color(1f, 1f, 1f, .5f); DrawNodePositionHandles(controlIsPressed); // Adding new edges if (controlIsPressed && !setEdgeWidth) { /// ----------------------------------- /// Connect two closest nodes to the cursor /// Wasn't that intuitive /// ----------------------------------- //List<Node> _nodes = new List<Node>(); //_nodes.AddRange(graph.nodes); //// Sort nodes according to the distance from cursor //_nodes.Sort((n1, n2) => //{ // Vector3 n1Pos = graph.transform.TransformPoint(n1.Position); // Vector3 n2Pos = graph.transform.TransformPoint(n2.Position); // float distN1 = Vector3.Distance(n1Pos, cursorWorldPosition); // float distN2 = Vector3.Distance(n2Pos, cursorWorldPosition); // return distN1.CompareTo(distN2); //}); //// Draw lines to closest nodes from the cursor //Handles.color = Color.green * new Color(1f, 1f, 1f, .5f); //Handles.DrawLine(cursorWorldPosition, graph.transform.TransformPoint(_nodes[0].Position)); //Handles.DrawLine(cursorWorldPosition, graph.transform.TransformPoint(_nodes[1].Position)); //Handles.color = Color.yellow; //Handles.DrawLine(graph.transform.TransformPoint(_nodes[0].Position), graph.transform.TransformPoint(_nodes[1].Position)); /// ----------------------------------- /// ----------------------------------- /// Connect selected node and closest other node to cursor /// ----------------------------------- if (activeNode >= 0 && activeNode < graph.nodes.Count) { Node _selected = graph.nodes[activeNode]; float toClosest; Node closestNode = CheckClosestNode(graph.nodes, 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 creatingNewEdge = new Edge(_selected.ID, closestNode.ID); } else creatingNewEdge = null; } } // Deleting edges Handles.color = new Color(1f, 0f, 0f, .5f); if (shiftIsPressed && !controlIsPressed && !setEdgeWidth) { //float toClosestNode; //Node closestNode = CheckClosestNode(graph.nodes, out toClosestNode); //float toClosestEdge = CheckClosestEdge(); Handles.color = new Color(1f, 0f, 0f, .5f); Handles.SphereCap(0, graph.GetEdgePosition(closestEdge.ID), Quaternion.identity, HandleUtility.GetHandleSize(graph.GetEdgePosition(closestEdge.ID)) * .4f); Handles.color = Color.blue; for (int i = 0; i < graph.edges.Count; i++) { Handles.CubeCap(i, graph.GetEdgePosition(graph.edges[i].ID), Quaternion.identity, HandleUtility.GetHandleSize(graph.GetEdgePosition(graph.edges[i].ID)) * .2f); } } // Edge width setting and debug information if (graph.edges != null && graph.edges.Count > 0) { edgesToSetWidth = new List<Edge>(); for (int i = 0; i < graph.edges.Count; i++) { Handles.color = Color.yellow; Node n1 = graph[graph.edges[i].Node1]; Node n2 = graph[graph.edges[i].Node2]; if (n1 != null && n2 != null) { Handles.DrawLine(graph.transform.TransformPoint(n1.Position), graph.transform.TransformPoint(n2.Position)); Vector3 edgePos = graph.GetEdgePosition(graph.edges[i].ID); float handleSize = HandleUtility.GetHandleSize(edgePos) * .2f; if (setEdgeWidth) { if (Vector3.Distance(cursorWorldPosition, edgePos) <= setEdgeWidthBrushSize) { Handles.color = Color.green; edgesToSetWidth.Add(graph.edges[i]); } Handles.CubeCap(i, edgePos, Quaternion.identity, handleSize); Handles.DrawLine(edgePos, edgePos + Vector3.right + Vector3.back); Handles.Label(edgePos + Vector3.right + Vector3.back, string.Format("{0:0.0}", graph.edges[i].Width), "box"); } if (debugMode) { //Edge normals Handles.color = Color.red; Vector3 edgePerpLeft = Edge.GetLeftPerpendicular(n1.Position, n2.Position); Handles.DrawLine(edgePos, edgePos + edgePerpLeft * .1f); Handles.color = Color.blue; Vector3 edgePerpRight = Edge.GetRightPerpendicular(n1.Position, n2.Position); Handles.DrawLine(edgePos, edgePos + edgePerpRight * .1f); //Edge widths visualized Handles.color = Color.yellow; float edgeWidth = graph.edges[i].Width / 2f; Vector3 left1 = graph.transform.TransformPoint(n1.Position + edgePerpLeft.normalized * edgeWidth); Vector3 left2 = graph.transform.TransformPoint(n2.Position + edgePerpLeft.normalized * edgeWidth); Vector3 right1 = graph.transform.TransformPoint(n1.Position + edgePerpRight.normalized * edgeWidth); Vector3 right2 = graph.transform.TransformPoint(n2.Position + edgePerpRight.normalized * edgeWidth); Handles.DrawDottedLine(left1, left2, HandleUtility.GetHandleSize((left1 + left2) / 2f)); Handles.DrawDottedLine(right1, right2, HandleUtility.GetHandleSize((right1 + right2) / 2f)); } } } } }
void PrimitiveTools() { if (currentEditorState != GraphEditorState.Primitives) return; if (mouseIsPressed && !shiftIsPressed && !controlIsPressed) { if (activeSubNode >= 0 && singleSelectedPrimitiveIdx >= 0 && singleSelectedPrimitiveIdx < graph.mainPrimitives.Count && activeSubNode >= 0 && activeSubNode < graph.mainPrimitives[singleSelectedPrimitiveIdx].subNodes.Count) { graph.mainPrimitives[singleSelectedPrimitiveIdx].subNodes[activeSubNode].Position = cursorLocalPosition; } } //if (MouseClickedDown) //{ //} if (mouseWasReleased) { // Adding new subedge if (controlIsPressed && !shiftIsPressed && SinglePrimitiveIsSelected && creatingNewSubEdge != null) { graph.mainPrimitives[singleSelectedPrimitiveIdx].subEdges.Add(creatingNewSubEdge); creatingNewSubEdge = null; } //Select primitive if (!shiftIsPressed && graph.mainPrimitives != null && graph.mainPrimitives.Count > 0 && activeSubNode == -1) { singleSelectedPrimitiveIdx = -1; int primitiveClicked = -1; for (int i = 0; i < graph.mainPrimitives.Count; i++) { if (EdgeGraphUtility.PointIsInside(cursorLocalPosition, graph.mainPrimitives[i].nodes, graph.mainPrimitives[i].edges)) { primitiveClicked = i; break; } } if (controlIsPressed && selectedPrimitiveMask != 0 && selectedPrimitiveMask != (1 << primitiveClicked)) { selectedPrimitiveMask |= 1 << primitiveClicked; } else if (primitiveClicked != -1) { singleSelectedPrimitiveIdx = primitiveClicked; selectedPrimitiveMask = 1 << primitiveClicked; } else { selectedPrimitiveMask = 0; } GUI.changed = true; } // Change subedge root index if (shiftIsPressed && !controlIsPressed && SinglePrimitiveIsSelected && subEdgeRootNode != null) { //subEdgeRootNode = EdgeGraphUtility.GetClosestNode(cursorWorldPosition, graph.mainPrimitives[selectedPrimitiveIdx].nodes, graph.transform); int subEdgeRootIdx = graph.mainPrimitives[singleSelectedPrimitiveIdx].nodes.IndexOf(subEdgeRootNode); graph.mainPrimitives[singleSelectedPrimitiveIdx].subEdgeRootIndex = subEdgeRootIdx; } } }
void EdgeTools() { if (graph.nodes == null || graph.nodes.Count <= 0 || currentEditorState != GraphEditorState.Edges) return; if (mouseWasReleased) { // Edge removal float toClosest; Node closestNode = CheckClosestNode(graph.nodes, out toClosest); if (shiftIsPressed && !controlIsPressed && closestNode != null && !setEdgeWidth) { graph.edges.Remove(closestEdge); } // Edge adding if (controlIsPressed && creatingNewEdge != null && !setEdgeWidth) { graph.edges.Add(creatingNewEdge); creatingNewEdge = null; } } // Edge width setting brush if (mouseIsPressed && setEdgeWidth) { foreach (var edge in edgesToSetWidth) edge.Width = setEdgeToWidth; } }
/// 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)); } } } }