forked from BilalY/Rasagar
400 lines
19 KiB
C#
400 lines
19 KiB
C#
|
using UnityEngine;
|
||
|
using Cinemachine.Utility;
|
||
|
using System;
|
||
|
|
||
|
namespace Cinemachine
|
||
|
{
|
||
|
/// <summary>Abstract base class for a world-space path,
|
||
|
/// suitable for a camera dolly track.</summary>
|
||
|
public abstract class CinemachinePathBase : MonoBehaviour
|
||
|
{
|
||
|
/// <summary>Path samples per waypoint</summary>
|
||
|
[Tooltip("Path samples per waypoint. This is used for calculating path distances.")]
|
||
|
[Range(1, 100)]
|
||
|
public int m_Resolution = 20;
|
||
|
|
||
|
/// <summary>This class holds the settings that control how the path
|
||
|
/// will appear in the editor scene view. The path is not visible in the game view</summary>
|
||
|
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
|
||
|
[Serializable] public class Appearance
|
||
|
{
|
||
|
/// <summary>The color of the path itself when it is active in the editor</summary>
|
||
|
[Tooltip("The color of the path itself when it is active in the editor")]
|
||
|
public Color pathColor = Color.green;
|
||
|
/// <summary>The color of the path itself when it is inactive in the editor</summary>
|
||
|
[Tooltip("The color of the path itself when it is inactive in the editor")]
|
||
|
public Color inactivePathColor = Color.gray;
|
||
|
/// <summary>The width of the railroad-tracks that are drawn to represent the path</summary>
|
||
|
[Tooltip("The width of the railroad-tracks that are drawn to represent the path")]
|
||
|
[Range(0f, 10f)]
|
||
|
public float width = 0.2f;
|
||
|
}
|
||
|
/// <summary>The settings that control how the path
|
||
|
/// will appear in the editor scene view.</summary>
|
||
|
[Tooltip("The settings that control how the path will appear in the editor scene view.")]
|
||
|
public Appearance m_Appearance = new Appearance();
|
||
|
|
||
|
/// <summary>The minimum value for the path position</summary>
|
||
|
public abstract float MinPos { get; }
|
||
|
|
||
|
/// <summary>The maximum value for the path position</summary>
|
||
|
public abstract float MaxPos { get; }
|
||
|
|
||
|
/// <summary>True if the path ends are joined to form a continuous loop</summary>
|
||
|
public abstract bool Looped { get; }
|
||
|
|
||
|
/// <summary>Get a standardized path position, taking spins into account if looped</summary>
|
||
|
/// <param name="pos">Position along the path</param>
|
||
|
/// <returns>Standardized position, between MinPos and MaxPos</returns>
|
||
|
public virtual float StandardizePos(float pos)
|
||
|
{
|
||
|
if (Looped && MaxPos > 0)
|
||
|
{
|
||
|
pos = pos % MaxPos;
|
||
|
if (pos < 0)
|
||
|
pos += MaxPos;
|
||
|
return pos;
|
||
|
}
|
||
|
return Mathf.Clamp(pos, 0, MaxPos);
|
||
|
}
|
||
|
|
||
|
/// <summary>Get a worldspace position of a point along the path</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>World-space position of the point along at path at pos</returns>
|
||
|
public virtual Vector3 EvaluatePosition(float pos) => transform.TransformPoint(EvaluateLocalPosition(pos));
|
||
|
|
||
|
/// <summary>Get the world-space tangent of the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>World-space direction of the path tangent.
|
||
|
/// Length of the vector represents the tangent strength</returns>
|
||
|
public virtual Vector3 EvaluateTangent(float pos) => transform.TransformDirection(EvaluateLocalTangent(pos));
|
||
|
|
||
|
/// <summary>Get the world-space orientation the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>World-space orientation of the path</returns>
|
||
|
public virtual Quaternion EvaluateOrientation(float pos) => transform.rotation * EvaluateLocalOrientation(pos);
|
||
|
|
||
|
/// <summary>Get a worldspace position of a point along the path</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>Local-space position of the point along at path at pos</returns>
|
||
|
public abstract Vector3 EvaluateLocalPosition(float pos);
|
||
|
|
||
|
/// <summary>Get the tangent of the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>Local-space direction of the path tangent.
|
||
|
/// Length of the vector represents the tangent strength</returns>
|
||
|
public abstract Vector3 EvaluateLocalTangent(float pos);
|
||
|
|
||
|
/// <summary>Get the orientation the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be standardized.</param>
|
||
|
/// <returns>Local-space orientation of the path</returns>
|
||
|
public abstract Quaternion EvaluateLocalOrientation(float pos);
|
||
|
|
||
|
/// <summary>Find the closest point on the path to a given worldspace target point.</summary>
|
||
|
/// <remarks>Performance could be improved by checking the bounding polygon of each segment,
|
||
|
/// and only entering the best segment(s)</remarks>
|
||
|
/// <param name="p">Worldspace target that we want to approach</param>
|
||
|
/// <param name="startSegment">In what segment of the path to start the search.
|
||
|
/// A Segment is a section of path between 2 waypoints.</param>
|
||
|
/// <param name="searchRadius">How many segments on either side of the startSegment
|
||
|
/// to search. -1 means no limit, i.e. search the entire path</param>
|
||
|
/// <param name="stepsPerSegment">We search a segment by dividing it into this many
|
||
|
/// straight pieces. The higher the number, the more accurate the result, but performance
|
||
|
/// is proportionally slower for higher numbers</param>
|
||
|
/// <returns>The position along the path that is closest to the target point.
|
||
|
/// The value is in Path Units, not Distance units.</returns>
|
||
|
public virtual float FindClosestPoint(
|
||
|
Vector3 p, int startSegment, int searchRadius, int stepsPerSegment)
|
||
|
{
|
||
|
float start = MinPos;
|
||
|
float end = MaxPos;
|
||
|
if (searchRadius >= 0)
|
||
|
{
|
||
|
if (Looped)
|
||
|
{
|
||
|
var r = Mathf.Min(searchRadius, Mathf.FloorToInt((end - start) / 2f));
|
||
|
start = startSegment - r;
|
||
|
end = startSegment + r + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
start = Mathf.Max(startSegment - searchRadius, MinPos);
|
||
|
end = Mathf.Min(startSegment + searchRadius + 1, MaxPos);
|
||
|
}
|
||
|
}
|
||
|
stepsPerSegment = Mathf.RoundToInt(Mathf.Clamp(stepsPerSegment, 1f, 100f));
|
||
|
float stepSize = 1f / stepsPerSegment;
|
||
|
float bestPos = startSegment;
|
||
|
float bestDistance = float.MaxValue;
|
||
|
int iterations = (stepsPerSegment == 1) ? 1 : 3;
|
||
|
for (int i = 0; i < iterations; ++i)
|
||
|
{
|
||
|
Vector3 v0 = EvaluatePosition(start);
|
||
|
for (float f = start + stepSize; f <= end; f += stepSize)
|
||
|
{
|
||
|
Vector3 v = EvaluatePosition(f);
|
||
|
float t = p.ClosestPointOnSegment(v0, v);
|
||
|
float d = Vector3.SqrMagnitude(p - Vector3.Lerp(v0, v, t));
|
||
|
if (d < bestDistance)
|
||
|
{
|
||
|
bestDistance = d;
|
||
|
bestPos = f - (1 - t) * stepSize;
|
||
|
}
|
||
|
v0 = v;
|
||
|
}
|
||
|
start = bestPos - stepSize;
|
||
|
end = bestPos + stepSize;
|
||
|
stepSize /= stepsPerSegment;
|
||
|
}
|
||
|
return bestPos;
|
||
|
}
|
||
|
|
||
|
/// <summary>How to interpret the Path Position</summary>
|
||
|
public enum PositionUnits
|
||
|
{
|
||
|
/// <summary>Use PathPosition units, where 0 is first waypoint, 1 is second waypoint, etc</summary>
|
||
|
PathUnits,
|
||
|
/// <summary>Use Distance Along Path. Path will be sampled according to its Resolution
|
||
|
/// setting, and a distance lookup table will be cached internally</summary>
|
||
|
Distance,
|
||
|
/// <summary>Normalized units, where 0 is the start of the path, and 1 is the end.
|
||
|
/// Path will be sampled according to its Resolution
|
||
|
/// setting, and a distance lookup table will be cached internally</summary>
|
||
|
Normalized
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the minimum value, for the given unit type</summary>
|
||
|
/// <param name="units">The unit type</param>
|
||
|
/// <returns>The minimum allowable value for this path</returns>
|
||
|
public float MinUnit(PositionUnits units)
|
||
|
{
|
||
|
if (units == PositionUnits.Normalized)
|
||
|
return 0;
|
||
|
return units == PositionUnits.Distance ? 0 : MinPos;
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the maximum value, for the given unit type</summary>
|
||
|
/// <param name="units">The unit type</param>
|
||
|
/// <returns>The maximum allowable value for this path</returns>
|
||
|
public float MaxUnit(PositionUnits units)
|
||
|
{
|
||
|
if (units == PositionUnits.Normalized)
|
||
|
return 1;
|
||
|
return units == PositionUnits.Distance ? PathLength : MaxPos;
|
||
|
}
|
||
|
|
||
|
/// <summary>Standardize the unit, so that it lies between MinUmit and MaxUnit</summary>
|
||
|
/// <param name="pos">The value to be standardized</param>
|
||
|
/// <param name="units">The unit type</param>
|
||
|
/// <returns>The standardized value of pos, between MinUnit and MaxUnit</returns>
|
||
|
public virtual float StandardizeUnit(float pos, PositionUnits units)
|
||
|
{
|
||
|
if (units == PositionUnits.PathUnits)
|
||
|
return StandardizePos(pos);
|
||
|
if (units == PositionUnits.Distance)
|
||
|
return StandardizePathDistance(pos);
|
||
|
float len = PathLength;
|
||
|
if (len < UnityVectorExtensions.Epsilon)
|
||
|
return 0;
|
||
|
return StandardizePathDistance(pos * len) / len;
|
||
|
}
|
||
|
|
||
|
/// <summary>Get a worldspace position of a point along the path</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be normalized.</param>
|
||
|
/// <param name="units">The unit to use when interpreting the value of pos.</param>
|
||
|
/// <returns>World-space position of the point along at path at pos</returns>
|
||
|
public Vector3 EvaluatePositionAtUnit(float pos, PositionUnits units)
|
||
|
{
|
||
|
return EvaluatePosition(ToNativePathUnits(pos, units));
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the tangent of the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be normalized.</param>
|
||
|
/// <param name="units">The unit to use when interpreting the value of pos.</param>
|
||
|
/// <returns>World-space direction of the path tangent.
|
||
|
/// Length of the vector represents the tangent strength</returns>
|
||
|
public Vector3 EvaluateTangentAtUnit(float pos, PositionUnits units)
|
||
|
{
|
||
|
return EvaluateTangent(ToNativePathUnits(pos, units));
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the orientation the curve at a point along the path.</summary>
|
||
|
/// <param name="pos">Postion along the path. Need not be normalized.</param>
|
||
|
/// <param name="units">The unit to use when interpreting the value of pos.</param>
|
||
|
/// <returns>World-space orientation of the path</returns>
|
||
|
public Quaternion EvaluateOrientationAtUnit(float pos, PositionUnits units)
|
||
|
{
|
||
|
return EvaluateOrientation(ToNativePathUnits(pos, units));
|
||
|
}
|
||
|
|
||
|
/// <summary>When calculating the distance cache, sample the path this many
|
||
|
/// times between points</summary>
|
||
|
public abstract int DistanceCacheSampleStepsPerSegment { get; }
|
||
|
|
||
|
/// <summary>Call this if the path changes in such a way as to affect distances
|
||
|
/// or other cached path elements</summary>
|
||
|
public virtual void InvalidateDistanceCache()
|
||
|
{
|
||
|
m_DistanceToPos = null;
|
||
|
m_PosToDistance = null;
|
||
|
m_CachedSampleSteps = 0;
|
||
|
m_PathLength = 0;
|
||
|
}
|
||
|
|
||
|
/// <summary>See whether the distance cache is valid. If it's not valid,
|
||
|
/// then any call to GetPathLength() or ToNativePathUnits() will
|
||
|
/// trigger a potentially costly regeneration of the path distance cache</summary>
|
||
|
/// <returns>Whether the cache is valid</returns>
|
||
|
public bool DistanceCacheIsValid()
|
||
|
{
|
||
|
return (MaxPos == MinPos)
|
||
|
|| (m_DistanceToPos != null && m_PosToDistance != null
|
||
|
&& m_CachedSampleSteps == DistanceCacheSampleStepsPerSegment
|
||
|
&& m_CachedSampleSteps > 0);
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the length of the path in distance units.
|
||
|
/// If the distance cache is not valid, then calling this will
|
||
|
/// trigger a potentially costly regeneration of the path distance cache</summary>
|
||
|
/// <returns>The length of the path in distance units, when sampled at this rate</returns>
|
||
|
public float PathLength
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (DistanceCacheSampleStepsPerSegment < 1)
|
||
|
return 0;
|
||
|
if (!DistanceCacheIsValid())
|
||
|
ResamplePath(DistanceCacheSampleStepsPerSegment);
|
||
|
return m_PathLength;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Standardize a distance along the path based on the path length.
|
||
|
/// If the distance cache is not valid, then calling this will
|
||
|
/// trigger a potentially costly regeneration of the path distance cache</summary>
|
||
|
/// <param name="distance">The distance to standardize</param>
|
||
|
/// <returns>The standardized distance, ranging from 0 to path length</returns>
|
||
|
public float StandardizePathDistance(float distance)
|
||
|
{
|
||
|
float length = PathLength;
|
||
|
if (length < Vector3.kEpsilon)
|
||
|
return 0;
|
||
|
if (Looped)
|
||
|
{
|
||
|
distance = distance % length;
|
||
|
if (distance < 0)
|
||
|
distance += length;
|
||
|
}
|
||
|
return Mathf.Clamp(distance, 0, length);
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the path position to native path units.
|
||
|
/// If the distance cache is not valid, then calling this will
|
||
|
/// trigger a potentially costly regeneration of the path distance cache</summary>
|
||
|
/// <param name="pos">The value to convert from</param>
|
||
|
/// <param name="units">The units in which pos is expressed</param>
|
||
|
/// <returns>The path position, in native units</returns>
|
||
|
public float ToNativePathUnits(float pos, PositionUnits units)
|
||
|
{
|
||
|
if (units == PositionUnits.PathUnits)
|
||
|
return pos;
|
||
|
if (DistanceCacheSampleStepsPerSegment < 1 || PathLength < UnityVectorExtensions.Epsilon)
|
||
|
return MinPos;
|
||
|
if (units == PositionUnits.Normalized)
|
||
|
pos *= PathLength;
|
||
|
pos = StandardizePathDistance(pos);
|
||
|
float d = pos / m_cachedDistanceStepSize;
|
||
|
int i = Mathf.FloorToInt(d);
|
||
|
if (i >= m_DistanceToPos.Length-1)
|
||
|
return MaxPos;
|
||
|
float t = d - (float)i;
|
||
|
return MinPos + Mathf.Lerp(m_DistanceToPos[i], m_DistanceToPos[i+1], t);
|
||
|
}
|
||
|
|
||
|
/// <summary>Convert a path position from native path units to the desired units.
|
||
|
/// If the distance cache is not valid, then calling this will
|
||
|
/// trigger a potentially costly regeneration of the path distance cache</summary>
|
||
|
/// <param name="pos">The value to convert from, in native units</param>
|
||
|
/// <param name="units">The units to convert to</param>
|
||
|
/// <returns>Tha path position, in the requested units</returns>
|
||
|
public float FromPathNativeUnits(float pos, PositionUnits units)
|
||
|
{
|
||
|
if (units == PositionUnits.PathUnits)
|
||
|
return pos;
|
||
|
float length = PathLength;
|
||
|
if (DistanceCacheSampleStepsPerSegment < 1 || length < UnityVectorExtensions.Epsilon)
|
||
|
return 0;
|
||
|
pos = StandardizePos(pos);
|
||
|
float d = pos / m_cachedPosStepSize;
|
||
|
int i = Mathf.FloorToInt(d);
|
||
|
if (i >= m_PosToDistance.Length-1)
|
||
|
pos = m_PathLength;
|
||
|
else
|
||
|
{
|
||
|
float t = d - (float)i;
|
||
|
pos = Mathf.Lerp(m_PosToDistance[i], m_PosToDistance[i+1], t);
|
||
|
}
|
||
|
if (units == PositionUnits.Normalized)
|
||
|
pos /= length;
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
private float[] m_DistanceToPos;
|
||
|
private float[] m_PosToDistance;
|
||
|
private int m_CachedSampleSteps;
|
||
|
private float m_PathLength;
|
||
|
private float m_cachedPosStepSize;
|
||
|
private float m_cachedDistanceStepSize;
|
||
|
|
||
|
private void ResamplePath(int stepsPerSegment)
|
||
|
{
|
||
|
InvalidateDistanceCache();
|
||
|
|
||
|
float minPos = MinPos;
|
||
|
float maxPos = MaxPos;
|
||
|
float stepSize = 1f / Mathf.Max(1, stepsPerSegment);
|
||
|
|
||
|
// Sample the positions
|
||
|
int numKeys = Mathf.RoundToInt((maxPos - minPos) / stepSize) + 1;
|
||
|
m_PosToDistance = new float[numKeys];
|
||
|
m_CachedSampleSteps = stepsPerSegment;
|
||
|
m_cachedPosStepSize = stepSize;
|
||
|
|
||
|
Vector3 p0 = EvaluatePosition(0);
|
||
|
m_PosToDistance[0] = 0;
|
||
|
float pos = minPos;
|
||
|
for (int i = 1; i < numKeys; ++i)
|
||
|
{
|
||
|
pos += stepSize;
|
||
|
Vector3 p = EvaluatePosition(pos);
|
||
|
float d = Vector3.Distance(p0, p);
|
||
|
m_PathLength += d;
|
||
|
p0 = p;
|
||
|
m_PosToDistance[i] = m_PathLength;
|
||
|
}
|
||
|
|
||
|
// Resample the distances
|
||
|
m_DistanceToPos = new float[numKeys];
|
||
|
m_DistanceToPos[0] = 0;
|
||
|
if (numKeys > 1)
|
||
|
{
|
||
|
stepSize = m_PathLength / (numKeys - 1);
|
||
|
m_cachedDistanceStepSize = stepSize;
|
||
|
float distance = 0;
|
||
|
int posIndex = 1;
|
||
|
for (int i = 1; i < numKeys; ++i)
|
||
|
{
|
||
|
distance += stepSize;
|
||
|
float d = m_PosToDistance[posIndex];
|
||
|
while (d < distance && posIndex < numKeys-1)
|
||
|
d = m_PosToDistance[++posIndex];
|
||
|
float d0 = m_PosToDistance[posIndex-1];
|
||
|
float delta = d - d0;
|
||
|
float t = (distance - d0) / delta;
|
||
|
m_DistanceToPos[i] = m_cachedPosStepSize * (t + posIndex - 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|