using UnityEngine; using Pathfinding.Serialization; namespace Pathfinding { /// /// Node used for the PointGraph. /// This is just a simple point with a list of connections (and associated costs) to other nodes. /// It does not have any concept of a surface like many other node types. /// /// See: PointGraph /// public class PointNode : GraphNode { /// /// All connections from this node. /// See: /// See: /// See: /// /// Note: If you modify this array or the contents of it you must call . /// /// Note: If you modify this array or the contents of it you must call with the length of the new connections. /// /// This may be null if the node has no connections to other nodes. /// public Connection[] connections; /// /// GameObject this node was created from (if any). /// Warning: When loading a graph from a saved file or from cache, this field will be null. /// /// /// var node = AstarPath.active.GetNearest(transform.position).node; /// var pointNode = node as PointNode; /// /// if (pointNode != null) { /// Debug.Log("That node was created from the GameObject named " + pointNode.gameObject.name); /// } else { /// Debug.Log("That node is not a PointNode"); /// } /// /// public GameObject gameObject; [System.Obsolete("Set node.position instead")] public void SetPosition (Int3 value) { position = value; } public PointNode() { } public PointNode (AstarPath astar) { astar.InitializeNode(this); } /// /// Closest point on the surface of this node to the point p. /// /// For a point node this is always the node's sicne it has no surface. /// public override Vector3 ClosestPointOnNode (Vector3 p) { return (Vector3)this.position; } /// /// Checks if point is inside the node when seen from above. /// /// Since point nodes have no surface area, this method always returns false. /// public override bool ContainsPoint (Vector3 point) { return false; } /// /// Checks if point is inside the node in graph space. /// /// Since point nodes have no surface area, this method always returns false. /// public override bool ContainsPointInGraphSpace (Int3 point) { return false; } public override void GetConnections(GetConnectionsWithData action, ref T data, int connectionFilter) { if (connections == null) return; for (int i = 0; i < connections.Length; i++) if ((connections[i].shapeEdgeInfo & connectionFilter) != 0) action(connections[i].node, ref data); } public override void ClearConnections (bool alsoReverse) { if (alsoReverse && connections != null) { for (int i = 0; i < connections.Length; i++) { connections[i].node.RemovePartialConnection(this); } } connections = null; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } public override bool ContainsOutgoingConnection (GraphNode node) { if (connections == null) return false; for (int i = 0; i < connections.Length; i++) if (connections[i].node == node && connections[i].isOutgoing) return true; return false; } public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) { if (node == null) throw new System.ArgumentNullException(); if (connections != null) { for (int i = 0; i < connections.Length; i++) { if (connections[i].node == node) { connections[i].cost = cost; connections[i].shapeEdgeInfo = Connection.PackShapeEdgeInfo(isOutgoing, isIncoming); return; } } } int connLength = connections != null ? connections.Length : 0; var newconns = new Connection[connLength+1]; for (int i = 0; i < connLength; i++) { newconns[i] = connections[i]; } newconns[connLength] = new Connection(node, cost, isOutgoing, isIncoming); connections = newconns; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); // Make sure the graph knows that there exists a connection with this length if (this.Graph is PointGraph pg) pg.RegisterConnectionLength((node.position - position).sqrMagnitudeLong); } public override void RemovePartialConnection (GraphNode node) { if (connections == null) return; for (int i = 0; i < connections.Length; i++) { if (connections[i].node == node) { int connLength = connections.Length; var newconns = new Connection[connLength-1]; for (int j = 0; j < i; j++) { newconns[j] = connections[j]; } for (int j = i+1; j < connLength; j++) { newconns[j-1] = connections[j]; } connections = newconns; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); return; } } } public override void Open (Path path, uint pathNodeIndex, uint gScore) { path.OpenCandidateConnectionsToEndNode(position, pathNodeIndex, pathNodeIndex, gScore); if (connections == null) return; for (int i = 0; i < connections.Length; i++) { GraphNode other = connections[i].node; if (connections[i].isOutgoing && path.CanTraverse(this, other)) { if (other is PointNode) { path.OpenCandidateConnection(pathNodeIndex, other.NodeIndex, gScore, connections[i].cost, 0, other.position); } else { // When connecting to a non-point node, use a special function to open the connection. // The typical case for this is that we are at the end of an off-mesh link and we are connecting to a navmesh node. // In that case, this node's position is in the interior of the navmesh node. We let the navmesh node decide how // that should be handled. other.OpenAtPoint(path, pathNodeIndex, position, gScore); } } } } public override void OpenAtPoint (Path path, uint pathNodeIndex, Int3 pos, uint gScore) { if (path.CanTraverse(this)) { // TODO: Ideally we should only allow connections to the temporary end node directly from the temporary start node // iff they lie on the same connection edge. Otherwise we need to pass through the center of this node. // // N1---E----N2 // | / // | / // S // | // N3 // path.OpenCandidateConnectionsToEndNode(pos, pathNodeIndex, pathNodeIndex, gScore); var cost = (uint)(pos - this.position).costMagnitude; path.OpenCandidateConnection(pathNodeIndex, NodeIndex, gScore, cost, 0, position); } } public override int GetGizmoHashCode () { var hash = base.GetGizmoHashCode(); if (connections != null) { for (int i = 0; i < connections.Length; i++) { hash ^= 17 * connections[i].GetHashCode(); } } return hash; } public override void SerializeNode (GraphSerializationContext ctx) { base.SerializeNode(ctx); ctx.SerializeInt3(position); } public override void DeserializeNode (GraphSerializationContext ctx) { base.DeserializeNode(ctx); position = ctx.DeserializeInt3(); } public override void SerializeReferences (GraphSerializationContext ctx) { ctx.SerializeConnections(connections, true); } public override void DeserializeReferences (GraphSerializationContext ctx) { connections = ctx.DeserializeConnections(ctx.meta.version >= AstarSerializer.V4_3_85); } } }