using UnityEngine; using System; using System.Collections.Generic; namespace Pathfinding { /// /// Radius path modifier for offsetting paths. /// /// The radius modifier will offset the path to create the effect /// of adjusting it to the characters radius. /// It gives good results on navmeshes which have not been offset with the /// character radius during scan. Especially useful when characters with different /// radiuses are used on the same navmesh. It is also useful when using /// rvo local avoidance with the RVONavmesh since the RVONavmesh assumes the /// navmesh has not been offset with the character radius. /// /// This modifier assumes all paths are in the XZ plane (i.e Y axis is up). /// /// It is recommended to use the Funnel Modifier on the path as well. /// /// [Open online documentation to see images] /// /// See: RVONavmesh /// See: modifiers /// /// Also check out the howto page "Using Modifiers". /// /// Since: Added in 3.2.6 /// [AddComponentMenu("Pathfinding/Modifiers/Radius Offset")] [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_radius_modifier.php")] public class RadiusModifier : MonoModifier { #if UNITY_EDITOR [UnityEditor.MenuItem("CONTEXT/Seeker/Add Radius Modifier")] public static void AddComp (UnityEditor.MenuCommand command) { (command.context as Component).gameObject.AddComponent(typeof(RadiusModifier)); } #endif public override int Order { get { return 41; } } /// /// Radius of the circle segments generated. /// Usually similar to the character radius. /// public float radius = 1f; /// /// Detail of generated circle segments. /// Measured as steps per full circle. /// /// It is more performant to use a low value. /// For movement, using a high value will barely improve path quality. /// public float detail = 10; /// /// Calculates inner tangents for a pair of circles. /// /// Add a to sigma to get the first tangent angle, subtract a from sigma to get the second tangent angle. /// /// Returns: True on success. False when the circles are overlapping. /// /// Position of first circle /// Position of the second circle /// Radius of the first circle /// Radius of the second circle /// Angle from the line joining the centers of the circles to the inner tangents. /// World angle from p1 to p2 (in XZ space) bool CalculateCircleInner (Vector3 p1, Vector3 p2, float r1, float r2, out float a, out float sigma) { float dist = (p1-p2).magnitude; if (r1+r2 > dist) { a = 0; sigma = 0; return false; } a = (float)Math.Acos((r1+r2)/dist); sigma = (float)Math.Atan2(p2.z-p1.z, p2.x-p1.x); return true; } /// /// Calculates outer tangents for a pair of circles. /// /// Add a to sigma to get the first tangent angle, subtract a from sigma to get the second tangent angle. /// /// Returns: True on success. False on failure (more specifically when |r1-r2| > |p1-p2| ) /// /// Position of first circle /// Position of the second circle /// Radius of the first circle /// Radius of the second circle /// Angle from the line joining the centers of the circles to the inner tangents. /// World angle from p1 to p2 (in XZ space) bool CalculateCircleOuter (Vector3 p1, Vector3 p2, float r1, float r2, out float a, out float sigma) { float dist = (p1-p2).magnitude; if (Math.Abs(r1 - r2) > dist) { a = 0; sigma = 0; return false; } a = (float)Math.Acos((r1-r2)/dist); sigma = (float)Math.Atan2(p2.z-p1.z, p2.x-p1.x); return true; } [System.Flags] enum TangentType { OuterRight = 1 << 0, InnerRightLeft = 1 << 1, InnerLeftRight = 1 << 2, OuterLeft = 1 << 3, Outer = OuterRight | OuterLeft, Inner = InnerRightLeft | InnerLeftRight } TangentType CalculateTangentType (Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4) { bool l1 = VectorMath.RightOrColinearXZ(p1, p2, p3); bool l2 = VectorMath.RightOrColinearXZ(p2, p3, p4); return (TangentType)(1 << ((l1 ? 2 : 0) + (l2 ? 1 : 0))); } TangentType CalculateTangentTypeSimple (Vector3 p1, Vector3 p2, Vector3 p3) { bool l2 = VectorMath.RightOrColinearXZ(p1, p2, p3); bool l1 = l2; return (TangentType)(1 << ((l1 ? 2 : 0) + (l2 ? 1 : 0))); } public override void Apply (Path p) { List vs = p.vectorPath; List res = Apply(vs); if (res != vs) { Pathfinding.Util.ListPool.Release(ref p.vectorPath); p.vectorPath = res; } } float[] radi = new float[10]; float[] a1 = new float[10]; float[] a2 = new float[10]; bool[] dir = new bool[10]; /// Apply this modifier on a raw Vector3 list public List Apply (List vs) { if (vs == null || vs.Count < 3) return vs; /// TODO: Do something about these allocations if (radi.Length < vs.Count) { radi = new float[vs.Count]; a1 = new float[vs.Count]; a2 = new float[vs.Count]; dir = new bool[vs.Count]; } for (int i = 0; i < vs.Count; i++) { radi[i] = radius; } radi[0] = 0; radi[vs.Count-1] = 0; int count = 0; for (int i = 0; i < vs.Count-1; i++) { count++; if (count > 2*vs.Count) { Debug.LogWarning("Could not resolve radiuses, the path is too complex. Try reducing the base radius"); break; } TangentType tt; if (i == 0) { tt = CalculateTangentTypeSimple(vs[i], vs[i+1], vs[i+2]); } else if (i == vs.Count-2) { tt = CalculateTangentTypeSimple(vs[i-1], vs[i], vs[i+1]); } else { tt = CalculateTangentType(vs[i-1], vs[i], vs[i+1], vs[i+2]); } //DrawCircle (vs[i], radi[i], Color.yellow); if ((tt & TangentType.Inner) != 0) { //Angle to tangent float a; //Angle to other circle float sigma; //Calculate angles to the next circle and angles for the inner tangents if (!CalculateCircleInner(vs[i], vs[i+1], radi[i], radi[i+1], out a, out sigma)) { //Failed, try modifying radiuses float magn = (vs[i+1]-vs[i]).magnitude; radi[i] = magn*(radi[i]/(radi[i]+radi[i+1])); radi[i+1] = magn - radi[i]; radi[i] *= 0.99f; radi[i+1] *= 0.99f; i -= 2; continue; } if (tt == TangentType.InnerRightLeft) { a2[i] = sigma-a; a1[i+1] = sigma-a + (float)Math.PI; dir[i] = true; } else { a2[i] = sigma+a; a1[i+1] = sigma+a + (float)Math.PI; dir[i] = false; } } else { float sigma; float a; //Calculate angles to the next circle and angles for the outer tangents if (!CalculateCircleOuter(vs[i], vs[i+1], radi[i], radi[i+1], out a, out sigma)) { //Failed, try modifying radiuses if (i == vs.Count-2) { //The last circle has a fixed radius at 0, don't modify it radi[i] = (vs[i+1]-vs[i]).magnitude; radi[i] *= 0.99f; i -= 1; } else { if (radi[i] > radi[i+1]) { radi[i+1] = radi[i] - (vs[i+1]-vs[i]).magnitude; } else { radi[i+1] = radi[i] + (vs[i+1]-vs[i]).magnitude; } radi[i+1] *= 0.99f; } i -= 1; continue; } if (tt == TangentType.OuterRight) { a2[i] = sigma-a; a1[i+1] = sigma-a; dir[i] = true; } else { a2[i] = sigma+a; a1[i+1] = sigma+a; dir[i] = false; } } } List res = Pathfinding.Util.ListPool.Claim(); res.Add(vs[0]); if (detail < 1) detail = 1; float step = (float)(2*Math.PI)/detail; for (int i = 1; i < vs.Count-1; i++) { float start = a1[i]; float end = a2[i]; float rad = radi[i]; if (dir[i]) { if (end < start) end += (float)Math.PI*2; for (float t = start; t < end; t += step) { res.Add(new Vector3((float)Math.Cos(t), 0, (float)Math.Sin(t))*rad + vs[i]); } } else { if (start < end) start += (float)Math.PI*2; for (float t = start; t > end; t -= step) { res.Add(new Vector3((float)Math.Cos(t), 0, (float)Math.Sin(t))*rad + vs[i]); } } } res.Add(vs[vs.Count-1]); return res; } } }