SamsonGame/Assets/Sources/Lean/Common/Extras/LeanPath.cs

404 lines
8.6 KiB
C#

using UnityEngine;
using System.Collections.Generic;
namespace Lean.Common
{
/// <summary>This component stores a list of points that form a path.</summary>
[ExecuteInEditMode]
[HelpURL(LeanHelper.HelpUrlPrefix + "LeanPath")]
[AddComponentMenu(LeanHelper.ComponentPathPrefix + "Path")]
public class LeanPath : MonoBehaviour
{
/// <summary>The points along the path.</summary>
[Tooltip("The points along the path.")]
public List<Vector3> Points;
/// <summary>Do these points loop back to the start?</summary>
[Tooltip("Do these points loop back to the start?")]
public bool Loop;
/// <summary>The coordinate system for the points.</summary>
[Tooltip("The coordinate system for the points.")]
public Space Space = Space.Self;
/// <summary>The amount of lines between each path point when read from LeanScreenDepth.</summary>
[Tooltip("The amount of lines between each path point when read from LeanScreenDepth.")]
public int Smoothing = 1;
/// <summary>This allows you to draw a visual of the path using a <b>LineRenderer</b>.</summary>
[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<Vector3>();
}
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
}
}