Guest User

Untitled

a guest
Oct 15th, 2020
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.82 KB | None | 0 0
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3.  
  4. namespace Pathfinding {
  5.     using Pathfinding.Util;
  6.  
  7.     /** Simplifies a path using raycasting.
  8.      * \ingroup modifiers
  9.      * This modifier will try to remove as many nodes as possible from the path using raycasting (linecasting) to validate the node removal.
  10.      * You can use either graph raycasting or Physics.Raycast.
  11.      * When using graph raycasting, the graph will be traversed and checked for obstacles. When physics raycasting is used, the Unity physics system
  12.      * will be asked if there are any colliders which intersect the line that is currently being checked.
  13.      *
  14.      * \see https://docs.unity3d.com/ScriptReference/Physics.html
  15.      * \see #Pathfinding.IRaycastableGraph
  16.      *
  17.      * This modifier is primarily intended for grid graphs and layered grid graphs. Though depending on your game it may also be
  18.      * useful for point graphs. However note that point graphs do not have any built-in raycasting so you need to use physics raycasting for that graph.
  19.      *
  20.      * For navmesh/recast graphs the #Pathfinding.FunnelModifier is a much better and faster alternative.
  21.      *
  22.      * On grid graphs you can combine the FunnelModifier with this modifier by simply attaching both of them to a GameObject with a Seeker.
  23.      * This may or may not give you better results. It will usually follow the border of the graph more closely when they are both used
  24.      * however it more often misses some simplification opportunities.
  25.      * When both modifiers are used then the funnel modifier will run first and simplify the path, and then this modifier will take
  26.      * the output from the funnel modifier and try to simplify that even more.
  27.      *
  28.      * This modifier has several different quality levels. The highest quality is significantly slower than the
  29.      * lowest quality level (10 times slower is not unusual). So make sure you pick the lowest quality that your game can get away with.
  30.      * You can use the Unity profiler to see if it takes up any significant amount of time. It will show up under the heading "Running Path Modifiers".
  31.      *
  32.      * \shadowimage{raycast_modifier_quality.gif}
  33.      *
  34.      * \see \ref modifiers
  35.      */
  36.     [AddComponentMenu("Pathfinding/Modifiers/Raycast Modifier")]
  37.     [RequireComponent(typeof(Seeker))]
  38.     [System.Serializable]
  39.     public class RaycastModifier : MonoModifier {
  40.     #if UNITY_EDITOR
  41.         [UnityEditor.MenuItem("CONTEXT/Seeker/Add Raycast Simplifier Modifier")]
  42.         public static void AddComp (UnityEditor.MenuCommand command) {
  43.             (command.context as Component).gameObject.AddComponent(typeof(RaycastModifier));
  44.         }
  45.     #endif
  46.  
  47.         public override int Order { get { return 40; } }
  48.  
  49.         /** Use Physics.Raycast to simplify the path */
  50.         public bool useRaycasting = true;
  51.  
  52.         /** Layer mask used for physics raycasting.
  53.          * All objects with layers that are included in this mask will be treated as obstacles.
  54.          * If you are using a grid graph you usually want this to be the same as the mask in the grid graph's 'Collision Testing' settings.
  55.           */
  56.         public LayerMask mask = -1;
  57.  
  58.         /** Checks around the line between two points, not just the exact line.
  59.          * Make sure the ground is either too far below or is not inside the mask since otherwise the raycast might always hit the ground.
  60.          *
  61.          * \see https://docs.unity3d.com/ScriptReference/Physics.SphereCast.html
  62.          */
  63.         [Tooltip("Checks around the line between two points, not just the exact line.\nMake sure the ground is either too far below or is not inside the mask since otherwise the raycast might always hit the ground.")]
  64.         public bool thickRaycast;
  65.  
  66.         /** Distance from the ray which will be checked for colliders */
  67.         [Tooltip("Distance from the ray which will be checked for colliders")]
  68.         public float thickRaycastRadius;
  69.  
  70.         /** Check for intersections with 2D colliders instead of 3D colliders.
  71.          * Useful for 2D games.
  72.          *
  73.          * \see https://docs.unity3d.com/ScriptReference/Physics2D.html
  74.          */
  75.         [Tooltip("Check for intersections with 2D colliders instead of 3D colliders.")]
  76.         public bool use2DPhysics;
  77.  
  78.         /** Offset from the original positions to perform the raycast.
  79.          * Can be useful to avoid the raycast intersecting the ground or similar things you do not want to it intersect
  80.          */
  81.         [Tooltip("Offset from the original positions to perform the raycast.\nCan be useful to avoid the raycast intersecting the ground or similar things you do not want to it intersect")]
  82.         public Vector3 raycastOffset = Vector3.zero;
  83.  
  84.         /** Use raycasting on the graphs. Only currently works with GridGraph and NavmeshGraph and RecastGraph. \astarpro */
  85.         [Tooltip("Use raycasting on the graphs. Only currently works with GridGraph and NavmeshGraph and RecastGraph. This is a pro version feature.")]
  86.         public bool useGraphRaycasting;
  87.  
  88.         /** Higher quality modes will try harder to find a shorter path.
  89.          * Higher qualities may be significantly slower than low quality.
  90.          * \shadowimage{raycast_modifier_quality.gif}
  91.          */
  92.         [Tooltip("When using the high quality mode the script will try harder to find a shorter path. This is significantly slower than the greedy low quality approach.")]
  93.         public Quality quality = Quality.Medium;
  94.  
  95.         public enum Quality {
  96.             /** One iteration using a greedy algorithm */
  97.             Low,
  98.             /** Two iterations using a greedy algorithm */
  99.             Medium,
  100.             /** One iteration using a dynamic programming algorithm */
  101.             High,
  102.             /** Three iterations using a dynamic programming algorithm */
  103.             Highest
  104.         }
  105.  
  106.         static readonly int[] iterationsByQuality = new [] { 1, 2, 1, 3 };
  107.         static List<Vector3> buffer = new List<Vector3>();
  108.         static float[] DPCosts = new float[16];
  109.         static int[] DPParents = new int[16];
  110.  
  111.         Filter cachedFilter = new Filter();
  112.  
  113.         NNConstraint cachedNNConstraint = NNConstraint.None;
  114.  
  115.         class Filter {
  116.             public Path path;
  117.             public readonly System.Func<GraphNode, bool> cachedDelegate;
  118.  
  119.             public Filter() {
  120.                 cachedDelegate = this.CanTraverse;
  121.             }
  122.  
  123.             bool CanTraverse (GraphNode node) {
  124.                 return path.CanTraverse(node);
  125.             }
  126.         }
  127.  
  128.         public override void Apply (Path p) {
  129.             if (!useRaycasting && !useGraphRaycasting) return;
  130.  
  131.             var points = p.vectorPath;
  132.             cachedFilter.path = p;
  133.  
  134.             // Use the same graph mask as the path.
  135.             // We don't want to use the tag mask or other options for this though since then the linecasting will be will confused.
  136.             cachedNNConstraint.graphMask = p.nnConstraint.graphMask;
  137.  
  138.             if (ValidateLine(null, null, p.vectorPath[0], p.vectorPath[p.vectorPath.Count-1], cachedFilter.cachedDelegate, cachedNNConstraint)) {
  139.                 // A very common case is that there is a straight line to the target.
  140.                 var s = p.vectorPath[0];
  141.                 var e = p.vectorPath[p.vectorPath.Count-1];
  142.                 points.ClearFast();
  143.                 points.Add(s);
  144.                 points.Add(e);
  145.             } else {
  146.                 int iterations = iterationsByQuality[(int)quality];
  147.                 for (int it = 0; it < iterations; it++) {
  148.                     if (it != 0) {
  149.                         Polygon.Subdivide(points, buffer, 3);
  150.                         Memory.Swap(ref buffer, ref points);
  151.                         buffer.ClearFast();
  152.                         points.Reverse();
  153.                     }
  154.  
  155.                     points = quality >= Quality.High ? ApplyDP(p, points, cachedFilter.cachedDelegate, cachedNNConstraint) : ApplyGreedy(p, points, cachedFilter.cachedDelegate, cachedNNConstraint);
  156.                 }
  157.                 if ((iterations % 2) == 0) points.Reverse();
  158.             }
  159.  
  160.             p.vectorPath = points;
  161.         }
  162.  
  163.         List<Vector3> ApplyGreedy (Path p, List<Vector3> points, System.Func<GraphNode, bool> filter, NNConstraint nnConstraint) {
  164.             bool canBeOriginalNodes = points.Count == p.path.Count;
  165.             int startIndex = 0;
  166.  
  167.             while (startIndex < points.Count) {
  168.                 Vector3 start = points[startIndex];
  169.                 var startNode = canBeOriginalNodes && points[startIndex] == (Vector3)p.path[startIndex].position ? p.path[startIndex] : null;
  170.                 buffer.Add(start);
  171.  
  172.                 // Do a binary search to find the furthest node we can see from this node
  173.                 int mn = 1, mx = 2;
  174.                 while (true) {
  175.                     int endIndex = startIndex + mx;
  176.                     if (endIndex >= points.Count) {
  177.                         mx = points.Count - startIndex;
  178.                         break;
  179.                     }
  180.                     Vector3 end = points[endIndex];
  181.                     var endNode = canBeOriginalNodes && end == (Vector3)p.path[endIndex].position ? p.path[endIndex] : null;
  182.                     if (!ValidateLine(startNode, endNode, start, end, filter, nnConstraint)) break;
  183.                     mn = mx;
  184.                     mx *= 2;
  185.                 }
  186.  
  187.                 while (mn + 1 < mx) {
  188.                     int mid = (mn + mx)/2;
  189.                     int endIndex = startIndex + mid;
  190.                     Vector3 end = points[endIndex];
  191.                     var endNode = canBeOriginalNodes && end == (Vector3)p.path[endIndex].position ? p.path[endIndex] : null;
  192.  
  193.                     if (ValidateLine(startNode, endNode, start, end, filter, nnConstraint)) {
  194.                         mn = mid;
  195.                     } else {
  196.                         mx = mid;
  197.                     }
  198.                 }
  199.                 startIndex += mn;
  200.             }
  201.  
  202.             Memory.Swap(ref buffer, ref points);
  203.             buffer.ClearFast();
  204.             return points;
  205.         }
  206.  
  207.         List<Vector3> ApplyDP (Path p, List<Vector3> points, System.Func<GraphNode, bool> filter, NNConstraint nnConstraint) {
  208.             if (DPCosts.Length < points.Count) {
  209.                 DPCosts = new float[points.Count];
  210.                 DPParents = new int[points.Count];
  211.             }
  212.             for (int i = 0; i < DPParents.Length; i++) DPCosts[i] = DPParents[i] = -1;
  213.             bool canBeOriginalNodes = points.Count == p.path.Count;
  214.  
  215.             for (int i = 0; i < points.Count; i++) {
  216.                 float d = DPCosts[i];
  217.                 Vector3 start = points[i];
  218.                 var startIsOriginalNode = canBeOriginalNodes && start == (Vector3)p.path[i].position;
  219.                 for (int j = i+1; j < points.Count; j++) {
  220.                     // Total distance from the start to this point using the best simplified path
  221.                     // The small additive constant is to make sure that the number of points is kept as small as possible
  222.                     // even when the total distance is the same (which can happen with e.g multiple colinear points).
  223.                     float d2 = d + (points[j] - start).magnitude + 0.0001f;
  224.                     if (DPParents[j] == -1 || d2 < DPCosts[j]) {
  225.                         var endIsOriginalNode = canBeOriginalNodes && points[j] == (Vector3)p.path[j].position;
  226.                         if (j == i+1 || ValidateLine(startIsOriginalNode ? p.path[i] : null, endIsOriginalNode ? p.path[j] : null, start, points[j], filter, nnConstraint)) {
  227.                             DPCosts[j] = d2;
  228.                             DPParents[j] = i;
  229.                         } else {
  230.                             break;
  231.                         }
  232.                     }
  233.                 }
  234.             }
  235.  
  236.             int c = points.Count - 1;
  237.             while (c != -1) {
  238.                 buffer.Add(points[c]);
  239.                 c = DPParents[c];
  240.             }
  241.             buffer.Reverse();
  242.             Memory.Swap(ref buffer, ref points);
  243.             buffer.ClearFast();
  244.             return points;
  245.         }
  246.  
  247.         /** Check if a straight path between v1 and v2 is valid.
  248.          * If both \a n1 and \a n2 are supplied it is assumed that the line goes from the center of \a n1 to the center of \a n2 and a more optimized graph linecast may be done.
  249.          */
  250.         protected bool ValidateLine (GraphNode n1, GraphNode n2, Vector3 v1, Vector3 v2, System.Func<GraphNode, bool> filter, NNConstraint nnConstraint) {
  251.             if (useRaycasting) {
  252.                 // Use raycasting to check if a straight path between v1 and v2 is valid
  253.                 if (use2DPhysics) {
  254.                     if (thickRaycast && thickRaycastRadius > 0 && Physics2D.CircleCast(v1 + raycastOffset, thickRaycastRadius, v2 - v1, (v2 - v1).magnitude, mask)) {
  255.                         return false;
  256.                     }
  257.  
  258.                     if (Physics2D.Linecast(v1+raycastOffset, v2+raycastOffset, mask)) {
  259.                         return false;
  260.                     }
  261.                 } else {
  262.                     // Perform a normal raycast
  263.                     // This is done even if a thick raycast is also done because thick raycasts do not report collisions for
  264.                     // colliders that overlapped the (imaginary) sphere at the origin of the thick raycast.
  265.                     // If this raycast was not done then some obstacles could be missed.
  266.                     // This is done before the normal raycast for performance.
  267.                     // Normal raycasts are cheaper, so if it can be used to rule out a line earlier that's good.
  268.                     if (Physics.Linecast(v1+raycastOffset, v2+raycastOffset, mask)) {
  269.                         return false;
  270.                     }
  271.  
  272.                     // Perform a thick raycast (if enabled)
  273.                     if (thickRaycast && thickRaycastRadius > 0) {
  274.                         // Sphere cast doesn't detect collisions which are inside the start position of the sphere.
  275.                         // That's why we do an additional check sphere which is slightly ahead of the start and which will catch most
  276.                         // of these omissions. It's slightly ahead to avoid false positives that are actuall behind the agent.
  277.                         if (Physics.CheckSphere(v1 + raycastOffset + (v2 - v1).normalized * thickRaycastRadius, thickRaycastRadius, mask)) {
  278.                             return false;
  279.                         }
  280.                         if (Physics.SphereCast(new Ray(v1+raycastOffset, v2-v1), thickRaycastRadius, (v2-v1).magnitude, mask)) {
  281.                             return false;
  282.                         }
  283.                     }
  284.                 }
  285.             }
  286.  
  287.             if (useGraphRaycasting) {
  288. #if !AstarFree && !ASTAR_NO_GRID_GRAPH
  289.                 bool betweenNodeCenters = n1 != null && n2 != null;
  290. #endif
  291.                 if (n1 == null) n1 = AstarPath.active.GetNearest(v1, nnConstraint).node;
  292.                 if (n2 == null) n2 = AstarPath.active.GetNearest(v2, nnConstraint).node;
  293.  
  294.                 if (n1 != null && n2 != null) {
  295.                     // Use graph raycasting to check if a straight path between v1 and v2 is valid
  296.                     NavGraph graph = n1.Graph;
  297.                     NavGraph graph2 = n2.Graph;
  298.  
  299.                     if (graph != graph2) {
  300.                         return false;
  301.                     }
  302.  
  303.                     var rayGraph = graph as IRaycastableGraph;
  304. #if !AstarFree && !ASTAR_NO_GRID_GRAPH
  305.                     GridGraph gg = graph as GridGraph;
  306.                     if (betweenNodeCenters && gg != null) {
  307.                         // If the linecast is exactly between the centers of two nodes on a grid graph then a more optimized linecast can be used.
  308.                         // This method is also more stable when raycasting along a diagonal when the line just touches an obstacle.
  309.                         // The normal linecast method may or may not detect that as a hit depending on floating point errors
  310.                         // however this method never detect it as an obstacle (and that is very good for this component as it improves the simplification).
  311.                         return !gg.Linecast(n1 as GridNodeBase, n2 as GridNodeBase, filter);
  312.                     } else
  313. #endif
  314.                     if (rayGraph != null) {
  315.                         return !rayGraph.Linecast(v1, v2, out GraphHitInfo _, null, filter);
  316.                     }
  317.                 }
  318.             }
  319.             return true;
  320.         }
  321.     }
  322. }
  323.  
Add Comment
Please, Sign In to add comment