using UnityEngine; using System.Collections.Generic; using Pathfinding.Serialization; namespace Pathfinding { using Pathfinding.Util; /// Represents a connection to another node public struct Connection { /// Node which this connection goes to public GraphNode node; /// /// Cost of moving along this connection. /// A cost of 1000 corresponds approximately to the cost of moving one world unit. /// public uint cost; /// /// Various metadata about the connection, such as the side of the node shape which this connection uses. /// /// - Bits 0..1 represent . /// - Bits 2..3 represent . /// - Bit 4 represents . /// - Bit 5 represents . /// - Bit 6 represents . /// /// Note: Due to alignment, the and fields use 12 bytes which will be padded /// to 16 bytes when used in an array even if this field would be removed. /// So this field does not contribute to increased memory usage. We could even expand it to 32-bits if we need to in the future. /// public byte shapeEdgeInfo; /// /// The edge of the shape which this connection uses. /// This is an index into the shape's vertices. /// /// A value of 0 corresponds to using the side for vertex 0 and vertex 1 on the node. 1 corresponds to vertex 1 and 2, etc. /// A value of 3 is invalid, and this will be the value if is false. /// /// See: /// See: /// public int shapeEdge => shapeEdgeInfo & 0b11; /// /// The edge of the shape in the other node, which this connection represents. /// /// See: /// public int adjacentShapeEdge => (shapeEdgeInfo >> 2) & 0b11; /// /// True if the two nodes share an identical edge. /// /// This is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents. /// /// In contrast to , this is true only if the triangle edge is identical (but reversed) in the other node. /// public bool edgesAreIdentical => (shapeEdgeInfo & IdenticalEdge) != 0; /// /// True if the two nodes share an edge. /// /// This is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents. /// Note that the edge does not need to be perfectly identical for this to be true, it is enough if the edge is very similar. /// public bool isEdgeShared => (shapeEdgeInfo & NoSharedEdge) != NoSharedEdge; /// /// True if the connection allows movement from this node to the other node. /// /// A connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. /// public bool isOutgoing => (shapeEdgeInfo & OutgoingConnection) != 0; /// /// True if the connection allows movement from the other node to this node. /// /// A connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. /// public bool isIncoming => (shapeEdgeInfo & IncomingConnection) != 0; public const byte NoSharedEdge = 0b1111; public const byte IncomingConnection = 1 << 4; public const byte OutgoingConnection = 1 << 5; public const byte IdenticalEdge = 1 << 6; [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public Connection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) { this.node = node; this.cost = cost; this.shapeEdgeInfo = PackShapeEdgeInfo(isOutgoing, isIncoming); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static byte PackShapeEdgeInfo(bool isOutgoing, bool isIncoming) => (byte)(NoSharedEdge | (isIncoming ? IncomingConnection : 0) | (isOutgoing ? OutgoingConnection : 0)); [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static byte PackShapeEdgeInfo (byte shapeEdge, byte adjacentShapeEdge, bool areEdgesIdentical, bool isOutgoing, bool isIncoming) { #if UNITY_EDITOR if (shapeEdge > 3) throw new System.ArgumentException("shapeEdge must be at most 3"); if (adjacentShapeEdge > 3) throw new System.ArgumentException("adjacentShapeEdge must be at most 3"); #endif return (byte)(shapeEdge | (adjacentShapeEdge << 2) | (areEdgesIdentical ? IdenticalEdge : 0) | (isOutgoing ? OutgoingConnection : 0) | (isIncoming ? IncomingConnection : 0)); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public Connection (GraphNode node, uint cost, byte shapeEdgeInfo) { this.node = node; this.cost = cost; this.shapeEdgeInfo = shapeEdgeInfo; } public override int GetHashCode () { return node.GetHashCode() ^ (int)cost; } public override bool Equals (object obj) { if (!(obj is Connection)) return false; var conn = (Connection)obj; return conn.node == node && conn.cost == cost && conn.shapeEdgeInfo == shapeEdgeInfo; } } /// Base class for all nodes public abstract class GraphNode { /// Internal unique index. Also stores some bitpacked values such as and . private int nodeIndex; /// /// Bitpacked field holding several pieces of data. /// See: Walkable /// See: Area /// See: GraphIndex /// See: Tag /// protected uint flags; #if !ASTAR_NO_PENALTY /// /// Penalty cost for walking on this node. /// This can be used to make it harder/slower to walk over certain nodes. /// /// A penalty of 1000 (Int3.Precision) corresponds to the cost of walking one world unit. /// /// See: graph-updates (view in online documentation for working links) /// private uint penalty; #endif /// /// Graph which this node belongs to. /// /// If you know the node belongs to a particular graph type, you can cast it to that type: /// /// GraphNode node = ...; /// GridGraph graph = node.Graph as GridGraph; /// /// /// Will return null if the node has been destroyed. /// public NavGraph Graph => AstarData.GetGraph(this); /// /// Destroys the node. /// Cleans up any temporary pathfinding data used for this node. /// The graph is responsible for calling this method on nodes when they are destroyed, including when the whole graph is destoyed. /// Otherwise memory leaks might present themselves. /// /// Once called the property will return true and subsequent calls to this method will not do anything. /// /// Note: Assumes the current active AstarPath instance is the same one that created this node. /// /// Warning: Should only be called by graph classes on their own nodes /// public void Destroy () { if (Destroyed) return; ClearConnections(true); if (AstarPath.active != null) { AstarPath.active.DestroyNode(this); } NodeIndex = DestroyedNodeIndex; } public bool Destroyed => NodeIndex == DestroyedNodeIndex; // If anyone creates more than about 200 million nodes then things will not go so well, however at that point one will certainly have more pressing problems, such as having run out of RAM const uint NodeIndexMask = 0xFFFFFFF; public const uint DestroyedNodeIndex = NodeIndexMask - 1; public const int InvalidNodeIndex = 0; const int TemporaryFlag1Mask = 0x10000000; const int TemporaryFlag2Mask = 0x20000000; /// /// Internal unique index. /// Every node will get a unique index. /// This index is not necessarily correlated with e.g the position of the node in the graph. /// public uint NodeIndex { get { return (uint)nodeIndex & NodeIndexMask; } internal set { nodeIndex = (int)(((uint)nodeIndex & ~NodeIndexMask) | value); } } /// /// How many path node variants should be created for each node. /// /// This should be a constant for each node type. /// /// Typically this is 1, but for example the triangle mesh node type has 3 variants, one for each edge. /// /// See: /// internal virtual int PathNodeVariants => 1; /// /// Temporary flag for internal purposes. /// May only be used in the Unity thread. Must be reset to false after every use. /// internal bool TemporaryFlag1 { get { return (nodeIndex & TemporaryFlag1Mask) != 0; } set { nodeIndex = (nodeIndex & ~TemporaryFlag1Mask) | (value ? TemporaryFlag1Mask : 0); } } /// /// Temporary flag for internal purposes. /// May only be used in the Unity thread. Must be reset to false after every use. /// internal bool TemporaryFlag2 { get { return (nodeIndex & TemporaryFlag2Mask) != 0; } set { nodeIndex = (nodeIndex & ~TemporaryFlag2Mask) | (value ? TemporaryFlag2Mask : 0); } } /// /// Position of the node in world space. /// Note: The position is stored as an Int3, not a Vector3. /// You can convert an Int3 to a Vector3 using an explicit conversion. /// var v3 = (Vector3)node.position; /// public Int3 position; #region Constants /// Position of the walkable bit. See: const int FlagsWalkableOffset = 0; /// Mask of the walkable bit. See: const uint FlagsWalkableMask = 1 << FlagsWalkableOffset; /// Start of hierarchical node index bits. See: const int FlagsHierarchicalIndexOffset = 1; /// Mask of hierarchical node index bits. See: const uint HierarchicalIndexMask = (131072-1) << FlagsHierarchicalIndexOffset; /// Start of bits. See: const int HierarchicalDirtyOffset = 18; /// Mask of the bit. See: const uint HierarchicalDirtyMask = 1 << HierarchicalDirtyOffset; /// Start of graph index bits. See: const int FlagsGraphOffset = 24; /// Mask of graph index bits. See: const uint FlagsGraphMask = (256u-1) << FlagsGraphOffset; public const uint MaxHierarchicalNodeIndex = HierarchicalIndexMask >> FlagsHierarchicalIndexOffset; /// Max number of graphs-1 public const uint MaxGraphIndex = (FlagsGraphMask-1) >> FlagsGraphOffset; public const uint InvalidGraphIndex = (FlagsGraphMask) >> FlagsGraphOffset; /// Start of tag bits. See: const int FlagsTagOffset = 19; /// Max number of tags - 1. Always a power of 2 minus one public const int MaxTagIndex = 32 - 1; /// Mask of tag bits. See: const uint FlagsTagMask = MaxTagIndex << FlagsTagOffset; #endregion #region Properties /// /// Holds various bitpacked variables. /// /// Bit 0: /// Bits 1 through 17: /// Bit 18: /// Bits 19 through 23: /// Bits 24 through 31: /// /// Warning: You should pretty much never modify this property directly. Use the other properties instead. /// public uint Flags { get => flags; set => flags = value; } /// /// Penalty cost for walking on this node. /// This can be used to make it harder/slower to walk over specific nodes. /// A cost of 1000 () corresponds to the cost of moving 1 world unit. /// /// See: graph-updates (view in online documentation for working links) /// public uint Penalty { #if !ASTAR_NO_PENALTY get => penalty; set { if (value > 0xFFFFFF) Debug.LogWarning("Very high penalty applied. Are you sure negative values haven't underflowed?\n" + "Penalty values this high could with long paths cause overflows and in some cases infinity loops because of that.\n" + "Penalty value applied: "+value); penalty = value; } #else get => 0U; set {} #endif } /// /// True if the node is traversable. /// /// See: graph-updates (view in online documentation for working links) /// public bool Walkable { get => (flags & FlagsWalkableMask) != 0; set { flags = flags & ~FlagsWalkableMask | (value ? 1U : 0U) << FlagsWalkableOffset; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } } /// /// Hierarchical Node that contains this node. /// The graph is divided into clusters of small hierarchical nodes in which there is a path from every node to every other node. /// This structure is used to speed up connected component calculations which is used to quickly determine if a node is reachable from another node. /// /// See: /// /// Warning: This is an internal property and you should most likely not change it. /// /// Warning: This is only guaranteed to be valid outside of graph updates, and only for walkable nodes. /// internal int HierarchicalNodeIndex { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] get => (int)((flags & HierarchicalIndexMask) >> FlagsHierarchicalIndexOffset); [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] set => flags = (flags & ~HierarchicalIndexMask) | (uint)(value << FlagsHierarchicalIndexOffset); } /// Some internal bookkeeping internal bool IsHierarchicalNodeDirty { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] get => (flags & HierarchicalDirtyMask) != 0; [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] set => flags = flags & ~HierarchicalDirtyMask | (value ? 1U : 0U) << HierarchicalDirtyOffset; } /// /// Connected component that contains the node. /// This is visualized in the scene view as differently colored nodes (if the graph coloring mode is set to 'Areas'). /// Each area represents a set of nodes such that there is no valid path between nodes of different colors. /// /// See: https://en.wikipedia.org/wiki/Connected_component_(graph_theory) /// See: /// public uint Area => AstarPath.active.hierarchicalGraph.GetConnectedComponent(HierarchicalNodeIndex); /// /// Graph which contains this node. /// See: /// See: /// public uint GraphIndex { [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] get => (flags & FlagsGraphMask) >> FlagsGraphOffset; set => flags = flags & ~FlagsGraphMask | value << FlagsGraphOffset; } /// /// Node tag. /// See: tags (view in online documentation for working links) /// See: graph-updates (view in online documentation for working links) /// public uint Tag { get => (flags & FlagsTagMask) >> FlagsTagOffset; set => flags = flags & ~FlagsTagMask | ((value << FlagsTagOffset) & FlagsTagMask); } #endregion /// /// Inform the system that the node's connectivity has changed. /// This is used for recalculating the connected components of the graph. /// /// See: /// /// You must call this method if you change the connectivity or walkability of the node without going through the high level methods /// such as the property or the method. For example if your manually change the array you need to call this method. /// public void SetConnectivityDirty () { AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } /// /// Calls the delegate with all connections from this node. /// /// * /// /// node.GetConnections(connectedTo => { /// Debug.DrawLine((Vector3)node.position, (Vector3)connectedTo.position, Color.red); /// }); /// /// /// You can add all connected nodes to a list like this /// /// var connections = new List(); /// node.GetConnections(connections.Add); /// /// /// The delegate which will be called once for every connection. /// A bitmask of which connection types will be included. You may pass any combination of \reflink{Connection.OutgoingConnection} and \reflink{Connection.IncomingConnection}. /// Defaults to only outgoing connections. Unless one-way links are added to a graph, all connections will typically be bidirectional. public virtual void GetConnections (System.Action action, int connectionFilter = Connection.OutgoingConnection) { GetConnections((GraphNode node, ref System.Action action) => action(node), ref action, connectionFilter); } /// /// Calls the delegate with all connections from or to this node, and passes a custom data value to the delegate. /// /// /// node.GetConnections(connectedTo => { /// Debug.DrawLine((Vector3)node.position, (Vector3)connectedTo.position, Color.red); /// }); /// /// /// You can add all connected nodes to a list like this /// /// var connections = new List(); /// node.GetConnections(connections.Add); /// /// /// The delegate which will be called once for every connection. /// The first parameter to the delegate is the connection and the second parameter is the custom data passed to this method. /// Custom data which will be passed to the delegate. /// A bitmask of which connection types will be included. A connection can either be incoming, outgoing, or both (bidirectional). You may pass any combination of \reflink{Connection.OutgoingConnection} and \reflink{Connection.IncomingConnection}. /// Defaults to only outgoing connections. Unless one-way links are added to a graph, all connections will typically be bidirectional. public abstract void GetConnections(GetConnectionsWithData action, ref T data, int connectionFilter = Connection.OutgoingConnection); public delegate void GetConnectionsWithData(GraphNode node, ref T data); /// /// Adds a connection between two nodes. /// /// If the nodes already have a connection to each other, that connection will be updated with the new cost. /// /// Note that some graphs have a special representation for some connections which is more efficient. /// For example grid graphs can represent connections to its 8 neighbours more efficiently. /// But to use that efficient representation you'll need to call instead of this method. /// /// This is different from an off-mesh link. An off-mesh link contains more metadata about the connection and is in many cases preferable to use instead of this method. /// This is a much lower-level method which is used by off-mesh links internally. /// /// Movement scripts such as the or may get confused if they try to follow a connection made using this method /// as it does not contain any information about how to traverse the connection. /// /// Internally, both nodes keep track of the connection to the other node, even for a one-way connection. /// This is done to make sure the connection can always be removed later on, if for example one of the nodes is destroyed. /// /// /// // Connect two nodes /// var node1 = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node; /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NNConstraint.None).node; /// var cost = (uint)(node2.position - node1.position).costMagnitude; /// /// GraphNode.Connect(node1, node2, cost, OffMeshLinks.Directionality.TwoWay); /// /// /// See: /// See: which is a lower level method. But if you use it, you need to handle invariants yourself. /// /// First node to connect. /// Second node to connect /// Cost of the connection. A cost of 1000 corresponds approximately to the cost of moving one world unit. See \reflink{Int3.Precision}. /// Determines if both lhs->rhs and rhs->lhs connections will be created, or if only a connection from lhs->rhs should be created. public static void Connect (GraphNode lhs, GraphNode rhs, uint cost, OffMeshLinks.Directionality directionality = OffMeshLinks.Directionality.TwoWay) { lhs.AddPartialConnection(rhs, cost, true, directionality == OffMeshLinks.Directionality.TwoWay); rhs.AddPartialConnection(lhs, cost, directionality == OffMeshLinks.Directionality.TwoWay, true); } /// /// Removes the connection between two nodes. /// /// If no connection exists between the nodes, nothing will be done. /// /// This will also handle special connections representations that some node types use. For example grid graphs represent /// the connections to their 8 grid neighbours differently from other connections. /// /// See: /// public static void Disconnect (GraphNode lhs, GraphNode rhs) { lhs.RemovePartialConnection(rhs); rhs.RemovePartialConnection(lhs); } /// /// Adds a connection to the given node. /// /// Deprecated: Use the static method instead, or if you really need to. /// [System.Obsolete("Use the static Connect method instead, or AddPartialConnection if you really need to")] public void AddConnection(GraphNode node, uint cost) => AddPartialConnection(node, cost, true, true); /// /// Removes a connection to the given node. /// /// Deprecated: Use the static method instead, or if you really need to. /// [System.Obsolete("Use the static Disconnect method instead, or RemovePartialConnection if you really need to")] public void RemoveConnection(GraphNode node) => RemovePartialConnection(node); /// /// Add a connection from this node to the specified node. /// If the connection already exists, the cost will simply be updated and /// no extra connection added. /// /// /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { /// // Connect two nodes /// var node1 = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node; /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NNConstraint.None).node; /// var cost = (uint)(node2.position - node1.position).costMagnitude; /// node1.AddPartialConnection(node2, cost, true, true); /// node2.AddPartialConnection(node1, cost, true, true); /// /// node1.ContainsOutgoingConnection(node2); // True /// /// node1.RemovePartialConnection(node2); /// node2.RemovePartialConnection(node1); /// })); /// /// /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. /// /// Some graphs have a special representation for some connections which is more efficient. /// For example grid graphs can represent connections to its 8 neighbours more efficiently. /// But to use that efficient representation you'll need to call instead of this method. /// public abstract void AddPartialConnection(GraphNode node, uint cost, bool isOutgoing, bool isIncoming); /// /// Removes any connection from this node to the specified node. /// If no such connection exists, nothing will be done. /// /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. /// Graphs sometimes use this method directly to improve performance in some situations. /// /// /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { /// // Connect two nodes /// var node1 = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node; /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NNConstraint.None).node; /// var cost = (uint)(node2.position - node1.position).costMagnitude; /// node1.AddPartialConnection(node2, cost, true, true); /// node2.AddPartialConnection(node1, cost, true, true); /// /// node1.ContainsOutgoingConnection(node2); // True /// /// node1.RemovePartialConnection(node2); /// node2.RemovePartialConnection(node1); /// })); /// /// public abstract void RemovePartialConnection(GraphNode node); /// /// Remove all connections between this node and other nodes. /// /// Warning: If you pass false to the alsoReverse parameter, you must ensure that you preserve the required invariants of connections. See . /// /// if true, neighbours will be requested to remove connections to this node. public abstract void ClearConnections(bool alsoReverse = true); /// /// True if this node contains a connection to the given node. /// /// Deprecated: Use instead /// [System.Obsolete("Use ContainsOutgoingConnection instead")] public bool ContainsConnection(GraphNode node) => ContainsOutgoingConnection(node); /// /// True if this node contains a connection to the given node. /// /// This will not return true if another node has a one-way connection to this node. /// /// /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { /// // Connect two nodes /// var node1 = AstarPath.active.GetNearest(transform.position, NNConstraint.None).node; /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NNConstraint.None).node; /// var cost = (uint)(node2.position - node1.position).costMagnitude; /// node1.AddPartialConnection(node2, cost, true, true); /// node2.AddPartialConnection(node1, cost, true, true); /// /// node1.ContainsOutgoingConnection(node2); // True /// /// node1.RemovePartialConnection(node2); /// node2.RemovePartialConnection(node1); /// })); /// /// public virtual bool ContainsOutgoingConnection (GraphNode node) { // Simple but slow default implementation bool contains = false; GetConnections((GraphNode neighbour, ref bool contains) => { contains |= neighbour == node; }, ref contains); return contains; } /// /// Add a portal from this node to the specified node. /// This function should add a portal to the left and right lists which is connecting the two nodes (this and other). /// /// Returns: True if the call was deemed successful. False if some unknown case was encountered and no portal could be added. /// If both calls to node1.GetPortal (node2,...) and node2.GetPortal (node1,...) return false, the funnel modifier will fall back to adding to the path /// the positions of the node. /// /// The default implementation simply returns false. /// /// This function may add more than one portal if necessary. /// /// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html /// /// Deprecated: Use GetPortal(GraphNode, out Vector3, out Vector3) instead /// /// The node which is on the other side of the portal (strictly speaking it does not actually have to be on the other side of the portal though). /// List of portal points on the left side of the funnel /// List of portal points on the right side of the funnel /// If this is true, the call was made on a node with the other node as the node before this one in the path. /// In this case you may choose to do nothing since a similar call will be made to the other node with this node referenced as other (but then with backwards = true). /// You do not have to care about switching the left and right lists, that is done for you already. [System.Obsolete("Use GetPortal(GraphNode, out Vector3, out Vector3) instead")] public bool GetPortal (GraphNode other, List left, List right, bool backwards) { if (!backwards && GetPortal(other, out var lp, out var rp)) { if (left != null) { left.Add(lp); right.Add(rp); } return true; } else { return false; } } /// /// Add a portal from this node to the specified node. /// This function returns a portal which connects this node to the given adjacenet node. /// /// Returns: True if the call was deemed successful. False if some unknown case was encountered and no portal could be added. /// If both calls to node1.GetPortal (node2,...) and node2.GetPortal (node1,...) return false, the funnel modifier will fall back to adding to the path /// the positions of the node. /// /// The default implementation simply returns false. /// /// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html /// /// The node which is on the other side of the portal. /// Output left side of the portal. /// Output right side of the portal. public virtual bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) { left = Vector3.zero; right = Vector3.zero; return false; } /// /// Open the node. /// Used internally by the A* algorithm. /// public abstract void Open(Path path, uint pathNodeIndex, uint gScore); /// /// Open the node at a specific point. /// /// Used internally by the A* algorithm. /// /// Used when a path starts inside a node, or when an off-mesh link is used to move to a point inside this node. /// public abstract void OpenAtPoint(Path path, uint pathNodeIndex, Int3 position, uint gScore); /// /// The position of the path node during the search. /// /// When an A* search on triangle nodes is carried out, each edge of the node is a separate path node variant. /// The search will additionally decide where on that edge the path node is located. /// This is encoded by the fractionAlongEdge variable. /// This function decodes the position of the path node. /// /// Note: Most node types only have a single path node variant and does not use the fractionAlongEdge field. /// In those cases this function only returns the node unchanged. /// public virtual Int3 DecodeVariantPosition(uint pathNodeIndex, uint fractionAlongEdge) => position; /// The surface area of the node in square world units public virtual float SurfaceArea() => 0; /// /// A random point on the surface of the node. /// For point nodes and other nodes which do not have a surface, this will always return the position of the node. /// public virtual Vector3 RandomPointOnSurface () { return (Vector3)position; } /// Closest point on the surface of this node to the point p public abstract Vector3 ClosestPointOnNode(Vector3 p); /// Checks if point is inside the node when seen from above public virtual bool ContainsPoint (Int3 point) { return ContainsPoint((Vector3)point); } /// Checks if point is inside the node when seen from above. public abstract bool ContainsPoint(Vector3 point); /// Checks if point is inside the node in graph space public abstract bool ContainsPointInGraphSpace(Int3 point); /// /// Hash code used for checking if the gizmos need to be updated. /// Will change when the gizmos for the node might change. /// public virtual int GetGizmoHashCode () { // Some hashing, the constants are just some arbitrary prime numbers. #flags contains the info for #Tag and #Walkable return position.GetHashCode() ^ (19 * (int)Penalty) ^ (41 * (int)(flags & ~(HierarchicalIndexMask | HierarchicalDirtyMask))); } /// Serialized the node data to a byte array public virtual void SerializeNode (GraphSerializationContext ctx) { // Write basic node data. ctx.writer.Write(Penalty); // Save all flags except the hierarchical node index and the dirty bit ctx.writer.Write(Flags & ~(HierarchicalIndexMask | HierarchicalDirtyMask)); } /// Deserializes the node data from a byte array public virtual void DeserializeNode (GraphSerializationContext ctx) { Penalty = ctx.reader.ReadUInt32(); // Load all flags except the hierarchical node index and the dirty bit (they aren't saved in newer versions and older data should just be cleared) // Note that the dirty bit needs to be preserved here because it may already be set (due to the node being created) Flags = (ctx.reader.ReadUInt32() & ~(HierarchicalIndexMask | HierarchicalDirtyMask)) | (Flags & (HierarchicalIndexMask | HierarchicalDirtyMask)); // Set the correct graph index (which might have changed, e.g if loading additively) GraphIndex = ctx.graphIndex; } /// /// Used to serialize references to other nodes e.g connections. /// Use the GraphSerializationContext.GetNodeIdentifier and /// GraphSerializationContext.GetNodeFromIdentifier methods /// for serialization and deserialization respectively. /// /// Nodes must override this method and serialize their connections. /// Graph generators do not need to call this method, it will be called automatically on all /// nodes at the correct time by the serializer. /// public virtual void SerializeReferences (GraphSerializationContext ctx) { } /// /// Used to deserialize references to other nodes e.g connections. /// Use the GraphSerializationContext.GetNodeIdentifier and /// GraphSerializationContext.GetNodeFromIdentifier methods /// for serialization and deserialization respectively. /// /// Nodes must override this method and serialize their connections. /// Graph generators do not need to call this method, it will be called automatically on all /// nodes at the correct time by the serializer. /// public virtual void DeserializeReferences (GraphSerializationContext ctx) { } } public abstract class MeshNode : GraphNode { /// /// All connections from this node. /// See: /// See: /// /// Note: If you modify this array or the contents of it you must call . /// /// May be null if the node has no connections. /// public Connection[] connections; /// Get a vertex of this node. /// vertex index. Must be between 0 and #GetVertexCount (exclusive). public abstract Int3 GetVertex(int i); /// /// Number of corner vertices that this node has. /// For example for a triangle node this will return 3. /// public abstract int GetVertexCount(); /// /// Closest point on the surface of this node when seen from above. /// This is usually very similar to but when the node is in a slope this can be significantly different. /// [Open online documentation to see images] /// When the blue point in the above image is used as an argument this method call will return the green point while the method will return the red point. /// public abstract Vector3 ClosestPointOnNodeXZ(Vector3 p); public override void ClearConnections (bool alsoReverse = true) { // Remove all connections to this node from our neighbours if (alsoReverse && connections != null) { for (int i = 0; i < connections.Length; i++) { connections[i].node.RemovePartialConnection(this); } } ArrayPool.Release(ref connections, true); AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } public override void GetConnections(GetConnectionsWithData action, ref T data, int connectionFilter = Connection.OutgoingConnection) { 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 bool ContainsOutgoingConnection (GraphNode node) { if (connections != null) 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) { AddPartialConnection(node, cost, Connection.PackShapeEdgeInfo(isOutgoing, isIncoming)); } /// /// Add a connection from this node to the specified node. /// /// If the connection already exists, the cost will simply be updated and /// no extra connection added. /// /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. /// /// Node to add a connection to /// Cost of traversing the connection. A cost of 1000 corresponds approximately to the cost of moving 1 world unit. /// Info about how the edge is which edge on the shape of this node to use or \reflink{Connection.NoSharedEdge} if no edge is used. See \reflink{Connection.PackShapeEdgeInfo(byte,byte,bool,bool,bool)}. public void AddPartialConnection (GraphNode node, uint cost, byte shapeEdgeInfo) { if (node == null) throw new System.ArgumentNullException(); // Check if we already have a connection to the node if (connections != null) { for (int i = 0; i < connections.Length; i++) { if (connections[i].node == node) { // Just update the cost for the existing connection connections[i].cost = cost; connections[i].shapeEdgeInfo = shapeEdgeInfo; return; } } } // Create new arrays which include the new connection int connLength = connections != null ? connections.Length : 0; var newconns = ArrayPool.ClaimWithExactLength(connLength+1); for (int i = 0; i < connLength; i++) { newconns[i] = connections[i]; } newconns[connLength] = new Connection(node, cost, shapeEdgeInfo); if (connections != null) { ArrayPool.Release(ref connections, true); } connections = newconns; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); } public override void RemovePartialConnection (GraphNode node) { if (connections == null) return; // Iterate through all connections and check if there are any to the node for (int i = 0; i < connections.Length; i++) { if (connections[i].node == node) { // Create new arrays which have the specified node removed int connLength = connections.Length; var newconns = ArrayPool.ClaimWithExactLength(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]; } if (connections != null) { ArrayPool.Release(ref connections, true); } connections = newconns; AstarPath.active.hierarchicalGraph.AddDirtyNode(this); return; } } } 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 SerializeReferences(GraphSerializationContext ctx) => ctx.SerializeConnections(connections, true); public override void DeserializeReferences(GraphSerializationContext ctx) => connections = ctx.DeserializeConnections(true); } }