Adding A* Algorythm and carpet roads

This commit is contained in:
Nicolas SANS
2023-04-24 15:49:03 +02:00
parent 75018ee6cd
commit 690e3fbd0b
685 changed files with 134125 additions and 19 deletions

View File

@ -0,0 +1,699 @@
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Util;
using Pathfinding.Serialization;
namespace Pathfinding {
/// <summary>
/// Exposes internal methods for graphs.
/// This is used to hide methods that should not be used by any user code
/// but still have to be 'public' or 'internal' (which is pretty much the same as 'public'
/// as this library is distributed with source code).
///
/// Hiding the internal methods cleans up the documentation and IntelliSense suggestions.
/// </summary>
public interface IGraphInternals {
string SerializedEditorSettings { get; set; }
void OnDestroy();
void DestroyAllNodes();
IEnumerable<Progress> ScanInternal();
void SerializeExtraInfo(GraphSerializationContext ctx);
void DeserializeExtraInfo(GraphSerializationContext ctx);
void PostDeserialization(GraphSerializationContext ctx);
void DeserializeSettingsCompatibility(GraphSerializationContext ctx);
}
/// <summary>Base class for all graphs</summary>
public abstract class NavGraph : IGraphInternals {
/// <summary>Reference to the AstarPath object in the scene</summary>
public AstarPath active;
/// <summary>
/// Used as an ID of the graph, considered to be unique.
/// Note: This is Pathfinding.Util.Guid not System.Guid. A replacement for System.Guid was coded for better compatibility with iOS
/// </summary>
[JsonMember]
public Guid guid;
/// <summary>Default penalty to apply to all nodes</summary>
[JsonMember]
public uint initialPenalty;
/// <summary>Is the graph open in the editor</summary>
[JsonMember]
public bool open;
/// <summary>Index of the graph, used for identification purposes</summary>
public uint graphIndex;
/// <summary>
/// Name of the graph.
/// Can be set in the unity editor
/// </summary>
[JsonMember]
public string name;
/// <summary>
/// Enable to draw gizmos in the Unity scene view.
/// In the inspector this value corresponds to the state of
/// the 'eye' icon in the top left corner of every graph inspector.
/// </summary>
[JsonMember]
public bool drawGizmos = true;
/// <summary>
/// Used in the editor to check if the info screen is open.
/// Should be inside UNITY_EDITOR only \<see cref="ifs"/> but just in case anyone tries to serialize a NavGraph instance using Unity, I have left it like this as it would otherwise cause a crash when building.
/// Version 3.0.8.1 was released because of this bug only
/// </summary>
[JsonMember]
public bool infoScreenOpen;
/// <summary>Used in the Unity editor to store serialized settings for graph inspectors</summary>
[JsonMember]
string serializedEditorSettings;
/// <summary>True if the graph exists, false if it has been destroyed</summary>
internal bool exists { get { return active != null; } }
/// <summary>
/// Number of nodes in the graph.
/// Note that this is, unless the graph type has overriden it, an O(n) operation.
///
/// This is an O(1) operation for grid graphs and point graphs.
/// For layered grid graphs it is an O(n) operation.
/// </summary>
public virtual int CountNodes () {
int count = 0;
GetNodes(node => count++);
return count;
}
/// <summary>Calls a delegate with all nodes in the graph until the delegate returns false</summary>
public void GetNodes (System.Func<GraphNode, bool> action) {
bool cont = true;
GetNodes(node => {
if (cont) cont &= action(node);
});
}
/// <summary>
/// Calls a delegate with all nodes in the graph.
/// This is the primary way of iterating through all nodes in a graph.
///
/// Do not change the graph structure inside the delegate.
///
/// <code>
/// var gg = AstarPath.active.data.gridGraph;
///
/// gg.GetNodes(node => {
/// // Here is a node
/// Debug.Log("I found a node at position " + (Vector3)node.position);
/// });
/// </code>
///
/// If you want to store all nodes in a list you can do this
///
/// <code>
/// var gg = AstarPath.active.data.gridGraph;
///
/// List<GraphNode> nodes = new List<GraphNode>();
///
/// gg.GetNodes((System.Action<GraphNode>)nodes.Add);
/// </code>
///
/// See: <see cref="Pathfinding.AstarData.GetNodes"/>
/// </summary>
public abstract void GetNodes(System.Action<GraphNode> action);
/// <summary>
/// A matrix for translating/rotating/scaling the graph.
/// Deprecated: Use the transform field (only available on some graph types) instead
/// </summary>
[System.Obsolete("Use the transform field (only available on some graph types) instead", true)]
public Matrix4x4 matrix = Matrix4x4.identity;
/// <summary>
/// Inverse of matrix.
/// Deprecated: Use the transform field (only available on some graph types) instead
/// </summary>
[System.Obsolete("Use the transform field (only available on some graph types) instead", true)]
public Matrix4x4 inverseMatrix = Matrix4x4.identity;
/// <summary>
/// Use to set both matrix and inverseMatrix at the same time.
/// Deprecated: Use the transform field (only available on some graph types) instead
/// </summary>
[System.Obsolete("Use the transform field (only available on some graph types) instead", true)]
public void SetMatrix (Matrix4x4 m) {
matrix = m;
inverseMatrix = m.inverse;
}
/// <summary>
/// Moves nodes in this graph.
/// Deprecated: Use RelocateNodes(Matrix4x4) instead.
/// To keep the same behavior you can call RelocateNodes(newMatrix * oldMatrix.inverse).
/// </summary>
[System.Obsolete("Use RelocateNodes(Matrix4x4) instead. To keep the same behavior you can call RelocateNodes(newMatrix * oldMatrix.inverse).")]
public void RelocateNodes (Matrix4x4 oldMatrix, Matrix4x4 newMatrix) {
RelocateNodes(newMatrix * oldMatrix.inverse);
}
/// <summary>
/// Moves the nodes in this graph.
/// Multiplies all node positions by deltaMatrix.
///
/// For example if you want to move all your nodes in e.g a point graph 10 units along the X axis from the initial position
/// <code>
/// var graph = AstarPath.data.pointGraph;
/// var m = Matrix4x4.TRS (new Vector3(10,0,0), Quaternion.identity, Vector3.one);
/// graph.RelocateNodes (m);
/// </code>
///
/// Note: For grid graphs, navmesh graphs and recast graphs it is recommended to
/// use their custom overloads of the RelocateNodes method which take parameters
/// for e.g center and nodeSize (and additional parameters) instead since
/// they are both easier to use and are less likely to mess up pathfinding.
///
/// Warning: This method is lossy for PointGraphs, so calling it many times may
/// cause node positions to lose precision. For example if you set the scale
/// to 0 in one call then all nodes will be scaled/moved to the same point and
/// you will not be able to recover their original positions. The same thing
/// happens for other - less extreme - values as well, but to a lesser degree.
/// </summary>
public virtual void RelocateNodes (Matrix4x4 deltaMatrix) {
GetNodes(node => node.position = ((Int3)deltaMatrix.MultiplyPoint((Vector3)node.position)));
}
/// <summary>
/// Returns the nearest node to a position.
/// See: Pathfinding.NNConstraint.None
/// </summary>
/// <param name="position">The position to try to find a close node to</param>
public NNInfoInternal GetNearest (Vector3 position) {
return GetNearest(position, NNConstraint.None);
}
/// <summary>Returns the nearest node to a position using the specified NNConstraint.</summary>
/// <param name="position">The position to try to find a close node to</param>
/// <param name="constraint">Can for example tell the function to try to return a walkable node. If you do not get a good node back, consider calling GetNearestForce.</param>
public NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint) {
return GetNearest(position, constraint, null);
}
/// <summary>Returns the nearest node to a position using the specified NNConstraint.</summary>
/// <param name="position">The position to try to find a close node to</param>
/// <param name="hint">Can be passed to enable some graph generators to find the nearest node faster.</param>
/// <param name="constraint">Can for example tell the function to try to return a walkable node. If you do not get a good node back, consider calling GetNearestForce.</param>
public virtual NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint, GraphNode hint) {
// This is a default implementation and it is pretty slow
// Graphs usually override this to provide faster and more specialised implementations
float maxDistSqr = constraint == null || constraint.constrainDistance ? AstarPath.active.maxNearestNodeDistanceSqr : float.PositiveInfinity;
float minDist = float.PositiveInfinity;
GraphNode minNode = null;
float minConstDist = float.PositiveInfinity;
GraphNode minConstNode = null;
// Loop through all nodes and find the closest suitable node
GetNodes(node => {
float dist = (position-(Vector3)node.position).sqrMagnitude;
if (dist < minDist) {
minDist = dist;
minNode = node;
}
if (dist < minConstDist && dist < maxDistSqr && (constraint == null || constraint.Suitable(node))) {
minConstDist = dist;
minConstNode = node;
}
});
var nnInfo = new NNInfoInternal(minNode);
nnInfo.constrainedNode = minConstNode;
if (minConstNode != null) {
nnInfo.constClampedPosition = (Vector3)minConstNode.position;
} else if (minNode != null) {
nnInfo.constrainedNode = minNode;
nnInfo.constClampedPosition = (Vector3)minNode.position;
}
return nnInfo;
}
/// <summary>
/// Returns the nearest node to a position using the specified \link Pathfinding.NNConstraint constraint \endlink.
/// Returns: an NNInfo. This method will only return an empty NNInfo if there are no nodes which comply with the specified constraint.
/// </summary>
public virtual NNInfoInternal GetNearestForce (Vector3 position, NNConstraint constraint) {
return GetNearest(position, constraint);
}
/// <summary>
/// Function for cleaning up references.
/// This will be called on the same time as OnDisable on the gameObject which the AstarPath script is attached to (remember, not in the editor).
/// Use for any cleanup code such as cleaning up static variables which otherwise might prevent resources from being collected.
/// Use by creating a function overriding this one in a graph class, but always call base.OnDestroy () in that function.
/// All nodes should be destroyed in this function otherwise a memory leak will arise.
/// </summary>
protected virtual void OnDestroy () {
DestroyAllNodes();
}
/// <summary>
/// Destroys all nodes in the graph.
/// Warning: This is an internal method. Unless you have a very good reason, you should probably not call it.
/// </summary>
protected virtual void DestroyAllNodes () {
GetNodes(node => node.Destroy());
}
/// <summary>
/// Scan the graph.
/// Deprecated: Use AstarPath.Scan() instead
/// </summary>
[System.Obsolete("Use AstarPath.Scan instead")]
public void ScanGraph () {
Scan();
}
/// <summary>
/// Scan the graph.
///
/// Consider using AstarPath.Scan() instead since this function only scans this graph and if you are using multiple graphs
/// with connections between them, then it is better to scan all graphs at once.
/// </summary>
public void Scan () {
active.Scan(this);
}
/// <summary>
/// Internal method to scan the graph.
/// Called from AstarPath.ScanAsync.
/// Override this function to implement custom scanning logic.
/// Progress objects can be yielded to show progress info in the editor and to split up processing
/// over several frames when using async scanning.
/// </summary>
protected abstract IEnumerable<Progress> ScanInternal();
/// <summary>
/// Serializes graph type specific node data.
/// This function can be overriden to serialize extra node information (or graph information for that matter)
/// which cannot be serialized using the standard serialization.
/// Serialize the data in any way you want and return a byte array.
/// When loading, the exact same byte array will be passed to the DeserializeExtraInfo function.\n
/// These functions will only be called if node serialization is enabled.\n
/// </summary>
protected virtual void SerializeExtraInfo (GraphSerializationContext ctx) {
}
/// <summary>
/// Deserializes graph type specific node data.
/// See: SerializeExtraInfo
/// </summary>
protected virtual void DeserializeExtraInfo (GraphSerializationContext ctx) {
}
/// <summary>
/// Called after all deserialization has been done for all graphs.
/// Can be used to set up more graph data which is not serialized
/// </summary>
protected virtual void PostDeserialization (GraphSerializationContext ctx) {
}
/// <summary>
/// An old format for serializing settings.
/// Deprecated: This is deprecated now, but the deserialization code is kept to
/// avoid loosing data when upgrading from older versions.
/// </summary>
protected virtual void DeserializeSettingsCompatibility (GraphSerializationContext ctx) {
guid = new Guid(ctx.reader.ReadBytes(16));
initialPenalty = ctx.reader.ReadUInt32();
open = ctx.reader.ReadBoolean();
name = ctx.reader.ReadString();
drawGizmos = ctx.reader.ReadBoolean();
infoScreenOpen = ctx.reader.ReadBoolean();
}
/// <summary>Draw gizmos for the graph</summary>
public virtual void OnDrawGizmos (RetainedGizmos gizmos, bool drawNodes) {
if (!drawNodes) {
return;
}
// This is a relatively slow default implementation.
// subclasses of the base graph class may override
// this method to draw gizmos in a more optimized way
var hasher = new RetainedGizmos.Hasher(active);
GetNodes(node => hasher.HashNode(node));
// Update the gizmo mesh if necessary
if (!gizmos.Draw(hasher)) {
using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
GetNodes((System.Action<GraphNode>)helper.DrawConnections);
}
}
if (active.showUnwalkableNodes) DrawUnwalkableNodes(active.unwalkableNodeDebugSize);
}
protected void DrawUnwalkableNodes (float size) {
Gizmos.color = AstarColor.UnwalkableNode;
GetNodes(node => {
if (!node.Walkable) Gizmos.DrawCube((Vector3)node.position, Vector3.one*size);
});
}
#region IGraphInternals implementation
string IGraphInternals.SerializedEditorSettings { get { return serializedEditorSettings; } set { serializedEditorSettings = value; } }
void IGraphInternals.OnDestroy () { OnDestroy(); }
void IGraphInternals.DestroyAllNodes () { DestroyAllNodes(); }
IEnumerable<Progress> IGraphInternals.ScanInternal () { return ScanInternal(); }
void IGraphInternals.SerializeExtraInfo (GraphSerializationContext ctx) { SerializeExtraInfo(ctx); }
void IGraphInternals.DeserializeExtraInfo (GraphSerializationContext ctx) { DeserializeExtraInfo(ctx); }
void IGraphInternals.PostDeserialization (GraphSerializationContext ctx) { PostDeserialization(ctx); }
void IGraphInternals.DeserializeSettingsCompatibility (GraphSerializationContext ctx) { DeserializeSettingsCompatibility(ctx); }
#endregion
}
/// <summary>
/// Handles collision checking for graphs.
/// Mostly used by grid based graphs
/// </summary>
[System.Serializable]
public class GraphCollision {
/// <summary>
/// Collision shape to use.
/// See: <see cref="Pathfinding.ColliderType"/>
/// </summary>
public ColliderType type = ColliderType.Capsule;
/// <summary>
/// Diameter of capsule or sphere when checking for collision.
/// When checking for collisions the system will check if any colliders
/// overlap a specific shape at the node's position. The shape is determined
/// by the <see cref="type"/> field.
///
/// A diameter of 1 means that the shape has a diameter equal to the node's width,
/// or in other words it is equal to \link Pathfinding.GridGraph.nodeSize nodeSize \endlink.
///
/// If <see cref="type"/> is set to Ray, this does not affect anything.
///
/// [Open online documentation to see images]
/// </summary>
public float diameter = 1F;
/// <summary>
/// Height of capsule or length of ray when checking for collision.
/// If <see cref="type"/> is set to Sphere, this does not affect anything.
///
/// [Open online documentation to see images]
///
/// Warning: In contrast to Unity's capsule collider and character controller this height does not include the end spheres of the capsule, but only the cylinder part.
/// This is mostly for historical reasons.
/// </summary>
public float height = 2F;
/// <summary>
/// Height above the ground that collision checks should be done.
/// For example, if the ground was found at y=0, collisionOffset = 2
/// type = Capsule and height = 3 then the physics system
/// will be queried to see if there are any colliders in a capsule
/// for which the bottom sphere that is made up of is centered at y=2
/// and the top sphere has its center at y=2+3=5.
///
/// If type = Sphere then the sphere's center would be at y=2 in this case.
/// </summary>
public float collisionOffset;
/// <summary>
/// Direction of the ray when checking for collision.
/// If <see cref="type"/> is not Ray, this does not affect anything
/// </summary>
public RayDirection rayDirection = RayDirection.Both;
/// <summary>Layers to be treated as obstacles.</summary>
public LayerMask mask;
/// <summary>Layers to be included in the height check.</summary>
public LayerMask heightMask = -1;
/// <summary>
/// The height to check from when checking height ('ray length' in the inspector).
///
/// As the image below visualizes, different ray lengths can make the ray hit different things.
/// The distance is measured up from the graph plane.
///
/// [Open online documentation to see images]
/// </summary>
public float fromHeight = 100;
/// <summary>
/// Toggles thick raycast.
/// See: https://docs.unity3d.com/ScriptReference/Physics.SphereCast.html
/// </summary>
public bool thickRaycast;
/// <summary>
/// Diameter of the thick raycast in nodes.
/// 1 equals \link Pathfinding.GridGraph.nodeSize nodeSize \endlink
/// </summary>
public float thickRaycastDiameter = 1;
/// <summary>Make nodes unwalkable when no ground was found with the height raycast. If height raycast is turned off, this doesn't affect anything.</summary>
public bool unwalkableWhenNoGround = true;
/// <summary>
/// Use Unity 2D Physics API.
/// See: http://docs.unity3d.com/ScriptReference/Physics2D.html
/// </summary>
public bool use2D;
/// <summary>Toggle collision check</summary>
public bool collisionCheck = true;
/// <summary>Toggle height check. If false, the grid will be flat</summary>
public bool heightCheck = true;
/// <summary>
/// Direction to use as UP.
/// See: Initialize
/// </summary>
public Vector3 up;
/// <summary>
/// <see cref="up"/> * <see cref="height"/>.
/// See: Initialize
/// </summary>
private Vector3 upheight;
/// <summary>Used for 2D collision queries</summary>
private ContactFilter2D contactFilter;
/// <summary>
/// Just so that the Physics2D.OverlapPoint method has some buffer to store things in.
/// We never actually read from this array, so we don't even care if this is thread safe.
/// </summary>
private static Collider2D[] dummyArray = new Collider2D[1];
/// <summary>
/// <see cref="diameter"/> * scale * 0.5.
/// Where scale usually is \link Pathfinding.GridGraph.nodeSize nodeSize \endlink
/// See: Initialize
/// </summary>
private float finalRadius;
/// <summary>
/// <see cref="thickRaycastDiameter"/> * scale * 0.5.
/// Where scale usually is \link Pathfinding.GridGraph.nodeSize nodeSize \endlink See: Initialize
/// </summary>
private float finalRaycastRadius;
/// <summary>Offset to apply after each raycast to make sure we don't hit the same point again in CheckHeightAll</summary>
public const float RaycastErrorMargin = 0.005F;
/// <summary>
/// Sets up several variables using the specified matrix and scale.
/// See: GraphCollision.up
/// See: GraphCollision.upheight
/// See: GraphCollision.finalRadius
/// See: GraphCollision.finalRaycastRadius
/// </summary>
public void Initialize (GraphTransform transform, float scale) {
up = (transform.Transform(Vector3.up) - transform.Transform(Vector3.zero)).normalized;
upheight = up*height;
finalRadius = diameter*scale*0.5F;
finalRaycastRadius = thickRaycastDiameter*scale*0.5F;
contactFilter = new ContactFilter2D { layerMask = mask, useDepth = false, useLayerMask = true, useNormalAngle = false, useTriggers = false };
}
/// <summary>
/// Returns true if the position is not obstructed.
/// If <see cref="collisionCheck"/> is false, this will always return true.\n
/// </summary>
public bool Check (Vector3 position) {
if (!collisionCheck) {
return true;
}
if (use2D) {
switch (type) {
case ColliderType.Capsule:
case ColliderType.Sphere:
return Physics2D.OverlapCircle(position, finalRadius, contactFilter, dummyArray) == 0;
default:
return Physics2D.OverlapPoint(position, contactFilter, dummyArray) == 0;
}
}
position += up*collisionOffset;
switch (type) {
case ColliderType.Capsule:
return !Physics.CheckCapsule(position, position+upheight, finalRadius, mask, QueryTriggerInteraction.Ignore);
case ColliderType.Sphere:
return !Physics.CheckSphere(position, finalRadius, mask, QueryTriggerInteraction.Ignore);
default:
switch (rayDirection) {
case RayDirection.Both:
return !Physics.Raycast(position, up, height, mask, QueryTriggerInteraction.Ignore) && !Physics.Raycast(position+upheight, -up, height, mask, QueryTriggerInteraction.Ignore);
case RayDirection.Up:
return !Physics.Raycast(position, up, height, mask, QueryTriggerInteraction.Ignore);
default:
return !Physics.Raycast(position+upheight, -up, height, mask, QueryTriggerInteraction.Ignore);
}
}
}
/// <summary>
/// Returns the position with the correct height.
/// If <see cref="heightCheck"/> is false, this will return position.
/// </summary>
public Vector3 CheckHeight (Vector3 position) {
RaycastHit hit;
bool walkable;
return CheckHeight(position, out hit, out walkable);
}
/// <summary>
/// Returns the position with the correct height.
/// If <see cref="heightCheck"/> is false, this will return position.\n
/// walkable will be set to false if nothing was hit.
/// The ray will check a tiny bit further than to the grids base to avoid floating point errors when the ground is exactly at the base of the grid
/// </summary>
public Vector3 CheckHeight (Vector3 position, out RaycastHit hit, out bool walkable) {
walkable = true;
if (!heightCheck || use2D) {
hit = new RaycastHit();
return position;
}
if (thickRaycast) {
var ray = new Ray(position+up*fromHeight, -up);
if (Physics.SphereCast(ray, finalRaycastRadius, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return VectorMath.ClosestPointOnLine(ray.origin, ray.origin+ray.direction, hit.point);
}
walkable &= !unwalkableWhenNoGround;
} else {
// Cast a ray from above downwards to try to find the ground
if (Physics.Raycast(position+up*fromHeight, -up, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return hit.point;
}
walkable &= !unwalkableWhenNoGround;
}
return position;
}
/// <summary>Internal buffer used by <see cref="CheckHeightAll"/></summary>
RaycastHit[] hitBuffer = new RaycastHit[8];
/// <summary>
/// Returns all hits when checking height for position.
/// Warning: Does not work well with thick raycast, will only return an object a single time
///
/// Warning: The returned array is ephermal. It will be invalidated when this method is called again.
/// If you need persistent results you should copy it.
///
/// The returned array may be larger than the actual number of hits, the numHits out parameter indicates how many hits there actually were.
/// </summary>
public RaycastHit[] CheckHeightAll (Vector3 position, out int numHits) {
if (!heightCheck || use2D) {
hitBuffer[0] = new RaycastHit {
point = position,
distance = 0,
};
numHits = 1;
return hitBuffer;
}
// Cast a ray from above downwards to try to find the ground
#if UNITY_2017_1_OR_NEWER
numHits = Physics.RaycastNonAlloc(position+up*fromHeight, -up, hitBuffer, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
if (numHits == hitBuffer.Length) {
// Try again with a larger buffer
hitBuffer = new RaycastHit[hitBuffer.Length*2];
return CheckHeightAll(position, out numHits);
}
return hitBuffer;
#else
var result = Physics.RaycastAll(position+up*fromHeight, -up, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
numHits = result.Length;
return result;
#endif
}
public void DeserializeSettingsCompatibility (GraphSerializationContext ctx) {
type = (ColliderType)ctx.reader.ReadInt32();
diameter = ctx.reader.ReadSingle();
height = ctx.reader.ReadSingle();
collisionOffset = ctx.reader.ReadSingle();
rayDirection = (RayDirection)ctx.reader.ReadInt32();
mask = (LayerMask)ctx.reader.ReadInt32();
heightMask = (LayerMask)ctx.reader.ReadInt32();
fromHeight = ctx.reader.ReadSingle();
thickRaycast = ctx.reader.ReadBoolean();
thickRaycastDiameter = ctx.reader.ReadSingle();
unwalkableWhenNoGround = ctx.reader.ReadBoolean();
use2D = ctx.reader.ReadBoolean();
collisionCheck = ctx.reader.ReadBoolean();
heightCheck = ctx.reader.ReadBoolean();
}
}
/// <summary>
/// Determines collision check shape.
/// See: <see cref="Pathfinding.GraphCollision"/>
/// </summary>
public enum ColliderType {
/// <summary>Uses a Sphere, Physics.CheckSphere. In 2D this is a circle instead.</summary>
Sphere,
/// <summary>Uses a Capsule, Physics.CheckCapsule. This will behave identically to the Sphere mode in 2D.</summary>
Capsule,
/// <summary>Uses a Ray, Physics.Linecast. In 2D this is a single point instead.</summary>
Ray
}
/// <summary>Determines collision check ray direction</summary>
public enum RayDirection {
Up, /// <summary>< Casts the ray from the bottom upwards</summary>
Down, /// <summary>< Casts the ray from the top downwards</summary>
Both /// <summary>< Casts two rays in both directions</summary>
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c7636164485c04efe8fad73ab1ee985f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d5d28978e568e40429b2981fab3e380e
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,272 @@
using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding {
using Pathfinding.Util;
using Pathfinding.Serialization;
public interface INavmesh {
void GetNodes(System.Action<GraphNode> del);
}
/// <summary>
/// Generates graphs based on navmeshes.
/// \ingroup graphs
/// Navmeshes are meshes where each triangle defines a walkable area.
/// These are great because the AI can get so much more information on how it can walk.
/// Polygons instead of points mean that the funnel smoother can produce really nice looking paths and the graphs are also really fast to search
/// and have a low memory footprint because fewer nodes are usually needed to describe the same area compared to grid graphs.
///
/// See: Pathfinding.RecastGraph
///
/// [Open online documentation to see images]
/// [Open online documentation to see images]
/// </summary>
[JsonOptIn]
[Pathfinding.Util.Preserve]
public class NavMeshGraph : NavmeshBase, IUpdatableGraph {
/// <summary>Mesh to construct navmesh from</summary>
[JsonMember]
public Mesh sourceMesh;
/// <summary>Offset in world space</summary>
[JsonMember]
public Vector3 offset;
/// <summary>Rotation in degrees</summary>
[JsonMember]
public Vector3 rotation;
/// <summary>Scale of the graph</summary>
[JsonMember]
public float scale = 1;
/// <summary>
/// Determines how normals are calculated.
/// Disable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.
///
/// By default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction.
/// The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way.
/// This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh.
/// If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.
///
/// If you for example take a look at the image below. In the upper case then the nodes on the bottom half of the
/// mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been
/// modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on
/// the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option
/// is set to false instead the nodes are connected properly as in the original mesh all normals point outwards.
/// [Open online documentation to see images]
///
/// The default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that
/// has all normals pointing downwards and this option is false, then some methods like <see cref="PointOnNavmesh"/> will not work correctly
/// as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway
/// as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction.
/// </summary>
[JsonMember]
public bool recalculateNormals = true;
/// <summary>
/// Cached bounding box minimum of <see cref="sourceMesh"/>.
/// This is important when the graph has been saved to a file and is later loaded again, but the original mesh does not exist anymore (or has been moved).
/// In that case we still need to be able to find the bounding box since the <see cref="CalculateTransform"/> method uses it.
/// </summary>
[JsonMember]
Vector3 cachedSourceMeshBoundsMin;
protected override bool RecalculateNormals { get { return recalculateNormals; } }
public override float TileWorldSizeX {
get {
return forcedBoundsSize.x;
}
}
public override float TileWorldSizeZ {
get {
return forcedBoundsSize.z;
}
}
protected override float MaxTileConnectionEdgeDistance {
get {
// Tiles are not supported, so this is irrelevant
return 0f;
}
}
public override GraphTransform CalculateTransform () {
return new GraphTransform(Matrix4x4.TRS(offset, Quaternion.Euler(rotation), Vector3.one) * Matrix4x4.TRS(sourceMesh != null ? sourceMesh.bounds.min * scale : cachedSourceMeshBoundsMin * scale, Quaternion.identity, Vector3.one));
}
GraphUpdateThreading IUpdatableGraph.CanUpdateAsync (GraphUpdateObject o) {
return GraphUpdateThreading.UnityThread;
}
void IUpdatableGraph.UpdateAreaInit (GraphUpdateObject o) {}
void IUpdatableGraph.UpdateAreaPost (GraphUpdateObject o) {}
void IUpdatableGraph.UpdateArea (GraphUpdateObject o) {
UpdateArea(o, this);
}
public static void UpdateArea (GraphUpdateObject o, INavmeshHolder graph) {
Bounds bounds = graph.transform.InverseTransform(o.bounds);
// Bounding rectangle with integer coordinates
var irect = new IntRect(
Mathf.FloorToInt(bounds.min.x*Int3.Precision),
Mathf.FloorToInt(bounds.min.z*Int3.Precision),
Mathf.CeilToInt(bounds.max.x*Int3.Precision),
Mathf.CeilToInt(bounds.max.z*Int3.Precision)
);
// Corners of the bounding rectangle
var a = new Int3(irect.xmin, 0, irect.ymin);
var b = new Int3(irect.xmin, 0, irect.ymax);
var c = new Int3(irect.xmax, 0, irect.ymin);
var d = new Int3(irect.xmax, 0, irect.ymax);
var ymin = ((Int3)bounds.min).y;
var ymax = ((Int3)bounds.max).y;
// Loop through all nodes and check if they intersect the bounding box
graph.GetNodes(_node => {
var node = _node as TriangleMeshNode;
bool inside = false;
int allLeft = 0;
int allRight = 0;
int allTop = 0;
int allBottom = 0;
// Check bounding box rect in XZ plane
for (int v = 0; v < 3; v++) {
Int3 p = node.GetVertexInGraphSpace(v);
if (irect.Contains(p.x, p.z)) {
inside = true;
break;
}
if (p.x < irect.xmin) allLeft++;
if (p.x > irect.xmax) allRight++;
if (p.z < irect.ymin) allTop++;
if (p.z > irect.ymax) allBottom++;
}
if (!inside && (allLeft == 3 || allRight == 3 || allTop == 3 || allBottom == 3)) {
return;
}
// Check if the polygon edges intersect the bounding rect
for (int v = 0; v < 3; v++) {
int v2 = v > 1 ? 0 : v+1;
Int3 vert1 = node.GetVertexInGraphSpace(v);
Int3 vert2 = node.GetVertexInGraphSpace(v2);
if (VectorMath.SegmentsIntersectXZ(a, b, vert1, vert2)) { inside = true; break; }
if (VectorMath.SegmentsIntersectXZ(a, c, vert1, vert2)) { inside = true; break; }
if (VectorMath.SegmentsIntersectXZ(c, d, vert1, vert2)) { inside = true; break; }
if (VectorMath.SegmentsIntersectXZ(d, b, vert1, vert2)) { inside = true; break; }
}
// Check if the node contains any corner of the bounding rect
if (inside || node.ContainsPointInGraphSpace(a) || node.ContainsPointInGraphSpace(b) || node.ContainsPointInGraphSpace(c) || node.ContainsPointInGraphSpace(d)) {
inside = true;
}
if (!inside) {
return;
}
int allAbove = 0;
int allBelow = 0;
// Check y coordinate
for (int v = 0; v < 3; v++) {
Int3 p = node.GetVertexInGraphSpace(v);
if (p.y < ymin) allBelow++;
if (p.y > ymax) allAbove++;
}
// Polygon is either completely above the bounding box or completely below it
if (allBelow == 3 || allAbove == 3) return;
// Triangle is inside the bounding box!
// Update it!
o.WillUpdateNode(node);
o.Apply(node);
});
}
/// <summary>Scans the graph using the path to an .obj mesh</summary>
[System.Obsolete("Set the mesh to ObjImporter.ImportFile(...) and scan the graph the normal way instead")]
public void ScanInternal (string objMeshPath) {
Mesh mesh = ObjImporter.ImportFile(objMeshPath);
if (mesh == null) {
Debug.LogError("Couldn't read .obj file at '"+objMeshPath+"'");
return;
}
sourceMesh = mesh;
var scan = ScanInternal().GetEnumerator();
while (scan.MoveNext()) {}
}
protected override IEnumerable<Progress> ScanInternal () {
cachedSourceMeshBoundsMin = sourceMesh != null ? sourceMesh.bounds.min : Vector3.zero;
transform = CalculateTransform();
tileZCount = tileXCount = 1;
tiles = new NavmeshTile[tileZCount*tileXCount];
TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this);
if (sourceMesh == null) {
FillWithEmptyTiles();
yield break;
}
yield return new Progress(0.0f, "Transforming Vertices");
forcedBoundsSize = sourceMesh.bounds.size * scale;
Vector3[] vectorVertices = sourceMesh.vertices;
var intVertices = ListPool<Int3>.Claim(vectorVertices.Length);
var matrix = Matrix4x4.TRS(-sourceMesh.bounds.min * scale, Quaternion.identity, Vector3.one * scale);
// Convert the vertices to integer coordinates and also position them in graph space
// so that the minimum of the bounding box of the mesh is at the origin
// (the vertices will later be transformed to world space)
for (int i = 0; i < vectorVertices.Length; i++) {
intVertices.Add((Int3)matrix.MultiplyPoint3x4(vectorVertices[i]));
}
yield return new Progress(0.1f, "Compressing Vertices");
// Remove duplicate vertices
Int3[] compressedVertices = null;
int[] compressedTriangles = null;
Polygon.CompressMesh(intVertices, new List<int>(sourceMesh.triangles), out compressedVertices, out compressedTriangles);
ListPool<Int3>.Release(ref intVertices);
yield return new Progress(0.2f, "Building Nodes");
ReplaceTile(0, 0, compressedVertices, compressedTriangles);
// Signal that tiles have been recalculated to the navmesh cutting system.
navmeshUpdateData.OnRecalculatedTiles(tiles);
if (OnRecalculatedTiles != null) OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]);
}
protected override void DeserializeSettingsCompatibility (GraphSerializationContext ctx) {
base.DeserializeSettingsCompatibility(ctx);
sourceMesh = ctx.DeserializeUnityObject() as Mesh;
offset = ctx.DeserializeVector3();
rotation = ctx.DeserializeVector3();
scale = ctx.reader.ReadSingle();
nearestSearchOnlyXZ = !ctx.reader.ReadBoolean();
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ffae751ca240c466185a168f4a9836cb
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f8b76f19632144117a3ac7f28faf2c15
timeCreated: 1474405146
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e8a4fbed4121f4ae5a44468fdf1bce0e

View File

@ -0,0 +1,361 @@
using System.Collections.Generic;
using Pathfinding.Serialization;
using UnityEngine;
namespace Pathfinding {
/// <summary>Node used for the GridGraph</summary>
public class GridNode : GridNodeBase {
public GridNode (AstarPath astar) : base(astar) {
}
#if !ASTAR_NO_GRID_GRAPH
private static GridGraph[] _gridGraphs = new GridGraph[0];
public static GridGraph GetGridGraph (uint graphIndex) { return _gridGraphs[(int)graphIndex]; }
public static void SetGridGraph (int graphIndex, GridGraph graph) {
if (_gridGraphs.Length <= graphIndex) {
var gg = new GridGraph[graphIndex+1];
for (int i = 0; i < _gridGraphs.Length; i++) gg[i] = _gridGraphs[i];
_gridGraphs = gg;
}
_gridGraphs[graphIndex] = graph;
}
public static void ClearGridGraph (int graphIndex, GridGraph graph) {
if (graphIndex < _gridGraphs.Length && _gridGraphs[graphIndex] == graph) {
_gridGraphs[graphIndex] = null;
}
}
/// <summary>Internal use only</summary>
internal ushort InternalGridFlags {
get { return gridFlags; }
set { gridFlags = value; }
}
const int GridFlagsConnectionOffset = 0;
const int GridFlagsConnectionBit0 = 1 << GridFlagsConnectionOffset;
const int GridFlagsConnectionMask = 0xFF << GridFlagsConnectionOffset;
const int GridFlagsEdgeNodeOffset = 10;
const int GridFlagsEdgeNodeMask = 1 << GridFlagsEdgeNodeOffset;
public override bool HasConnectionsToAllEightNeighbours {
get {
return (InternalGridFlags & GridFlagsConnectionMask) == GridFlagsConnectionMask;
}
}
/// <summary>
/// True if the node has a connection in the specified direction.
/// The dir parameter corresponds to directions in the grid as:
/// <code>
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
/// </code>
///
/// See: SetConnectionInternal
/// </summary>
public bool HasConnectionInDirection (int dir) {
return (gridFlags >> dir & GridFlagsConnectionBit0) != 0;
}
/// <summary>
/// True if the node has a connection in the specified direction.
/// Deprecated: Use HasConnectionInDirection
/// </summary>
[System.Obsolete("Use HasConnectionInDirection")]
public bool GetConnectionInternal (int dir) {
return HasConnectionInDirection(dir);
}
/// <summary>
/// Enables or disables a connection in a specified direction on the graph.
/// See: HasConnectionInDirection
/// </summary>
public void SetConnectionInternal (int dir, bool value) {
// Set bit number #dir to 1 or 0 depending on #value
unchecked { gridFlags = (ushort)(gridFlags & ~((ushort)1 << GridFlagsConnectionOffset << dir) | (value ? (ushort)1 : (ushort)0) << GridFlagsConnectionOffset << dir); }
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Sets the state of all grid connections.
///
/// See: SetConnectionInternal
/// </summary>
/// <param name="connections">a bitmask of the connections (bit 0 is the first connection, bit 1 the second connection, etc.).</param>
public void SetAllConnectionInternal (int connections) {
unchecked { gridFlags = (ushort)((gridFlags & ~GridFlagsConnectionMask) | (connections << GridFlagsConnectionOffset)); }
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Disables all grid connections from this node.
/// Note: Other nodes might still be able to get to this node.
/// Therefore it is recommended to also disable the relevant connections on adjacent nodes.
/// </summary>
public void ResetConnectionsInternal () {
unchecked {
gridFlags = (ushort)(gridFlags & ~GridFlagsConnectionMask);
}
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Work in progress for a feature that required info about which nodes were at the border of the graph.
/// Note: This property is not functional at the moment.
/// </summary>
public bool EdgeNode {
get {
return (gridFlags & GridFlagsEdgeNodeMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsEdgeNodeMask | (value ? GridFlagsEdgeNodeMask : 0)); }
}
}
public override GridNodeBase GetNeighbourAlongDirection (int direction) {
if (HasConnectionInDirection(direction)) {
GridGraph gg = GetGridGraph(GraphIndex);
return gg.nodes[NodeInGridIndex+gg.neighbourOffsets[direction]];
}
return null;
}
public override void ClearConnections (bool alsoReverse) {
if (alsoReverse) {
// Note: This assumes that all connections are bidirectional
// which should hold for all grid graphs unless some custom code has been added
for (int i = 0; i < 8; i++) {
var other = GetNeighbourAlongDirection(i) as GridNode;
if (other != null) {
// Remove reverse connection. See doc for GridGraph.neighbourOffsets to see which indices are used for what.
other.SetConnectionInternal(i < 4 ? ((i + 2) % 4) : (((i-2) % 4) + 4), false);
}
}
}
ResetConnectionsInternal();
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.ClearConnections(alsoReverse);
#endif
}
public override void GetConnections (System.Action<GraphNode> action) {
GridGraph gg = GetGridGraph(GraphIndex);
int[] neighbourOffsets = gg.neighbourOffsets;
GridNode[] nodes = gg.nodes;
for (int i = 0; i < 8; i++) {
if (HasConnectionInDirection(i)) {
GridNode other = nodes[NodeInGridIndex + neighbourOffsets[i]];
if (other != null) action(other);
}
}
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.GetConnections(action);
#endif
}
public override Vector3 ClosestPointOnNode (Vector3 p) {
var gg = GetGridGraph(GraphIndex);
// Convert to graph space
p = gg.transform.InverseTransform(p);
// Calculate graph position of this node
int x = NodeInGridIndex % gg.width;
int z = NodeInGridIndex / gg.width;
// Handle the y coordinate separately
float y = gg.transform.InverseTransform((Vector3)position).y;
var closestInGraphSpace = new Vector3(Mathf.Clamp(p.x, x, x+1f), y, Mathf.Clamp(p.z, z, z+1f));
// Convert to world space
return gg.transform.Transform(closestInGraphSpace);
}
public override bool GetPortal (GraphNode other, List<Vector3> left, List<Vector3> right, bool backwards) {
if (backwards) return true;
GridGraph gg = GetGridGraph(GraphIndex);
int[] neighbourOffsets = gg.neighbourOffsets;
GridNode[] nodes = gg.nodes;
for (int i = 0; i < 4; i++) {
if (HasConnectionInDirection(i) && other == nodes[NodeInGridIndex + neighbourOffsets[i]]) {
Vector3 middle = ((Vector3)(position + other.position))*0.5f;
Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position));
cross.Normalize();
cross *= gg.nodeSize*0.5f;
left.Add(middle - cross);
right.Add(middle + cross);
return true;
}
}
for (int i = 4; i < 8; i++) {
if (HasConnectionInDirection(i) && other == nodes[NodeInGridIndex + neighbourOffsets[i]]) {
bool rClear = false;
bool lClear = false;
if (HasConnectionInDirection(i-4)) {
GridNode n2 = nodes[NodeInGridIndex + neighbourOffsets[i-4]];
if (n2.Walkable && n2.HasConnectionInDirection((i-4+1)%4)) {
rClear = true;
}
}
if (HasConnectionInDirection((i-4+1)%4)) {
GridNode n2 = nodes[NodeInGridIndex + neighbourOffsets[(i-4+1)%4]];
if (n2.Walkable && n2.HasConnectionInDirection(i-4)) {
lClear = true;
}
}
Vector3 middle = ((Vector3)(position + other.position))*0.5f;
Vector3 cross = Vector3.Cross(gg.collision.up, (Vector3)(other.position-position));
cross.Normalize();
cross *= gg.nodeSize*1.4142f;
left.Add(middle - (lClear ? cross : Vector3.zero));
right.Add(middle + (rClear ? cross : Vector3.zero));
return true;
}
}
return false;
}
public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) {
GridGraph gg = GetGridGraph(GraphIndex);
int[] neighbourOffsets = gg.neighbourOffsets;
GridNode[] nodes = gg.nodes;
pathNode.UpdateG(path);
handler.heap.Add(pathNode);
ushort pid = handler.PathID;
var index = NodeInGridIndex;
for (int i = 0; i < 8; i++) {
if (HasConnectionInDirection(i)) {
GridNode other = nodes[index + neighbourOffsets[i]];
PathNode otherPN = handler.GetPathNode(other);
if (otherPN.parent == pathNode && otherPN.pathID == pid) other.UpdateRecursiveG(path, otherPN, handler);
}
}
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.UpdateRecursiveG(path, pathNode, handler);
#endif
}
public override void Open (Path path, PathNode pathNode, PathHandler handler) {
GridGraph gg = GetGridGraph(GraphIndex);
ushort pid = handler.PathID;
{
int[] neighbourOffsets = gg.neighbourOffsets;
uint[] neighbourCosts = gg.neighbourCosts;
GridNode[] nodes = gg.nodes;
var index = NodeInGridIndex;
for (int i = 0; i < 8; i++) {
if (HasConnectionInDirection(i)) {
GridNode other = nodes[index + neighbourOffsets[i]];
if (!path.CanTraverse(other)) continue;
PathNode otherPN = handler.GetPathNode(other);
uint tmpCost = neighbourCosts[i];
// Check if the other node has not yet been visited by this path
if (otherPN.pathID != pid) {
otherPN.parent = pathNode;
otherPN.pathID = pid;
otherPN.cost = tmpCost;
otherPN.H = path.CalculateHScore(other);
otherPN.UpdateG(path);
handler.heap.Add(otherPN);
} else {
// Sorry for the huge number of #ifs
//If not we can test if the path from the current node to this one is a better one then the one already used
#if ASTAR_NO_TRAVERSAL_COST
if (pathNode.G+tmpCost < otherPN.G)
#else
if (pathNode.G+tmpCost+path.GetTraversalCost(other) < otherPN.G)
#endif
{
//Debug.Log ("Path better from " + NodeIndex + " to " + otherPN.node.NodeIndex + " " + (pathNode.G+tmpCost+path.GetTraversalCost(other)) + " < " + otherPN.G);
otherPN.cost = tmpCost;
otherPN.parent = pathNode;
other.UpdateRecursiveG(path, otherPN, handler);
}
}
}
}
}
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
base.Open(path, pathNode, handler);
#endif
}
public override void SerializeNode (GraphSerializationContext ctx) {
base.SerializeNode(ctx);
ctx.SerializeInt3(position);
ctx.writer.Write(gridFlags);
}
public override void DeserializeNode (GraphSerializationContext ctx) {
base.DeserializeNode(ctx);
position = ctx.DeserializeInt3();
gridFlags = ctx.reader.ReadUInt16();
}
#else
public override void AddConnection (GraphNode node, uint cost) {
throw new System.NotImplementedException();
}
public override void ClearConnections (bool alsoReverse) {
throw new System.NotImplementedException();
}
public override void GetConnections (GraphNodeDelegate del) {
throw new System.NotImplementedException();
}
public override void Open (Path path, PathNode pathNode, PathHandler handler) {
throw new System.NotImplementedException();
}
public override void RemoveConnection (GraphNode node) {
throw new System.NotImplementedException();
}
#endif
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5291f1ec332d746138ac025aecb1e12d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,362 @@
using UnityEngine;
using Pathfinding.Serialization;
namespace Pathfinding {
/// <summary>Base class for GridNode and LevelGridNode</summary>
public abstract class GridNodeBase : GraphNode {
protected GridNodeBase (AstarPath astar) : base(astar) {
}
const int GridFlagsWalkableErosionOffset = 8;
const int GridFlagsWalkableErosionMask = 1 << GridFlagsWalkableErosionOffset;
const int GridFlagsWalkableTmpOffset = 9;
const int GridFlagsWalkableTmpMask = 1 << GridFlagsWalkableTmpOffset;
protected const int NodeInGridIndexLayerOffset = 24;
protected const int NodeInGridIndexMask = 0xFFFFFF;
/// <summary>
/// Bitfield containing the x and z coordinates of the node as well as the layer (for layered grid graphs).
/// See: NodeInGridIndex
/// </summary>
protected int nodeInGridIndex;
protected ushort gridFlags;
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
/// <summary>
/// Custon non-grid connections from this node.
/// See: <see cref="AddConnection"/>
/// See: <see cref="RemoveConnection"/>
///
/// This field is removed if the ASTAR_GRID_NO_CUSTOM_CONNECTIONS compiler directive is used.
/// Removing it can save a tiny bit of memory. You can enable the define in the Optimizations tab in the A* inspector.
/// See: compiler-directives (view in online documentation for working links)
///
/// Note: If you modify this array or the contents of it you must call <see cref="SetConnectivityDirty"/>.
/// </summary>
public Connection[] connections;
#endif
/// <summary>
/// The index of the node in the grid.
/// This is x + z*graph.width
/// So you can get the X and Z indices using
/// <code>
/// int index = node.NodeInGridIndex;
/// int x = index % graph.width;
/// int z = index / graph.width;
/// // where graph is GridNode.GetGridGraph (node.graphIndex), i.e the graph the nodes are contained in.
/// </code>
/// </summary>
public int NodeInGridIndex { get { return nodeInGridIndex & NodeInGridIndexMask; } set { nodeInGridIndex = (nodeInGridIndex & ~NodeInGridIndexMask) | value; } }
/// <summary>
/// X coordinate of the node in the grid.
/// The node in the bottom left corner has (x,z) = (0,0) and the one in the opposite
/// corner has (x,z) = (width-1, depth-1)
/// See: ZCoordInGrid
/// See: NodeInGridIndex
/// </summary>
public int XCoordinateInGrid {
get {
return NodeInGridIndex % GridNode.GetGridGraph(GraphIndex).width;
}
}
/// <summary>
/// Z coordinate of the node in the grid.
/// The node in the bottom left corner has (x,z) = (0,0) and the one in the opposite
/// corner has (x,z) = (width-1, depth-1)
/// See: XCoordInGrid
/// See: NodeInGridIndex
/// </summary>
public int ZCoordinateInGrid {
get {
return NodeInGridIndex / GridNode.GetGridGraph(GraphIndex).width;
}
}
/// <summary>
/// Stores walkability before erosion is applied.
/// Used internally when updating the graph.
/// </summary>
public bool WalkableErosion {
get {
return (gridFlags & GridFlagsWalkableErosionMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsWalkableErosionMask | (value ? (ushort)GridFlagsWalkableErosionMask : (ushort)0)); }
}
}
/// <summary>Temporary variable used internally when updating the graph.</summary>
public bool TmpWalkable {
get {
return (gridFlags & GridFlagsWalkableTmpMask) != 0;
}
set {
unchecked { gridFlags = (ushort)(gridFlags & ~GridFlagsWalkableTmpMask | (value ? (ushort)GridFlagsWalkableTmpMask : (ushort)0)); }
}
}
/// <summary>
/// True if the node has grid connections to all its 8 neighbours.
/// Note: This will always return false if GridGraph.neighbours is set to anything other than Eight.
/// See: GetNeighbourAlongDirection
/// </summary>
public abstract bool HasConnectionsToAllEightNeighbours { get; }
public override float SurfaceArea () {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
return gg.nodeSize*gg.nodeSize;
}
public override Vector3 RandomPointOnSurface () {
GridGraph gg = GridNode.GetGridGraph(GraphIndex);
var graphSpacePosition = gg.transform.InverseTransform((Vector3)position);
return gg.transform.Transform(graphSpacePosition + new Vector3(Random.value - 0.5f, 0, Random.value - 0.5f));
}
public override int GetGizmoHashCode () {
var hash = base.GetGizmoHashCode();
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
hash ^= 17 * connections[i].GetHashCode();
}
}
#endif
hash ^= 109 * gridFlags;
return hash;
}
/// <summary>
/// Adjacent grid node in the specified direction.
/// This will return null if the node does not have a connection to a node
/// in that direction.
///
/// The dir parameter corresponds to directions in the grid as:
/// <code>
/// Z
/// |
/// |
///
/// 6 2 5
/// \ | /
/// -- 3 - X - 1 ----- X
/// / | \
/// 7 0 4
///
/// |
/// |
/// </code>
///
/// See: GetConnections
/// </summary>
public abstract GridNodeBase GetNeighbourAlongDirection(int direction);
public override bool ContainsConnection (GraphNode node) {
#if !ASTAR_GRID_NO_CUSTOM_CONNECTIONS
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node == node) {
return true;
}
}
}
#endif
for (int i = 0; i < 8; i++) {
if (node == GetNeighbourAlongDirection(i)) {
return true;
}
}
return false;
}
#if ASTAR_GRID_NO_CUSTOM_CONNECTIONS
public override void AddConnection (GraphNode node, uint cost) {
throw new System.NotImplementedException("GridNodes do not have support for adding manual connections with your current settings."+
"\nPlease disable ASTAR_GRID_NO_CUSTOM_CONNECTIONS in the Optimizations tab in the A* Inspector");
}
public override void RemoveConnection (GraphNode node) {
throw new System.NotImplementedException("GridNodes do not have support for adding manual connections with your current settings."+
"\nPlease disable ASTAR_GRID_NO_CUSTOM_CONNECTIONS in the Optimizations tab in the A* Inspector");
}
public void ClearCustomConnections (bool alsoReverse) {
}
#else
/// <summary>Same as <see cref="ClearConnections"/>, but does not clear grid connections, only custom ones (e.g added by <see cref="AddConnection"/> or a NodeLink component)</summary>
public void ClearCustomConnections (bool alsoReverse) {
if (connections != null) for (int i = 0; i < connections.Length; i++) connections[i].node.RemoveConnection(this);
connections = null;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
public override void ClearConnections (bool alsoReverse) {
ClearCustomConnections(alsoReverse);
}
public override void GetConnections (System.Action<GraphNode> action) {
if (connections != null) for (int i = 0; i < connections.Length; i++) action(connections[i].node);
}
public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) {
ushort pid = handler.PathID;
if (connections != null) for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
PathNode otherPN = handler.GetPathNode(other);
if (otherPN.parent == pathNode && otherPN.pathID == pid) other.UpdateRecursiveG(path, otherPN, handler);
}
}
public override void Open (Path path, PathNode pathNode, PathHandler handler) {
ushort pid = handler.PathID;
if (connections != null) for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
if (!path.CanTraverse(other)) continue;
PathNode otherPN = handler.GetPathNode(other);
uint tmpCost = connections[i].cost;
if (otherPN.pathID != pid) {
otherPN.parent = pathNode;
otherPN.pathID = pid;
otherPN.cost = tmpCost;
otherPN.H = path.CalculateHScore(other);
otherPN.UpdateG(path);
//Debug.Log ("G " + otherPN.G + " F " + otherPN.F);
handler.heap.Add(otherPN);
//Debug.DrawRay ((Vector3)otherPN.node.Position, Vector3.up,Color.blue);
} else {
// Sorry for the huge number of #ifs
//If not we can test if the path from the current node to this one is a better one then the one already used
#if ASTAR_NO_TRAVERSAL_COST
if (pathNode.G+tmpCost < otherPN.G)
#else
if (pathNode.G+tmpCost+path.GetTraversalCost(other) < otherPN.G)
#endif
{
//Debug.Log ("Path better from " + NodeIndex + " to " + otherPN.node.NodeIndex + " " + (pathNode.G+tmpCost+path.GetTraversalCost(other)) + " < " + otherPN.G);
otherPN.cost = tmpCost;
otherPN.parent = pathNode;
other.UpdateRecursiveG(path, otherPN, handler);
}
}
}
}
/// <summary>
/// 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.
///
/// Note: Only adds a one-way connection. Consider calling the same function on the other node
/// to get a two-way connection.
/// </summary>
public override void AddConnection (GraphNode node, uint cost) {
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;
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);
connections = newconns;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
/// <summary>
/// Removes any connection from this node to the specified node.
/// If no such connection exists, nothing will be done.
///
/// Note: This only removes the connection from this node to the other node.
/// You may want to call the same function on the other node to remove its eventual connection
/// to this node.
/// </summary>
public override void RemoveConnection (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 SerializeReferences (GraphSerializationContext ctx) {
// TODO: Deduplicate code
if (connections == null) {
ctx.writer.Write(-1);
} else {
ctx.writer.Write(connections.Length);
for (int i = 0; i < connections.Length; i++) {
ctx.SerializeNodeReference(connections[i].node);
ctx.writer.Write(connections[i].cost);
}
}
}
public override void DeserializeReferences (GraphSerializationContext ctx) {
// Grid nodes didn't serialize references before 3.8.3
if (ctx.meta.version < AstarSerializer.V3_8_3)
return;
int count = ctx.reader.ReadInt32();
if (count == -1) {
connections = null;
} else {
connections = new Connection[count];
for (int i = 0; i < count; i++) {
connections[i] = new Connection(ctx.DeserializeNodeReference(), ctx.reader.ReadUInt32());
}
}
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1b723ddfaa37b46eca23cd8d042ad3e9
timeCreated: 1459629300
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,272 @@
using UnityEngine;
using Pathfinding.Serialization;
namespace Pathfinding {
/// <summary>
/// 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
/// </summary>
public class PointNode : GraphNode {
/// <summary>
/// All connections from this node.
/// See: <see cref="AddConnection"/>
/// See: <see cref="RemoveConnection"/>
///
/// Note: If you modify this array or the contents of it you must call <see cref="SetConnectivityDirty"/>.
///
/// Note: If you modify this array or the contents of it you must call <see cref="PointGraph.RegisterConnectionLength"/> with the length of the new connections.
/// </summary>
public Connection[] connections;
/// <summary>
/// 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.
///
/// <code>
/// 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");
/// }
/// </code>
/// </summary>
public GameObject gameObject;
public void SetPosition (Int3 value) {
position = value;
}
public PointNode (AstarPath astar) : base(astar) {
}
/// <summary>
/// Closest point on the surface of this node to the point p.
///
/// For a point node this is always the node's <see cref="position"/> sicne it has no surface.
/// </summary>
public override Vector3 ClosestPointOnNode (Vector3 p) {
return (Vector3)this.position;
}
public override void GetConnections (System.Action<GraphNode> action) {
if (connections == null) return;
for (int i = 0; i < connections.Length; i++) action(connections[i].node);
}
public override void ClearConnections (bool alsoReverse) {
if (alsoReverse && connections != null) {
for (int i = 0; i < connections.Length; i++) {
connections[i].node.RemoveConnection(this);
}
}
connections = null;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
}
public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) {
pathNode.UpdateG(path);
handler.heap.Add(pathNode);
for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
PathNode otherPN = handler.GetPathNode(other);
if (otherPN.parent == pathNode && otherPN.pathID == handler.PathID) {
other.UpdateRecursiveG(path, otherPN, handler);
}
}
}
public override bool ContainsConnection (GraphNode node) {
if (connections == null) return false;
for (int i = 0; i < connections.Length; i++) if (connections[i].node == node) return true;
return false;
}
/// <summary>
/// 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.
///
/// Note: Only adds a one-way connection. Consider calling the same function on the other node
/// to get a two-way connection.
///
/// <code>
/// 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.AddConnection(node2, cost);
/// node2.AddConnection(node1, cost);
///
/// node1.ContainsConnection(node2); // True
///
/// node1.RemoveConnection(node2);
/// node2.RemoveConnection(node1);
/// }));
/// </code>
/// </summary>
public override void AddConnection (GraphNode node, uint cost) {
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;
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);
connections = newconns;
AstarPath.active.hierarchicalGraph.AddDirtyNode(this);
// Make sure the graph knows that there exists a connection with this length
(this.Graph as PointGraph).RegisterConnectionLength((node.position - position).sqrMagnitudeLong);
}
/// <summary>
/// Removes any connection from this node to the specified node.
/// If no such connection exists, nothing will be done.
///
/// Note: This only removes the connection from this node to the other node.
/// You may want to call the same function on the other node to remove its possible connection
/// to this node.
///
/// <code>
/// 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.AddConnection(node2, cost);
/// node2.AddConnection(node1, cost);
///
/// node1.ContainsConnection(node2); // True
///
/// node1.RemoveConnection(node2);
/// node2.RemoveConnection(node1);
/// }));
/// </code>
/// </summary>
public override void RemoveConnection (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, PathNode pathNode, PathHandler handler) {
if (connections == null) return;
for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
if (path.CanTraverse(other)) {
PathNode pathOther = handler.GetPathNode(other);
if (pathOther.pathID != handler.PathID) {
pathOther.parent = pathNode;
pathOther.pathID = handler.PathID;
pathOther.cost = connections[i].cost;
pathOther.H = path.CalculateHScore(other);
pathOther.UpdateG(path);
handler.heap.Add(pathOther);
} else {
//If not we can test if the path from this node to the other one is a better one then the one already used
uint tmpCost = connections[i].cost;
if (pathNode.G + tmpCost + path.GetTraversalCost(other) < pathOther.G) {
pathOther.cost = tmpCost;
pathOther.parent = pathNode;
other.UpdateRecursiveG(path, pathOther, handler);
}
}
}
}
}
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) {
if (connections == null) {
ctx.writer.Write(-1);
} else {
ctx.writer.Write(connections.Length);
for (int i = 0; i < connections.Length; i++) {
ctx.SerializeNodeReference(connections[i].node);
ctx.writer.Write(connections[i].cost);
}
}
}
public override void DeserializeReferences (GraphSerializationContext ctx) {
int count = ctx.reader.ReadInt32();
if (count == -1) {
connections = null;
} else {
connections = new Connection[count];
for (int i = 0; i < count; i++) {
connections[i] = new Connection(ctx.DeserializeNodeReference(), ctx.reader.ReadUInt32());
}
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 986ad6174b59e40068c715a916740ce9
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,460 @@
using UnityEngine;
using Pathfinding.Serialization;
namespace Pathfinding {
/// <summary>Interface for something that holds a triangle based navmesh</summary>
public interface INavmeshHolder : ITransformedGraph, INavmesh {
/// <summary>Position of vertex number i in the world</summary>
Int3 GetVertex(int i);
/// <summary>
/// Position of vertex number i in coordinates local to the graph.
/// The up direction is always the +Y axis for these coordinates.
/// </summary>
Int3 GetVertexInGraphSpace(int i);
int GetVertexArrayIndex(int index);
/// <summary>Transforms coordinates from graph space to world space</summary>
void GetTileCoordinates(int tileIndex, out int x, out int z);
}
/// <summary>Node represented by a triangle</summary>
public class TriangleMeshNode : MeshNode {
public TriangleMeshNode (AstarPath astar) : base(astar) {}
/// <summary>Internal vertex index for the first vertex</summary>
public int v0;
/// <summary>Internal vertex index for the second vertex</summary>
public int v1;
/// <summary>Internal vertex index for the third vertex</summary>
public int v2;
/// <summary>Holds INavmeshHolder references for all graph indices to be able to access them in a performant manner</summary>
protected static INavmeshHolder[] _navmeshHolders = new INavmeshHolder[0];
/// <summary>Used for synchronised access to the <see cref="_navmeshHolders"/> array</summary>
protected static readonly System.Object lockObject = new System.Object();
public static INavmeshHolder GetNavmeshHolder (uint graphIndex) {
return _navmeshHolders[(int)graphIndex];
}
/// <summary>
/// Sets the internal navmesh holder for a given graph index.
/// Warning: Internal method
/// </summary>
public static void SetNavmeshHolder (int graphIndex, INavmeshHolder graph) {
// We need to lock to make sure that
// the resize operation is thread safe
lock (lockObject) {
if (graphIndex >= _navmeshHolders.Length) {
var gg = new INavmeshHolder[graphIndex+1];
_navmeshHolders.CopyTo(gg, 0);
_navmeshHolders = gg;
}
_navmeshHolders[graphIndex] = graph;
}
}
/// <summary>Set the position of this node to the average of its 3 vertices</summary>
public void UpdatePositionFromVertices () {
Int3 a, b, c;
GetVertices(out a, out b, out c);
position = (a + b + c) * 0.333333f;
}
/// <summary>
/// Return a number identifying a vertex.
/// This number does not necessarily need to be a index in an array but two different vertices (in the same graph) should
/// not have the same vertex numbers.
/// </summary>
public int GetVertexIndex (int i) {
return i == 0 ? v0 : (i == 1 ? v1 : v2);
}
/// <summary>
/// Return a number specifying an index in the source vertex array.
/// The vertex array can for example be contained in a recast tile, or be a navmesh graph, that is graph dependant.
/// This is slower than GetVertexIndex, if you only need to compare vertices, use GetVertexIndex.
/// </summary>
public int GetVertexArrayIndex (int i) {
return GetNavmeshHolder(GraphIndex).GetVertexArrayIndex(i == 0 ? v0 : (i == 1 ? v1 : v2));
}
/// <summary>Returns all 3 vertices of this node in world space</summary>
public void GetVertices (out Int3 v0, out Int3 v1, out Int3 v2) {
// Get the object holding the vertex data for this node
// This is usually a graph or a recast graph tile
var holder = GetNavmeshHolder(GraphIndex);
v0 = holder.GetVertex(this.v0);
v1 = holder.GetVertex(this.v1);
v2 = holder.GetVertex(this.v2);
}
/// <summary>Returns all 3 vertices of this node in graph space</summary>
public void GetVerticesInGraphSpace (out Int3 v0, out Int3 v1, out Int3 v2) {
// Get the object holding the vertex data for this node
// This is usually a graph or a recast graph tile
var holder = GetNavmeshHolder(GraphIndex);
v0 = holder.GetVertexInGraphSpace(this.v0);
v1 = holder.GetVertexInGraphSpace(this.v1);
v2 = holder.GetVertexInGraphSpace(this.v2);
}
public override Int3 GetVertex (int i) {
return GetNavmeshHolder(GraphIndex).GetVertex(GetVertexIndex(i));
}
public Int3 GetVertexInGraphSpace (int i) {
return GetNavmeshHolder(GraphIndex).GetVertexInGraphSpace(GetVertexIndex(i));
}
public override int GetVertexCount () {
// A triangle has 3 vertices
return 3;
}
public override Vector3 ClosestPointOnNode (Vector3 p) {
Int3 a, b, c;
GetVertices(out a, out b, out c);
return Pathfinding.Polygon.ClosestPointOnTriangle((Vector3)a, (Vector3)b, (Vector3)c, p);
}
/// <summary>
/// Closest point on the node when seen from above.
/// This method is mostly for internal use as the <see cref="Pathfinding.NavmeshBase.Linecast"/> methods use it.
///
/// - The returned point is the closest one on the node to p when seen from above (relative to the graph).
/// This is important mostly for sloped surfaces.
/// - The returned point is an Int3 point in graph space.
/// - It is guaranteed to be inside the node, so if you call <see cref="ContainsPointInGraphSpace"/> with the return value from this method the result is guaranteed to be true.
///
/// This method is slower than e.g <see cref="ClosestPointOnNode"/> or <see cref="ClosestPointOnNodeXZ"/>.
/// However they do not have the same guarantees as this method has.
/// </summary>
internal Int3 ClosestPointOnNodeXZInGraphSpace (Vector3 p) {
// Get the vertices that make up the triangle
Int3 a, b, c;
GetVerticesInGraphSpace(out a, out b, out c);
// Convert p to graph space
p = GetNavmeshHolder(GraphIndex).transform.InverseTransform(p);
// Find the closest point on the triangle to p when looking at the triangle from above (relative to the graph)
var closest = Pathfinding.Polygon.ClosestPointOnTriangleXZ((Vector3)a, (Vector3)b, (Vector3)c, p);
// Make sure the point is actually inside the node
var i3closest = (Int3)closest;
if (ContainsPointInGraphSpace(i3closest)) {
// Common case
return i3closest;
} else {
// Annoying...
// The closest point when converted from floating point coordinates to integer coordinates
// is not actually inside the node. It needs to be inside the node for some methods
// (like for example Linecast) to work properly.
// Try the 8 integer coordinates around the closest point
// and check if any one of them are completely inside the node.
// This will most likely succeed as it should be very close.
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
if ((dx != 0 || dz != 0)) {
var candidate = new Int3(i3closest.x + dx, i3closest.y, i3closest.z + dz);
if (ContainsPointInGraphSpace(candidate)) return candidate;
}
}
}
// Happens veery rarely.
// Pick the closest vertex of the triangle.
// The vertex is guaranteed to be inside the triangle.
var da = (a - i3closest).sqrMagnitudeLong;
var db = (b - i3closest).sqrMagnitudeLong;
var dc = (c - i3closest).sqrMagnitudeLong;
return da < db ? (da < dc ? a : c) : (db < dc ? b : c);
}
}
public override Vector3 ClosestPointOnNodeXZ (Vector3 p) {
// Get all 3 vertices for this node
Int3 tp1, tp2, tp3;
GetVertices(out tp1, out tp2, out tp3);
return Polygon.ClosestPointOnTriangleXZ((Vector3)tp1, (Vector3)tp2, (Vector3)tp3, p);
}
public override bool ContainsPoint (Vector3 p) {
return ContainsPointInGraphSpace((Int3)GetNavmeshHolder(GraphIndex).transform.InverseTransform(p));
}
public override bool ContainsPointInGraphSpace (Int3 p) {
// Get all 3 vertices for this node
Int3 a, b, c;
GetVerticesInGraphSpace(out a, out b, out c);
if ((long)(b.x - a.x) * (long)(p.z - a.z) - (long)(p.x - a.x) * (long)(b.z - a.z) > 0) return false;
if ((long)(c.x - b.x) * (long)(p.z - b.z) - (long)(p.x - b.x) * (long)(c.z - b.z) > 0) return false;
if ((long)(a.x - c.x) * (long)(p.z - c.z) - (long)(p.x - c.x) * (long)(a.z - c.z) > 0) return false;
return true;
// Equivalent code, but the above code is faster
//return Polygon.IsClockwiseMargin (a,b, p) && Polygon.IsClockwiseMargin (b,c, p) && Polygon.IsClockwiseMargin (c,a, p);
//return Polygon.ContainsPoint(g.GetVertex(v0),g.GetVertex(v1),g.GetVertex(v2),p);
}
public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) {
pathNode.UpdateG(path);
handler.heap.Add(pathNode);
if (connections == null) return;
for (int i = 0; i < connections.Length; i++) {
GraphNode other = connections[i].node;
PathNode otherPN = handler.GetPathNode(other);
if (otherPN.parent == pathNode && otherPN.pathID == handler.PathID) other.UpdateRecursiveG(path, otherPN, handler);
}
}
public override void Open (Path path, PathNode pathNode, PathHandler handler) {
if (connections == null) return;
// Flag2 indicates if this node needs special treatment
// with regard to connection costs
bool flag2 = pathNode.flag2;
// Loop through all connections
for (int i = connections.Length-1; i >= 0; i--) {
var conn = connections[i];
var other = conn.node;
// Make sure we can traverse the neighbour
if (path.CanTraverse(conn.node)) {
PathNode pathOther = handler.GetPathNode(conn.node);
// Fast path out, worth it for triangle mesh nodes since they usually have degree 2 or 3
if (pathOther == pathNode.parent) {
continue;
}
uint cost = conn.cost;
if (flag2 || pathOther.flag2) {
// Get special connection cost from the path
// This is used by the start and end nodes
cost = path.GetConnectionSpecialCost(this, conn.node, cost);
}
// Test if we have seen the other node before
if (pathOther.pathID != handler.PathID) {
// We have not seen the other node before
// So the path from the start through this node to the other node
// must be the shortest one so far
// Might not be assigned
pathOther.node = conn.node;
pathOther.parent = pathNode;
pathOther.pathID = handler.PathID;
pathOther.cost = cost;
pathOther.H = path.CalculateHScore(other);
pathOther.UpdateG(path);
handler.heap.Add(pathOther);
} else {
// If not we can test if the path from this node to the other one is a better one than the one already used
if (pathNode.G + cost + path.GetTraversalCost(other) < pathOther.G) {
pathOther.cost = cost;
pathOther.parent = pathNode;
other.UpdateRecursiveG(path, pathOther, handler);
}
}
}
}
}
/// <summary>
/// Returns the edge which is shared with other.
/// If no edge is shared, -1 is returned.
/// If there is a connection with the other node, but the connection is not marked as using a particular edge of the shape of the node
/// then 0xFF will be returned.
///
/// The vertices in the edge can be retrieved using
/// <code>
/// var edge = node.SharedEdge(other);
/// var a = node.GetVertex(edge);
/// var b = node.GetVertex((edge+1) % node.GetVertexCount());
/// </code>
///
/// See: <see cref="GetPortal"/> which also handles edges that are shared over tile borders and some types of node links
/// </summary>
public int SharedEdge (GraphNode other) {
var edge = -1;
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node == other) edge = connections[i].shapeEdge;
}
}
return edge;
}
public override bool GetPortal (GraphNode toNode, System.Collections.Generic.List<Vector3> left, System.Collections.Generic.List<Vector3> right, bool backwards) {
int aIndex, bIndex;
return GetPortal(toNode, left, right, backwards, out aIndex, out bIndex);
}
public bool GetPortal (GraphNode toNode, System.Collections.Generic.List<Vector3> left, System.Collections.Generic.List<Vector3> right, bool backwards, out int aIndex, out int bIndex) {
aIndex = -1;
bIndex = -1;
//If the nodes are in different graphs, this function has no idea on how to find a shared edge.
if (backwards || toNode.GraphIndex != GraphIndex) return false;
// Since the nodes are in the same graph, they are both TriangleMeshNodes
// So we don't need to care about other types of nodes
var toTriNode = toNode as TriangleMeshNode;
var edge = SharedEdge(toTriNode);
// A connection was found, but it specifically didn't use an edge
if (edge == 0xFF) return false;
// No connection was found between the nodes
// Check if there is a node link that connects them
if (edge == -1) {
#if !ASTAR_NO_POINT_GRAPH
if (connections != null) {
for (int i = 0; i < connections.Length; i++) {
if (connections[i].node.GraphIndex != GraphIndex) {
var mid = connections[i].node as NodeLink3Node;
if (mid != null && mid.GetOther(this) == toTriNode) {
// We have found a node which is connected through a NodeLink3Node
mid.GetPortal(toTriNode, left, right, false);
return true;
}
}
}
}
#endif
return false;
}
aIndex = edge;
bIndex = (edge + 1) % GetVertexCount();
// Get the vertices of the shared edge for the first node
Int3 v1a = GetVertex(edge);
Int3 v1b = GetVertex((edge+1) % GetVertexCount());
// Get tile indices
int tileIndex1 = (GetVertexIndex(0) >> NavmeshBase.TileIndexOffset) & NavmeshBase.TileIndexMask;
int tileIndex2 = (toTriNode.GetVertexIndex(0) >> NavmeshBase.TileIndexOffset) & NavmeshBase.TileIndexMask;
if (tileIndex1 != tileIndex2) {
// When the nodes are in different tiles, the edges might not be completely identical
// so another technique is needed.
// Get the tile coordinates, from them we can figure out which edge is going to be shared
int x1, x2, z1, z2, coord;
INavmeshHolder nm = GetNavmeshHolder(GraphIndex);
nm.GetTileCoordinates(tileIndex1, out x1, out z1);
nm.GetTileCoordinates(tileIndex2, out x2, out z2);
if (System.Math.Abs(x1-x2) == 1) coord = 2;
else if (System.Math.Abs(z1-z2) == 1) coord = 0;
else return false; // Tiles are not adjacent. This is likely a custom connection between two nodes.
var otherEdge = toTriNode.SharedEdge(this);
// A connection was found, but it specifically didn't use an edge. This is odd since the connection in the other direction did use an edge
if (otherEdge == 0xFF) throw new System.Exception("Connection used edge in one direction, but not in the other direction. Has the wrong overload of AddConnection been used?");
// If it is -1 then it must be a one-way connection. Fall back to using the whole edge
if (otherEdge != -1) {
// When the nodes are in different tiles, they might not share exactly the same edge
// so we clamp the portal to the segment of the edges which they both have.
int mincoord = System.Math.Min(v1a[coord], v1b[coord]);
int maxcoord = System.Math.Max(v1a[coord], v1b[coord]);
// Get the vertices of the shared edge for the second node
Int3 v2a = toTriNode.GetVertex(otherEdge);
Int3 v2b = toTriNode.GetVertex((otherEdge+1) % toTriNode.GetVertexCount());
mincoord = System.Math.Max(mincoord, System.Math.Min(v2a[coord], v2b[coord]));
maxcoord = System.Math.Min(maxcoord, System.Math.Max(v2a[coord], v2b[coord]));
if (v1a[coord] < v1b[coord]) {
v1a[coord] = mincoord;
v1b[coord] = maxcoord;
} else {
v1a[coord] = maxcoord;
v1b[coord] = mincoord;
}
}
}
if (left != null) {
// All triangles should be laid out in clockwise order so v1b is the rightmost vertex (seen from this node)
left.Add((Vector3)v1a);
right.Add((Vector3)v1b);
}
return true;
}
/// <summary>TODO: This is the area in XZ space, use full 3D space for higher correctness maybe?</summary>
public override float SurfaceArea () {
var holder = GetNavmeshHolder(GraphIndex);
return System.Math.Abs(VectorMath.SignedTriangleAreaTimes2XZ(holder.GetVertex(v0), holder.GetVertex(v1), holder.GetVertex(v2))) * 0.5f;
}
public override Vector3 RandomPointOnSurface () {
// Find a random point inside the triangle
// This generates uniformly distributed trilinear coordinates
// See http://mathworld.wolfram.com/TrianglePointPicking.html
float r1;
float r2;
do {
r1 = Random.value;
r2 = Random.value;
} while (r1+r2 > 1);
var holder = GetNavmeshHolder(GraphIndex);
// Pick the point corresponding to the trilinear coordinate
return ((Vector3)(holder.GetVertex(v1)-holder.GetVertex(v0)))*r1 + ((Vector3)(holder.GetVertex(v2)-holder.GetVertex(v0)))*r2 + (Vector3)holder.GetVertex(v0);
}
public override void SerializeNode (GraphSerializationContext ctx) {
base.SerializeNode(ctx);
ctx.writer.Write(v0);
ctx.writer.Write(v1);
ctx.writer.Write(v2);
}
public override void DeserializeNode (GraphSerializationContext ctx) {
base.DeserializeNode(ctx);
v0 = ctx.reader.ReadInt32();
v1 = ctx.reader.ReadInt32();
v2 = ctx.reader.ReadInt32();
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 54908f58720324c048a5b475a27077fa
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,591 @@
using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Serialization;
namespace Pathfinding {
/// <summary>
/// Basic point graph.
/// \ingroup graphs
/// The point graph is the most basic graph structure, it consists of a number of interconnected points in space called nodes or waypoints.\n
/// The point graph takes a Transform object as "root", this Transform will be searched for child objects, every child object will be treated as a node.
/// If <see cref="recursive"/> is enabled, it will also search the child objects of the children recursively.
/// It will then check if any connections between the nodes can be made, first it will check if the distance between the nodes isn't too large (<see cref="maxDistance)"/>
/// and then it will check if the axis aligned distance isn't too high. The axis aligned distance, named <see cref="limits"/>,
/// is useful because usually an AI cannot climb very high, but linking nodes far away from each other,
/// but on the same Y level should still be possible. <see cref="limits"/> and <see cref="maxDistance"/> are treated as being set to infinity if they are set to 0 (zero). \n
/// Lastly it will check if there are any obstructions between the nodes using
/// <a href="http://unity3d.com/support/documentation/ScriptReference/Physics.Raycast.html">raycasting</a> which can optionally be thick.\n
/// One thing to think about when using raycasting is to either place the nodes a small
/// distance above the ground in your scene or to make sure that the ground is not in the raycast mask to avoid the raycast from hitting the ground.\n
///
/// Alternatively, a tag can be used to search for nodes.
/// See: http://docs.unity3d.com/Manual/Tags.html
///
/// For larger graphs, it can take quite some time to scan the graph with the default settings.
/// If you have the pro version you can enable <see cref="optimizeForSparseGraph"/> which will in most cases reduce the calculation times
/// drastically.
///
/// Note: Does not support linecast because of obvious reasons.
///
/// [Open online documentation to see images]
/// [Open online documentation to see images]
/// </summary>
[JsonOptIn]
[Pathfinding.Util.Preserve]
public class PointGraph : NavGraph {
/// <summary>Childs of this transform are treated as nodes</summary>
[JsonMember]
public Transform root;
/// <summary>If no <see cref="root"/> is set, all nodes with the tag is used as nodes</summary>
[JsonMember]
public string searchTag;
/// <summary>
/// Max distance for a connection to be valid.
/// The value 0 (zero) will be read as infinity and thus all nodes not restricted by
/// other constraints will be added as connections.
///
/// A negative value will disable any neighbours to be added.
/// It will completely stop the connection processing to be done, so it can save you processing
/// power if you don't these connections.
/// </summary>
[JsonMember]
public float maxDistance;
/// <summary>Max distance along the axis for a connection to be valid. 0 = infinity</summary>
[JsonMember]
public Vector3 limits;
/// <summary>Use raycasts to check connections</summary>
[JsonMember]
public bool raycast = true;
/// <summary>Use the 2D Physics API</summary>
[JsonMember]
public bool use2DPhysics;
/// <summary>Use thick raycast</summary>
[JsonMember]
public bool thickRaycast;
/// <summary>Thick raycast radius</summary>
[JsonMember]
public float thickRaycastRadius = 1;
/// <summary>Recursively search for child nodes to the <see cref="root"/></summary>
[JsonMember]
public bool recursive = true;
/// <summary>Layer mask to use for raycast</summary>
[JsonMember]
public LayerMask mask;
/// <summary>
/// All nodes in this graph.
/// Note that only the first <see cref="nodeCount"/> will be non-null.
///
/// You can also use the GetNodes method to get all nodes.
/// </summary>
public PointNode[] nodes;
/// <summary>
/// \copydoc Pathfinding::PointGraph::NodeDistanceMode
///
/// See: <see cref="NodeDistanceMode"/>
///
/// If you enable this during runtime, you will need to call <see cref="RebuildConnectionDistanceLookup"/> to make sure some cache data is properly recalculated.
/// If the graph doesn't have any nodes yet or if you are going to scan the graph afterwards then you do not need to do this.
/// </summary>
[JsonMember]
public NodeDistanceMode nearestNodeDistanceMode;
/// <summary>Number of nodes in this graph</summary>
public int nodeCount { get; protected set; }
/// <summary>
/// Distance query mode.
/// [Open online documentation to see images]
///
/// In the image above there are a few red nodes. Assume the agent is the orange circle. Using the Node mode the closest point on the graph that would be found would be the node at the bottom center which
/// may not be what you want. Using the %Connection mode it will find the closest point on the connection between the two nodes in the top half of the image.
///
/// When using the %Connection option you may also want to use the %Connection option for the Seeker's Start End Modifier snapping options.
/// This is not strictly necessary, but it most cases it is what you want.
///
/// See: <see cref="Pathfinding.StartEndModifier.exactEndPoint"/>
/// </summary>
public enum NodeDistanceMode {
/// <summary>
/// All nearest node queries find the closest node center.
/// This is the fastest option but it may not be what you want if you have long connections.
/// </summary>
Node,
/// <summary>
/// All nearest node queries find the closest point on edges between nodes.
/// This is useful if you have long connections where the agent might be closer to some unrelated node if it is standing on a long connection between two nodes.
/// This mode is however slower than the Node mode.
/// </summary>
Connection,
}
public override int CountNodes () {
return nodeCount;
}
public override void GetNodes (System.Action<GraphNode> action) {
if (nodes == null) return;
var count = nodeCount;
for (int i = 0; i < count; i++) action(nodes[i]);
}
public override NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint, GraphNode hint) {
return GetNearestInternal(position, constraint, true);
}
public override NNInfoInternal GetNearestForce (Vector3 position, NNConstraint constraint) {
return GetNearestInternal(position, constraint, false);
}
NNInfoInternal GetNearestInternal (Vector3 position, NNConstraint constraint, bool fastCheck) {
if (nodes == null) return new NNInfoInternal();
var iposition = (Int3)position;
float maxDistSqr = constraint == null || constraint.constrainDistance ? AstarPath.active.maxNearestNodeDistanceSqr : float.PositiveInfinity;
maxDistSqr *= Int3.FloatPrecision * Int3.FloatPrecision;
var nnInfo = new NNInfoInternal(null);
long minDist = long.MaxValue;
long minConstDist = long.MaxValue;
for (int i = 0; i < nodeCount; i++) {
PointNode node = nodes[i];
long dist = (iposition - node.position).sqrMagnitudeLong;
if (dist < minDist) {
minDist = dist;
nnInfo.node = node;
}
if (dist < minConstDist && (float)dist < maxDistSqr && (constraint == null || constraint.Suitable(node))) {
minConstDist = dist;
nnInfo.constrainedNode = node;
}
}
if (!fastCheck) nnInfo.node = nnInfo.constrainedNode;
nnInfo.UpdateInfo();
return nnInfo;
}
NNInfoInternal FindClosestConnectionPoint (PointNode node, Vector3 position) {
var closestConnectionPoint = (Vector3)node.position;
var conns = node.connections;
var nodePos = (Vector3)node.position;
var bestDist = float.PositiveInfinity;
if (conns != null) {
for (int i = 0; i < conns.Length; i++) {
var connectionMidpoint = ((UnityEngine.Vector3)conns[i].node.position + nodePos) * 0.5f;
var closestPoint = VectorMath.ClosestPointOnSegment(nodePos, connectionMidpoint, position);
var dist = (closestPoint - position).sqrMagnitude;
if (dist < bestDist) {
bestDist = dist;
closestConnectionPoint = closestPoint;
}
}
}
var result = new NNInfoInternal();
result.node = node;
result.clampedPosition = closestConnectionPoint;
return result;
}
/// <summary>
/// Add a node to the graph at the specified position.
/// Note: Vector3 can be casted to Int3 using (Int3)myVector.
///
/// Note: This needs to be called when it is safe to update nodes, which is
/// - when scanning
/// - during a graph update
/// - inside a callback registered using AstarPath.AddWorkItem
///
/// <code>
/// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => {
/// var graph = AstarPath.active.data.pointGraph;
/// // Add 2 nodes and connect them
/// var node1 = graph.AddNode((Int3)transform.position);
/// var node2 = graph.AddNode((Int3)(transform.position + Vector3.right));
/// var cost = (uint)(node2.position - node1.position).costMagnitude;
/// node1.AddConnection(node2, cost);
/// node2.AddConnection(node1, cost);
/// }));
/// </code>
///
/// See: runtime-graphs (view in online documentation for working links)
/// </summary>
public PointNode AddNode (Int3 position) {
return AddNode(new PointNode(active), position);
}
/// <summary>
/// Add a node with the specified type to the graph at the specified position.
///
/// Note: Vector3 can be casted to Int3 using (Int3)myVector.
///
/// Note: This needs to be called when it is safe to update nodes, which is
/// - when scanning
/// - during a graph update
/// - inside a callback registered using AstarPath.AddWorkItem
///
/// See: <see cref="AstarPath.AddWorkItem"/>
/// See: runtime-graphs (view in online documentation for working links)
/// </summary>
/// <param name="node">This must be a node created using T(AstarPath.active) right before the call to this method.
/// The node parameter is only there because there is no new(AstarPath) constraint on
/// generic type parameters.</param>
/// <param name="position">The node will be set to this position.</param>
public T AddNode<T>(T node, Int3 position) where T : PointNode {
if (nodes == null || nodeCount == nodes.Length) {
var newNodes = new PointNode[nodes != null ? System.Math.Max(nodes.Length+4, nodes.Length*2) : 4];
if (nodes != null) nodes.CopyTo(newNodes, 0);
nodes = newNodes;
}
node.SetPosition(position);
node.GraphIndex = graphIndex;
node.Walkable = true;
nodes[nodeCount] = node;
nodeCount++;
return node;
}
/// <summary>Recursively counds children of a transform</summary>
protected static int CountChildren (Transform tr) {
int c = 0;
foreach (Transform child in tr) {
c++;
c += CountChildren(child);
}
return c;
}
/// <summary>Recursively adds childrens of a transform as nodes</summary>
protected void AddChildren (ref int c, Transform tr) {
foreach (Transform child in tr) {
nodes[c].position = (Int3)child.position;
nodes[c].Walkable = true;
nodes[c].gameObject = child.gameObject;
c++;
AddChildren(ref c, child);
}
}
/// <summary>
/// Rebuilds the lookup structure for nodes.
///
/// This is used when <see cref="optimizeForSparseGraph"/> is enabled.
///
/// You should call this method every time you move a node in the graph manually and
/// you are using <see cref="optimizeForSparseGraph"/>, otherwise pathfinding might not work correctly.
///
/// You may also call this after you have added many nodes using the
/// <see cref="AddNode"/> method. When adding nodes using the <see cref="AddNode"/> method they
/// will be added to the lookup structure. The lookup structure will
/// rebalance itself when it gets too unbalanced however if you are
/// sure you won't be adding any more nodes in the short term, you can
/// make sure it is perfectly balanced and thus squeeze out the last
/// bit of performance by calling this method. This can improve the
/// performance of the <see cref="GetNearest"/> method slightly. The improvements
/// are on the order of 10-20%.
/// </summary>
public void RebuildNodeLookup () {
// A* Pathfinding Project Pro Only
}
/// <summary>Rebuilds a cache used when <see cref="nearestNodeDistanceMode"/> = <see cref="NodeDistanceMode.ToConnection"/></summary>
public void RebuildConnectionDistanceLookup () {
}
void AddToLookup (PointNode node) {
// A* Pathfinding Project Pro Only
}
/// <summary>
/// Ensures the graph knows that there is a connection with this length.
/// This is used when the nearest node distance mode is set to ToConnection.
/// If you are modifying node connections yourself (i.e. manipulating the PointNode.connections array) then you must call this function
/// when you add any connections.
///
/// When using PointNode.AddConnection this is done automatically.
/// It is also done for all nodes when <see cref="RebuildNodeLookup"/> is called.
/// </summary>
/// <param name="sqrLength">The length of the connection in squared Int3 units. This can be calculated using (node1.position - node2.position).sqrMagnitudeLong.</param>
public void RegisterConnectionLength (long sqrLength) {
// A* Pathfinding Project Pro Only
}
protected virtual PointNode[] CreateNodes (int count) {
var nodes = new PointNode[count];
for (int i = 0; i < nodeCount; i++) nodes[i] = new PointNode(active);
return nodes;
}
protected override IEnumerable<Progress> ScanInternal () {
yield return new Progress(0, "Searching for GameObjects");
if (root == null) {
// If there is no root object, try to find nodes with the specified tag instead
GameObject[] gos = searchTag != null? GameObject.FindGameObjectsWithTag(searchTag) : null;
if (gos == null) {
nodes = new PointNode[0];
nodeCount = 0;
} else {
yield return new Progress(0.1f, "Creating nodes");
// Create all the nodes
nodeCount = gos.Length;
nodes = CreateNodes(nodeCount);
for (int i = 0; i < gos.Length; i++) {
nodes[i].position = (Int3)gos[i].transform.position;
nodes[i].Walkable = true;
nodes[i].gameObject = gos[i].gameObject;
}
}
} else {
// Search the root for children and create nodes for them
if (!recursive) {
nodeCount = root.childCount;
nodes = CreateNodes(nodeCount);
int c = 0;
foreach (Transform child in root) {
nodes[c].position = (Int3)child.position;
nodes[c].Walkable = true;
nodes[c].gameObject = child.gameObject;
c++;
}
} else {
nodeCount = CountChildren(root);
nodes = CreateNodes(nodeCount);
int startID = 0;
AddChildren(ref startID, root);
}
}
foreach (var progress in ConnectNodesAsync()) yield return progress.MapTo(0.15f, 0.95f);
}
/// <summary>
/// Recalculates connections for all nodes in the graph.
/// This is useful if you have created nodes manually using <see cref="AddNode"/> and then want to connect them in the same way as the point graph normally connects nodes.
/// </summary>
public void ConnectNodes () {
var ie = ConnectNodesAsync().GetEnumerator();
while (ie.MoveNext()) {}
RebuildConnectionDistanceLookup();
}
/// <summary>
/// Calculates connections for all nodes in the graph.
/// This is an IEnumerable, you can iterate through it using e.g foreach to get progress information.
/// </summary>
IEnumerable<Progress> ConnectNodesAsync () {
if (maxDistance >= 0) {
// To avoid too many allocations, these lists are reused for each node
var connections = new List<Connection>();
long maxSquaredRange;
// Max possible squared length of a connection between two nodes
// This is used to speed up the calculations by skipping a lot of nodes that do not need to be checked
if (maxDistance == 0 && (limits.x == 0 || limits.y == 0 || limits.z == 0)) {
maxSquaredRange = long.MaxValue;
} else {
maxSquaredRange = (long)(Mathf.Max(limits.x, Mathf.Max(limits.y, Mathf.Max(limits.z, maxDistance))) * Int3.Precision) + 1;
maxSquaredRange *= maxSquaredRange;
}
// Report progress every N nodes
const int YieldEveryNNodes = 512;
// Loop through all nodes and add connections to other nodes
for (int i = 0; i < nodeCount; i++) {
if (i % YieldEveryNNodes == 0) {
yield return new Progress(i/(float)nodeCount, "Connecting nodes");
}
connections.Clear();
var node = nodes[i];
// Only brute force is available in the free version
for (int j = 0; j < nodeCount; j++) {
if (i == j) continue;
PointNode other = nodes[j];
float dist;
if (IsValidConnection(node, other, out dist)) {
connections.Add(new Connection(
other,
/// <summary>TODO: Is this equal to .costMagnitude</summary>
(uint)Mathf.RoundToInt(dist*Int3.FloatPrecision)
));
}
}
node.connections = connections.ToArray();
node.SetConnectivityDirty();
}
}
}
/// <summary>
/// Returns if the connection between a and b is valid.
/// Checks for obstructions using raycasts (if enabled) and checks for height differences.\n
/// As a bonus, it outputs the distance between the nodes too if the connection is valid.
///
/// Note: This is not the same as checking if node a is connected to node b.
/// That should be done using a.ContainsConnection(b)
/// </summary>
public virtual bool IsValidConnection (GraphNode a, GraphNode b, out float dist) {
dist = 0;
if (!a.Walkable || !b.Walkable) return false;
var dir = (Vector3)(b.position-a.position);
if (
(!Mathf.Approximately(limits.x, 0) && Mathf.Abs(dir.x) > limits.x) ||
(!Mathf.Approximately(limits.y, 0) && Mathf.Abs(dir.y) > limits.y) ||
(!Mathf.Approximately(limits.z, 0) && Mathf.Abs(dir.z) > limits.z)) {
return false;
}
dist = dir.magnitude;
if (maxDistance == 0 || dist < maxDistance) {
if (raycast) {
var ray = new Ray((Vector3)a.position, dir);
var invertRay = new Ray((Vector3)b.position, -dir);
if (use2DPhysics) {
if (thickRaycast) {
return !Physics2D.CircleCast(ray.origin, thickRaycastRadius, ray.direction, dist, mask) && !Physics2D.CircleCast(invertRay.origin, thickRaycastRadius, invertRay.direction, dist, mask);
} else {
return !Physics2D.Linecast((Vector2)(Vector3)a.position, (Vector2)(Vector3)b.position, mask) && !Physics2D.Linecast((Vector2)(Vector3)b.position, (Vector2)(Vector3)a.position, mask);
}
} else {
if (thickRaycast) {
return !Physics.SphereCast(ray, thickRaycastRadius, dist, mask) && !Physics.SphereCast(invertRay, thickRaycastRadius, dist, mask);
} else {
return !Physics.Linecast((Vector3)a.position, (Vector3)b.position, mask) && !Physics.Linecast((Vector3)b.position, (Vector3)a.position, mask);
}
}
} else {
return true;
}
}
return false;
}
#if UNITY_EDITOR
public override void OnDrawGizmos (Pathfinding.Util.RetainedGizmos gizmos, bool drawNodes) {
base.OnDrawGizmos(gizmos, drawNodes);
if (!drawNodes) return;
Gizmos.color = new Color(0.161f, 0.341f, 1f, 0.5f);
if (root != null) {
DrawChildren(this, root);
} else if (!string.IsNullOrEmpty(searchTag)) {
GameObject[] gos = GameObject.FindGameObjectsWithTag(searchTag);
for (int i = 0; i < gos.Length; i++) {
Gizmos.DrawCube(gos[i].transform.position, Vector3.one*UnityEditor.HandleUtility.GetHandleSize(gos[i].transform.position)*0.1F);
}
}
}
static void DrawChildren (PointGraph graph, Transform tr) {
foreach (Transform child in tr) {
Gizmos.DrawCube(child.position, Vector3.one*UnityEditor.HandleUtility.GetHandleSize(child.position)*0.1F);
if (graph.recursive) DrawChildren(graph, child);
}
}
#endif
protected override void PostDeserialization (GraphSerializationContext ctx) {
RebuildNodeLookup();
}
public override void RelocateNodes (Matrix4x4 deltaMatrix) {
base.RelocateNodes(deltaMatrix);
RebuildNodeLookup();
}
protected override void DeserializeSettingsCompatibility (GraphSerializationContext ctx) {
base.DeserializeSettingsCompatibility(ctx);
root = ctx.DeserializeUnityObject() as Transform;
searchTag = ctx.reader.ReadString();
maxDistance = ctx.reader.ReadSingle();
limits = ctx.DeserializeVector3();
raycast = ctx.reader.ReadBoolean();
use2DPhysics = ctx.reader.ReadBoolean();
thickRaycast = ctx.reader.ReadBoolean();
thickRaycastRadius = ctx.reader.ReadSingle();
recursive = ctx.reader.ReadBoolean();
ctx.reader.ReadBoolean(); // Deprecated field
mask = (LayerMask)ctx.reader.ReadInt32();
}
protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
// Serialize node data
if (nodes == null) ctx.writer.Write(-1);
// Length prefixed array of nodes
ctx.writer.Write(nodeCount);
for (int i = 0; i < nodeCount; i++) {
// -1 indicates a null field
if (nodes[i] == null) ctx.writer.Write(-1);
else {
ctx.writer.Write(0);
nodes[i].SerializeNode(ctx);
}
}
}
protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
int count = ctx.reader.ReadInt32();
if (count == -1) {
nodes = null;
return;
}
nodes = new PointNode[count];
nodeCount = count;
for (int i = 0; i < nodes.Length; i++) {
if (ctx.reader.ReadInt32() == -1) continue;
nodes[i] = new PointNode(active);
nodes[i].DeserializeNode(ctx);
}
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f7458b5480c614cebb219a8f7f5df111
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,4 @@
// This file has been removed from the project. Since UnityPackages cannot
// delete files, only replace them, this message is left here to prevent old
// files from causing compiler errors

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4931e76e5e5784200a904f4b59596556
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e9fd53d051f6d4130872d5c2244b2fc6

View File

@ -0,0 +1,547 @@
//#define ASTARDEBUG //"BBTree Debug" If enables, some queries to the tree will show debug lines. Turn off multithreading when using this since DrawLine calls cannot be called from a different thread
using System;
using UnityEngine;
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Axis Aligned Bounding Box Tree.
/// Holds a bounding box tree of triangles.
/// </summary>
public class BBTree : IAstarPooledObject {
/// <summary>Holds all tree nodes</summary>
BBTreeBox[] tree = null;
TriangleMeshNode[] nodeLookup = null;
int count;
int leafNodes;
const int MaximumLeafSize = 4;
public Rect Size {
get {
if (count == 0) {
return new Rect(0, 0, 0, 0);
} else {
var rect = tree[0].rect;
return Rect.MinMaxRect(rect.xmin*Int3.PrecisionFactor, rect.ymin*Int3.PrecisionFactor, rect.xmax*Int3.PrecisionFactor, rect.ymax*Int3.PrecisionFactor);
}
}
}
/// <summary>
/// Clear the tree.
/// Note that references to old nodes will still be intact so the GC cannot immediately collect them.
/// </summary>
public void Clear () {
count = 0;
leafNodes = 0;
if (tree != null) ArrayPool<BBTreeBox>.Release(ref tree);
if (nodeLookup != null) {
// Prevent memory leaks as the pool does not clear the array
for (int i = 0; i < nodeLookup.Length; i++) nodeLookup[i] = null;
ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
}
tree = ArrayPool<BBTreeBox>.Claim(0);
nodeLookup = ArrayPool<TriangleMeshNode>.Claim(0);
}
void IAstarPooledObject.OnEnterPool () {
Clear();
}
void EnsureCapacity (int c) {
if (c > tree.Length) {
var newArr = ArrayPool<BBTreeBox>.Claim(c);
tree.CopyTo(newArr, 0);
ArrayPool<BBTreeBox>.Release(ref tree);
tree = newArr;
}
}
void EnsureNodeCapacity (int c) {
if (c > nodeLookup.Length) {
var newArr = ArrayPool<TriangleMeshNode>.Claim(c);
nodeLookup.CopyTo(newArr, 0);
ArrayPool<TriangleMeshNode>.Release(ref nodeLookup);
nodeLookup = newArr;
}
}
int GetBox (IntRect rect) {
if (count >= tree.Length) EnsureCapacity(count+1);
tree[count] = new BBTreeBox(rect);
count++;
return count-1;
}
/// <summary>Rebuilds the tree using the specified nodes</summary>
public void RebuildFrom (TriangleMeshNode[] nodes) {
Clear();
if (nodes.Length == 0) return;
// We will use approximately 2N tree nodes
EnsureCapacity(Mathf.CeilToInt(nodes.Length * 2.1f));
// We will use approximately N node references
EnsureNodeCapacity(Mathf.CeilToInt(nodes.Length * 1.1f));
// This will store the order of the nodes while the tree is being built
// It turns out that it is a lot faster to do this than to actually modify
// the nodes and nodeBounds arrays (presumably since that involves shuffling
// around 20 bytes of memory (sizeof(pointer) + sizeof(IntRect)) per node
// instead of 4 bytes (sizeof(int)).
// It also means we don't have to make a copy of the nodes array since
// we do not modify it
var permutation = ArrayPool<int>.Claim(nodes.Length);
for (int i = 0; i < nodes.Length; i++) {
permutation[i] = i;
}
// Precalculate the bounds of the nodes in XZ space.
// It turns out that calculating the bounds is a bottleneck and precalculating
// the bounds makes it around 3 times faster to build a tree
var nodeBounds = ArrayPool<IntRect>.Claim(nodes.Length);
for (int i = 0; i < nodes.Length; i++) {
Int3 v0, v1, v2;
nodes[i].GetVertices(out v0, out v1, out v2);
var rect = new IntRect(v0.x, v0.z, v0.x, v0.z);
rect = rect.ExpandToContain(v1.x, v1.z);
rect = rect.ExpandToContain(v2.x, v2.z);
nodeBounds[i] = rect;
}
RebuildFromInternal(nodes, permutation, nodeBounds, 0, nodes.Length, false);
ArrayPool<int>.Release(ref permutation);
ArrayPool<IntRect>.Release(ref nodeBounds);
}
static int SplitByX (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
int mx = to;
for (int i = from; i < mx; i++) {
if (nodes[permutation[i]].position.x > divider) {
mx--;
// Swap items i and mx
var tmp = permutation[mx];
permutation[mx] = permutation[i];
permutation[i] = tmp;
i--;
}
}
return mx;
}
static int SplitByZ (TriangleMeshNode[] nodes, int[] permutation, int from, int to, int divider) {
int mx = to;
for (int i = from; i < mx; i++) {
if (nodes[permutation[i]].position.z > divider) {
mx--;
// Swap items i and mx
var tmp = permutation[mx];
permutation[mx] = permutation[i];
permutation[i] = tmp;
i--;
}
}
return mx;
}
int RebuildFromInternal (TriangleMeshNode[] nodes, int[] permutation, IntRect[] nodeBounds, int from, int to, bool odd) {
var rect = NodeBounds(permutation, nodeBounds, from, to);
int box = GetBox(rect);
if (to - from <= MaximumLeafSize) {
var nodeOffset = tree[box].nodeOffset = leafNodes*MaximumLeafSize;
EnsureNodeCapacity(nodeOffset + MaximumLeafSize);
leafNodes++;
// Assign all nodes to the array. Note that we also need clear unused slots as the array from the pool may contain any information
for (int i = 0; i < MaximumLeafSize; i++) {
nodeLookup[nodeOffset + i] = i < to - from ? nodes[permutation[from + i]] : null;
}
return box;
}
int splitIndex;
if (odd) {
// X
int divider = (rect.xmin + rect.xmax)/2;
splitIndex = SplitByX(nodes, permutation, from, to, divider);
} else {
// Y/Z
int divider = (rect.ymin + rect.ymax)/2;
splitIndex = SplitByZ(nodes, permutation, from, to, divider);
}
if (splitIndex == from || splitIndex == to) {
// All nodes were on one side of the divider
// Try to split along the other axis
if (!odd) {
// X
int divider = (rect.xmin + rect.xmax)/2;
splitIndex = SplitByX(nodes, permutation, from, to, divider);
} else {
// Y/Z
int divider = (rect.ymin + rect.ymax)/2;
splitIndex = SplitByZ(nodes, permutation, from, to, divider);
}
if (splitIndex == from || splitIndex == to) {
// All nodes were on one side of the divider
// Just pick one half
splitIndex = (from+to)/2;
}
}
tree[box].left = RebuildFromInternal(nodes, permutation, nodeBounds, from, splitIndex, !odd);
tree[box].right = RebuildFromInternal(nodes, permutation, nodeBounds, splitIndex, to, !odd);
return box;
}
/// <summary>Calculates the bounding box in XZ space of all nodes between from (inclusive) and to (exclusive)</summary>
static IntRect NodeBounds (int[] permutation, IntRect[] nodeBounds, int from, int to) {
var rect = nodeBounds[permutation[from]];
for (int j = from + 1; j < to; j++) {
var otherRect = nodeBounds[permutation[j]];
// Equivalent to rect = IntRect.Union(rect, otherRect)
// but manually inlining is approximately
// 25% faster when building an entire tree.
// This code is hot when using navmesh cutting.
rect.xmin = Math.Min(rect.xmin, otherRect.xmin);
rect.ymin = Math.Min(rect.ymin, otherRect.ymin);
rect.xmax = Math.Max(rect.xmax, otherRect.xmax);
rect.ymax = Math.Max(rect.ymax, otherRect.ymax);
}
return rect;
}
[System.Diagnostics.Conditional("ASTARDEBUG")]
static void DrawDebugRect (IntRect rect) {
Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymin), Color.white);
Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymax), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
Debug.DrawLine(new Vector3(rect.xmin, 0, rect.ymin), new Vector3(rect.xmin, 0, rect.ymax), Color.white);
Debug.DrawLine(new Vector3(rect.xmax, 0, rect.ymin), new Vector3(rect.xmax, 0, rect.ymax), Color.white);
}
[System.Diagnostics.Conditional("ASTARDEBUG")]
static void DrawDebugNode (TriangleMeshNode node, float yoffset, Color color) {
Debug.DrawLine((Vector3)node.GetVertex(1) + Vector3.up*yoffset, (Vector3)node.GetVertex(2) + Vector3.up*yoffset, color);
Debug.DrawLine((Vector3)node.GetVertex(0) + Vector3.up*yoffset, (Vector3)node.GetVertex(1) + Vector3.up*yoffset, color);
Debug.DrawLine((Vector3)node.GetVertex(2) + Vector3.up*yoffset, (Vector3)node.GetVertex(0) + Vector3.up*yoffset, color);
}
/// <summary>
/// Queries the tree for the closest node to p constrained by the NNConstraint.
/// Note that this function will only fill in the constrained node.
/// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
/// </summary>
public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, out float distance) {
distance = float.PositiveInfinity;
return QueryClosest(p, constraint, ref distance, new NNInfoInternal(null));
}
/// <summary>
/// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
/// Note that this function will only fill in the constrained node.
/// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
///
/// This method will completely ignore any Y-axis differences in positions.
/// </summary>
/// <param name="p">Point to search around</param>
/// <param name="constraint">Optionally set to constrain which nodes to return</param>
/// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
/// after this search. Will be positive infinity if no node could be found.
/// Set to positive infinity if there was no previous solution.</param>
/// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
/// Even if the search fails on this call, the solution will never be worse than previous.
/// Note that the distance parameter need to be configured with the distance for the previous result
/// otherwise it may get overwritten even though it was actually closer.</param>
public NNInfoInternal QueryClosestXZ (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
var sqrDistance = distance*distance;
var origSqrDistance = sqrDistance;
if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
SearchBoxClosestXZ(0, p, ref sqrDistance, constraint, ref previous);
// Only update the distance if the squared distance changed as otherwise #distance
// might change due to rounding errors even if no better solution was found
if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
}
return previous;
}
void SearchBoxClosestXZ (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
BBTreeBox box = tree[boxi];
if (box.IsLeaf) {
var nodes = nodeLookup;
for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
var node = nodes[box.nodeOffset+i];
// Update the NNInfo
DrawDebugNode(node, 0.2f, Color.red);
if (constraint == null || constraint.Suitable(node)) {
Vector3 closest = node.ClosestPointOnNodeXZ(p);
// XZ squared distance
float dist = (closest.x-p.x)*(closest.x-p.x)+(closest.z-p.z)*(closest.z-p.z);
// There's a theoretical case when the closest point is on the edge of a node which may cause the
// closest point's xz coordinates to not line up perfectly with p's xz coordinates even though they should
// (because floating point errors are annoying). So use a tiny margin to cover most of those cases.
const float fuzziness = 0.000001f;
if (nnInfo.constrainedNode == null || dist < closestSqrDist - fuzziness || (dist <= closestSqrDist + fuzziness && Mathf.Abs(closest.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) {
nnInfo.constrainedNode = node;
nnInfo.constClampedPosition = closest;
closestSqrDist = dist;
}
}
}
} else {
DrawDebugRect(box.rect);
int first = box.left, second = box.right;
float firstDist, secondDist;
GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
// Search children (closest box first to improve performance)
if (firstDist <= closestSqrDist) {
SearchBoxClosestXZ(first, p, ref closestSqrDist, constraint, ref nnInfo);
}
if (secondDist <= closestSqrDist) {
SearchBoxClosestXZ(second, p, ref closestSqrDist, constraint, ref nnInfo);
}
}
}
/// <summary>
/// Queries the tree for the closest node to p constrained by the NNConstraint trying to improve an existing solution.
/// Note that this function will only fill in the constrained node.
/// If you want a node not constrained by any NNConstraint, do an additional search with constraint = NNConstraint.None
/// </summary>
/// <param name="p">Point to search around</param>
/// <param name="constraint">Optionally set to constrain which nodes to return</param>
/// <param name="distance">The best distance for the previous solution. Will be updated with the best distance
/// after this search. Will be positive infinity if no node could be found.
/// Set to positive infinity if there was no previous solution.</param>
/// <param name="previous">This search will start from the previous NNInfo and improve it if possible.
/// Even if the search fails on this call, the solution will never be worse than previous.</param>
public NNInfoInternal QueryClosest (Vector3 p, NNConstraint constraint, ref float distance, NNInfoInternal previous) {
var sqrDistance = distance*distance;
var origSqrDistance = sqrDistance;
if (count > 0 && SquaredRectPointDistance(tree[0].rect, p) < sqrDistance) {
SearchBoxClosest(0, p, ref sqrDistance, constraint, ref previous);
// Only update the distance if the squared distance changed as otherwise #distance
// might change due to rounding errors even if no better solution was found
if (sqrDistance < origSqrDistance) distance = Mathf.Sqrt(sqrDistance);
}
return previous;
}
void SearchBoxClosest (int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) {
BBTreeBox box = tree[boxi];
if (box.IsLeaf) {
var nodes = nodeLookup;
for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
var node = nodes[box.nodeOffset+i];
Vector3 closest = node.ClosestPointOnNode(p);
float dist = (closest-p).sqrMagnitude;
if (dist < closestSqrDist) {
DrawDebugNode(node, 0.2f, Color.red);
if (constraint == null || constraint.Suitable(node)) {
// Update the NNInfo
nnInfo.constrainedNode = node;
nnInfo.constClampedPosition = closest;
closestSqrDist = dist;
}
} else {
DrawDebugNode(node, 0.0f, Color.blue);
}
}
} else {
DrawDebugRect(box.rect);
int first = box.left, second = box.right;
float firstDist, secondDist;
GetOrderedChildren(ref first, ref second, out firstDist, out secondDist, p);
// Search children (closest box first to improve performance)
if (firstDist < closestSqrDist) {
SearchBoxClosest(first, p, ref closestSqrDist, constraint, ref nnInfo);
}
if (secondDist < closestSqrDist) {
SearchBoxClosest(second, p, ref closestSqrDist, constraint, ref nnInfo);
}
}
}
/// <summary>Orders the box indices first and second by the approximate distance to the point p</summary>
void GetOrderedChildren (ref int first, ref int second, out float firstDist, out float secondDist, Vector3 p) {
firstDist = SquaredRectPointDistance(tree[first].rect, p);
secondDist = SquaredRectPointDistance(tree[second].rect, p);
if (secondDist < firstDist) {
// Swap
var tmp = first;
first = second;
second = tmp;
var tmp2 = firstDist;
firstDist = secondDist;
secondDist = tmp2;
}
}
/// <summary>
/// Searches for a node which contains the specified point.
/// If there are multiple nodes that contain the point any one of them
/// may be returned.
///
/// See: TriangleMeshNode.ContainsPoint
/// </summary>
public TriangleMeshNode QueryInside (Vector3 p, NNConstraint constraint) {
return count != 0 && tree[0].Contains(p) ? SearchBoxInside(0, p, constraint) : null;
}
TriangleMeshNode SearchBoxInside (int boxi, Vector3 p, NNConstraint constraint) {
BBTreeBox box = tree[boxi];
if (box.IsLeaf) {
var nodes = nodeLookup;
for (int i = 0; i < MaximumLeafSize && nodes[box.nodeOffset+i] != null; i++) {
var node = nodes[box.nodeOffset+i];
if (node.ContainsPoint((Int3)p)) {
DrawDebugNode(node, 0.2f, Color.red);
if (constraint == null || constraint.Suitable(node)) {
return node;
}
} else {
DrawDebugNode(node, 0.0f, Color.blue);
}
}
} else {
DrawDebugRect(box.rect);
//Search children
if (tree[box.left].Contains(p)) {
var result = SearchBoxInside(box.left, p, constraint);
if (result != null) return result;
}
if (tree[box.right].Contains(p)) {
var result = SearchBoxInside(box.right, p, constraint);
if (result != null) return result;
}
}
return null;
}
struct BBTreeBox {
public IntRect rect;
public int nodeOffset;
public int left, right;
public bool IsLeaf {
get {
return nodeOffset >= 0;
}
}
public BBTreeBox (IntRect rect) {
nodeOffset = -1;
this.rect = rect;
left = right = -1;
}
public BBTreeBox (int nodeOffset, IntRect rect) {
this.nodeOffset = nodeOffset;
this.rect = rect;
left = right = -1;
}
public bool Contains (Vector3 point) {
var pi = (Int3)point;
return rect.Contains(pi.x, pi.z);
}
}
public void OnDrawGizmos () {
Gizmos.color = new Color(1, 1, 1, 0.5F);
if (count == 0) return;
OnDrawGizmos(0, 0);
}
void OnDrawGizmos (int boxi, int depth) {
BBTreeBox box = tree[boxi];
var min = (Vector3) new Int3(box.rect.xmin, 0, box.rect.ymin);
var max = (Vector3) new Int3(box.rect.xmax, 0, box.rect.ymax);
Vector3 center = (min+max)*0.5F;
Vector3 size = (max-center)*2;
size = new Vector3(size.x, 1, size.z);
center.y += depth * 2;
Gizmos.color = AstarMath.IntToColor(depth, 1f);
Gizmos.DrawCube(center, size);
if (!box.IsLeaf) {
OnDrawGizmos(box.left, depth + 1);
OnDrawGizmos(box.right, depth + 1);
}
}
static bool NodeIntersectsCircle (TriangleMeshNode node, Vector3 p, float radius) {
if (float.IsPositiveInfinity(radius)) return true;
/// <summary>\bug Is not correct on the Y axis</summary>
return (p - node.ClosestPointOnNode(p)).sqrMagnitude < radius*radius;
}
/// <summary>
/// Returns true if p is within radius from r.
/// Correctly handles cases where radius is positive infinity.
/// </summary>
static bool RectIntersectsCircle (IntRect r, Vector3 p, float radius) {
if (float.IsPositiveInfinity(radius)) return true;
Vector3 po = p;
p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
// XZ squared magnitude comparison
return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z) < radius*radius;
}
/// <summary>Returns distance from p to the rectangle r</summary>
static float SquaredRectPointDistance (IntRect r, Vector3 p) {
Vector3 po = p;
p.x = Math.Max(p.x, r.xmin*Int3.PrecisionFactor);
p.x = Math.Min(p.x, r.xmax*Int3.PrecisionFactor);
p.z = Math.Max(p.z, r.ymin*Int3.PrecisionFactor);
p.z = Math.Min(p.z, r.ymax*Int3.PrecisionFactor);
// XZ squared magnitude comparison
return (p.x-po.x)*(p.x-po.x) + (p.z-po.z)*(p.z-po.z);
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3a20480c673fd40a5bd2a4cc2206dbc4
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,58 @@
#pragma warning disable 414
using System.Collections.Generic;
using UnityEngine;
namespace Pathfinding {
public enum HeuristicOptimizationMode {
None,
Random,
RandomSpreadOut,
Custom
}
/// <summary>
/// Implements heuristic optimizations.
///
/// See: heuristic-opt
/// See: Game AI Pro - Pathfinding Architecture Optimizations by Steve Rabin and Nathan R. Sturtevant
/// </summary>
[System.Serializable]
public class EuclideanEmbedding {
/// <summary>
/// If heuristic optimization should be used and how to place the pivot points.
/// See: heuristic-opt
/// See: Game AI Pro - Pathfinding Architecture Optimizations by Steve Rabin and Nathan R. Sturtevant
/// </summary>
public HeuristicOptimizationMode mode;
public int seed;
/// <summary>All children of this transform will be used as pivot points</summary>
public Transform pivotPointRoot;
public int spreadOutCount = 1;
[System.NonSerialized]
public bool dirty;
void EnsureCapacity (int index) {
}
public uint GetHeuristic (int nodeIndex1, int nodeIndex2) {
return 0;
}
public void RecalculatePivots () {
}
public void RecalculateCosts () {
dirty = false;
}
public void OnDrawGizmos () {
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3ac3213e3eeb14eef91939f5281682e6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:

View File

@ -0,0 +1,215 @@
using UnityEngine;
namespace Pathfinding.Util {
/// <summary>
/// Transforms to and from world space to a 2D movement plane.
/// The transformation is guaranteed to be purely a rotation
/// so no scale or offset is used. This interface is primarily
/// used to make it easier to write movement scripts which can
/// handle movement both in the XZ plane and in the XY plane.
///
/// See: <see cref="Pathfinding.Util.GraphTransform"/>
/// </summary>
public interface IMovementPlane {
Vector2 ToPlane(Vector3 p);
Vector2 ToPlane(Vector3 p, out float elevation);
Vector3 ToWorld(Vector2 p, float elevation = 0);
}
/// <summary>Generic 3D coordinate transformation</summary>
public interface ITransform {
Vector3 Transform(Vector3 position);
Vector3 InverseTransform(Vector3 position);
}
/// <summary>
/// Defines a transformation from graph space to world space.
/// This is essentially just a simple wrapper around a matrix, but it has several utilities that are useful.
/// </summary>
public class GraphTransform : IMovementPlane, ITransform {
/// <summary>True if this transform is the identity transform (i.e it does not do anything)</summary>
public readonly bool identity;
/// <summary>True if this transform is a pure translation without any scaling or rotation</summary>
public readonly bool onlyTranslational;
readonly bool isXY;
readonly bool isXZ;
readonly Matrix4x4 matrix;
readonly Matrix4x4 inverseMatrix;
readonly Vector3 up;
readonly Vector3 translation;
readonly Int3 i3translation;
readonly Quaternion rotation;
readonly Quaternion inverseRotation;
public static readonly GraphTransform identityTransform = new GraphTransform(Matrix4x4.identity);
public GraphTransform (Matrix4x4 matrix) {
this.matrix = matrix;
inverseMatrix = matrix.inverse;
identity = matrix.isIdentity;
onlyTranslational = MatrixIsTranslational(matrix);
up = matrix.MultiplyVector(Vector3.up).normalized;
translation = matrix.MultiplyPoint3x4(Vector3.zero);
i3translation = (Int3)translation;
// Extract the rotation from the matrix. This is only correct if the matrix has no skew, but we only
// want to use it for the movement plane so as long as the Up axis is parpendicular to the Forward
// axis everything should be ok. In fact the only case in the project when all three axes are not
// perpendicular is when hexagon or isometric grid graphs are used, but in those cases only the
// X and Z axes are not perpendicular.
rotation = Quaternion.LookRotation(TransformVector(Vector3.forward), TransformVector(Vector3.up));
inverseRotation = Quaternion.Inverse(rotation);
// Some short circuiting code for the movement plane calculations
isXY = rotation == Quaternion.Euler(-90, 0, 0);
isXZ = rotation == Quaternion.Euler(0, 0, 0);
}
public Vector3 WorldUpAtGraphPosition (Vector3 point) {
return up;
}
static bool MatrixIsTranslational (Matrix4x4 matrix) {
return matrix.GetColumn(0) == new Vector4(1, 0, 0, 0) && matrix.GetColumn(1) == new Vector4(0, 1, 0, 0) && matrix.GetColumn(2) == new Vector4(0, 0, 1, 0) && matrix.m33 == 1;
}
public Vector3 Transform (Vector3 point) {
if (onlyTranslational) return point + translation;
return matrix.MultiplyPoint3x4(point);
}
public Vector3 TransformVector (Vector3 point) {
if (onlyTranslational) return point;
return matrix.MultiplyVector(point);
}
public void Transform (Int3[] arr) {
if (onlyTranslational) {
for (int i = arr.Length - 1; i >= 0; i--) arr[i] += i3translation;
} else {
for (int i = arr.Length - 1; i >= 0; i--) arr[i] = (Int3)matrix.MultiplyPoint3x4((Vector3)arr[i]);
}
}
public void Transform (Vector3[] arr) {
if (onlyTranslational) {
for (int i = arr.Length - 1; i >= 0; i--) arr[i] += translation;
} else {
for (int i = arr.Length - 1; i >= 0; i--) arr[i] = matrix.MultiplyPoint3x4(arr[i]);
}
}
public Vector3 InverseTransform (Vector3 point) {
if (onlyTranslational) return point - translation;
return inverseMatrix.MultiplyPoint3x4(point);
}
public Int3 InverseTransform (Int3 point) {
if (onlyTranslational) return point - i3translation;
return (Int3)inverseMatrix.MultiplyPoint3x4((Vector3)point);
}
public void InverseTransform (Int3[] arr) {
for (int i = arr.Length - 1; i >= 0; i--) arr[i] = (Int3)inverseMatrix.MultiplyPoint3x4((Vector3)arr[i]);
}
public static GraphTransform operator * (GraphTransform lhs, Matrix4x4 rhs) {
return new GraphTransform(lhs.matrix * rhs);
}
public static GraphTransform operator * (Matrix4x4 lhs, GraphTransform rhs) {
return new GraphTransform(lhs * rhs.matrix);
}
public Bounds Transform (Bounds bounds) {
if (onlyTranslational) return new Bounds(bounds.center + translation, bounds.size);
var corners = ArrayPool<Vector3>.Claim(8);
var extents = bounds.extents;
corners[0] = Transform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
corners[1] = Transform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
corners[2] = Transform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
corners[3] = Transform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
corners[4] = Transform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
corners[5] = Transform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
corners[6] = Transform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
corners[7] = Transform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
var min = corners[0];
var max = corners[0];
for (int i = 1; i < 8; i++) {
min = Vector3.Min(min, corners[i]);
max = Vector3.Max(max, corners[i]);
}
ArrayPool<Vector3>.Release(ref corners);
return new Bounds((min+max)*0.5f, max - min);
}
public Bounds InverseTransform (Bounds bounds) {
if (onlyTranslational) return new Bounds(bounds.center - translation, bounds.size);
var corners = ArrayPool<Vector3>.Claim(8);
var extents = bounds.extents;
corners[0] = InverseTransform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
corners[1] = InverseTransform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
corners[2] = InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
corners[3] = InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
corners[4] = InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
corners[5] = InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
corners[6] = InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
corners[7] = InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
var min = corners[0];
var max = corners[0];
for (int i = 1; i < 8; i++) {
min = Vector3.Min(min, corners[i]);
max = Vector3.Max(max, corners[i]);
}
ArrayPool<Vector3>.Release(ref corners);
return new Bounds((min+max)*0.5f, max - min);
}
#region IMovementPlane implementation
/// <summary>
/// Transforms from world space to the 'ground' plane of the graph.
/// The transformation is purely a rotation so no scale or offset is used.
///
/// For a graph rotated with the rotation (-90, 0, 0) this will transform
/// a coordinate (x,y,z) to (x,y). For a graph with the rotation (0,0,0)
/// this will tranform a coordinate (x,y,z) to (x,z). More generally for
/// a graph with a quaternion rotation R this will transform a vector V
/// to R * V (i.e rotate the vector V using the rotation R).
/// </summary>
Vector2 IMovementPlane.ToPlane (Vector3 point) {
// These special cases cover most graph orientations used in practice.
// Having them here improves performance in those cases by a factor of
// 2.5 without impacting the generic case in any significant way.
if (isXY) return new Vector2(point.x, point.y);
if (!isXZ) point = inverseRotation * point;
return new Vector2(point.x, point.z);
}
/// <summary>
/// Transforms from world space to the 'ground' plane of the graph.
/// The transformation is purely a rotation so no scale or offset is used.
/// </summary>
Vector2 IMovementPlane.ToPlane (Vector3 point, out float elevation) {
if (!isXZ) point = inverseRotation * point;
elevation = point.y;
return new Vector2(point.x, point.z);
}
/// <summary>
/// Transforms from the 'ground' plane of the graph to world space.
/// The transformation is purely a rotation so no scale or offset is used.
/// </summary>
Vector3 IMovementPlane.ToWorld (Vector2 point, float elevation) {
return rotation * new Vector3(point.x, elevation, point.y);
}
#endregion
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f9d3961465175430a84fd52d1bd31b05
timeCreated: 1474479722
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,192 @@
using System.Collections.Generic;
namespace Pathfinding.Util {
/// <summary>
/// Holds a lookup datastructure to quickly find objects inside rectangles.
/// Objects of type T occupy an integer rectangle in the grid and they can be
/// moved efficiently. You can query for all objects that touch a specified
/// rectangle that runs in O(m*k+r) time where m is the number of objects that
/// the query returns, k is the average number of cells that an object
/// occupies and r is the area of the rectangle query.
///
/// All objects must be contained within a rectangle with one point at the origin
/// (inclusive) and one at <see cref="size"/> (exclusive) that is specified in the constructor.
/// </summary>
public class GridLookup<T> where T : class {
Int2 size;
Item[] cells;
/// <summary>
/// Linked list of all items.
/// Note that the first item in the list is a dummy item and does not contain any data.
/// </summary>
Root all = new Root();
Dictionary<T, Root> rootLookup = new Dictionary<T, Root>();
Stack<Item> itemPool = new Stack<Item>();
public GridLookup (Int2 size) {
this.size = size;
cells = new Item[size.x*size.y];
for (int i = 0; i < cells.Length; i++) cells[i] = new Item();
}
internal class Item {
public Root root;
public Item prev, next;
}
public class Root {
/// <summary>Underlying object</summary>
public T obj;
/// <summary>Next item in the linked list of all roots</summary>
public Root next;
/// <summary>Previous item in the linked list of all roots</summary>
internal Root prev;
internal IntRect previousBounds = new IntRect(0, 0, -1, -1);
/// <summary>References to an item in each grid cell that this object is contained inside</summary>
internal List<Item> items = new List<Item>();
internal bool flag;
}
/// <summary>Linked list of all items</summary>
public Root AllItems {
get {
return all.next;
}
}
public void Clear () {
rootLookup.Clear();
all.next = null;
foreach (var item in cells) item.next = null;
}
public Root GetRoot (T item) {
Root root;
rootLookup.TryGetValue(item, out root);
return root;
}
/// <summary>
/// Add an object to the lookup data structure.
/// Returns: A handle which can be used for Move operations
/// </summary>
public Root Add (T item, IntRect bounds) {
var root = new Root {
obj = item,
prev = all,
next = all.next
};
all.next = root;
if (root.next != null) root.next.prev = root;
rootLookup.Add(item, root);
Move(item, bounds);
return root;
}
/// <summary>Removes an item from the lookup data structure</summary>
public void Remove (T item) {
Root root;
if (!rootLookup.TryGetValue(item, out root)) {
return;
}
// Make the item occupy no cells at all
Move(item, new IntRect(0, 0, -1, -1));
rootLookup.Remove(item);
root.prev.next = root.next;
if (root.next != null) root.next.prev = root.prev;
}
/// <summary>Move an object to occupy a new set of cells</summary>
public void Move (T item, IntRect bounds) {
Root root;
if (!rootLookup.TryGetValue(item, out root)) {
throw new System.ArgumentException("The item has not been added to this object");
}
var prev = root.previousBounds;
if (prev == bounds) return;
// Remove all
for (int i = 0; i < root.items.Count; i++) {
Item ob = root.items[i];
ob.prev.next = ob.next;
if (ob.next != null) ob.next.prev = ob.prev;
}
root.previousBounds = bounds;
int reusedItems = 0;
for (int z = bounds.ymin; z <= bounds.ymax; z++) {
for (int x = bounds.xmin; x <= bounds.xmax; x++) {
Item ob;
if (reusedItems < root.items.Count) {
ob = root.items[reusedItems];
} else {
ob = itemPool.Count > 0 ? itemPool.Pop() : new Item();
ob.root = root;
root.items.Add(ob);
}
reusedItems++;
ob.prev = cells[x + z*size.x];
ob.next = ob.prev.next;
ob.prev.next = ob;
if (ob.next != null) ob.next.prev = ob;
}
}
for (int i = root.items.Count-1; i >= reusedItems; i--) {
Item ob = root.items[i];
ob.root = null;
ob.next = null;
ob.prev = null;
root.items.RemoveAt(i);
itemPool.Push(ob);
}
}
/// <summary>
/// Returns all objects of a specific type inside the cells marked by the rectangle.
/// Note: For better memory usage, consider pooling the list using Pathfinding.Util.ListPool after you are done with it
/// </summary>
public List<U> QueryRect<U>(IntRect r) where U : class, T {
List<U> result = Pathfinding.Util.ListPool<U>.Claim();
// Loop through tiles and check which objects are inside them
for (int z = r.ymin; z <= r.ymax; z++) {
var zs = z*size.x;
for (int x = r.xmin; x <= r.xmax; x++) {
Item c = cells[x + zs];
// Note, first item is a dummy, so it is ignored
while (c.next != null) {
c = c.next;
var obj = c.root.obj as U;
if (!c.root.flag && obj != null) {
c.root.flag = true;
result.Add(obj);
}
}
}
}
// Reset flags
for (int z = r.ymin; z <= r.ymax; z++) {
var zs = z*size.x;
for (int x = r.xmin; x <= r.xmax; x++) {
Item c = cells[x + zs];
while (c.next != null) {
c = c.next;
c.root.flag = false;
}
}
}
return result;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7b09e5cbe5d4644c2b4ed9eed14cc13a
timeCreated: 1475417043
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,4 @@
// This file has been removed from the project. Since UnityPackages cannot
// delete files, only replace them, this message is left here to prevent old
// files from causing compiler errors

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b56789f958bf1496ba91f7e2b4147166
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,76 @@
namespace Pathfinding {
using Pathfinding.Util;
using UnityEngine;
public class NavmeshTile : INavmeshHolder {
/// <summary>Tile triangles</summary>
public int[] tris;
/// <summary>Tile vertices</summary>
public Int3[] verts;
/// <summary>Tile vertices in graph space</summary>
public Int3[] vertsInGraphSpace;
/// <summary>Tile X Coordinate</summary>
public int x;
/// <summary>Tile Z Coordinate</summary>
public int z;
/// <summary>
/// Width, in tile coordinates.
/// Warning: Widths other than 1 are not supported. This is mainly here for possible future features.
/// </summary>
public int w;
/// <summary>
/// Depth, in tile coordinates.
/// Warning: Depths other than 1 are not supported. This is mainly here for possible future features.
/// </summary>
public int d;
/// <summary>All nodes in the tile</summary>
public TriangleMeshNode[] nodes;
/// <summary>Bounding Box Tree for node lookups</summary>
public BBTree bbTree;
/// <summary>Temporary flag used for batching</summary>
public bool flag;
public NavmeshBase graph;
#region INavmeshHolder implementation
public void GetTileCoordinates (int tileIndex, out int x, out int z) {
x = this.x;
z = this.z;
}
public int GetVertexArrayIndex (int index) {
return index & NavmeshBase.VertexIndexMask;
}
/// <summary>Get a specific vertex in the tile</summary>
public Int3 GetVertex (int index) {
int idx = index & NavmeshBase.VertexIndexMask;
return verts[idx];
}
public Int3 GetVertexInGraphSpace (int index) {
return vertsInGraphSpace[index & NavmeshBase.VertexIndexMask];
}
/// <summary>Transforms coordinates from graph space to world space</summary>
public GraphTransform transform { get { return graph.transform; } }
#endregion
public void GetNodes (System.Action<GraphNode> action) {
if (nodes == null) return;
for (int i = 0; i < nodes.Length; i++) action(nodes[i]);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7408cbadf2e744d22853a92b15abede1
timeCreated: 1474405146
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,243 @@
/// <summary>
/// This is a simple utility class for importing obj files into a Unity mesh at runtime.
/// This version of ObjImporter first reads through the entire file, getting a count of how large
/// the final arrays will be, and then uses standard arrays for everything (as opposed to ArrayLists
/// or any other fancy things).
/// \author el anónimo at the UnifyCommunity wiki (at least he seems to have created the page)
/// </summary>
using UnityEngine;
using System.Collections.Generic;
using System.IO;
#if NETFX_CORE && !UNITY_EDITOR
//using MarkerMetro.Unity.WinLegacy.IO;
#endif
namespace Pathfinding {
public class ObjImporter {
private struct meshStruct {
public Vector3[] vertices;
public Vector3[] normals;
public Vector2[] uv;
public int[] triangles;
public Vector3[] faceData;
public string fileName;
}
// Use this for initialization
public static Mesh ImportFile (string filePath) {
#if NETFX_CORE
throw new System.NotSupportedException("Method not available on this platform");
#else
if (!File.Exists(filePath)) {
Debug.LogError("No file was found at '"+filePath+"'");
return null;
}
meshStruct newMesh = createMeshStruct(filePath);
populateMeshStruct(ref newMesh);
Vector3[] newVerts = new Vector3[newMesh.faceData.Length];
Vector2[] newUVs = new Vector2[newMesh.faceData.Length];
Vector3[] newNormals = new Vector3[newMesh.faceData.Length];
int i = 0;
/* The following foreach loops through the facedata and assigns the appropriate vertex, uv, or normal
* for the appropriate Unity mesh array.
*/
foreach (Vector3 v in newMesh.faceData) {
newVerts[i] = newMesh.vertices[(int)v.x - 1];
if (v.y >= 1)
newUVs[i] = newMesh.uv[(int)v.y - 1];
if (v.z >= 1)
newNormals[i] = newMesh.normals[(int)v.z - 1];
i++;
}
Mesh mesh = new Mesh();
mesh.vertices = newVerts;
mesh.uv = newUVs;
mesh.normals = newNormals;
mesh.triangles = newMesh.triangles;
mesh.RecalculateBounds();
//mesh.Optimize();
return mesh;
#endif
}
private static meshStruct createMeshStruct (string filename) {
#if NETFX_CORE
throw new System.NotSupportedException("Method not available on this platform");
#else
int triangles = 0;
int vertices = 0;
int vt = 0;
int vn = 0;
int face = 0;
meshStruct mesh = new meshStruct();
mesh.fileName = filename;
StreamReader stream = File.OpenText(filename);
string entireText = stream.ReadToEnd();
stream.Dispose();
using (StringReader reader = new StringReader(entireText))
{
string currentText = reader.ReadLine();
char[] splitIdentifier = { ' ' };
string[] brokenString;
while (currentText != null) {
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ")
&& !currentText.StartsWith("vn ")) {
currentText = reader.ReadLine();
if (currentText != null) {
currentText = currentText.Replace(" ", " ");
}
} else {
currentText = currentText.Trim(); //Trim the current line
brokenString = currentText.Split(splitIdentifier, 50); //Split the line into an array, separating the original line by blank spaces
switch (brokenString[0]) {
case "v":
vertices++;
break;
case "vt":
vt++;
break;
case "vn":
vn++;
break;
case "f":
face = face + brokenString.Length - 1;
triangles = triangles + 3 * (brokenString.Length - 2); /*brokenString.Length is 3 or greater since a face must have at least
* 3 vertices. For each additional vertice, there is an additional
* triangle in the mesh (hence this formula).*/
break;
}
currentText = reader.ReadLine();
if (currentText != null) {
currentText = currentText.Replace(" ", " ");
}
}
}
}
mesh.triangles = new int[triangles];
mesh.vertices = new Vector3[vertices];
mesh.uv = new Vector2[vt];
mesh.normals = new Vector3[vn];
mesh.faceData = new Vector3[face];
return mesh;
#endif
}
private static void populateMeshStruct (ref meshStruct mesh) {
#if NETFX_CORE
throw new System.NotSupportedException("Method not available on this platform");
#else
StreamReader stream = File.OpenText(mesh.fileName);
string entireText = stream.ReadToEnd();
stream.Close();
using (StringReader reader = new StringReader(entireText))
{
string currentText = reader.ReadLine();
char[] splitIdentifier = { ' ' };
char[] splitIdentifier2 = { '/' };
string[] brokenString;
string[] brokenBrokenString;
int f = 0;
int f2 = 0;
int v = 0;
int vn = 0;
int vt = 0;
int vt1 = 0;
int vt2 = 0;
while (currentText != null) {
if (!currentText.StartsWith("f ") && !currentText.StartsWith("v ") && !currentText.StartsWith("vt ") &&
!currentText.StartsWith("vn ") && !currentText.StartsWith("g ") && !currentText.StartsWith("usemtl ") &&
!currentText.StartsWith("mtllib ") && !currentText.StartsWith("vt1 ") && !currentText.StartsWith("vt2 ") &&
!currentText.StartsWith("vc ") && !currentText.StartsWith("usemap ")) {
currentText = reader.ReadLine();
if (currentText != null) {
currentText = currentText.Replace(" ", " ");
}
} else {
currentText = currentText.Trim();
brokenString = currentText.Split(splitIdentifier, 50);
switch (brokenString[0]) {
case "g":
break;
case "usemtl":
break;
case "usemap":
break;
case "mtllib":
break;
case "v":
mesh.vertices[v] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
System.Convert.ToSingle(brokenString[3]));
v++;
break;
case "vt":
mesh.uv[vt] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt++;
break;
case "vt1":
mesh.uv[vt1] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt1++;
break;
case "vt2":
mesh.uv[vt2] = new Vector2(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]));
vt2++;
break;
case "vn":
mesh.normals[vn] = new Vector3(System.Convert.ToSingle(brokenString[1]), System.Convert.ToSingle(brokenString[2]),
System.Convert.ToSingle(brokenString[3]));
vn++;
break;
case "vc":
break;
case "f":
int j = 1;
List<int> intArray = new List<int>();
while (j < brokenString.Length && ("" + brokenString[j]).Length > 0) {
Vector3 temp = new Vector3();
brokenBrokenString = brokenString[j].Split(splitIdentifier2, 3); //Separate the face into individual components (vert, uv, normal)
temp.x = System.Convert.ToInt32(brokenBrokenString[0]);
if (brokenBrokenString.Length > 1) { //Some .obj files skip UV and normal
if (brokenBrokenString[1] != "") { //Some .obj files skip the uv and not the normal
temp.y = System.Convert.ToInt32(brokenBrokenString[1]);
}
temp.z = System.Convert.ToInt32(brokenBrokenString[2]);
}
j++;
mesh.faceData[f2] = temp;
intArray.Add(f2);
f2++;
}
j = 1;
while (j + 2 < brokenString.Length) { //Create triangles out of the face data. There will generally be more than 1 triangle per face.
mesh.triangles[f] = intArray[0];
f++;
mesh.triangles[f] = intArray[j];
f++;
mesh.triangles[f] = intArray[j+1];
f++;
j++;
}
break;
}
currentText = reader.ReadLine();
if (currentText != null) {
currentText = currentText.Replace(" ", " "); //Some .obj files insert double spaces, this removes them.
}
}
}
}
#endif
}
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a4ed1c6c3af454395b428ad955994366
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}

View File

@ -0,0 +1,309 @@
using System.Collections.Generic;
namespace Pathfinding {
using Pathfinding.Util;
/// <summary>
/// Represents a collection of GraphNodes.
/// It allows for fast lookups of the closest node to a point.
///
/// See: https://en.wikipedia.org/wiki/K-d_tree
/// </summary>
public class PointKDTree {
// TODO: Make constant
public const int LeafSize = 10;
public const int LeafArraySize = LeafSize*2 + 1;
Node[] tree = new Node[16];
int numNodes = 0;
readonly List<GraphNode> largeList = new List<GraphNode>();
readonly Stack<GraphNode[]> arrayCache = new Stack<GraphNode[]>();
static readonly IComparer<GraphNode>[] comparers = new IComparer<GraphNode>[] { new CompareX(), new CompareY(), new CompareZ() };
struct Node {
/// <summary>Nodes in this leaf node (null if not a leaf node)</summary>
public GraphNode[] data;
/// <summary>Split point along the <see cref="splitAxis"/> if not a leaf node</summary>
public int split;
/// <summary>Number of non-null entries in <see cref="data"/></summary>
public ushort count;
/// <summary>Axis to split along if not a leaf node (x=0, y=1, z=2)</summary>
public byte splitAxis;
}
// Pretty ugly with one class for each axis, but it has been verified to make the tree around 5% faster
class CompareX : IComparer<GraphNode> {
public int Compare (GraphNode lhs, GraphNode rhs) { return lhs.position.x.CompareTo(rhs.position.x); }
}
class CompareY : IComparer<GraphNode> {
public int Compare (GraphNode lhs, GraphNode rhs) { return lhs.position.y.CompareTo(rhs.position.y); }
}
class CompareZ : IComparer<GraphNode> {
public int Compare (GraphNode lhs, GraphNode rhs) { return lhs.position.z.CompareTo(rhs.position.z); }
}
public PointKDTree() {
tree[1] = new Node { data = GetOrCreateList() };
}
/// <summary>Add the node to the tree</summary>
public void Add (GraphNode node) {
numNodes++;
Add(node, 1);
}
/// <summary>Rebuild the tree starting with all nodes in the array between index start (inclusive) and end (exclusive)</summary>
public void Rebuild (GraphNode[] nodes, int start, int end) {
if (start < 0 || end < start || end > nodes.Length)
throw new System.ArgumentException();
for (int i = 0; i < tree.Length; i++) {
var data = tree[i].data;
if (data != null) {
for (int j = 0; j < LeafArraySize; j++) data[j] = null;
arrayCache.Push(data);
tree[i].data = null;
}
}
numNodes = end - start;
Build(1, new List<GraphNode>(nodes), start, end);
}
GraphNode[] GetOrCreateList () {
// Note, the lists will never become larger than this initial capacity, so possibly they should be replaced by arrays
return arrayCache.Count > 0 ? arrayCache.Pop() : new GraphNode[LeafArraySize];
}
int Size (int index) {
return tree[index].data != null ? tree[index].count : Size(2 * index) + Size(2 * index + 1);
}
void CollectAndClear (int index, List<GraphNode> buffer) {
var nodes = tree[index].data;
var count = tree[index].count;
if (nodes != null) {
tree[index] = new Node();
for (int i = 0; i < count; i++) {
buffer.Add(nodes[i]);
nodes[i] = null;
}
arrayCache.Push(nodes);
} else {
CollectAndClear(index*2, buffer);
CollectAndClear(index*2 + 1, buffer);
}
}
static int MaxAllowedSize (int numNodes, int depth) {
// Allow a node to be 2.5 times as full as it should ideally be
// but do not allow it to contain more than 3/4ths of the total number of nodes
// (important to make sure nodes near the top of the tree also get rebalanced).
// A node should ideally contain numNodes/(2^depth) nodes below it (^ is exponentiation, not xor)
return System.Math.Min(((5 * numNodes) / 2) >> depth, (3 * numNodes) / 4);
}
void Rebalance (int index) {
CollectAndClear(index, largeList);
Build(index, largeList, 0, largeList.Count);
largeList.ClearFast();
}
void EnsureSize (int index) {
if (index >= tree.Length) {
var newLeaves = new Node[System.Math.Max(index + 1, tree.Length*2)];
tree.CopyTo(newLeaves, 0);
tree = newLeaves;
}
}
void Build (int index, List<GraphNode> nodes, int start, int end) {
EnsureSize(index);
if (end - start <= LeafSize) {
var leafData = tree[index].data = GetOrCreateList();
tree[index].count = (ushort)(end - start);
for (int i = start; i < end; i++)
leafData[i - start] = nodes[i];
} else {
Int3 mn, mx;
mn = mx = nodes[start].position;
for (int i = start; i < end; i++) {
var p = nodes[i].position;
mn = new Int3(System.Math.Min(mn.x, p.x), System.Math.Min(mn.y, p.y), System.Math.Min(mn.z, p.z));
mx = new Int3(System.Math.Max(mx.x, p.x), System.Math.Max(mx.y, p.y), System.Math.Max(mx.z, p.z));
}
Int3 diff = mx - mn;
var axis = diff.x > diff.y ? (diff.x > diff.z ? 0 : 2) : (diff.y > diff.z ? 1 : 2);
nodes.Sort(start, end - start, comparers[axis]);
int mid = (start+end)/2;
tree[index].split = (nodes[mid-1].position[axis] + nodes[mid].position[axis] + 1)/2;
tree[index].splitAxis = (byte)axis;
Build(index*2 + 0, nodes, start, mid);
Build(index*2 + 1, nodes, mid, end);
}
}
void Add (GraphNode point, int index, int depth = 0) {
// Move down in the tree until the leaf node is found that this point is inside of
while (tree[index].data == null) {
index = 2 * index + (point.position[tree[index].splitAxis] < tree[index].split ? 0 : 1);
depth++;
}
// Add the point to the leaf node
tree[index].data[tree[index].count++] = point;
// Check if the leaf node is large enough that we need to do some rebalancing
if (tree[index].count >= LeafArraySize) {
int levelsUp = 0;
// Search upwards for nodes that are too large and should be rebalanced
// Rebalance the node above the node that had a too large size so that it can
// move children over to the sibling
while (depth - levelsUp > 0 && Size(index >> levelsUp) > MaxAllowedSize(numNodes, depth-levelsUp)) {
levelsUp++;
}
Rebalance(index >> levelsUp);
}
}
/// <summary>Closest node to the point which satisfies the constraint</summary>
public GraphNode GetNearest (Int3 point, NNConstraint constraint) {
GraphNode best = null;
long bestSqrDist = long.MaxValue;
GetNearestInternal(1, point, constraint, ref best, ref bestSqrDist);
return best;
}
void GetNearestInternal (int index, Int3 point, NNConstraint constraint, ref GraphNode best, ref long bestSqrDist) {
var data = tree[index].data;
if (data != null) {
for (int i = tree[index].count - 1; i >= 0; i--) {
var dist = (data[i].position - point).sqrMagnitudeLong;
if (dist < bestSqrDist && (constraint == null || constraint.Suitable(data[i]))) {
bestSqrDist = dist;
best = data[i];
}
}
} else {
var dist = (long)(point[tree[index].splitAxis] - tree[index].split);
var childIndex = 2 * index + (dist < 0 ? 0 : 1);
GetNearestInternal(childIndex, point, constraint, ref best, ref bestSqrDist);
// Try the other one if it is possible to find a valid node on the other side
if (dist*dist < bestSqrDist) {
// childIndex ^ 1 will flip the last bit, so if childIndex is odd, then childIndex ^ 1 will be even
GetNearestInternal(childIndex ^ 0x1, point, constraint, ref best, ref bestSqrDist);
}
}
}
/// <summary>Closest node to the point which satisfies the constraint</summary>
public GraphNode GetNearestConnection (Int3 point, NNConstraint constraint, long maximumSqrConnectionLength) {
GraphNode best = null;
long bestSqrDist = long.MaxValue;
// Given a found point at a distance of r world units
// then any node that has a connection on which a closer point lies must have a squared distance lower than
// d^2 < (maximumConnectionLength/2)^2 + r^2
// Note: (x/2)^2 = (x^2)/4
// Note: (x+3)/4 to round up
long offset = (maximumSqrConnectionLength+3)/4;
GetNearestConnectionInternal(1, point, constraint, ref best, ref bestSqrDist, offset);
return best;
}
void GetNearestConnectionInternal (int index, Int3 point, NNConstraint constraint, ref GraphNode best, ref long bestSqrDist, long distanceThresholdOffset) {
var data = tree[index].data;
if (data != null) {
var pointv3 = (UnityEngine.Vector3)point;
for (int i = tree[index].count - 1; i >= 0; i--) {
var dist = (data[i].position - point).sqrMagnitudeLong;
// Note: the subtraction is important. If we used an addition on the RHS instead the result might overflow as bestSqrDist starts as long.MaxValue
if (dist - distanceThresholdOffset < bestSqrDist && (constraint == null || constraint.Suitable(data[i]))) {
// This node may contains the closest connection
// Check all connections
var conns = (data[i] as PointNode).connections;
if (conns != null) {
var nodePos = (UnityEngine.Vector3)data[i].position;
for (int j = 0; j < conns.Length; j++) {
// Find the closest point on the connection, but only on this node's side of the connection
// This ensures that we will find the closest node with the closest connection.
var connectionMidpoint = ((UnityEngine.Vector3)conns[j].node.position + nodePos) * 0.5f;
float sqrConnectionDistance = VectorMath.SqrDistancePointSegment(nodePos, connectionMidpoint, pointv3);
// Convert to Int3 space
long sqrConnectionDistanceInt = (long)(sqrConnectionDistance*Int3.FloatPrecision*Int3.FloatPrecision);
if (sqrConnectionDistanceInt < bestSqrDist) {
bestSqrDist = sqrConnectionDistanceInt;
best = data[i];
}
}
}
// Also check if the node itself is close enough.
// This is important if the node has no connections at all.
if (dist < bestSqrDist) {
bestSqrDist = dist;
best = data[i];
}
}
}
} else {
var dist = (long)(point[tree[index].splitAxis] - tree[index].split);
var childIndex = 2 * index + (dist < 0 ? 0 : 1);
GetNearestConnectionInternal(childIndex, point, constraint, ref best, ref bestSqrDist, distanceThresholdOffset);
// Try the other one if it is possible to find a valid node on the other side
// Note: the subtraction is important. If we used an addition on the RHS instead the result might overflow as bestSqrDist starts as long.MaxValue
if (dist*dist - distanceThresholdOffset < bestSqrDist) {
// childIndex ^ 1 will flip the last bit, so if childIndex is odd, then childIndex ^ 1 will be even
GetNearestConnectionInternal(childIndex ^ 0x1, point, constraint, ref best, ref bestSqrDist, distanceThresholdOffset);
}
}
}
/// <summary>Add all nodes within a squared distance of the point to the buffer.</summary>
/// <param name="point">Nodes around this point will be added to the buffer.</param>
/// <param name="sqrRadius">squared maximum distance in Int3 space. If you are converting from world space you will need to multiply by Int3.Precision:
/// <code> var sqrRadius = (worldSpaceRadius * Int3.Precision) * (worldSpaceRadius * Int3.Precision); </code></param>
/// <param name="buffer">All nodes will be added to this list.</param>
public void GetInRange (Int3 point, long sqrRadius, List<GraphNode> buffer) {
GetInRangeInternal(1, point, sqrRadius, buffer);
}
void GetInRangeInternal (int index, Int3 point, long sqrRadius, List<GraphNode> buffer) {
var data = tree[index].data;
if (data != null) {
for (int i = tree[index].count - 1; i >= 0; i--) {
var dist = (data[i].position - point).sqrMagnitudeLong;
if (dist < sqrRadius) {
buffer.Add(data[i]);
}
}
} else {
var dist = (long)(point[tree[index].splitAxis] - tree[index].split);
// Pick the first child to enter based on which side of the splitting line the point is
var childIndex = 2 * index + (dist < 0 ? 0 : 1);
GetInRangeInternal(childIndex, point, sqrRadius, buffer);
// Try the other one if it is possible to find a valid node on the other side
if (dist*dist < sqrRadius) {
// childIndex ^ 1 will flip the last bit, so if childIndex is odd, then childIndex ^ 1 will be even
GetInRangeInternal(childIndex ^ 0x1, point, sqrRadius, buffer);
}
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4aef007a0dd474c20872caa35fbbc8a7
timeCreated: 1462714767
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: