diff options
author | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
commit | 8722a9920c1f6119bf6e769cba270e63097f8e25 (patch) | |
tree | 2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs | |
parent | 3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff) |
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs | 797 |
1 files changed, 797 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs new file mode 100644 index 0000000..268a34e --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/AI/RichPath.cs @@ -0,0 +1,797 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; +using UnityEngine.Assertions; + +namespace Pathfinding { + public class RichPath { + int currentPart; + readonly List<RichPathPart> parts = new List<RichPathPart>(); + + public Seeker seeker; + + /// <summary> + /// Transforms points from path space to world space. + /// If null the identity transform will be used. + /// + /// This is used when the world position of the agent does not match the + /// corresponding position on the graph. This is the case in the example + /// scene called 'Moving'. + /// + /// See: <see cref="Pathfinding.Examples.LocalSpaceRichAI"/> + /// </summary> + public ITransform transform; + + public RichPath () { + Clear(); + } + + public void Clear () { + parts.Clear(); + currentPart = 0; + Endpoint = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + } + + /// <summary>Use this for initialization.</summary> + /// <param name="seeker">Optionally provide in order to take tag penalties into account. May be null if you do not use a Seeker\</param> + /// <param name="path">Path to follow</param> + /// <param name="mergePartEndpoints">If true, then adjacent parts that the path is split up in will + /// try to use the same start/end points. For example when using a link on a navmesh graph + /// Instead of first following the path to the center of the node where the link is and then + /// follow the link, the path will be adjusted to go to the exact point where the link starts + /// which usually makes more sense.</param> + /// <param name="simplificationMode">The path can optionally be simplified. This can be a bit expensive for long paths.</param> + public void Initialize (Seeker seeker, Path path, bool mergePartEndpoints, bool simplificationMode) { + if (path.error) throw new System.ArgumentException("Path has an error"); + + List<GraphNode> nodes = path.path; + if (nodes.Count == 0) throw new System.ArgumentException("Path traverses no nodes"); + + this.seeker = seeker; + // Release objects back to object pool + // Yeah, I know, it's casting... but this won't be called much + for (int i = 0; i < parts.Count; i++) { + var funnelPart = parts[i] as RichFunnel; + var specialPart = parts[i] as RichSpecial; + if (funnelPart != null) ObjectPool<RichFunnel>.Release(ref funnelPart); + else if (specialPart != null) ObjectPool<RichSpecial>.Release(ref specialPart); + } + + Clear(); + + // Initialize new + Endpoint = path.vectorPath[path.vectorPath.Count-1]; + + //Break path into parts + for (int i = 0; i < nodes.Count; i++) { + if (nodes[i] is TriangleMeshNode) { + var graph = AstarData.GetGraph(nodes[i]) as NavmeshBase; + if (graph == null) throw new System.Exception("Found a TriangleMeshNode that was not in a NavmeshBase graph"); + + RichFunnel f = ObjectPool<RichFunnel>.Claim().Initialize(this, graph); + + f.funnelSimplification = simplificationMode; + + int sIndex = i; + uint currentGraphIndex = nodes[sIndex].GraphIndex; + + + for (; i < nodes.Count; i++) { + if (nodes[i].GraphIndex != currentGraphIndex && !(nodes[i] is NodeLink3Node)) { + break; + } + } + i--; + + if (sIndex == 0) { + f.exactStart = path.vectorPath[0]; + } else { + f.exactStart = (Vector3)nodes[mergePartEndpoints ? sIndex-1 : sIndex].position; + } + + if (i == nodes.Count-1) { + f.exactEnd = path.vectorPath[path.vectorPath.Count-1]; + } else { + f.exactEnd = (Vector3)nodes[mergePartEndpoints ? i+1 : i].position; + } + + f.BuildFunnelCorridor(nodes, sIndex, i); + + parts.Add(f); + } else if (nodes[i] is LinkNode nl) { + int sIndex = i; + uint currentGraphIndex = nodes[sIndex].GraphIndex; + + while (i < nodes.Count && nodes[i].GraphIndex == currentGraphIndex) i++; + i--; + + if (i - sIndex > 1) { + throw new System.Exception("NodeLink2 path length greater than two (2) nodes. " + (i - sIndex)); + } else if (i - sIndex == 0) { + // The link is a single node. + // Just ignore it. It can happen in very rare circumstances with some path types. + // For example, a RandomPath can stop at the first node of a node link, without including the other end of the link + continue; + } + Assert.AreEqual(nl.linkConcrete, (nodes[i] as LinkNode).linkConcrete); + + RichSpecial rps = ObjectPool<RichSpecial>.Claim().Initialize(nl.linkConcrete.GetTracer(nl)); + parts.Add(rps); + } else if (!(nodes[i] is PointNode)) { + // Some other graph type which we do not have support for + throw new System.InvalidOperationException("The RichAI movment script can only be used on recast/navmesh graphs. A node of type " + nodes[i].GetType().Name + " was in the path."); + } + } + } + + public Vector3 Endpoint { get; private set; } + + /// <summary>True if we have completed (called NextPart for) the last part in the path</summary> + public bool CompletedAllParts { + get { + return currentPart >= parts.Count; + } + } + + /// <summary>True if we are traversing the last part of the path</summary> + public bool IsLastPart { + get { + return currentPart >= parts.Count - 1; + } + } + + public void NextPart () { + currentPart = Mathf.Min(currentPart + 1, parts.Count); + } + + public RichPathPart GetCurrentPart () { + if (parts.Count == 0) return null; + return currentPart < parts.Count ? parts[currentPart] : parts[parts.Count - 1]; + } + + /// <summary> + /// Replaces the buffer with the remaining path. + /// See: <see cref="Pathfinding.IAstarAI.GetRemainingPath"/> + /// </summary> + public void GetRemainingPath (List<Vector3> buffer, List<PathPartWithLinkInfo> partsBuffer, Vector3 currentPosition, out bool requiresRepath) { + buffer.Clear(); + buffer.Add(currentPosition); + requiresRepath = false; + for (int i = currentPart; i < parts.Count; i++) { + var part = parts[i]; + if (part is RichFunnel funnel) { + bool lastCorner; + var startIndex = buffer.Count; + if (i != 0) buffer.Add(funnel.exactStart); + funnel.Update(i == 0 ? currentPosition : funnel.exactStart, buffer, int.MaxValue, out lastCorner, out requiresRepath); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(startIndex, buffer.Count-1)); + if (requiresRepath) { + return; + } + } else if (part is RichSpecial rs) { + // By adding all points above the link will look like just a stright line, which is reasonable + // The part's start/end indices refer to the last point in previous part and first point in the next part, respectively + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(buffer.Count-1, buffer.Count, rs.nodeLink)); + } + } + } + } + + public abstract class RichPathPart : Pathfinding.Util.IAstarPooledObject { + public abstract void OnEnterPool(); + } + + public class RichFunnel : RichPathPart { + readonly List<Vector3> left; + readonly List<Vector3> right; + List<TriangleMeshNode> nodes; + public Vector3 exactStart; + public Vector3 exactEnd; + NavmeshBase graph; + int currentNode; + Vector3 currentPosition; + int checkForDestroyedNodesCounter; + RichPath path; + int[] triBuffer = new int[3]; + + /// <summary>Post process the funnel corridor or not</summary> + public bool funnelSimplification = true; + + public RichFunnel () { + left = Pathfinding.Util.ListPool<Vector3>.Claim(); + right = Pathfinding.Util.ListPool<Vector3>.Claim(); + nodes = new List<TriangleMeshNode>(); + this.graph = null; + } + + /// <summary>Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining</summary> + public RichFunnel Initialize (RichPath path, NavmeshBase graph) { + if (graph == null) throw new System.ArgumentNullException("graph"); + if (this.graph != null) throw new System.InvalidOperationException("Trying to initialize an already initialized object. " + graph); + + this.graph = graph; + this.path = path; + return this; + } + + public override void OnEnterPool () { + left.Clear(); + right.Clear(); + nodes.Clear(); + graph = null; + currentNode = 0; + checkForDestroyedNodesCounter = 0; + } + + public TriangleMeshNode CurrentNode { + get { + var node = nodes[currentNode]; + if (!node.Destroyed) { + return node; + } + return null; + } + } + + /// <summary> + /// Build a funnel corridor from a node list slice. + /// The nodes are assumed to be of type TriangleMeshNode. + /// </summary> + /// <param name="nodes">Nodes to build the funnel corridor from</param> + /// <param name="start">Start index in the nodes list</param> + /// <param name="end">End index in the nodes list, this index is inclusive</param> + public void BuildFunnelCorridor (List<GraphNode> nodes, int start, int end) { + //Make sure start and end points are on the correct nodes + exactStart = (nodes[start] as MeshNode).ClosestPointOnNode(exactStart); + exactEnd = (nodes[end] as MeshNode).ClosestPointOnNode(exactEnd); + + left.Clear(); + right.Clear(); + left.Add(exactStart); + right.Add(exactStart); + + this.nodes.Clear(); + + if (funnelSimplification) { + List<GraphNode> tmp = ListPool<GraphNode>.Claim(end-start); + + var tagPenalties = path.seeker != null ? path.seeker.tagPenalties : Path.ZeroTagPenalties; + var traversableTags = path.seeker != null ? path.seeker.traversableTags : -1; + + Funnel.Simplify(new Funnel.PathPart { + startIndex = start, + endIndex = end, + startPoint = exactStart, + endPoint = exactEnd, + type = Funnel.PartType.NodeSequence, + }, graph, nodes, tmp, tagPenalties, traversableTags); + + if (this.nodes.Capacity < tmp.Count) this.nodes.Capacity = tmp.Count; + + for (int i = 0; i < tmp.Count; i++) { + // Guaranteed to be TriangleMeshNodes since they are all in the same graph + var node = tmp[i] as TriangleMeshNode; + if (node != null) this.nodes.Add(node); + } + + ListPool<GraphNode>.Release(ref tmp); + } else { + if (this.nodes.Capacity < end-start) this.nodes.Capacity = (end-start); + for (int i = start; i <= end; i++) { + // Guaranteed to be TriangleMeshNodes since they are all in the same graph + var node = nodes[i] as TriangleMeshNode; + if (node != null) this.nodes.Add(node); + } + } + + for (int i = 0; i < this.nodes.Count-1; i++) { + if (this.nodes[i].GetPortal(this.nodes[i+1], out var leftP, out var rightP)) { + left.Add(leftP); + right.Add(rightP); + } else { + // Can happen in case custom connections have been added + left.Add((Vector3)this.nodes[i].position); + right.Add((Vector3)this.nodes[i].position); + left.Add((Vector3)this.nodes[i+1].position); + right.Add((Vector3)this.nodes[i+1].position); + } + } + + left.Add(exactEnd); + right.Add(exactEnd); + } + + /// <summary> + /// Split funnel at node index splitIndex and throw the nodes up to that point away and replace with prefix. + /// Used when the AI has happened to get sidetracked and entered a node outside the funnel. + /// </summary> + void UpdateFunnelCorridor (int splitIndex, List<TriangleMeshNode> prefix) { + nodes.RemoveRange(0, splitIndex); + nodes.InsertRange(0, prefix); + + left.Clear(); + right.Clear(); + left.Add(exactStart); + right.Add(exactStart); + + for (int i = 0; i < nodes.Count-1; i++) { + if (nodes[i].GetPortal(nodes[i+1], out var leftP, out var rightP)) { + left.Add(leftP); + right.Add(rightP); + } + } + + left.Add(exactEnd); + right.Add(exactEnd); + } + + /// <summary>True if any node in the path is destroyed</summary> + bool CheckForDestroyedNodes () { + // Loop through all nodes and check if they are destroyed + // If so, we really need a recalculation of our path quickly + // since there might be an obstacle blocking our path after + // a graph update or something similar + for (int i = 0, t = nodes.Count; i < t; i++) { + if (nodes[i].Destroyed) { + return true; + } + } + return false; + } + + /// <summary> + /// Approximate distance (as the crow flies) to the endpoint of this path part. + /// See: <see cref="exactEnd"/> + /// </summary> + public float DistanceToEndOfPath { + get { + var currentNode = CurrentNode; + Vector3 closestOnNode = currentNode != null? currentNode.ClosestPointOnNode(currentPosition) : currentPosition; + return (exactEnd - closestOnNode).magnitude; + } + } + + /// <summary> + /// Clamps the position to the navmesh and repairs the path if the agent has moved slightly outside it. + /// You should not call this method with anything other than the agent's position. + /// </summary> + public Vector3 ClampToNavmesh (Vector3 position) { + if (path.transform != null) position = path.transform.InverseTransform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + ClampToNavmeshInternal(ref position); + if (path.transform != null) position = path.transform.Transform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + return position; + } + + /// <summary> + /// Find the next points to move towards and clamp the position to the navmesh. + /// + /// Returns: The position of the agent clamped to make sure it is inside the navmesh. + /// </summary> + /// <param name="position">The position of the agent.</param> + /// <param name="buffer">Will be filled with up to numCorners points which are the next points in the path towards the target.</param> + /// <param name="numCorners">See buffer.</param> + /// <param name="lastCorner">True if the buffer contains the end point of the path.</param> + /// <param name="requiresRepath">True if nodes along the path have been destroyed and a path recalculation is necessary.</param> + public Vector3 Update (Vector3 position, List<Vector3> buffer, int numCorners, out bool lastCorner, out bool requiresRepath) { + if (path.transform != null) position = path.transform.InverseTransform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + + lastCorner = false; + requiresRepath = false; + + // Only check for destroyed nodes every 10 frames + if (checkForDestroyedNodesCounter >= 10) { + checkForDestroyedNodesCounter = 0; + requiresRepath |= CheckForDestroyedNodes(); + } else { + checkForDestroyedNodesCounter++; + } + + bool nodesDestroyed = ClampToNavmeshInternal(ref position); + + currentPosition = position; + + if (nodesDestroyed) { + // Some nodes on the path have been destroyed + // we need to recalculate the path immediately + requiresRepath = true; + lastCorner = false; + buffer.Add(position); + } else if (!FindNextCorners(position, currentNode, buffer, numCorners, out lastCorner)) { + Debug.LogError("Failed to find next corners in the path"); + buffer.Add(position); + } + + if (path.transform != null) { + for (int i = 0; i < buffer.Count; i++) { + buffer[i] = path.transform.Transform(buffer[i]); + } + + position = path.transform.Transform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + } + + return position; + } + + /// <summary>Cached object to avoid unnecessary allocations</summary> + static Queue<TriangleMeshNode> navmeshClampQueue = new Queue<TriangleMeshNode>(); + /// <summary>Cached object to avoid unnecessary allocations</summary> + static List<TriangleMeshNode> navmeshClampList = new List<TriangleMeshNode>(); + /// <summary>Cached object to avoid unnecessary allocations</summary> + static Dictionary<TriangleMeshNode, TriangleMeshNode> navmeshClampDict = new Dictionary<TriangleMeshNode, TriangleMeshNode>(); + + /// <summary> + /// Searches for the node the agent is inside. + /// This will also clamp the position to the navmesh + /// and repair the funnel cooridor if the agent moves slightly outside it. + /// + /// Returns: True if nodes along the path have been destroyed so that a path recalculation is required + /// </summary> + bool ClampToNavmeshInternal (ref Vector3 position) { + var previousNode = nodes[currentNode]; + + if (previousNode.Destroyed) { + return true; + } + + // Check if we are in the same node as we were in during the last frame and otherwise do a more extensive search + if (previousNode.ContainsPoint(position)) { + return false; + } + + // This part of the code is relatively seldom called + // Most of the time we are still on the same node as during the previous frame + + var que = navmeshClampQueue; + var allVisited = navmeshClampList; + var parent = navmeshClampDict; + previousNode.TemporaryFlag1 = true; + parent[previousNode] = null; + que.Enqueue(previousNode); + allVisited.Add(previousNode); + + float bestDistance = float.PositiveInfinity; + Vector3 bestPoint = position; + TriangleMeshNode bestNode = null; + + while (que.Count > 0) { + var node = que.Dequeue(); + + // Snap to the closest point in XZ space (keep the Y coordinate) + // If we would have snapped to the closest point in 3D space, the agent + // might slow down when traversing slopes + var closest = node.ClosestPointOnNodeXZ(position); + var dist = VectorMath.MagnitudeXZ(closest - position); + + // Check if this node is any closer than the previous best node. + // Allow for a small margin to both avoid floating point errors and to allow + // moving past very small local minima. + if (dist <= bestDistance * 1.05f + 0.001f) { + if (dist < bestDistance) { + bestDistance = dist; + bestPoint = closest; + bestNode = node; + } + + for (int i = 0; i < node.connections.Length; i++) { + if (!node.connections[i].isOutgoing) continue; + var neighbour = node.connections[i].node as TriangleMeshNode; + if (neighbour != null && !neighbour.TemporaryFlag1) { + neighbour.TemporaryFlag1 = true; + parent[neighbour] = node; + que.Enqueue(neighbour); + allVisited.Add(neighbour); + } + } + } + } + + UnityEngine.Assertions.Assert.IsNotNull(bestNode); + + for (int i = 0; i < allVisited.Count; i++) allVisited[i].TemporaryFlag1 = false; + allVisited.ClearFast(); + + var closestNodeInPath = nodes.IndexOf(bestNode); + + // Move the x and z coordinates of the chararacter but not the y coordinate + // because the navmesh surface may not line up with the ground + position.x = bestPoint.x; + position.z = bestPoint.z; + + // Check if the closest node + // was on the path already or if we need to adjust it + if (closestNodeInPath == -1) { + // Reuse this list, because why not. + var prefix = navmeshClampList; + + while (closestNodeInPath == -1) { + prefix.Add(bestNode); + bestNode = parent[bestNode]; + closestNodeInPath = nodes.IndexOf(bestNode); + } + + // We have found a node containing the position, but it is outside the funnel + // Recalculate the funnel to include this node + exactStart = position; + UpdateFunnelCorridor(closestNodeInPath, prefix); + + prefix.ClearFast(); + + // Restart from the first node in the updated path + currentNode = 0; + } else { + currentNode = closestNodeInPath; + } + + parent.Clear(); + // Do a quick check to see if the next node in the path has been destroyed + // If that is the case then we should plan a new path immediately + return currentNode + 1 < nodes.Count && nodes[currentNode+1].Destroyed; + } + + /// <summary> + /// Fill wallBuffer with all navmesh wall segments close to the current position. + /// A wall segment is a node edge which is not shared by any other neighbour node, i.e an outer edge on the navmesh. + /// </summary> + public void FindWalls (List<Vector3> wallBuffer, float range) { + FindWalls(currentNode, wallBuffer, currentPosition, range); + } + + void FindWalls (int nodeIndex, List<Vector3> wallBuffer, Vector3 position, float range) { + if (range <= 0) return; + + bool negAbort = false; + bool posAbort = false; + + range *= range; + + position.y = 0; + //Looping as 0,-1,1,-2,2,-3,3,-4,4 etc. Avoids code duplication by keeping it to one loop instead of two + for (int i = 0; !negAbort || !posAbort; i = i < 0 ? -i : -i-1) { + if (i < 0 && negAbort) continue; + if (i > 0 && posAbort) continue; + + if (i < 0 && nodeIndex+i < 0) { + negAbort = true; + continue; + } + + if (i > 0 && nodeIndex+i >= nodes.Count) { + posAbort = true; + continue; + } + + TriangleMeshNode prev = nodeIndex+i-1 < 0 ? null : nodes[nodeIndex+i-1]; + TriangleMeshNode node = nodes[nodeIndex+i]; + TriangleMeshNode next = nodeIndex+i+1 >= nodes.Count ? null : nodes[nodeIndex+i+1]; + + if (node.Destroyed) { + break; + } + + if ((node.ClosestPointOnNodeXZ(position)-position).sqrMagnitude > range) { + if (i < 0) negAbort = true; + else posAbort = true; + continue; + } + + for (int j = 0; j < 3; j++) triBuffer[j] = 0; + + for (int j = 0; j < node.connections.Length; j++) { + var other = node.connections[j].node as TriangleMeshNode; + if (other == null) continue; + + int va = -1; + for (int a = 0; a < 3; a++) { + for (int b = 0; b < 3; b++) { + if (node.GetVertex(a) == other.GetVertex((b+1) % 3) && node.GetVertex((a+1) % 3) == other.GetVertex(b)) { + va = a; + a = 3; + break; + } + } + } + if (va == -1) { + //No direct connection + } else { + triBuffer[va] = other == prev || other == next ? 2 : 1; + } + } + + for (int j = 0; j < 3; j++) { + //Tribuffer values + // 0 : Navmesh border, outer edge + // 1 : Inner edge, to node inside funnel + // 2 : Inner edge, to node outside funnel + if (triBuffer[j] == 0) { + //Add edge to list of walls + wallBuffer.Add((Vector3)node.GetVertex(j)); + wallBuffer.Add((Vector3)node.GetVertex((j+1) % 3)); + } + } + } + + if (path.transform != null) { + for (int i = 0; i < wallBuffer.Count; i++) { + wallBuffer[i] = path.transform.Transform(wallBuffer[i]); + } + } + } + + bool FindNextCorners (Vector3 origin, int startIndex, List<Vector3> funnelPath, int numCorners, out bool lastCorner) { + lastCorner = false; + + if (left == null) throw new System.Exception("left list is null"); + if (right == null) throw new System.Exception("right list is null"); + if (funnelPath == null) throw new System.ArgumentNullException("funnelPath"); + + if (left.Count != right.Count) throw new System.ArgumentException("left and right lists must have equal length"); + + int diagonalCount = left.Count; + + if (diagonalCount == 0) throw new System.ArgumentException("no diagonals"); + + if (diagonalCount-startIndex < 3) { + //Direct path + funnelPath.Add(left[diagonalCount-1]); + lastCorner = true; + return true; + } + +#if ASTARDEBUG + for (int i = startIndex; i < left.Count-1; i++) { + Debug.DrawLine(left[i], left[i+1], Color.red); + Debug.DrawLine(right[i], right[i+1], Color.magenta); + Debug.DrawRay(right[i], Vector3.up, Color.magenta); + } + for (int i = 0; i < left.Count; i++) { + Debug.DrawLine(right[i], left[i], Color.cyan); + } +#endif + + //Remove identical vertices + while (left[startIndex+1] == left[startIndex+2] && right[startIndex+1] == right[startIndex+2]) { + //System.Console.WriteLine ("Removing identical left and right"); + //left.RemoveAt (1); + //right.RemoveAt (1); + startIndex++; + + if (diagonalCount-startIndex <= 3) { + return false; + } + } + + Vector3 swPoint = left[startIndex+2]; + if (swPoint == left[startIndex+1]) { + swPoint = right[startIndex+2]; + } + + + //Test + while (VectorMath.IsColinearXZ(origin, left[startIndex+1], right[startIndex+1]) || VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], swPoint) == VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], origin)) { +#if ASTARDEBUG + Debug.DrawLine(left[startIndex+1], right[startIndex+1], new Color(0, 0, 0, 0.5F)); + Debug.DrawLine(origin, swPoint, new Color(0, 0, 0, 0.5F)); +#endif + //left.RemoveAt (1); + //right.RemoveAt (1); + startIndex++; + + if (diagonalCount-startIndex < 3) { + //Debug.Log ("#2 " + left.Count + " - " + startIndex + " = " + (left.Count-startIndex)); + //Direct path + funnelPath.Add(left[diagonalCount-1]); + lastCorner = true; + return true; + } + + swPoint = left[startIndex+2]; + if (swPoint == left[startIndex+1]) { + swPoint = right[startIndex+2]; + } + } + + + //funnelPath.Add (origin); + + Vector3 portalApex = origin; + Vector3 portalLeft = left[startIndex+1]; + Vector3 portalRight = right[startIndex+1]; + + int apexIndex = startIndex+0; + int rightIndex = startIndex+1; + int leftIndex = startIndex+1; + + for (int i = startIndex+2; i < diagonalCount; i++) { + if (funnelPath.Count >= numCorners) { + return true; + } + + if (funnelPath.Count > 2000) { + Debug.LogWarning("Avoiding infinite loop. Remove this check if you have this long paths."); + break; + } + + Vector3 pLeft = left[i]; + Vector3 pRight = right[i]; + + /*Debug.DrawLine (portalApex,portalLeft,Color.red); + * Debug.DrawLine (portalApex,portalRight,Color.yellow); + * Debug.DrawLine (portalApex,left,Color.cyan); + * Debug.DrawLine (portalApex,right,Color.cyan);*/ + + if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pRight) >= 0) { + if (portalApex == portalRight || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pRight) <= 0) { + portalRight = pRight; + rightIndex = i; + } else { + funnelPath.Add(portalLeft); + portalApex = portalLeft; + apexIndex = leftIndex; + + portalLeft = portalApex; + portalRight = portalApex; + + leftIndex = apexIndex; + rightIndex = apexIndex; + + i = apexIndex; + + continue; + } + } + + if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pLeft) <= 0) { + if (portalApex == portalLeft || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pLeft) >= 0) { + portalLeft = pLeft; + leftIndex = i; + } else { + funnelPath.Add(portalRight); + portalApex = portalRight; + apexIndex = rightIndex; + + portalLeft = portalApex; + portalRight = portalApex; + + leftIndex = apexIndex; + rightIndex = apexIndex; + + i = apexIndex; + + continue; + } + } + } + + lastCorner = true; + funnelPath.Add(left[diagonalCount-1]); + + return true; + } + } + + public struct FakeTransform { + public Vector3 position; + public Quaternion rotation; + } + + public class RichSpecial : RichPathPart { + public OffMeshLinks.OffMeshLinkTracer nodeLink; + public FakeTransform first => new FakeTransform { position = nodeLink.relativeStart, rotation = nodeLink.isReverse ? nodeLink.link.end.rotation : nodeLink.link.start.rotation }; + public FakeTransform second => new FakeTransform { position = nodeLink.relativeEnd, rotation = nodeLink.isReverse ? nodeLink.link.start.rotation : nodeLink.link.end.rotation }; + public bool reverse => nodeLink.isReverse; + + public override void OnEnterPool () { + nodeLink = default; + } + + /// <summary>Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining</summary> + public RichSpecial Initialize (OffMeshLinks.OffMeshLinkTracer nodeLink) { + this.nodeLink = nodeLink; + return this; + } + } +} |