using UnityEngine; using System.Collections.Generic; namespace Lean.Common { /// This component stores a list of points that form a path. [ExecuteInEditMode] [HelpURL(LeanHelper.HelpUrlPrefix + "LeanPath")] [AddComponentMenu(LeanHelper.ComponentPathPrefix + "Path")] public class LeanPath : MonoBehaviour { /// The points along the path. [Tooltip("The points along the path.")] public List Points; /// Do these points loop back to the start? [Tooltip("Do these points loop back to the start?")] public bool Loop; /// The coordinate system for the points. [Tooltip("The coordinate system for the points.")] public Space Space = Space.Self; /// The amount of lines between each path point when read from LeanScreenDepth. [Tooltip("The amount of lines between each path point when read from LeanScreenDepth.")] public int Smoothing = 1; /// This allows you to draw a visual of the path using a LineRenderer. [Tooltip("This allows you to draw a visual of the path using a LineRenderer.")] public LineRenderer Visual; public static Vector3 LastWorldNormal = Vector3.forward; public int PointCount { get { if (Points != null) { var count = Points.Count; if (count >= 2) { if (Loop == true) { return count + 1; } else { return count; } } } return 0; } } public int GetPointCount(int smoothing = -1) { if (Points != null) { if (smoothing < 0) { smoothing = Smoothing; } var count = Points.Count; if (count >= 2 && smoothing >= 1) { if (Loop == true) { return count * smoothing + 1; } else { return (count - 1) * smoothing + 1; } } } return 0; } public Vector3 GetSmoothedPoint(float index) { if (Points == null) { throw new System.IndexOutOfRangeException(); } var count = Points.Count; if (count < 2) { throw new System.Exception(); } // Get int and fractional part of float index var i = (int)index; var t = Mathf.Abs(index - i); // Get 4 control points var a = GetPointRaw(i - 1, count); var b = GetPointRaw(i , count); var c = GetPointRaw(i + 1, count); var d = GetPointRaw(i + 2, count); // Interpolate and return var p = default(Vector3); p.x = CubicInterpolate(a.x, b.x, c.x, d.x, t); p.y = CubicInterpolate(a.y, b.y, c.y, d.y, t); p.z = CubicInterpolate(a.z, b.z, c.z, d.z, t); return p; } public Vector3 GetPoint(int index, int smoothing = -1) { if (Points == null) { throw new System.IndexOutOfRangeException(); } if (smoothing < 0) { smoothing = Smoothing; } if (smoothing < 1) { throw new System.ArgumentOutOfRangeException(); } var count = Points.Count; if (count < 2) { throw new System.Exception(); } if (smoothing > 0) { return GetSmoothedPoint(index / (float)smoothing); } return GetPointRaw(index, count); } private Vector3 GetPointRaw(int index, int count) { if (Loop == true) { index = Mod(index, count); } else { index = Mathf.Clamp(index, 0, count - 1); } var point = Points[index]; if (Space == Space.Self) { point = transform.TransformPoint(point); } return point; } public void SetLine(Vector3 a, Vector3 b) { if (Points == null) { Points = new List(); } else { Points.Clear(); } Points.Add(a); Points.Add(b); } public bool TryGetClosest(Vector3 position, ref Vector3 closestPoint, ref int closestIndexA, ref int closestIndexB, int smoothing = -1) { var count = GetPointCount(smoothing); if (count >= 2) { var indexA = 0; var pointA = GetPoint(indexA, smoothing); var closestDistance = float.PositiveInfinity; for (var i = 1; i < count; i++) { var indexB = i; var pointB = GetPoint(indexB, smoothing); var point = GetClosestPoint(position, pointA, pointB - pointA); var distance = Vector3.Distance(position, point); if (distance < closestDistance) { closestIndexA = indexA; closestIndexB = i; closestPoint = point; closestDistance = distance; LastWorldNormal = Vector3.Normalize(point - pointB); } pointA = pointB; indexA = indexB; } return true; } return false; } public bool TryGetClosest(Vector3 position, ref Vector3 closestPoint, int smoothing = -1) { var closestIndexA = default(int); var closestIndexB = default(int); return TryGetClosest(position, ref closestPoint, ref closestIndexA, ref closestIndexB, smoothing); } public bool TryGetClosest(Ray ray, ref Vector3 closestPoint, ref int closestIndexA, ref int closestIndexB, int smoothing = -1) { var count = GetPointCount(smoothing); if (count >= 2) { var indexA = 0; var pointA = GetPoint(0, smoothing); var closestDistance = float.PositiveInfinity; for (var i = 1; i < count; i++) { var pointB = GetPoint(i, smoothing); var point = GetClosestPoint(ray, pointA, pointB - pointA); var distance = GetClosestDistance(ray, point); if (distance < closestDistance) { closestIndexA = indexA; closestIndexB = i; closestPoint = point; closestDistance = distance; LastWorldNormal = Vector3.Normalize(point - pointB); } pointA = pointB; indexA = i; } return true; } return false; } public bool TryGetClosest(Ray ray, ref Vector3 currentPoint, int smoothing = -1) { var closestIndexA = default(int); var closestIndexB = default(int); return TryGetClosest(ray, ref currentPoint, ref closestIndexA, ref closestIndexB, smoothing); } public bool TryGetClosest(Ray ray, ref Vector3 currentPoint, int smoothing = -1, float maximumDelta = -1.0f) { if (maximumDelta > 0.0f) { var closestPoint = currentPoint; if (TryGetClosest(ray, ref closestPoint, smoothing) == true) { // Move toward closest point var targetPoint = Vector3.MoveTowards(currentPoint, closestPoint, maximumDelta); return TryGetClosest(targetPoint, ref currentPoint, smoothing); } return false; } return TryGetClosest(ray, ref currentPoint, smoothing); } private Vector3 GetClosestPoint(Vector3 position, Vector3 origin, Vector3 direction) { var denom = Vector3.Dot(direction, direction); // If the line doesn't point anywhere, return origin if (denom == 0.0f) { return origin; } var dist01 = Vector3.Dot(position - origin, direction) / denom; return origin + direction * Mathf.Clamp01(dist01); } private Vector3 GetClosestPoint(Ray ray, Vector3 origin, Vector3 direction) { var crossA = Vector3.Cross(ray.direction, direction); var denom = Vector3.Dot(crossA, crossA); // If lines are parallel, we can return any point on line if (denom == 0.0f) { return origin; } var crossB = Vector3.Cross(ray.direction, ray.origin - origin); var dist01 = Vector3.Dot(crossA, crossB) / denom; return origin + direction * Mathf.Clamp01(dist01); } private float GetClosestDistance(Ray ray, Vector3 point) { var denom = Vector3.Dot(ray.direction, ray.direction); // If the ray doesn't point anywhere, return distance from origin to point if (denom == 0.0f) { return Vector3.Distance(ray.origin, point); } var dist01 = Vector3.Dot(point - ray.origin, ray.direction) / denom; return Vector3.Distance(point, ray.GetPoint(dist01)); } private int Mod(int a, int b) { a %= b; return a < 0 ? a + b : a; } private float CubicInterpolate(float a, float b, float c, float d, float t) { var tt = t * t; var ttt = tt * t; var e = a - b; var f = d - c; var g = f - e; var h = e - g; var i = c - a; return g * ttt + h * tt + i * t + b; } public void UpdateVisual() { if (Visual != null) { var count = GetPointCount(); Visual.positionCount = count; for (var i = 0; i < count; i++) { Visual.SetPosition(i, GetPoint(i)); } } } protected virtual void Update() { UpdateVisual(); } #if UNITY_EDITOR protected virtual void OnDrawGizmosSelected() { var count = GetPointCount(); if (count >= 2) { var pointA = GetPoint(0); for (var i = 1; i < count; i++) { var pointB = GetPoint(i); Gizmos.DrawLine(pointA, pointB); pointA = pointB; } } } #endif } }