initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Tests")]
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ede6128040e87a146aa6430dfad44478
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 65945d4401044bc095f3fe90d615eb81
|
||||
timeCreated: 1623790171
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb146aa65c3e4768bb8e02d69185a3ae
|
||||
timeCreated: 1626876005
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a6c59f50ab3b47fba320494d3f3defa5
|
||||
timeCreated: 1625671559
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 508048e85d1b4f1aac151183c59fadf1
|
||||
timeCreated: 1613496307
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 515aafa3af3048d18c327fa7af8028a7
|
||||
timeCreated: 1628707414
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 014bdaee1f9322e4aab0358e6f7e3959
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c4f48d56433f4be3b319c943db02157
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b0da2494b9f846808b9175a7333f72dc
|
||||
timeCreated: 1613681438
|
426
Library/PackageCache/com.unity.splines@1.0.1/Runtime/Spline.cs
Normal file
426
Library/PackageCache/com.unity.splines@1.0.1/Runtime/Spline.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1737f1b4489bd468cbf919947c6836e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 29a074d529cf945029ef7cf40540c9df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8647d8846516479e99dbf89e8b927446
|
||||
timeCreated: 1643399835
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dab5c7d4c32e743048dfca98e2d5914f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5475addd28b94489a727bbc680f89a3d
|
||||
timeCreated: 1613497560
|
|
@ -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 {}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f26841404a934577bf234bc14475f649
|
||||
timeCreated: 1625517748
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 222dc08d484f16869bdd89edd9d368fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e9d35cf5830b403f8a24cef0e22ee40c
|
||||
timeCreated: 1628776092
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2b74686beb0670a4ea61325c0acf4411
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e824b25245647358e471f403288f20d
|
||||
timeCreated: 1637607905
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cd0dbc3db7748baa71797f5bc16a1b4
|
||||
timeCreated: 1628884843
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bc3d3aa21eb40814391409a5b8bb11e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fd0f968f285cfdd48aa6d1e9bf2a599f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 130dcbb6b6362ac8c8b12deb05150463
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 21d1eb854b91ade49bc69a263d12bee2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue