initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Splines.Editor")]
[assembly: InternalsVisibleTo("Unity.Splines.Tests")]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ede6128040e87a146aa6430dfad44478
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,188 @@
using System;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// Control points for a cubic bezier curve.
///
/// Points P0 through P3 are in sequential order, describing the starting point, second, third, and ending controls
/// for a cubic bezier curve.
/// </summary>
public struct BezierCurve : IEquatable<BezierCurve>
{
/// <summary>
/// First control point.
/// </summary>
public float3 P0;
/// <summary>
/// Second control point.
/// Subtract <see cref="P0"/> from <see cref="P1"/> to derive the first tangent for a curve.
/// </summary>
public float3 P1;
/// <summary>
/// Third control point.
/// Subtract <see cref="P3"/> from <see cref="P2"/> to derive the second tangent for a curve.
/// </summary>
public float3 P2;
/// <summary>
/// Fourth control point.
/// </summary>
public float3 P3;
/// <summary>
/// The direction and magnitude of the first tangent in a cubic curve.
/// </summary>
public float3 Tangent0
{
get => P1 - P0;
set => P1 = P0 + value;
}
/// <summary>
/// The direction and magnitude of the second tangent in a cubic curve.
/// </summary>
public float3 Tangent1
{
get => P2 - P3;
set => P2 = P3 + value;
}
/// <summary>
/// Construct a cubic bezier curve from a linear curve. A linear curve is simply a straight line.
/// </summary>
/// <param name="p0">The first control point. This is the start point of the curve.</param>
/// <param name="p1">The second control point. This is the end point of the curve.</param>
public BezierCurve(float3 p0, float3 p1)
{
P0 = P2 = p0;
P1 = P3 = p1;
}
/// <summary>
/// Construct a cubic bezier curve by elevating a quadratic curve.
/// </summary>
/// <param name="p0">The first control point. This is the start point of the curve.</param>
/// <param name="p1">The second control point.</param>
/// <param name="p2">The third control point. This is the end point of the curve.</param>
public BezierCurve(float3 p0, float3 p1, float3 p2)
{
const float k_13 = 1 / 3f;
const float k_23 = 2 / 3f;
float3 tan = k_23 * p1;
P0 = p0;
P1 = k_13 * p0 + tan;
P2 = k_13 * p2 + tan;
P3 = p2;
}
/// <summary>
/// Construct a cubic bezier curve from a series of control points.
/// </summary>
/// <param name="p0">The first control point. This is the start point of the curve.</param>
/// <param name="p1">The second control point.</param>
/// <param name="p2">The third control point.</param>
/// <param name="p3">The fourth control point. This is the end point of the curve.</param>
public BezierCurve(float3 p0, float3 p1, float3 p2, float3 p3)
{
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
}
/// <summary>
/// Construct a cubic bezier curve from a start and end <see cref="BezierKnot"/>.
/// </summary>
/// <param name="a">The knot to use as the first and second control points. The first control point is equal
/// to <see cref="BezierKnot.Position"/>, and the second control point is equal to
/// (<see cref="BezierKnot.Position"/> + <see cref="BezierKnot.TangentOut"/> that's rotated by <see cref="BezierKnot.Rotation"/>).</param>
/// <param name="b">The knot to use as the third and fourth control points. The third control point is equal
/// to (<see cref="BezierKnot.Position"/> + <see cref="BezierKnot.TangentIn"/> that's rotated by <see cref="BezierKnot.Rotation"/>), and the fourth control point is
/// equal to <see cref="BezierKnot.Position"/>.</param>
public BezierCurve(BezierKnot a, BezierKnot b) :
this(a.Position, a.Position + math.rotate(a.Rotation, a.TangentOut), b.Position + math.rotate(b.Rotation, b.TangentIn), b.Position)
{
}
/// <summary>
/// Create a BezierCurve from a start and end point plus tangent directions.
/// </summary>
/// <param name="pointA">Starting position of the curve.</param>
/// <param name="tangentOutA">The direction and magnitude to the second control point.</param>
/// <param name="pointB">Ending position of the curve.</param>
/// <param name="tangentInB">The direction and magnitude to the third control point.</param>
/// <returns>A new BezierCurve from the derived control points.</returns>
public static BezierCurve FromTangent(float3 pointA, float3 tangentOutA, float3 pointB, float3 tangentInB)
{
return new BezierCurve(pointA, pointA + tangentOutA, pointB + tangentInB, pointB);
}
/// <summary>
/// Compare two curves for equality.
/// </summary>
/// <param name="other">The curve to compare against.</param>
/// <returns>Returns true when the control points of each curve are identical.</returns>
public bool Equals(BezierCurve other)
{
return P0.Equals(other.P0) && P1.Equals(other.P1) && P2.Equals(other.P2) && P3.Equals(other.P3);
}
/// <summary>
/// Compare against an object for equality.
/// </summary>
/// <param name="obj">The object to compare against.</param>
/// <returns>
/// Returns true when <paramref name="obj"/> is a <see cref="BezierCurve"/> and the control points of each
/// curve are identical.
/// </returns>
public override bool Equals(object obj)
{
return obj is BezierCurve other && Equals(other);
}
/// <summary>
/// Calculate a hash code for this curve.
/// </summary>
/// <returns>
/// A hash code for the curve.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = P0.GetHashCode();
hashCode = (hashCode * 397) ^ P1.GetHashCode();
hashCode = (hashCode * 397) ^ P2.GetHashCode();
hashCode = (hashCode * 397) ^ P3.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Compare two curves for equality.
/// </summary>
/// <param name="left">The first curve.</param>
/// <param name="right">The second curve.</param>
/// <returns>Returns true when the control points of each curve are identical.</returns>
public static bool operator ==(BezierCurve left, BezierCurve right)
{
return left.Equals(right);
}
/// <summary>
/// Compare two curves for inequality.
/// </summary>
/// <param name="left">The first curve.</param>
/// <param name="right">The second curve.</param>
/// <returns>Returns false when the control points of each curve are identical.</returns>
public static bool operator !=(BezierCurve left, BezierCurve right)
{
return !left.Equals(right);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 65945d4401044bc095f3fe90d615eb81
timeCreated: 1623790171

View file

@ -0,0 +1,123 @@
using System;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// This struct contains position and tangent data for a knot.
/// The <see cref="Spline"/> class stores a collection of BezierKnot that form a series of connected
/// <see cref="BezierCurve"/>. Each knot contains a Position, Tangent In, and Tangent Out. When a Spline is not
/// closed, the first and last knots will contain an extraneous tangent (in and out, respectively).
/// </summary>
[Serializable]
public struct BezierKnot: ISerializationCallbackReceiver
{
/// <summary>
/// The position of the knot. On a cubic bezier curve, this is equivalent to <see cref="BezierCurve.P0"/> or
/// <see cref="BezierCurve.P3"/>, depending on whether this knot is forming the first or second control point
/// of the curve.
/// </summary>
public float3 Position;
/// <summary>
/// The tangent leading into this knot. On a cubic bezier curve, this value is used to calculate
/// <see cref="BezierCurve.P2"/> when used as the second knot in a curve.
/// </summary>
public float3 TangentIn;
/// <summary>
/// The tangent following this knot. On a cubic bezier curve, this value is used to calculate
/// <see cref="BezierCurve.P1"/> when used as the first knot in a curve.
/// </summary>
public float3 TangentOut;
/// <summary>
/// Rotation of the knot.
/// </summary>
public quaternion Rotation;
/// <summary>
/// Create a new BezierKnot struct.
/// </summary>
/// <param name="position">The position of the knot relative to the spline.</param>
public BezierKnot(float3 position)
{
Position = position;
TangentIn = float3.zero;
TangentOut = float3.zero;
Rotation = quaternion.identity;
}
/// <summary>
/// Create a new BezierKnot struct.
/// </summary>
/// <param name="position">The position of the knot relative to the spline.</param>
/// <param name="tangentIn">The leading tangent to this knot.</param>
/// <param name="tangentOut">The following tangent to this knot.</param>
/// <param name="rotation">The rotation of the knot relative to the spline.</param>
public BezierKnot(float3 position, float3 tangentIn, float3 tangentOut, quaternion rotation)
{
Position = position;
TangentIn = tangentIn;
TangentOut = tangentOut;
Rotation = rotation;
}
/// <summary>
/// Multiply the position and tangents by a matrix.
/// </summary>
/// <param name="matrix">The matrix to multiply.</param>
/// <returns>A new BezierKnot multiplied by matrix.</returns>
public BezierKnot Transform(float4x4 matrix)
{
var rotation = math.mul(new quaternion(matrix), Rotation);
var invRotation = math.inverse(rotation);
// Tangents need to be scaled, so rotation should be applied to them.
// No need however to use the translation as this is only a direction.
return new BezierKnot(
math.transform(matrix, Position),
math.rotate(invRotation, math.rotate(matrix, math.rotate(Rotation,TangentIn))),
math.rotate(invRotation, math.rotate(matrix, math.rotate(Rotation,TangentOut))),
rotation);
}
/// <summary>
/// Knot position addition. This operation only applies to the position, tangents and rotation are unmodified.
/// </summary>
/// <param name="knot">The target knot.</param>
/// <param name="rhs">The value to add.</param>
/// <returns>A new BezierKnot where position is the sum of knot.position and rhs.</returns>
public static BezierKnot operator +(BezierKnot knot, float3 rhs)
{
return new BezierKnot(knot.Position + rhs, knot.TangentIn, knot.TangentOut, knot.Rotation);
}
/// <summary>
/// Knot position subtraction. This operation only applies to the position, tangents and rotation are unmodified.
/// </summary>
/// <param name="knot">The target knot.</param>
/// <param name="rhs">The value to subtract.</param>
/// <returns>A new BezierKnot where position is the sum of knot.position minus rhs.</returns>
public static BezierKnot operator -(BezierKnot knot, float3 rhs)
{
return new BezierKnot(knot.Position - rhs, knot.TangentIn, knot.TangentOut, knot.Rotation);
}
/// <summary>
/// See ISerializationCallbackReceiver.
/// </summary>
public void OnBeforeSerialize() {}
/// <summary>
/// See ISerializationCallbackReceiver.
/// </summary>
public void OnAfterDeserialize()
{
// Ensures that when adding the first knot via Unity inspector
// or when deserializing knot that did not have the rotation field prior,
// rotation is deserialized to identity instead of (0, 0, 0, 0) which does not represent a valid rotation.
if (math.lengthsq(Rotation) == 0f)
Rotation = quaternion.identity;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb146aa65c3e4768bb8e02d69185a3ae
timeCreated: 1626876005

View file

@ -0,0 +1,360 @@
using System.Collections.Generic;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// A collection of methods for extracting information about <see cref="BezierCurve"/> types.
/// </summary>
public static class CurveUtility
{
struct FrenetFrame
{
public float3 origin;
public float3 tangent;
public float3 normal;
public float3 binormal;
}
const int k_NormalsPerCurve = 16;
static readonly float3[] s_NormalBuffer = new float3[k_NormalsPerCurve];
/// <summary>
/// Given a bezier curve, return an interpolated position at ratio t.
/// </summary>
/// <param name="curve">A cubic bezier curve.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A position on the curve.</returns>
public static float3 EvaluatePosition(BezierCurve curve, float t)
{
t = math.clamp(t, 0, 1);
var t2 = t * t;
var t3 = t2 * t;
var position =
curve.P0 * ( -1f * t3 + 3f * t2 - 3f * t + 1f ) +
curve.P1 * ( 3f * t3 - 6f * t2 + 3f * t) +
curve.P2 * ( -3f * t3 + 3f * t2) +
curve.P3 * ( t3 );
return position;
}
/// <summary>
/// Given a bezier curve, return an interpolated tangent at ratio t.
/// </summary>
/// <param name="curve">A cubic bezier curve.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A tangent on the curve.</returns>
public static float3 EvaluateTangent(BezierCurve curve, float t)
{
t = math.clamp(t, 0, 1);
float t2 = t * t;
var tangent =
curve.P0 * ( -3f * t2 + 6f * t - 3f ) +
curve.P1 * ( 9f * t2 - 12f * t + 3f) +
curve.P2 * ( -9f * t2 + 6f * t ) +
curve.P3 * ( 3f * t2 );
return tangent;
}
/// <summary>
/// Given a bezier curve, return an interpolated acceleration at ratio t.
/// </summary>
/// <param name="curve">A cubic bezier curve.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>An acceleration vector on the curve.</returns>
public static float3 EvaluateAcceleration(BezierCurve curve, float t)
{
t = math.clamp(t, 0, 1);
var acceleration =
curve.P0 * ( -6f * t + 6f ) +
curve.P1 * ( 18f * t - 12f) +
curve.P2 * (-18f * t + 6f ) +
curve.P3 * ( 6f * t );
return acceleration;
}
/// <summary>
/// Given a bezier curve, return an interpolated curvature at ratio t.
/// </summary>
/// <param name="curve">A cubic bezier curve.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A curvature value on the curve.</returns>
public static float EvaluateCurvature(BezierCurve curve, float t)
{
t = math.clamp(t, 0, 1);
var firstDerivative = EvaluateTangent(curve, t);
var secondDerivative = EvaluateAcceleration(curve, t);
var firstDerivativeNormSq = math.lengthsq(firstDerivative);
var secondDerivativeNormSq = math.lengthsq(secondDerivative);
var derivativesDot = math.dot(firstDerivative, secondDerivative);
var kappa = math.sqrt(
( firstDerivativeNormSq * secondDerivativeNormSq ) - ( derivativesDot * derivativesDot ))
/ ( firstDerivativeNormSq * math.length(firstDerivative));
return kappa;
}
/// <summary>
/// Given a bezier curve, return an interpolated position at ratio t.
/// </summary>
/// <param name="curve">A cubic bezier curve.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A position on the curve.</returns>
static float3 DeCasteljau(BezierCurve curve, float t)
{
float3 p0 = curve.P0, p1 = curve.P1;
float3 p2 = curve.P2, p3 = curve.P3;
float3 a0 = math.lerp(p0, p1, t);
float3 a1 = math.lerp(p1, p2, t);
float3 a2 = math.lerp(p2, p3, t);
float3 b0 = math.lerp(a0, a1, t);
float3 b1 = math.lerp(a1, a2, t);
return math.lerp(b0, b1, t);
}
/// <summary>
/// Decompose a curve into two smaller curves matching the source curve.
/// </summary>
/// <param name="curve">The source curve.</param>
/// <param name="t">A mid-point on the source curve defining where the two smaller curves control points meet.</param>
/// <param name="left">A curve from the source curve first control point to the mid-point, matching the curvature of the source curve.</param>
/// <param name="right">A curve from the mid-point to the source curve fourth control point, matching the curvature of the source curve.</param>
public static void Split(BezierCurve curve, float t, out BezierCurve left, out BezierCurve right)
{
t = math.clamp(t, 0f, 1f);
// subdivide control points, first iteration
float3 split0 = math.lerp(curve.P0, curve.P1, t);
float3 split1 = math.lerp(curve.P1, curve.P2, t);
float3 split2 = math.lerp(curve.P2, curve.P3, t);
// subdivide control points, second iteration
float3 split3 = math.lerp(split0, split1, t);
float3 split4 = math.lerp(split1, split2, t);
// subdivide control points, third iteration
float3 split5 = math.lerp(split3, split4, t);
left = new BezierCurve(curve.P0, split0, split3, split5);
right = new BezierCurve(split5, split4, split2, curve.P3);
}
/// <summary>
/// Calculate the length of a <see cref="BezierCurve"/> by unrolling the curve into linear segments and summing
/// the lengths of the lines. This is equivalent to accessing <see cref="Spline.GetCurveLength"/>.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to calculate length.</param>
/// <param name="resolution">The number of linear segments used to calculate the curve length.</param>
/// <returns>The sum length of a collection of linear segments fitting this curve.</returns>
/// <seealso cref="ApproximateLength(BezierCurve)"/>
public static float CalculateLength(BezierCurve curve, int resolution = 30)
{
float magnitude = 0f;
float3 prev = EvaluatePosition(curve, 0f);
for (int i = 1; i < resolution; i++)
{
var point = EvaluatePosition(curve, i / (resolution - 1f));
var dir = point - prev;
magnitude += math.length(dir);
prev = point;
}
return magnitude;
}
/// <summary>
/// Populate a pre-allocated lookupTable array with distance to 't' values. The number of table entries is
/// dependent on the size of the passed lookupTable.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to create a distance to 't' lookup table for.</param>
/// <param name="lookupTable">A pre-allocated array to populate with distance to interpolation ratio data.</param>
public static void CalculateCurveLengths(BezierCurve curve, DistanceToInterpolation[] lookupTable)
{
var resolution = lookupTable.Length;
float magnitude = 0f;
float3 prev = EvaluatePosition(curve, 0f);
lookupTable[0] = new DistanceToInterpolation() { Distance = 0f , T = 0f };
for (int i = 1; i < resolution; i++)
{
var t = i / ( resolution - 1f );
var point = EvaluatePosition(curve, t);
var dir = point - prev;
magnitude += math.length(dir);
lookupTable[i] = new DistanceToInterpolation() { Distance = magnitude , T = t};
prev = point;
}
}
/// <summary>
/// Calculate the approximate length of a <see cref="BezierCurve"/>. This is less accurate than
/// <seealso cref="CalculateLength"/>, but can be significantly faster. Use this when accuracy is
/// not paramount and the curve control points are changing frequently.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to calculate length.</param>
/// <returns>An estimate of the length of a curve.</returns>
public static float ApproximateLength(BezierCurve curve)
{
float chord = math.length(curve.P3 - curve.P0);
float net = math.length(curve.P0 - curve.P1) + math.length(curve.P2 - curve.P1) + math.length(curve.P3 - curve.P2);
return (net + chord) / 2;
}
internal static float3 EvaluateUpVector(BezierCurve curve, float t, float3 startUp, float3 endUp)
{
// Ensure we have workable tangents by linearizing ones that are of zero length
var linearTangentLen = math.length(SplineUtility.GetLinearTangent(curve.P0, curve.P3));
var linearTangentOut = math.normalize(curve.P3 - curve.P0) * linearTangentLen;
if (Mathf.Approximately(math.length(curve.P0 - curve.P1), 0f))
curve.P1 = curve.P0 + linearTangentOut;
if (Mathf.Approximately(math.length(curve.P2 - curve.P3), 0f))
curve.P2 = curve.P3 - linearTangentOut;
// Construct initial frenet frame
FrenetFrame frame;
frame.origin = curve.P0;
frame.tangent = curve.P1 - curve.P0;
frame.normal = startUp;
frame.binormal = math.normalize(math.cross(frame.tangent, frame.normal));
s_NormalBuffer[0] = frame.normal;
// Continue building remaining rotation minimizing frames
var stepSize = 1f / (k_NormalsPerCurve - 1);
var currentT = stepSize;
var prevT = 0f;
var upVector = float3.zero;
FrenetFrame prevFrame;
for (int i = 1; i < k_NormalsPerCurve; ++i)
{
prevFrame = frame;
frame = GetNextRotationMinimizingFrame(curve, prevFrame, currentT);
s_NormalBuffer[i] = frame.normal;
if (prevT <= t && currentT >= t)
{
var lerpT = (t - prevT) / stepSize;
upVector = Vector3.Slerp(prevFrame.normal, frame.normal, lerpT);
}
prevT = currentT;
currentT += stepSize;
}
if (prevT <= t && currentT >= t)
upVector = endUp;
var lastFrameNormal = s_NormalBuffer[k_NormalsPerCurve - 1];
var angleBetweenNormals = math.acos(math.clamp(math.dot(lastFrameNormal, endUp), -1f, 1f));
if (angleBetweenNormals == 0f)
return upVector;
// Since there's an angle difference between the end knot's normal and the last evaluated frenet frame's normal,
// the remaining code gradually applies the angle delta across the evaluated frames' normals.
var lastNormalTangent = math.normalize(frame.tangent);
var positiveRotation = quaternion.AxisAngle(lastNormalTangent, angleBetweenNormals);
var negativeRotation = quaternion.AxisAngle(lastNormalTangent, -angleBetweenNormals);
var positiveRotationResult = math.acos(math.clamp(math.dot(math.rotate(positiveRotation, endUp), lastFrameNormal), -1f, 1f));
var negativeRotationResult = math.acos(math.clamp(math.dot(math.rotate(negativeRotation, endUp), lastFrameNormal), -1f, 1f));
if (positiveRotationResult > negativeRotationResult)
angleBetweenNormals *= -1f;
currentT = stepSize;
prevT = 0f;
for (int i = 1; i < s_NormalBuffer.Length; i++)
{
var normal = s_NormalBuffer[i];
var adjustmentAngle = math.lerp(0f, angleBetweenNormals, currentT);
var tangent = math.normalize(CurveUtility.EvaluateTangent(curve, currentT));
var adjustedNormal = math.rotate(quaternion.AxisAngle(tangent, -adjustmentAngle), normal);
s_NormalBuffer[i] = adjustedNormal;
// Early exit if we've already adjusted the normals at offsets that curveT is in between
if (prevT <= t && currentT >= t)
{
var lerpT = (t - prevT) / stepSize;
upVector = Vector3.Slerp(s_NormalBuffer[i - 1], s_NormalBuffer[i], lerpT);
return upVector;
}
prevT = currentT;
currentT += stepSize;
}
return endUp;
}
static FrenetFrame GetNextRotationMinimizingFrame(BezierCurve curve, FrenetFrame previousRMFrame, float nextRMFrameT)
{
FrenetFrame nextRMFrame;
// Evaluate position and tangent for next RM frame
nextRMFrame.origin = CurveUtility.EvaluatePosition(curve, nextRMFrameT);
nextRMFrame.tangent = CurveUtility.EvaluateTangent(curve, nextRMFrameT);
// Mirror the rotational axis and tangent
float3 toCurrentFrame = nextRMFrame.origin - previousRMFrame.origin;
float c1 = math.dot(toCurrentFrame, toCurrentFrame);
float3 riL = previousRMFrame.binormal - toCurrentFrame * 2f / c1 * math.dot(toCurrentFrame, previousRMFrame.binormal);
float3 tiL = previousRMFrame.tangent - toCurrentFrame * 2f / c1 * math.dot(toCurrentFrame, previousRMFrame.tangent);
// Compute a more stable binormal
float3 v2 = nextRMFrame.tangent - tiL;
float c2 = math.dot(v2, v2);
// Fix binormal's axis
nextRMFrame.binormal = math.normalize(riL - v2 * 2f / c2 * math.dot(v2, riL));
nextRMFrame.normal = math.normalize(math.cross(nextRMFrame.binormal, nextRMFrame.tangent));
return nextRMFrame;
}
/// <summary>
/// Return the normalized interpolation (t) corresponding to a distance on a <see cref="BezierCurve"/>. This
/// method accepts a look-up table (referred to in code with acronym "LUT") that may be constructed using
/// <see cref="CalculateCurveLengths"/>. The built-in Spline class implementations (<see cref="Spline"/> and
/// <see cref="NativeSpline"/>) cache these look-up tables internally.
/// </summary>
/// <typeparam name="T">The collection type.</typeparam>
/// <param name="lut">A look-up table of distance to 't' values. See <see cref="CalculateCurveLengths"/> for creating
/// this collection.</param>
/// <param name="distance">The curve-relative distance to convert to an interpolation ratio (also referred to as 't').</param>
/// <returns> The normalized interpolation ratio associated to distance on the designated curve.</returns>
public static float GetDistanceToInterpolation<T>(T lut, float distance) where T : IReadOnlyList<DistanceToInterpolation>
{
if(lut == null || lut.Count < 1 || distance <= 0)
return 0f;
var resolution = lut.Count;
var curveLength = lut[resolution-1].Distance;
if(distance >= curveLength)
return 1f;
var prev = lut[0];
for(int i = 1; i < resolution; i++)
{
var current = lut[i];
if(distance < current.Distance)
return math.lerp(prev.T, current.T, (distance - prev.Distance) / (current.Distance - prev.Distance));
prev = current;
}
return 1f;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a6c59f50ab3b47fba320494d3f3defa5
timeCreated: 1625671559

View file

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using UnityEngine.Serialization;
namespace UnityEngine.Splines
{
/// <summary>
/// Defines an interpolation ratio 't' for a Data Point.
/// </summary>
public interface IDataPoint
{
/// <summary>
/// The interpolation ratio. How this value is interpreted depends on the <see cref="PathIndexUnit"/> specified
/// by <see cref="SplineData{T}"/>.
/// </summary>
float Index { get; set; }
}
/// <summary>
/// A pair containing an interpolation ratio and {TDataType} value.
/// </summary>
/// <typeparam name="TDataType">The type of data this data point stores.</typeparam>
[Serializable]
public struct DataPoint<TDataType> : IComparable<DataPoint<TDataType>>, IComparable<float>, IDataPoint
{
[FormerlySerializedAs("m_Time")]
[SerializeField]
float m_Index;
[SerializeField]
TDataType m_Value;
/// <summary>
/// The interpolation ratio relative to a spline. How this value is interpolated depends on the <see cref="PathIndexUnit"/>
/// specified by <see cref="SplineData{T}"/>.
/// </summary>
public float Index
{
get => m_Index;
set => m_Index = value;
}
/// <summary>
/// A value to store with this Data Point.
/// </summary>
public TDataType Value
{
get => m_Value;
set => m_Value = value;
}
/// <summary>
/// Create a new Data Point with interpolation ratio and value.
/// </summary>
/// <param name="index">Interpolation ratio.</param>
/// <param name="value">The value to store.</param>
public DataPoint(float index, TDataType value)
{
m_Index = index;
m_Value = value;
}
/// <summary>
/// Compare DataPoint <see cref="Index"/> values.
/// </summary>
/// <param name="other">The DataPoint to compare against.</param>
/// <returns>An integer less than 0 if other.Key is greater than <see cref="Index"/>, 0 if key values are equal, and greater
/// than 0 when other.Key is less than <see cref="Index"/>.</returns>
public int CompareTo(DataPoint<TDataType> other) => Index.CompareTo(other.Index);
/// <summary>
/// Compare DataPoint <see cref="Index"/> values.
/// </summary>
/// <param name="other">An interpolation ratio to compare against.</param>
/// <returns>An integer less than 0 if other.Key is greater than <see cref="Index"/>, 0 if key values are equal, and greater
/// than 0 when other.Key is less than <see cref="Index"/>.</returns>
public int CompareTo(float other) => Index.CompareTo(other);
/// <summary>
/// A summary of the DataPoint time and value.
/// </summary>
/// <returns>A summary of the DataPoint key and value.</returns>
public override string ToString() => $"{Index} {Value}";
}
class DataPointComparer<T> : IComparer<T> where T : IDataPoint
{
public int Compare(T x, T y)
{
return x.Index.CompareTo(y.Index);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 508048e85d1b4f1aac151183c59fadf1
timeCreated: 1613496307

View file

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace UnityEngine.Splines
{
/// <summary>
/// A key-value pair associating a distance to interpolation ratio ('t') value. This is used when evaluating Spline
/// attributes to ensure uniform ditribution of sampling points.
/// </summary>
/// <seealso cref="CurveUtility.CalculateCurveLengths"/>
public struct DistanceToInterpolation
{
/// <summary>
/// Distance in Unity units.
/// </summary>
public float Distance;
/// <summary>
/// A normalized interpolation ratio ('t').
/// </summary>
public float T;
}
/// <summary>
/// ISpline defines the interface from which Spline types inherit.
/// </summary>
public interface ISpline : IReadOnlyList<BezierKnot>
{
/// <summary>
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// </summary>
bool Closed { get; }
/// <summary>
/// Return the sum of all curve lengths, accounting for <see cref="Closed"/> state.
/// </summary>
/// <returns>
/// Returns the sum length of all curves composing this spline, accounting for closed state.
/// </returns>
float GetLength();
/// <summary>
/// Get a <see cref="BezierCurve"/> from a knot index.
/// </summary>
/// <param name="index">The knot index that serves as the first control point for this curve.</param>
/// <returns>
/// A <see cref="BezierCurve"/> formed by the knot at index and the next knot.
/// </returns>
public BezierCurve GetCurve(int index);
/// <summary>
/// Return the length of a curve.
/// </summary>
/// <param name="index">The index of the curve for which the length needs to be retrieved</param>
/// <seealso cref="GetLength"/>
/// <returns>
/// Returns the length of the curve of index 'index' in the spline.
/// </returns>
public float GetCurveLength(int index);
/// <summary>
/// Return the interpolation ratio (0 to 1) corresponding to a distance on a <see cref="BezierCurve"/>. Distance
/// is relative to the curve.
/// </summary>
/// <param name="curveIndex"> The zero-based index of the curve.</param>
/// <param name="curveDistance"> The distance (measuring from the knot at curveIndex) to convert to a normalized interpolation ratio.</param>
/// <returns>The normalized interpolation ratio matching distance on the designated curve. </returns>
public float GetCurveInterpolation(int curveIndex, float curveDistance);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 515aafa3af3048d18c327fa7af8028a7
timeCreated: 1628707414

View file

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace UnityEngine.Splines
{
/// <summary>
/// Implement ISplineProvider on a MonoBehaviour to enable Spline tools in the Editor.
/// </summary>
public interface ISplineProvider
{
/// <summary>
/// A collection of Splines contained on this MonoBehaviour.
/// </summary>
IEnumerable<Spline> Splines { get; }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 014bdaee1f9322e4aab0358e6f7e3959
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,253 @@
using Unity.Mathematics;
namespace UnityEngine.Splines.Interpolators
{
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpFloat : IInterpolator<float>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float Interpolate(float a, float b, float t)
{
return math.lerp(a, b, t);
}
}
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpFloat2 : IInterpolator<float2>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float2 Interpolate(float2 a, float2 b, float t)
{
return math.lerp(a, b, t);
}
}
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpFloat3 : IInterpolator<float3>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float3 Interpolate(float3 a, float3 b, float t)
{
return math.lerp(a, b, t);
}
}
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpFloat4 : IInterpolator<float4>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float4 Interpolate(float4 a, float4 b, float t)
{
return math.lerp(a, b, t);
}
}
/// <summary>
/// Spherically interpolate between two values a and b by ratio t.
/// </summary>
public struct SlerpFloat2 : IInterpolator<float2>
{
/// <summary>
/// Spherically interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The spherically interpolated result between the two values.</returns>
public float2 Interpolate(float2 a, float2 b, float t)
{
// Using Vector3 API as Mathematics does not provide Slerp for float2.
var result = Vector3.Slerp(new Vector3(a.x, a.y, 0f), new Vector3(b.x, b.y, 0f), t);
return new float2(result.x, result.y);
}
}
/// <summary>
/// Spherically interpolate between two values a and b by ratio t.
/// </summary>
public struct SlerpFloat3 : IInterpolator<float3>
{
/// <summary>
/// Spherically interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The spherically interpolated result between the two values.</returns>
public float3 Interpolate(float3 a, float3 b, float t)
{
// Using Vector3 API as Mathematics does not provide Slerp for float3.
return Vector3.Slerp(a, b, t);
}
}
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpQuaternion : IInterpolator<quaternion>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public quaternion Interpolate(quaternion a, quaternion b, float t)
{
return math.nlerp(a, b, t);
}
}
/// <summary>
/// Linearly interpolate between two values a and b by ratio t.
/// </summary>
public struct LerpColor : IInterpolator<Color>
{
/// <summary>
/// Linearly interpolates between a and b by t.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public Color Interpolate(Color a, Color b, float t)
{
return Color.Lerp(a, b, t);
}
}
/// <summary>
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
/// </summary>
public struct SmoothStepFloat : IInterpolator<float>
{
/// <summary>
/// Interpolates between a and b by ratio t with smoothing at the limits.
/// This function interpolates between min and max in a similar way to Lerp. However, the interpolation will
/// gradually speed up from the start and slow down toward the end. This is useful for creating natural-looking
/// animation, fading and other transitions.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float Interpolate(float a, float b, float t)
{
return math.smoothstep(a, b, t);
}
}
/// <summary>
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
/// </summary>
public struct SmoothStepFloat2 : IInterpolator<float2>
{
/// <summary>
/// Interpolates between a and b by ratio t with smoothing at the limits.
/// This function interpolates between min and max in a similar way to Lerp. However, the interpolation will
/// gradually speed up from the start and slow down toward the end. This is useful for creating natural-looking
/// animation, fading and other transitions.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float2 Interpolate(float2 a, float2 b, float t)
{
return math.smoothstep(a, b, t);
}
}
/// <summary>
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
/// </summary>
public struct SmoothStepFloat3 : IInterpolator<float3>
{
/// <summary>
/// Interpolates between a and b by ratio t with smoothing at the limits.
/// This function interpolates between min and max in a similar way to Lerp. However, the interpolation will
/// gradually speed up from the start and slow down toward the end. This is useful for creating natural-looking
/// animation, fading and other transitions.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float3 Interpolate(float3 a, float3 b, float t)
{
return math.smoothstep(a, b, t);
}
}
/// <summary>
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
/// </summary>
public struct SmoothStepFloat4 : IInterpolator<float4>
{
/// <summary>
/// Interpolates between a and b by ratio t with smoothing at the limits.
/// This function interpolates between min and max in a similar way to Lerp. However, the interpolation will
/// gradually speed up from the start and slow down toward the end. This is useful for creating natural-looking
/// animation, fading and other transitions.
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns> The interpolated result between the two values.</returns>
public float4 Interpolate(float4 a, float4 b, float t)
{
return math.smoothstep(a, b, t);
}
}
/// <summary>
/// Spherically interpolates between quaternions a and b by ratio t. The parameter t is clamped b the range [0, 1].
/// </summary>
public struct SlerpQuaternion : IInterpolator<quaternion>
{
/// <summary>
/// Spherically interpolates between quaternions a and b by ratio t. The parameter t is clamped b the range [0, 1].
/// </summary>
/// <param name="a">Start value, returned when t = 0.</param>
/// <param name="b">End value, returned when t = 1.</param>
/// <param name="t">Interpolation ratio.</param>
/// <returns>A quaternion spherically interpolated between quaternions a and b.</returns>
public quaternion Interpolate(quaternion a, quaternion b, float t)
{
return math.slerp(a, b, t);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c4f48d56433f4be3b319c943db02157
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,179 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// A readonly representation of <see cref="Spline"/> that is optimized for efficient access and queries.
/// NativeSpline can be constructed with a Spline and Transform. If a transform is applied, all values will be
/// relative to the transformed knot positions.
/// </summary>
/// <remarks>
/// NativeSpline is compatible with the job system.
/// </remarks>
public struct NativeSpline : ISpline, IDisposable
{
NativeArray<BezierKnot> m_Knots;
// As we cannot make a NativeArray of NativeArray all segments lookup tables are stored in a single array
// each lookup table as a length of k_SegmentResolution and starts at index i = curveIndex * k_SegmentResolution
NativeArray<DistanceToInterpolation> m_SegmentLengthsLookupTable;
bool m_Closed;
float m_Length;
const int k_SegmentResolution = 30;
/// <summary>
/// A NativeArray of <see cref="BezierKnot"/> that form this Spline.
/// </summary>
/// <returns>
/// Returns a reference to the knots array.
/// </returns>
public NativeArray<BezierKnot> Knots => m_Knots;
/// <summary>
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// </summary>
public bool Closed => m_Closed;
/// <summary>
/// Return the number of knots.
/// </summary>
public int Count => m_Knots.Length;
/// <summary>
/// Return the sum of all curve lengths, accounting for <see cref="Closed"/> state.
/// Note that this value is affected by the transform used to create this NativeSpline.
/// </summary>
/// <returns>
/// Returns the sum length of all curves composing this spline, accounting for closed state.
/// </returns>
public float GetLength() => m_Length;
/// <summary>
/// Get the knot at <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index of the knot.</param>
public BezierKnot this[int index] => m_Knots[index];
/// <summary>
/// Get an enumerator that iterates through the <see cref="BezierKnot"/> collection.
/// </summary>
/// <returns>An IEnumerator that is used to iterate the <see cref="BezierKnot"/> collection.</returns>
public IEnumerator<BezierKnot> GetEnumerator() => m_Knots.GetEnumerator();
/// <inheritdoc cref="GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Create a new NativeSpline from a set of <see cref="BezierKnot"/>.
/// </summary>
/// <param name="spline">The <see cref="ISpline"/> object to convert to a <see cref="NativeSpline"/>.</param>
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
public NativeSpline(ISpline spline, Allocator allocator = Allocator.Temp)
: this(spline, spline.Closed, float4x4.identity, allocator) { }
/// <summary>
/// Create a new NativeSpline from a set of <see cref="BezierKnot"/>.
/// </summary>
/// <param name="spline">The <see cref="ISpline"/> object to convert to a <see cref="NativeSpline"/>.</param>
/// <param name="transform">A transform matrix to be applied to the spline knots and tangents.</param>
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
public NativeSpline(ISpline spline, float4x4 transform, Allocator allocator = Allocator.Temp)
: this(spline, spline.Closed, transform, allocator) { }
/// <summary>
/// Create a new NativeSpline from a set of <see cref="BezierKnot"/>.
/// </summary>
/// <param name="knots">A collection of sequential <see cref="BezierKnot"/> forming the spline path.</param>
/// <param name="closed">Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).</param>
/// <param name="transform">Apply a transformation matrix to the control <see cref="Knots"/>.</param>
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
public NativeSpline(IReadOnlyList<BezierKnot> knots, bool closed, float4x4 transform, Allocator allocator = Allocator.Temp)
{
int kc = knots.Count;
m_Knots = new NativeArray<BezierKnot>(knots.Count, allocator);
for (int i = 0; i < kc; i++)
m_Knots[i] = knots[i].Transform(transform);
m_Closed = closed;
m_Length = 0f;
int curveCount = m_Closed ? kc : kc - 1;
// As we cannot make a NativeArray of NativeArray all segments lookup tables are stored in a single array
// each lookup table as a length of k_SegmentResolution and starts at index i = curveIndex * k_SegmentResolution
m_SegmentLengthsLookupTable = new NativeArray<DistanceToInterpolation>(knots.Count * k_SegmentResolution, allocator);
m_Length = 0f;
DistanceToInterpolation[] distanceToTimes = new DistanceToInterpolation[k_SegmentResolution];
for (int i = 0; i < curveCount; i++)
{
CurveUtility.CalculateCurveLengths(GetCurve(i), distanceToTimes);
m_Length += distanceToTimes[k_SegmentResolution - 1].Distance;
for(int distanceToTimeIndex = 0; distanceToTimeIndex < k_SegmentResolution; distanceToTimeIndex++)
m_SegmentLengthsLookupTable[i * k_SegmentResolution + distanceToTimeIndex] = distanceToTimes[distanceToTimeIndex];
}
}
/// <summary>
/// Get a <see cref="BezierCurve"/> from a knot index.
/// </summary>
/// <param name="index">The knot index that serves as the first control point for this curve.</param>
/// <returns>
/// A <see cref="BezierCurve"/> formed by the knot at index and the next knot.
/// </returns>
public BezierCurve GetCurve(int index)
{
int next = m_Closed ? (index + 1) % Count : math.min(index + 1, Count - 1);
return new BezierCurve(m_Knots[index], m_Knots[next]);
}
/// <summary>
/// Get the length of a <see cref="BezierCurve"/>.
/// </summary>
/// <param name="curveIndex">The 0 based index of the curve to find length for.</param>
/// <returns>The length of the bezier curve at index.</returns>
public float GetCurveLength(int curveIndex) => m_SegmentLengthsLookupTable[curveIndex * k_SegmentResolution + k_SegmentResolution - 1].Distance;
/// <summary>
/// Release allocated resources.
/// </summary>
public void Dispose()
{
m_Knots.Dispose();
m_SegmentLengthsLookupTable.Dispose();
}
struct Slice<T> : IReadOnlyList<T> where T : struct
{
NativeSlice<T> m_Slice;
public Slice(NativeArray<T> array, int start, int count) { m_Slice = new NativeSlice<T>(array, start, count); }
public IEnumerator<T> GetEnumerator() => m_Slice.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => m_Slice.Length;
public T this[int index] => m_Slice[index];
}
/// <summary>
/// Return the normalized interpolation (t) corresponding to a distance on a <see cref="BezierCurve"/>.
/// </summary>
/// <param name="curveIndex"> The zero-based index of the curve.</param>
/// <param name="curveDistance">The curve-relative distance to convert to an interpolation ratio (also referred to as 't').</param>
/// <returns> The normalized interpolation ratio associated to distance on the designated curve.</returns>
public float GetCurveInterpolation(int curveIndex, float curveDistance)
{
if(curveIndex <0 || curveIndex >= m_SegmentLengthsLookupTable.Length || curveDistance <= 0)
return 0f;
var curveLength = GetCurveLength(curveIndex);
if(curveDistance >= curveLength)
return 1f;
var startIndex = curveIndex * k_SegmentResolution;
var slice = new Slice<DistanceToInterpolation>(m_SegmentLengthsLookupTable, startIndex, k_SegmentResolution);
return CurveUtility.GetDistanceToInterpolation(slice, curveDistance);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b0da2494b9f846808b9175a7333f72dc
timeCreated: 1613681438

View file

@ -0,0 +1,426 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// The Spline class is a collection of <see cref="BezierKnot"/>, the closed/open state, and editing representation.
/// </summary>
[Serializable]
public class Spline : ISpline, IList<BezierKnot>
{
const int k_CurveDistanceLutResolution = 30;
[SerializeField]
SplineType m_EditModeType = SplineType.Bezier;
[SerializeField]
List<BezierKnot> m_Knots = new List<BezierKnot>();
[SerializeField]
List<DistanceToInterpolation[]> m_LengthsLookupTable = new List<DistanceToInterpolation[]>();
[SerializeField]
float m_Length = -1f;
[SerializeField]
bool m_Closed;
/// <summary>
/// Return the number of knots.
/// </summary>
public int Count => m_Knots.Count;
/// <summary>
/// Returns true if this Spline is read-only, false if it is mutable.
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Invoked in the editor any time a spline property is modified.
/// </summary>
/// <remarks>
/// In the editor this can be invoked many times per-frame.
/// Prefer to use <see cref="UnityEditor.Splines.EditorSplineUtility.afterSplineDataWasModified"/> when
/// working with splines in the editor.
/// </remarks>
public event Action changed;
#if UNITY_EDITOR
internal static Action<Spline> afterSplineWasModified;
[NonSerialized] //In the editor, this seemed to be surviving domain reloads
bool m_Dirty;
#endif
internal void SetDirty()
{
SetLengthCacheDirty();
changed?.Invoke();
OnSplineChanged();
#if UNITY_EDITOR
if (m_Dirty)
return;
m_Dirty = true;
UnityEditor.EditorApplication.delayCall += () =>
{
afterSplineWasModified?.Invoke(this);
m_Dirty = false;
};
#endif
}
/// <summary>
/// Invoked any time a spline property is modified.
/// </summary>
/// <remarks>
/// In the editor this can be invoked many times per-frame.
/// Prefer to use <see cref="UnityEditor.Splines.EditorSplineUtility.afterSplineWasModified"/> when working
/// with splines in the editor.
/// </remarks>
protected virtual void OnSplineChanged()
{
}
// todo Remove this and refactor m_Knots to store a struct with knot+cached data
void EnsureCurveLengthCacheValid()
{
if (m_LengthsLookupTable.Count != m_Knots.Count)
{
m_LengthsLookupTable.Clear();
m_LengthsLookupTable.Capacity = m_Knots.Count;
for (int i = 0; i < m_Knots.Count; i++)
m_LengthsLookupTable.Add(null);
}
}
// todo Only Catmull Rom requires every curve to be re-evaluated when dirty.
// Linear and cubic bezier could be more selective about dirtying cached curve lengths.
// Important - This function also serves to enable backwards compatibility with serialized Spline instances
// that did not have a length cache.
void SetLengthCacheDirty()
{
EnsureCurveLengthCacheValid();
m_Length = -1f;
for (int i = 0; i < m_Knots.Count; i++)
m_LengthsLookupTable[i] = null;
}
/// <summary>
/// The SplineType that this spline should be presented as to the user.
/// </summary>
/// <remarks>
/// Internally all splines are stored as a collection of bezier knots, and when editing converted or displayed
/// with the handles appropriate to the editable type.
/// </remarks>
public SplineType EditType
{
get => m_EditModeType;
set
{
if (m_EditModeType == value)
return;
m_EditModeType = value;
SetDirty();
}
}
/// <summary>
/// A collection of <see cref="BezierKnot"/>.
/// </summary>
public IEnumerable<BezierKnot> Knots => m_Knots;
/// <summary>
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
/// </summary>
public bool Closed
{
get => m_Closed;
set
{
if (m_Closed == value)
return;
m_Closed = value;
SetDirty();
}
}
/// <summary>
/// Return the first index of an element matching item.
/// </summary>
/// <param name="item">The knot to locate.</param>
/// <returns>The zero-based index of the knot, or -1 if not found.</returns>
public int IndexOf(BezierKnot item) => m_Knots.IndexOf(item);
/// <summary>
/// Insert a <see cref="BezierKnot"/> at the specified <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index to insert the new element.</param>
/// <param name="knot">The <see cref="BezierKnot"/> to insert.</param>
public void Insert(int index, BezierKnot knot)
{
m_Knots.Insert(index, knot);
m_LengthsLookupTable.Insert(index, null);
SetDirty();
}
/// <summary>
/// Removes the knot at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public void RemoveAt(int index)
{
m_Knots.RemoveAt(index);
m_LengthsLookupTable.RemoveAt(index);
SetDirty();
}
/// <summary>
/// Get or set the knot at <paramref name="index"/>.
/// </summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
public BezierKnot this[int index]
{
get => m_Knots[index];
set
{
m_Knots[index] = value;
SetDirty();
}
}
/// <summary>
/// Default constructor creates a spline with no knots, not closed.
/// </summary>
public Spline() { }
/// <summary>
/// Create a spline with a pre-allocated knot capacity.
/// </summary>
/// <param name="knotCapacity">The capacity of the knot collection.</param>
/// <param name="closed">Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).</param>
public Spline(int knotCapacity, bool closed = false)
{
m_Knots = new List<BezierKnot>(knotCapacity);
m_Closed = closed;
}
/// <summary>
/// Create a spline from a collection of <see cref="BezierKnot"/>.
/// </summary>
/// <param name="knots">A collection of <see cref="BezierKnot"/>.</param>
/// <param name="closed">Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).</param>
public Spline(IEnumerable<BezierKnot> knots, bool closed = false)
{
m_Knots = knots.ToList();
m_Closed = closed;
SetDirty();
}
/// <summary>
/// Get a <see cref="BezierCurve"/> from a knot index.
/// </summary>
/// <param name="index">The knot index that serves as the first control point for this curve.</param>
/// <returns>
/// A <see cref="BezierCurve"/> formed by the knot at index and the next knot.
/// </returns>
public BezierCurve GetCurve(int index)
{
int next = m_Closed ? (index + 1) % m_Knots.Count : math.min(index + 1, m_Knots.Count - 1);
return new BezierCurve(m_Knots[index], m_Knots[next]);
}
/// <summary>
/// Return the length of a curve.
/// </summary>
/// <param name="index"></param>
/// <seealso cref="Warmup"/>
/// <seealso cref="GetLength"/>
/// <returns></returns>
public float GetCurveLength(int index)
{
EnsureCurveLengthCacheValid();
if(m_LengthsLookupTable[index] == null)
{
m_LengthsLookupTable[index] = new DistanceToInterpolation[k_CurveDistanceLutResolution];
CurveUtility.CalculateCurveLengths(GetCurve(index), m_LengthsLookupTable[index]);
}
var cumulativeCurveLengths = m_LengthsLookupTable[index];
return cumulativeCurveLengths.Length > 0 ? cumulativeCurveLengths[cumulativeCurveLengths.Length - 1].Distance : 0f;
}
/// <summary>
/// Return the sum of all curve lengths, accounting for <see cref="Closed"/> state.
/// Note that this value is not accounting for transform hierarchy. If you require length in world space use
/// <see cref="SplineUtility.CalculateLength"/>.
/// </summary>
/// <remarks>
/// This value is cached. It is recommended to call this once in a non-performance critical path to ensure that
/// the cache is valid.
/// </remarks>
/// <seealso cref="Warmup"/>
/// <seealso cref="GetCurveLength"/>
/// <returns>
/// Returns the sum length of all curves composing this spline, accounting for closed state.
/// </returns>
public float GetLength()
{
if (m_Length < 0f)
{
m_Length = 0f;
for (int i = 0, c = Closed ? Count : Count - 1; i < c; ++i)
m_Length += GetCurveLength(i);
}
return m_Length;
}
DistanceToInterpolation[] GetCurveDistanceLut(int index)
{
if (m_LengthsLookupTable[index] == null)
{
m_LengthsLookupTable[index] = new DistanceToInterpolation[k_CurveDistanceLutResolution];
CurveUtility.CalculateCurveLengths(GetCurve(index), m_LengthsLookupTable[index]);
}
return m_LengthsLookupTable[index];
}
/// <summary>
/// Return the normalized interpolation (t) corresponding to a distance on a <see cref="BezierCurve"/>.
/// </summary>
/// <param name="curveIndex"> The zero-based index of the curve.</param>
/// <param name="curveDistance">The curve-relative distance to convert to an interpolation ratio (also referred to as 't').</param>
/// <returns> The normalized interpolation ratio associated to distance on the designated curve.</returns>
public float GetCurveInterpolation(int curveIndex, float curveDistance)
=> CurveUtility.GetDistanceToInterpolation(GetCurveDistanceLut(curveIndex), curveDistance);
/// <summary>
/// Ensure that all caches contain valid data. Call this to avoid unexpected performance costs when accessing
/// spline data. Caches remain valid until any part of the spline state is modified.
/// </summary>
public void Warmup()
{
var _ = GetLength();
}
/// <summary>
/// Change the size of the <see cref="BezierKnot"/> list.
/// </summary>
/// <param name="newSize">The new size of the knots collection.</param>
public void Resize(int newSize)
{
int count = Count;
if (newSize == count)
return;
if (newSize > count)
{
while (m_Knots.Count < newSize)
{
m_Knots.Add(new BezierKnot { Rotation = quaternion.identity });
m_LengthsLookupTable.Add(null);
}
}
else if (newSize < count)
{
m_Knots.RemoveRange(newSize, m_Knots.Count - newSize);
m_LengthsLookupTable.RemoveRange(newSize, m_Knots.Count - newSize);
}
SetDirty();
}
/// <summary>
/// Create an array of spline knots.
/// </summary>
/// <returns>Return a new array copy of the knots collection.</returns>
public BezierKnot[] ToArray()
{
return m_Knots.ToArray();
}
/// <summary>
/// Copy the values from <paramref name="toCopy"/> to this spline.
/// </summary>
/// <param name="toCopy">The Spline to copy property data from.</param>
public void Copy(Spline toCopy)
{
if (toCopy == this)
return;
m_EditModeType = toCopy.m_EditModeType;
m_Closed = toCopy.Closed;
m_Knots.Clear();
m_Knots.AddRange(toCopy.m_Knots);
m_LengthsLookupTable.AddRange(toCopy.m_LengthsLookupTable);
SetDirty();
}
/// <summary>
/// Get an enumerator that iterates through the <see cref="BezierKnot"/> collection.
/// </summary>
/// <returns>An IEnumerator that is used to iterate the <see cref="BezierKnot"/> collection.</returns>
public IEnumerator<BezierKnot> GetEnumerator() => m_Knots.GetEnumerator();
/// <inheritdoc cref="GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => m_Knots.GetEnumerator();
/// <summary>
/// Adds a knot to the spline.
/// </summary>
/// <param name="item">The <see cref="BezierKnot"/> to add.</param>
public void Add(BezierKnot item)
{
m_Knots.Add(item);
SetDirty();
}
/// <summary>
/// Remove all knots from the spline.
/// </summary>
public void Clear()
{
m_Knots.Clear();
SetDirty();
}
/// <summary>
/// Return true if a knot is present in the spline.
/// </summary>
/// <param name="item">The <see cref="BezierKnot"/> to locate.</param>
/// <returns>Returns true if the knot is found, false if it is not present.</returns>
public bool Contains(BezierKnot item) => m_Knots.Contains(item);
/// <summary>
/// Copies the contents of the knot list to an array starting at an index.
/// </summary>
/// <param name="array">The destination array to place the copied item in.</param>
/// <param name="arrayIndex">The zero-based index to copy.</param>
public void CopyTo(BezierKnot[] array, int arrayIndex) => m_Knots.CopyTo(array, arrayIndex);
/// <summary>
/// Removes the first matching knot.
/// </summary>
/// <param name="item">The <see cref="BezierKnot"/> to locate and remove.</param>
/// <returns>Returns true if a matching item was found and removed, false if no match was discovered.</returns>
public bool Remove(BezierKnot item)
{
if (m_Knots.Remove(item))
{
SetDirty();
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1737f1b4489bd468cbf919947c6836e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,641 @@
using System;
using UnityEngine.Splines;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Splines
{
/// <summary>
/// A component for animating an object along a Spline.
/// </summary>
[AddComponentMenu("Splines/Spline Animate")]
public class SplineAnimate : SplineComponent
{
/// <summary>
/// Describes the different methods that may be used to animated the object along the Spline.
/// </summary>
public enum Method
{
/// <summary> Spline will be traversed in the given amount of seconds. </summary>
Time,
/// <summary> Spline will be traversed at a given maximum speed. </summary>
Speed
}
/// <summary>
/// Describes the different ways the object's animation along the Spline can be looped.
/// </summary>
public enum LoopMode
{
/// <summary> Traverse the Spline once and stop at the end. </summary>
[InspectorName("Once")]
Once,
/// <summary> Traverse the Spline continously without stopping. </summary>
[InspectorName("Loop Continous")]
Loop,
/// <summary> Traverse the Spline continously without stopping. If <see cref="SplineAnimate.easingMode"/> is set to <see cref="SplineAnimate.EasingMode.EaseIn"/> or
/// <see cref="SplineAnimate.EasingMode.EaseInOut"/> then only ease in is applied and only during the first loop. Otherwise, no easing is applied when using this loop mode.
/// </summary>
[InspectorName("Ease In Then Continous")]
LoopEaseInOnce,
/// <summary> Traverse the Spline continously without stopping and reverse movement direction uppon reaching end or beginning of the Spline. </summary>
[InspectorName("Ping Pong")]
PingPong
}
/// <summary>
/// Describes the different ways the object's animation along the Spline can be eased.
/// </summary>
public enum EasingMode
{
/// <summary> Apply no easing - animation will be linear. </summary>
[InspectorName("None")]
None,
/// <summary> Apply easing to the beginning of animation. </summary>
[InspectorName("Ease In Only")]
EaseIn,
/// <summary> Apply easing to the end of animation. </summary>
[InspectorName("Ease Out Only")]
EaseOut,
/// <summary> Apply easing to the beginning and end of animation. </summary>
[InspectorName("Ease In-Out")]
EaseInOut
}
/// <summary>
/// Describes the ways the object can be aligned when animating along the Spline.
/// </summary>
public enum AlignmentMode
{
/// <summary> No aligment is done and object's rotation is unaffected. </summary>
[InspectorName("None")]
None,
/// <summary> Object's forward and up axes align to the Spline's tangent and up vectors. </summary>
[InspectorName("Spline Element")]
SplineElement,
/// <summary> Object's forward and up axes align to the Spline tranform's Z and Y axes. </summary>
[InspectorName("Spline Object")]
SplineObject,
/// <summary> Object's forward and up axes align to to the World's Z and Y axes. </summary>
[InspectorName("World")]
World
}
[SerializeField, Tooltip("The target Spline to follow.")]
SplineContainer m_Target;
[SerializeField, Tooltip("If true, transform will automatically start following the target Spline on awake.")]
bool m_PlayOnAwake = true;
[SerializeField, Tooltip("The loop mode used when animating object along the Spline.\n" +
"Once - Traverse the Spline once and stop at the end.\n" +
"Loop Continous - Traverse the Spline continously without stopping.\n" +
"Ease In Then Continous - Traverse the Spline repeatedly without stopping. If ease in easing is enabled, apply it only for the first loop.\n" +
"Ping Pong - Traverse the Spline continously without stopping and reverse movement direction upon reaching end or beginning of the Spline.\n")]
LoopMode m_LoopMode = LoopMode.Loop;
[SerializeField, Tooltip("The method used to animate object along the Spline.\n" +
"Time - spline will be traversed in given amount of seconds.\n" +
"Speed - spline will be traversed at a given maximum speed.")]
Method m_Method = Method.Time;
[SerializeField, Tooltip("Amount of time (in seconds) that the spline will be traversed in.")]
float m_Duration = 1f;
[SerializeField, Tooltip("Speed (in meters/second) at which the spline will be traversed.")]
float m_MaxSpeed = 10f;
[SerializeField, Tooltip("Easing mode used when the object along the Spline.\n" +
"None - Apply no easing. Animation will be linear.\n" +
"Ease In Only - Apply easing to the beginning of animation.\n" +
"Ease Out Only - Apply easing to the end of animation.\n" +
"Ease In-Out - Apply easing to the beginning and end of animation.\n")]
EasingMode m_EasingMode = EasingMode.None;
[SerializeField, Tooltip("The coordinate space to which the object's up and forward axes should align to.")]
AlignmentMode m_AlignmentMode = AlignmentMode.SplineElement;
[SerializeField, Tooltip("Which axis of the object should be treated as the forward axis.")]
AlignAxis m_ObjectForwardAxis = AlignAxis.ZAxis;
[SerializeField, Tooltip("Which axis of the object should be treated as the up axis.")]
AlignAxis m_ObjectUpAxis = AlignAxis.YAxis;
Spline m_Spline;
float m_SplineLength = -1;
bool m_Playing;
float m_NormalizedTime;
float m_ElapsedTime;
#if UNITY_EDITOR
double m_LastEditorUpdateTime;
#endif
/// <summary>The target Spline to follow.</summary>
public SplineContainer splineContainer
{
get => m_Target;
set
{
if (enabled && m_Target != null && m_Target.Spline != null)
m_Target.Spline.changed -= OnSplineChange;
m_Target = value;
if (enabled && m_Target != null && m_Target.Spline != null)
{
m_Target.Spline.changed += OnSplineChange;
CalculateSplineLength();
OnSplineChange();
}
}
}
/// <summary>If true, transform will automatically start following the target Spline on awake.</summary>
public bool playOnAwake
{
get => m_PlayOnAwake;
set => m_PlayOnAwake = value;
}
/// <summary>The way the Spline should be looped. See <see cref="LoopMode"/> for details.</summary>
public LoopMode loopMode
{
get => m_LoopMode;
set => m_LoopMode = value;
}
/// <summary> The method used to traverse the Spline. See <see cref="Method"/> for details. </summary>
public Method method
{
get => m_Method;
set => m_Method = value;
}
/// <summary> The time (in seconds) it takes to traverse the Spline once. </summary>
/// <remarks>
/// When animation method is set to <see cref="Method.Time"/> this setter will set the <see cref="duration"/> value and automatically recalculate <see cref="maxSpeed"/>,
/// otherwise, it will have no effect.
/// </remarks>
public float duration
{
get => m_Duration;
set
{
if (m_Method == Method.Time)
{
m_Duration = Mathf.Max(0f, value);
CalculateMaxSpeed();
}
}
}
/// <summary> The maxSpeed speed (in Unity units/second) that the Spline traversal will advance in. </summary>
/// <remarks>
/// If <see cref="EasingMode"/> is to <see cref="EasingMode.None"/> then the Spline will be traversed at MaxSpeed throughout its length.
/// Otherwise, the traversal speed will range from 0 to MaxSpeed throughout the Spline's length depending on the easing mode set.
/// When animation method is set to <see cref="Method.Speed"/> this setter will set the <see cref="maxSpeed"/> value and automatically recalculate <see cref="duration"/>,
/// otherwise, it will have no effect.
/// </remarks>
public float maxSpeed
{
get => m_MaxSpeed;
set
{
if (m_Method == Method.Speed)
{
m_MaxSpeed = Mathf.Max(0f, value);
CalculateDuration();
}
}
}
/// <summary> Easing mode used when animating the object along the Spline. See <see cref="EasingMode"/> for details. </summary>
public EasingMode easingMode
{
get => m_EasingMode;
set => m_EasingMode = value;
}
/// <summary> The way the object should align when animating along the Spline. See <see cref="AlignmentMode"/> for details. </summary>
public AlignmentMode alignmentMode
{
get => m_AlignmentMode;
set => m_AlignmentMode = value;
}
/// <summary> Object space axis that should be considered as the object's forward vector. </summary>
public AlignAxis objectForwardAxis
{
get => m_ObjectForwardAxis;
set => m_ObjectUpAxis = SetObjectAlignAxis(value, ref m_ObjectForwardAxis, m_ObjectUpAxis);
}
/// <summary> Object space axis that should be considered as the object's up vector. </summary>
public AlignAxis objectUpAxis
{
get => m_ObjectUpAxis;
set => m_ObjectForwardAxis = SetObjectAlignAxis(value, ref m_ObjectUpAxis, m_ObjectForwardAxis);
}
/// <summary>
/// Normalized time of the Spline's traversal. The integer part is the number of times the Spline has been traversed.
/// The fractional part is the % (0-1) of progress in the current loop.
/// </summary>
public float normalizedTime
{
get => m_NormalizedTime;
set
{
m_NormalizedTime = value;
m_ElapsedTime = m_Duration * m_NormalizedTime;
UpdateTransform();
}
}
/// <summary> Total time (in seconds) since the start of Spline's traversal. </summary>
public float elapsedTime
{
get => m_ElapsedTime;
set
{
m_ElapsedTime = value;
CalculateNormalizedTime(0f);
UpdateTransform();
}
}
/// <summary> Returns true if object is currently animating along the Spline. </summary>
public bool isPlaying => m_Playing;
/// <summary> Invoked each time object's animation along the Spline is updated.</summary>
public event Action<Vector3, Quaternion> onUpdated;
void Awake()
{
CalculateSplineLength();
Restart(m_PlayOnAwake);
}
void OnEnable()
{
if (m_Target != null && m_Target.Spline != null)
m_Target.Spline.changed += OnSplineChange;
}
void OnDisable()
{
if (m_Target != null && m_Target.Spline != null)
m_Target.Spline.changed -= OnSplineChange;
}
void OnValidate()
{
switch (m_Method)
{
case Method.Time:
m_Duration = Mathf.Max(0f, m_Duration);
CalculateMaxSpeed();
break;
case Method.Speed:
m_MaxSpeed = Mathf.Max(0f, m_MaxSpeed);
CalculateDuration();
break;
default:
Debug.Log($"{m_Method} animation method is not supported!");
break;
}
}
bool CheckForNullContainerOrSpline()
{
if (m_Target == null || m_Target.Spline == null)
{
Debug.LogError("Spline Follow does not have a valid SplineContainer set.");
return true;
}
return false;
}
/// <summary> Begin animating object along the Spline. </summary>
public void Play()
{
if (CheckForNullContainerOrSpline())
return;
m_Playing = true;
#if UNITY_EDITOR
m_LastEditorUpdateTime = EditorApplication.timeSinceStartup;
#endif
}
/// <summary> Pause object's animation along the Spline. </summary>
public void Pause()
{
m_Playing = false;
}
/// <summary> Stop the animation and place the object at the beginning of the Spline. </summary>
/// <param name="autoplay"> If true, the animation along the Spline will start over again. </param>
public void Restart(bool autoplay)
{
if (CheckForNullContainerOrSpline())
return;
m_Playing = false;
m_ElapsedTime = 0f;
normalizedTime = 0f;
switch (m_Method)
{
case Method.Time:
CalculateMaxSpeed();
break;
case Method.Speed:
CalculateDuration();
break;
default:
Debug.Log($"{m_Method} animation method is not supported!");
break;
}
UpdateTransform();
if (autoplay)
Play();
}
/// <summary>
/// Evaluates the animation along the Spline based on deltaTime.
/// </summary>
public void Update()
{
if (!m_Playing || (m_LoopMode == LoopMode.Once && m_NormalizedTime >= 1f))
return;
var dt = Time.deltaTime;
#if UNITY_EDITOR
if (!EditorApplication.isPlaying)
{
dt = (float)(EditorApplication.timeSinceStartup - m_LastEditorUpdateTime);
m_LastEditorUpdateTime = EditorApplication.timeSinceStartup;
}
#endif
CalculateNormalizedTime(dt);
UpdateTransform();
}
void CalculateNormalizedTime(float deltaTime)
{
m_ElapsedTime += deltaTime;
var currentDuration = m_Duration;
var t = 0f;
switch (m_LoopMode)
{
case LoopMode.Once:
t = Mathf.Min(m_ElapsedTime, currentDuration);
break;
case LoopMode.Loop:
t = m_ElapsedTime % currentDuration;
break;
case LoopMode.LoopEaseInOnce:
/* If the first loop had an ease in, then our velocity is double that of linear traversal.
Therefore time to traverse subsequent loops should be half of the first loop. */
if ((m_EasingMode == EasingMode.EaseIn || m_EasingMode == EasingMode.EaseInOut) &&
m_ElapsedTime >= currentDuration)
currentDuration *= 0.5f;
t = m_ElapsedTime % currentDuration;
break;
case LoopMode.PingPong:
t = Mathf.PingPong(m_ElapsedTime, currentDuration);
break;
default:
Debug.Log($"{m_LoopMode} animation loop mode is not supported!");
break;
}
t /= currentDuration;
if (m_LoopMode == LoopMode.LoopEaseInOnce)
{
// Apply ease in for the first loop and continue linearly for remaining loops
if ((m_EasingMode == EasingMode.EaseIn || m_EasingMode == EasingMode.EaseInOut) &&
m_ElapsedTime < currentDuration)
t = EaseInQuadratic(t);
}
else
{
switch (m_EasingMode)
{
case EasingMode.EaseIn:
t = EaseInQuadratic(t);
break;
case EasingMode.EaseOut:
t = EaseOutQuadratic(t);
break;
case EasingMode.EaseInOut:
t = EaseInOutQuadratic(t);
break;
}
}
m_NormalizedTime = Mathf.Floor(m_NormalizedTime) + t;
if (m_NormalizedTime >= 1f && m_LoopMode == LoopMode.Once)
m_Playing = false;
}
void UpdateTransform()
{
if (m_Target == null)
return;
EvaluatePositionAndRotation(out var position, out var rotation);
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
#endif
transform.position = position;
if (m_AlignmentMode != AlignmentMode.None)
transform.rotation = rotation;
#if UNITY_EDITOR
}
#endif
onUpdated?.Invoke(position, rotation);
}
void EvaluatePositionAndRotation(out Vector3 position, out Quaternion rotation)
{
var t = GetLoopInterpolation();
position = m_Target.EvaluatePosition(t);
rotation = Quaternion.identity;
// Correct forward and up vectors based on axis remapping parameters
var remappedForward = GetAxis(m_ObjectForwardAxis);
var remappedUp = GetAxis(m_ObjectUpAxis);
var axisRemapRotation = Quaternion.Inverse(Quaternion.LookRotation(remappedForward, remappedUp));
if (m_AlignmentMode != AlignmentMode.None)
{
var forward = Vector3.forward;
var up = Vector3.up;
switch (m_AlignmentMode)
{
case AlignmentMode.SplineElement:
forward = Vector3.Normalize(m_Target.EvaluateTangent(t));
up = m_Target.EvaluateUpVector(t);
break;
case AlignmentMode.SplineObject:
var objectRotation = m_Target.transform.rotation;
forward = objectRotation * forward;
up = objectRotation * up;
break;
default:
Debug.Log($"{m_AlignmentMode} animation aligment mode is not supported!");
break;
}
rotation = Quaternion.LookRotation(forward, up) * axisRemapRotation;
}
else
rotation = axisRemapRotation;
}
void CalculateDuration()
{
if (m_SplineLength < 0f)
CalculateSplineLength();
if (m_SplineLength >= 0f)
{
switch (m_EasingMode)
{
case EasingMode.None:
m_Duration = m_SplineLength / m_MaxSpeed;
break;
case EasingMode.EaseIn:
case EasingMode.EaseOut:
case EasingMode.EaseInOut:
m_Duration = (2f * m_SplineLength) / m_MaxSpeed;
break;
default:
Debug.Log($"{m_EasingMode} animation easing mode is not supported!");
break;
}
}
}
void CalculateMaxSpeed()
{
if (m_SplineLength <= 0f)
CalculateSplineLength();
if (m_SplineLength >= 0f)
{
switch (m_EasingMode)
{
case EasingMode.None:
m_MaxSpeed = m_SplineLength / m_Duration;
break;
case EasingMode.EaseIn:
case EasingMode.EaseOut:
case EasingMode.EaseInOut:
m_MaxSpeed = (2f * m_SplineLength) / m_Duration;
break;
default:
Debug.Log($"{m_EasingMode} animation easing mode is not supported!");
break;
}
}
}
void CalculateSplineLength()
{
if (m_Target != null)
m_SplineLength = m_Target.CalculateLength();
}
AlignAxis SetObjectAlignAxis(AlignAxis newValue, ref AlignAxis targetAxis, AlignAxis otherAxis)
{
// Swap axes if the new value matches that of the other axis
if (newValue == otherAxis)
{
otherAxis = targetAxis;
targetAxis = newValue;
}
// Do not allow configuring object's forward and up axes as opposite
else if ((int) newValue % 3 != (int) otherAxis % 3)
targetAxis = newValue;
return otherAxis;
}
void OnSplineChange()
{
CalculateSplineLength();
switch (m_Method)
{
case Method.Time:
CalculateMaxSpeed();
break;
case Method.Speed:
CalculateDuration();
break;
default:
Debug.Log($"{m_Method} animation method is not supported!");
break;
}
}
internal float GetLoopInterpolation()
{
var t = 0f;
if (Mathf.Floor(normalizedTime) == normalizedTime)
t = Mathf.Clamp01(normalizedTime);
else
t = normalizedTime % 1f;
return t;
}
float EaseInQuadratic(float t)
{
return t * t;
}
float EaseOutQuadratic(float t)
{
return t * (2f - t);
}
float EaseInOutQuadratic(float t)
{
var eased = 2f * t * t;
if (t > 0.5f)
eased = 4f * t - eased - 1f;
return eased;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29a074d529cf945029ef7cf40540c9df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,47 @@
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// Base class for SplineInstantiate and SplineExtrude, contains common elements to both of these Components
/// </summary>
public abstract class SplineComponent : MonoBehaviour
{
/// <summary>
/// Describes the different types of object alignment axes.
/// </summary>
public enum AlignAxis
{
/// <summary> Object space X axis. </summary>
[InspectorName("Object X+")]
XAxis,
/// <summary> Object space Y axis. </summary>
[InspectorName("Object Y+")]
YAxis,
/// <summary> Object space Z axis. </summary>
[InspectorName("Object Z+")]
ZAxis,
/// <summary> Object space negative X axis. </summary>
[InspectorName("Object X-")]
NegativeXAxis,
/// <summary> Object space negative Y axis. </summary>
[InspectorName("Object Y-")]
NegativeYAxis,
/// <summary> Object space negative Z axis. </summary>
[InspectorName("Object Z-")]
NegativeZAxis
}
readonly float3[] m_AlignAxisToVector = new float3[] {math.right(), math.up(), math.forward(), math.left(), math.down(), math.back()};
/// <summary>
/// Transform a AlignAxis to the associated float3 direction.
/// </summary>
/// <param name="axis">The AlignAxis to transform</param>
/// <returns></returns>
protected float3 GetAxis(AlignAxis axis)
{
return m_AlignAxisToVector[(int) axis];
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8647d8846516479e99dbf89e8b927446
timeCreated: 1643399835

View file

@ -0,0 +1,165 @@
using System.Collections.Generic;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// A Component that holds a <see cref="Spline"/> object.
/// </summary>
#if UNITY_2021_2_OR_NEWER
[Icon(k_IconPath)]
#endif
[AddComponentMenu("Splines/Spline")]
public sealed class SplineContainer : MonoBehaviour, ISplineProvider
{
const string k_IconPath = "Packages/com.unity.splines/Editor/Resources/Icons/SplineComponent.png";
readonly Spline[] m_SplineArray = new Spline[1];
[SerializeField]
Spline m_Spline = new Spline();
IEnumerable<Spline> ISplineProvider.Splines
{
get
{
m_SplineArray[0] = Spline;
return m_SplineArray;
}
}
bool IsScaled => transform.lossyScale != Vector3.one;
/// <summary>
/// The instantiated <see cref="Spline"/> object attached to this component.
/// </summary>
public Spline Spline
{
get => m_Spline;
set => m_Spline = value;
}
/// <summary>
/// Compute interpolated position, direction and upDirection at ratio t. Calling this method to get the
/// 3 vectors is faster than calling independently EvaluateSplinePosition, EvaluateSplineTangent and EvaluateSplineUpVector
/// for the same time t as it reduces some redundant computation.
/// </summary>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <param name="position">Output variable for the float3 position at t.</param>
/// <param name="tangent">Output variable for the float3 tangent at t.</param>
/// <param name="upVector">Output variable for the float3 up direction at t.</param>
/// <returns>Boolean value, true if a valid set of output variables as been computed.</returns>
public bool Evaluate(float t, out float3 position, out float3 tangent, out float3 upVector)
{
if (Spline == null)
{
position = float3.zero;
tangent = new float3(0, 0, 1);
upVector = new float3(0, 1, 0);
return false;
}
if (IsScaled)
{
using var nativeSpline = new NativeSpline(Spline, transform.localToWorldMatrix);
return SplineUtility.Evaluate(nativeSpline, t, out position, out tangent, out upVector);
}
var evaluationStatus = SplineUtility.Evaluate(Spline, t, out position, out tangent, out upVector);
if (evaluationStatus)
{
position = transform.TransformPoint(position);
tangent = transform.TransformVector(tangent);
upVector = transform.TransformDirection(upVector);
}
return evaluationStatus;
}
/// <summary>
/// Evaluate a tangent vector on a curve at a specific t in world space.
/// </summary>
/// <param name="t">A value between 0 and 1 representing a percentage of the curve.</param>
/// <returns>A tangent vector.</returns>
public float3 EvaluatePosition(float t)
{
if(Spline == null)
return float.PositiveInfinity;
if(IsScaled)
{
using var nativeSpline = new NativeSpline(Spline, transform.localToWorldMatrix);
return SplineUtility.EvaluatePosition(nativeSpline, t);
}
return transform.TransformPoint(SplineUtility.EvaluatePosition(Spline, t));
}
/// <summary>
/// Evaluate a tangent vector at a specific t in world space.
/// </summary>
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline</param>
/// <returns>A tangent vector</returns>
public float3 EvaluateTangent(float t)
{
if (Spline == null)
return 0;
if(IsScaled)
{
using var nativeSpline = new NativeSpline(Spline, transform.localToWorldMatrix);
return SplineUtility.EvaluateTangent(nativeSpline, t);
}
return transform.TransformVector(SplineUtility.EvaluateTangent(Spline, t));
}
/// <summary>
/// Evaluate an up vector direction at a specific t
/// </summary>
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline</param>
/// <returns>An up direction.</returns>
public float3 EvaluateUpVector(float t)
{
if (Spline == null)
return float.PositiveInfinity;
if(IsScaled)
{
using var nativeSpline = new NativeSpline(Spline, transform.localToWorldMatrix);
return SplineUtility.EvaluateUpVector(nativeSpline, t);
}
//Using TransformDirection as up direction is Not sensible to scale
return transform.TransformDirection(SplineUtility.EvaluateUpVector(Spline, t));
}
/// <summary>
/// Evaluate an acceleration vector at a specific t
/// </summary>
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline</param>
/// <returns>An acceleration vector.</returns>
public float3 EvaluateAcceleration(float t)
{
if (Spline == null)
return float.PositiveInfinity;
if(IsScaled)
{
using var nativeSpline = new NativeSpline(Spline, transform.localToWorldMatrix);
return SplineUtility.EvaluateAcceleration(nativeSpline, t);
}
return transform.TransformVector(SplineUtility.EvaluateAcceleration(Spline, t));
}
/// <summary>
/// Calculate the length of <see cref="Spline"/> in world space.
/// </summary>
/// <returns>The length of <see cref="Spline"/> in world space</returns>
public float CalculateLength()
{
if(Spline == null)
return 0;
return SplineUtility.CalculateLength(Spline, transform.localToWorldMatrix);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dab5c7d4c32e743048dfca98e2d5914f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,518 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// To calculate a value at some distance along a spline, interpolation is required. The IInterpolator interface
/// allows you to define how data is interpreted given a start value, end value, and normalized interpolation value
/// (commonly referred to as 't').
/// </summary>
/// <typeparam name="T">
/// The data type to interpolate.
/// </typeparam>
public interface IInterpolator<T>
{
/// <summary>
/// Calculate a value between from and to at time interval.
/// </summary>
/// <param name="from">The starting value. At t = 0 this method should return an unmodified 'from' value.</param>
/// <param name="to">The ending value. At t = 1 this method should return an unmodified 'to' value.</param>
/// <param name="t">A percentage between 'from' and 'to'. Must be between 0 and 1.</param>
/// <returns>A value between 'from' and 'to'.</returns>
T Interpolate(T from, T to, float t);
}
/// <summary>
/// Describes the unit of measurement used by <see cref="DataPoint{T}"/>.
/// </summary>
public enum PathIndexUnit
{
/// <summary>
/// The 't' value used when interpolating is measured in game units. Values range from 0 (start of Spline) to
/// <see cref="Spline.GetLength()"/> (end of Spline).
/// </summary>
Distance,
/// <summary>
/// The 't' value used when interpolating is normalized. Values range from 0 (start of Spline) to 1 (end of Spline).
/// </summary>
Normalized,
/// <summary>
/// The 't' value used when interpolating is defined by knot indices and a fractional value representing the
/// normalized interpolation between the specific knot index and the next knot.
/// </summary>
Knot
}
/// <summary>
/// The SplineData{T} class is used to store information relative to a <see cref="Spline"/> without coupling data
/// directly to the Spline class. SplineData can store any type of data, and provides options for how to index
/// DataPoints.
/// </summary>
/// <typeparam name="T"> The type of data to store. </typeparam>
[Serializable]
public class SplineData<T> : IEnumerable<DataPoint<T>>
{
static readonly DataPointComparer<DataPoint<T>> k_DataPointComparer = new DataPointComparer<DataPoint<T>>();
[SerializeField]
PathIndexUnit m_IndexUnit = PathIndexUnit.Knot;
[SerializeField]
List<DataPoint<T>> m_DataPoints = new List<DataPoint<T>>();
// When working with IMGUI it's necessary to keep keys array consistent while a hotcontrol is active. Most
// accessors will keep the SplineData sorted, but sometimes it's not possible.
[NonSerialized]
bool m_NeedsSort;
/// <summary>
/// Access a <see cref="DataPoint{T}"/> by index. DataPoints are sorted in ascending order by the
/// <see cref="DataPoint{DataType}.Index"/> value.
/// </summary>
/// <param name="index">
/// The index of the DataPoint to access.
/// </param>
public DataPoint<T> this[int index]
{
get => m_DataPoints[index];
set => SetDataPoint(index, value);
}
/// <summary>
/// PathIndexUnit defines how SplineData will interpret 't' values when interpolating data.
/// </summary>
/// <seealso cref="PathIndexUnit"/>
public PathIndexUnit PathIndexUnit
{
get => m_IndexUnit;
set => m_IndexUnit = value;
}
/// <summary>
/// How many data points the SplineData collection contains.
/// </summary>
public int Count => m_DataPoints.Count;
/// <summary>
/// The DataPoint Indexes of the current SplineData.
/// </summary>
public IEnumerable<float> Indexes => m_DataPoints.Select(dp => dp.Index);
/// <summary>
/// Invoked any time a SplineData is modified.
/// </summary>
/// <remarks>
/// In the editor this can be invoked many times per-frame.
/// Prefer to use <see cref="UnityEditor.Splines.EditorSplineUtility.RegisterSplineDataChanged"/> when working with
/// splines in the editor.
/// </remarks>
public event Action changed;
#if UNITY_EDITOR
bool m_Dirty = false;
internal static Action<SplineData<T>> afterSplineDataWasModified;
#endif
/// <summary>
/// Create a new SplineData instance.
/// </summary>
public SplineData() {}
/// <summary>
/// Create a new SplineData instance with a single value in it.
/// </summary>
/// <param name="init">
/// A single value to add to the spline data at t = 0.`
/// </param>
public SplineData(T init)
{
Add(0f, init);
SetDirty();
}
/// <summary>
/// Create a new SplineData instance and initialize it with a collection of data points. DataPoints will be sorted and stored
/// in ascending order by <see cref="DataPoint{DataType}.Index"/>.
/// </summary>
/// <param name="dataPoints">
/// A collection of DataPoints to initialize SplineData.`
/// </param>
public SplineData(IEnumerable<DataPoint<T>> dataPoints)
{
foreach(var dataPoint in dataPoints)
Add(dataPoint);
SetDirty();
}
void SetDirty()
{
changed?.Invoke();
#if UNITY_EDITOR
if(m_Dirty)
return;
m_Dirty = true;
UnityEditor.EditorApplication.delayCall += () =>
{
afterSplineDataWasModified?.Invoke(this);
m_Dirty = false;
};
#endif
}
/// <summary>
/// Append a <see cref="DataPoint{T}"/> to this collection.
/// </summary>
/// <param name="t">
/// The interpolant relative to Spline. How this value is interpreted is dependent on <see cref="get_PathIndexUnit"/>.
/// </param>
/// <param name="data">
/// The data to store in the created data point.
/// </param>
public void Add(float t, T data) => Add(new DataPoint<T>(t, data));
/// <summary>
/// Append a <see cref="DataPoint{T}"/> to this collection.
/// </summary>
/// <param name="dataPoint">
/// The data point to append to the SplineData collection.
/// </param>
/// <returns>
/// The index of the inserted dataPoint.
/// </returns>
public int Add(DataPoint<T> dataPoint)
{
int index = m_DataPoints.BinarySearch(0, Count, dataPoint, k_DataPointComparer);
index = index < 0 ? ~index : index;
m_DataPoints.Insert(index, dataPoint);
SetDirty();
return index;
}
/// <summary>
/// Append a <see cref="DataPoint{T}"/> with default value to this collection.
/// </summary>
/// <param name="t">
/// The interpolant relative to Spline. How this value is interpreted is dependent on <see cref="get_PathIndexUnit"/>.
/// </param>
/// <returns>
/// The index of the inserted dataPoint.
/// </returns>
public int AddDataPointWithDefaultValue(float t)
{
var dataPoint = new DataPoint<T>() { Index = t };
if(Count == 0)
return Add(dataPoint);
if(Count == 1)
{
dataPoint.Value = m_DataPoints[0].Value;
return Add(dataPoint);
}
int index = m_DataPoints.BinarySearch(0, Count, dataPoint, k_DataPointComparer);
index = index < 0 ? ~index : index;
dataPoint.Value = index == 0 ? m_DataPoints[0].Value : m_DataPoints[index-1].Value;
m_DataPoints.Insert(index, dataPoint);
SetDirty();
return index;
}
/// <summary>
/// Remove a <see cref="DataPoint{T}"/> at index.
/// </summary>
/// <param name="index">The index to remove.</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
m_DataPoints.RemoveAt(index);
SetDirty();
}
/// <summary>
/// Remove a <see cref="DataPoint{T}"/> from this collection, if one exists.
/// </summary>
/// <param name="t">
/// The interpolant relative to Spline. How this value is interpreted is dependent on <see cref="get_PathIndexUnit"/>.
/// </param>
/// <returns>
/// True is deleted, false otherwise.
/// </returns>
public bool RemoveDataPoint(float t)
{
var removed = m_DataPoints.Remove(m_DataPoints.FirstOrDefault(point => Mathf.Approximately(point.Index, t)));
if(removed)
SetDirty();
return removed;
}
/// <summary>
/// Move a <see cref="DataPoint{T}"/> (if it exists) from this collection, from one index to the another.
/// </summary>
/// <param name="index">The index of the <see cref="DataPoint{T}"/> to move.</param>
/// <param name="newIndex">The new index for this <see cref="DataPoint{T}"/>.</param>
/// <returns>The index of the modified <see cref="DataPoint{T}"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public int MoveDataPoint(int index, float newIndex)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
var dataPoint = m_DataPoints[index];
if(Mathf.Approximately(newIndex, dataPoint.Index))
return index;
RemoveAt(index);
dataPoint.Index = newIndex;
int newRealIndex = Add(dataPoint);
return newRealIndex;
}
/// <summary>
/// Remove all data points.
/// </summary>
public void Clear()
{
m_DataPoints.Clear();
SetDirty();
}
static int Wrap(int value, int lowerBound, int upperBound)
{
int range_size = upperBound - lowerBound + 1;
if(value < lowerBound)
value += range_size * ( ( lowerBound - value ) / range_size + 1 );
return lowerBound + ( value - lowerBound ) % range_size;
}
int ResolveBinaryIndex(int index, bool wrap)
{
index = ( index < 0 ? ~index : index ) - 1;
if(wrap)
index = Wrap(index, 0, Count - 1);
return math.clamp(index, 0, Count - 1);
}
(int, int, float) GetIndex(float t, float splineLength, int knotCount, bool closed)
{
if(Count < 1)
return default;
SortIfNecessary();
float splineLengthInIndexUnits = splineLength;
if(m_IndexUnit == PathIndexUnit.Normalized)
splineLengthInIndexUnits = 1f;
else if(m_IndexUnit == PathIndexUnit.Knot)
splineLengthInIndexUnits = closed ? knotCount : knotCount - 1;
float maxDataPointTime = m_DataPoints[m_DataPoints.Count - 1].Index;
float maxRevolutionLength = math.ceil(maxDataPointTime / splineLengthInIndexUnits) * splineLengthInIndexUnits;
float maxTime = closed ? math.max(maxRevolutionLength, splineLengthInIndexUnits) : splineLengthInIndexUnits;
if(closed)
{
if(t < 0f)
t = maxTime + t % maxTime;
else
t = t % maxTime;
}
else
t = math.clamp(t, 0f, maxTime);
int index = m_DataPoints.BinarySearch(0, Count, new DataPoint<T>(t, default), k_DataPointComparer);
int fromIndex = ResolveBinaryIndex(index, closed);
int toIndex = closed ? ( fromIndex + 1 ) % Count : math.clamp(fromIndex + 1, 0, Count - 1);
float fromTime = m_DataPoints[fromIndex].Index;
float toTime = m_DataPoints[toIndex].Index;
if(fromIndex > toIndex)
toTime += maxTime;
if(t < fromTime && closed)
t += maxTime;
if(fromTime == toTime)
return ( fromIndex, toIndex, fromTime );
return ( fromIndex, toIndex, math.abs(math.max(0f, t - fromTime) / ( toTime - fromTime )) );
}
/// <summary>
/// Calculate an interpolated value at a given 't' along a spline.
/// </summary>
/// <param name="spline">The Spline to interpolate.</param>
/// <param name="t">The interpolator value. How this is interpreted is defined by <see cref="PathIndexUnit"/>.</param>
/// <param name="indexUnit">The <see cref="PathIndexUnit"/> that <paramref name="t"/> is represented as.</param>
/// <param name="interpolator">The <see cref="IInterpolator{T}"/> to use. A collection of commonly used
/// interpolators are available in the <see cref="UnityEngine.Splines.Interpolators"/> namespace.</param>
/// <typeparam name="TInterpolator">The IInterpolator type.</typeparam>
/// <typeparam name="TSpline">The Spline type.</typeparam>
/// <returns>An interpolated value.</returns>
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, PathIndexUnit indexUnit, TInterpolator interpolator)
where TSpline : ISpline
where TInterpolator : IInterpolator<T>
{
if(indexUnit == m_IndexUnit)
return Evaluate(spline, t, interpolator);
return Evaluate(spline, SplineUtility.ConvertIndexUnit(spline, t, indexUnit, m_IndexUnit), interpolator);
}
/// <summary>
/// Calculate an interpolated value at a given 't' along a spline.
/// </summary>
/// <param name="spline">The Spline to interpolate.</param>
/// <param name="t">The interpolator value. How this is interpreted is defined by <see cref="PathIndexUnit"/>.</param>
/// <param name="interpolator">The <see cref="IInterpolator{T}"/> to use. A collection of commonly used
/// interpolators are available in the <see cref="UnityEngine.Splines.Interpolators"/> namespace.</param>
/// <typeparam name="TInterpolator">The IInterpolator type.</typeparam>
/// <typeparam name="TSpline">The Spline type.</typeparam>
/// <returns>An interpolated value.</returns>
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, TInterpolator interpolator)
where TSpline : ISpline
where TInterpolator : IInterpolator<T>
{
var knotCount = spline.Count;
if(knotCount < 1 || m_DataPoints.Count == 0)
return default;
var indices = GetIndex(t, spline.GetLength(), knotCount, spline.Closed);
DataPoint<T> a = m_DataPoints[indices.Item1];
DataPoint<T> b = m_DataPoints[indices.Item2];
return interpolator.Interpolate(a.Value, b.Value, indices.Item3);
}
/// <summary>
/// Set the data for a <see cref="DataPoint{T}"/> at an index.
/// </summary>
/// <param name="index">The DataPoint index.</param>
/// <param name="value">The value to set.</param>
/// <remarks>
/// Using this method will search the DataPoint list and invoke the <see cref="changed"/>
/// callback every time. This may be inconvenient when setting multiple DataPoints during the same frame.
/// In this case, consider calling <see cref="SetDataPointNoSort"/> for each DataPoint, followed by
/// a single call to <see cref="SortIfNecessary"/>. Note that the call to <see cref="SortIfNecessary"/> is
/// optional and can be omitted if DataPoint sorting is not required and the <see cref="changed"/> callback
/// should not be invoked.
/// </remarks>
public void SetDataPoint(int index, DataPoint<T> value)
{
if(index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
RemoveAt(index);
Add(value);
SetDirty();
}
/// <summary>
/// Set the data for a <see cref="DataPoint{T}"/> at an index.
/// </summary>
/// <param name="index">The DataPoint index.</param>
/// <param name="value">The value to set.</param>
/// <remarks>
/// Use this method as an altenative to <see cref="SetDataPoint"/> when manual control
/// over DataPoint sorting and the <see cref="changed"/> callback is required.
/// See also <see cref="SortIfNecessary"/>.
/// </remarks>
public void SetDataPointNoSort(int index, DataPoint<T> value)
{
if(index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
// could optimize this by storing affected range
m_NeedsSort = true;
m_DataPoints[index] = value;
}
/// <summary>
/// Triggers sorting of the <see cref="DataPoint{T}"/> list if the data is dirty.
/// </summary>
/// <remarks>
/// Call this after a single or series of calls to <see cref="SetDataPointNoSort"/>.
/// This will trigger DataPoint sort and invoke the <see cref="changed"/> callback.
/// This method has two main use cases: to prevent frequent <see cref="changed"/> callback
/// calls within the same frame and to reduce multiple DataPoints list searches
/// to a single sort in performance critical paths.
/// </remarks>
public void SortIfNecessary()
{
if(!m_NeedsSort)
return;
m_NeedsSort = false;
m_DataPoints.Sort();
SetDirty();
}
internal void ForceSort()
{
m_NeedsSort = true;
SortIfNecessary();
}
/// <summary>
/// Given a spline and a target PathIndex Unit, convert the SplineData to a new PathIndexUnit without changing the final positions on the Spline.
/// </summary>
/// <typeparam name="TSplineType">The Spline type.</typeparam>
/// <param name="spline">The Spline to use for the conversion, this is necessary to compute most of PathIndexUnits.</param>
/// <param name="toUnit">The unit to convert SplineData to.</param>>
public void ConvertPathUnit<TSplineType>(TSplineType spline, PathIndexUnit toUnit)
where TSplineType : ISpline
{
if(toUnit == m_IndexUnit)
return;
for(int i = 0; i<m_DataPoints.Count; i++)
{
var dataPoint = m_DataPoints[i];
var newTime = spline.ConvertIndexUnit(dataPoint.Index, m_IndexUnit, toUnit);
m_DataPoints[i] = new DataPoint<T>(newTime, dataPoint.Value);
}
m_IndexUnit = toUnit;
SetDirty();
}
/// <summary>
/// Given a time value using a certain PathIndexUnit type, calculate the normalized time value regarding a specific spline.
/// </summary>
/// <param name="spline">The Spline to use for the conversion, this is necessary to compute Normalized and Distance PathIndexUnits.</param>
/// <param name="t">The time to normalize in the original PathIndexUnit.</param>>
/// <typeparam name="TSplineType">The Spline type.</typeparam>
/// <returns>The normalized time.</returns>
public float GetNormalizedInterpolation<TSplineType>(TSplineType spline, float t) where TSplineType : ISpline
{
return SplineUtility.GetNormalizedInterpolation(spline, t, m_IndexUnit);
}
/// <inheritdoc cref="GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through the DataPoints collection.
/// </summary>
/// <returns>
/// An IEnumerator{DataPoint{T}} for this collection.</returns>
public IEnumerator<DataPoint<T>> GetEnumerator()
{
for (int i = 0, c = Count; i < c; ++i)
yield return m_DataPoints[i];
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5475addd28b94489a727bbc680f89a3d
timeCreated: 1613497560

View file

@ -0,0 +1,12 @@
using System;
using UnityEngine;
namespace UnityEngine.Splines
{
/// <summary>
/// SplineDataHandleAttribute can be use to add custom handles to <see cref="SplineData{T}"/>.
/// The custom drawer class must inherit from SplineDataHandle and override one of the Draw static method.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public abstract class SplineDataHandleAttribute : Attribute {}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f26841404a934577bf234bc14475f649
timeCreated: 1625517748

View file

@ -0,0 +1,231 @@
using System;
using System.IO;
using UnityEngine;
namespace UnityEngine.Splines
{
/// <summary>
/// A component for creating a tube mesh from a Spline at runtime.
/// </summary>
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
[AddComponentMenu("Splines/Spline Extrude")]
public class SplineExtrude : MonoBehaviour
{
[SerializeField, Tooltip("The Spline to extrude.")]
SplineContainer m_Container;
[SerializeField, Tooltip("Enable to regenerate the extruded mesh when the target Spline is modified. Disable " +
"this option if the Spline will not be modified at runtime.")]
bool m_RebuildOnSplineChange;
[SerializeField, Tooltip("The maximum number of times per-second that the mesh will be rebuilt.")]
int m_RebuildFrequency = 30;
[SerializeField, Tooltip("Automatically update any Mesh, Box, or Sphere collider components when the mesh is extruded.")]
bool m_UpdateColliders = true;
[SerializeField, Tooltip("The number of sides that comprise the radius of the mesh.")]
int m_Sides = 8;
[SerializeField, Tooltip("The number of edge loops that comprise the length of one unit of the mesh. The " +
"total number of sections is equal to \"Spline.GetLength() * segmentsPerUnit\".")]
float m_SegmentsPerUnit = 4;
[SerializeField, Tooltip("Indicates if the start and end of the mesh are filled. When the Spline is closed this setting is ignored.")]
bool m_Capped = true;
[SerializeField, Tooltip("The radius of the extruded mesh.")]
float m_Radius = .25f;
[SerializeField, Tooltip("The section of the Spline to extrude.")]
Vector2 m_Range = new Vector2(0f, 1f);
Spline m_Spline;
Mesh m_Mesh;
bool m_RebuildRequested;
float m_NextScheduledRebuild;
/// <summary>The SplineContainer of the <see cref="Spline"/> to extrude.</summary>
public SplineContainer container
{
get => m_Container;
set => m_Container = value;
}
/// <summary>
/// Enable to regenerate the extruded mesh when the target Spline is modified. Disable this option if the Spline
/// will not be modified at runtime.
/// </summary>
public bool rebuildOnSplineChange
{
get => m_RebuildOnSplineChange;
set => m_RebuildOnSplineChange = value;
}
/// <summary>The maximum number of times per-second that the mesh will be rebuilt.</summary>
public int rebuildFrequency
{
get => m_RebuildFrequency;
set => m_RebuildFrequency = Mathf.Max(value, 1);
}
/// <summary>How many sides make up the radius of the mesh.</summary>
public int sides
{
get => m_Sides;
set => m_Sides = Mathf.Max(value, 3);
}
/// <summary>How many edge loops comprise the one unit length of the mesh.</summary>
public float segmentsPerUnit
{
get => m_SegmentsPerUnit;
set => m_SegmentsPerUnit = Mathf.Max(value, .0001f);
}
/// <summary>Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</summary>
public bool capped
{
get => m_Capped;
set => m_Capped = value;
}
/// <summary>The radius of the extruded mesh.</summary>
public float radius
{
get => m_Radius;
set => m_Radius = Mathf.Max(value, .00001f);
}
/// <summary>
/// The section of the Spline to extrude.
/// </summary>
public Vector2 range
{
get => m_Range;
set => m_Range = new Vector2(Mathf.Min(value.x, value.y), Mathf.Max(value.x, value.y));
}
/// <summary>The Spline to extrude.</summary>
public Spline spline
{
get
{
// m_Spline is cached in the Start() method, meaning that it is not valid in the Editor.
#if UNITY_EDITOR
return m_Container.Spline;
#else
return m_Spline;
#endif
}
}
void Reset()
{
TryGetComponent(out m_Container);
if (TryGetComponent<MeshFilter>(out var filter) && filter.sharedMesh != null)
m_Mesh = filter.sharedMesh;
else
filter.sharedMesh = m_Mesh = CreateMeshAsset();
if (TryGetComponent<MeshRenderer>(out var renderer) && renderer.sharedMaterial == null)
{
// todo Make Material.GetDefaultMaterial() public
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var mat = cube.GetComponent<MeshRenderer>().sharedMaterial;
DestroyImmediate(cube);
renderer.sharedMaterial = mat;
}
Rebuild();
}
void Start()
{
if (m_Container == null || (m_Spline = m_Container.Spline) == null)
{
Debug.LogError("Spline Extrude does not have a valid SplineContainer set.");
return;
}
if((m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null)
Debug.LogError("SplineExtrude.createMeshInstance is disabled, but there is no valid mesh assigned. " +
"Please create or assign a writable mesh asset.");
if (m_RebuildOnSplineChange)
m_Spline.changed += () => m_RebuildRequested = true;
Rebuild();
}
void Update()
{
if(m_RebuildRequested && Time.time >= m_NextScheduledRebuild)
Rebuild();
}
/// <summary>
/// Triggers the rebuild of a Spline's extrusion mesh and collider.
/// </summary>
public void Rebuild()
{
if(m_Mesh == null && (m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null)
return;
float span = Mathf.Abs(range.y - range.x);
int segments = Mathf.Max((int)Mathf.Ceil(spline.GetLength() * span * m_SegmentsPerUnit), 1);
SplineMesh.Extrude(spline, m_Mesh, m_Radius, m_Sides, segments, m_Capped, m_Range);
m_NextScheduledRebuild = Time.time + 1f / m_RebuildFrequency;
if (m_UpdateColliders)
{
if (TryGetComponent<MeshCollider>(out var meshCollider))
meshCollider.sharedMesh = m_Mesh;
if (TryGetComponent<BoxCollider>(out var boxCollider))
{
boxCollider.center = m_Mesh.bounds.center;
boxCollider.size = m_Mesh.bounds.size;
}
if (TryGetComponent<SphereCollider>(out var sphereCollider))
{
sphereCollider.center = m_Mesh.bounds.center;
var ext = m_Mesh.bounds.extents;
sphereCollider.radius = Mathf.Max(ext.x, ext.y, ext.z);
}
}
}
void OnValidate()
{
Rebuild();
}
internal Mesh CreateMeshAsset()
{
var mesh = new Mesh();
mesh.name = name;
#if UNITY_EDITOR
var scene = SceneManagement.SceneManager.GetActiveScene();
var sceneDataDir = "Assets";
if (!string.IsNullOrEmpty(scene.path))
{
var dir = Path.GetDirectoryName(scene.path);
sceneDataDir = $"{dir}/{Path.GetFileNameWithoutExtension(scene.path)}";
if (!Directory.Exists(sceneDataDir))
Directory.CreateDirectory(sceneDataDir);
}
var path = UnityEditor.AssetDatabase.GenerateUniqueAssetPath($"{sceneDataDir}/{mesh.name}.asset");
UnityEditor.AssetDatabase.CreateAsset(mesh, path);
mesh = UnityEditor.AssetDatabase.LoadAssetAtPath<Mesh>(path);
UnityEditor.EditorGUIUtility.PingObject(mesh);
#endif
return mesh;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 222dc08d484f16869bdd89edd9d368fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,75 @@
using System.Collections.Generic;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// Methods to create spline shapes.
/// </summary>
public static class SplineFactory
{
/// <summary>
/// Create a <see cref="Spline"/> from a list of positions.
/// </summary>
/// <param name="positions">A collection of knot positions.</param>
/// <param name="closed">Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).</param>
/// <returns>A new Spline.</returns>
public static Spline CreateLinear(IList<float3> positions, bool closed = false)
{
var knotCount = positions.Count;
var spline = new Spline(knotCount, closed) { EditType = SplineType.Linear };
for (int i = 0; i < knotCount; ++i)
{
var position = positions[i];
var n = SplineUtility.NextIndex(i, knotCount, closed);
var p = SplineUtility.PreviousIndex(i, knotCount, closed);
var tangentIn = SplineUtility.GetLinearTangent(position, positions[p]);
var tangentOut = SplineUtility.GetLinearTangent(position, positions[n]);
spline.Add(new BezierKnot(position, tangentIn, tangentOut, quaternion.identity));
}
return spline;
}
/// <summary>
/// Create a <see cref="Spline"/> in a square shape with rounding at the edges.
/// </summary>
/// <param name="radius">The distance from center to outermost edge.</param>
/// <param name="rounding">The amount of rounding to apply to corners.</param>
/// <returns>A new Spline.</returns>
public static Spline CreateRoundedSquare(float radius, float rounding)
{
float3 p0 = new float3(-.5f, 0f, -.5f);
float3 p1 = new float3(-.5f, 0f, .5f);
float3 p2 = new float3( .5f, 0f, .5f);
float3 p3 = new float3( .5f, 0f, -.5f);
float3 tanIn = new float3(0f, 0f, -1f);
float3 tanOut = new float3(0f, 0f, 1f);
return new Spline(new BezierKnot[]
{
new BezierKnot(p0 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, -45f, 0f)),
new BezierKnot(p1 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, 45f, 0f)),
new BezierKnot(p2 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, 135f, 0f)),
new BezierKnot(p3 * radius, tanIn * rounding, tanOut * rounding, Quaternion.Euler(0f, -135f, 0f))
}, true) { EditType = SplineType.Bezier };
}
/// <summary>
/// Create a <see cref="Spline"/> in a square shape with sharp corners.
/// </summary>
/// <param name="radius">The distance from center to outermost edge.</param>
/// <returns>A new Spline.</returns>
public static Spline CreateSquare(float radius)
{
float3 p0 = new float3(-.5f, 0f, -.5f);
float3 p1 = new float3(-.5f, 0f, .5f);
float3 p2 = new float3( .5f, 0f, .5f);
float3 p3 = new float3( .5f, 0f, -.5f);
return CreateLinear(new float3[] { p0, p1, p2, p3 }, true);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e9d35cf5830b403f8a24cef0e22ee40c
timeCreated: 1628776092

View file

@ -0,0 +1,828 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// SplineInstantiate is used to automatically instantiate prefabs or objects along a spline.
/// </summary>
[ExecuteInEditMode]
[AddComponentMenu("Splines/Spline Instantiate")]
public class SplineInstantiate : SplineComponent
{
/// <summary>
/// The space in which to interpret the offset, this can be different from the orientation space used to instantiate objects.
/// </summary>
public enum OffsetSpace
{
/// <summary> Use the spline space to orient instances.</summary>
[InspectorName("Spline Element")]
Spline = Space.Spline,
/// <summary> Use the spline GameObject space to orient instances.</summary>
[InspectorName("Spline Object")]
Local = Space.Local,
/// <summary> Use world space to orient instances.</summary>
[InspectorName("World Space")]
World = Space.World,
/// <summary> Use the original object space to orient instances.</summary>
[InspectorName("Instantiated Object")]
Object
}
[Serializable]
internal struct Vector3Offset
{
[Flags]
public enum Setup
{
None = 0x0,
HasOffset= 0x1,
HasCustomSpace= 0x2
}
public Setup setup;
public Vector3 min;
public Vector3 max;
public bool randomX;
public bool randomY;
public bool randomZ;
public OffsetSpace space;
public bool hasOffset => ( setup & Setup.HasOffset ) != 0;
public bool hasCustomSpace => ( setup & Setup.HasCustomSpace ) != 0;
internal Vector3 GetNextOffset()
{
if(( setup & Setup.HasOffset ) != 0)
{
return new Vector3(
randomX ? Random.Range(min.x,max.x) : min.x,
randomY ? Random.Range(min.y,max.y) : min.y,
randomZ ? Random.Range(min.z,max.z) : min.z);
}
return Vector3.zero;
}
internal void CheckMinMaxValidity()
{
max.x = Mathf.Max(min.x, max.x);
max.y = Mathf.Max(min.y, max.y);
max.z = Mathf.Max(min.z, max.z);
}
internal void CheckMinMax()
{
CheckMinMaxValidity();
if(max.magnitude > 0)
setup |= Setup.HasOffset;
else
setup &= ~Setup.HasOffset;
}
internal void CheckCustomSpace(Space instanceSpace)
{
if((int)space == (int)instanceSpace)
setup &= ~Setup.HasCustomSpace;
else
setup |= Setup.HasCustomSpace;
}
}
/// <summary>
/// Describe the item prefab to instantiate and associate it with a probability
/// </summary>
[Serializable]
public struct InstantiableItem
{
/// <summary> The prefab to instantiate.</summary>
public GameObject prefab;
/// <summary> Probability for this prefab. </summary>
public float probability;
}
/// <summary>
/// Describe the possible methods to instantiate instances along the spline.
/// </summary>
public enum Method
{
/// <summary> Use exact number of instances.</summary>
[InspectorName("Instance Count")]
InstanceCount,
/// <summary> Use distance along the spline between 2 instances.</summary>
[InspectorName("Spline Distance")]
SpacingDistance,
/// <summary> Use distance in straight line between 2 instances.</summary>
[InspectorName("Linear Distance")]
LinearDistance
}
/// <summary>
/// Describe the space that is used to orientate the instantiated object
/// </summary>
public enum Space
{
/// <summary> Use the spline space to orient instances.</summary>
[InspectorName("Spline Element")]
Spline,
/// <summary> Use the spline GameObject space to orient instances.</summary>
[InspectorName("Spline Object")]
Local,
/// <summary> Use world space to orient instances.</summary>
[InspectorName("World Space")]
World,
}
[SerializeField]
SplineContainer m_Container;
/// <summary>
/// The SplineContainer containing the targeted spline.
/// </summary>
public SplineContainer container
{
get => m_Container;
set => m_Container = value;
}
[SerializeField]
List<InstantiableItem> m_ItemsToInstantiate = new List<InstantiableItem>();
[SerializeField]
Method m_Method = Method.SpacingDistance;
/// <summary>
/// The instantiation method to use.
/// </summary>
public Method method
{
get => m_Method;
set => m_Method = value;
}
[SerializeField]
Space m_Space = Space.Spline;
/// <summary>
/// The space in which to orient the instanced object.
/// </summary>
public Space space
{
get => m_Space;
set => m_Space = value;
}
[SerializeField]
Vector2 m_Spacing = new Vector2(1f,1f);
/// <summary>
/// Minimum spacing between 2 generated instances,
/// if equal to the maxSpacing, then all instances will have the exact same spacing
/// </summary>
float minSpacing
{
get => m_Spacing.x;
set
{
m_Spacing = new Vector2(value, m_Spacing.y);
ValidateSpacing();
}
}
/// <summary>
/// Maximum spacing between 2 generated instances,
/// if equal to the minSpacing, then all instances will have the exact same spacing
/// </summary>
float maxSpacing
{
get => m_Spacing.y;
set
{
m_Spacing = new Vector2(m_Spacing.x, value);
ValidateSpacing();
}
}
[SerializeField]
AlignAxis m_Up = AlignAxis.YAxis;
/// <summary>
/// Up axis of the object, by default set to the Y Axis
/// </summary>
public AlignAxis upAxis
{
get => m_Up;
set => m_Up = value;
}
[SerializeField]
AlignAxis m_Forward = AlignAxis.ZAxis;
/// <summary>
/// Forward axis of the object, by default set to the Z Axis
/// </summary>
public AlignAxis forwardAxis
{
get => m_Forward;
set
{
m_Forward = value;
ValidateAxis();
}
}
[SerializeField]
Vector3Offset m_PositionOffset;
/// <summary>
/// Minimum (X,Y,Z) position offset to randomize instanced objects positions.
/// (X,Y and Z) values have to be lower to the ones of maxPositionOffset.
/// </summary>
public Vector3 minPositionOffset
{
get => m_PositionOffset.min;
set
{
m_PositionOffset.min = value;
m_PositionOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) position offset to randomize instanced objects positions.
/// (X,Y and Z) values have to be higher to the ones of minPositionOffset.
/// </summary>
public Vector3 maxPositionOffset
{
get => m_PositionOffset.max;
set
{
m_PositionOffset.max = value;
m_PositionOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset positions of the instances.
/// </summary>
public OffsetSpace positionSpace
{
get => m_PositionOffset.space;
set
{
m_PositionOffset.space = value;
m_PositionOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
Vector3Offset m_RotationOffset;
/// <summary>
/// Minimum (X,Y,Z) euler rotation offset to randomize instanced objects rotations.
/// (X,Y and Z) values have to be lower to the ones of maxRotationOffset.
/// </summary>
public Vector3 minRotationOffset
{
get => m_RotationOffset.min;
set
{
m_RotationOffset.min = value;
m_RotationOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) euler rotation offset to randomize instanced objects rotations.
/// (X,Y and Z) values have to be higher to the ones of minRotationOffset.
/// </summary>
public Vector3 maxRotationOffset
{
get => m_RotationOffset.max;
set
{
m_RotationOffset.max = value;
m_RotationOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset rotations of the instances.
/// </summary>
public OffsetSpace rotationSpace
{
get => m_RotationOffset.space;
set
{
m_RotationOffset.space = value;
m_RotationOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
Vector3Offset m_ScaleOffset;
/// <summary>
/// Minimum (X,Y,Z) scale offset to randomize instanced objects scales.
/// (X,Y and Z) values have to be lower to the ones of maxScaleOffset.
/// </summary>
public Vector3 minScaleOffset
{
get => m_ScaleOffset.min;
set
{
m_ScaleOffset.min = value;
m_ScaleOffset.CheckMinMax();
}
}
/// <summary>
/// Maximum (X,Y,Z) scale offset to randomize instanced objects scales.
/// (X,Y and Z) values have to be higher to the ones of minScaleOffset.
/// </summary>
public Vector3 maxScaleOffset
{
get => m_ScaleOffset.max;
set
{
m_ScaleOffset.max = value;
m_ScaleOffset.CheckMinMax();
}
}
/// <summary>
/// Coordinate space to use to offset rotations of the instances (usually OffsetSpace.Object).
/// </summary>
public OffsetSpace scaleSpace
{
get => m_ScaleOffset.space;
set
{
m_ScaleOffset.space = value;
m_ScaleOffset.CheckCustomSpace(m_Space);
}
}
[SerializeField]
List<GameObject> m_Instances = new List<GameObject>();
internal List<GameObject> instances => m_Instances;
bool m_InstancesCacheDirty = false;
[SerializeField]
bool m_AutoRefresh = true;
InstantiableItem m_CurrentItem;
bool m_SplineDirty = false;
float m_MaxProbability = 1f;
float maxProbability
{
get => m_MaxProbability;
set
{
if(m_MaxProbability != value)
{
m_MaxProbability = value;
m_InstancesCacheDirty = true;
}
}
}
[HideInInspector]
[SerializeField]
int m_Seed = 0;
int seed
{
get => m_Seed;
set
{
m_Seed = value;
m_InstancesCacheDirty = true;
Random.InitState(m_Seed);
}
}
void OnEnable()
{
if(m_Seed == 0)
m_Seed = GetInstanceID();
#if UNITY_EDITOR
Undo.undoRedoPerformed += UndoRedoPerformed;
#endif
}
void OnDestroy()
{
#if UNITY_EDITOR
Undo.undoRedoPerformed -= UndoRedoPerformed;
#endif
Clear();
}
void UndoRedoPerformed()
{
m_InstancesCacheDirty = true;
m_SplineDirty = true;
UpdateInstances();
}
void OnValidate()
{
if(m_Container != null && m_Container.Spline != null)
m_Container.Spline.changed += delegate() { m_SplineDirty = m_AutoRefresh; };
ValidateSpacing();
m_SplineDirty = m_AutoRefresh;
float probability = 0;
for(int i = 0; i<m_ItemsToInstantiate.Count; i++)
{
var item = m_ItemsToInstantiate[i];
if(item.prefab != null)
probability += item.probability;
}
m_PositionOffset.CheckMinMaxValidity();
m_RotationOffset.CheckMinMaxValidity();
m_ScaleOffset.CheckMinMaxValidity();
maxProbability = probability;
}
void ValidateSpacing()
{
var xSpacing = Mathf.Max(0.1f, m_Spacing.x);
if(m_Method != Method.LinearDistance)
{
var ySpacing = float.IsNaN(m_Spacing.y) ? xSpacing : Mathf.Max(0.1f, m_Spacing.y);
m_Spacing = new Vector2(xSpacing, Mathf.Max(xSpacing, ySpacing));
} else if(m_Method == Method.LinearDistance)
{
var ySpacing = float.IsNaN(m_Spacing.y) ? m_Spacing.y : xSpacing;
m_Spacing = new Vector2(xSpacing, ySpacing);
}
}
/// <summary>
/// This method prevents Up and Forward axis to be aligned.
/// Up axis will always be kept as the prioritized one.
/// If Forward axis is in the same direction than the Up (or -Up) it'll be changed to the next axis.
/// </summary>
void ValidateAxis()
{
if(m_Forward == m_Up || (int)m_Forward == ( (int)m_Up + 3 ) % 6)
m_Forward = (AlignAxis)(( (int)m_Forward + 1 ) % 6);
}
internal void SetSplineDirty(Spline spline)
{
if(m_Container != null && spline == m_Container.Spline && m_AutoRefresh)
UpdateInstances();
}
void InitContainer()
{
if(m_Container == null)
m_Container = GetComponent<SplineContainer>();
if(m_Container != null && m_Container.Spline != null)
{
m_Container.Spline.changed += () => { m_SplineDirty = m_AutoRefresh; };
}
}
/// <summary>
/// Clear all the created instances along the spline
/// </summary>
public void Clear()
{
SetDirty();
TryClearCache();
}
/// <summary>
/// Set the created instances dirty to erase them next time instances will be generated
/// (otherwise the next generation will reuse cached instances)
/// </summary>
public void SetDirty()
{
m_InstancesCacheDirty = true;
}
void TryClearCache()
{
if(!m_InstancesCacheDirty)
{
for(int i = 0; i < m_Instances.Count; i++)
{
if(m_Instances[i] == null)
{
m_InstancesCacheDirty = true;
break;
}
}
}
if(m_InstancesCacheDirty)
{
for(int i = m_Instances.Count - 1; i >= 0; --i)
{
#if UNITY_EDITOR
DestroyImmediate(m_Instances[i]);
#else
Destroy(m_Instances[i]);
#endif
}
m_Instances.Clear();
m_InstancesCacheDirty = false;
}
}
/// <summary>
/// Change the Random seed to obtain a new generation along the Spline
/// </summary>
public void Randomize()
{
#if UNITY_EDITOR
Undo.RecordObject(this, "Changing SplineInstantiate seed");
#endif
seed = Random.Range(int.MinValue, int.MaxValue);
m_SplineDirty = true;
}
void Update()
{
if(m_SplineDirty)
UpdateInstances();
}
/// <summary>
/// Create and update all instances along the spline based on the list of available prefabs/objects.
/// </summary>
public void UpdateInstances()
{
TryClearCache();
if(m_Container == null)
InitContainer();
if(m_Container == null || m_ItemsToInstantiate.Count == 0)
return;
#if UNITY_EDITOR
Undo.RegisterFullObjectHierarchyUndo(this,"Update spline instances");
#endif
using(var nativeSpline = new NativeSpline(m_Container.Spline, m_Container.transform.localToWorldMatrix, Allocator.TempJob))
{
float currentDist = 0f;
float splineLength = m_Container.CalculateLength();
Random.InitState(m_Seed);
//Spawning instances
var times = new List<float>();
int index = 0;
var spacing = Random.Range(m_Spacing.x, m_Spacing.y);
if(m_Method == Method.InstanceCount && spacing <= 1)
currentDist = (int)spacing == 1 ? splineLength / 2f : splineLength + 1f;
while(currentDist <= splineLength)
{
var prefabIndex = m_ItemsToInstantiate.Count == 1 ? 0 : GetPrefabIndex();
m_CurrentItem = m_ItemsToInstantiate[prefabIndex];
if(m_CurrentItem.prefab == null)
return;
if(index >= m_Instances.Count)
{
#if UNITY_EDITOR
var assetType = PrefabUtility.GetPrefabAssetType(m_CurrentItem.prefab);
if(assetType == PrefabAssetType.MissingAsset)
{
Debug.LogError($"Trying to instantiate a missing asset for item index [{prefabIndex}].");
return;
}
if(assetType != PrefabAssetType.NotAPrefab)
m_Instances.Add(PrefabUtility.InstantiatePrefab(m_CurrentItem.prefab, transform) as GameObject);
else
#endif
m_Instances.Add(Instantiate(m_CurrentItem.prefab, transform));
m_Instances[index].hideFlags |= HideFlags.HideInHierarchy;
}
m_Instances[index].transform.localPosition = m_CurrentItem.prefab.transform.localPosition;
m_Instances[index].transform.localRotation = m_CurrentItem.prefab.transform.localRotation;
m_Instances[index].transform.localScale = m_CurrentItem.prefab.transform.localScale;
times.Add(currentDist / splineLength);
if(m_Method == Method.SpacingDistance)
{
spacing = Random.Range(m_Spacing.x, m_Spacing.y);
currentDist += spacing;
}
else if(m_Method == Method.InstanceCount)
{
if(spacing > 1)
{
var previousDist = currentDist;
currentDist += splineLength / ( nativeSpline.Closed ? (int)spacing : (int)spacing - 1 );
if(previousDist < splineLength && currentDist > splineLength)
currentDist = splineLength;
}
else
currentDist += splineLength;
}
else if(m_Method == Method.LinearDistance)
{
//m_Spacing.y is set to NaN to trigger automatic computation
if(float.IsNaN(m_Spacing.y))
{
var meshfilter = m_Instances[index].GetComponent<MeshFilter>();
var axis = Vector3.right;
if(m_Forward == AlignAxis.ZAxis || m_Forward == AlignAxis.NegativeZAxis)
axis = Vector3.forward;
if(m_Forward == AlignAxis.YAxis || m_Forward == AlignAxis.NegativeYAxis)
axis = Vector3.up;
if(meshfilter == null)
{
meshfilter = m_Instances[index].GetComponentInChildren<MeshFilter>();
if(meshfilter != null)
axis = Vector3.Scale(meshfilter.transform.InverseTransformDirection(m_Instances[index].transform.TransformDirection(axis)), meshfilter.transform.lossyScale);
}
if(meshfilter != null)
{
var bounds = meshfilter.sharedMesh.bounds;
var filters = meshfilter.GetComponentsInChildren<MeshFilter>();
foreach(var filter in filters)
{
var localBounds = filter.sharedMesh.bounds;
bounds.size = new Vector3(Mathf.Max(bounds.size.x, localBounds.size.x),
Mathf.Max(bounds.size.z, localBounds.size.z),
Mathf.Max(bounds.size.z, localBounds.size.z));
}
spacing = Vector3.Scale(bounds.size, axis).magnitude;
}
}
else
spacing = Random.Range(m_Spacing.x, m_Spacing.y);
nativeSpline.GetPointAtLinearDistance(times[index], spacing, out var nextT);
currentDist = nextT >= 1f ? splineLength + 1f : nextT * splineLength;
}
index++;
}
//removing extra unnecessary instances
for(int u = m_Instances.Count-1; u >= index; u--)
{
if(m_Instances[u] != null)
{
#if UNITY_EDITOR
DestroyImmediate(m_Instances[u]);
#else
Destroy(m_Instances[u]);
#endif
m_Instances.RemoveAt(u);
}
}
//Positioning elements
for(int i = 0; i < index; i++)
{
var instance = m_Instances[i];
var splineT = times[i];
nativeSpline.Evaluate(splineT, out var position, out var direction, out var splineUp);
instance.transform.position = position;
if(m_Method == Method.LinearDistance)
{
var nextPosition = nativeSpline.EvaluatePosition(i + 1 < index ? times[i + 1] : 1f);
direction = nextPosition - position;
}
var up = splineUp;
var forward = direction;
if(m_Space == Space.World)
{
up = Vector3.up;
forward = Vector3.forward;
}else if(m_Space == Space.Local)
{
up = transform.TransformDirection(Vector3.up);
forward = transform.TransformDirection(Vector3.forward);
}
// Correct forward and up vectors based on axis remapping parameters
var remappedForward = GetAxis(m_Forward);
var remappedUp = GetAxis(m_Up);
var axisRemapRotation = Quaternion.Inverse(Quaternion.LookRotation(remappedForward, remappedUp));
instance.transform.rotation = Quaternion.LookRotation(forward, up) * axisRemapRotation;
var customUp = up;
var customForward = forward;
if(m_PositionOffset.hasOffset)
{
if(m_PositionOffset.hasCustomSpace)
GetCustomSpaceAxis(m_PositionOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
var offset = m_PositionOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
instance.transform.position += offset.x * right + offset.y * (Vector3)customUp + offset.z * (Vector3)customForward;
}
if(m_RotationOffset.hasOffset)
{
customUp = up;
customForward = forward;
if(m_RotationOffset.hasCustomSpace)
{
GetCustomSpaceAxis(m_RotationOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
if(m_RotationOffset.space == OffsetSpace.Object)
axisRemapRotation = quaternion.identity;
}
var offset = m_RotationOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
customForward = Quaternion.AngleAxis(offset.y, customUp) * Quaternion.AngleAxis(offset.x, right) * customForward;
customUp = Quaternion.AngleAxis(offset.x, right) * Quaternion.AngleAxis(offset.z, customForward) * customUp;
instance.transform.rotation = Quaternion.LookRotation(customForward, customUp) * axisRemapRotation;
}
if(m_ScaleOffset.hasOffset)
{
customUp = up;
customForward = forward;
if(m_ScaleOffset.hasCustomSpace)
GetCustomSpaceAxis(m_ScaleOffset.space,splineUp, direction, instance.transform, out customUp, out customForward);
customUp = instance.transform.InverseTransformDirection(customUp);
customForward = instance.transform.InverseTransformDirection(customForward);
var offset = m_ScaleOffset.GetNextOffset();
var right = Vector3.Cross(customUp, customForward).normalized;
instance.transform.localScale += offset.x * right + offset.y * (Vector3)customUp + offset.z * (Vector3)customForward;;
}
}
}
m_SplineDirty = false;
}
void GetCustomSpaceAxis(OffsetSpace space, float3 splineUp, float3 direction, Transform instanceTransform, out float3 customUp,out float3 customForward)
{
customUp = Vector3.up;
customForward = Vector3.forward;
if(space == OffsetSpace.Local)
{
customUp = transform.TransformDirection(Vector3.up);
customForward = transform.TransformDirection(Vector3.forward);
}
else if(space == OffsetSpace.Spline)
{
customUp = splineUp;
customForward = direction;
}
else if(space == OffsetSpace.Object)
{
customUp = instanceTransform.TransformDirection(Vector3.up);
customForward = instanceTransform.TransformDirection(Vector3.forward);
}
}
int GetPrefabIndex()
{
var prefabChoice = Random.Range(0, m_MaxProbability);
var currentProbability = 0f;
for(int i = 0; i < m_ItemsToInstantiate.Count; i++)
{
if(m_ItemsToInstantiate[i].prefab == null)
continue;
var itemProbability = m_ItemsToInstantiate[i].probability;
if(prefabChoice < currentProbability + itemProbability)
return i;
currentProbability += itemProbability;
}
return 0;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b74686beb0670a4ea61325c0acf4411
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,413 @@
using System;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Rendering;
namespace UnityEngine.Splines
{
/// <summary>
/// Utility methods for creating and working with meshes.
/// </summary>
public static class SplineMesh
{
const float k_RadiusMin = .00001f, k_RadiusMax = 10000f;
const int k_SidesMin = 3, k_SidesMax = 2084;
const int k_SegmentsMin = 2, k_SegmentsMax = 4096;
static readonly VertexAttributeDescriptor[] k_PipeVertexAttribs = new VertexAttributeDescriptor[]
{
new (VertexAttribute.Position),
new (VertexAttribute.Normal),
new (VertexAttribute.TexCoord0, dimension: 2)
};
/// <summary>
/// Interface for Spline mesh vertex data. Implement this interface if you are extruding custom mesh data and
/// do not want to use the vertex layout provided by <see cref="SplineMesh"/>."/>.
/// </summary>
public interface ISplineVertexData
{
/// <summary>
/// Vertex position.
/// </summary>
public Vector3 position { get; set; }
/// <summary>
/// Vertex normal.
/// </summary>
public Vector3 normal { get; set; }
/// <summary>
/// Vertex texture, corresponds to UV0.
/// </summary>
public Vector2 texture { get; set; }
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct VertexData : ISplineVertexData
{
public Vector3 position { get; set; }
public Vector3 normal { get; set; }
public Vector2 texture { get; set; }
}
static void ExtrudeRing<T, K>(T spline, float t, NativeArray<K> data, int start, int count, float radius)
where T : ISpline
where K : struct, ISplineVertexData
{
var evaluationT = spline.Closed ? math.frac(t) : math.clamp(t, 0f, 1f);
spline.Evaluate(evaluationT, out var sp, out var st, out _);
var length = math.lengthsq(st);
if (length == 0f || float.IsNaN(length))
st = spline.EvaluateTangent(math.clamp(evaluationT + (0.0001f * (t < 1f ? 1f : -1f)), 0f, 1f));
st = math.normalize(st);
var rot = Quaternion.LookRotation(st);
var rad = math.radians(360f / count);
for (int n = 0; n < count; ++n)
{
var vertex = new K();
var p = new Vector3(math.cos(n * rad), math.sin(n * rad), 0f) * radius;
vertex.position = (Vector3)sp + (rot * p);
vertex.normal = (vertex.position - (Vector3)sp).normalized;
// instead of inserting a seam, wrap UVs using a triangle wave so that texture wraps back onto itself
float ut = n / ((float)count + count%2);
float u = math.abs(ut - math.floor(ut + .5f)) * 2f;
vertex.texture = new Vector2(u, t * spline.GetLength());
data[start + n] = vertex;
}
}
// The logic around when caps and closing is a little complicated and easy to confuse. This wraps settings in a
// consistent way so that methods aren't working with mixed data.
struct Settings
{
public int sides { get; private set; }
public int segments { get; private set; }
public bool capped { get; private set; }
public bool closed { get; private set; }
public float2 range { get; private set; }
public float radius { get; private set; }
public Settings(int sides, int segments, bool capped, bool closed, float2 range, float radius)
{
this.sides = math.clamp(sides, k_SidesMin, k_SidesMax);
this.segments = math.clamp(segments, k_SegmentsMin, k_SegmentsMax);
this.range = new float2(math.min(range.x, range.y), math.max(range.x, range.y));
this.closed = math.abs(1f - (this.range.y - this.range.x)) < float.Epsilon && closed;
this.capped = capped && !this.closed;
this.radius = math.clamp(radius, k_RadiusMin, k_RadiusMax);
}
}
/// <summary>
/// Calculate the vertex and index count required for an extruded mesh.
/// Use this method to allocate attribute and index buffers for use with Extrude.
/// </summary>
/// <param name="vertexCount">The number of vertices required for an extruded mesh using the provided settings.</param>
/// <param name="indexCount">The number of indices required for an extruded mesh using the provided settings.</param>
/// <param name="sides">How many sides make up the radius of the mesh.</param>
/// <param name="segments">How many sections compose the length of the mesh.</param>
/// <param name="range">
/// The section of the Spline to extrude. This value expects a normalized interpolation start and end.
/// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline.
/// </param>
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
/// <param name="closed">Whether the extruded mesh is closed or open. This can be separate from the Spline.Closed value.</param>
public static void GetVertexAndIndexCount(int sides, int segments, bool capped, bool closed, Vector2 range, out int vertexCount, out int indexCount)
{
var settings = new Settings(sides, segments, capped, closed, range, 1f);
GetVertexAndIndexCount(settings, out vertexCount, out indexCount);
}
static void GetVertexAndIndexCount(Settings settings, out int vertexCount, out int indexCount)
{
vertexCount = settings.sides * (settings.segments + (settings.capped ? 2 : 0));
indexCount = settings.sides * 6 * (settings.segments - (settings.closed ? 0 : 1)) + (settings.capped ? (settings.sides - 2) * 3 * 2 : 0);
}
/// <summary>
/// Extrude a mesh along a spline in a tube-like shape.
/// </summary>
/// <param name="spline">The spline to extrude.</param>
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
/// <param name="radius">The radius of the extruded mesh.</param>
/// <param name="sides">How many sides make up the radius of the mesh.</param>
/// <param name="segments">How many sections compose the length of the mesh.</param>
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
public static void Extrude<T>(T spline, Mesh mesh, float radius, int sides, int segments, bool capped = true) where T : ISpline
{
Extrude(spline, mesh, radius, sides, segments, capped, new float2(0f, 1f));
}
/// <summary>
/// Extrude a mesh along a spline in a tube-like shape.
/// </summary>
/// <param name="spline">The spline to extrude.</param>
/// <param name="mesh">A mesh that will be cleared and filled with vertex data for the shape.</param>
/// <param name="radius">The radius of the extruded mesh.</param>
/// <param name="sides">How many sides make up the radius of the mesh.</param>
/// <param name="segments">How many sections compose the length of the mesh.</param>
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline is closed.</param>
/// <param name="range">
/// The section of the Spline to extrude. This value expects a normalized interpolation start and end.
/// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline.
/// </param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
public static void Extrude<T>(T spline, Mesh mesh, float radius, int sides, int segments, bool capped, float2 range) where T : ISpline
{
var settings = new Settings(sides, segments, capped, spline.Closed, range, radius);
GetVertexAndIndexCount(settings, out var vertexCount, out var indexCount);
var meshDataArray = Mesh.AllocateWritableMeshData(1);
var data = meshDataArray[0];
var indexFormat = vertexCount >= ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16;
data.SetIndexBufferParams(indexCount, indexFormat);
data.SetVertexBufferParams(vertexCount, k_PipeVertexAttribs);
var vertices = data.GetVertexData<VertexData>();
if (indexFormat == IndexFormat.UInt16)
{
var indices = data.GetIndexData<UInt16>();
Extrude(spline, vertices, indices, radius, sides, segments, capped, range);
}
else
{
var indices = data.GetIndexData<UInt32>();
Extrude(spline, vertices, indices, radius, sides, segments, capped, range);
}
mesh.Clear();
data.subMeshCount = 1;
data.SetSubMesh(0, new SubMeshDescriptor(0, indexCount));
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh);
mesh.RecalculateBounds();
}
/// <summary>
/// Extrude a mesh along a spline in a tube-like shape.
/// </summary>
/// <param name="spline">The spline to extrude.</param>
/// <param name="vertices">A pre-allocated buffer of vertex data.</param>
/// <param name="indices">A pre-allocated index buffer. Must be of type UInt16 or UInt32.</param>
/// <param name="radius">The radius of the extruded mesh.</param>
/// <param name="sides">How many sides make up the radius of the mesh.</param>
/// <param name="segments">How many sections compose the length of the mesh.</param>
/// <param name="capped">Whether the start and end of the mesh is filled. This setting is ignored when spline
/// is closed.</param>
/// <param name="range">
/// The section of the Spline to extrude. This value expects a normalized interpolation start and end.
/// I.e., [0,1] is the entire Spline, whereas [.5, 1] is the last half of the Spline.
/// </param>
/// <typeparam name="TSplineType">A type implementing ISpline.</typeparam>
/// <typeparam name="TVertexType">A type implementing ISplineVertexData.</typeparam>
/// <typeparam name="TIndexType">The mesh index format. Must be UInt16 or UInt32.</typeparam>
/// <exception cref="ArgumentOutOfRangeException">An out of range exception is thrown if the vertex or index
/// buffer lengths do not match the expected size. Use <see cref="GetVertexAndIndexCount"/> to calculate the
/// expected buffer sizes.
/// </exception>
/// <exception cref="ArgumentException">
/// An argument exception is thrown if {TIndexType} is not UInt16 or UInt32.
/// </exception>
public static void Extrude<TSplineType, TVertexType, TIndexType>(
TSplineType spline,
NativeArray<TVertexType> vertices,
NativeArray<TIndexType> indices,
float radius,
int sides,
int segments,
bool capped,
float2 range)
where TSplineType : ISpline
where TVertexType : struct, ISplineVertexData
where TIndexType : struct
{
Extrude(spline, vertices, indices, new Settings(sides, segments, capped, spline.Closed, range, radius));
}
static void Extrude<TSplineType, TVertexType, TIndexType>(
TSplineType spline,
NativeArray<TVertexType> vertices,
NativeArray<TIndexType> indices,
Settings settings)
where TSplineType : ISpline
where TVertexType : struct, ISplineVertexData
where TIndexType : struct
{
var radius = settings.radius;
var sides = settings.sides;
var segments = settings.segments;
var range = settings.range;
var capped = settings.capped;
var closed = settings.closed;
GetVertexAndIndexCount(settings, out var vertexCount, out var indexCount);
if (sides < 3)
throw new ArgumentOutOfRangeException(nameof(sides), "Sides must be greater than 3");
if (segments < 2)
throw new ArgumentOutOfRangeException(nameof(segments), "Segments must be greater than 2");
if (vertices.Length != vertexCount)
throw new ArgumentOutOfRangeException($"Vertex array is incorrect size. Expected {vertexCount}, but received {vertices.Length}.");
if (indices.Length != indexCount)
throw new ArgumentOutOfRangeException($"Index array is incorrect size. Expected {indexCount}, but received {indices.Length}.");
if (typeof(TIndexType) == typeof(UInt16))
{
var ushortIndices = indices.Reinterpret<UInt16>();
WindTris(ushortIndices, settings);
}
else if (typeof(TIndexType) == typeof(UInt32))
{
var ulongIndices = indices.Reinterpret<UInt32>();
WindTris(ulongIndices, settings);
}
else
{
throw new ArgumentException("Indices must be UInt16 or UInt32", nameof(indices));
}
for (int i = 0; i < segments; ++i)
ExtrudeRing(spline, math.lerp(range.x, range.y, i / (segments - 1f)), vertices, i * sides, sides, radius);
if (capped)
{
var capVertexStart = segments * sides;
var endCapVertexStart = (segments + 1) * sides;
var rng = spline.Closed ? math.frac(range) : math.clamp(range, 0f, 1f);
ExtrudeRing(spline, rng.x, vertices, capVertexStart, sides, radius);
ExtrudeRing(spline, rng.y, vertices, endCapVertexStart, sides, radius);
var beginAccel = math.normalize(spline.EvaluateTangent(rng.x));
var accelLen = math.lengthsq(beginAccel);
if (accelLen == 0f || float.IsNaN(accelLen))
beginAccel = math.normalize(spline.EvaluateTangent(rng.x + 0.0001f));
var endAccel = math.normalize(spline.EvaluateTangent(rng.y));
accelLen = math.lengthsq(endAccel);
if (accelLen == 0f || float.IsNaN(accelLen))
endAccel = math.normalize(spline.EvaluateTangent(rng.y - 0.0001f));
var rad = math.radians(360f / sides);
var off = new float2(.5f, .5f);
for (int i = 0; i < sides; ++i)
{
var v0 = vertices[capVertexStart + i];
var v1 = vertices[endCapVertexStart + i];
v0.normal = -beginAccel;
v0.texture = off + new float2(math.cos(i * rad), math.sin(i * rad)) * .5f;
v1.normal = endAccel;
v1.texture = off + new float2(-math.cos(i * rad), math.sin(i * rad)) * .5f;
vertices[capVertexStart + i] = v0;
vertices[endCapVertexStart + i] = v1;
}
}
}
// Two overloads for winding triangles because there is no generic constraint for UInt{16, 32}
static void WindTris(NativeArray<UInt16> indices, Settings settings)
{
var closed = settings.closed;
var segments = settings.segments;
var sides = settings.sides;
var capped = settings.capped;
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
{
for (int n = 0; n < sides; ++n)
{
var index0 = i * sides + n;
var index1 = i * sides + ((n + 1) % sides);
var index2 = ((i+1) % segments) * sides + n;
var index3 = ((i+1) % segments) * sides + ((n + 1) % sides);
indices[i * sides * 6 + n * 6 + 0] = (UInt16) index0;
indices[i * sides * 6 + n * 6 + 1] = (UInt16) index1;
indices[i * sides * 6 + n * 6 + 2] = (UInt16) index2;
indices[i * sides * 6 + n * 6 + 3] = (UInt16) index1;
indices[i * sides * 6 + n * 6 + 4] = (UInt16) index3;
indices[i * sides * 6 + n * 6 + 5] = (UInt16) index2;
}
}
if (capped)
{
var capVertexStart = segments * sides;
var capIndexStart = sides * 6 * (segments-1);
var endCapVertexStart = (segments + 1) * sides;
var endCapIndexStart = (segments-1) * 6 * sides + (sides-2) * 3;
for(ushort i = 0; i < sides - 2; ++i)
{
indices[capIndexStart + i * 3 + 0] = (UInt16)(capVertexStart);
indices[capIndexStart + i * 3 + 1] = (UInt16)(capVertexStart + i + 2);
indices[capIndexStart + i * 3 + 2] = (UInt16)(capVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 0] = (UInt16) (endCapVertexStart);
indices[endCapIndexStart + i * 3 + 1] = (UInt16) (endCapVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 2] = (UInt16) (endCapVertexStart + i + 2);
}
}
}
// Two overloads for winding triangles because there is no generic constraint for UInt{16, 32}
static void WindTris(NativeArray<UInt32> indices, Settings settings)
{
var closed = settings.closed;
var segments = settings.segments;
var sides = settings.sides;
var capped = settings.capped;
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
{
for (int n = 0; n < sides; ++n)
{
var index0 = i * sides + n;
var index1 = i * sides + ((n + 1) % sides);
var index2 = ((i+1) % segments) * sides + n;
var index3 = ((i+1) % segments) * sides + ((n + 1) % sides);
indices[i * sides * 6 + n * 6 + 0] = (UInt32) index0;
indices[i * sides * 6 + n * 6 + 1] = (UInt32) index1;
indices[i * sides * 6 + n * 6 + 2] = (UInt32) index2;
indices[i * sides * 6 + n * 6 + 3] = (UInt32) index1;
indices[i * sides * 6 + n * 6 + 4] = (UInt32) index3;
indices[i * sides * 6 + n * 6 + 5] = (UInt32) index2;
}
}
if (capped)
{
var capVertexStart = segments * sides;
var capIndexStart = sides * 6 * (segments-1);
var endCapVertexStart = (segments + 1) * sides;
var endCapIndexStart = (segments-1) * 6 * sides + (sides-2) * 3;
for(ushort i = 0; i < sides - 2; ++i)
{
indices[capIndexStart + i * 3 + 0] = (UInt32)(capVertexStart);
indices[capIndexStart + i * 3 + 1] = (UInt32)(capVertexStart + i + 2);
indices[capIndexStart + i * 3 + 2] = (UInt32)(capVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 0] = (UInt32) (endCapVertexStart);
indices[endCapIndexStart + i * 3 + 1] = (UInt32) (endCapVertexStart + i + 1);
indices[endCapIndexStart + i * 3 + 2] = (UInt32) (endCapVertexStart + i + 2);
}
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e824b25245647358e471f403288f20d
timeCreated: 1637607905

View file

@ -0,0 +1,130 @@
using System;
using Unity.Collections;
namespace UnityEngine.Splines
{
/// <summary>
/// SplineComputeBufferScope is a convenient way to extract from a spline the information necessary to evaluate
/// spline values in a ComputeShader.
/// To access Spline evaluation methods in a shader, include the "Splines.cginc" file:
/// <code>#include "Packages/com.unity.splines/Shader/Spline.cginc"</code>
/// </summary>
/// <typeparam name="T">The type of spline.</typeparam>
public struct SplineComputeBufferScope<T> : IDisposable where T : ISpline
{
T m_Spline;
int m_KnotCount;
ComputeBuffer m_CurveBuffer, m_LengthBuffer;
// Optional shader property bindings
ComputeShader m_Shader;
string m_Info, m_Curves, m_CurveLengths;
int m_Kernel;
/// <summary>
/// Create a new SplineComputeBufferScope.
/// </summary>
/// <param name="spline">The spline to create GPU data for.</param>
public SplineComputeBufferScope(T spline)
{
m_Spline = spline;
m_KnotCount = 0;
m_CurveBuffer = m_LengthBuffer = null;
m_Shader = null;
m_Info = m_Curves = m_CurveLengths = null;
m_Kernel = 0;
Upload();
}
/// <summary>
/// Set up a shader with all of the necessary ComputeBuffer and Spline metadata for working with functions found
/// in Spline.cginc.
/// </summary>
/// <param name="shader">The compute shader to bind.</param>
/// <param name="kernel">The kernel to target.</param>
/// <param name="info">The float4 (typedef to SplineData in Spline.cginc) Spline info.</param>
/// <param name="curves">A StructuredBuffer{BezierCurve} or RWStructuredBuffer{BezierCurve}.</param>
/// <param name="lengths">A StructuredBuffer{float} or RWStructuredBuffer{float}.</param>
/// <exception cref="ArgumentNullException">Thrown if any of the expected properties are invalid.</exception>
public void Bind(ComputeShader shader, int kernel, string info, string curves, string lengths)
{
if (shader == null) throw new ArgumentNullException(nameof(shader));
if (string.IsNullOrEmpty(info)) throw new ArgumentNullException(nameof(info));
if (string.IsNullOrEmpty(curves)) throw new ArgumentNullException(nameof(curves));
if (string.IsNullOrEmpty(lengths)) throw new ArgumentNullException(nameof(lengths));
m_Shader = shader;
m_Info = info;
m_Curves = curves;
m_CurveLengths = lengths;
m_Kernel = kernel;
m_Shader.SetVector(m_Info, Info);
m_Shader.SetBuffer(m_Kernel, m_Curves, Curves);
m_Shader.SetBuffer(m_Kernel, m_CurveLengths, CurveLengths);
}
/// <summary>
/// Free resources allocated by this object.
/// </summary>
public void Dispose()
{
m_CurveBuffer?.Dispose();
m_LengthBuffer?.Dispose();
}
/// <summary>
/// Copy Spline curve, info, and length caches to their GPU buffers.
/// </summary>
public void Upload()
{
int knotCount = m_Spline.Count;
if (m_KnotCount != knotCount)
{
m_KnotCount = m_Spline.Count;
m_CurveBuffer?.Dispose();
m_LengthBuffer?.Dispose();
m_CurveBuffer = new ComputeBuffer(m_KnotCount, sizeof(float) * 3 * 4);
m_LengthBuffer = new ComputeBuffer(m_KnotCount, sizeof(float));
}
var curves = new NativeArray<BezierCurve>(m_KnotCount, Allocator.Temp);
var lengths = new NativeArray<float>(m_KnotCount, Allocator.Temp);
for (int i = 0; i < m_KnotCount; ++i)
{
curves[i] = m_Spline.GetCurve(i);
lengths[i] = m_Spline.GetCurveLength(i);
}
if(!string.IsNullOrEmpty(m_Info))
m_Shader.SetVector(m_Info, Info);
m_CurveBuffer.SetData(curves);
m_LengthBuffer.SetData(lengths);
curves.Dispose();
lengths.Dispose();
}
/// <summary>
/// Returns a SplineInfo Vector4.
/// </summary>
public Vector4 Info => new Vector4(m_Spline.Count, m_Spline.Closed ? 1 : 0, m_Spline.GetLength(), 0);
/// <summary>
/// A ComputeBuffer containing <see cref="BezierCurve"/>.
/// </summary>
public ComputeBuffer Curves => m_CurveBuffer;
/// <summary>
/// A ComputeBuffer containing the cached length of all spline curves.
/// </summary>
public ComputeBuffer CurveLengths => m_LengthBuffer;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5cd0dbc3db7748baa71797f5bc16a1b4
timeCreated: 1628884843

View file

@ -0,0 +1,27 @@
namespace UnityEngine.Splines
{
/// <summary>
/// Describes the different supported Spline representations.
/// </summary>
/// <remarks>
/// Internally all <see cref="Spline"/> objects are saved as series of cubic curves. In the editor Splines can be
/// manipulated in a lower order form.
/// </remarks>
public enum SplineType : byte
{
/// <summary>
/// Catmull-Rom Spline is a type of Cubic Hermite Spline. Tangents are calculated from control points rather than
/// discretely defined.
/// See https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline for more information.
/// </summary>
CatmullRom,
/// <summary>
/// A series of connected cubic bezier curves. This is the default Spline type.
/// </summary>
Bezier,
/// <summary>
/// A series of connected straight line segments.
/// </summary>
Linear
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc3d3aa21eb40814391409a5b8bb11e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,792 @@
using System;
using Unity.Collections;
using Unity.Mathematics;
namespace UnityEngine.Splines
{
/// <summary>
/// A collection of methods for extracting information about <see cref="Spline"/> types.
/// </summary>
public static class SplineUtility
{
const int k_ResolutionSegmentCountMin = 6;
const int k_ResolutionSegmentCountMax = 1024;
/// <summary>
/// The minimum resolution allowable when unrolling a curve to hit test while picking (selecting a spline with a cursor).
///
/// Pick resolution is used when determining how many segments are required to unroll a curve. Unrolling is the
/// process of calculating a series of line segments to approximate a curve. Some functions in SplineUtility
/// allow you to specify a resolution. Lower resolution means fewer segments, while higher resolutions result
/// in more segments. Use lower resolutions where performance is critical and accuracy is not paramount. Use
/// higher resolution where a fine degree of accuracy is necessary and performance is less important.
/// </summary>
public const int PickResolutionMin = 2;
/// <summary>
/// The default resolution used when unrolling a curve to hit test while picking (selecting a spline with a cursor).
///
/// Pick resolution is used when determining how many segments are required to unroll a curve. Unrolling is the
/// process of calculating a series of line segments to approximate a curve. Some functions in SplineUtility
/// allow you to specify a resolution. Lower resolution means fewer segments, while higher resolutions result
/// in more segments. Use lower resolutions where performance is critical and accuracy is not paramount. Use
/// higher resolution where a fine degree of accuracy is necessary and performance is less important.
/// </summary>
public const int PickResolutionDefault = 4;
/// <summary>
/// The maximum resolution allowed when unrolling a curve to hit test while picking (selecting a spline with a cursor).
///
/// Pick resolution is used when determining how many segments are required to unroll a curve. Unrolling is the
/// process of calculating a series of line segments to approximate a curve. Some functions in SplineUtility
/// allow you to specify a resolution. Lower resolution means fewer segments, while higher resolutions result
/// in more segments. Use lower resolutions where performance is critical and accuracy is not paramount. Use
/// higher resolution where a fine degree of accuracy is necessary and performance is less important.
/// </summary>
public const int PickResolutionMax = 64;
/// <summary>
/// The default resolution used when unrolling a curve to draw a preview in the Scene View.
///
/// Pick resolution is used when determining how many segments are required to unroll a curve. Unrolling is the
/// process of calculating a series of line segments to approximate a curve. Some functions in SplineUtility
/// allow you to specify a resolution. Lower resolution means fewer segments, while higher resolutions result
/// in more segments. Use lower resolutions where performance is critical and accuracy is not paramount. Use
/// higher resolution where a fine degree of accuracy is necessary and performance is less important.
/// </summary>
public const int DrawResolutionDefault = 10;
/// <summary>
/// Compute interpolated position, direction and upDirection at ratio t. Calling this method to get the
/// 3 vectors is faster than calling independently EvaluatePosition, EvaluateDirection and EvaluateUpVector
/// for the same time t as it reduces some redundant computation.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <param name="position">Output variable for the float3 position at t.</param>
/// <param name="tangent">Output variable for the float3 tangent at t.</param>
/// <param name="upVector">Output variable for the float3 up direction at t.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>Boolean value, true if a valid set of output variables as been computed.</returns>
public static bool Evaluate<T>(this T spline,
float t,
out float3 position,
out float3 tangent,
out float3 upVector
) where T : ISpline
{
if(spline.Count < 1)
{
position = float3.zero;
tangent = new float3(0, 0, 1);
upVector = new float3(0, 1, 0);
return false;
}
var curveIndex = SplineToCurveT(spline, t, out var curveT);
var curve = spline.GetCurve(curveIndex);
position = CurveUtility.EvaluatePosition(curve, curveT);
tangent = CurveUtility.EvaluateTangent(curve, curveT);
upVector = spline.EvaluateUpVector(curveIndex, curveT);
return true;
}
/// <summary>
/// Return an interpolated position at ratio t.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>A position on the spline.</returns>
public static float3 EvaluatePosition<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return float.PositiveInfinity;
var curve = spline.GetCurve(SplineToCurveT(spline, t, out var curveT));
return CurveUtility.EvaluatePosition(curve, curveT);
}
/// <summary>
/// Return an interpolated direction at ratio t.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>A direction on the spline.</returns>
public static float3 EvaluateTangent<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return float.PositiveInfinity;
var curve = spline.GetCurve(SplineToCurveT(spline, t, out var curveT));
return CurveUtility.EvaluateTangent(curve, curveT);
}
/// <summary>
/// Evaluate an up vector of a spline at a specific t
/// </summary>
/// <param name="spline">The <seealso cref="NativeSpline"/> to evaluate.</param>
/// <param name="t">A value between 0 and 1 representing a percentage of the curve.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>An up vector</returns>
public static float3 EvaluateUpVector<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return float3.zero;
var curveIndex = SplineToCurveT(spline, t, out var curveT);
return spline.EvaluateUpVector(curveIndex, curveT);
}
static float3 EvaluateUpVector<T>(this T spline, int curveIndex, float curveT) where T : ISpline
{
if (spline.Count < 1)
return float3.zero;
var curve = spline.GetCurve(curveIndex);
var curveStartRotation = spline[curveIndex].Rotation;
var curveStartUp = math.rotate(curveStartRotation, math.up());
if (curveT == 0f)
return curveStartUp;
var endKnotIndex = spline.NextIndex(curveIndex);
var curveEndRotation = spline[endKnotIndex].Rotation;
var curveEndUp = math.rotate(curveEndRotation, math.up());
if (curveT == 1f)
return curveEndUp;
return CurveUtility.EvaluateUpVector(curve, curveT, curveStartUp, curveEndUp);
}
/// <summary>
/// Return an interpolated acceleration at ratio t.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>An acceleration on the spline.</returns>
public static float3 EvaluateAcceleration<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return float3.zero;
var curve = spline.GetCurve(SplineToCurveT(spline, t, out var curveT));
return CurveUtility.EvaluateAcceleration(curve, curveT);
}
/// <summary>
/// Return an interpolated curvature at ratio t.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A curvature on the spline.</returns>
public static float EvaluateCurvature<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return 0f;
var curveIndex = SplineToCurveT(spline, t, out var curveT);
var curve = spline.GetCurve(curveIndex);
return CurveUtility.EvaluateCurvature(curve, curveT);
}
/// <summary>
/// Return the curvature center at ratio t. The curvature center represents the center of the circle
/// that is tangent to the curve at t. This circle is in the plane defined by the curve velocity (tangent)
/// and the curve acceleration at that point.
/// </summary>
/// <param name="spline">The spline to interpolate.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
/// <returns>A point representing the curvature center associated to the position at t on the spline.</returns>
public static float3 EvaluateCurvatureCenter<T>(this T spline, float t) where T : ISpline
{
if (spline.Count < 1)
return 0f;
var curveIndex = SplineToCurveT(spline, t, out var curveT);
var curve = spline.GetCurve(curveIndex);
var curvature = CurveUtility.EvaluateCurvature(curve, curveT);
if(curvature != 0)
{
var radius = 1f / curvature;
var position = CurveUtility.EvaluatePosition(curve, curveT);
var velocity = CurveUtility.EvaluateTangent(curve, curveT);
var acceleration = CurveUtility.EvaluateAcceleration(curve, curveT);
var curvatureUp = math.normalize(math.cross(acceleration, velocity));
var curvatureRight = math.normalize(math.cross(velocity, curvatureUp));
return position + radius * curvatureRight;
}
return float3.zero;
}
internal static float3 GetContinuousTangent(float3 otherTangent, float3 tangentToAlign)
{
// Mirror tangent but keep the same length
float3 dir = math.length(otherTangent) > 0 ? -math.normalize(otherTangent) : new float3(0,0,0);
float tangentToAlignLength = math.length(tangentToAlign);
return dir * tangentToAlignLength;
}
/// <summary>
/// Given a normalized interpolation (t) for a spline, calculate the curve index and curve-relative
/// normalized interpolation.
/// </summary>
/// <param name="spline">The target spline.</param>
/// <param name="splineT">A normalized spline interpolation value to be converted into curve space.</param>
/// <param name="curveT">A normalized curve interpolation value.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The curve index.</returns>
public static int SplineToCurveT<T>(this T spline, float splineT, out float curveT) where T : ISpline
{
return SplineToCurveT(spline, splineT, out curveT, true);
}
static int SplineToCurveT<T>(this T spline, float splineT, out float curveT, bool useLUT) where T : ISpline
{
var knotCount = spline.Count;
if(knotCount <= 1)
{
curveT = 0f;
return 0;
}
splineT = math.clamp(splineT, 0, 1);
var tLength = splineT * spline.GetLength();
var start = 0f;
var closed = spline.Closed;
for (int i = 0, c = closed ? knotCount : knotCount - 1; i < c; i++)
{
var index = i % knotCount;
var curveLength = spline.GetCurveLength(index);
if (tLength <= (start + curveLength))
{
curveT = useLUT ?
spline.GetCurveInterpolation(index, tLength - start) :
(tLength - start)/ curveLength;
return index;
}
start += curveLength;
}
curveT = 1f;
return closed ? knotCount - 1 : knotCount - 2;
}
/// <summary>
/// Given an interpolation value for a curve, calculate the relative normalized spline interpolation.
/// </summary>
/// <param name="spline">The target spline.</param>
/// <param name="curve">A curve index and normalized interpolation. The curve index is represented by the
/// integer part of the float, and interpolation is the fractional part. This is the format used by
/// <seealso cref="PathIndexUnit.Knot"/>.
/// </param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>An interpolation value relative to normalized Spline length (0 to 1).</returns>
/// <seealso cref="SplineToCurveT{T}"/>
public static float CurveToSplineT<T>(this T spline, float curve) where T : ISpline
{
if(spline.Count <= 1 || curve < 0f)
return 0f;
if(curve >= ( spline.Closed ? spline.Count : spline.Count - 1 ))
return 1f;
var curveIndex = (int) math.floor(curve);
float t = 0f;
for (int i = 0; i < curveIndex; i++)
t += spline.GetCurveLength(i);
t += spline.GetCurveLength(curveIndex) * math.frac(curve);
return t / spline.GetLength();
}
/// <summary>
/// Calculate the length of a spline when transformed by a matrix.
/// </summary>
/// <param name="spline"></param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="transform"></param>
/// <returns></returns>
public static float CalculateLength<T>(this T spline, float4x4 transform) where T : ISpline
{
using var nativeSpline = new NativeSpline(spline, transform);
return nativeSpline.GetLength();
}
/// <summary>
/// Calculate the bounding box of a Spline.
/// </summary>
/// <param name="spline">The spline for which to calculate bounds.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The bounds of a spline.</returns>
public static Bounds GetBounds<T>(this T spline) where T : ISpline
{
if (spline.Count < 1)
return default;
var knot = spline[0];
Bounds bounds = new Bounds(knot.Position, Vector3.zero);
for (int i = 1, c = spline.Count; i < c; ++i)
{
knot = spline[i];
bounds.Encapsulate(knot.Position);
}
return bounds;
}
// Get the point on a line segment at the smallest distance to intersection
static float3 RayLineSegmentNearestPoint(float3 rayOrigin, float3 rayDir, float3 s0, float3 s1, out float t)
{
float3 am = s1 - s0;
float al = math.length(am);
float3 ad = (1f / al) * am;
float dot = math.dot(ad, rayDir);
if (1f - math.abs(dot) < Mathf.Epsilon)
{
t = 0f;
return s0;
}
float3 c = rayOrigin - s0;
float rm = math.dot(rayDir, rayDir);
float n = -dot * math.dot(rayDir, c) + math.dot(ad, c) * rm;
float d = math.dot(ad, ad) * rm - dot * dot;
float mag = math.min(al, math.max(0f, n / d));
t = mag / al;
return s0 + ad * mag;
}
static float3 PointLineSegmentNearestPoint(float3 p, float3 a, float3 b, out float t)
{
float l2 = math.lengthsq(b - a);
if (l2 == 0.0)
{
t = 0f;
return a;
}
t = math.dot(p - a, b - a) / l2;
if (t < 0.0)
return a;
if (t > 1.0)
return b;
return a + t * (b - a);
}
// Same as ProjectPointLine but without clamping.
static float3 ProjectPointRay(float3 point, float3 ro, float3 rd)
{
float3 relativePoint = point - ro;
float dot = math.dot(rd, relativePoint);
return ro + rd * dot;
}
/// <summary>
/// Use this function to calculate the number of segments for a given spline length and resolution.
/// </summary>
/// <param name="length">A distance value in <see cref="PathIndexUnit"/>.</param>
/// <param name="resolution">A value used to calculate the number of segments for a length. This is calculated
/// as max(MIN_SEGMENTS, min(MAX_SEGMENTS, sqrt(length) * resolution)).
/// </param>
/// <returns>
/// The number of segments as calculated for given length and resolution.
/// </returns>
public static int GetSegmentCount(float length, int resolution)
{
return (int) math.max(k_ResolutionSegmentCountMin, math.min(k_ResolutionSegmentCountMax, math.sqrt(length) * resolution));
}
struct Segment
{
public float start, length;
public Segment(float start, float length)
{
this.start = start;
this.length = length;
}
}
static Segment GetNearestPoint<T>(T spline,
float3 ro, float3 rd,
Segment range,
out float distance, out float3 nearest, out float time,
int segments) where T : ISpline
{
distance = float.PositiveInfinity;
nearest = float.PositiveInfinity;
time = float.PositiveInfinity;
Segment segment = new Segment(-1f, 0f);
float t0 = range.start;
float3 a = EvaluatePosition(spline, t0);
for (int i = 1; i < segments; i++)
{
float t1 = range.start + (range.length * (i / (segments - 1f)));
float3 b = EvaluatePosition(spline, t1);
float3 p = RayLineSegmentNearestPoint(ro, rd, a, b, out float st);
float d = math.length(ProjectPointRay(p, ro, rd) - p);
if (d < distance)
{
segment.start = t0;
segment.length = t1 - t0;
time = segment.start + segment.length * st;
distance = d;
nearest = p;
}
t0 = t1;
a = b;
}
return segment;
}
static Segment GetNearestPoint<T>(T spline,
float3 point,
Segment range,
out float distance, out float3 nearest, out float time,
int segments) where T : ISpline
{
distance = float.PositiveInfinity;
nearest = float.PositiveInfinity;
time = float.PositiveInfinity;
Segment segment = new Segment(-1f, 0f);
float t0 = range.start;
float3 a = EvaluatePosition(spline, t0);
float dsqr = distance;
for (int i = 1; i < segments; i++)
{
float t1 = range.start + (range.length * (i / (segments - 1f)));
float3 b = EvaluatePosition(spline, t1);
float3 p = PointLineSegmentNearestPoint(point, a, b, out float st);
float d = math.distancesq(p, point);
if (d < dsqr)
{
segment.start = t0;
segment.length = t1 - t0;
time = segment.start + segment.length * st;
dsqr = d;
distance = math.sqrt(d);
nearest = p;
}
t0 = t1;
a = b;
}
return segment;
}
/// <summary>
/// Calculate the point on a spline nearest to a ray.
/// </summary>
/// <param name="spline">The input spline to search for nearest point.</param>
/// <param name="ray">The input ray to search against.</param>
/// <param name="nearest">The point on a spline nearest to the input ray. The accuracy of this value is
/// affected by the <paramref name="resolution"/>.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="t">The normalized time value to the nearest point.</param>
/// <param name="resolution">Affects how many segments to split a spline into when calculating the nearest point.
/// Higher values mean smaller and more segments, which increases accuracy at the cost of processing time.
/// The minimum resolution is defined by <seealso cref="PickResolutionMin"/>, and the maximum is defined by
/// <seealso cref="PickResolutionMax"/>.
/// In most cases, the default resolution is appropriate. Use with <seealso cref="iterations"/> to fine tune
/// point accuracy.
/// </param>
/// <param name="iterations">
/// The nearest point is calculated by finding the nearest point on the entire length
/// of the spline using <seealso cref="resolution"/> to divide into equally spaced line segments. Successive
/// iterations will then subdivide further the nearest segment, producing more accurate results. In most cases,
/// the default value is sufficient, but if extreme accuracy is required this value can be increased to a
/// maximum of <see cref="PickResolutionMax"/>.
/// </param>
/// <returns>The distance from ray to nearest point.</returns>
public static float GetNearestPoint<T>(T spline,
Ray ray,
out float3 nearest,
out float t,
int resolution = PickResolutionDefault,
int iterations = 2) where T : ISpline
{
float distance = float.PositiveInfinity;
nearest = float.PositiveInfinity;
float3 ro = ray.origin, rd = ray.direction;
Segment segment = new Segment(0f, 1f);
t = 0f;
int res = math.min(math.max(PickResolutionMin, resolution), PickResolutionMax);
for (int i = 0, c = math.min(10, iterations); i < c; i++)
{
int segments = GetSegmentCount(spline.GetLength() * segment.length, res);
segment = GetNearestPoint(spline, ro, rd, segment, out distance, out nearest, out t, segments);
}
return distance;
}
/// <summary>
/// Calculate the point on a spline nearest to a point.
/// </summary>
/// <param name="spline">The input spline to search for nearest point.</param>
/// <param name="point">The input point to compare.</param>
/// <param name="nearest">The point on a spline nearest to the input point. The accuracy of this value is
/// affected by the <paramref name="resolution"/>.</param>
/// <param name="t">The normalized interpolation ratio corresponding to the nearest point.</param>
/// <param name="resolution">Affects how many segments to split a spline into when calculating the nearest point.
/// Higher values mean smaller and more segments, which increases accuracy at the cost of processing time.
/// The minimum resolution is defined by <seealso cref="PickResolutionMin"/>, and the maximum is defined by
/// <seealso cref="PickResolutionMax"/>.
/// In most cases, the default resolution is appropriate. Use with <seealso cref="iterations"/> to fine tune
/// point accuracy.
/// </param>
/// <param name="iterations">
/// The nearest point is calculated by finding the nearest point on the entire length
/// of the spline using <seealso cref="resolution"/> to divide into equally spaced line segments. Successive
/// iterations will then subdivide further the nearest segment, producing more accurate results. In most cases,
/// the default value is sufficient, but if extreme accuracy is required this value can be increased to a
/// maximum of <see cref="PickResolutionMax"/>.
/// </param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The distance from input point to nearest point on spline.</returns>
public static float GetNearestPoint<T>(T spline,
float3 point,
out float3 nearest,
out float t,
int resolution = PickResolutionDefault,
int iterations = 2) where T : ISpline
{
float distance = float.PositiveInfinity;
nearest = float.PositiveInfinity;
Segment segment = new Segment(0f, 1f);
t = 0f;
int res = math.min(math.max(PickResolutionMin, resolution), PickResolutionMax);
for (int i = 0, c = math.min(10, iterations); i < c; i++)
{
int segments = GetSegmentCount(spline.GetLength() * segment.length, res);
segment = GetNearestPoint(spline, point, segment, out distance, out nearest, out t, segments);
}
return distance;
}
/// <summary>
/// Given a Spline and interpolation ratio, calculate the 3d point at a linear distance from point at spline.EvaluatePosition(t).
/// Returns the corresponding time associated to this 3d position on the Spline.
/// </summary>
/// <param name="spline">The Spline on which to compute the point.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <param name="fromT">The Spline interpolation ratio 't' (normalized) from which the next position need to be computed.</param>
/// <param name="relativeDistance">
/// The relative distance at which the new point should be placed. A negative value will compute a point at a
/// 'resultPointTime' previous to 'fromT' (backward search).
/// </param>
/// <param name="resultPointT">The normalized interpolation ratio of the resulting point.</param>
/// <returns>The 3d point from the spline located at a linear distance from the point at t.</returns>
public static float3 GetPointAtLinearDistance<T>(this T spline,
float fromT,
float relativeDistance,
out float resultPointT) where T : ISpline
{
const float epsilon = 0.001f;
if(fromT <0)
{
resultPointT = 0f;
return spline.EvaluatePosition(0f);
}
var length = spline.GetLength();
var lengthAtT = fromT * length;
float currentLength = lengthAtT;
if(currentLength + relativeDistance >= length) //relativeDistance >= 0 -> Forward search
{
resultPointT = 1f;
return spline.EvaluatePosition(1f);
}
else if(currentLength + relativeDistance <= 0) //relativeDistance < 0 -> Forward search
{
resultPointT = 0f;
return spline.EvaluatePosition(0f);
}
var currentPos = spline.EvaluatePosition(fromT);
resultPointT = fromT;
var forwardSearch = relativeDistance >= 0;
var residual = math.abs(relativeDistance);
float linearDistance = 0;
float3 point = spline.EvaluatePosition(fromT);
while(residual > epsilon && (forwardSearch ? resultPointT < 1f : resultPointT > 0))
{
currentLength += forwardSearch ? residual : -residual;
resultPointT = currentLength / length;
if(resultPointT > 1f) //forward search
{
resultPointT = 1f;
point = spline.EvaluatePosition(1f);
}else if(resultPointT < 0f) //backward search
{
resultPointT = 0f;
point = spline.EvaluatePosition(0f);
}
point = spline.EvaluatePosition(resultPointT);
linearDistance = math.distance(currentPos, point);
residual = math.abs(relativeDistance) - linearDistance;
}
return point;
}
/// <summary>
/// Given a normalized interpolation ratio, calculate the associated interpolation value in another targetPathUnit regarding a specific spline.
/// </summary>
/// <param name="spline">The Spline to use for the conversion, this is necessary to compute Normalized and Distance PathIndexUnits.</param>
/// <param name="t">Normalized interpolation ratio (0 to 1).</param>
/// <param name="targetPathUnit">The PathIndexUnit to which 't' should be converted.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The interpolation value converted to targetPathUnit.</returns>
public static float ConvertIndexUnit<T>(this T spline, float t, PathIndexUnit targetPathUnit)
where T : ISpline
{
if (targetPathUnit == PathIndexUnit.Normalized)
return WrapInterpolation(t);
return ConvertNormalizedIndexUnit(spline, t, targetPathUnit);
}
/// <summary>
/// Given an interpolation value using a certain PathIndexUnit type, calculate the associated interpolation value in another targetPathUnit regarding a specific spline.
/// </summary>
/// <param name="spline">The Spline to use for the conversion, this is necessary to compute Normalized and Distance PathIndexUnits.</param>
/// <param name="t">Interpolation in the original PathIndexUnit.</param>
/// <param name="fromPathUnit">The PathIndexUnit for the original interpolation value.</param>
/// <param name="targetPathUnit">The PathIndexUnit to which 't' should be converted.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The interpolation value converted to targetPathUnit.</returns>
public static float ConvertIndexUnit<T>(this T spline, float t, PathIndexUnit fromPathUnit, PathIndexUnit targetPathUnit)
where T : ISpline
{
if (fromPathUnit == targetPathUnit)
{
if (targetPathUnit == PathIndexUnit.Normalized)
t = WrapInterpolation(t);
return t;
}
return ConvertNormalizedIndexUnit(spline, GetNormalizedInterpolation(spline, t, fromPathUnit), targetPathUnit);
}
static float ConvertNormalizedIndexUnit<T>(T spline, float t, PathIndexUnit targetPathUnit) where T : ISpline
{
switch(targetPathUnit)
{
case PathIndexUnit.Knot:
//LUT SHOULD NOT be used here as PathIndexUnit.KnotIndex is linear regarding the distance
//(and thus not be interpreted using the LUT and the interpolated T)
int splineIndex = spline.SplineToCurveT(t, out float curveTime, false);
return splineIndex + curveTime;
case PathIndexUnit.Distance:
return t * spline.GetLength();
default:
return t;
}
}
static float WrapInterpolation(float t)
{
return t % 1f == 0f ? math.clamp(t, 0f, 1f) : t - math.floor(t);
}
/// <summary>
/// Given an interpolation value in any PathIndexUnit type, calculate the normalized interpolation ratio value
/// relative to a <see cref="Spline"/>.
/// </summary>
/// <param name="spline">The Spline to use for the conversion, this is necessary to compute Normalized and Distance PathIndexUnits.</param>
/// <param name="t">The 't' value to normalize in the original PathIndexUnit.</param>
/// <param name="originalPathUnit">The PathIndexUnit from the original 't'.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
/// <returns>The normalized interpolation ratio (0 to 1).</returns>
public static float GetNormalizedInterpolation<T>(T spline, float t, PathIndexUnit originalPathUnit) where T : ISpline
{
switch(originalPathUnit)
{
case PathIndexUnit.Knot:
return WrapInterpolation(CurveToSplineT(spline, t));
case PathIndexUnit.Distance:
var length = spline.GetLength();
return WrapInterpolation(length > 0 ? t / length : 0f);
default:
return WrapInterpolation(t);
}
}
internal static int PreviousIndex<T>(this T spline, int index) where T : ISpline
=> PreviousIndex(index, spline.Count, spline.Closed);
internal static int NextIndex<T>(this T spline, int index) where T : ISpline
=> NextIndex(index, spline.Count, spline.Closed);
internal static int PreviousIndex(int index, int count, bool wrap)
{
return wrap ? (index + (count-1)) % count : math.max(index - 1, 0);
}
internal static int NextIndex(int index, int count, bool wrap)
{
return wrap ? (index + 1) % count : math.min(index + 1, count - 1);
}
internal static float3 GetLinearTangent(float3 point, float3 to)
{
return (to - point) / 3.0f;
}
/// <summary>
/// Reset a transform position to a position while keeping knot positions in the same place. This modifies both
/// knot positions and transform position.
/// </summary>
/// <param name="container">The target spline.</param>
/// <param name="position">The </param>
public static void SetPivot(SplineContainer container, Vector3 position)
{
var transform = container.transform;
var delta = position - transform.position;
transform.position = position;
var spline = container.Spline;
for (int i = 0, c = spline.Count; i < c; i++)
spline[i] = spline[i] - delta;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd0f968f285cfdd48aa6d1e9bf2a599f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,334 @@
// This file is generated. Do not modify by hand.
// XML documentation file not found. To check if public methods have XML comments,
// make sure the XML doc file is present and located next to the scraped dll
namespace UnityEngine.Splines
{
public struct BezierCurve : System.IEquatable<UnityEngine.Splines.BezierCurve>
{
public Unity.Mathematics.float3 P0;
public Unity.Mathematics.float3 P1;
public Unity.Mathematics.float3 P2;
public Unity.Mathematics.float3 P3;
public Unity.Mathematics.float3 Tangent0 { get; set; }
public Unity.Mathematics.float3 Tangent1 { get; set; }
public BezierCurve(Unity.Mathematics.float3 p0, Unity.Mathematics.float3 p1) {}
public BezierCurve(UnityEngine.Splines.BezierKnot a, UnityEngine.Splines.BezierKnot b) {}
public BezierCurve(Unity.Mathematics.float3 p0, Unity.Mathematics.float3 p1, Unity.Mathematics.float3 p2) {}
public BezierCurve(Unity.Mathematics.float3 p0, Unity.Mathematics.float3 p1, Unity.Mathematics.float3 p2, Unity.Mathematics.float3 p3) {}
public virtual bool Equals(object obj);
public virtual bool Equals(UnityEngine.Splines.BezierCurve other);
public static UnityEngine.Splines.BezierCurve FromTangent(Unity.Mathematics.float3 pointA, Unity.Mathematics.float3 tangentOutA, Unity.Mathematics.float3 pointB, Unity.Mathematics.float3 tangentInB);
public virtual int GetHashCode();
public static bool operator ==(UnityEngine.Splines.BezierCurve left, UnityEngine.Splines.BezierCurve right);
public static bool operator !=(UnityEngine.Splines.BezierCurve left, UnityEngine.Splines.BezierCurve right);
}
public struct BezierKnot : UnityEngine.ISerializationCallbackReceiver
{
public Unity.Mathematics.float3 Position;
public Unity.Mathematics.quaternion Rotation;
public Unity.Mathematics.float3 TangentIn;
public Unity.Mathematics.float3 TangentOut;
public BezierKnot(Unity.Mathematics.float3 position) {}
public BezierKnot(Unity.Mathematics.float3 position, Unity.Mathematics.float3 tangentIn, Unity.Mathematics.float3 tangentOut, Unity.Mathematics.quaternion rotation) {}
public virtual void OnAfterDeserialize();
public virtual void OnBeforeSerialize();
public static UnityEngine.Splines.BezierKnot operator +(UnityEngine.Splines.BezierKnot knot, Unity.Mathematics.float3 rhs);
public static UnityEngine.Splines.BezierKnot operator -(UnityEngine.Splines.BezierKnot knot, Unity.Mathematics.float3 rhs);
public UnityEngine.Splines.BezierKnot Transform(Unity.Mathematics.float4x4 matrix);
}
public static class CurveUtility
{
public static float ApproximateLength(UnityEngine.Splines.BezierCurve curve);
public static void CalculateCurveLengths(UnityEngine.Splines.BezierCurve curve, UnityEngine.Splines.DistanceToInterpolation[] lookupTable);
public static float CalculateLength(UnityEngine.Splines.BezierCurve curve, int resolution = 30);
public static Unity.Mathematics.float3 EvaluateAcceleration(UnityEngine.Splines.BezierCurve curve, float t);
public static float EvaluateCurvature(UnityEngine.Splines.BezierCurve curve, float t);
public static Unity.Mathematics.float3 EvaluatePosition(UnityEngine.Splines.BezierCurve curve, float t);
public static Unity.Mathematics.float3 EvaluateTangent(UnityEngine.Splines.BezierCurve curve, float t);
public static float GetDistanceToInterpolation<T>(T lut, float distance) where T : System.Collections.Generic.IReadOnlyList<UnityEngine.Splines.DistanceToInterpolation>;
public static void Split(UnityEngine.Splines.BezierCurve curve, float t, out UnityEngine.Splines.BezierCurve left, out UnityEngine.Splines.BezierCurve right);
}
public struct DataPoint<TDataType> : System.IComparable<float>, System.IComparable<UnityEngine.Splines.DataPoint<TDataType>>, UnityEngine.Splines.IDataPoint
{
public virtual float Index { get; set; }
public TDataType Value { get; set; }
public DataPoint(float index, TDataType value) {}
public virtual int CompareTo(float other);
public virtual int CompareTo(UnityEngine.Splines.DataPoint<TDataType> other);
public virtual string ToString();
}
public struct DistanceToInterpolation
{
public float Distance;
public float T;
}
public interface IDataPoint
{
public abstract float Index { get; set; }
}
public interface IInterpolator<T>
{
public abstract T Interpolate(T from, T to, float t);
}
public interface ISpline : System.Collections.Generic.IEnumerable<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyCollection<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyList<UnityEngine.Splines.BezierKnot>, System.Collections.IEnumerable
{
public abstract bool Closed { get; }
public abstract UnityEngine.Splines.BezierCurve GetCurve(int index);
public abstract float GetCurveInterpolation(int curveIndex, float curveDistance);
public abstract float GetCurveLength(int index);
public abstract float GetLength();
}
public interface ISplineProvider
{
public abstract System.Collections.Generic.IEnumerable<UnityEngine.Splines.Spline> Splines { get; }
}
[System.Reflection.DefaultMember(@"Item")] public struct NativeSpline : System.Collections.Generic.IEnumerable<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyCollection<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyList<UnityEngine.Splines.BezierKnot>, System.Collections.IEnumerable, System.IDisposable, UnityEngine.Splines.ISpline
{
public virtual bool Closed { get; }
public virtual int Count { get; }
public virtual UnityEngine.Splines.BezierKnot this[int index] { get; }
public Unity.Collections.NativeArray<UnityEngine.Splines.BezierKnot> Knots { get; }
public NativeSpline(UnityEngine.Splines.ISpline spline, Unity.Collections.Allocator allocator = 2) {}
public NativeSpline(UnityEngine.Splines.ISpline spline, Unity.Mathematics.float4x4 transform, Unity.Collections.Allocator allocator = 2) {}
public NativeSpline(System.Collections.Generic.IReadOnlyList<UnityEngine.Splines.BezierKnot> knots, bool closed, Unity.Mathematics.float4x4 transform, Unity.Collections.Allocator allocator = 2) {}
public virtual void Dispose();
public virtual UnityEngine.Splines.BezierCurve GetCurve(int index);
public virtual float GetCurveInterpolation(int curveIndex, float curveDistance);
public virtual float GetCurveLength(int curveIndex);
public virtual System.Collections.Generic.IEnumerator<UnityEngine.Splines.BezierKnot> GetEnumerator();
public virtual float GetLength();
}
public enum PathIndexUnit
{
public const UnityEngine.Splines.PathIndexUnit Distance = 0;
public const UnityEngine.Splines.PathIndexUnit Knot = 2;
public const UnityEngine.Splines.PathIndexUnit Normalized = 1;
public int value__;
}
[System.Reflection.DefaultMember(@"Item")] public class Spline : System.Collections.Generic.ICollection<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IEnumerable<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IList<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyCollection<UnityEngine.Splines.BezierKnot>, System.Collections.Generic.IReadOnlyList<UnityEngine.Splines.BezierKnot>, System.Collections.IEnumerable, UnityEngine.Splines.ISpline
{
public event System.Action changed;
public virtual bool Closed { get; set; }
public virtual int Count { get; }
public UnityEngine.Splines.SplineType EditType { get; set; }
public virtual bool IsReadOnly { get; }
public virtual UnityEngine.Splines.BezierKnot this[int index] { get; set; }
public System.Collections.Generic.IEnumerable<UnityEngine.Splines.BezierKnot> Knots { get; }
public Spline() {}
public Spline(System.Collections.Generic.IEnumerable<UnityEngine.Splines.BezierKnot> knots, bool closed = False) {}
public Spline(int knotCapacity, bool closed = False) {}
public virtual void Add(UnityEngine.Splines.BezierKnot item);
public virtual void Clear();
public virtual bool Contains(UnityEngine.Splines.BezierKnot item);
public void Copy(UnityEngine.Splines.Spline toCopy);
public virtual void CopyTo(UnityEngine.Splines.BezierKnot[] array, int arrayIndex);
public virtual UnityEngine.Splines.BezierCurve GetCurve(int index);
public virtual float GetCurveInterpolation(int curveIndex, float curveDistance);
public virtual float GetCurveLength(int index);
public virtual System.Collections.Generic.IEnumerator<UnityEngine.Splines.BezierKnot> GetEnumerator();
public virtual float GetLength();
public virtual int IndexOf(UnityEngine.Splines.BezierKnot item);
public virtual void Insert(int index, UnityEngine.Splines.BezierKnot knot);
protected virtual void OnSplineChanged();
public virtual bool Remove(UnityEngine.Splines.BezierKnot item);
public virtual void RemoveAt(int index);
public void Resize(int newSize);
public UnityEngine.Splines.BezierKnot[] ToArray();
public void Warmup();
}
public struct SplineComputeBufferScope<T> : System.IDisposable where T : UnityEngine.Splines.ISpline
{
public UnityEngine.ComputeBuffer CurveLengths { get; }
public UnityEngine.ComputeBuffer Curves { get; }
public UnityEngine.Vector4 Info { get; }
public SplineComputeBufferScope(T spline) {}
public void Bind(UnityEngine.ComputeShader shader, int kernel, string info, string curves, string lengths);
public virtual void Dispose();
public void Upload();
}
[UnityEngine.Icon(@"Packages/com.unity.splines/Editor/Resources/Icons/KnotPlacementTool.png")] public sealed class SplineContainer : UnityEngine.MonoBehaviour, UnityEngine.Splines.ISplineProvider
{
public UnityEngine.Splines.Spline Spline { get; set; }
public SplineContainer() {}
public float CalculateLength();
public bool Evaluate(float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector);
public Unity.Mathematics.float3 EvaluateAcceleration(float t);
public Unity.Mathematics.float3 EvaluatePosition(float t);
public Unity.Mathematics.float3 EvaluateTangent(float t);
public Unity.Mathematics.float3 EvaluateUpVector(float t);
}
[System.Reflection.DefaultMember(@"Item")] public class SplineData<T> : System.Collections.Generic.IEnumerable<UnityEngine.Splines.DataPoint<T>>, System.Collections.IEnumerable
{
public event System.Action changed;
public int Count { get; }
public UnityEngine.Splines.DataPoint<T> this[int index] { get; set; }
public UnityEngine.Splines.PathIndexUnit PathIndexUnit { get; set; }
public SplineData() {}
public SplineData(System.Collections.Generic.IEnumerable<UnityEngine.Splines.DataPoint<T>> dataPoints) {}
public SplineData(T init) {}
public void Add(UnityEngine.Splines.DataPoint<T> dataPoint);
public void Add(float t, T data);
public void Clear();
public void ConvertPathUnit<TSplineType>(TSplineType spline, UnityEngine.Splines.PathIndexUnit toUnit) where TSplineType : UnityEngine.Splines.ISpline;
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, TInterpolator interpolator) where TSpline : UnityEngine.Splines.ISpline where TInterpolator : UnityEngine.Splines.IInterpolator<T>;
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, UnityEngine.Splines.PathIndexUnit indexUnit, TInterpolator interpolator) where TSpline : UnityEngine.Splines.ISpline where TInterpolator : UnityEngine.Splines.IInterpolator<T>;
[System.Runtime.CompilerServices.IteratorStateMachine(typeof(UnityEngine.Splines.SplineData<>))] public virtual System.Collections.Generic.IEnumerator<UnityEngine.Splines.DataPoint<T>> GetEnumerator();
public float GetNormalizedInterpolation<TSplineType>(TSplineType spline, float t) where TSplineType : UnityEngine.Splines.ISpline;
public void RemoveAt(int index);
public void SetDataPoint(int index, UnityEngine.Splines.DataPoint<T> value);
public void SetKeyframeNoSort(int index, UnityEngine.Splines.DataPoint<T> value);
public void SortIfNecessary();
}
[System.AttributeUsage(256)] public abstract class SplineDataHandleAttribute : System.Attribute
{
protected SplineDataHandleAttribute() {}
}
[UnityEngine.RequireComponent(typeof(UnityEngine.MeshFilter), typeof(UnityEngine.MeshRenderer))] public class SplineExtrude : UnityEngine.MonoBehaviour
{
public bool capped { get; set; }
public UnityEngine.Splines.SplineContainer container { get; set; }
public float radius { get; set; }
public UnityEngine.Vector2 range { get; set; }
public int rebuildFrequency { get; set; }
public bool rebuildOnSplineChange { get; set; }
public float segmentsPerUnit { get; set; }
public int sides { get; set; }
public UnityEngine.Splines.Spline spline { get; }
public SplineExtrude() {}
public void Rebuild();
}
public static class SplineFactory
{
public static UnityEngine.Splines.Spline CreateLinear(System.Collections.Generic.IList<Unity.Mathematics.float3> positions, bool closed = False);
public static UnityEngine.Splines.Spline CreateRoundedSquare(float radius, float rounding);
public static UnityEngine.Splines.Spline CreateSquare(float radius);
}
public static class SplineGizmoUtility
{
public static void DrawGizmos(UnityEngine.Splines.ISplineProvider provider);
}
public static class SplineMesh
{
public static void Extrude<T>(T spline, UnityEngine.Mesh mesh, float radius, int sides, int segments, bool capped = True) where T : UnityEngine.Splines.ISpline;
public static void Extrude<T>(T spline, UnityEngine.Mesh mesh, float radius, int sides, int segments, bool capped, Unity.Mathematics.float2 range) where T : UnityEngine.Splines.ISpline;
public static void Extrude<TSplineType, TVertexType, TIndexType>(TSplineType spline, Unity.Collections.NativeArray<TVertexType> vertices, Unity.Collections.NativeArray<TIndexType> indices, float radius, int sides, int segments, bool capped, Unity.Mathematics.float2 range) where TSplineType : UnityEngine.Splines.ISpline where TVertexType : System.ValueType, UnityEngine.Splines.SplineMesh.ISplineVertexData, new() where TIndexType : System.ValueType, new();
public static void GetVertexAndIndexCount(int sides, int segments, bool capped, bool closed, UnityEngine.Vector2 range, out int vertexCount, out int indexCount);
public interface ISplineVertexData
{
public abstract UnityEngine.Vector3 normal { get; set; }
public abstract UnityEngine.Vector3 position { get; set; }
public abstract UnityEngine.Vector2 texture { get; set; }
}
}
public enum SplineType
{
public const UnityEngine.Splines.SplineType Bezier = 1;
public const UnityEngine.Splines.SplineType CatmullRom = 0;
public const UnityEngine.Splines.SplineType Linear = 2;
public byte value__;
}
[System.Runtime.CompilerServices.Extension] public static class SplineUtility
{
public const int DrawResolutionDefault = 10;
public const int PickResolutionDefault = 4;
public const int PickResolutionMax = 64;
public const int PickResolutionMin = 2;
[System.Runtime.CompilerServices.Extension] public static float CalculateLength<T>(T spline, Unity.Mathematics.float4x4 transform) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static float ConvertIndexUnit<T>(T spline, float t, UnityEngine.Splines.PathIndexUnit fromPathUnit, UnityEngine.Splines.PathIndexUnit targetPathUnit) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static float CurveToSplineT<T>(T spline, float curve) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static bool Evaluate<T>(T spline, float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 EvaluateAcceleration<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static float EvaluateCurvature<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 EvaluateCurvatureCenter<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 EvaluatePosition<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 EvaluateTangent<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 EvaluateUpVector<T>(T spline, float t) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static UnityEngine.Bounds GetBounds<T>(T spline) where T : UnityEngine.Splines.ISpline;
public static float GetNearestPoint<T>(T spline, Unity.Mathematics.float3 point, out Unity.Mathematics.float3 nearest, out float t, int resolution = 4, int iterations = 2) where T : UnityEngine.Splines.ISpline;
public static float GetNearestPoint<T>(T spline, UnityEngine.Ray ray, out Unity.Mathematics.float3 nearest, out float t, int resolution = 4, int iterations = 2) where T : UnityEngine.Splines.ISpline;
public static float GetNormalizedInterpolation<T>(T spline, float t, UnityEngine.Splines.PathIndexUnit originalPathUnit) where T : UnityEngine.Splines.ISpline;
[System.Runtime.CompilerServices.Extension] public static Unity.Mathematics.float3 GetPointAtLinearDistance<T>(T spline, float fromT, float relativeDistance, out float resultPointT) where T : UnityEngine.Splines.ISpline;
public static int GetSegmentCount(float length, int resolution);
public static void SetPivot(UnityEngine.Splines.SplineContainer container, UnityEngine.Vector3 position);
[System.Runtime.CompilerServices.Extension] public static int SplineToCurveT<T>(T spline, float splineT, out float curveT) where T : UnityEngine.Splines.ISpline;
}
}
namespace UnityEngine.Splines.Interpolators
{
public struct LerpColor : UnityEngine.Splines.IInterpolator<UnityEngine.Color>
{
public virtual UnityEngine.Color Interpolate(UnityEngine.Color a, UnityEngine.Color b, float t);
}
public struct LerpFloat : UnityEngine.Splines.IInterpolator<float>
{
public virtual float Interpolate(float a, float b, float t);
}
public struct LerpFloat2 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2>
{
public virtual Unity.Mathematics.float2 Interpolate(Unity.Mathematics.float2 a, Unity.Mathematics.float2 b, float t);
}
public struct LerpFloat3 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3>
{
public virtual Unity.Mathematics.float3 Interpolate(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, float t);
}
public struct LerpFloat4 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4>
{
public virtual Unity.Mathematics.float4 Interpolate(Unity.Mathematics.float4 a, Unity.Mathematics.float4 b, float t);
}
public struct LerpQuaternion : UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion>
{
public virtual Unity.Mathematics.quaternion Interpolate(Unity.Mathematics.quaternion a, Unity.Mathematics.quaternion b, float t);
}
public struct SlerpQuaternion : UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion>
{
public virtual Unity.Mathematics.quaternion Interpolate(Unity.Mathematics.quaternion a, Unity.Mathematics.quaternion b, float t);
}
public struct SmoothStepFloat : UnityEngine.Splines.IInterpolator<float>
{
public virtual float Interpolate(float a, float b, float t);
}
public struct SmoothStepFloat2 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2>
{
public virtual Unity.Mathematics.float2 Interpolate(Unity.Mathematics.float2 a, Unity.Mathematics.float2 b, float t);
}
public struct SmoothStepFloat3 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3>
{
public virtual Unity.Mathematics.float3 Interpolate(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, float t);
}
public struct SmoothStepFloat4 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4>
{
public virtual Unity.Mathematics.float4 Interpolate(Unity.Mathematics.float4 a, Unity.Mathematics.float4 b, float t);
}
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 130dcbb6b6362ac8c8b12deb05150463
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
{
"name": "Unity.Splines",
"rootNamespace": "UnityEngine.Splines",
"references": [
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:2665a8d13d1b3f18800f46e256720795"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 21d1eb854b91ade49bc69a263d12bee2
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: