first commit
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
static class ArrayUtility
|
||||
{
|
||||
public static void RemoveAt<T>(ref T[] array, int index)
|
||||
{
|
||||
if (index < 0 || index >= array.Length)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
Array.Copy(array, index + 1, array, index, array.Length - index - 1);
|
||||
Array.Resize(ref array, array.Length - 1);
|
||||
}
|
||||
|
||||
public static void RemoveAt<T>(ref T[] array, IEnumerable<int> indices)
|
||||
{
|
||||
List<int> sorted = new List<int>(indices);
|
||||
sorted.Sort();
|
||||
SortedRemoveAt(ref array, sorted);
|
||||
}
|
||||
|
||||
public static void SortedRemoveAt<T>(ref T[] array, IList<int> sorted)
|
||||
{
|
||||
int indexeSortedCount = sorted.Count;
|
||||
int len = array.Length;
|
||||
|
||||
T[] newArray = new T[len - indexeSortedCount];
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (n < indexeSortedCount && sorted[n] == i)
|
||||
{
|
||||
// handle duplicate indexes
|
||||
while (n < indexeSortedCount && sorted[n] == i)
|
||||
n++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
newArray[i - n] = array[i];
|
||||
}
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
public static void Remove<T>(ref T[] array, T element)
|
||||
{
|
||||
var index = Array.IndexOf(array, element);
|
||||
if (index >= 0)
|
||||
RemoveAt(ref array, index);
|
||||
}
|
||||
|
||||
public static void Add<T>(ref T[] array, T element)
|
||||
{
|
||||
Array.Resize(ref array, array.Length + 1);
|
||||
array[array.Length - 1] = element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 118eaaeabde5fb9419b05ba3dd5e7078
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Debug")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Tests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Debug")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ede6128040e87a146aa6430dfad44478
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,211 @@
|
||||
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 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>
|
||||
/// Multiply the curve positions by a matrix.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to multiply.</param>
|
||||
/// <returns>A new BezierCurve multiplied by matrix.</returns>
|
||||
public BezierCurve Transform(float4x4 matrix)
|
||||
{
|
||||
return new BezierCurve(
|
||||
math.transform(matrix, P0),
|
||||
math.transform(matrix, P1),
|
||||
math.transform(matrix, P2),
|
||||
math.transform(matrix, P3));
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Gets the same BezierCurve but in the opposite direction.
|
||||
/// </summary>
|
||||
/// <returns>Returns the BezierCurve struct in the inverse direction.</returns>
|
||||
public BezierCurve GetInvertedCurve()
|
||||
{
|
||||
return new BezierCurve(P3, P2, P1, P0);
|
||||
}
|
||||
|
||||
/// <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,194 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// This struct contains position and tangent data for a knot. The position is a scalar point and the tangents are vectors.
|
||||
/// 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, IEquatable<BezierKnot>
|
||||
{
|
||||
/// <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 vector that leads 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 vector that follows 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): this(position, 0f, 0f, quaternion.identity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="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>
|
||||
public BezierKnot(float3 position, float3 tangentIn, float3 tangentOut)
|
||||
: this(position, tangentIn, tangentOut, 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);
|
||||
}
|
||||
|
||||
internal BezierKnot BakeTangentDirectionToRotation(bool mirrored, BezierTangent main = BezierTangent.Out)
|
||||
{
|
||||
if (mirrored)
|
||||
{
|
||||
float lead = math.length(main == BezierTangent.In ? TangentIn : TangentOut);
|
||||
return new BezierKnot(Position,
|
||||
new float3(0f, 0f, -lead),
|
||||
new float3(0f, 0f, lead),
|
||||
SplineUtility.GetKnotRotation(
|
||||
math.mul(Rotation, main == BezierTangent.In ? -TangentIn : TangentOut),
|
||||
math.mul(Rotation, math.up())));
|
||||
}
|
||||
|
||||
return new BezierKnot(Position,
|
||||
new float3(0, 0, -math.length(TangentIn)),
|
||||
new float3(0, 0, math.length(TangentOut)),
|
||||
Rotation = SplineUtility.GetKnotRotation(
|
||||
math.mul(Rotation, main == BezierTangent.In ? -TangentIn : TangentOut),
|
||||
math.mul(Rotation, math.up())));
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a string with the values of this knot.
|
||||
/// </summary>
|
||||
/// <returns>A summary of the values contained by this knot.</returns>
|
||||
public override string ToString() => $"{{{Position}, {TangentIn}, {TangentOut}, {Rotation}}}";
|
||||
|
||||
/// <summary>
|
||||
/// Compare two knots for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">The knot to compare against.</param>
|
||||
/// <returns>Returns true when the position, tangents, and rotation of each knot are identical.</returns>
|
||||
public bool Equals(BezierKnot other)
|
||||
{
|
||||
return Position.Equals(other.Position)
|
||||
&& TangentIn.Equals(other.TangentIn)
|
||||
&& TangentOut.Equals(other.TangentOut)
|
||||
&& Rotation.Equals(other.Rotation);
|
||||
}
|
||||
|
||||
/// <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="BezierKnot"/> and the values of each knot are
|
||||
/// identical.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is BezierKnot other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a hash code for this knot.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the knot.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Position, TangentIn, TangentOut, Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb146aa65c3e4768bb8e02d69185a3ae
|
||||
timeCreated: 1626876005
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the different ways a tool might interact with a tangent handle.
|
||||
/// </summary>
|
||||
public enum TangentMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Tangents are calculated using the previous and next knot positions.
|
||||
/// </summary>
|
||||
AutoSmooth = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Tangents are not used. A linear spline is a series of knots connected by a path with no curvature.
|
||||
/// </summary>
|
||||
Linear = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Tangents are kept parallel and with matching lengths. Modifying one tangent updates the opposite
|
||||
/// tangent to the inverse direction and equivalent length.
|
||||
/// </summary>
|
||||
Mirrored = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Tangents are kept parallel. Modifying one tangent changes the direction of the opposite tangent,
|
||||
/// but does not affect the opposite tangent's length.
|
||||
/// </summary>
|
||||
Continuous = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The length and direction of the tangents are independent of each other. Modifying one tangent on a knot does not affect the other.
|
||||
/// </summary>
|
||||
Broken = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5934d14b31f50047a10b7006a91034b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the direction of a <see cref="BezierKnot"/> tangent. A spline is composed of a list of
|
||||
/// <see cref="BezierKnot"/>, where every knot can be either the start or end of a <see cref="BezierCurve"/>. The
|
||||
/// <see cref="BezierTangent"/> enum indicates which tangent should be used to construct a curve.
|
||||
/// </summary>
|
||||
public enum BezierTangent
|
||||
{
|
||||
/// <summary>
|
||||
/// The "In" tangent is the second tangent in a curve composed of two knots.
|
||||
/// </summary>
|
||||
In = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The "Out" tangent is the first tangent in a curve composed of two knots.
|
||||
/// </summary>
|
||||
Out = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4f5990dfe3c40a2b2b9c51a7637f347
|
||||
timeCreated: 1649118570
|
||||
@@ -0,0 +1,487 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
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;
|
||||
|
||||
/// <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 nativeLUT = new NativeArray<DistanceToInterpolation>(lookupTable, Allocator.Temp);
|
||||
CalculateCurveLengths(curve, nativeLUT);
|
||||
nativeLUT.CopyTo(lookupTable);
|
||||
}
|
||||
|
||||
/// <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 native array to populate with distance to interpolation ratio data.</param>
|
||||
public static void CalculateCurveLengths(BezierCurve curve, NativeArray<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;
|
||||
}
|
||||
}
|
||||
|
||||
const float k_Epsilon = 0.0001f;
|
||||
/// <summary>
|
||||
/// Mathf.Approximately is not working when using BurstCompile, causing NaN values in the EvaluateUpVector
|
||||
/// method when tangents have a 0 length. Using this method instead fixes that.
|
||||
/// </summary>
|
||||
static bool Approximately(float a, float b)
|
||||
{
|
||||
// Reusing Mathf.Approximately code
|
||||
return math.abs(b - a) < math.max(0.000001f * math.max(math.abs(a), math.abs(b)), k_Epsilon * 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the approximate length of a <see cref="BezierCurve"/>. This is less accurate than
|
||||
/// <see 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 void EvaluateUpVectors(BezierCurve curve, float3 startUp, float3 endUp, NativeArray<float3> upVectors)
|
||||
{
|
||||
upVectors[0] = startUp;
|
||||
upVectors[upVectors.Length - 1] = endUp;
|
||||
|
||||
for(int i = 1; i < upVectors.Length - 1; i++)
|
||||
{
|
||||
var curveT = i / (float)(upVectors.Length - 1);
|
||||
upVectors[i] = EvaluateUpVector(curve, curveT, upVectors[0], endUp);
|
||||
}
|
||||
}
|
||||
|
||||
internal static float3 EvaluateUpVector(BezierCurve curve, float t, float3 startUp, float3 endUp,
|
||||
bool fixEndUpMismatch = true)
|
||||
{
|
||||
// Ensure we have workable tangents by linearizing ones that are of zero length
|
||||
var linearTangentLen = math.length(SplineUtility.GetExplicitLinearTangent(curve.P0, curve.P3));
|
||||
var linearTangentOut = math.normalize(curve.P3 - curve.P0) * linearTangentLen;
|
||||
if (Approximately(math.length(curve.P1 - curve.P0), 0f))
|
||||
curve.P1 = curve.P0 + linearTangentOut;
|
||||
if (Approximately(math.length(curve.P2 - curve.P3), 0f))
|
||||
curve.P2 = curve.P3 - linearTangentOut;
|
||||
|
||||
var normalBuffer = new NativeArray<float3>(k_NormalsPerCurve, Allocator.Temp);
|
||||
|
||||
// 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));
|
||||
// SPLB-185 : If the tangent and normal are parallel, we can't construct a valid frame
|
||||
// rather than returning a value based on startUp and endUp, we return a zero vector
|
||||
// to indicate that this is not a valid up vector.
|
||||
if(float.IsNaN(frame.binormal.x))
|
||||
return float3.zero;
|
||||
|
||||
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);
|
||||
|
||||
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 (!fixEndUpMismatch)
|
||||
return upVector;
|
||||
|
||||
if (prevT <= t && currentT >= t)
|
||||
upVector = endUp;
|
||||
|
||||
var lastFrameNormal = 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 < normalBuffer.Length; i++)
|
||||
{
|
||||
var normal = normalBuffer[i];
|
||||
var adjustmentAngle = math.lerp(0f, angleBetweenNormals, currentT);
|
||||
var tangent = math.normalize(EvaluateTangent(curve, currentT));
|
||||
var adjustedNormal = math.rotate(quaternion.AxisAngle(tangent, -adjustmentAngle), normal);
|
||||
|
||||
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(normalBuffer[i - 1], 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 = EvaluatePosition(curve, nextRMFrameT);
|
||||
nextRMFrame.tangent = 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;
|
||||
}
|
||||
|
||||
static readonly DistanceToInterpolation[] k_DistanceLUT = new DistanceToInterpolation[24];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normalized interpolation, (t), that corresponds to a distance on a <see cref="BezierCurve"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is inefficient to call this method frequently. For better performance create a
|
||||
/// <see cref="DistanceToInterpolation"/> cache with <see cref="CalculateCurveLengths"/> and use the
|
||||
/// overload of this method which accepts a lookup table.
|
||||
/// </remarks>
|
||||
/// <param name="curve">The <see cref="BezierCurve"/> to calculate the distance to interpolation ratio for.</param>
|
||||
/// <param name="distance">The curve-relative distance to convert to an interpolation ratio (also referred to as 't').</param>
|
||||
/// <returns> Returns the normalized interpolation ratio associated to distance on the designated curve.</returns>
|
||||
public static float GetDistanceToInterpolation(BezierCurve curve, float distance)
|
||||
{
|
||||
CalculateCurveLengths(curve, k_DistanceLUT);
|
||||
return GetDistanceToInterpolation(k_DistanceLUT, distance);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point on a <see cref="BezierCurve"/> nearest to a ray.
|
||||
/// </summary>
|
||||
/// <param name="curve">The <see cref="BezierCurve"/> to compare.</param>
|
||||
/// <param name="ray">The input ray.</param>
|
||||
/// <param name="resolution">The number of line segments on this curve that are rasterized when testing
|
||||
/// for the nearest point. A higher value is more accurate, but slower to calculate.</param>
|
||||
/// <returns>Returns the nearest position on the curve to a ray.</returns>
|
||||
public static float3 GetNearestPoint(BezierCurve curve, Ray ray, int resolution = 16)
|
||||
{
|
||||
GetNearestPoint(curve, ray, out var position, out _, resolution);
|
||||
return position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point on a <see cref="BezierCurve"/> nearest to a ray.
|
||||
/// </summary>
|
||||
/// <param name="curve">The <see cref="BezierCurve"/> to compare.</param>
|
||||
/// <param name="ray">The input ray.</param>
|
||||
/// <param name="position">The nearest position on the curve to a ray.</param>
|
||||
/// <param name="interpolation">The ratio from range 0 to 1 along the curve at which the nearest point is located.</param>
|
||||
/// <param name="resolution">The number of line segments that this curve will be rasterized to when testing
|
||||
/// for nearest point. A higher value will be more accurate, but slower to calculate.</param>
|
||||
/// <returns>The distance from ray to nearest point on a <see cref="BezierCurve"/>.</returns>
|
||||
public static float GetNearestPoint(BezierCurve curve, Ray ray, out float3 position, out float interpolation, int resolution = 16)
|
||||
{
|
||||
float bestDistSqr = float.PositiveInfinity;
|
||||
float bestLineParam = 0f;
|
||||
|
||||
interpolation = 0f;
|
||||
position = float3.zero;
|
||||
|
||||
float3 a = EvaluatePosition(curve, 0f);
|
||||
float3 ro = ray.origin, rd = ray.direction;
|
||||
|
||||
for (int i = 1; i < resolution; ++i)
|
||||
{
|
||||
float t = i / (resolution - 1f);
|
||||
float3 b = EvaluatePosition(curve, t);
|
||||
|
||||
var (rayPoint, linePoint) = SplineMath.RayLineNearestPoint(ro, rd, a, b, out _, out var lineParam);
|
||||
var distSqr = math.lengthsq(linePoint - rayPoint);
|
||||
|
||||
if (distSqr < bestDistSqr)
|
||||
{
|
||||
position = linePoint;
|
||||
bestDistSqr = distSqr;
|
||||
bestLineParam = lineParam;
|
||||
interpolation = (i - 1) / (resolution - 1f);
|
||||
}
|
||||
|
||||
a = b;
|
||||
}
|
||||
|
||||
interpolation += bestLineParam * (1f / (resolution - 1f));
|
||||
return math.sqrt(bestDistSqr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,192 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for accessing a <see cref="SplineData{T}"/> value stored on <see cref="Spline"/> through one of the
|
||||
/// embedded key value collections. It is not required to use this class to access embedded
|
||||
/// <see cref="SplineData{T}"/>, however it does provide some convenient functionality for working with this data
|
||||
/// in the Inspector.
|
||||
/// </summary>
|
||||
/// <seealso cref="EmbeddedSplineDataFieldsAttribute"/>
|
||||
/// <seealso cref="Spline.GetOrCreateFloatData"/>
|
||||
/// <seealso cref="Spline.GetOrCreateFloat4Data"/>
|
||||
/// <seealso cref="Spline.GetOrCreateIntData"/>
|
||||
/// <seealso cref="Spline.GetOrCreateObjectData"/>
|
||||
[Serializable]
|
||||
public class EmbeddedSplineData
|
||||
{
|
||||
[SerializeField]
|
||||
SplineContainer m_Container;
|
||||
|
||||
[SerializeField]
|
||||
int m_SplineIndex;
|
||||
|
||||
[SerializeField]
|
||||
EmbeddedSplineDataType m_Type;
|
||||
|
||||
[SerializeField]
|
||||
string m_Key;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SplineContainer"/> that holds the <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
public SplineContainer Container
|
||||
{
|
||||
get => m_Container;
|
||||
set => m_Container = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of the <see cref="Spline"/> on the <see cref="SplineContainer"/>.
|
||||
/// </summary>
|
||||
public int SplineIndex
|
||||
{
|
||||
get => m_SplineIndex;
|
||||
set => m_SplineIndex = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of data stored by the <see cref="SplineData{T}"/> collection. Embedded <see cref="SplineData{T}"/>
|
||||
/// is restricted to a pre-defined set of primitive types.
|
||||
/// </summary>
|
||||
public EmbeddedSplineDataType Type
|
||||
{
|
||||
get => m_Type;
|
||||
set => m_Type = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A unique string value used to identify and access a <see cref="SplineData{T}"/> collection stored in a
|
||||
/// <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
public string Key
|
||||
{
|
||||
get => m_Key;
|
||||
set => m_Key = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="EmbeddedSplineData"/> instance with no parameters.
|
||||
/// </summary>
|
||||
public EmbeddedSplineData() : this(null, EmbeddedSplineDataType.Float)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="EmbeddedSplineData"/> with parameters.
|
||||
/// </summary>
|
||||
/// <param name="key">A unique string value used to identify and access a <see cref="SplineData{T}"/> collection
|
||||
/// stored in a <see cref="Spline"/>.</param>
|
||||
/// <param name="type">The type of data stored by the <see cref="SplineData{T}"/> collection.</param>
|
||||
/// <param name="container">The <see cref="SplineContainer"/> that holds the <see cref="Spline"/>.</param>
|
||||
/// <param name="splineIndex">The index of the <see cref="Spline"/> on the <see cref="SplineContainer"/>.</param>
|
||||
public EmbeddedSplineData(
|
||||
string key,
|
||||
EmbeddedSplineDataType type,
|
||||
SplineContainer container = null,
|
||||
int splineIndex = 0)
|
||||
{
|
||||
m_Container = container;
|
||||
m_SplineIndex = splineIndex;
|
||||
m_Key = key;
|
||||
m_Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to get a reference to the <see cref="Spline"/> described by this object.
|
||||
/// </summary>
|
||||
/// <param name="spline">A <see cref="Spline"/> if the <see cref="Container"/> and <see cref="SplineIndex"/> are
|
||||
/// valid, otherwise null.</param>
|
||||
/// <returns>Returns true if the <see cref="Container"/> and <see cref="SplineIndex"/> are valid, otherwise
|
||||
/// false.</returns>
|
||||
public bool TryGetSpline(out Spline spline)
|
||||
{
|
||||
if(Container == null || SplineIndex < 0 || SplineIndex >= Container.Splines.Count)
|
||||
spline = null;
|
||||
else
|
||||
spline = Container.Splines[SplineIndex];
|
||||
return spline != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to get a reference to the <see cref="SplineData{T}"/> described by this object.
|
||||
/// </summary>
|
||||
/// <param name="data">A <see cref="SplineData{T}"/> reference if the <see cref="Container"/>,
|
||||
/// <see cref="SplineIndex"/>, <see cref="Key"/>, and <see cref="Type"/> are valid, otherwise null.</param>
|
||||
/// <returns>Returns true if a <see cref="SplineData{T}"/> value exists, otherwise false.</returns>
|
||||
/// <exception cref="InvalidCastException">An exception is thrown if the requested <see cref="SplineData{T}"/>
|
||||
/// does not match the <see cref="EmbeddedSplineDataType"/>.</exception>
|
||||
public bool TryGetFloatData(out SplineData<float> data)
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Float)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(float)}");
|
||||
return Container.Splines[SplineIndex].TryGetFloatData(Key, out data);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetFloatData"/>
|
||||
public bool TryGetFloat4Data(out SplineData<float4> data)
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Float4)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(float4)}");
|
||||
return Container.Splines[SplineIndex].TryGetFloat4Data(Key, out data);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetFloatData"/>
|
||||
public bool TryGetIntData(out SplineData<int> data)
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Int)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(int)}");
|
||||
return Container.Splines[SplineIndex].TryGetIntData(Key, out data);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetFloatData"/>
|
||||
public bool TryGetObjectData(out SplineData<Object> data)
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Object)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(Object)}");
|
||||
return Container.Splines[SplineIndex].TryGetObjectData(Key, out data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="SplineData{T}"/> for <see cref="Key"/> and <see cref="Type"/>. If an instance matching
|
||||
/// the key and type does not exist, a new entry is appended to the internal collection and returned.
|
||||
/// Note that this is a reference to the stored <see cref="SplineData{T}"/>, not a copy. Any modifications to
|
||||
/// this collection will affect the <see cref="Spline"/> data.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="SplineData{T}"/> of the requested type.</returns>
|
||||
/// <exception cref="InvalidCastException">An exception is thrown if the requested <see cref="SplineData{T}"/>
|
||||
/// does not match the <see cref="EmbeddedSplineDataType"/>.</exception>
|
||||
public SplineData<float> GetOrCreateFloatData()
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Float)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(float)}");
|
||||
return Container.Splines[SplineIndex].GetOrCreateFloatData(Key);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetOrCreateFloatData"/>
|
||||
public SplineData<float4> GetOrCreateFloat4Data()
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Float4)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(float4)}");
|
||||
return Container.Splines[SplineIndex].GetOrCreateFloat4Data(Key);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetOrCreateFloatData"/>
|
||||
public SplineData<int> GetOrCreateIntData()
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Int)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(int)}");
|
||||
return Container.Splines[SplineIndex].GetOrCreateIntData(Key);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetOrCreateFloatData"/>
|
||||
public SplineData<Object> GetOrCreateObjectData()
|
||||
{
|
||||
if(Type != EmbeddedSplineDataType.Object)
|
||||
throw new InvalidCastException($"EmbeddedSplineDataType {Type} does not match requested SplineData collection: {typeof(Object)}");
|
||||
return Container.Splines[SplineIndex].GetOrCreateObjectData(Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ad8fbd56afb4ac7b2e5d9c34a0affb0
|
||||
timeCreated: 1671224952
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a type of data stored by a <see cref="SplineData{T}"/> collection embedded in <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
public enum EmbeddedSplineDataType
|
||||
{
|
||||
/// <summary>
|
||||
/// Integer data type.
|
||||
/// </summary>
|
||||
/// <seealso cref="Spline.GetOrCreateIntData"/>
|
||||
Int = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Float data type.
|
||||
/// </summary>
|
||||
/// <seealso cref="Spline.GetOrCreateFloatData"/>
|
||||
Float = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Float4 data type.
|
||||
/// </summary>
|
||||
/// <seealso cref="Spline.GetOrCreateFloat4Data"/>
|
||||
Float4 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// UnityEngine.Object data type.
|
||||
/// </summary>
|
||||
/// <seealso cref="Spline.GetOrCreateObjectData"/>
|
||||
Object = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 627a593abae54148b748c319c242e79e
|
||||
timeCreated: 1671132694
|
||||
@@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Implement this class to create a customized shape that can be extruded along a <see cref="Spline"/> using the
|
||||
/// <see cref="SplineMesh"/> class.
|
||||
///
|
||||
/// Some default shape implementations are available in the <see cref="ExtrusionShapes"/> namespace.
|
||||
///
|
||||
/// <see cref="SplineMesh"/> generates extruded mesh geometry in the following manner (pseudo-code):</para>
|
||||
/// <code>
|
||||
/// extrudeShape.Setup(spline, numberOfSegments);
|
||||
/// for(int i = 0; i < numberOfSegments; ++i)
|
||||
/// {
|
||||
/// float t = i / (numberOfSegments - 1);
|
||||
/// extrudeShape.SetSegment(i, t, splinePositionAtT, splineTangentAtT, splineUpAtT);
|
||||
/// for(int n = 0; n < extrudeShape.SideCount; ++n)
|
||||
/// vertices.Add(extrudeShape.GetPosition(n / (extrudeShape.SideCount - 1), n));
|
||||
/// }
|
||||
/// </code>
|
||||
/// <para>This example IExtrudeShape implementation creates a tube.</para>
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// ```lang-csharp
|
||||
/// <![CDATA[
|
||||
/// // While not strictly necessary, marking the class as Serializable means that
|
||||
/// // this can be edited in the Inspector.
|
||||
/// [Serializable]
|
||||
/// public class Circle : IExtrudeShape
|
||||
/// {
|
||||
/// [SerializeField, Min(2)]
|
||||
/// int m_Sides = 8;
|
||||
///
|
||||
/// float m_Rads;
|
||||
///
|
||||
/// // We only need to calculate the radians step once, so do it in the Setup method.
|
||||
/// public void Setup(ISpline path, int segmentCount)
|
||||
/// {
|
||||
/// m_Rads = math.radians(360f / SideCount);
|
||||
/// }
|
||||
///
|
||||
/// public float2 GetPosition(float t, int index)
|
||||
/// {
|
||||
/// return new float2(math.cos(index * m_Rads), math.sin(index * m_Rads));
|
||||
/// }
|
||||
///
|
||||
/// public int SideCount
|
||||
/// {
|
||||
/// get => m_Sides;
|
||||
/// set => m_Sides = value;
|
||||
/// }
|
||||
/// }
|
||||
/// ]]>
|
||||
/// ```
|
||||
/// </example>
|
||||
public interface IExtrudeShape
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this function to access information about the <see cref="ISpline"/> path being extruded and
|
||||
/// number of segments. <see cref="SplineMesh"/> invokes this method once prior to extruding the mesh.
|
||||
/// </summary>
|
||||
/// <param name="path">The <see cref="ISpline"/> that this template is being extruded along.</param>
|
||||
/// <param name="segmentCount">The total number of segments to be created on the extruded mesh. This is
|
||||
/// equivalent to the number of vertex "rings" that make up the mesh positions.</param>
|
||||
public void Setup(ISpline path, int segmentCount) {}
|
||||
|
||||
/// <summary>
|
||||
/// Implement this function to access information about the spline path being extruded for each segment.
|
||||
/// <see cref="SplineMesh"/> invokes this method once before each ring of vertices is calculated.
|
||||
/// </summary>
|
||||
/// <param name="index">The segment index for the current vertex ring.</param>
|
||||
/// <param name="t">The normalized interpolation ratio corresponding to the segment index. Equivalent to index divided by segmentCount - 1.</param>
|
||||
/// <param name="position">The position on the <see cref="Spline"/> path being extruded along at <paramref name="t"/>.</param>
|
||||
/// <param name="tangent">The tangent on the <see cref="Spline"/> path being extruded along at <paramref name="t"/>.</param>
|
||||
/// <param name="up">The up vector on the <see cref="Spline"/> path being extruded along at <paramref name="t"/>.</param>
|
||||
public void SetSegment(int index, float t, float3 position, float3 tangent, float3 up) {}
|
||||
|
||||
/// <summary>
|
||||
/// How many vertices make up a single ring around the mesh.
|
||||
/// </summary>
|
||||
/// <value>How many vertices make up a revolution for each segment of the extruded mesh.</value>
|
||||
public int SideCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is responsible for returning a 2D position of the template shape for each vertex of a single
|
||||
/// ring around the extruded mesh.
|
||||
/// Note that both interpolation <paramref name="t"/> and <paramref name="index"/> are provided as a convenience.
|
||||
/// </summary>
|
||||
/// <param name="t">The normalized interpolation [0...1] for a vertex around an extruded ring.</param>
|
||||
/// <param name="index">The index of the vertex in the extruded ring.</param>
|
||||
/// <returns>A 2D position interpolated along a template shape to be extruded. This value will be converted to
|
||||
/// a 3D point and rotated to align with the spline at the current segment index.</returns>
|
||||
public float2 GetPosition(float t, int index);
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnityEngine.Splines.ExtrusionShapes
|
||||
{
|
||||
// This is intentionally not public. It is used only by the SplineExtrudeEditor class to provide
|
||||
// an enum popup.
|
||||
// when updating this, make sure to also update ShapeTypeUtility.{GetShapeType, CreateShape}
|
||||
enum ShapeType
|
||||
{
|
||||
Circle,
|
||||
Square,
|
||||
Road,
|
||||
[InspectorName("Spline Profile")]
|
||||
Spline
|
||||
}
|
||||
|
||||
static class ShapeTypeUtility
|
||||
{
|
||||
public static ShapeType GetShapeType(object obj)
|
||||
{
|
||||
return obj switch
|
||||
{
|
||||
Circle => ShapeType.Circle,
|
||||
Square => ShapeType.Square,
|
||||
Road => ShapeType.Road,
|
||||
SplineShape => ShapeType.Spline,
|
||||
_ => throw new ArgumentException($"{nameof(obj)} is not a recognized shape", nameof(obj))
|
||||
};
|
||||
}
|
||||
|
||||
public static IExtrudeShape CreateShape(ShapeType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ShapeType.Square => new Square(),
|
||||
ShapeType.Road => new Road(),
|
||||
ShapeType.Spline => new SplineShape(),
|
||||
ShapeType.Circle => new Circle(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a circle shape to be extruded along a spline.
|
||||
/// <![CDATA[
|
||||
/// ___
|
||||
/// / \
|
||||
/// | |
|
||||
/// \ /
|
||||
/// ---
|
||||
/// ]]>
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineMesh"/>
|
||||
/// <seealso cref="IExtrudeShape"/>
|
||||
/// <seealso cref="SplineExtrude"/>
|
||||
[Serializable]
|
||||
public sealed class Circle : IExtrudeShape
|
||||
{
|
||||
[SerializeField, Min(2)]
|
||||
int m_Sides = 8;
|
||||
|
||||
float m_Rads;
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.Setup"/>
|
||||
public void Setup(ISpline path, int segmentCount) => m_Rads = math.radians(360f / SideCount);
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.GetPosition"/>
|
||||
public float2 GetPosition(float t, int index)
|
||||
{
|
||||
return new float2(math.cos(index * m_Rads), math.sin(index * m_Rads));
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.SideCount"/>
|
||||
public int SideCount
|
||||
{
|
||||
get => m_Sides;
|
||||
set => m_Sides = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a square shape to be extruded along a spline.
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineMesh"/>
|
||||
/// <seealso cref="IExtrudeShape"/>
|
||||
/// <seealso cref="SplineExtrude"/>
|
||||
[Serializable]
|
||||
public sealed class Square : IExtrudeShape
|
||||
{
|
||||
/// <inheritdoc cref="IExtrudeShape.SideCount"/>
|
||||
/// <value>Square is fixed to 4 sides.</value>
|
||||
public int SideCount => 4;
|
||||
|
||||
static readonly float2[] k_Sides = new[]
|
||||
{
|
||||
new float2(-.5f, -.5f),
|
||||
new float2(.5f, -.5f),
|
||||
new float2(.5f, .5f),
|
||||
new float2(-.5f, .5f),
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.GetPosition"/>
|
||||
public float2 GetPosition(float t, int index) => k_Sides[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple plane with skirts at the edges to better blend with uneven terrain.
|
||||
/// <![CDATA[
|
||||
/// // Looks like this
|
||||
/// __________
|
||||
/// / \
|
||||
/// ]]>
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineMesh"/>
|
||||
/// <seealso cref="IExtrudeShape"/>
|
||||
/// <seealso cref="SplineExtrude"/>
|
||||
[Serializable]
|
||||
public sealed class Road : IExtrudeShape
|
||||
{
|
||||
/// <inheritdoc cref="IExtrudeShape.SideCount"/>
|
||||
/// <value>A road is fixed to 3 sides.</value>
|
||||
public int SideCount => 3;
|
||||
|
||||
static readonly float2[] k_Sides = new[]
|
||||
{
|
||||
new float2(-.6f, -.1f),
|
||||
new float2(-.5f, 0f),
|
||||
new float2( .5f, 0f),
|
||||
new float2( .6f, -.1f)
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.GetPosition"/>
|
||||
public float2 GetPosition(float t, int index) => k_Sides[3-index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a shape using a <see cref="Spline"/> as the template path. The
|
||||
/// referenced Spline is sampled at <see cref="IExtrudeShape.SideCount"/>
|
||||
/// points along the path to form the vertex rings at each segment.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SplineShape : IExtrudeShape
|
||||
{
|
||||
[SerializeField]
|
||||
SplineContainer m_Template;
|
||||
|
||||
[SerializeField, SplineIndex(nameof(m_Template))]
|
||||
int m_SplineIndex;
|
||||
|
||||
[SerializeField, Min(2)]
|
||||
int m_SideCount = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the axes used to project the input spline template to 2D coordinates.
|
||||
/// </summary>
|
||||
public enum Axis
|
||||
{
|
||||
/// <summary>
|
||||
/// Project from the horizontal (X) axis. Uses the {Y, Z} components of
|
||||
/// the position to form the 2D template coordinates.
|
||||
/// </summary>
|
||||
X,
|
||||
/// <summary>
|
||||
/// Project from the vertical (Y) axis. Uses the {X, Z} components of
|
||||
/// the position to form the 2D template coordinates.
|
||||
/// </summary>
|
||||
Y,
|
||||
/// <summary>
|
||||
/// Project from the forward (Z) axis. Uses the {X, Y} components of
|
||||
/// the position to form the 2D template coordinates.
|
||||
/// </summary>
|
||||
Z
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the axes used to project the input spline template to 2D coordinates.
|
||||
/// </summary>
|
||||
[SerializeField, Tooltip("The axis of the template spline to be used when winding the vertices along the " +
|
||||
"extruded mesh.")]
|
||||
public Axis m_Axis = Axis.Y;
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.SideCount"/>
|
||||
public int SideCount
|
||||
{
|
||||
get => m_SideCount;
|
||||
set => m_SideCount = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SplineContainer"/> which contains the
|
||||
/// <see cref="Spline"/> to use as the shape template.
|
||||
/// </summary>
|
||||
public SplineContainer SplineContainer
|
||||
{
|
||||
get => m_Template;
|
||||
set => m_Template = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index of the <see cref="Spline"/> in the <see cref="SplineContainer"/>
|
||||
/// to use as the shape template. This value must be greater than or
|
||||
/// equal to 0. If the index is out of bounds of the container spline
|
||||
/// count, the modulo of SplineIndex and Container.Splines.Count is used
|
||||
/// as the index.
|
||||
/// </summary>
|
||||
public int SplineIndex
|
||||
{
|
||||
get => m_SplineIndex;
|
||||
set => m_SplineIndex = math.max(0, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="Spline"/> referenced by the
|
||||
/// <see cref="SplineContainer"/> and <see cref="SplineIndex"/>.
|
||||
/// </summary>
|
||||
public Spline Spline => m_Template != null
|
||||
? m_Template[m_SplineIndex % m_Template.Splines.Count]
|
||||
: null;
|
||||
|
||||
/// <inheritdoc cref="IExtrudeShape.GetPosition"/>
|
||||
public float2 GetPosition(float t, int index)
|
||||
{
|
||||
if (Spline == null)
|
||||
return 0f;
|
||||
|
||||
if (t == 1)
|
||||
t = 0.9999f;
|
||||
else if (t == 0)
|
||||
t = 0.0001f;
|
||||
|
||||
return m_Axis switch
|
||||
{
|
||||
Axis.X => Spline.EvaluatePosition(1 - t).zy,
|
||||
Axis.Y => Spline.EvaluatePosition(1 - t).xz,
|
||||
Axis.Z => Spline.EvaluatePosition(1 - t).xy,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdae7080c9b7421bbca5d79fd103c8dd
|
||||
timeCreated: 1708547297
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
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 distribution of sampling points.
|
||||
/// </summary>
|
||||
/// <seealso cref="CurveUtility.CalculateCurveLengths"/>
|
||||
[Serializable]
|
||||
public struct DistanceToInterpolation
|
||||
{
|
||||
/// <summary>
|
||||
/// Distance in Unity units.
|
||||
/// </summary>
|
||||
public float Distance;
|
||||
|
||||
/// <summary>
|
||||
/// A normalized interpolation ratio ('t').
|
||||
/// </summary>
|
||||
public float T;
|
||||
|
||||
internal static readonly DistanceToInterpolation Invalid = new () { Distance = -1f, T = -1f };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines a collection of knot indices that should be considered disconnected from the following
|
||||
/// knot indices when creating a <see cref="BezierCurve"/>.
|
||||
/// </summary>
|
||||
public interface IHasEmptyCurves
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of knot indices that should be considered degenerate curves for the purpose of creating a
|
||||
/// non-interpolated gap between curves.
|
||||
/// </summary>
|
||||
public IReadOnlyList<int> EmptyCurves { get; }
|
||||
}
|
||||
|
||||
/// <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 up vector for a t ratio on the curve. Contrary to <see cref="SplineUtility.EvaluateUpVector"/>,
|
||||
/// this method uses cached values when possible for better performance when accessing
|
||||
/// these values regularly.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the curve for which the length needs to be retrieved.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
|
||||
/// <returns>
|
||||
/// Returns the up vector at the t ratio of the curve of index 'index'.
|
||||
/// </returns>
|
||||
public float3 GetCurveUpVector(int index, float t);
|
||||
|
||||
/// <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,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that represents ISplineContainer on a MonoBehaviour to enable Spline tools in the Editor.
|
||||
/// </summary>
|
||||
public interface ISplineContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of splines contained in this MonoBehaviour.
|
||||
/// </summary>
|
||||
IReadOnlyList<Spline> Splines { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A collection of KnotLinks to maintain valid links between knots.
|
||||
/// </summary>
|
||||
KnotLinkCollection KnotLinkCollection { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1e8bfa6c48dec14793c3b140f549f44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement ISplineProvider on a MonoBehaviour to enable Spline tools in the Editor.
|
||||
/// </summary>
|
||||
[Obsolete("Use " + nameof(ISplineContainer) + " instead.")]
|
||||
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,94 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// InterpolatorUtility provides easy access to all the different IInterpolator implementations.
|
||||
/// </summary>
|
||||
public static class InterpolatorUtility
|
||||
{
|
||||
static readonly IInterpolator<float> s_LerpFloat = new Interpolators.LerpFloat();
|
||||
static readonly IInterpolator<float2> s_LerpFloat2 = new Interpolators.LerpFloat2();
|
||||
static readonly IInterpolator<float3> s_LerpFloat3 = new Interpolators.LerpFloat3();
|
||||
static readonly IInterpolator<float4> s_LerpFloat4 = new Interpolators.LerpFloat4();
|
||||
|
||||
static readonly IInterpolator<float2> s_SlerpFloat2 = new Interpolators.SlerpFloat2();
|
||||
static readonly IInterpolator<float3> s_SlerpFloat3 = new Interpolators.SlerpFloat3();
|
||||
|
||||
static readonly IInterpolator<quaternion> s_LerpQuaternion = new Interpolators.LerpQuaternion();
|
||||
|
||||
static readonly IInterpolator<Color> s_LerpColor = new Interpolators.LerpColor();
|
||||
|
||||
static readonly IInterpolator<float> s_SmoothStepFloat = new Interpolators.SmoothStepFloat();
|
||||
static readonly IInterpolator<float2> s_SmoothStepFloat2 = new Interpolators.SmoothStepFloat2();
|
||||
static readonly IInterpolator<float3> s_SmoothStepFloat3 = new Interpolators.SmoothStepFloat3();
|
||||
static readonly IInterpolator<float4> s_SmoothStepFloat4 = new Interpolators.SmoothStepFloat4();
|
||||
|
||||
static readonly IInterpolator<quaternion> s_SlerpQuaternion = new Interpolators.SlerpQuaternion();
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float> LerpFloat => s_LerpFloat;
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float2> LerpFloat2 => s_LerpFloat2;
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float3> LerpFloat3 => s_LerpFloat3;
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float4> LerpFloat4 => s_LerpFloat4;
|
||||
|
||||
/// <summary>
|
||||
/// Spherically interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float2> SlerpFloat2 => s_SlerpFloat2;
|
||||
|
||||
/// <summary>
|
||||
/// Spherically interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<float3> SlerpFloat3 => s_SlerpFloat3;
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<quaternion> LerpQuaternion => s_LerpQuaternion;
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values a and b by ratio t.
|
||||
/// </summary>
|
||||
public static IInterpolator<Color> LerpColor => s_LerpColor;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
|
||||
/// </summary>
|
||||
public static IInterpolator<float> SmoothStepFloat => s_SmoothStepFloat;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
|
||||
/// </summary>
|
||||
public static IInterpolator<float2> SmoothStepFloat2 => s_SmoothStepFloat2;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
|
||||
/// </summary>
|
||||
public static IInterpolator<float3> SmoothStepFloat3 => s_SmoothStepFloat3;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate between two values a and b by ratio t with smoothing at the start and end.
|
||||
/// </summary>
|
||||
public static IInterpolator<float4> SmoothStepFloat4 => s_SmoothStepFloat4;
|
||||
|
||||
/// <summary>
|
||||
/// Spherically interpolates between quaternions a and b by ratio t. The parameter t is clamped b the range [0, 1].
|
||||
/// </summary>
|
||||
public static IInterpolator<quaternion> SlerpQuaternion => s_SlerpQuaternion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fc75f2dd6f043c43ae2a5708af31111
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of KnotLinks to track how spline knots are linked and the utilities to
|
||||
/// update these links when splines are modified.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class KnotLinkCollection
|
||||
{
|
||||
[Serializable]
|
||||
sealed class KnotLink : IReadOnlyList<SplineKnotIndex>
|
||||
{
|
||||
public SplineKnotIndex[] Knots;
|
||||
|
||||
public IEnumerator<SplineKnotIndex> GetEnumerator() => ((IEnumerable<SplineKnotIndex>)Knots).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => Knots.GetEnumerator();
|
||||
|
||||
public int Count => Knots.Length;
|
||||
|
||||
public SplineKnotIndex this[int index] => Knots[index];
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
KnotLink[] m_KnotsLink = new KnotLink[0];
|
||||
|
||||
/// <summary>
|
||||
/// How many KnotLinks the collection contains.
|
||||
/// </summary>
|
||||
public int Count => m_KnotsLink.Length;
|
||||
|
||||
KnotLink GetKnotLinksInternal(SplineKnotIndex index)
|
||||
{
|
||||
foreach (var knotLink in m_KnotsLink)
|
||||
if (Array.IndexOf(knotLink.Knots, index) >= 0)
|
||||
return knotLink;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the knots linked to a specific knot.
|
||||
/// </summary>
|
||||
/// <param name="knotIndex">The <see cref="SplineKnotIndex"/> of the knot.</param>
|
||||
/// <param name="linkedKnots">The output list of the knots linked to the specified knot if they exist or null if they do not exist.</param>
|
||||
/// <returns>Returns true if linked knots are found, false otherwise.</returns>
|
||||
public bool TryGetKnotLinks(SplineKnotIndex knotIndex, out IReadOnlyList<SplineKnotIndex> linkedKnots)
|
||||
{
|
||||
linkedKnots = GetKnotLinksInternal(knotIndex);
|
||||
return linkedKnots != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the knots linked to a specific knot.
|
||||
/// </summary>
|
||||
/// <param name="knotIndex">The <see cref="SplineKnotIndex"/> of the knot.</param>
|
||||
/// <returns>Returns a list of knots linked to the specified knot. The specified knot is also in the list. </returns>
|
||||
public IReadOnlyList<SplineKnotIndex> GetKnotLinks(SplineKnotIndex knotIndex)
|
||||
{
|
||||
if(TryGetKnotLinks(knotIndex, out var linkedKnots))
|
||||
return linkedKnots;
|
||||
|
||||
return new KnotLink { Knots = new[] { knotIndex } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all the links in the collection.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_KnotsLink = new KnotLink[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Links two knots positions to each other. If you link knots that are already linked to other knots, then all of the knots link to each other.
|
||||
/// </summary>
|
||||
/// <param name="knotA">The first knot to link.</param>
|
||||
/// <param name="knotB">The first knot to link.</param>
|
||||
public void Link(SplineKnotIndex knotA, SplineKnotIndex knotB)
|
||||
{
|
||||
if (knotA.Equals(knotB))
|
||||
return;
|
||||
|
||||
var originalLink = GetKnotLinksInternal(knotA);
|
||||
var targetLink = GetKnotLinksInternal(knotB);
|
||||
|
||||
// If the knot was already linked to other knots, merge both shared knot
|
||||
if (originalLink != null && targetLink != null)
|
||||
{
|
||||
if (originalLink.Equals(targetLink))
|
||||
return;
|
||||
|
||||
var indices = new SplineKnotIndex[originalLink.Knots.Length + targetLink.Knots.Length];
|
||||
Array.Copy(originalLink.Knots, indices, originalLink.Knots.Length);
|
||||
Array.Copy(targetLink.Knots, 0, indices, originalLink.Knots.Length, targetLink.Knots.Length);
|
||||
originalLink.Knots = indices;
|
||||
ArrayUtility.Remove(ref m_KnotsLink, targetLink);
|
||||
}
|
||||
else if (targetLink != null)
|
||||
{
|
||||
var indices = targetLink.Knots;
|
||||
ArrayUtility.Add(ref indices, knotA);
|
||||
targetLink.Knots = indices;
|
||||
}
|
||||
else if (originalLink != null)
|
||||
{
|
||||
var indices = originalLink.Knots;
|
||||
ArrayUtility.Add(ref indices, knotB);
|
||||
originalLink.Knots = indices;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newShared = new KnotLink { Knots = new[] {knotA, knotB}};
|
||||
ArrayUtility.Add(ref m_KnotsLink, newShared);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlinks a knot from the knots it is linked to. This method unlinks the knot specified, but does not unlink the other knots from each other.
|
||||
/// </summary>
|
||||
/// <param name="knot">The knot to unlink.</param>
|
||||
public void Unlink(SplineKnotIndex knot)
|
||||
{
|
||||
var shared = GetKnotLinksInternal(knot);
|
||||
if (shared == null)
|
||||
return;
|
||||
|
||||
var indices = shared.Knots;
|
||||
ArrayUtility.Remove(ref indices, knot);
|
||||
shared.Knots = indices;
|
||||
|
||||
// Remove shared knot if empty or alone
|
||||
if (shared.Knots.Length < 2)
|
||||
ArrayUtility.Remove(ref m_KnotsLink, shared);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection after a spline is removed.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the removed spline.</param>
|
||||
public void SplineRemoved(int splineIndex)
|
||||
{
|
||||
List<int> indicesToRemove = new List<int>(1);
|
||||
for (var index = m_KnotsLink.Length - 1; index >= 0; --index)
|
||||
{
|
||||
var sharedKnot = m_KnotsLink[index];
|
||||
|
||||
indicesToRemove.Clear();
|
||||
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
|
||||
if (sharedKnot.Knots[i].Spline == splineIndex)
|
||||
indicesToRemove.Add(i);
|
||||
|
||||
// Remove shared knot if it will become empty
|
||||
if (sharedKnot.Knots.Length - indicesToRemove.Count < 2)
|
||||
ArrayUtility.RemoveAt(ref m_KnotsLink, index);
|
||||
else
|
||||
{
|
||||
var indices = sharedKnot.Knots;
|
||||
ArrayUtility.SortedRemoveAt(ref indices, indicesToRemove);
|
||||
sharedKnot.Knots = indices;
|
||||
}
|
||||
|
||||
// Decrement by one every knot of a spline higher than this one
|
||||
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
|
||||
{
|
||||
var knotIndex = sharedKnot.Knots[i];
|
||||
if (knotIndex.Spline > splineIndex)
|
||||
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline - 1, knotIndex.Knot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a spline index changes.
|
||||
/// </summary>
|
||||
/// <param name="previousIndex">The previous index of that spline in the SplineContainer.</param>
|
||||
/// <param name="newIndex">The new index of that spline in the SplineContainer.</param>
|
||||
public void SplineIndexChanged(int previousIndex, int newIndex)
|
||||
{
|
||||
for (var index = m_KnotsLink.Length - 1; index >= 0; --index)
|
||||
{
|
||||
var sharedKnot = m_KnotsLink[index];
|
||||
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
|
||||
{
|
||||
var knotIndex = sharedKnot.Knots[i];
|
||||
if (knotIndex.Spline == previousIndex)
|
||||
sharedKnot.Knots[i] = new SplineKnotIndex(newIndex, knotIndex.Knot);
|
||||
else if (knotIndex.Spline > previousIndex && knotIndex.Spline <= newIndex)
|
||||
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline - 1, knotIndex.Knot);
|
||||
else if (knotIndex.Spline < previousIndex && knotIndex.Spline >= newIndex)
|
||||
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline + 1, knotIndex.Knot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot index changes.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline.</param>
|
||||
/// <param name="previousKnotIndex">The previous index of the knot in the spline.</param>
|
||||
/// <param name="newKnotIndex">The new index of the knot in the spline.</param>
|
||||
public void KnotIndexChanged(int splineIndex, int previousKnotIndex, int newKnotIndex) => KnotIndexChanged(new SplineKnotIndex(splineIndex, previousKnotIndex),new SplineKnotIndex(splineIndex, newKnotIndex));
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot index changes.
|
||||
/// </summary>
|
||||
/// <param name="previousIndex">The previous SplineKnotIndex of the knot.</param>
|
||||
/// <param name="newIndex">The new SplineKnotIndex of the knot.</param>
|
||||
public void KnotIndexChanged(SplineKnotIndex previousIndex, SplineKnotIndex newIndex)
|
||||
{
|
||||
if (previousIndex.Knot > newIndex.Knot)
|
||||
previousIndex.Knot += 1;
|
||||
else
|
||||
newIndex.Knot += 1;
|
||||
|
||||
//Insert the knot to shift indices
|
||||
KnotInserted(newIndex);
|
||||
//Link the 2 knots together temporary to link the new knot to the same knots as previous knot
|
||||
Link(previousIndex, newIndex);
|
||||
//Now that links has been updated, remove the previous knot
|
||||
KnotRemoved(previousIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot has been removed.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline.</param>
|
||||
/// <param name="knotIndex">The index of the removed knot in the spline.</param>
|
||||
public void KnotRemoved(int splineIndex, int knotIndex) => KnotRemoved(new SplineKnotIndex(splineIndex, knotIndex));
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot has been removed.
|
||||
/// </summary>
|
||||
/// <param name="index">The SplineKnotIndex of the removed knot.</param>
|
||||
public void KnotRemoved(SplineKnotIndex index)
|
||||
{
|
||||
Unlink(index);
|
||||
ShiftKnotIndices(index, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot has been inserted.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline.</param>
|
||||
/// <param name="knotIndex">The index of the inserted knot in the spline.</param>
|
||||
public void KnotInserted(int splineIndex, int knotIndex) => KnotInserted(new SplineKnotIndex(splineIndex, knotIndex));
|
||||
|
||||
/// <summary>
|
||||
/// Updates the KnotLinkCollection indices after a knot has been inserted.
|
||||
/// </summary>
|
||||
/// <param name="index">The SplineKnotIndex of the inserted knot.</param>
|
||||
public void KnotInserted(SplineKnotIndex index) => ShiftKnotIndices(index, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the indices of the KnotLinkCollection to ensure they are valid. This is mainly used when splines or
|
||||
/// knots are inserted or removed from a <see cref="SplineContainer"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The SplineKnotIndex of the knot.</param>
|
||||
/// <param name="offset">The offset to apply on other knots.</param>
|
||||
public void ShiftKnotIndices(SplineKnotIndex index, int offset)
|
||||
{
|
||||
foreach (var sharedKnot in m_KnotsLink)
|
||||
{
|
||||
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
|
||||
{
|
||||
var current = sharedKnot.Knots[i];
|
||||
// Increment by one every knot of the same spline above or equal to the inserted knot
|
||||
if (current.Spline == index.Spline
|
||||
&& current.Knot >= index.Knot)
|
||||
sharedKnot.Knots[i] = new SplineKnotIndex(current.Spline, current.Knot + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9973ae8b03a8ccd4aafd1477225380eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
static class MathUtility
|
||||
{
|
||||
// Transforms a direction by this matrix - float4x4 equivalent of Matrix4x4.MultiplyVector.
|
||||
public static float3 MultiplyVector(float4x4 matrix, float3 vector)
|
||||
{
|
||||
float3 res;
|
||||
res.x = matrix.c0.x * vector.x + matrix.c1.x * vector.y + matrix.c2.x * vector.z;
|
||||
res.y = matrix.c0.y * vector.x + matrix.c1.y * vector.y + matrix.c2.y * vector.z;
|
||||
res.z = matrix.c0.z * vector.x + matrix.c1.z * vector.y + matrix.c2.z * vector.z;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns true if the corresponding elements in each of the matrices are equal
|
||||
// Unity.Mathematics does not have a math.all(bool4x4).
|
||||
public static bool All(float4x4 matrixA, float4x4 matrixB)
|
||||
{
|
||||
var equal4x4 = (matrixA == matrixB);
|
||||
|
||||
return math.all(equal4x4.c0) &&
|
||||
math.all(equal4x4.c1) &&
|
||||
math.all(equal4x4.c2) &&
|
||||
math.all(equal4x4.c3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0962c806cc7b430cba39e8f3a82affc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only 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
|
||||
{
|
||||
[ReadOnly]
|
||||
NativeArray<BezierKnot> m_Knots;
|
||||
|
||||
[ReadOnly]
|
||||
NativeArray<BezierCurve> m_Curves;
|
||||
|
||||
// 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
|
||||
[ReadOnly]
|
||||
NativeArray<DistanceToInterpolation> m_SegmentLengthsLookupTable;
|
||||
|
||||
// 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
|
||||
[ReadOnly]
|
||||
NativeArray<float3> m_UpVectorsLookupTable;
|
||||
|
||||
bool m_Closed;
|
||||
float m_Length;
|
||||
const int k_SegmentResolution = 30;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of <see cref="BezierKnot"/> that form this Spline.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Returns a reference to the knots array.
|
||||
/// </value>
|
||||
public NativeArray<BezierKnot> Knots => m_Knots;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of <see cref="BezierCurve"/> that form this Spline.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Returns a reference to the curves array.
|
||||
/// </value>
|
||||
public NativeArray<BezierCurve> Curves => m_Curves;
|
||||
|
||||
/// <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();
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
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, float4x4.identity, false, 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="cacheUpVectors"> Whether to cache the values of the Up vectors along the entire spline to reduce
|
||||
/// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might
|
||||
/// be less performant because all the Up vectors along the spline are computed. Consider how often you need to
|
||||
/// access the values of Up vectors along the spline before you cache them. </param>
|
||||
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
|
||||
public NativeSpline(ISpline spline, bool cacheUpVectors, Allocator allocator = Allocator.Temp)
|
||||
: this(spline, float4x4.identity, cacheUpVectors, 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 is IHasEmptyCurves disconnect ? disconnect.EmptyCurves : null,
|
||||
spline.Closed,
|
||||
transform,
|
||||
false,
|
||||
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="cacheUpVectors"> Whether to cache the values of the Up vectors along the entire spline to reduce
|
||||
/// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might
|
||||
/// be less performant because all the Up vectors along the spline are computed. Consider how often you need to
|
||||
/// access the values of Up vectors along the spline before you cache them. </param>
|
||||
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
|
||||
public NativeSpline(ISpline spline, float4x4 transform, bool cacheUpVectors, Allocator allocator = Allocator.Temp)
|
||||
: this(spline,
|
||||
spline is IHasEmptyCurves disconnect ? disconnect.EmptyCurves : null,
|
||||
spline.Closed,
|
||||
transform,
|
||||
cacheUpVectors,
|
||||
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) : this(knots, null, closed, transform, false, 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="cacheUpVectors"> Whether to cache the values of the Up vectors along the entire spline to reduce
|
||||
/// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might
|
||||
/// be less performant because all the Up vectors along the spline are computed. Consider how often you need to
|
||||
/// access the values of Up vectors along the spline before you cache them. </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,
|
||||
bool cacheUpVectors,
|
||||
Allocator allocator = Allocator.Temp) : this(knots, null, closed, transform, cacheUpVectors, 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="splits">A collection of knot indices that should be considered degenerate curves for the
|
||||
/// purpose of creating a non-interpolated gap between curves.</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, IReadOnlyList<int> splits, bool closed, float4x4 transform, Allocator allocator = Allocator.Temp)
|
||||
: this(knots, splits, closed, transform, false, 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="splits">A collection of knot indices that should be considered degenerate curves for the
|
||||
/// purpose of creating a non-interpolated gap between curves.</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="cacheUpVectors"> Whether to cache the values of the Up vectors along the entire spline to reduce
|
||||
/// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might
|
||||
/// be less performant because all the Up vectors along the spline are computed. Consider how often you need to
|
||||
/// access the values of Up vectors along the spline before you cache them. </param>
|
||||
/// <param name="allocator">The memory allocation method to use when reserving space for native arrays.</param>
|
||||
public NativeSpline(IReadOnlyList<BezierKnot> knots, IReadOnlyList<int> splits, bool closed, float4x4 transform, bool cacheUpVectors, Allocator allocator = Allocator.Temp)
|
||||
{
|
||||
int knotCount = knots.Count;
|
||||
m_Knots = new NativeArray<BezierKnot>(knotCount, allocator);
|
||||
m_Curves = new NativeArray<BezierCurve>(knotCount, allocator);
|
||||
m_SegmentLengthsLookupTable = new NativeArray<DistanceToInterpolation>(knotCount * k_SegmentResolution, allocator);
|
||||
m_Closed = closed;
|
||||
m_Length = 0f;
|
||||
|
||||
//Costly to do this for temporary NativeSpline that does not require to access/compute up vectors
|
||||
m_UpVectorsLookupTable = new NativeArray<float3>(cacheUpVectors ? knotCount * k_SegmentResolution : 0, allocator);
|
||||
|
||||
// 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
|
||||
|
||||
var distanceToTimes = new NativeArray<DistanceToInterpolation>(k_SegmentResolution, Allocator.Temp);
|
||||
var upVectors = cacheUpVectors ? new NativeArray<float3>(k_SegmentResolution, Allocator.Temp) : default;
|
||||
|
||||
if (knotCount > 0)
|
||||
{
|
||||
BezierKnot cur = knots[0].Transform(transform);
|
||||
for (int i = 0; i < knotCount; ++i)
|
||||
{
|
||||
BezierKnot next = knots[(i + 1) % knotCount].Transform(transform);
|
||||
m_Knots[i] = cur;
|
||||
|
||||
if (splits != null && splits.Contains(i))
|
||||
{
|
||||
m_Curves[i] = new BezierCurve(new BezierKnot(cur.Position), new BezierKnot(cur.Position));
|
||||
var up = cacheUpVectors ? math.rotate(cur.Rotation, math.up()) : float3.zero;
|
||||
for (int n = 0; n < k_SegmentResolution; ++n)
|
||||
{
|
||||
//Cache Distance in case of a split is empty
|
||||
distanceToTimes[n] = new DistanceToInterpolation();
|
||||
//up Vectors in case of a split is the knot up vector
|
||||
if(cacheUpVectors)
|
||||
upVectors[n] = up;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Curves[i] = new BezierCurve(cur, next);
|
||||
CurveUtility.CalculateCurveLengths(m_Curves[i], distanceToTimes);
|
||||
|
||||
if (cacheUpVectors)
|
||||
{
|
||||
var curveStartUp = math.rotate(cur.Rotation, math.up());
|
||||
var curveEndUp = math.rotate(next.Rotation, math.up());
|
||||
CurveUtility.EvaluateUpVectors(m_Curves[i], curveStartUp, curveEndUp, upVectors);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Closed || i < knotCount - 1)
|
||||
m_Length += distanceToTimes[k_SegmentResolution - 1].Distance;
|
||||
|
||||
for (int index = 0; index < k_SegmentResolution; index++)
|
||||
{
|
||||
m_SegmentLengthsLookupTable[i * k_SegmentResolution + index] = distanceToTimes[index];
|
||||
|
||||
if(cacheUpVectors)
|
||||
m_UpVectorsLookupTable[i * k_SegmentResolution + index] = upVectors[index];
|
||||
}
|
||||
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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) => m_Curves[index];
|
||||
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return m_SegmentLengthsLookupTable[curveIndex * k_SegmentResolution + k_SegmentResolution - 1].Distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the up vector for a t ratio on the curve.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the curve for which the length needs to be retrieved.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing the ratio along the spline.</param>
|
||||
/// <returns>
|
||||
/// Returns the up vector at the t ratio of the curve of index 'index'.
|
||||
/// </returns>
|
||||
public float3 GetCurveUpVector(int index, float t)
|
||||
{
|
||||
// Value is not cached, compute the value directly on demand
|
||||
if (m_UpVectorsLookupTable.Length == 0)
|
||||
return this.CalculateUpVector(index, t);
|
||||
|
||||
var curveIndex = index * k_SegmentResolution;
|
||||
var offset = 1f / (float)(k_SegmentResolution - 1);
|
||||
var curveT = 0f;
|
||||
for (int i = 0; i < k_SegmentResolution; i++)
|
||||
{
|
||||
if (t <= curveT + offset)
|
||||
{
|
||||
var value = math.lerp(m_UpVectorsLookupTable[curveIndex + i],
|
||||
m_UpVectorsLookupTable[curveIndex + i + 1],
|
||||
(t - curveT) / offset);
|
||||
|
||||
return value;
|
||||
}
|
||||
curveT += offset;
|
||||
}
|
||||
|
||||
//Otherwise, no value has been found, return the one at the end of the segment
|
||||
return m_UpVectorsLookupTable[curveIndex + k_SegmentResolution - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release allocated resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
m_Knots.Dispose();
|
||||
m_Curves.Dispose();
|
||||
m_SegmentLengthsLookupTable.Dispose();
|
||||
m_UpVectorsLookupTable.Dispose();
|
||||
}
|
||||
|
||||
// Wrapper around NativeSlice<T> because the native type does not implement IReadOnlyList<T>.
|
||||
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
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to make an integer variable show in the Inspector as a popup menu with spline choices relative
|
||||
/// to a <see cref="ISplineContainer"/>.
|
||||
/// </summary>
|
||||
public class SplineIndexAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the field that references an <see cref="ISplineContainer"/>.
|
||||
/// </summary>
|
||||
public readonly string SplineContainerProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SplineIndexAttribute with the name of a variable that references an <see cref="ISplineContainer"/>.
|
||||
/// </summary>
|
||||
/// <param name="splineContainerProperty">The name of the field that references an <see cref="ISplineContainer"/>.
|
||||
/// It is recommended to pass this value using `nameof()` to avoid typos.</param>
|
||||
public SplineIndexAttribute(string splineContainerProperty)
|
||||
{
|
||||
SplineContainerProperty = splineContainerProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the fields on an <see cref="EmbeddedSplineData"/> type.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum EmbeddedSplineDataField
|
||||
{
|
||||
/// <summary>The <see cref="ISplineContainer"/> that holds the <see cref="SplineData{T}"/> collection.</summary>
|
||||
Container = 1 << 0,
|
||||
/// <summary>The index of the <see cref="Spline"/> in the referenced <see cref="SplineContainer"/>.</summary>
|
||||
SplineIndex = 1 << 1,
|
||||
/// <summary>A string value used to identify and access <see cref="SplineData{T}"/> stored on a <see cref="Spline"/>.</summary>
|
||||
Key = 1 << 2,
|
||||
/// <summary>The <see cref="EmbeddedSplineDataType"/> for the target <see cref="SplineData{T}"/>.</summary>
|
||||
Type = 1 << 3,
|
||||
/// <summary>All fields will be shown in the Inspector.</summary>
|
||||
All = 0xFF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute used to make an <see cref="EmbeddedSplineData"/> variable show in the Inspector with a filtered set
|
||||
/// of fields editable. Use this in situations where you want to specify <see cref="EmbeddedSplineData"/> parameters
|
||||
/// in code and not allow them to be modified in the Inspector.
|
||||
/// to a <see cref="ISplineContainer"/>.
|
||||
/// </summary>
|
||||
public class EmbeddedSplineDataFieldsAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The fields to show in the Inspector.
|
||||
/// </summary>
|
||||
public readonly EmbeddedSplineDataField Fields;
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="EmbeddedSplineDataFieldsAttribute"/> attribute.
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields to show in the Inspector. <see cref="EmbeddedSplineDataField"/>.</param>
|
||||
public EmbeddedSplineDataFieldsAttribute(EmbeddedSplineDataField fields)
|
||||
{
|
||||
Fields = fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 576bb5c67fbf94e8c83cda3e96a8d6aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
class RamerDouglasPeucker<T> where T : IList<float3>
|
||||
{
|
||||
T m_Points;
|
||||
bool[] m_Keep;
|
||||
float m_Epsilon;
|
||||
int m_KeepCount;
|
||||
|
||||
// not using C# Range type because we need to operate on lists and iterate without making copies of arrays
|
||||
struct Range
|
||||
{
|
||||
public int Start;
|
||||
public int Count;
|
||||
// inclusive end
|
||||
public int End => Start + Count - 1;
|
||||
|
||||
public Range(int start, int count)
|
||||
{
|
||||
Start = start;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[{Start}, {End}]";
|
||||
}
|
||||
|
||||
public RamerDouglasPeucker(T points)
|
||||
{
|
||||
m_Points = points;
|
||||
}
|
||||
|
||||
public void Reduce(List<float3> results, float epsilon)
|
||||
{
|
||||
if (results == null)
|
||||
throw new ArgumentNullException(nameof(results));
|
||||
|
||||
m_Epsilon = math.max(float.Epsilon, epsilon);
|
||||
m_KeepCount = m_Points.Count;
|
||||
m_Keep = new bool[m_KeepCount];
|
||||
for(int i = 0; i < m_KeepCount; i++) Keep(i);
|
||||
Reduce(new Range(0, m_KeepCount));
|
||||
|
||||
results.Clear();
|
||||
|
||||
if (results.Capacity < m_KeepCount)
|
||||
results.Capacity = m_KeepCount;
|
||||
|
||||
for(int i = 0; i < m_Keep.Length; ++i)
|
||||
if (m_Keep[i])
|
||||
results.Add(m_Points[i]);
|
||||
}
|
||||
|
||||
void Keep(int index) => m_Keep[index] = true;
|
||||
|
||||
void Discard(Range range)
|
||||
{
|
||||
m_KeepCount -= range.Count;
|
||||
for (int i = range.Start; i <= range.End; ++i)
|
||||
m_Keep[i] = false;
|
||||
}
|
||||
|
||||
void Reduce(Range range)
|
||||
{
|
||||
if (range.Count < 3)
|
||||
return;
|
||||
|
||||
var farthest = FindFarthest(range);
|
||||
|
||||
if (farthest.distance < m_Epsilon)
|
||||
{
|
||||
Discard(new Range(range.Start + 1, range.Count - 2));
|
||||
return;
|
||||
}
|
||||
|
||||
Reduce(new Range(range.Start, farthest.index - range.Start + 1));
|
||||
Reduce(new Range(farthest.index, (range.End - farthest.index) + 1));
|
||||
}
|
||||
|
||||
(int index, float distance) FindFarthest(Range range)
|
||||
{
|
||||
float distance = 0f;
|
||||
int index = -1;
|
||||
|
||||
for (int i = range.Start+1; i < range.End; ++i)
|
||||
{
|
||||
var d = SplineMath.DistancePointLine(m_Points[i], m_Points[range.Start], m_Points[range.End]);
|
||||
if (d > distance)
|
||||
{
|
||||
distance = d;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return (index, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d914a4e0d6342a3a05bab1dc0fffbc1
|
||||
timeCreated: 1654180958
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1737f1b4489bd468cbf919947c6836e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,774 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// A component to animate an object along a spline.
|
||||
/// </summary>
|
||||
[AddComponentMenu("Splines/Spline Animate")]
|
||||
[ExecuteInEditMode]
|
||||
public class SplineAnimate : SplineComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the different methods that can be used to animated an object along a 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 continuously without stopping. </summary>
|
||||
[InspectorName("Loop Continuous")]
|
||||
Loop,
|
||||
/// <summary> Traverse the spline continuously without stopping. If <see cref="SplineAnimate.Easing"/> is set to <see cref="SplineAnimate.EasingMode.EaseIn"/> or
|
||||
/// <see cref="SplineAnimate.EasingMode.EaseInOut"/> then easing is only applied to the first loop of the animation. Otherwise, no easing is applied with this loop mode.
|
||||
/// </summary>
|
||||
[InspectorName("Ease In Then Continuous")]
|
||||
LoopEaseInOnce,
|
||||
/// <summary> Traverse the spline and then reverse direction at the end of the spline. The animation plays repeatedly. </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. The animation speed is 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> The object's forward and up axes align to the spline's tangent and up vectors. </summary>
|
||||
[InspectorName("Spline Element")]
|
||||
SplineElement,
|
||||
/// <summary> The object's forward and up axes align to the spline tranform's z-axis and y-axis. </summary>
|
||||
[InspectorName("Spline Object")]
|
||||
SplineObject,
|
||||
/// <summary> The object's forward and up axes align to to the world's z-axis and y-axis. </summary>
|
||||
[InspectorName("World Space")]
|
||||
World
|
||||
}
|
||||
|
||||
[SerializeField, Tooltip("The target spline to follow.")]
|
||||
SplineContainer m_Target;
|
||||
|
||||
[SerializeField, Tooltip("Enable to have the animation start when the GameObject first loads.")]
|
||||
bool m_PlayOnAwake = true;
|
||||
|
||||
[SerializeField, Tooltip("The loop mode that the animation uses. Loop modes cause the animation to repeat after it finishes. The following loop modes are available:.\n" +
|
||||
"Once - Traverse the spline once and stop at the end.\n" +
|
||||
"Loop Continuous - Traverse the spline continuously without stopping.\n" +
|
||||
"Ease In Then Continuous - Traverse the spline repeatedly without stopping. If Ease In easing is enabled, apply easing to the first loop only.\n" +
|
||||
"Ping Pong - Traverse the spline continuously without stopping and reverse direction after an end of the spline is reached.\n")]
|
||||
LoopMode m_LoopMode = LoopMode.Loop;
|
||||
|
||||
[SerializeField, Tooltip("The method used to animate the GameObject along the spline.\n" +
|
||||
"Time - The spline is traversed in a given amount of seconds.\n" +
|
||||
"Speed - The spline is traversed at a given maximum speed.")]
|
||||
Method m_Method = Method.Time;
|
||||
|
||||
[SerializeField, Tooltip("The period of time that it takes for the GameObject to complete its animation along the spline.")]
|
||||
float m_Duration = 1f;
|
||||
|
||||
[SerializeField, Tooltip("The speed in meters/second that the GameObject animates along the spline at.")]
|
||||
float m_MaxSpeed = 10f;
|
||||
|
||||
[SerializeField, Tooltip("The easing mode used when the GameObject animates along the spline.\n" +
|
||||
"None - Apply no easing to the animation. The animation speed is 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 that the GameObject's up and forward axes align to.")]
|
||||
AlignmentMode m_AlignmentMode = AlignmentMode.SplineElement;
|
||||
|
||||
[SerializeField, Tooltip("Which axis of the GameObject is treated as the forward axis.")]
|
||||
AlignAxis m_ObjectForwardAxis = AlignAxis.ZAxis;
|
||||
|
||||
[SerializeField, Tooltip("Which axis of the GameObject is treated as the up axis.")]
|
||||
AlignAxis m_ObjectUpAxis = AlignAxis.YAxis;
|
||||
|
||||
[SerializeField, Tooltip("Normalized distance [0;1] offset along the spline at which the GameObject should be placed when the animation begins.")]
|
||||
float m_StartOffset;
|
||||
[NonSerialized]
|
||||
float m_StartOffsetT;
|
||||
|
||||
float m_SplineLength = -1;
|
||||
bool m_Playing;
|
||||
float m_NormalizedTime;
|
||||
float m_ElapsedTime;
|
||||
#if UNITY_EDITOR
|
||||
double m_LastEditorUpdateTime;
|
||||
#endif
|
||||
SplinePath<Spline> m_SplinePath;
|
||||
|
||||
/// <summary>The target container of the splines to follow.</summary>
|
||||
[Obsolete("Use Container instead.", false)]
|
||||
public SplineContainer splineContainer => Container;
|
||||
/// <summary>The target container of the splines to follow.</summary>
|
||||
public SplineContainer Container
|
||||
{
|
||||
get => m_Target;
|
||||
set
|
||||
{
|
||||
m_Target = value;
|
||||
|
||||
if (enabled && m_Target != null && m_Target.Splines != null)
|
||||
{
|
||||
for (int i = 0; i < m_Target.Splines.Count; i++)
|
||||
OnSplineChange(m_Target.Splines[i], -1, SplineModification.Default);
|
||||
}
|
||||
|
||||
UpdateStartOffsetT();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>If true, transform will automatically start following the target Spline on awake.</summary>
|
||||
[Obsolete("Use PlayOnAwake instead.", false)]
|
||||
public bool playOnAwake => PlayOnAwake;
|
||||
/// <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>
|
||||
[Obsolete("Use Loop instead.", false)]
|
||||
public LoopMode loopMode => Loop;
|
||||
/// <summary>The way the Spline should be looped. See <see cref="LoopMode"/> for details.</summary>
|
||||
public LoopMode Loop
|
||||
{
|
||||
get => m_LoopMode;
|
||||
set => m_LoopMode = value;
|
||||
}
|
||||
|
||||
/// <summary> The method used to traverse the Spline. See <see cref="Method"/> for details. </summary>
|
||||
[Obsolete("Use AnimationMethod instead.", false)]
|
||||
public Method method => AnimationMethod;
|
||||
/// <summary> The method used to traverse the Spline. See <see cref="Method"/> for details. </summary>
|
||||
public Method AnimationMethod
|
||||
{
|
||||
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>
|
||||
[Obsolete("Use Duration instead.", false)]
|
||||
public float duration => Duration;
|
||||
/// <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>
|
||||
[Obsolete("Use MaxSpeed instead.", false)]
|
||||
public float maxSpeed => MaxSpeed;
|
||||
/// <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>
|
||||
[Obsolete("Use Easing instead.", false)]
|
||||
public EasingMode easingMode => Easing;
|
||||
/// <summary> Easing mode used when animating the object along the Spline. See <see cref="EasingMode"/> for details. </summary>
|
||||
public EasingMode Easing
|
||||
{
|
||||
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>
|
||||
[Obsolete("Use Alignment instead.", false)]
|
||||
public AlignmentMode alignmentMode => Alignment;
|
||||
/// <summary> The way the object should align when animating along the Spline. See <see cref="AlignmentMode"/> for details. </summary>
|
||||
public AlignmentMode Alignment
|
||||
{
|
||||
get => m_AlignmentMode;
|
||||
set => m_AlignmentMode = value;
|
||||
}
|
||||
|
||||
/// <summary> Object space axis that should be considered as the object's forward vector. </summary>
|
||||
[Obsolete("Use ObjectForwardAxis instead.", false)]
|
||||
public AlignAxis objectForwardAxis => ObjectForwardAxis;
|
||||
/// <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>
|
||||
[Obsolete("Use ObjectUpAxis instead.", false)]
|
||||
public AlignAxis objectUpAxis => 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>
|
||||
[Obsolete("Use NormalizedTime instead.", false)]
|
||||
public float normalizedTime => NormalizedTime;
|
||||
/// <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;
|
||||
if (m_LoopMode == LoopMode.PingPong)
|
||||
{
|
||||
var currentDirection = (int)(m_ElapsedTime / m_Duration);
|
||||
m_ElapsedTime = m_Duration * m_NormalizedTime + ((currentDirection % 2 == 1) ? m_Duration : 0f);
|
||||
}
|
||||
else
|
||||
m_ElapsedTime = m_Duration * m_NormalizedTime;
|
||||
|
||||
UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Total time (in seconds) since the start of Spline's traversal. </summary>
|
||||
[Obsolete("Use ElapsedTime instead.", false)]
|
||||
public float elapsedTime => ElapsedTime;
|
||||
/// <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> Normalized distance [0;1] offset along the spline at which the object should be placed when the animation begins. </summary>
|
||||
public float StartOffset
|
||||
{
|
||||
get => m_StartOffset;
|
||||
set
|
||||
{
|
||||
if (m_SplineLength < 0f)
|
||||
RebuildSplinePath();
|
||||
|
||||
m_StartOffset = Mathf.Clamp01(value);
|
||||
UpdateStartOffsetT();
|
||||
}
|
||||
}
|
||||
|
||||
internal float StartOffsetT => m_StartOffsetT;
|
||||
|
||||
/// <summary> Returns true if object is currently animating along the Spline. </summary>
|
||||
[Obsolete("Use IsPlaying instead.", false)]
|
||||
public bool isPlaying => IsPlaying;
|
||||
/// <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>
|
||||
[Obsolete("Use Updated instead.", false)]
|
||||
public event Action<Vector3, Quaternion> onUpdated;
|
||||
/// <summary> Invoked each time object's animation along the Spline is updated.</summary>
|
||||
public event Action<Vector3, Quaternion> Updated;
|
||||
|
||||
private bool m_EndReached = false;
|
||||
/// <summary> Invoked every time the object's animation reaches the end of the Spline.
|
||||
/// In case the animation loops, this event is called at the end of each loop.</summary>
|
||||
public event Action Completed;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
RecalculateAnimationParameters();
|
||||
#if UNITY_EDITOR
|
||||
if(EditorApplication.isPlaying)
|
||||
#endif
|
||||
Restart(m_PlayOnAwake);
|
||||
#if UNITY_EDITOR
|
||||
else // Place the animated object back at the animation start position.
|
||||
Restart(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
RecalculateAnimationParameters();
|
||||
Spline.Changed += OnSplineChange;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Spline.Changed -= OnSplineChange;
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
m_Duration = Mathf.Max(0f, m_Duration);
|
||||
m_MaxSpeed = Mathf.Max(0f, m_MaxSpeed);
|
||||
RecalculateAnimationParameters();
|
||||
}
|
||||
|
||||
internal void RecalculateAnimationParameters()
|
||||
{
|
||||
RebuildSplinePath();
|
||||
|
||||
switch (m_Method)
|
||||
{
|
||||
case Method.Time:
|
||||
CalculateMaxSpeed();
|
||||
break;
|
||||
|
||||
case Method.Speed:
|
||||
CalculateDuration();
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Log($"{m_Method} animation method is not supported!", this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly string k_EmptyContainerError = "SplineAnimate does not have a valid SplineContainer set.";
|
||||
bool IsNullOrEmptyContainer()
|
||||
{
|
||||
if (m_Target == null || m_Target.Splines.Count == 0)
|
||||
{
|
||||
if(Application.isPlaying)
|
||||
Debug.LogError(k_EmptyContainerError, this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary> Begin animating object along the Spline. </summary>
|
||||
public void Play()
|
||||
{
|
||||
if (IsNullOrEmptyContainer())
|
||||
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)
|
||||
{
|
||||
// [SPLB-269]: Early exit if the container is null to remove log error when initializing the spline animate object from code
|
||||
if (Container == null)
|
||||
return;
|
||||
|
||||
if(IsNullOrEmptyContainer())
|
||||
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!", this);
|
||||
break;
|
||||
}
|
||||
UpdateTransform();
|
||||
UpdateStartOffsetT();
|
||||
|
||||
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)
|
||||
{
|
||||
var previousElapsedTime = m_ElapsedTime;
|
||||
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;
|
||||
UpdateEndReached(previousElapsedTime, 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;
|
||||
UpdateEndReached(previousElapsedTime, currentDuration);
|
||||
break;
|
||||
|
||||
case LoopMode.PingPong:
|
||||
t = Mathf.PingPong(m_ElapsedTime, currentDuration);
|
||||
UpdateEndReached(previousElapsedTime, currentDuration);
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Log($"{m_LoopMode} animation loop mode is not supported!", this);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// forcing reset to 0 if the m_NormalizedTime reach the end of the spline previously (1).
|
||||
m_NormalizedTime = t == 0 ? 0f : Mathf.Floor(m_NormalizedTime) + t;
|
||||
if (m_NormalizedTime >= 1f && m_LoopMode == LoopMode.Once)
|
||||
{
|
||||
m_EndReached = true;
|
||||
m_Playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateEndReached(float previousTime, float currentDuration)
|
||||
{
|
||||
m_EndReached = Mathf.FloorToInt(previousTime/currentDuration) < Mathf.FloorToInt(m_ElapsedTime/currentDuration);
|
||||
}
|
||||
|
||||
void UpdateStartOffsetT()
|
||||
{
|
||||
if (m_SplinePath != null)
|
||||
m_StartOffsetT = m_SplinePath.ConvertIndexUnit(m_StartOffset * m_SplineLength, PathIndexUnit.Distance, PathIndexUnit.Normalized);
|
||||
}
|
||||
|
||||
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);
|
||||
Updated?.Invoke(position, rotation);
|
||||
|
||||
if (m_EndReached)
|
||||
{
|
||||
m_EndReached = false;
|
||||
Completed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
void EvaluatePositionAndRotation(out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
var t = GetLoopInterpolation(true);
|
||||
position = m_Target.EvaluatePosition(m_SplinePath, 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 = m_Target.EvaluateTangent(m_SplinePath, t);
|
||||
if (Vector3.Magnitude(forward) <= Mathf.Epsilon)
|
||||
{
|
||||
if (t < 1f)
|
||||
forward = m_Target.EvaluateTangent(m_SplinePath, Mathf.Min(1f, t + 0.01f));
|
||||
else
|
||||
forward = m_Target.EvaluateTangent(m_SplinePath, t - 0.01f);
|
||||
}
|
||||
forward.Normalize();
|
||||
up = m_Target.EvaluateUpVector(m_SplinePath, t);
|
||||
break;
|
||||
|
||||
case AlignmentMode.SplineObject:
|
||||
var objectRotation = m_Target.transform.rotation;
|
||||
forward = objectRotation * forward;
|
||||
up = objectRotation * up;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Log($"{m_AlignmentMode} animation alignment mode is not supported!", this);
|
||||
break;
|
||||
}
|
||||
|
||||
var valid = math.isfinite(forward) & math.isfinite(up);
|
||||
|
||||
if (math.all(valid))
|
||||
rotation = Quaternion.LookRotation(forward, up) * axisRemapRotation;
|
||||
else
|
||||
Debug.LogError("Trying to EvaluatePositionAndRotation with invalid parameters. Please check the SplineAnimate component.", this);
|
||||
}
|
||||
else
|
||||
rotation = transform.rotation;
|
||||
}
|
||||
|
||||
void CalculateDuration()
|
||||
{
|
||||
if (m_SplineLength < 0f)
|
||||
RebuildSplinePath();
|
||||
|
||||
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!", this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateMaxSpeed()
|
||||
{
|
||||
if (m_SplineLength < 0f)
|
||||
RebuildSplinePath();
|
||||
|
||||
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!", this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildSplinePath()
|
||||
{
|
||||
if (m_Target != null)
|
||||
{
|
||||
m_SplinePath = new SplinePath<Spline>(m_Target.Splines);
|
||||
m_SplineLength = m_SplinePath != null ? m_SplinePath.GetLength() : 0f;
|
||||
}
|
||||
}
|
||||
|
||||
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(Spline spline, int knotIndex, SplineModification modificationType)
|
||||
{
|
||||
RecalculateAnimationParameters();
|
||||
}
|
||||
|
||||
internal float GetLoopInterpolation(bool offset)
|
||||
{
|
||||
var t = 0f;
|
||||
var normalizedTimeWithOffset = NormalizedTime + (offset ? m_StartOffsetT : 0f);
|
||||
if (Mathf.Floor(normalizedTimeWithOffset) == normalizedTimeWithOffset)
|
||||
t = Mathf.Clamp01(normalizedTimeWithOffset);
|
||||
else
|
||||
t = normalizedTimeWithOffset % 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 the corresponding <see cref="float3"/> direction for the specified <see cref="AlignAxis"/>.</returns>
|
||||
protected float3 GetAxis(AlignAxis axis)
|
||||
{
|
||||
return m_AlignAxisToVector[(int) axis];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8647d8846516479e99dbf89e8b927446
|
||||
timeCreated: 1643399835
|
||||
@@ -0,0 +1,527 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// A component that holds a list of <see cref="Spline"/> objects.
|
||||
/// </summary>
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
[Icon(k_IconPath)]
|
||||
#endif
|
||||
[AddComponentMenu("Splines/Spline Container")]
|
||||
[ExecuteAlways]
|
||||
public sealed class SplineContainer : MonoBehaviour, ISplineContainer, ISerializationCallbackReceiver
|
||||
{
|
||||
const string k_IconPath = "Packages/com.unity.splines/Editor/Resources/Icons/SplineComponent.png";
|
||||
|
||||
// Keeping a main spline to be backwards compatible with older versions of the spline package
|
||||
[SerializeField, Obsolete, HideInInspector]
|
||||
Spline m_Spline;
|
||||
|
||||
[SerializeField]
|
||||
Spline[] m_Splines = { new Spline() };
|
||||
|
||||
[SerializeField]
|
||||
KnotLinkCollection m_Knots = new KnotLinkCollection();
|
||||
|
||||
List<(int previousIndex, int newIndex)> m_ReorderedSplinesIndices = new List<(int, int)>();
|
||||
List<int> m_RemovedSplinesIndices = new List<int>();
|
||||
List<int> m_AddedSplinesIndices = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked any time a spline is added to the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The parameter corresponds to the spline index.
|
||||
/// </remarks>
|
||||
public static event Action<SplineContainer, int> SplineAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked any time a spline is removed from the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The parameter corresponds to the spline index.
|
||||
/// </remarks>
|
||||
public static event Action<SplineContainer, int> SplineRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked any time a spline is reordered in the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The first parameter corresponds to the previous spline index,
|
||||
/// the second parameter corresponds to the new spline index.
|
||||
/// </remarks>
|
||||
public static event Action<SplineContainer, int, int> SplineReordered;
|
||||
|
||||
ReadOnlyCollection<Spline> m_ReadOnlySplines;
|
||||
Dictionary<ISpline, NativeSpline> m_NativeSplinesCache = new();
|
||||
float4x4 m_NativeSplinesCacheTransform = float4x4.identity;
|
||||
|
||||
/// <summary>
|
||||
/// The list of all splines attached to that container.
|
||||
/// </summary>
|
||||
public IReadOnlyList<Spline> Splines
|
||||
{
|
||||
get => m_ReadOnlySplines ??= new ReadOnlyCollection<Spline>(m_Splines);
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
m_Splines = Array.Empty<Spline>();
|
||||
return;
|
||||
}
|
||||
|
||||
ClearCaches();
|
||||
DisposeNativeSplinesCache();
|
||||
|
||||
for (var i = 0; i < m_Splines.Length; i++)
|
||||
{
|
||||
var index = IndexOf(value, m_Splines[i]);
|
||||
if (index == -1)
|
||||
m_RemovedSplinesIndices.Add(i);
|
||||
else if (index != i)
|
||||
m_ReorderedSplinesIndices.Add((i, index));
|
||||
}
|
||||
|
||||
for (var i = 0; i < value.Count; i++)
|
||||
{
|
||||
var index = Array.FindIndex(m_Splines, spline => spline == value[i]);
|
||||
if (index == -1)
|
||||
m_AddedSplinesIndices.Add(i);
|
||||
}
|
||||
|
||||
m_Splines = new Spline[value.Count];
|
||||
for (int i = 0; i < m_Splines.Length; ++i)
|
||||
{
|
||||
m_Splines[i] = value[i];
|
||||
if (IsNonUniformlyScaled)
|
||||
GetOrBakeNativeSpline(m_Splines[i]);
|
||||
}
|
||||
|
||||
m_ReadOnlySplines = new ReadOnlyCollection<Spline>(m_Splines);
|
||||
|
||||
foreach (var removedIndex in m_RemovedSplinesIndices)
|
||||
SplineRemoved?.Invoke(this, removedIndex);
|
||||
|
||||
foreach (var addedIndex in m_AddedSplinesIndices)
|
||||
SplineAdded?.Invoke(this, addedIndex);
|
||||
|
||||
foreach (var reorderedSpline in m_ReorderedSplinesIndices)
|
||||
SplineReordered?.Invoke(this, reorderedSpline.previousIndex, reorderedSpline.newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
static int IndexOf(IReadOnlyList<Spline> self, Spline elementToFind)
|
||||
{
|
||||
for (var i = 0; i < self.Count; i++)
|
||||
{
|
||||
var element = self[i];
|
||||
if (element == elementToFind)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of all linked knots. Linked knots can be on different splines. However, knots can
|
||||
/// only link to other knots within the same container. This collection is used to maintain
|
||||
/// the validity of the links when operations such as knot insertions or removals are performed on the splines.
|
||||
/// </summary>
|
||||
public KnotLinkCollection KnotLinkCollection => m_Knots;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Spline"/> at <paramref name="index"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
public Spline this[int index] => m_Splines[index];
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Spline.Changed += OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
DisposeNativeSplinesCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that all caches contain valid data. Call this to avoid unexpected performance costs when evaluating
|
||||
/// splines data. Caches remain valid until any part of the splines state is modified.
|
||||
/// </summary>
|
||||
public void Warmup()
|
||||
{
|
||||
for (int i = 0; i < Splines.Count; ++i)
|
||||
{
|
||||
var spline = Splines[i];
|
||||
spline.Warmup();
|
||||
GetOrBakeNativeSpline(spline);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearCaches()
|
||||
{
|
||||
m_ReorderedSplinesIndices.Clear();
|
||||
m_RemovedSplinesIndices.Clear();
|
||||
m_AddedSplinesIndices.Clear();
|
||||
|
||||
m_ReadOnlySplines = null;
|
||||
}
|
||||
|
||||
void DisposeNativeSplinesCache()
|
||||
{
|
||||
// Dispose cached native splines
|
||||
foreach (var splineToNativePair in m_NativeSplinesCache)
|
||||
splineToNativePair.Value.Dispose();
|
||||
m_NativeSplinesCache.Clear();
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int index, SplineModification modificationType)
|
||||
{
|
||||
var splineIndex = Array.IndexOf(m_Splines, spline);
|
||||
if (splineIndex < 0)
|
||||
return;
|
||||
|
||||
switch (modificationType)
|
||||
{
|
||||
case SplineModification.KnotModified:
|
||||
this.SetLinkedKnotPosition(new SplineKnotIndex(splineIndex, index));
|
||||
break;
|
||||
|
||||
case SplineModification.KnotReordered:
|
||||
case SplineModification.KnotInserted:
|
||||
m_Knots.KnotInserted(splineIndex, index);
|
||||
break;
|
||||
|
||||
case SplineModification.KnotRemoved:
|
||||
m_Knots.KnotRemoved(splineIndex, index);
|
||||
break;
|
||||
}
|
||||
|
||||
m_NativeSplinesCache.Remove(spline);
|
||||
}
|
||||
|
||||
void OnKnotModified(Spline spline, int index)
|
||||
{
|
||||
var splineIndex = Array.IndexOf(m_Splines, spline);
|
||||
if (splineIndex >= 0)
|
||||
this.SetLinkedKnotPosition(new SplineKnotIndex(splineIndex, index));
|
||||
}
|
||||
|
||||
bool IsNonUniformlyScaled
|
||||
{
|
||||
get
|
||||
{
|
||||
float3 lossyScale = transform.lossyScale;
|
||||
return !math.all(lossyScale == lossyScale.x);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main <see cref="Spline"/> attached to this component.
|
||||
/// </summary>
|
||||
public Spline Spline
|
||||
{
|
||||
get => m_Splines.Length > 0 ? m_Splines[0] : null;
|
||||
set
|
||||
{
|
||||
if (m_Splines.Length > 0)
|
||||
m_Splines[0] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes 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">The output variable for the float3 position at t.</param>
|
||||
/// <param name="tangent">The output variable for the float3 tangent at t.</param>
|
||||
/// <param name="upVector">The 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)
|
||||
=> Evaluate(0, t, out position, out tangent, out upVector);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the interpolated position, direction and upDirection at ratio t for the spline at index `splineIndex`. 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="splineIndex">The index of the spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 that represents the ratio along the curve.</param>
|
||||
/// <param name="position">The output variable for the float3 position at t.</param>
|
||||
/// <param name="tangent">The output variable for the float3 tangent at t.</param>
|
||||
/// <param name="upVector">The output variable for the float3 up direction at t.</param>
|
||||
/// <returns>True if a valid set of output variables is computed and false otherwise.</returns>
|
||||
public bool Evaluate(int splineIndex, float t, out float3 position, out float3 tangent, out float3 upVector)
|
||||
=> Evaluate(m_Splines[splineIndex], t, out position, out tangent, out upVector);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interpolated position, direction, and upDirection at ratio t for a spline. This method gets the three
|
||||
/// vectors faster than EvaluateSplinePosition, EvaluateSplineTangent and EvaluateSplineUpVector for the same
|
||||
/// time t, because it reduces some redundant computation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The spline type.</typeparam>
|
||||
/// <param name="spline">The spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 that represents the ratio along the curve.</param>
|
||||
/// <param name="position">The output variable for the float3 position at t.</param>
|
||||
/// <param name="tangent">The output variable for the float3 tangent at t.</param>
|
||||
/// <param name="upVector">The output variable for the float3 up direction at t.</param>
|
||||
/// <returns>True if a valid set of output variables is computed and false otherwise.</returns>
|
||||
public bool Evaluate<T>(T spline, float t, out float3 position, out float3 tangent, out float3 upVector) where T : ISpline
|
||||
{
|
||||
if (spline == null)
|
||||
{
|
||||
position = float3.zero;
|
||||
tangent = new float3(0, 0, 1);
|
||||
upVector = new float3(0, 1, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsNonUniformlyScaled)
|
||||
return SplineUtility.Evaluate(GetOrBakeNativeSpline(spline), 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>
|
||||
/// Evaluates the position of a point, t, on a spline 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) => EvaluatePosition(0, t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the position of a point, t, on a spline at an index, `splineIndex`, in world space.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of the curve.</param>
|
||||
/// <returns>A world position along the spline.</returns>
|
||||
public float3 EvaluatePosition(int splineIndex, float t) => EvaluatePosition(m_Splines[splineIndex], t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the position of a point, t, on a given spline, in world space.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The spline type.</typeparam>
|
||||
/// <param name="spline">The spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of the curve.</param>
|
||||
/// <returns>A world position along the spline.</returns>
|
||||
public float3 EvaluatePosition<T>(T spline, float t) where T : ISpline
|
||||
{
|
||||
if (spline== null)
|
||||
return float.PositiveInfinity;
|
||||
|
||||
if (IsNonUniformlyScaled)
|
||||
return SplineUtility.EvaluatePosition(GetOrBakeNativeSpline(spline), t);
|
||||
|
||||
return transform.TransformPoint(SplineUtility.EvaluatePosition(spline, t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the tangent vector of a point, t, on a spline in world space.
|
||||
/// </summary>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed tangent vector.</returns>
|
||||
public float3 EvaluateTangent(float t) => EvaluateTangent(0, t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the tangent vector of a point, t, on a spline at an index, `splineIndex`, in world space.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed tangent vector.</returns>
|
||||
public float3 EvaluateTangent(int splineIndex, float t) => EvaluateTangent(m_Splines[splineIndex], t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the tangent vector of a point, t, on a given spline, in world space.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The spline type.</typeparam>
|
||||
/// <param name="spline">The spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed tangent vector.</returns>
|
||||
public float3 EvaluateTangent<T>(T spline, float t) where T : ISpline
|
||||
{
|
||||
if (spline == null)
|
||||
return float.PositiveInfinity;
|
||||
|
||||
if (IsNonUniformlyScaled)
|
||||
return SplineUtility.EvaluateTangent(GetOrBakeNativeSpline(spline), t);
|
||||
|
||||
return transform.TransformVector(SplineUtility.EvaluateTangent(spline, t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the up vector of a point, t, on a spline in world space.
|
||||
/// </summary>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed up direction.</returns>
|
||||
public float3 EvaluateUpVector(float t) => EvaluateUpVector(0, t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the up vector of a point, t, on a spline at an index, `splineIndex`, in world space.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the Spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed up direction.</returns>
|
||||
public float3 EvaluateUpVector(int splineIndex, float t) => EvaluateUpVector(m_Splines[splineIndex], t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the up vector of a point, t, on a given spline, in world space.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The spline type.</typeparam>
|
||||
/// <param name="spline">The Spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed up direction.</returns>
|
||||
public float3 EvaluateUpVector<T>(T spline, float t) where T : ISpline
|
||||
{
|
||||
if (spline == null)
|
||||
return float3.zero;
|
||||
|
||||
if (IsNonUniformlyScaled)
|
||||
return SplineUtility.EvaluateUpVector(GetOrBakeNativeSpline(spline), t);
|
||||
|
||||
//Using TransformDirection as up direction is not sensible to scale.
|
||||
return transform.TransformDirection(SplineUtility.EvaluateUpVector(spline, t));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the acceleration vector of a point, t, on a spline in world space.
|
||||
/// </summary>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed acceleration vector.</returns>
|
||||
public float3 EvaluateAcceleration(float t) => EvaluateAcceleration(0, t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the acceleration vector of a point, t, on a spline at an index, `splineIndex, in world space.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed acceleration vector.</returns>
|
||||
public float3 EvaluateAcceleration(int splineIndex, float t) => EvaluateAcceleration(m_Splines[splineIndex], t);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the acceleration vector of a point, t, on a given Spline, in world space.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The spline type.</typeparam>
|
||||
/// <param name="spline">The Spline to evaluate.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing a percentage of entire spline.</param>
|
||||
/// <returns>The computed acceleration vector.</returns>
|
||||
public float3 EvaluateAcceleration<T>(T spline, float t) where T : ISpline
|
||||
{
|
||||
if (spline == null)
|
||||
return float3.zero;
|
||||
|
||||
if (IsNonUniformlyScaled)
|
||||
return SplineUtility.EvaluateAcceleration(GetOrBakeNativeSpline(spline), 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() => CalculateLength(0);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the length of `Splines[splineIndex]` in world space.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index of the spline to evaluate.</param>
|
||||
/// <returns>The length of `Splines[splineIndex]` in world space</returns>
|
||||
public float CalculateLength(int splineIndex)
|
||||
{
|
||||
return SplineUtility.CalculateLength(m_Splines[splineIndex], transform.localToWorldMatrix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See ISerializationCallbackReceiver.
|
||||
/// </summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See ISerializationCallbackReceiver.
|
||||
/// </summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
if (m_Spline != null && m_Spline.Count > 0)
|
||||
{
|
||||
if (m_Splines == null || m_Splines.Length == 0 || m_Splines.Length == 1 && m_Splines[0].Count == 0)
|
||||
{
|
||||
m_Splines = new[] { m_Spline };
|
||||
m_ReadOnlySplines = new ReadOnlyCollection<Spline>(m_Splines);
|
||||
}
|
||||
|
||||
m_Spline = new Spline(); //Clear spline
|
||||
}
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
struct SplineToNative
|
||||
{
|
||||
public ISpline spline;
|
||||
public NativeSpline nativeSpline;
|
||||
}
|
||||
static List<SplineToNative> m_AllocPreventionHelperBuffer = new (capacity:32);
|
||||
NativeSpline GetOrBakeNativeSpline<T>(T spline) where T : ISpline
|
||||
{
|
||||
// Build native spline if we don't have one cached for this spline
|
||||
if (!m_NativeSplinesCache.TryGetValue(spline, out var cachedNativeSpline))
|
||||
{
|
||||
m_NativeSplinesCacheTransform = transform.localToWorldMatrix;
|
||||
cachedNativeSpline = new NativeSpline(spline, m_NativeSplinesCacheTransform, true, Allocator.Persistent);
|
||||
m_NativeSplinesCache.Add(spline, cachedNativeSpline);
|
||||
}
|
||||
// or if the cached spline was baked using different transform
|
||||
else if (!MathUtility.All(m_NativeSplinesCacheTransform, transform.localToWorldMatrix))
|
||||
{
|
||||
// Since we have one m_NativeSplinesCacheTransform for all cached splines we need to rebake all
|
||||
m_NativeSplinesCacheTransform = transform.localToWorldMatrix;
|
||||
|
||||
m_AllocPreventionHelperBuffer.Clear();
|
||||
foreach (ISpline iSpline in m_NativeSplinesCache.Keys)
|
||||
{
|
||||
var oldCache = m_NativeSplinesCache[iSpline];
|
||||
var newCache = new NativeSpline(spline, m_NativeSplinesCacheTransform, true, Allocator.Persistent);
|
||||
|
||||
if (iSpline == (ISpline)spline)
|
||||
cachedNativeSpline = newCache;
|
||||
|
||||
oldCache.Dispose();
|
||||
// Doing this dance as dictionaries can't be modified mid-iteration
|
||||
m_AllocPreventionHelperBuffer.Add(new SplineToNative() { spline = iSpline, nativeSpline = newCache });
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_AllocPreventionHelperBuffer.Count; ++i)
|
||||
{
|
||||
var dataToSet = m_AllocPreventionHelperBuffer[i];
|
||||
m_NativeSplinesCache[dataToSet.spline] = dataToSet.nativeSpline;
|
||||
}
|
||||
}
|
||||
|
||||
return cachedNativeSpline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dab5c7d4c32e743048dfca98e2d5914f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,659 @@
|
||||
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
|
||||
}
|
||||
|
||||
// Used internally to try preserving index positioning for SplineData embedded in Spline class. It is not very
|
||||
// useful outside of this specific context, which is why it is not public. Additionally, in the future I'd like to
|
||||
// explore passing the previous and new spline length to enable better preservation of distance and normalized
|
||||
// indices.
|
||||
interface ISplineModificationHandler
|
||||
{
|
||||
void OnSplineModified(SplineModificationData info);
|
||||
}
|
||||
|
||||
/// <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>>, ISplineModificationHandler
|
||||
{
|
||||
static readonly DataPointComparer<DataPoint<T>> k_DataPointComparer = new DataPointComparer<DataPoint<T>>();
|
||||
|
||||
[SerializeField]
|
||||
PathIndexUnit m_IndexUnit = PathIndexUnit.Knot;
|
||||
|
||||
[SerializeField]
|
||||
T m_DefaultValue;
|
||||
|
||||
[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>
|
||||
/// Default value to use when a new DataPoint is automatically added.
|
||||
/// </summary>
|
||||
/// <seealso cref="PathIndexUnit"/>
|
||||
public T DefaultValue
|
||||
{
|
||||
get => m_DefaultValue;
|
||||
set => m_DefaultValue = 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>
|
||||
[Obsolete("Use Changed instead.", false)]
|
||||
public event Action changed;
|
||||
/// <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();
|
||||
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>
|
||||
/// <param name="useDefaultValue">
|
||||
/// If true will use <see cref="m_DefaultValue"/> to set the value, otherwise will interpolate the value regarding the closest DataPoints.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The index of the inserted dataPoint.
|
||||
/// </returns>
|
||||
public int AddDataPointWithDefaultValue(float t, bool useDefaultValue = false)
|
||||
{
|
||||
var dataPoint = new DataPoint<T>(t, m_DefaultValue);
|
||||
if(Count == 0 || useDefaultValue)
|
||||
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. This is the index into the collection, not the PathIndexUnit.Knot.</param>
|
||||
/// <param name="newIndex">The new index (<see cref="UnityEngine.Splines.PathIndexUnit.Knot"/>) for this <see cref="DataPoint{T}"/>.</param>
|
||||
/// <returns>The index of the modified <see cref="DataPoint{T}"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified <paramref name="index"/> is out of range.</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);
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the DataPoints collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An IEnumerator{DataPoint{T}} for this collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
static float WrapInt(float index, int lowerBound, int upperBound)
|
||||
{
|
||||
return Wrap((int)math.floor(index), lowerBound, upperBound) + math.frac(index);
|
||||
}
|
||||
|
||||
static float ClampInt(float index, int lowerBound, int upperBound)
|
||||
{
|
||||
return math.clamp((int)math.floor(index), lowerBound, upperBound) + math.frac(index);
|
||||
}
|
||||
|
||||
// IMPORTANT - NOT PUBLIC API. See ISplineModificationHandler for more information.
|
||||
/// <summary>
|
||||
/// Attempts to preserve knot indices relative to their current position after a Spline has been modified. This
|
||||
/// is only valid for SplineData that is indexed using <see cref="UnityEngine.Splines.PathIndexUnit.Knot"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function is only valid for PathIndexUnit.Knot because other representations are (1) implicitly better
|
||||
/// suited to handle knot insertion/deletion while preserving locality, and (2) converting to the Knot index
|
||||
/// format for the purposes of order preservation would need to be done _before_ knot insertion/deletion in
|
||||
/// order to be correct.
|
||||
/// </remarks>
|
||||
void ISplineModificationHandler.OnSplineModified(SplineModificationData data)
|
||||
{
|
||||
if (m_IndexUnit != PathIndexUnit.Knot)
|
||||
return;
|
||||
|
||||
if (data.Modification == SplineModification.KnotModified || data.Modification == SplineModification.KnotReordered || data.Modification == SplineModification.Default)
|
||||
return;
|
||||
|
||||
var editedKnotOldIdx = data.KnotIndex;
|
||||
var prevLength = data.PrevCurveLength;
|
||||
var nextLength = data.NextCurveLength;
|
||||
|
||||
var dataPointsToRemove = new List<int>();
|
||||
for (int dataIdx = 0, c = Count; dataIdx < c; ++dataIdx)
|
||||
{
|
||||
var point = m_DataPoints[dataIdx];
|
||||
var dataKnotOldIdx = (int)math.floor(point.Index);
|
||||
var fracIdx = point.Index - dataKnotOldIdx;
|
||||
|
||||
if (data.Modification == SplineModification.KnotInserted)
|
||||
{
|
||||
var currentLength = data.Spline.GetCurveLength(data.Spline.PreviousIndex(editedKnotOldIdx));
|
||||
if (dataKnotOldIdx == editedKnotOldIdx - 1)
|
||||
{
|
||||
if (fracIdx < currentLength / prevLength)
|
||||
point.Index = dataKnotOldIdx + fracIdx * (prevLength / currentLength);
|
||||
else
|
||||
point.Index = (dataKnotOldIdx + 1) + (fracIdx * prevLength - currentLength) / (prevLength - currentLength);
|
||||
}
|
||||
else if (data.Spline.Closed && dataKnotOldIdx == data.Spline.Count - 2 && editedKnotOldIdx == 0)
|
||||
{
|
||||
if (fracIdx < currentLength / prevLength)
|
||||
point.Index = (dataKnotOldIdx + 1) + fracIdx * (prevLength / currentLength);
|
||||
else
|
||||
point.Index = (fracIdx * prevLength - currentLength) / (prevLength - currentLength);
|
||||
}
|
||||
else
|
||||
point.Index += 1;
|
||||
}
|
||||
else if (data.Modification == SplineModification.KnotRemoved)
|
||||
{
|
||||
// The spline is cleared.
|
||||
if (editedKnotOldIdx == -1)
|
||||
{
|
||||
dataPointsToRemove.Add(dataIdx);
|
||||
continue;
|
||||
}
|
||||
|
||||
var removingHardLink = (fracIdx == 0f && dataKnotOldIdx == editedKnotOldIdx);
|
||||
|
||||
var removingEndKnots = !data.Spline.Closed &&
|
||||
((dataKnotOldIdx <= 0 && editedKnotOldIdx == 0) || // Removed first curve with data points on it.
|
||||
(editedKnotOldIdx == data.Spline.Count && // Removed last curve
|
||||
math.ceil(point.Index) >= editedKnotOldIdx)); // and data point is either on last curve or beyond it/clamped.
|
||||
|
||||
if (removingHardLink || removingEndKnots || data.Spline.Count == 1)
|
||||
dataPointsToRemove.Add(dataIdx);
|
||||
else if (dataKnotOldIdx == editedKnotOldIdx - 1)
|
||||
point.Index = dataKnotOldIdx + fracIdx * prevLength / (prevLength + nextLength);
|
||||
else if (dataKnotOldIdx == editedKnotOldIdx)
|
||||
point.Index = (dataKnotOldIdx - 1) + (prevLength + fracIdx * nextLength) / (prevLength + nextLength);
|
||||
else if ((data.Spline.Closed && editedKnotOldIdx == 0) /*acting on the previous first knot of a closed spline*/
|
||||
&& dataKnotOldIdx == data.Spline.Count /*and considering data that is on the last curve closing the spline*/)
|
||||
point.Index = (dataKnotOldIdx - 1) + (fracIdx * prevLength) / (prevLength + nextLength);
|
||||
else if (dataKnotOldIdx >= editedKnotOldIdx)
|
||||
point.Index -= 1;
|
||||
}
|
||||
else // Closed Modified
|
||||
{
|
||||
if (!data.Spline.Closed && // Spline has been opened and
|
||||
(math.ceil(point.Index) >= data.Spline.Count)) // data point is on connecting curve or the very end of it (treat same as open spline last curve deletion).
|
||||
dataPointsToRemove.Add(dataIdx);
|
||||
}
|
||||
|
||||
m_DataPoints[dataIdx] = point;
|
||||
}
|
||||
|
||||
for (int i = dataPointsToRemove.Count - 1; i > -1; --i)
|
||||
{
|
||||
m_DataPoints.RemoveAt(dataPointsToRemove[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5475addd28b94489a727bbc680f89a3d
|
||||
timeCreated: 1613497560
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
[Serializable]
|
||||
class SplineDataKeyValuePair<T>
|
||||
{
|
||||
public string Key;
|
||||
public SplineData<T> Value;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class SplineDataDictionary<T> : IEnumerable<SplineDataKeyValuePair<T>>
|
||||
{
|
||||
[SerializeField]
|
||||
List<SplineDataKeyValuePair<T>> m_Data = new ();
|
||||
|
||||
public IEnumerable<string> Keys => m_Data.Select(x => x.Key);
|
||||
|
||||
public IEnumerable<SplineData<T>> Values => m_Data.Select(x => x.Value);
|
||||
|
||||
int FindIndex(string key)
|
||||
{
|
||||
for (int i = 0, c = m_Data.Count; i < c; ++i)
|
||||
if (m_Data[i].Key == key)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out SplineData<T> value)
|
||||
{
|
||||
var index = FindIndex(key);
|
||||
value = index < 0 ? null : m_Data[index].Value;
|
||||
return index > -1;
|
||||
}
|
||||
|
||||
public SplineData<T> GetOrCreate(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
|
||||
if (!TryGetValue(key, out var data))
|
||||
m_Data.Add(new SplineDataKeyValuePair<T>()
|
||||
{
|
||||
Key = key,
|
||||
Value = data = new SplineData<T>()
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
public SplineData<T> this[string key]
|
||||
{
|
||||
get => TryGetValue(key, out var data) ? data : null;
|
||||
set
|
||||
{
|
||||
int i = FindIndex(key);
|
||||
var copy = new SplineData<T>(value);
|
||||
if (i < 0)
|
||||
m_Data.Add(new SplineDataKeyValuePair<T>() { Key = key, Value = copy });
|
||||
else
|
||||
m_Data[i].Value = copy;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string key) => FindIndex(key) > -1;
|
||||
|
||||
public IEnumerator<SplineDataKeyValuePair<T>> GetEnumerator() => m_Data.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)m_Data).GetEnumerator();
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
var i = FindIndex(key);
|
||||
if (i < 0)
|
||||
return false;
|
||||
m_Data.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveEmpty()
|
||||
{
|
||||
for (int i = m_Data.Count - 1; i > -1; --i)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_Data[i].Key) || m_Data[i].Value?.Count < 1)
|
||||
{
|
||||
Debug.Log($"{typeof(T)} remove empty key \"{m_Data[i].Key}\"");
|
||||
m_Data.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68cccfca0c5a4d6aaff0dee4d7e65c7e
|
||||
timeCreated: 1670616826
|
||||
@@ -0,0 +1,13 @@
|
||||
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>
|
||||
[Obsolete("Use SplineDataHandles.DataPointHandles instead and EditorTools to interact with SplineData.", false)]
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public abstract class SplineDataHandleAttribute : Attribute {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f26841404a934577bf234bc14475f649
|
||||
timeCreated: 1625517748
|
||||
@@ -0,0 +1,486 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine.Splines.ExtrusionShapes;
|
||||
|
||||
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")]
|
||||
[ExecuteAlways]
|
||||
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 = true;
|
||||
|
||||
[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.")]
|
||||
#pragma warning disable 414
|
||||
bool m_UpdateColliders = true;
|
||||
#pragma warning restore 414
|
||||
|
||||
[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 target Spline is closed or when the profile of the shape to extrude is concave, 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);
|
||||
|
||||
[SerializeField, Tooltip("Set true to reverse the winding order of vertices so that the face normals are inverted.")]
|
||||
bool m_FlipNormals = false;
|
||||
|
||||
Mesh m_Mesh;
|
||||
|
||||
float m_NextScheduledRebuild;
|
||||
|
||||
// This is the angle that gives the best results.
|
||||
float m_AutosmoothAngle = 180f;
|
||||
|
||||
bool m_RebuildRequested;
|
||||
|
||||
bool m_CanCapEnds;
|
||||
internal bool CanCapEnds => m_CanCapEnds;
|
||||
|
||||
[SerializeReference]
|
||||
IExtrudeShape m_Shape;
|
||||
|
||||
internal IExtrudeShape Shape
|
||||
{
|
||||
get => m_Shape;
|
||||
set => m_Shape = value;
|
||||
}
|
||||
|
||||
/// <summary>The SplineContainer of the <see cref="Spline"/> to extrude.</summary>
|
||||
[Obsolete("Use Container instead.", false)]
|
||||
public SplineContainer container => Container;
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use RebuildOnSplineChange instead.", false)]
|
||||
public bool rebuildOnSplineChange => RebuildOnSplineChange;
|
||||
|
||||
/// <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;
|
||||
|
||||
if (!value)
|
||||
m_RebuildRequested = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The maximum number of times per-second that the mesh will be rebuilt.</summary>
|
||||
[Obsolete("Use RebuildFrequency instead.", false)]
|
||||
public int rebuildFrequency => RebuildFrequency;
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use Sides instead.", false)]
|
||||
public int sides => Sides;
|
||||
|
||||
/// <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);
|
||||
|
||||
if (m_Shape == null)
|
||||
{
|
||||
var circle = new Circle();
|
||||
circle.SideCount = m_Sides;
|
||||
m_Shape = circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>How many edge loops comprise the one unit length of the mesh.</summary>
|
||||
[Obsolete("Use SegmentsPerUnit instead.", false)]
|
||||
public float segmentsPerUnit => SegmentsPerUnit;
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use Capped instead.", false)]
|
||||
public bool capped => Capped;
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use Radius instead.", false)]
|
||||
public float radius => Radius;
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use Range instead.", false)]
|
||||
public Vector2 range => Range;
|
||||
|
||||
/// <summary>
|
||||
/// The section of the Spline to extrude. The X value is the start interpolation, the Y value is the end
|
||||
/// interpolation. X and Y are normalized values between 0 and 1.
|
||||
/// </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>
|
||||
/// Set true to reverse the winding order of vertices so that the face normals are inverted. This is useful
|
||||
/// primarily for <see cref="SplineShape"/> templates where the input path may not produce a counter-clockwise
|
||||
/// vertex ring. Counter-clockwise winding equates to triangles facing outwards.
|
||||
/// </summary>
|
||||
public bool FlipNormals
|
||||
{
|
||||
get => m_FlipNormals;
|
||||
set => m_FlipNormals = value;
|
||||
}
|
||||
|
||||
/// <summary>The main Spline to extrude.</summary>
|
||||
[Obsolete("Use Spline instead.", false)]
|
||||
public Spline spline => Spline;
|
||||
|
||||
/// <summary>The main Spline to extrude.</summary>
|
||||
public Spline Spline
|
||||
{
|
||||
get => m_Container?.Spline;
|
||||
}
|
||||
|
||||
/// <summary>The Splines to extrude.</summary>
|
||||
public IReadOnlyList<Spline> Splines
|
||||
{
|
||||
get => m_Container?.Splines;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
TryGetComponent(out m_Container);
|
||||
|
||||
if (TryGetComponent<MeshFilter>(out var filter))
|
||||
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 UNITY_EDITOR
|
||||
if (UnityEditor.EditorApplication.isPlaying)
|
||||
#endif
|
||||
{
|
||||
if (m_Container == null || m_Container.Spline == null || m_Container.Splines.Count == 0)
|
||||
return;
|
||||
|
||||
if ((m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null)
|
||||
return;
|
||||
}
|
||||
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
internal static readonly string k_EmptyContainerError = "Spline Extrude does not have a valid SplineContainer set.";
|
||||
bool IsNullOrEmptyContainer()
|
||||
{
|
||||
var isNull = m_Container == null || m_Container.Spline == null || m_Container.Splines.Count == 0;
|
||||
if (isNull)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
Debug.LogError(k_EmptyContainerError, this);
|
||||
|
||||
if (!IsNullOrEmptyMeshFilter(false))
|
||||
m_Mesh.Clear();
|
||||
}
|
||||
|
||||
return isNull;
|
||||
}
|
||||
|
||||
internal static readonly string k_EmptyMeshFilterError = "SplineExtrude.createMeshInstance is disabled," +
|
||||
" but there is no valid mesh assigned. " +
|
||||
"Please create or assign a writable mesh asset.";
|
||||
bool IsNullOrEmptyMeshFilter(bool logError = true)
|
||||
{
|
||||
var isNull = (m_Mesh = GetComponent<MeshFilter>().sharedMesh) == null;
|
||||
if (isNull && logError)
|
||||
Debug.LogError(k_EmptyMeshFilterError, this);
|
||||
|
||||
return isNull;
|
||||
}
|
||||
|
||||
internal void SetSplineContainerOnGO()
|
||||
{
|
||||
// Ensure that we use the spline container on the GameObject.
|
||||
// For example, in the case of pasting a SplineExtrude component from one
|
||||
// GameObject to another.
|
||||
var splineContainer = gameObject.GetComponent<SplineContainer>();
|
||||
if (splineContainer != null && splineContainer != m_Container)
|
||||
m_Container = splineContainer;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Spline.Changed += OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modificationType)
|
||||
{
|
||||
if (!m_RebuildOnSplineChange)
|
||||
return;
|
||||
|
||||
var isMainSpline = m_Container != null && Splines.Contains(spline);
|
||||
var isShapeSpline = (m_Shape is SplineShape splineShape) && splineShape.Spline != null && splineShape.Spline.Equals(spline);
|
||||
m_RebuildRequested = isMainSpline || isShapeSpline;
|
||||
}
|
||||
|
||||
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_Shape == null)
|
||||
{
|
||||
var circle = new Circle();
|
||||
circle.SideCount = m_Sides;
|
||||
m_Shape = circle;
|
||||
}
|
||||
|
||||
if (IsNullOrEmptyContainer() || IsNullOrEmptyMeshFilter())
|
||||
return;
|
||||
|
||||
// SegmentCount is intentionally omitted for backwards compatibility reasons. This component extrudes many
|
||||
// Splines using the same mesh, taking into account each spline length in order to calculate the total
|
||||
// number of segments. This is unlike the normal `Extrude` method which accepts an int segment count.
|
||||
var settings = new ExtrudeSettings<IExtrudeShape>(m_Shape)
|
||||
{
|
||||
Radius = m_Radius,
|
||||
CapEnds = m_Capped,
|
||||
Range = m_Range,
|
||||
FlipNormals = m_FlipNormals
|
||||
};
|
||||
|
||||
SplineMesh.Extrude(m_Container.Splines, m_Mesh, settings, SegmentsPerUnit);
|
||||
|
||||
m_CanCapEnds = SplineMesh.s_IsConvex && !Spline.Closed;
|
||||
|
||||
AutosmoothNormals();
|
||||
|
||||
m_NextScheduledRebuild = Time.time + 1f / m_RebuildFrequency;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (UnityEditor.EditorUtility.IsPersistent(m_Mesh))
|
||||
UnityEditor.EditorApplication.delayCall += () => UnityEditor.AssetDatabase.SaveAssetIfDirty(m_Mesh);
|
||||
#endif
|
||||
|
||||
#if UNITY_PHYSICS_MODULE
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutosmoothNormals()
|
||||
{
|
||||
var vertices = m_Mesh.vertices;
|
||||
var triangles = m_Mesh.triangles;
|
||||
var normals = new Vector3[vertices.Length];
|
||||
|
||||
// Dictionary to hold face normals and the vertices that make up each face.
|
||||
var faceNormals = new Dictionary<int, Vector3>();
|
||||
var vertexFaces = new Dictionary<int, List<int>>();
|
||||
|
||||
// Calculate the face normals.
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
var v1 = vertices[triangles[i]];
|
||||
var v2 = vertices[triangles[i + 1]];
|
||||
var v3 = vertices[triangles[i + 2]];
|
||||
var faceNormal = Vector3.Cross(v2 - v1, v3 - v1).normalized;
|
||||
|
||||
var faceIndex = i / 3;
|
||||
faceNormals[faceIndex] = faceNormal;
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
var vertexIndex = triangles[i + j];
|
||||
|
||||
if (!vertexFaces.ContainsKey(vertexIndex))
|
||||
vertexFaces[vertexIndex] = new List<int>();
|
||||
|
||||
vertexFaces[vertexIndex].Add(faceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the vertex normals.
|
||||
foreach (var pair in vertexFaces)
|
||||
{
|
||||
var vertexIndex = pair.Key;
|
||||
var connectedFaces = pair.Value;
|
||||
var averageNormal = Vector3.zero;
|
||||
|
||||
foreach (var faceIndex in connectedFaces)
|
||||
{
|
||||
var currentFaceNormal = faceNormals[faceIndex];
|
||||
var sharedNormal = true;
|
||||
|
||||
foreach (var otherFaceIndex in connectedFaces)
|
||||
{
|
||||
if (faceIndex == otherFaceIndex)
|
||||
continue;
|
||||
|
||||
var otherFaceNormal = faceNormals[otherFaceIndex];
|
||||
var angle = Vector3.Angle(currentFaceNormal, otherFaceNormal);
|
||||
|
||||
if (angle > m_AutosmoothAngle)
|
||||
{
|
||||
sharedNormal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedNormal) // Not a sharp normal.
|
||||
{
|
||||
averageNormal += currentFaceNormal;
|
||||
}
|
||||
else // Sharp normal.
|
||||
{
|
||||
normals[vertexIndex] = currentFaceNormal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (normals[vertexIndex] == Vector3.zero) // If not set to a sharp normal.
|
||||
normals[vertexIndex] = averageNormal.normalized;
|
||||
}
|
||||
|
||||
// Apply the normals to the mesh.
|
||||
m_Mesh.normals = normals;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
if (UnityEditor.EditorApplication.isPlaying)
|
||||
return;
|
||||
|
||||
Rebuild();
|
||||
}
|
||||
#endif
|
||||
|
||||
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}/SplineExtrude_{mesh.name}.asset");
|
||||
UnityEditor.AssetDatabase.CreateAsset(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,314 @@
|
||||
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)
|
||||
{
|
||||
return CreateLinear(positions, null, closed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Spline"/> from a list of positions.
|
||||
/// </summary>
|
||||
/// <param name="positions">A collection of knot positions.</param>
|
||||
/// <param name="rotations">A collection of knot rotations. Must be equal in length to the positions array.</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, IList<quaternion> rotations, bool closed = false)
|
||||
{
|
||||
var knotCount = positions.Count;
|
||||
var spline = new Spline(knotCount, closed);
|
||||
|
||||
for (int i = 0; i < knotCount; ++i)
|
||||
{
|
||||
var position = positions[i];
|
||||
var rotation = rotations?[i] ?? quaternion.identity;
|
||||
var tangentIn = float3.zero;
|
||||
var tangentOut = float3.zero;
|
||||
|
||||
spline.Add(new BezierKnot(position, tangentIn, tangentOut, rotation), TangentMode.Linear);
|
||||
}
|
||||
|
||||
return spline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Spline"/> from a list of positions and place tangents to create Catmull Rom curves.
|
||||
/// </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 CreateCatmullRom(IList<float3> positions, bool closed = false)
|
||||
{
|
||||
return CreateCatmullRom(positions, null, closed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Spline"/> from a list of positions and place tangents to create Catmull Rom curves.
|
||||
/// </summary>
|
||||
/// <param name="positions">A collection of knot positions.</param>
|
||||
/// <param name="rotations">A collection of knot rotations. Must be equal in length to the positions array.</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>
|
||||
internal static Spline CreateCatmullRom(IList<float3> positions, IList<quaternion> rotations, bool closed = false)
|
||||
{
|
||||
var knotCount = positions.Count;
|
||||
var spline = new Spline(knotCount, closed);
|
||||
|
||||
for (int i = 0; i < knotCount; ++i)
|
||||
{
|
||||
var position = positions[i];
|
||||
var rotation = rotations?[i] ?? quaternion.identity;
|
||||
var n = SplineUtility.NextIndex(i, knotCount, closed);
|
||||
var p = SplineUtility.PreviousIndex(i, knotCount, closed);
|
||||
|
||||
var tangentOut = math.rotate(
|
||||
math.inverse(rotation),
|
||||
SplineUtility.GetAutoSmoothTangent(positions[p], positions[i], positions[n], SplineUtility.CatmullRomTension));
|
||||
var tangentIn = -tangentOut;
|
||||
spline.Add(new BezierKnot(position, tangentIn, tangentOut, rotation), TangentMode.AutoSmooth);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var spline = 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);
|
||||
|
||||
for (int i = 0; i < spline.Count; ++i)
|
||||
spline.SetTangentMode(i, TangentMode.Mirrored);
|
||||
|
||||
return spline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in the shape of a helix with a single revolution.
|
||||
/// </summary>
|
||||
/// <param name="radius">The distance from the center to the helix's curve.</param>
|
||||
/// <param name="height">The height of the helix shape.</param>
|
||||
/// <param name="revolutions">The number of revolutions the helix should have.</param>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreateHelix(float radius, float height, int revolutions)
|
||||
{
|
||||
revolutions = math.max(1, revolutions);
|
||||
var revHeight = height / revolutions;
|
||||
var alpha = 0.5f * math.PI;
|
||||
var p = revHeight / (2f * math.PI);
|
||||
var ax = radius * math.cos(alpha);
|
||||
var az = radius * math.sin(alpha);
|
||||
var b = p * alpha * (radius - ax) * (3f * radius - ax) / (az * (4f * radius - ax) * math.tan(alpha));
|
||||
|
||||
var yOffset = revHeight * 0.25f;
|
||||
var p0 = new float3(ax, -alpha * p + yOffset, -az);
|
||||
var p1 = new float3((4f * radius - ax) / 3f, -b + yOffset, -(radius - ax) * (3f * radius - ax) / (3f * az));
|
||||
var p2 = new float3((4f * radius - ax) / 3f, b + yOffset, (radius - ax) * (3f * radius - ax) / (3f * az));
|
||||
var p3 = new float3(ax, alpha * p + yOffset, az);
|
||||
|
||||
Spline spline = new Spline();
|
||||
|
||||
// Create the first two points and tangents forming the first half of the helix.
|
||||
var tangent = p1 - p0;
|
||||
var tangentLength = math.length(tangent);
|
||||
var tangentNorm = math.normalize(tangent);
|
||||
var normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
|
||||
spline.Add(new BezierKnot(p0, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
|
||||
tangent = p3 - p2;
|
||||
tangentNorm = math.normalize(tangent);
|
||||
normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
|
||||
spline.Add(new BezierKnot(p3, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
|
||||
|
||||
// Rotate and offset the first half to form a full single revolution helix.
|
||||
var rotation = quaternion.AxisAngle(math.up(), math.radians(180f));
|
||||
yOffset = revHeight * 0.5f;
|
||||
p3 = math.rotate(rotation, p3);
|
||||
p3.y += yOffset;
|
||||
tangent = p1 - p0;
|
||||
tangentNorm = math.normalize(tangent);
|
||||
normal = math.cross(math.cross(tangentNorm, math.up()), tangentNorm);
|
||||
spline.Add(new BezierKnot(p3, new float3(0f, 0f, -tangentLength), new float3(0f, 0f, tangentLength), quaternion.LookRotation(tangentNorm, normal)));
|
||||
|
||||
// Create knots for remaining revolutions
|
||||
var revYOffset = new float3(0f, revHeight, 0f);
|
||||
for (int i = 1; i < revolutions; ++i)
|
||||
{
|
||||
var knotA = spline[^1];
|
||||
knotA.Position += revYOffset;
|
||||
var knotB = spline[^2];
|
||||
knotB.Position += revYOffset;
|
||||
|
||||
spline.Add(knotB);
|
||||
spline.Add(knotA);
|
||||
}
|
||||
|
||||
return spline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in the shape of a square with circular arcs at its corners.
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the square's edges.</param>
|
||||
/// <param name="cornerRadius">The radius of the circular arcs at the corners of the shape.
|
||||
/// A value of 0 creates a square with no rounding. A value that is half of <paramref name="size"/> creates a circle.</param>
|
||||
/// <remarks>The range for <paramref name="cornerRadius"/> is 0 and half of <paramref name="size"/>.</remarks>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreateRoundedCornerSquare(float size, float cornerRadius)
|
||||
{
|
||||
float radius = size * 0.5f;
|
||||
cornerRadius = math.clamp(cornerRadius, 0f, radius);
|
||||
if (cornerRadius == 0f)
|
||||
return CreateSquare(size);
|
||||
|
||||
float3 cornerP0 = new float3(-radius, 0f, radius - cornerRadius);
|
||||
float3 cornerP1 = new float3(-radius + cornerRadius, 0f, radius);
|
||||
float cornerTangentLen = SplineMath.GetUnitCircleTangentLength() * cornerRadius;
|
||||
float cornerAngle = 0f;
|
||||
|
||||
var spline = new Spline();
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var rotation = Quaternion.Euler(0f, cornerAngle, 0f);
|
||||
if (cornerRadius < 1f)
|
||||
{
|
||||
spline.Add(new BezierKnot(rotation * cornerP0, new float3(0f, 0f, -math.min(cornerTangentLen, 0f)), new float3(0f, 0f, cornerTangentLen), Quaternion.identity * rotation));
|
||||
spline.Add(new BezierKnot(rotation * cornerP1, new float3(0f, 0f, -cornerTangentLen), new float3(0f, 0f, math.min(cornerTangentLen, 0f)), Quaternion.Euler(0f, 90f, 0f) * rotation));
|
||||
}
|
||||
else
|
||||
spline.Add(new BezierKnot(rotation * cornerP0, new float3(0f, 0f, -cornerTangentLen), new float3(0f, 0f, cornerTangentLen), Quaternion.identity * rotation));
|
||||
|
||||
cornerAngle += 90f;
|
||||
}
|
||||
|
||||
spline.Closed = true;
|
||||
return spline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in the shape of a square with sharp corners.
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the square's edges.</param>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreateSquare(float size)
|
||||
{
|
||||
float3 p0 = new float3(-.5f, 0f, -.5f) * size;
|
||||
float3 p1 = new float3(-.5f, 0f, .5f) * size;
|
||||
float3 p2 = new float3( .5f, 0f, .5f) * size;
|
||||
float3 p3 = new float3( .5f, 0f, -.5f) * size;
|
||||
return CreateLinear(new float3[] { p0, p1, p2, p3 }, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in the shape of a circle.
|
||||
/// </summary>
|
||||
/// <param name="radius">The radius of the circle.</param>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreateCircle(float radius)
|
||||
{
|
||||
float3 point = new float3(-radius, 0f, 0);
|
||||
float3 tangent = new float3(0f, 0f, SplineMath.GetUnitCircleTangentLength() * radius);
|
||||
|
||||
var spline = new Spline();
|
||||
quaternion rotation = quaternion.identity;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
spline.Add(new BezierKnot(math.rotate(rotation, point), -tangent, tangent, rotation));
|
||||
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), math.PI * 0.5f));
|
||||
}
|
||||
spline.Closed = true;
|
||||
|
||||
return spline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in the shape of a polygon with a specific number of sides.
|
||||
/// </summary>
|
||||
/// <param name="edgeSize">The size of the polygon's edges.</param>
|
||||
/// <param name="sides">The amount of sides the polygon has.</param>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreatePolygon(float edgeSize, int sides)
|
||||
{
|
||||
sides = math.max(3, sides);
|
||||
|
||||
var points = new float3[sides];
|
||||
var angleStep = 2f * math.PI / sides;
|
||||
var radius = edgeSize * 0.5f / math.sin(angleStep * 0.5f);
|
||||
var point = new float3(0f, 0f, radius);
|
||||
var rotation = quaternion.identity;
|
||||
|
||||
for (int i = 0; i < sides; ++i)
|
||||
{
|
||||
points[i] = math.rotate(rotation, point);
|
||||
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), angleStep));
|
||||
}
|
||||
|
||||
return CreateLinear(points, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Spline"/> in in the shape of a star with a specified number of corners.
|
||||
/// </summary>
|
||||
/// <param name="edgeSize">The distance between the corners of the star.</param>
|
||||
/// <param name="corners">The amount of corners the star has.</param>
|
||||
/// <param name="concavity">The sharpness of the corners. The range is 0 through 1. </param>
|
||||
/// <returns>A new Spline.</returns>
|
||||
public static Spline CreateStarPolygon(float edgeSize, int corners, float concavity)
|
||||
{
|
||||
concavity = math.clamp(concavity, 0f, 1f);
|
||||
if (concavity == 0f)
|
||||
CreatePolygon(edgeSize, corners);
|
||||
|
||||
corners = math.max(3, corners);
|
||||
|
||||
var sidesDouble = corners * 2;
|
||||
var points = new float3[sidesDouble];
|
||||
var angleStep = 2f * math.PI / corners;
|
||||
var radius = edgeSize * 0.5f / math.sin(angleStep * 0.5f);
|
||||
var point = new float3(0f, 0f, radius);
|
||||
var rotation = quaternion.identity;
|
||||
|
||||
for (int i = 0; i < sidesDouble; i+= 2)
|
||||
{
|
||||
points[i] = math.rotate(rotation, point);
|
||||
rotation = math.mul(rotation, quaternion.AxisAngle(math.up(), angleStep));
|
||||
|
||||
if (i != 0)
|
||||
points[i - 1] = (points[i - 2] + points[i]) * 0.5f * (1f - concavity);
|
||||
if (i == sidesDouble - 2)
|
||||
points[i + 1] = (points[0] + points[i]) * 0.5f * (1f - concavity);
|
||||
}
|
||||
|
||||
return CreateLinear(points, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9d35cf5830b403f8a24cef0e22ee40c
|
||||
timeCreated: 1628776092
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// SplineInfo is used to wrap a reference to the <see cref="SplineContainer"/> and index within that container to
|
||||
/// describe a <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SplineInfo : IEquatable<SplineInfo>, ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField]
|
||||
Object m_Object;
|
||||
|
||||
[SerializeReference]
|
||||
ISplineContainer m_Container;
|
||||
|
||||
[SerializeField]
|
||||
int m_SplineIndex;
|
||||
|
||||
/// <summary>
|
||||
/// If the referenced <see cref="ISplineContainer"/> is a UnityEngine.Object this field contains that object.
|
||||
/// </summary>
|
||||
public Object Object => m_Object;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISplineContainer"/> that contains the spline.
|
||||
/// </summary>
|
||||
public ISplineContainer Container
|
||||
{
|
||||
get => m_Container ?? m_Object as ISplineContainer;
|
||||
set => m_Container = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The associated <see cref="UnityEngine.Transform"/> of the target. This may be null if the container is not
|
||||
/// a MonoBehaviour.
|
||||
/// </summary>
|
||||
public Transform Transform => Object is Component component ? component.transform : null;
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the <see cref="UnityEngine.Splines.Spline"/>. This may be null if the container or index are
|
||||
/// invalid.
|
||||
/// </summary>
|
||||
public Spline Spline => Container != null && Index > -1 && Index < Container.Splines.Count
|
||||
? Container.Splines[Index]
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the spline in the enumerable returned by the <see cref="ISplineContainer"/>.
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
get => m_SplineIndex;
|
||||
set => m_SplineIndex = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matrix that transforms the <see cref="Object"/> from local space into world space.
|
||||
/// </summary>
|
||||
public float4x4 LocalToWorld => Transform != null ? (float4x4)Transform.localToWorldMatrix : float4x4.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplineInfo"/> object.
|
||||
/// </summary>
|
||||
/// <param name="container">The <see cref="ISplineContainer"/> that contains the spline.</param>
|
||||
/// <param name="index">The index of the spline in the enumerable returned by the <see cref="ISplineContainer"/>.</param>
|
||||
public SplineInfo(ISplineContainer container, int index)
|
||||
{
|
||||
m_Container = container;
|
||||
m_Object = container as Object;
|
||||
m_SplineIndex = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare against another <see cref="SplineInfo"/> for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance to compare against.</param>
|
||||
/// <returns>
|
||||
/// Returns true when <paramref name="other"/> is a <see cref="SplineInfo"/> and the values of each instance are
|
||||
/// identical.
|
||||
/// </returns>
|
||||
public bool Equals(SplineInfo other)
|
||||
{
|
||||
return Equals(Container, other.Container) && Index == other.Index;
|
||||
}
|
||||
|
||||
/// <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="SplineInfo"/> and the values of each instance are
|
||||
/// identical.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is SplineInfo other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a hash code for this info.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the <see cref="SplineInfo"/>.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (Container != null ? Container.GetHashCode() : 0);
|
||||
hashCode = (hashCode * 397) ^ Index;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before this object is serialized by the Editor.
|
||||
/// </summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
if (m_Container is Object obj)
|
||||
{
|
||||
m_Object = obj;
|
||||
m_Container = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after this object is deserialized by the Editor.
|
||||
/// </summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
m_Container ??= m_Object as ISplineContainer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bde05c1df765c046bf58d1435722167
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b74686beb0670a4ea61325c0acf4411
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
#if UNITY_BURST_ENABLED
|
||||
using Unity.Burst;
|
||||
#endif
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to calculate, in parallel, the positions along <see cref="NativeSpline"/>.
|
||||
/// </summary>
|
||||
#if UNITY_BURST_ENABLED
|
||||
[BurstCompile]
|
||||
#endif
|
||||
public struct GetPosition : IJobParallelFor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="NativeSpline"/> to be evaluated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must be allocated with a Allocator.Persistent or Allocator.TempJob.
|
||||
/// </remarks>
|
||||
[ReadOnly]
|
||||
public NativeSpline Spline;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of float3 to be written. The size of this array determines how many positions are
|
||||
/// evaluated.
|
||||
/// </summary>
|
||||
[WriteOnly]
|
||||
public NativeArray<float3> Positions;
|
||||
|
||||
/// <summary>
|
||||
/// Called by the job system to evaluate a position at an index. The interpolation value is calculated as
|
||||
/// `index / positions.Length - 1`.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the positions array to evaluate.</param>
|
||||
public void Execute(int index)
|
||||
{
|
||||
Positions[index] = Spline.EvaluatePosition(index / (Positions.Length-1f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A job struct for calculating in parallel the position, tangent, and normal (up) vectors along a
|
||||
/// <see cref="NativeSpline"/>.
|
||||
/// </summary>
|
||||
public struct GetPositionTangentNormal : IJobParallelFor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="NativeSpline"/> to be evaluated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must be allocated with a Allocator.Persistent or Allocator.TempJob.
|
||||
/// </remarks>
|
||||
[ReadOnly]
|
||||
public NativeSpline Spline;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of float3 to be written. The size of this array determines how many positions are
|
||||
/// evaluated.
|
||||
/// </summary>
|
||||
[WriteOnly]
|
||||
public NativeArray<float3> Positions;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of float3 to be written. The size of this array must match the length of <see cref="Positions"/>.
|
||||
/// </summary>
|
||||
[WriteOnly]
|
||||
public NativeArray<float3> Tangents;
|
||||
|
||||
/// <summary>
|
||||
/// A NativeArray of float3 to be written. The size of this array must match the length of <see cref="Positions"/>.
|
||||
/// </summary>
|
||||
[WriteOnly]
|
||||
public NativeArray<float3> Normals;
|
||||
|
||||
/// <summary>
|
||||
/// Called by the job system to evaluate position, tangent, and normal at an index. The interpolation value is
|
||||
/// calculated as `index / positions.Length - 1`.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the positions array to evaluate.</param>
|
||||
public void Execute(int index)
|
||||
{
|
||||
Spline.Evaluate(index / (Positions.Length - 1f), out var p, out var t, out var n);
|
||||
Positions[index] = p;
|
||||
Tangents[index] = t;
|
||||
Normals[index] = n;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SplineJobs class contains utility methods for evaluating spline data using the Jobs system.
|
||||
/// </summary>
|
||||
public static class SplineJobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Populate a preallocated NativeArray with position data from a spline.
|
||||
/// </summary>
|
||||
/// <param name="spline">The spline to evaluate. If you pass a NativeSpline, it must be allocated
|
||||
/// with the Persistent or TempJob allocator. Temp is invalid for use with the Jobs system.</param>
|
||||
/// <param name="positions">A preallocated array of float3 to be populated with evenly interpolated positions
|
||||
/// from a spline.</param>
|
||||
/// <typeparam name="T">The type of ISpline.</typeparam>
|
||||
public static void EvaluatePosition<T>(T spline, NativeArray<float3> positions) where T : ISpline
|
||||
{
|
||||
using var native = new NativeSpline(spline, Allocator.TempJob);
|
||||
EvaluatePosition(native, positions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate a preallocated NativeArray with position data from a spline.
|
||||
/// </summary>
|
||||
/// <param name="spline">The spline to evaluate. The NativeSpline must be allocated with a Persistent
|
||||
/// or TempJob allocator. Temp is invalid for use in the Jobs system.</param>
|
||||
/// <param name="positions">A preallocated array of float3 to be populated with evenly interpolated positions
|
||||
/// from a spline.</param>
|
||||
public static void EvaluatePosition(NativeSpline spline, NativeArray<float3> positions)
|
||||
{
|
||||
var job = new GetPosition()
|
||||
{
|
||||
Spline = spline,
|
||||
Positions = positions
|
||||
};
|
||||
|
||||
var handle = job.Schedule(positions.Length, 1);
|
||||
handle.Complete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate a set of pre-allocated NativeArray with position, tangent, and normal data from a spline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To apply a transform to the results of this method, pass a new NativeSpline constructed with the desired
|
||||
/// transformation matrix.
|
||||
///
|
||||
/// This method creates a temporary NativeSpline copy of the spline to be evaluated. In some cases, this can
|
||||
/// be more resource intensive than iterating and evaluating a spline on a single thread. For the best performance,
|
||||
/// pass an existing NativeSpline instance to the <paramref name="spline"/>
|
||||
/// parameter.
|
||||
/// </remarks>
|
||||
/// <param name="spline">The spline to evaluate. If you pass a NativeSpline, it must be allocated
|
||||
/// with the Persistent or TempJob allocator. Temp is invalid for use with the Jobs system.</param>
|
||||
/// <param name="positions">A preallocated array of float3 to be populated with evenly interpolated positions
|
||||
/// from a spline.</param>
|
||||
/// <param name="tangents">A preallocated array of float3 to be populated with evenly interpolated tangents
|
||||
/// from a spline. Must be the same size as the positions array.</param>
|
||||
/// <param name="normals">A preallocated array of float3 to be populated with evenly interpolated normals
|
||||
/// from a spline. Must be the same size as the positions array.</param>
|
||||
/// <typeparam name="T">The type of ISpline.</typeparam>
|
||||
public static void EvaluatePositionTangentNormal<T>(
|
||||
T spline,
|
||||
NativeArray<float3> positions,
|
||||
NativeArray<float3> tangents,
|
||||
NativeArray<float3> normals) where T : ISpline
|
||||
{
|
||||
using var native = new NativeSpline(spline, Allocator.TempJob);
|
||||
EvaluatePositionTangentNormal(native, positions, tangents, normals);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate a set of preallocated NativeArray with position, tangent, and normal data from a spline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To apply a transform to the results of this method, pass a new NativeSpline constructed with the desired
|
||||
/// transformation matrix.
|
||||
/// </remarks>
|
||||
/// <param name="spline">The spline to evaluate. The NativeSpline must be allocated with a Persistent
|
||||
/// or TempJob allocator. Temp is invalid for use in the Jobs system.</param>
|
||||
/// <param name="positions">A preallocated array of float3 to be populated with evenly interpolated positions
|
||||
/// from a spline.</param>
|
||||
/// <param name="tangents">A preallocated array of float3 to be populated with evenly interpolated tangents
|
||||
/// from a spline. Must be the same size as the positions array.</param>
|
||||
/// <param name="normals">A preallocated array of float3 to be populated with evenly interpolated normals
|
||||
/// from a spline. Must be the same size as the positions array.</param>
|
||||
public static void EvaluatePositionTangentNormal(NativeSpline spline,
|
||||
NativeArray<float3> positions,
|
||||
NativeArray<float3> tangents,
|
||||
NativeArray<float3> normals)
|
||||
{
|
||||
var job = new GetPositionTangentNormal()
|
||||
{
|
||||
Spline = spline,
|
||||
Positions = positions,
|
||||
Tangents = tangents,
|
||||
Normals = normals
|
||||
};
|
||||
|
||||
var handle = job.Schedule(positions.Length, 1);
|
||||
handle.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c7a3ac809e47bea9665591771154ce
|
||||
timeCreated: 1647366323
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a tuple to define a couple (Spline index, Knot index) that identifies a particular knot on a spline.
|
||||
/// This tuple is used by <see cref="KnotLinkCollection"/> to maintain links between knots.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SplineKnotIndex : IEquatable<SplineKnotIndex>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the default value for an invalid index.
|
||||
/// </summary>
|
||||
public static SplineKnotIndex Invalid = new SplineKnotIndex(-1, -1);
|
||||
|
||||
/// <summary>
|
||||
/// The index of the spline in the <see cref="SplineContainer.Splines"/>.
|
||||
/// </summary>
|
||||
public int Spline;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the knot in the spline.
|
||||
/// </summary>
|
||||
public int Knot;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new SplineKnotIndex to reference a knot.
|
||||
/// </summary>
|
||||
/// <param name="spline">The spline index.</param>
|
||||
/// <param name="knot">The knot index.</param>
|
||||
public SplineKnotIndex(int spline, int knot)
|
||||
{
|
||||
Spline = spline;
|
||||
Knot = knot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two indices are equal.
|
||||
/// </summary>
|
||||
/// <param name="indexA">The first index.</param>
|
||||
/// <param name="indexB">The second index.</param>
|
||||
/// <returns>Returns true if the indices reference the same knot on the same spline, false otherwise.</returns>
|
||||
public static bool operator ==(SplineKnotIndex indexA, SplineKnotIndex indexB)
|
||||
{
|
||||
return indexA.Equals(indexB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two indices are not equal.
|
||||
/// </summary>
|
||||
/// <param name="indexA">The first index.</param>
|
||||
/// <param name="indexB">The second index.</param>
|
||||
/// <returns>Returns false if the indices reference the same knot on the same spline, true otherwise.</returns>
|
||||
public static bool operator !=(SplineKnotIndex indexA, SplineKnotIndex indexB)
|
||||
{
|
||||
return !indexA.Equals(indexB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two indices are equal.
|
||||
/// </summary>
|
||||
/// <param name="otherIndex">The index to compare against.</param>
|
||||
/// <returns>Returns true if the indices reference the same knot on the same spline, false otherwise.</returns>
|
||||
public bool Equals(SplineKnotIndex otherIndex)
|
||||
{
|
||||
return Spline == otherIndex.Spline && Knot == otherIndex.Knot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two indices are equal.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare against.</param>
|
||||
/// <returns>Returns true if the object is a SplineKnotIndex and the indices reference the same knot on the same spline, false otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is SplineKnotIndex other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an index is greater than or equal to 0.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the indices are greater than or equal to 0, false otherwise.</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return Spline >= 0 && Knot >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash code for this SplineKnotIndex.
|
||||
/// </summary>
|
||||
/// <returns> A hash code for the SplineKnotIndex. </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Spline * 397) ^ Knot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representation of a SplineKnotIndex.
|
||||
/// </summary>
|
||||
/// <returns> A string representation of this SplineKnotIndex. </returns>
|
||||
public override string ToString() => $"{{{Spline}, {Knot}}}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3aac33faa99ca184787470685f1bfa5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Assorted utility functions for math equations commonly used when working with Splines.
|
||||
/// </summary>
|
||||
public static class SplineMath
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the parameterization of a ray line projection. The parameter will be negative if the nearest point
|
||||
/// between the ray/line is negative to 'lineOrigin', and greater than 1 if nearest intersection is past the end
|
||||
/// off the line segment (lineOrigin + lineDir).
|
||||
/// </summary>
|
||||
/// <param name="ro">The ray origin point.</param>
|
||||
/// <param name="rd">The ray direction (normalized vector).</param>
|
||||
/// <param name="lineOrigin">Line segment first point.</param>
|
||||
/// <param name="lineDir">Line segment direction (with magnitude).</param>
|
||||
/// <returns>The parameter of a ray line projection.</returns>
|
||||
public static float RayLineParameter(float3 ro, float3 rd, float3 lineOrigin, float3 lineDir)
|
||||
{
|
||||
var v0 = ro - lineOrigin;
|
||||
var v1 = math.cross(rd, math.cross(rd, lineDir));
|
||||
// the parameter of a ray to line projection will be negative if the intersection is negative to line
|
||||
// direction from 'a', and greater than 1 if intersection is past the line segment end 'b'
|
||||
return math.dot(v0, v1) / math.dot(lineDir, v1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the shortest distance between a ray and line segment as a direction and magnitude.
|
||||
/// </summary>
|
||||
/// <param name="ro">The ray origin point.</param>
|
||||
/// <param name="rd">The ray direction (normalized vector).</param>
|
||||
/// <param name="a">The line start point.</param>
|
||||
/// <param name="b">The line end point.</param>
|
||||
/// <returns>Returns the shortest distance between a ray and line segment as a direction and magnitude.</returns>
|
||||
public static float3 RayLineDistance(float3 ro, float3 rd, float3 a, float3 b)
|
||||
{
|
||||
var points = RayLineNearestPoint(ro, rd, a, b);
|
||||
return points.linePoint - points.rayPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the nearest points between a ray and line segment.
|
||||
/// </summary>
|
||||
/// <param name="ro">The ray origin point.</param>
|
||||
/// <param name="rd">The ray direction (normalized vector).</param>
|
||||
/// <param name="a">The line start point.</param>
|
||||
/// <param name="b">The line end point.</param>
|
||||
/// <returns>Returns the nearest points between a ray and line segment.</returns>
|
||||
public static (float3 rayPoint, float3 linePoint) RayLineNearestPoint(
|
||||
float3 ro,
|
||||
float3 rd,
|
||||
float3 a,
|
||||
float3 b)
|
||||
{
|
||||
return RayLineNearestPoint(ro, rd, a, b, out _, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the nearest points on a ray and a line segment to one another.
|
||||
/// </summary>
|
||||
/// <param name="ro">The ray origin point.</param>
|
||||
/// <param name="rd">The ray direction (normalized vector).</param>
|
||||
/// <param name="a">The line start point.</param>
|
||||
/// <param name="b">The line end point.</param>
|
||||
/// <param name="rayParam">The signed distance between point 'ro' and the projection of the line segment along the ray.</param>
|
||||
/// <param name="lineParam">The signed distance between point 'a' and the projection of point 'p' on the line segment.</param>
|
||||
/// <returns>Returns the nearest points on a ray and a line segment to one another.</returns>
|
||||
public static (float3 rayPoint, float3 linePoint) RayLineNearestPoint(
|
||||
float3 ro,
|
||||
float3 rd,
|
||||
float3 a,
|
||||
float3 b,
|
||||
out float rayParam,
|
||||
out float lineParam)
|
||||
{
|
||||
var lineDir = b - a;
|
||||
lineParam = RayLineParameter(ro, rd, a, lineDir);
|
||||
var linePoint = a + lineDir * math.saturate(lineParam);
|
||||
rayParam = math.dot(rd, linePoint - ro);
|
||||
var rayPoint = ro + rd * rayParam;
|
||||
return (rayPoint, linePoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the nearest point on a finite line segment to a point.
|
||||
/// </summary>
|
||||
/// <param name="p">The point to compare to.</param>
|
||||
/// <param name="a">The line start point.</param>
|
||||
/// <param name="b">The line end point.</param>
|
||||
/// <param name="lineParam">The signed distance between point 'a' and the projection of point 'p' on the line segment.</param>
|
||||
/// <returns>The nearest point on a line segment to another point.</returns>
|
||||
public static float3 PointLineNearestPoint(float3 p, float3 a, float3 b, out float lineParam)
|
||||
{
|
||||
var dir = b - a;
|
||||
var len = math.length(dir);
|
||||
var nrm = math.select(0f, dir * (1f / len), len > math.FLT_MIN_NORMAL);
|
||||
lineParam = math.dot(nrm, p - a);
|
||||
return a + nrm * math.clamp(lineParam, 0f, len);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance from a point to a line segment.
|
||||
/// </summary>
|
||||
/// <param name="p">The point to compare against.</param>
|
||||
/// <param name="a">The start point of the line segment.</param>
|
||||
/// <param name="b">The end point of the line segment.</param>
|
||||
/// <returns>Returns the distance of the closest line from a point to a line segment.</returns>
|
||||
public static float DistancePointLine(float3 p, float3 a, float3 b)
|
||||
{
|
||||
return math.length(PointLineNearestPoint(p, a, b, out _) - p);
|
||||
}
|
||||
|
||||
internal static float GetUnitCircleTangentLength()
|
||||
{
|
||||
// https://mechanicalexpressions.com/explore/geometric-modeling/circle-spline-approximation.pdf
|
||||
return (4f * (math.sqrt(2f) - 1f)) / 3f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59c27bc09d9b426f9c90c5d8eb9a933a
|
||||
timeCreated: 1652726670
|
||||
@@ -0,0 +1,856 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Splines.ExtrusionShapes;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains settings pertaining to the creation of mesh geometry for an
|
||||
/// extruded shape. Use with <see cref="SplineMesh"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
||||
public struct ExtrudeSettings<T> where T : IExtrudeShape
|
||||
{
|
||||
const int k_SegmentsMin = 2, k_SegmentsMax = 4096;
|
||||
const float k_RadiusMin = .00001f, k_RadiusMax = 10000f;
|
||||
|
||||
[SerializeField]
|
||||
T m_Shape;
|
||||
|
||||
[SerializeField]
|
||||
bool m_CapEnds;
|
||||
|
||||
[SerializeField]
|
||||
bool m_FlipNormals;
|
||||
|
||||
[SerializeField]
|
||||
int m_SegmentCount;
|
||||
|
||||
[SerializeField]
|
||||
float m_Radius;
|
||||
|
||||
[SerializeField]
|
||||
Vector2 m_Range;
|
||||
|
||||
/// <summary>
|
||||
/// How many sections compose the length of the mesh.
|
||||
/// </summary>
|
||||
public int SegmentCount
|
||||
{
|
||||
get => m_SegmentCount;
|
||||
set => m_SegmentCount = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the start and end of the mesh is filled. This setting is ignored
|
||||
/// when the extruded spline is closed.
|
||||
/// Important note - cap are triangulated using a method that assumes convex geometry.
|
||||
/// If the input shape is concave, caps may show visual artifacts or overlaps.
|
||||
/// </summary>
|
||||
public bool CapEnds
|
||||
{
|
||||
get => m_CapEnds;
|
||||
set => m_CapEnds = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set true to reverse the winding order of vertices so that the face normals are inverted. This is useful
|
||||
/// primarily for <see cref="SplineShape"/> templates where the input path may not produce a counter-clockwise
|
||||
/// vertex ring. Counter-clockwise winding equates to triangles facing outwards.
|
||||
/// </summary>
|
||||
public bool FlipNormals
|
||||
{
|
||||
get => m_FlipNormals;
|
||||
set => m_FlipNormals = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public float2 Range
|
||||
{
|
||||
get => m_Range;
|
||||
set => m_Range = math.clamp(new float2(math.min(value.x, value.y), math.max(value.x, value.y)), 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the extruded mesh. Radius is half of the width of the entire shape.
|
||||
/// The return value of <see cref="IExtrudeShape.GetPosition"/> is multiplied by this
|
||||
/// value to determine the size of the resulting shape.
|
||||
/// </summary>
|
||||
public float Radius
|
||||
{
|
||||
get => m_Radius;
|
||||
set => m_Radius = math.clamp(value, k_RadiusMin, k_RadiusMax);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IExtrudeShape"/> object defines the outline path of each segment.
|
||||
/// </summary>
|
||||
public T Shape
|
||||
{
|
||||
get => m_Shape;
|
||||
set => m_Shape = value;
|
||||
}
|
||||
|
||||
internal bool DoCapEnds<K>(K spline) where K : ISpline => m_CapEnds && !spline.Closed;
|
||||
|
||||
internal bool DoCloseSpline<K>(K spline) where K : ISpline => math.abs(1f - (Range.y - Range.x)) < float.Epsilon && spline.Closed;
|
||||
|
||||
internal int sides
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Shape is SplineShape)
|
||||
return wrapped ? Shape.SideCount + 1 : Shape.SideCount;
|
||||
|
||||
return wrapped ? Shape.SideCount : Shape.SideCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// true if a revolution ends at the start vertex, or false if it ends at the last vertex in the ring
|
||||
internal bool wrapped
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Shape is SplineShape splineShape)
|
||||
{
|
||||
if (splineShape.Spline != null)
|
||||
return splineShape.Spline.Closed;
|
||||
}
|
||||
|
||||
if (Shape is Road)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new settings object with an <see cref="IExtrudeShape"/>
|
||||
/// instance. A default set of parameters will be used.
|
||||
/// </summary>
|
||||
/// <param name="shape">The <see cref="IExtrudeShape"/> template to
|
||||
/// be used as the shape template when extruding.</param>
|
||||
public ExtrudeSettings(T shape) : this(16, false, new float2(0, 1), .5f, shape)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new settings object. This is used by functions in
|
||||
/// <see cref="SplineMesh"/> to extrude a shape template along a spline.
|
||||
/// </summary>
|
||||
/// <param name="segments">The number of segments to divide the extruded spline into when creating vertex rings.</param>
|
||||
/// <param name="capped">Defines whether the ends of the extruded spline mesh should be closed.</param>
|
||||
/// <param name="range">The start and end points as normalized interpolation values.</param>
|
||||
/// <param name="radius">Defines the size of the extruded mesh.</param>
|
||||
/// <param name="shape">The <see cref="IExtrudeShape"/> template to
|
||||
/// be used as the shape template when extruding.</param>
|
||||
public ExtrudeSettings(int segments, bool capped, float2 range, float radius, T shape)
|
||||
{
|
||||
m_SegmentCount = math.clamp(segments, k_SegmentsMin, k_SegmentsMax);
|
||||
m_FlipNormals = false;
|
||||
m_Range = math.clamp(new float2(math.min(range.x, range.y), math.max(range.x, range.y)), 0f, 1f);
|
||||
m_CapEnds = capped;
|
||||
m_Radius = math.clamp(radius, k_RadiusMin, k_RadiusMax);
|
||||
m_Shape = shape;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static functions for extruding meshes along <see cref="Spline"/> paths.
|
||||
/// Use this class to build extruded meshes from code, or <see cref="SplineExtrude"/>
|
||||
/// for a pre-built Component that can be attached to any GameObject with
|
||||
/// a <see cref="SplineContainer"/>.
|
||||
/// </summary>
|
||||
public static class SplineMesh
|
||||
{
|
||||
const int k_SidesMin = 2, k_SidesMax = 2084;
|
||||
|
||||
static readonly VertexAttributeDescriptor[] k_PipeVertexAttribs = new VertexAttributeDescriptor[]
|
||||
{
|
||||
new (VertexAttribute.Position),
|
||||
new (VertexAttribute.Normal),
|
||||
new (VertexAttribute.TexCoord0, dimension: 2)
|
||||
};
|
||||
|
||||
static readonly Circle s_DefaultShape = new Circle();
|
||||
|
||||
internal static bool s_IsConvex;
|
||||
static bool s_IsConvexComputed;
|
||||
|
||||
/// <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<TSpline, TShape, TVertex>(
|
||||
TSpline spline,
|
||||
ExtrudeSettings<TShape> settings,
|
||||
int segment,
|
||||
NativeArray<TVertex> data,
|
||||
int start,
|
||||
bool uvsAreCaps = false)
|
||||
where TSpline : ISpline
|
||||
where TShape : IExtrudeShape
|
||||
where TVertex : struct, ISplineVertexData
|
||||
{
|
||||
TShape shape = settings.Shape;
|
||||
int sideCount = settings.sides;
|
||||
float radius = settings.Radius;
|
||||
bool wrap = settings.wrapped;
|
||||
float t = math.lerp(settings.Range.x, settings.Range.y, segment / (settings.SegmentCount - 1f));
|
||||
|
||||
var evaluationT = spline.Closed ? math.frac(t) : math.clamp(t, 0f, 1f);
|
||||
spline.Evaluate(evaluationT, out var sp, out var st, out var up);
|
||||
|
||||
var tangentLength = math.lengthsq(st);
|
||||
if (tangentLength == 0f || float.IsNaN(tangentLength))
|
||||
{
|
||||
var adjustedT = math.clamp(evaluationT + (0.0001f * (t < 1f ? 1f : -1f)), 0f, 1f);
|
||||
spline.Evaluate(adjustedT, out _, out st, out up);
|
||||
}
|
||||
|
||||
st = math.normalize(st);
|
||||
|
||||
var rot = quaternion.LookRotationSafe(st, up);
|
||||
shape.SetSegment(segment, t, sp, st, up);
|
||||
var flip = settings.FlipNormals;
|
||||
|
||||
for (int n = 0; n < sideCount; ++n)
|
||||
{
|
||||
var vertex = new TVertex();
|
||||
int index = flip ? sideCount - n - 1 : n;
|
||||
float v = index / (sideCount - 1f);
|
||||
var p = shape.GetPosition(v, index) * radius;
|
||||
vertex.position = sp + math.rotate(rot, new float3(p, 0f));
|
||||
vertex.normal = (vertex.position - (Vector3)sp).normalized * (flip ? -1f : 1f);
|
||||
|
||||
// first branch is a special case for wrapping uvs at the caps
|
||||
if (uvsAreCaps)
|
||||
{
|
||||
// the division by 2 is just a guess at a decent default for matching the uvs around the
|
||||
// circumference of extruded shapes. the more accurate solution would be calculate the actual
|
||||
// circumference and use that value to set cap scale.
|
||||
vertex.texture = (p.xy / radius) / 2;
|
||||
}
|
||||
else if (wrap)
|
||||
{
|
||||
// instead of inserting a vertex seam wrap UVs using a triangle wave so that
|
||||
// texture wraps back onto itself
|
||||
float ut = index / ((float)sideCount + sideCount % 2);
|
||||
float u = math.abs(ut - math.floor(ut + 0.5f)) * 2f;
|
||||
vertex.texture = new Vector2(1f - u, t * spline.GetLength());
|
||||
}
|
||||
else
|
||||
{
|
||||
vertex.texture = new Vector2(1 - index / (sideCount - 1f), t * spline.GetLength());
|
||||
}
|
||||
|
||||
data[start + n] = vertex;
|
||||
}
|
||||
|
||||
if (s_IsConvexComputed)
|
||||
return;
|
||||
|
||||
ComputeIsConvex(data, st, start, sideCount);
|
||||
}
|
||||
|
||||
static void ComputeIsConvex<TVertex>(
|
||||
NativeArray<TVertex> data,
|
||||
float3 normal,
|
||||
int start,
|
||||
int sideCount)
|
||||
where TVertex : struct, ISplineVertexData
|
||||
{
|
||||
s_IsConvexComputed = true;
|
||||
|
||||
bool isNegative = false;
|
||||
bool isPositive = false;
|
||||
|
||||
for (int n = 0; n < sideCount; ++n)
|
||||
{
|
||||
var indexA = start + n;
|
||||
var indexB = (indexA + 1) % (sideCount - 1);
|
||||
var indexC = (indexB + 1) % (sideCount - 1);
|
||||
|
||||
var vertexA = data[indexA].position;
|
||||
var vertexB = data[indexB].position;
|
||||
var vertexC = data[indexC].position;
|
||||
|
||||
var vectorAB = vertexB - vertexA;
|
||||
var vectorBC = vertexC - vertexB;
|
||||
|
||||
var cross = math.cross(vectorAB, vectorBC);
|
||||
var crossNormalized = math.normalizesafe(cross);
|
||||
var dot = math.dot(normal, crossNormalized);
|
||||
|
||||
if (dot < 0)
|
||||
isNegative = true;
|
||||
else if (dot > 0)
|
||||
isPositive = true;
|
||||
|
||||
if (isNegative && isPositive)
|
||||
{
|
||||
s_IsConvex = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s_IsConvex = true;
|
||||
}
|
||||
|
||||
/// <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="closeRing">Whether the extruded vertex ring is open (has a start and end point) or closed (forms an unbroken loop).</param>
|
||||
/// <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="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>
|
||||
/// <returns>Returns true if the computed vertex count exceeds 3 and the computed index count exceeds 5.</returns>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static bool GetVertexAndIndexCount(int sides, int segments, bool capped, bool closed, bool closeRing, out int vertexCount, out int indexCount)
|
||||
{
|
||||
vertexCount = sides * (segments + (capped ? 2 : 0));
|
||||
indexCount = (closeRing ? sides : sides - 1) * 6 * (segments - (closed ? 0 : 1)) + (capped ? (sides - 2) * 3 * 2 : 0);
|
||||
// make sure we at least have enough vertices and indices for a quad
|
||||
return vertexCount > 3 && indexCount > 5;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
GetVertexAndIndexCount(sides, segments, capped, closed, true, out vertexCount, out indexCount);
|
||||
}
|
||||
|
||||
static bool GetVertexAndIndexCount<T, K>(T spline, ExtrudeSettings<K> settings, out int vertexCount, out int indexCount)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
return GetVertexAndIndexCount(settings.sides,
|
||||
settings.SegmentCount,
|
||||
settings.DoCapEnds(spline),
|
||||
settings.DoCloseSpline(spline),
|
||||
settings.wrapped,
|
||||
out vertexCount, out indexCount);
|
||||
}
|
||||
|
||||
/// <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 with a customised 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="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="shape">The <see cref="IExtrudeShape"/> object defines the outline path of each segment.</param>
|
||||
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
||||
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
||||
public static void Extrude<T, K>(T spline, Mesh mesh, float radius, int segments, bool capped, K shape)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
Extrude(spline, mesh, radius, segments, capped, new float2(0f, 1f), shape);
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
s_DefaultShape.SideCount = sides;
|
||||
Extrude(spline, mesh, radius, segments, capped, range, s_DefaultShape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extrude a mesh along a spline following a shape template.
|
||||
/// </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="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>
|
||||
/// <param name="shape">The <see cref="IExtrudeShape"/> object defines the outline path of each segment.</param>
|
||||
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
||||
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
||||
public static void Extrude<T, K>(T spline, Mesh mesh, float radius, int segments, bool capped, float2 range,
|
||||
K shape)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
var settings = new ExtrudeSettings<K>()
|
||||
{
|
||||
Radius = radius,
|
||||
CapEnds = capped,
|
||||
Range = range,
|
||||
SegmentCount = segments,
|
||||
Shape = shape
|
||||
};
|
||||
|
||||
Extrude(spline, mesh, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extrude a mesh along a spline following a shape template.
|
||||
/// </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="settings">The <see cref="ExtrudeSettings{T}"/> parameters used when creating mesh geometry.</param>
|
||||
/// <typeparam name="T">A type implementing ISpline.</typeparam>
|
||||
/// <typeparam name="K">A type implementing <see cref="IExtrudeShape"/>.</typeparam>
|
||||
/// <returns>Returns true if mesh was created, or false if the settings configuration resulted in an invalid state (ex, too few vertices).</returns>
|
||||
public static bool Extrude<T, K>(T spline, Mesh mesh, ExtrudeSettings<K> settings)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
if (!GetVertexAndIndexCount(spline, settings, out var vertexCount, out var indexCount))
|
||||
return false;
|
||||
|
||||
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, settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
var indices = data.GetIndexData<UInt32>();
|
||||
Extrude(spline, vertices, indices, settings);
|
||||
}
|
||||
|
||||
mesh.Clear();
|
||||
data.subMeshCount = 1;
|
||||
data.SetSubMesh(0, new SubMeshDescriptor(0, indexCount));
|
||||
Mesh.ApplyAndDisposeWritableMeshData(meshDataArray, mesh);
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extrude a mesh along a list of splines in a tube-like shape.
|
||||
/// </summary>
|
||||
/// <param name="splines">The splines 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="segmentsPerUnit">The number of edge loops that comprise the length of one unit 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>(IReadOnlyList<T> splines, Mesh mesh, float radius, int sides, float segmentsPerUnit, bool capped, float2 range) where T : ISpline
|
||||
{
|
||||
s_DefaultShape.SideCount = sides;
|
||||
|
||||
var settings = new ExtrudeSettings<Circle>(s_DefaultShape)
|
||||
{
|
||||
Radius = radius,
|
||||
SegmentCount = (int) segmentsPerUnit,
|
||||
CapEnds = capped,
|
||||
Range = range
|
||||
};
|
||||
|
||||
Extrude(splines, mesh, settings, segmentsPerUnit);
|
||||
}
|
||||
|
||||
// this is not public for good reason. it mutates the settings object by necessity, to preserve the behaviour
|
||||
// of `segmentsPerUnit` rather than use the `Settings.SegmentCount` property.
|
||||
internal static void Extrude<T, K>(IReadOnlyList<T> splines, Mesh mesh, ExtrudeSettings<K> settings, float segmentsPerUnit)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
mesh.Clear();
|
||||
if (splines == null)
|
||||
{
|
||||
if(Application.isPlaying)
|
||||
Debug.LogError("Trying to extrude a spline mesh with no valid splines.");
|
||||
return;
|
||||
}
|
||||
|
||||
var meshDataArray = Mesh.AllocateWritableMeshData(1);
|
||||
var data = meshDataArray[0];
|
||||
data.subMeshCount = 1;
|
||||
|
||||
var totalVertexCount = 0;
|
||||
var totalIndexCount = 0;
|
||||
var splineMeshOffsets = new (int indexStart, int vertexStart)[splines.Count];
|
||||
|
||||
int GetSegmentCount(T spline)
|
||||
{
|
||||
var span = Mathf.Abs(settings.Range.y - settings.Range.x);
|
||||
return Mathf.Max((int)Mathf.Ceil(spline.GetLength() * span * segmentsPerUnit), 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < splines.Count; ++i)
|
||||
{
|
||||
if(splines[i].Count < 2)
|
||||
continue;
|
||||
|
||||
settings.SegmentCount = GetSegmentCount(splines[i]);
|
||||
GetVertexAndIndexCount(splines[i], settings, out int vertexCount, out int indexCount);
|
||||
splineMeshOffsets[i] = (totalIndexCount, totalVertexCount);
|
||||
totalVertexCount += vertexCount;
|
||||
totalIndexCount += indexCount;
|
||||
}
|
||||
|
||||
var indexFormat = totalVertexCount >= ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16;
|
||||
|
||||
data.SetIndexBufferParams(totalIndexCount, indexFormat);
|
||||
data.SetVertexBufferParams(totalVertexCount, k_PipeVertexAttribs);
|
||||
|
||||
var vertices = data.GetVertexData<VertexData>();
|
||||
if (indexFormat == IndexFormat.UInt16)
|
||||
{
|
||||
var indices = data.GetIndexData<UInt16>();
|
||||
for (int i = 0; i < splines.Count; ++i)
|
||||
{
|
||||
if (splines[i].Count < 2)
|
||||
continue;
|
||||
settings.SegmentCount = GetSegmentCount(splines[i]);
|
||||
Extrude(splines[i], vertices, indices, settings, splineMeshOffsets[i].vertexStart, splineMeshOffsets[i].indexStart);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var indices = data.GetIndexData<UInt32>();
|
||||
for (int i = 0; i < splines.Count; ++i)
|
||||
{
|
||||
if (splines[i].Count < 2)
|
||||
continue;
|
||||
settings.SegmentCount = GetSegmentCount(splines[i]);
|
||||
Extrude(splines[i], vertices, indices, settings, splineMeshOffsets[i].vertexStart, splineMeshOffsets[i].indexStart);
|
||||
}
|
||||
}
|
||||
|
||||
data.SetSubMesh(0, new SubMeshDescriptor(0, totalIndexCount));
|
||||
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
|
||||
{
|
||||
s_DefaultShape.SideCount = math.clamp(sides, k_SidesMin, k_SidesMax);
|
||||
Extrude(spline, vertices, indices, new ExtrudeSettings<Circle>(segments, capped, range, radius, s_DefaultShape));
|
||||
}
|
||||
|
||||
static void Extrude<TSplineType, TVertexType, TIndexType, TShapeType>(
|
||||
TSplineType spline,
|
||||
NativeArray<TVertexType> vertices,
|
||||
NativeArray<TIndexType> indices,
|
||||
ExtrudeSettings<TShapeType> settings,
|
||||
int vertexArrayOffset = 0,
|
||||
int indicesArrayOffset = 0)
|
||||
where TSplineType : ISpline
|
||||
where TVertexType : struct, ISplineVertexData
|
||||
where TIndexType : struct
|
||||
where TShapeType : IExtrudeShape
|
||||
{
|
||||
var sides = settings.sides;
|
||||
var segments = settings.SegmentCount;
|
||||
var range = settings.Range;
|
||||
var capped = settings.DoCapEnds(spline);
|
||||
|
||||
if (!GetVertexAndIndexCount(spline, settings, out var vertexCount, out var indexCount))
|
||||
return;
|
||||
|
||||
if (settings.Shape == null)
|
||||
throw new ArgumentNullException(nameof(settings.Shape), "Shape template is null.");
|
||||
|
||||
if (sides < 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(sides), "Sides must be greater than 2");
|
||||
|
||||
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} or more, but received {vertices.Length}.");
|
||||
|
||||
if (indices.Length < indexCount)
|
||||
throw new ArgumentOutOfRangeException($"Index array is incorrect size. Expected {indexCount} or more, but received {indices.Length}.");
|
||||
|
||||
if (typeof(TIndexType) == typeof(UInt16))
|
||||
{
|
||||
var ushortIndices = indices.Reinterpret<UInt16>();
|
||||
WindTris(ushortIndices, spline, settings, vertexArrayOffset, indicesArrayOffset);
|
||||
}
|
||||
else if (typeof(TIndexType) == typeof(UInt32))
|
||||
{
|
||||
var ulongIndices = indices.Reinterpret<UInt32>();
|
||||
WindTris(ulongIndices, spline, settings, vertexArrayOffset, indicesArrayOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Indices must be UInt16 or UInt32", nameof(indices));
|
||||
}
|
||||
|
||||
var shape = settings.Shape;
|
||||
|
||||
shape.Setup(spline, segments);
|
||||
|
||||
s_IsConvexComputed = false;
|
||||
|
||||
for (int i = 0; i < segments; ++i)
|
||||
ExtrudeRing(spline, settings, i, vertices, vertexArrayOffset + i * sides);
|
||||
|
||||
if (capped)
|
||||
{
|
||||
var capVertexStart = vertexArrayOffset + segments * sides;
|
||||
var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides;
|
||||
|
||||
var rng = spline.Closed ? math.frac(range) : math.clamp(range, 0f, 1f);
|
||||
ExtrudeRing(spline, settings, 0, vertices, capVertexStart, true);
|
||||
ExtrudeRing(spline, settings, segments-1, vertices, endCapVertexStart, true);
|
||||
|
||||
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));
|
||||
|
||||
for (int i = 0; i < sides; ++i)
|
||||
{
|
||||
var v0 = vertices[capVertexStart + i];
|
||||
var v1 = vertices[endCapVertexStart + i];
|
||||
|
||||
v0.normal = -beginAccel;
|
||||
v1.normal = endAccel;
|
||||
|
||||
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<T, K>(NativeArray<UInt16> indices, T spline, ExtrudeSettings<K> settings, int vertexArrayOffset = 0, int indexArrayOffset = 0)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
var closed = settings.DoCloseSpline(spline);
|
||||
var segments = settings.SegmentCount;
|
||||
var sides = settings.sides;
|
||||
var wrap = settings.wrapped;
|
||||
var capped = settings.DoCapEnds(spline);
|
||||
var sideFaceCount = wrap ? sides : sides - 1;
|
||||
|
||||
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
|
||||
{
|
||||
for (int n = 0; n < (wrap ? sides : sides - 1); ++n)
|
||||
{
|
||||
var index0 = vertexArrayOffset + i * sides + n;
|
||||
var index1 = vertexArrayOffset + i * sides + ((n + 1) % sides);
|
||||
var index2 = vertexArrayOffset + ((i+1) % segments) * sides + n;
|
||||
var index3 = vertexArrayOffset + ((i+1) % segments) * sides + ((n + 1) % sides);
|
||||
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 0] = (UInt16) index0;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 1] = (UInt16) index1;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 2] = (UInt16) index2;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 3] = (UInt16) index1;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 4] = (UInt16) index3;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 5] = (UInt16) index2;
|
||||
}
|
||||
}
|
||||
|
||||
if (capped)
|
||||
{
|
||||
var capVertexStart = vertexArrayOffset + segments * sides;
|
||||
var capIndexStart = indexArrayOffset + sideFaceCount * 6 * (segments-1);
|
||||
var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides;
|
||||
var endCapIndexStart = indexArrayOffset + (segments-1) * 6 * sideFaceCount + (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<T, K>(NativeArray<UInt32> indices, T spline, ExtrudeSettings<K> settings, int vertexArrayOffset = 0, int indexArrayOffset = 0)
|
||||
where T : ISpline
|
||||
where K : IExtrudeShape
|
||||
{
|
||||
var closed = settings.DoCloseSpline(spline);
|
||||
var segments = settings.SegmentCount;
|
||||
var sides = settings.sides;
|
||||
var wrap = settings.wrapped;
|
||||
var capped = settings.DoCapEnds(spline);
|
||||
var sideFaceCount = wrap ? sides : sides - 1;
|
||||
|
||||
for (int i = 0; i < (closed ? segments : segments - 1); ++i)
|
||||
{
|
||||
for (int n = 0; n < (wrap ? sides : sides - 1); ++n)
|
||||
{
|
||||
var index0 = vertexArrayOffset + i * sides + n;
|
||||
var index1 = vertexArrayOffset + i * sides + ((n + 1) % sides);
|
||||
var index2 = vertexArrayOffset + ((i+1) % segments) * sides + n;
|
||||
var index3 = vertexArrayOffset + ((i+1) % segments) * sides + ((n + 1) % sides);
|
||||
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 0] = (UInt16) index0;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 1] = (UInt16) index1;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 2] = (UInt16) index2;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 3] = (UInt16) index1;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 4] = (UInt16) index3;
|
||||
indices[indexArrayOffset + i * sideFaceCount * 6 + n * 6 + 5] = (UInt16) index2;
|
||||
}
|
||||
}
|
||||
|
||||
if (capped)
|
||||
{
|
||||
var capVertexStart = vertexArrayOffset + segments * sides;
|
||||
var capIndexStart = indexArrayOffset + sideFaceCount * 6 * (segments-1);
|
||||
var endCapVertexStart = vertexArrayOffset + (segments + 1) * sides;
|
||||
var endCapIndexStart = indexArrayOffset + (segments-1) * 6 * sideFaceCount + (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e824b25245647358e471f403288f20d
|
||||
timeCreated: 1637607905
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the different types of changes that can occur to a spline.
|
||||
/// </summary>
|
||||
public enum SplineModification
|
||||
{
|
||||
/// <summary>
|
||||
/// The default modification type. This is used when no other SplineModification types apply.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// The spline's <see cref="Spline.Closed"/> property was modified.
|
||||
/// </summary>
|
||||
ClosedModified,
|
||||
|
||||
/// <summary>
|
||||
/// A knot was modified.
|
||||
/// </summary>
|
||||
KnotModified,
|
||||
|
||||
/// <summary>
|
||||
/// A knot was inserted.
|
||||
/// </summary>
|
||||
KnotInserted,
|
||||
|
||||
/// <summary>
|
||||
/// A knot was removed.
|
||||
/// </summary>
|
||||
KnotRemoved,
|
||||
|
||||
/// <summary>
|
||||
/// A knot was reordered.
|
||||
/// </summary>
|
||||
KnotReordered
|
||||
}
|
||||
|
||||
struct SplineModificationData
|
||||
{
|
||||
public readonly Spline @Spline;
|
||||
public readonly SplineModification Modification;
|
||||
public readonly int KnotIndex;
|
||||
// Length of curve before the edited knot (if insert then length of the curve inserted into).
|
||||
public readonly float PrevCurveLength;
|
||||
// Length of the edited knot's curve (has no meaning if modification is insert).
|
||||
public readonly float NextCurveLength;
|
||||
|
||||
public SplineModificationData(Spline spline, SplineModification modification, int knotIndex, float prevCurveLength, float nextCurveLength)
|
||||
{
|
||||
Spline = spline;
|
||||
Modification = modification;
|
||||
KnotIndex = knotIndex;
|
||||
PrevCurveLength = prevCurveLength;
|
||||
NextCurveLength = nextCurveLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00a29581dcde43288acba10562a4dc63
|
||||
timeCreated: 1674236045
|
||||
@@ -0,0 +1,342 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
// IMPORTANT:
|
||||
// SplinePathRef is the serializable equivalent of SplinePath. It is intentionally not public due to
|
||||
// questions around tooling and user interface. Left here because it is used for tests and debugging.
|
||||
// ...and if it needs to be stated, the name is terrible and should obviously be replaced before becoming public.
|
||||
[Serializable]
|
||||
class SplinePathRef
|
||||
{
|
||||
/// <summary>
|
||||
/// SliceRef represents a partial or complete range of curves from another <see cref="Spline"/>. This is a
|
||||
/// serializable type, intended to be used with <see cref="SplinePathRef"/>. To create an evaluable
|
||||
/// <see cref="ISpline"/> from a <see cref="Spline"/> and <see cref="SplineRange"/>, use <see cref="SplineSlice{T}"/>.
|
||||
/// A <see cref="SliceRef"/> by itself does not store any <see cref="BezierKnot"/>s. It stores a reference to
|
||||
/// a separate <see cref="Spline"/> index within a <see cref="SplineContainer"/>, then retrieves knots by iterating
|
||||
/// the <see cref="SplineRange"/>.
|
||||
/// Use <see cref="SliceRef"/> in conjunction with <see cref="SplinePathRef"/> to create seamless paths from
|
||||
/// discrete <see cref="Spline"/> segments.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SliceRef
|
||||
{
|
||||
/// <summary>
|
||||
/// The index in the <see cref="SplineContainer.Branches"/> array of the referenced <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public int Index;
|
||||
|
||||
/// <summary>
|
||||
/// An inclusive start index, number of indices, and direction to iterate.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public SplineRange Range;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a new <see cref="SliceRef"/>.
|
||||
/// </summary>
|
||||
/// <param name="splineIndex">The index in the <see cref="SplineContainer.Branches"/> array of the referenced <see cref="Spline"/>.</param>
|
||||
/// <param name="range">An inclusive start index, number of indices, and direction to iterate.</param>
|
||||
public SliceRef(int splineIndex, SplineRange range)
|
||||
{
|
||||
Index = splineIndex;
|
||||
Range = range;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public SliceRef[] Splines;
|
||||
|
||||
public SplinePathRef()
|
||||
{
|
||||
}
|
||||
|
||||
public SplinePathRef(IEnumerable<SliceRef> slices)
|
||||
{
|
||||
Splines = slices.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SplinePath type is an implementation of <see cref="ISpline"/> that is composed of multiple sections of
|
||||
/// other splines (see <see cref="SplineSlice{T}"/>). This is useful when you want to evaluate a path that follows
|
||||
/// multiple splines, typically in the case where splines share linked knots.
|
||||
///
|
||||
/// This class is a data structure that defines the range of curves to associate together. This class is not meant to be
|
||||
/// used intensively for runtime evaluation because it is not performant. Data is not meant to be
|
||||
/// stored in that struct and that struct is not reactive to spline changes. The GameObject that contains this
|
||||
/// slice can be scaled and the knots of the targeted spline that can moved around the curve length cannot be stored
|
||||
/// here so evaluating positions, tangents and up vectors is expensive.
|
||||
///
|
||||
/// If performance is a critical requirement, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> from your <see cref="SplinePath{T}"/>. Note that you might pass a <see cref="SplinePath{T}"/>
|
||||
/// to constructors for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineRange"/>
|
||||
/// <seealso cref="KnotLinkCollection"/>
|
||||
/// <seealso cref="SplineKnotIndex"/>
|
||||
public class SplinePath : SplinePath<SplineSlice<Spline>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SplinePath"/> from a collection of <see cref="SplineSlice{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="slices">The splines to create this path with.</param>
|
||||
public SplinePath(IEnumerable<SplineSlice<Spline>> slices) : base(slices)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The SplinePath type is an implementation of <see cref="ISpline"/> that is composed of multiple sections of
|
||||
/// other splines (see <see cref="SplineSlice{T}"/>). This is useful when you want to evaluate a path that follows
|
||||
/// multiple splines, typically in the case where splines share linked knots.
|
||||
///
|
||||
/// If performance is a critical requirement, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> from your <see cref="SplinePath{T}"/>. Note that you might pass a <see cref="SplinePath{T}"/>
|
||||
/// to constructors for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineRange"/>
|
||||
/// <seealso cref="KnotLinkCollection"/>
|
||||
/// <seealso cref="SplineKnotIndex"/>
|
||||
/// <typeparam name="T">The type of spline to create a path with.</typeparam>
|
||||
public class SplinePath<T> : ISpline, IHasEmptyCurves where T : ISpline
|
||||
{
|
||||
T[] m_Splines;
|
||||
|
||||
int[] m_Splits;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISpline"/> splines that make up this path.
|
||||
/// </summary>
|
||||
public IReadOnlyList<T> Slices
|
||||
{
|
||||
get => m_Splines;
|
||||
|
||||
set
|
||||
{
|
||||
m_Splines = value.ToArray();
|
||||
BuildSplitData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SplinePath{T}"/> from a collection of <see cref="ISpline"/>.
|
||||
/// </summary>
|
||||
/// <param name="slices">A collection of <see cref="ISpline"/>.</param>
|
||||
public SplinePath(IEnumerable<T> slices)
|
||||
{
|
||||
m_Splines = slices.ToArray();
|
||||
BuildSplitData();
|
||||
}
|
||||
|
||||
void BuildSplitData()
|
||||
{
|
||||
m_Splits = new int[m_Splines.Length];
|
||||
for (int i = 0, c = m_Splits.Length, k = 0; i < c; ++i)
|
||||
m_Splits[i] = (k += (m_Splines[i].Count + (m_Splines[i].Closed ? 1 : 0))) - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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()
|
||||
{
|
||||
foreach (var branch in m_Splines)
|
||||
foreach (var knot in branch)
|
||||
yield return knot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 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>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of knots.
|
||||
/// Note that there are duplicate knots where two <see cref="ISpline"/> meet.
|
||||
/// In addition, each closed <see cref="ISpline"/> have their first knot duplicated.
|
||||
/// Use <see cref="GetCurve"/> to access curves rather than construct the curve yourself.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var spline in m_Splines)
|
||||
count += (spline.Count + (spline.Closed ? 1 : 0));
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the knot at <paramref name="index"/>. If the <see cref="ISpline"/> section that contains this
|
||||
/// knot has a <see cref="SplineRange"/> with <see cref="SliceDirection.Backward"/>, the in and out tangents
|
||||
/// are reversed.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
public BezierKnot this[int index] => this[GetBranchKnotIndex(index)];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the knot at <paramref name="index"/>. If the <see cref="ISpline"/> segment that contains this
|
||||
/// knot has a <see cref="SplineRange"/> with <see cref="SliceDirection.Backward"/>, the in and out tangents
|
||||
/// are reversed.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the slice and knot to get.</param>
|
||||
public BezierKnot this[SplineKnotIndex index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var spline = m_Splines[index.Spline];
|
||||
var knotIndex = spline.Closed ? index.Knot % spline.Count : index.Knot;
|
||||
return spline[knotIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// used by tests
|
||||
internal SplineKnotIndex GetBranchKnotIndex(int knot)
|
||||
{
|
||||
knot = Closed ? knot % Count : math.clamp(knot, 0, Count);
|
||||
|
||||
for (int i = 0, offset = 0; i < m_Splines.Length; i++)
|
||||
{
|
||||
var slice = m_Splines[i];
|
||||
var sliceCount = slice.Count + (slice.Closed ? 1 : 0);
|
||||
if (knot < offset + sliceCount)
|
||||
return new SplineKnotIndex(i, math.max(0, knot - offset));
|
||||
offset += sliceCount;
|
||||
}
|
||||
|
||||
return new SplineKnotIndex(m_Splines.Length - 1, m_Splines[^1].Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SplinePathRef"/> does not support Closed splines.
|
||||
/// </summary>
|
||||
public bool Closed => false;
|
||||
|
||||
/// <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>
|
||||
public float GetLength()
|
||||
{
|
||||
var length = 0f;
|
||||
for (int i = 0, c = Closed ? Count : Count - 1; i < c; ++i)
|
||||
length += GetCurveLength(i);
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of knot indices that should be considered degenerate curves for the purpose of creating a
|
||||
/// non-interpolated gap between curves.
|
||||
/// </summary>
|
||||
public IReadOnlyList<int> EmptyCurves => m_Splits;
|
||||
|
||||
bool IsDegenerate(int index)
|
||||
{
|
||||
// because splits are set up by this class, we know that indices are sorted
|
||||
int split = Array.BinarySearch(m_Splits, index);
|
||||
if (split < 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="BezierCurve"/> from a knot index. This function returns
|
||||
/// degenerate (0 length) curves at the overlap points between each <see cref="ISpline"/>.
|
||||
/// </summary>
|
||||
/// <param name="knot">The knot index that is 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 knot)
|
||||
{
|
||||
var index = GetBranchKnotIndex(knot);
|
||||
|
||||
if (IsDegenerate(knot))
|
||||
{
|
||||
var point = new BezierKnot(this[index].Position);
|
||||
return new BezierCurve(point, point);
|
||||
}
|
||||
|
||||
BezierKnot a = this[index], b = this.Next(knot);
|
||||
return new BezierCurve(a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the length of a curve. This function returns 0 length for knot indices where
|
||||
/// <see cref="ISpline"/> segments overlap.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the curve that the length is retrieved from.</param>
|
||||
/// <seealso cref="GetLength"/>
|
||||
/// <returns>
|
||||
/// Returns the length of the curve of index 'index' in the spline.
|
||||
/// </returns>
|
||||
public float GetCurveLength(int index)
|
||||
{
|
||||
if(IsDegenerate(index))
|
||||
return 0f;
|
||||
var knot = GetBranchKnotIndex(index);
|
||||
var slice = m_Splines[knot.Spline];
|
||||
if (knot.Spline >= m_Splines.Length - 1 && knot.Knot >= slice.Count - 1)
|
||||
return CurveUtility.CalculateLength(GetCurve(index));
|
||||
return slice.GetCurveLength(knot.Knot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the up vector for a t ratio on the curve.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the curve for which the length needs to be retrieved.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
|
||||
/// <returns>
|
||||
/// Returns the up vector at the t ratio of the curve of index 'index'.
|
||||
/// </returns>
|
||||
public float3 GetCurveUpVector(int index, float t)
|
||||
{
|
||||
if(IsDegenerate(index))
|
||||
return 0f;
|
||||
|
||||
var knot = GetBranchKnotIndex(index);
|
||||
var slice = m_Splines[knot.Spline];
|
||||
|
||||
// Closing curve
|
||||
if (knot.Spline >= m_Splines.Length - 1 && knot.Knot >= slice.Count - 1)
|
||||
{
|
||||
BezierKnot a = this[knot], b = this.Next(index);
|
||||
var curve = new BezierCurve(a, b);
|
||||
|
||||
var curveStartUp = math.rotate(a.Rotation, math.up());
|
||||
var curveEndUp = math.rotate(b.Rotation, math.up());
|
||||
|
||||
return CurveUtility.EvaluateUpVector(curve, t, curveStartUp, curveEndUp);
|
||||
}
|
||||
|
||||
return slice.GetCurveUpVector(knot.Knot, t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the interpolation ratio (0 to 1) that corresponds to a distance on a <see cref="BezierCurve"/>. The
|
||||
/// distance is relative to the curve.
|
||||
/// </summary>
|
||||
/// <param name="curveIndex"> The zero-based index of the curve.</param>
|
||||
/// <param name="curveDistance"> The distance measured from the knot at curveIndex to convert to a normalized interpolation ratio.</param>
|
||||
/// <returns>The normalized interpolation ratio that matches the distance on the designated curve. </returns>
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance)
|
||||
{
|
||||
var knot = GetBranchKnotIndex(curveIndex);
|
||||
var slice = m_Splines[knot.Spline];
|
||||
return slice.GetCurveInterpolation(knot.Knot, curveDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 107f80db9cc8434c94d28d818be09f54
|
||||
timeCreated: 1649859491
|
||||
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the direction that a <see cref="SplineRange"/> interpolates. Use <see cref="SplineSlice{T}"/> and
|
||||
/// <see cref="SplineRange"/> to create paths that interpolate along a <see cref="Spline"/> in either a forward
|
||||
/// or backward direction.
|
||||
/// </summary>
|
||||
/// <seealso cref="SplineSlice{T}"/>
|
||||
/// <seealso cref="SplineRange"/>
|
||||
public enum SliceDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="SplineSlice{T}"/> interpolates along the direction of the referenced spline.
|
||||
/// </summary>
|
||||
Forward,
|
||||
/// <summary>
|
||||
/// The <see cref="SplineSlice{T}"/> interpolates in the reverse direction of the referenced spline.
|
||||
/// </summary>
|
||||
Backward
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a subset of knot indices in a <see cref="Spline"/>. The range might iterate in either the
|
||||
/// forward or backward direction.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct SplineRange : IEnumerable<int>
|
||||
{
|
||||
[SerializeField]
|
||||
int m_Start;
|
||||
|
||||
[SerializeField]
|
||||
int m_Count;
|
||||
|
||||
[SerializeField]
|
||||
SliceDirection m_Direction;
|
||||
|
||||
/// <summary>
|
||||
/// The inclusive first index, starting at 0.
|
||||
/// </summary>
|
||||
public int Start
|
||||
{
|
||||
get => m_Start;
|
||||
set => m_Start = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The inclusive end index of this range.
|
||||
/// </summary>
|
||||
public int End => this[Count - 1];
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of indices.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get => m_Count;
|
||||
set => m_Count = math.max(value, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction that this range interpolates. <see cref="SliceDirection.Forward"/> increments
|
||||
/// the knot index when it iterates, whereas <see cref="SliceDirection.Backward"/> decrements this index.
|
||||
/// </summary>
|
||||
public SliceDirection Direction
|
||||
{
|
||||
get => m_Direction;
|
||||
set => m_Direction = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SplineRange"/> from a start index and count.
|
||||
/// </summary>
|
||||
/// <param name="start">The inclusive first index of a range.</param>
|
||||
/// <param name="count">The number of elements this range encompasses. This value might be negative,
|
||||
/// which is shorthand to call the constructor with an explicit <see cref="SliceDirection"/> parameter.
|
||||
/// </param>
|
||||
public SplineRange(int start, int count) : this(start, count,
|
||||
count < 0 ? SliceDirection.Backward : SliceDirection.Forward)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SplineRange"/> from a start index and count.
|
||||
/// </summary>
|
||||
/// <param name="start">The inclusive first index of a range.</param>
|
||||
/// <param name="count">The number of elements this range encompasses.</param>
|
||||
/// <param name="direction">Whether when iterating this range it is incrementing from start to start + count, or
|
||||
/// decrementing from start to start - count.
|
||||
/// </param>
|
||||
public SplineRange(int start, int count, SliceDirection direction)
|
||||
{
|
||||
m_Start = start;
|
||||
m_Count = math.abs(count);
|
||||
m_Direction = direction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the <see cref="Spline"/> knot index at an index <paramref name="index"/>.
|
||||
/// This indexer allows you to write a for loop to iterate through a range without needing to know in which
|
||||
/// direction the range is iterating.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Create a new range into an existing Spline starting at knot 5, and interpolating the span of 3 knots.
|
||||
/// // range[0,1,2] will map to { 6, 5, 4 } respectively.
|
||||
/// var range = new SplineRange(6, 3, SplineDirection.Backward);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
public int this[int index] => Direction == SliceDirection.Backward ? m_Start - index : m_Start + index;
|
||||
|
||||
/// <summary>
|
||||
/// Get an enumerator that iterates through the index collection. Note that this will either increment or
|
||||
/// decrement indices depending on the value of the <see cref="Direction"/> property.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerator that is used to iterate the collection.</returns>
|
||||
public IEnumerator<int> GetEnumerator() => new SplineRangeEnumerator(this);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator that iterates through the index collection. It either increments or
|
||||
/// decrements indices depending on the value of the <see cref="Direction"/> property.
|
||||
/// </summary>
|
||||
/// <returns>Returns an IEnumerator that is used to iterate the collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// A struct for iterating a <see cref="SplineRange"/>.
|
||||
/// </summary>
|
||||
public struct SplineRangeEnumerator : IEnumerator<int>
|
||||
{
|
||||
int m_Index, m_Start, m_End, m_Count;
|
||||
bool m_Reverse;
|
||||
|
||||
/// <summary>
|
||||
/// Advances the enumerator to the next element of the collection.
|
||||
/// </summary>
|
||||
/// <returns>Returns <c>true</c> if the enumerator was successfully advanced to the next element;
|
||||
/// <c>false</c> if the enumerator has passed the end of the collection.</returns>
|
||||
public bool MoveNext() => ++m_Index < m_Count;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element in the collection.
|
||||
/// </summary>
|
||||
public void Reset() => m_Index = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public int Current => m_Reverse ? m_End - m_Index : m_Start + m_Index;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for an IEnumerator of a <see cref="SplineRange"/>.
|
||||
/// </summary>
|
||||
/// <param name="range">The <see cref="SplineRange"/> to iterate.</param>
|
||||
public SplineRangeEnumerator(SplineRange range)
|
||||
{
|
||||
m_Index = -1;
|
||||
m_Reverse = range.Direction == SliceDirection.Backward;
|
||||
int a = range.Start,
|
||||
b = m_Reverse ? range.Start - range.Count : range.Start + range.Count;
|
||||
m_Start = math.min(a, b);
|
||||
m_End = math.max(a, b);
|
||||
m_Count = range.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IDisposable implementation. SplineSliceEnumerator does not allocate any resources.
|
||||
/// </summary>
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string summary of this range.
|
||||
/// </summary>
|
||||
/// <returns>Returns a string summary of this range.</returns>
|
||||
public override string ToString() => $"{{{Start}..{End}}}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2fffbd3edfc4d078419c7771be03764
|
||||
timeCreated: 1649859531
|
||||
@@ -0,0 +1,132 @@
|
||||
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:
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>#include "Packages/com.unity.splines/Shader/Spline.cginc"</code>
|
||||
/// </example>
|
||||
/// <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,234 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// SplineSlice represents a partial or complete range of curves from another <see cref="Spline"/>.
|
||||
/// A <see cref="SplineSlice{T}"/> by itself does not store any <see cref="BezierKnot"/>s. It stores a reference to
|
||||
/// a separate <see cref="Spline"/>, then retrieves knots by iterating the <see cref="SplineRange"/>.
|
||||
/// Use <see cref="SplineSlice{T}"/> in conjunction with <see cref="SplinePath"/> to create seamless paths from
|
||||
/// discrete <see cref="Spline"/> segments.
|
||||
///
|
||||
/// This class is a data structure that defines the range of curves to associate together. This class is not meant to be
|
||||
/// used intensively for runtime evaluation because it is not performant. Data is not meant to be
|
||||
/// stored in that struct and that struct is not reactive to spline changes. The GameObject that contains this
|
||||
/// slice can be scaled and the knots of the targeted spline that can moved around the curve length cannot be stored
|
||||
/// here so evaluating positions, tangents and up vectors is expensive.
|
||||
///
|
||||
/// If performance is a critical requirement, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> from the relevant <see cref="SplinePath{T}"/> or <see cref="SplineSlice{T}"/>.
|
||||
/// Note that you might pass a <see cref="SplineSlice{T}"/> to constructors for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Iterating a <see cref="SplineSlice{T}"/> is not as efficient as iterating a <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> because it does not cache any information. Where performance is a concern, create
|
||||
/// a new <see cref="Spline"/> or <see cref="NativeSpline"/> from the <see cref="SplineSlice{T}"/>.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of spline that this slice represents.</typeparam>
|
||||
public struct SplineSlice<T> : ISpline where T : ISpline
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Spline"/> that this Slice will read <see cref="BezierKnot"/> and <see cref="BezierCurve"/>
|
||||
/// data from.
|
||||
/// A <see cref="SplineSlice{T}"/> by itself does not store any <see cref="BezierKnot"/>s. Instead, it references
|
||||
/// a partial or complete range of existing <see cref="Spline"/>s.
|
||||
/// </summary>
|
||||
public T Spline;
|
||||
|
||||
/// <summary>
|
||||
/// An inclusive start index, number of indices, and direction to iterate.
|
||||
/// </summary>
|
||||
public SplineRange Range;
|
||||
|
||||
/// <summary>
|
||||
/// A transform matrix to be applied to the spline knots and tangents.
|
||||
/// </summary>
|
||||
public float4x4 Transform;
|
||||
|
||||
/// <summary>
|
||||
/// Return the number of knots in this branch. This function clamps the <see cref="Range"/> to the Count of the
|
||||
/// the referenced <see cref="Spline"/>.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Spline.Closed)
|
||||
return math.clamp(Range.Count, 0, Spline.Count + 1);
|
||||
|
||||
if (Range.Direction == SliceDirection.Backward)
|
||||
return math.clamp(Range.Count, 0, Range.Start + 1);
|
||||
else
|
||||
return math.clamp(Range.Count, 0, Spline.Count - Range.Start);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop).
|
||||
/// </summary>
|
||||
public bool Closed => false;
|
||||
|
||||
static BezierKnot FlipTangents(BezierKnot knot) =>
|
||||
new BezierKnot(knot.Position, knot.TangentOut, knot.TangentIn, knot.Rotation);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="BezierKnot"/> at the zero-based index of this <see cref="SplineSlice{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index to get.</param>
|
||||
public BezierKnot this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
int indexFromRange = Range[index];
|
||||
indexFromRange = (indexFromRange + Spline.Count) % Spline.Count;
|
||||
|
||||
return Range.Direction == SliceDirection.Backward
|
||||
? FlipTangents(Spline[indexFromRange]).Transform(Transform)
|
||||
: Spline[indexFromRange].Transform(Transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an enumerator that iterates through the <see cref="BezierKnot"/> collection. Note that this will either
|
||||
/// increment or decrement indices depending on the value of the <see cref="SplineRange.Direction"/>.
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerator that is used to iterate the <see cref="BezierKnot"/> collection.</returns>
|
||||
public IEnumerator<BezierKnot> GetEnumerator()
|
||||
{
|
||||
for (int i = 0, c = Range.Count; i < c; ++i)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator that iterates through the <see cref="BezierKnot"/> collection. It either
|
||||
/// increments or decrements indices depending on the value of the <see cref="SplineRange.Direction"/>.
|
||||
/// </summary>
|
||||
/// <returns>Returns an IEnumerator that is used to iterate the <see cref="BezierKnot"/> collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a new SplineSlice.
|
||||
/// </summary>
|
||||
/// <param name="spline">
|
||||
/// The <see cref="Spline"/> that this Slice will read <see cref="BezierKnot"/> and <see cref="BezierCurve"/>
|
||||
/// data from.
|
||||
/// </param>
|
||||
/// <param name="range">The start index and count of knot indices that compose this slice.</param>
|
||||
public SplineSlice(T spline, SplineRange range)
|
||||
: this(spline, range, float4x4.identity)
|
||||
{}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for a new SplineSlice.
|
||||
/// </summary>
|
||||
/// <param name="spline">
|
||||
/// The <see cref="Spline"/> that this Slice will read <see cref="BezierKnot"/> and <see cref="BezierCurve"/>
|
||||
/// data from.
|
||||
/// </param>
|
||||
/// <param name="range">The start index and count of knot indices that compose this slice.</param>
|
||||
/// <param name="transform">A transform matrix to be applied to the spline knots and tangents.</param>
|
||||
public SplineSlice(T spline, SplineRange range, float4x4 transform)
|
||||
{
|
||||
Spline = spline;
|
||||
Range = range;
|
||||
Transform = transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the sum of all curve lengths.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is inefficient to call this method frequently, as it will calculate the length of all curves every time
|
||||
/// it is invoked. In cases where performance is critical, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> instead. Note that you may pass a <see cref="SplineSlice{T}"/> to constructors
|
||||
/// for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetCurveLength"/>
|
||||
/// <returns>
|
||||
/// Returns the sum length of all curves composing this spline.
|
||||
/// </returns>
|
||||
public float GetLength()
|
||||
{
|
||||
var len = 0f;
|
||||
for (int i = 0, c = Count; i < c; ++i)
|
||||
len += GetCurveLength(i);
|
||||
return len;
|
||||
}
|
||||
|
||||
/// <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 bi = math.min(math.max(index + 1, 0), Range.Count-1);
|
||||
BezierKnot a = this[index], b = this[bi];
|
||||
if (index == bi)
|
||||
return new BezierCurve(a.Position, b.Position);
|
||||
return new BezierCurve(a, b);
|
||||
}
|
||||
|
||||
/// <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"/>
|
||||
/// <remarks>
|
||||
/// The curve length cannot be cached here because the transform matrix associated to this slice might impact that
|
||||
/// value, like using a non-uniform scale on the GameObject associated with that slice. It is inefficient
|
||||
/// to call this method frequently, because it calculates the length of the curve every time
|
||||
/// it is invoked.
|
||||
/// If performance is a critical requirement, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> from the relevant <see cref="SplinePath{T}"/> or <see cref="SplineSlice{T}"/>.
|
||||
/// Note that you might pass a <see cref="SplineSlice{T}"/> to constructors for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Returns the length of the curve of index 'index' in the spline.
|
||||
/// </returns>
|
||||
public float GetCurveLength(int index)
|
||||
{
|
||||
return CurveUtility.CalculateLength(GetCurve(index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the up vector for a t ratio on the curve.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the curve for which the length needs to be retrieved.</param>
|
||||
/// <param name="t">A value between 0 and 1 representing the ratio along the curve.</param>
|
||||
/// <remarks>
|
||||
/// It is inefficient to call this method frequently, as it will calculate the up Vector of the curve every time
|
||||
/// it is invoked. In cases where performance is critical, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> instead. Note that you may pass a <see cref="SplineSlice{T}"/> to constructors
|
||||
/// for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Returns the up vector at the t ratio of the curve of index 'index'.
|
||||
/// </returns>
|
||||
public float3 GetCurveUpVector(int index, float t)
|
||||
{
|
||||
return SplineUtility.CalculateUpVector(this, index, t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the normalized interpolation (t) corresponding to a distance on a <see cref="BezierCurve"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is inefficient to call this method frequently, as it will calculate the interpolation lookup table every
|
||||
/// time it is invoked. In cases where performance is critical, create a new <see cref="Spline"/> or
|
||||
/// <see cref="NativeSpline"/> instead. Note that you may pass a <see cref="SplineSlice{T}"/> to constructors
|
||||
/// for both <see cref="Spline"/> and <see cref="NativeSpline"/>.
|
||||
/// </remarks>
|
||||
/// <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)
|
||||
{
|
||||
return CurveUtility.GetDistanceToInterpolation(GetCurve(curveIndex), curveDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8594d6ceba4b4bd3bda82bd87b23bb44
|
||||
timeCreated: 1651682927
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
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>
|
||||
[Obsolete("Replaced by " + nameof(Spline.GetTangentMode) + " and " + nameof(Spline.SetTangentMode) + ".")]
|
||||
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
|
||||
}
|
||||
|
||||
static class SplineTypeUtility
|
||||
{
|
||||
#pragma warning disable 618
|
||||
internal static TangentMode GetTangentMode(this SplineType splineType)
|
||||
{
|
||||
switch (splineType)
|
||||
{
|
||||
case SplineType.Bezier:
|
||||
return TangentMode.Mirrored;
|
||||
case SplineType.Linear:
|
||||
return TangentMode.Linear;
|
||||
default:
|
||||
return TangentMode.AutoSmooth;
|
||||
}
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc3d3aa21eb40814391409a5b8bb11e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd0f968f285cfdd48aa6d1e9bf2a599f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,860 @@
|
||||
// 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<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(BezierKnot a, 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 override bool Equals(object obj);
|
||||
public bool Equals(BezierCurve other);
|
||||
public static BezierCurve FromTangent(Unity.Mathematics.float3 pointA, Unity.Mathematics.float3 tangentOutA, Unity.Mathematics.float3 pointB, Unity.Mathematics.float3 tangentInB);
|
||||
public override int GetHashCode();
|
||||
public BezierCurve GetInvertedCurve();
|
||||
public static bool operator ==(BezierCurve left, BezierCurve right);
|
||||
public static bool operator !=(BezierCurve left, BezierCurve right);
|
||||
public BezierCurve Transform(Unity.Mathematics.float4x4 matrix);
|
||||
}
|
||||
|
||||
public struct BezierKnot : ISerializationCallbackReceiver, System.IEquatable<BezierKnot>
|
||||
{
|
||||
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) {}
|
||||
public BezierKnot(Unity.Mathematics.float3 position, Unity.Mathematics.float3 tangentIn, Unity.Mathematics.float3 tangentOut, Unity.Mathematics.quaternion rotation) {}
|
||||
public override bool Equals(object obj);
|
||||
public bool Equals(BezierKnot other);
|
||||
public override int GetHashCode();
|
||||
public void OnAfterDeserialize();
|
||||
public void OnBeforeSerialize();
|
||||
public static BezierKnot operator +(BezierKnot knot, Unity.Mathematics.float3 rhs);
|
||||
public static BezierKnot operator -(BezierKnot knot, Unity.Mathematics.float3 rhs);
|
||||
public override string ToString();
|
||||
public BezierKnot Transform(Unity.Mathematics.float4x4 matrix);
|
||||
}
|
||||
|
||||
public enum BezierTangent
|
||||
{
|
||||
In = 0,
|
||||
Out = 1,
|
||||
}
|
||||
|
||||
public static class CurveUtility
|
||||
{
|
||||
public static float ApproximateLength(BezierCurve curve);
|
||||
public static void CalculateCurveLengths(BezierCurve curve, DistanceToInterpolation[] lookupTable);
|
||||
public static float CalculateLength(BezierCurve curve, int resolution = 30);
|
||||
public static Unity.Mathematics.float3 EvaluateAcceleration(BezierCurve curve, float t);
|
||||
public static float EvaluateCurvature(BezierCurve curve, float t);
|
||||
public static Unity.Mathematics.float3 EvaluatePosition(BezierCurve curve, float t);
|
||||
public static Unity.Mathematics.float3 EvaluateTangent(BezierCurve curve, float t);
|
||||
public static float GetDistanceToInterpolation<T>(T lut, float distance) where T : System.Collections.Generic.IReadOnlyList<DistanceToInterpolation>;
|
||||
public static float GetDistanceToInterpolation(BezierCurve curve, float distance);
|
||||
public static Unity.Mathematics.float3 GetNearestPoint(BezierCurve curve, Ray ray, int resolution = 16);
|
||||
public static float GetNearestPoint(BezierCurve curve, Ray ray, out Unity.Mathematics.float3 position, out float interpolation, int resolution = 16);
|
||||
public static void Split(BezierCurve curve, float t, out BezierCurve left, out BezierCurve right);
|
||||
}
|
||||
|
||||
public struct DataPoint<TDataType> : IDataPoint, System.IComparable<float>, System.IComparable<UnityEngine.Splines.DataPoint<TDataType>>
|
||||
{
|
||||
public float Index { get; set; }
|
||||
public TDataType Value { get; set; }
|
||||
public DataPoint(float index, TDataType value) {}
|
||||
public int CompareTo(float other);
|
||||
public int CompareTo(UnityEngine.Splines.DataPoint<TDataType> other);
|
||||
public override string ToString();
|
||||
}
|
||||
|
||||
public struct DistanceToInterpolation
|
||||
{
|
||||
public float Distance;
|
||||
public float T;
|
||||
}
|
||||
|
||||
public class EmbeddedSplineData
|
||||
{
|
||||
public SplineContainer Container { get; set; }
|
||||
public string Key { get; set; }
|
||||
public int SplineIndex { get; set; }
|
||||
public EmbeddedSplineDataType Type { get; set; }
|
||||
public EmbeddedSplineData() {}
|
||||
public EmbeddedSplineData(string key, EmbeddedSplineDataType type, SplineContainer container = default(SplineContainer), int splineIndex = 0) {}
|
||||
public UnityEngine.Splines.SplineData<Unity.Mathematics.float4> GetOrCreateFloat4Data();
|
||||
public UnityEngine.Splines.SplineData<float> GetOrCreateFloatData();
|
||||
public UnityEngine.Splines.SplineData<int> GetOrCreateIntData();
|
||||
public UnityEngine.Splines.SplineData<UnityEngine.Object> GetOrCreateObjectData();
|
||||
public bool TryGetFloat4Data(out UnityEngine.Splines.SplineData<Unity.Mathematics.float4> data);
|
||||
public bool TryGetFloatData(out UnityEngine.Splines.SplineData<float> data);
|
||||
public bool TryGetIntData(out UnityEngine.Splines.SplineData<int> data);
|
||||
public bool TryGetObjectData(out UnityEngine.Splines.SplineData<UnityEngine.Object> data);
|
||||
public bool TryGetSpline(out Spline spline);
|
||||
}
|
||||
|
||||
[System.Flags] public enum EmbeddedSplineDataField
|
||||
{
|
||||
All = 255,
|
||||
Container = 1,
|
||||
Key = 4,
|
||||
SplineIndex = 2,
|
||||
Type = 8,
|
||||
}
|
||||
|
||||
public class EmbeddedSplineDataFieldsAttribute : PropertyAttribute
|
||||
{
|
||||
public readonly EmbeddedSplineDataField Fields;
|
||||
public EmbeddedSplineDataFieldsAttribute(EmbeddedSplineDataField fields) {}
|
||||
}
|
||||
|
||||
public enum EmbeddedSplineDataType
|
||||
{
|
||||
Float = 1,
|
||||
Float4 = 2,
|
||||
Int = 0,
|
||||
Object = 3,
|
||||
}
|
||||
|
||||
public struct GetPosition : Unity.Jobs.IJobParallelFor
|
||||
{
|
||||
[Unity.Collections.WriteOnly] public Unity.Collections.NativeArray<Unity.Mathematics.float3> Positions;
|
||||
[Unity.Collections.ReadOnly] public NativeSpline Spline;
|
||||
public void Execute(int index);
|
||||
}
|
||||
|
||||
public struct GetPositionTangentNormal : Unity.Jobs.IJobParallelFor
|
||||
{
|
||||
[Unity.Collections.WriteOnly] public Unity.Collections.NativeArray<Unity.Mathematics.float3> Normals;
|
||||
[Unity.Collections.WriteOnly] public Unity.Collections.NativeArray<Unity.Mathematics.float3> Positions;
|
||||
[Unity.Collections.ReadOnly] public NativeSpline Spline;
|
||||
[Unity.Collections.WriteOnly] public Unity.Collections.NativeArray<Unity.Mathematics.float3> Tangents;
|
||||
public void Execute(int index);
|
||||
}
|
||||
|
||||
public interface IDataPoint
|
||||
{
|
||||
public float Index { get; set; }
|
||||
}
|
||||
|
||||
public interface IHasEmptyCurves
|
||||
{
|
||||
public System.Collections.Generic.IReadOnlyList<int> EmptyCurves { get; }
|
||||
}
|
||||
|
||||
public interface IInterpolator<T>
|
||||
{
|
||||
public T Interpolate(T from, T to, float t);
|
||||
}
|
||||
|
||||
public static class InterpolatorUtility
|
||||
{
|
||||
public static UnityEngine.Splines.IInterpolator<Color> LerpColor { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<float> LerpFloat { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2> LerpFloat2 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3> LerpFloat3 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4> LerpFloat4 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion> LerpQuaternion { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2> SlerpFloat2 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3> SlerpFloat3 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion> SlerpQuaternion { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<float> SmoothStepFloat { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2> SmoothStepFloat2 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3> SmoothStepFloat3 { get; }
|
||||
public static UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4> SmoothStepFloat4 { get; }
|
||||
}
|
||||
|
||||
public interface ISpline : System.Collections.Generic.IEnumerable<BezierKnot>, System.Collections.Generic.IReadOnlyCollection<BezierKnot>, System.Collections.Generic.IReadOnlyList<BezierKnot>, System.Collections.IEnumerable
|
||||
{
|
||||
public bool Closed { get; }
|
||||
public BezierCurve GetCurve(int index);
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance);
|
||||
public float GetCurveLength(int index);
|
||||
public float GetLength();
|
||||
}
|
||||
|
||||
public interface ISplineContainer
|
||||
{
|
||||
public KnotLinkCollection KnotLinkCollection { get; }
|
||||
public System.Collections.Generic.IReadOnlyList<Spline> Splines { get; set; }
|
||||
}
|
||||
|
||||
[System.Obsolete(@"Use ISplineContainer instead.")] public interface ISplineProvider
|
||||
{
|
||||
public System.Collections.Generic.IEnumerable<Spline> Splines { get; }
|
||||
}
|
||||
|
||||
public sealed class KnotLinkCollection
|
||||
{
|
||||
public int Count { get; }
|
||||
public KnotLinkCollection() {}
|
||||
public void Clear();
|
||||
public System.Collections.Generic.IReadOnlyList<SplineKnotIndex> GetKnotLinks(SplineKnotIndex knotIndex);
|
||||
public void KnotIndexChanged(SplineKnotIndex previousIndex, SplineKnotIndex newIndex);
|
||||
public void KnotIndexChanged(int splineIndex, int previousKnotIndex, int newKnotIndex);
|
||||
public void KnotInserted(SplineKnotIndex index);
|
||||
public void KnotInserted(int splineIndex, int knotIndex);
|
||||
public void KnotRemoved(SplineKnotIndex index);
|
||||
public void KnotRemoved(int splineIndex, int knotIndex);
|
||||
public void Link(SplineKnotIndex knotA, SplineKnotIndex knotB);
|
||||
public void ShiftKnotIndices(SplineKnotIndex index, int offset);
|
||||
public void SplineIndexChanged(int previousIndex, int newIndex);
|
||||
public void SplineRemoved(int splineIndex);
|
||||
public bool TryGetKnotLinks(SplineKnotIndex knotIndex, out System.Collections.Generic.IReadOnlyList<SplineKnotIndex> linkedKnots);
|
||||
public void Unlink(SplineKnotIndex knot);
|
||||
}
|
||||
|
||||
public struct NativeSpline : ISpline, System.Collections.Generic.IEnumerable<BezierKnot>, System.Collections.Generic.IReadOnlyCollection<BezierKnot>, System.Collections.Generic.IReadOnlyList<BezierKnot>, System.Collections.IEnumerable, System.IDisposable
|
||||
{
|
||||
public bool Closed { get; }
|
||||
public int Count { get; }
|
||||
public Unity.Collections.NativeArray<BezierCurve> Curves { get; }
|
||||
public BezierKnot this[int index] { get; }
|
||||
public Unity.Collections.NativeArray<BezierKnot> Knots { get; }
|
||||
public NativeSpline(ISpline spline, Unity.Collections.Allocator allocator = Unity.Collections.Allocator.Temp) {}
|
||||
public NativeSpline(ISpline spline, Unity.Mathematics.float4x4 transform, Unity.Collections.Allocator allocator = Unity.Collections.Allocator.Temp) {}
|
||||
public NativeSpline(System.Collections.Generic.IReadOnlyList<BezierKnot> knots, bool closed, Unity.Mathematics.float4x4 transform, Unity.Collections.Allocator allocator = Unity.Collections.Allocator.Temp) {}
|
||||
public NativeSpline(System.Collections.Generic.IReadOnlyList<BezierKnot> knots, System.Collections.Generic.IReadOnlyList<int> splits, bool closed, Unity.Mathematics.float4x4 transform, Unity.Collections.Allocator allocator = Unity.Collections.Allocator.Temp) {}
|
||||
public void Dispose();
|
||||
public BezierCurve GetCurve(int index);
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance);
|
||||
public float GetCurveLength(int curveIndex);
|
||||
public System.Collections.Generic.IEnumerator<BezierKnot> GetEnumerator();
|
||||
public float GetLength();
|
||||
}
|
||||
|
||||
public enum PathIndexUnit
|
||||
{
|
||||
Distance = 0,
|
||||
Knot = 2,
|
||||
Normalized = 1,
|
||||
}
|
||||
|
||||
public enum SliceDirection
|
||||
{
|
||||
Backward = 1,
|
||||
Forward = 0,
|
||||
}
|
||||
|
||||
public class Spline : ISpline, System.Collections.Generic.ICollection<BezierKnot>, System.Collections.Generic.IEnumerable<BezierKnot>, System.Collections.Generic.IList<BezierKnot>, System.Collections.Generic.IReadOnlyCollection<BezierKnot>, System.Collections.Generic.IReadOnlyList<BezierKnot>, System.Collections.IEnumerable
|
||||
{
|
||||
[System.Obsolete(@"Deprecated, use Changed instead.")] public event System.Action changed;
|
||||
public static event System.Action<Spline, int, SplineModification> Changed;
|
||||
public bool Closed { get; set; }
|
||||
public int Count { get; }
|
||||
[System.Obsolete(@"Use GetTangentMode and SetTangentMode.")] public SplineType EditType { get; set; }
|
||||
public bool IsReadOnly { get; }
|
||||
public BezierKnot this[int index] { get; set; }
|
||||
public System.Collections.Generic.IEnumerable<BezierKnot> Knots { get; set; }
|
||||
public Spline() {}
|
||||
public Spline(Spline spline) {}
|
||||
public Spline(System.Collections.Generic.IEnumerable<BezierKnot> knots, bool closed = false) {}
|
||||
public Spline(int knotCapacity, bool closed = false) {}
|
||||
public void Add(BezierKnot item);
|
||||
public void Add(BezierKnot item, TangentMode mode);
|
||||
public void Add(BezierKnot item, TangentMode mode, float tension);
|
||||
public void Clear();
|
||||
public bool Contains(BezierKnot item);
|
||||
public void Copy(Spline copyFrom);
|
||||
public void CopyTo(BezierKnot[] array, int arrayIndex);
|
||||
public void EnforceTangentModeNoNotify(int index);
|
||||
public void EnforceTangentModeNoNotify(SplineRange range);
|
||||
public float GetAutoSmoothTension(int index);
|
||||
public BezierCurve GetCurve(int index);
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance);
|
||||
public float GetCurveLength(int index);
|
||||
public System.Collections.Generic.IEnumerator<BezierKnot> GetEnumerator();
|
||||
public System.Collections.Generic.IEnumerable<string> GetFloat4DataKeys();
|
||||
public System.Collections.Generic.IEnumerable<UnityEngine.Splines.SplineData<Unity.Mathematics.float4>> GetFloat4DataValues();
|
||||
public System.Collections.Generic.IEnumerable<string> GetFloatDataKeys();
|
||||
public System.Collections.Generic.IEnumerable<UnityEngine.Splines.SplineData<float>> GetFloatDataValues();
|
||||
public System.Collections.Generic.IEnumerable<string> GetIntDataKeys();
|
||||
public System.Collections.Generic.IEnumerable<UnityEngine.Splines.SplineData<int>> GetIntDataValues();
|
||||
public float GetLength();
|
||||
public System.Collections.Generic.IEnumerable<string> GetObjectDataKeys();
|
||||
public System.Collections.Generic.IEnumerable<UnityEngine.Splines.SplineData<UnityEngine.Object>> GetObjectDataValues();
|
||||
public UnityEngine.Splines.SplineData<Unity.Mathematics.float4> GetOrCreateFloat4Data(string key);
|
||||
public UnityEngine.Splines.SplineData<float> GetOrCreateFloatData(string key);
|
||||
public UnityEngine.Splines.SplineData<int> GetOrCreateIntData(string key);
|
||||
public UnityEngine.Splines.SplineData<UnityEngine.Object> GetOrCreateObjectData(string key);
|
||||
public System.Collections.Generic.IEnumerable<string> GetSplineDataKeys(EmbeddedSplineDataType type);
|
||||
public TangentMode GetTangentMode(int index);
|
||||
public int IndexOf(BezierKnot item);
|
||||
public void Insert(int index, BezierKnot knot);
|
||||
public void Insert(int index, BezierKnot knot, TangentMode mode);
|
||||
public void Insert(int index, BezierKnot knot, TangentMode mode, float tension);
|
||||
protected virtual void OnSplineChanged();
|
||||
public bool Remove(BezierKnot item);
|
||||
public void RemoveAt(int index);
|
||||
public bool RemoveFloat4Data(string key);
|
||||
public bool RemoveFloatData(string key);
|
||||
public bool RemoveIntData(string key);
|
||||
public bool RemoveObjectData(string key);
|
||||
public void Resize(int newSize);
|
||||
public void SetAutoSmoothTension(int index, float tension);
|
||||
public void SetAutoSmoothTension(SplineRange range, float tension);
|
||||
public void SetAutoSmoothTensionNoNotify(int index, float tension);
|
||||
public void SetAutoSmoothTensionNoNotify(SplineRange range, float tension);
|
||||
public void SetFloat4Data(string key, UnityEngine.Splines.SplineData<Unity.Mathematics.float4> value);
|
||||
public void SetFloatData(string key, UnityEngine.Splines.SplineData<float> value);
|
||||
public void SetIntData(string key, UnityEngine.Splines.SplineData<int> value);
|
||||
public void SetKnot(int index, BezierKnot value, BezierTangent main = BezierTangent.Out);
|
||||
public void SetKnotNoNotify(int index, BezierKnot value, BezierTangent main = BezierTangent.Out);
|
||||
public void SetObjectData(string key, UnityEngine.Splines.SplineData<UnityEngine.Object> value);
|
||||
public void SetTangentMode(TangentMode mode);
|
||||
public void SetTangentMode(int index, TangentMode mode, BezierTangent main = BezierTangent.Out);
|
||||
public void SetTangentMode(SplineRange range, TangentMode mode, BezierTangent main = BezierTangent.Out);
|
||||
public void SetTangentModeNoNotify(int index, TangentMode mode, BezierTangent main = BezierTangent.Out);
|
||||
public BezierKnot[] ToArray();
|
||||
public bool TryGetFloat4Data(string key, out UnityEngine.Splines.SplineData<Unity.Mathematics.float4> data);
|
||||
public bool TryGetFloatData(string key, out UnityEngine.Splines.SplineData<float> data);
|
||||
public bool TryGetIntData(string key, out UnityEngine.Splines.SplineData<int> data);
|
||||
public bool TryGetObjectData(string key, out UnityEngine.Splines.SplineData<UnityEngine.Object> data);
|
||||
public void Warmup();
|
||||
}
|
||||
|
||||
[AddComponentMenu(@"Splines/Spline Animate")] public class SplineAnimate : SplineComponent
|
||||
{
|
||||
[System.Obsolete(@"Use Updated instead.", false)] public event System.Action<Vector3, Quaternion> onUpdated;
|
||||
public event System.Action<Vector3, Quaternion> Updated;
|
||||
public SplineAnimate.AlignmentMode Alignment { get; set; }
|
||||
[System.Obsolete(@"Use Alignment instead.", false)] public SplineAnimate.AlignmentMode alignmentMode { get; }
|
||||
public SplineAnimate.Method AnimationMethod { get; set; }
|
||||
public SplineContainer Container { get; set; }
|
||||
[System.Obsolete(@"Use Duration instead.", false)] public float duration { get; }
|
||||
public float Duration { get; set; }
|
||||
public SplineAnimate.EasingMode Easing { get; set; }
|
||||
[System.Obsolete(@"Use Easing instead.", false)] public SplineAnimate.EasingMode easingMode { get; }
|
||||
[System.Obsolete(@"Use ElapsedTime instead.", false)] public float elapsedTime { get; }
|
||||
public float ElapsedTime { get; set; }
|
||||
[System.Obsolete(@"Use IsPlaying instead.", false)] public bool isPlaying { get; }
|
||||
public bool IsPlaying { get; }
|
||||
public SplineAnimate.LoopMode Loop { get; set; }
|
||||
[System.Obsolete(@"Use Loop instead.", false)] public SplineAnimate.LoopMode loopMode { get; }
|
||||
[System.Obsolete(@"Use MaxSpeed instead.", false)] public float maxSpeed { get; }
|
||||
public float MaxSpeed { get; set; }
|
||||
[System.Obsolete(@"Use AnimationMethod instead.", false)] public SplineAnimate.Method method { get; }
|
||||
[System.Obsolete(@"Use NormalizedTime instead.", false)] public float normalizedTime { get; }
|
||||
public float NormalizedTime { get; set; }
|
||||
[System.Obsolete(@"Use ObjectForwardAxis instead.", false)] public SplineComponent.AlignAxis objectForwardAxis { get; }
|
||||
public SplineComponent.AlignAxis ObjectForwardAxis { get; set; }
|
||||
[System.Obsolete(@"Use ObjectUpAxis instead.", false)] public SplineComponent.AlignAxis objectUpAxis { get; }
|
||||
public SplineComponent.AlignAxis ObjectUpAxis { get; set; }
|
||||
[System.Obsolete(@"Use PlayOnAwake instead.", false)] public bool playOnAwake { get; }
|
||||
public bool PlayOnAwake { get; set; }
|
||||
[System.Obsolete(@"Use Container instead.", false)] public SplineContainer splineContainer { get; }
|
||||
public float StartOffset { get; set; }
|
||||
public SplineAnimate() {}
|
||||
public void Pause();
|
||||
public void Play();
|
||||
public void Restart(bool autoplay);
|
||||
public void Update();
|
||||
public enum AlignmentMode
|
||||
{
|
||||
[InspectorName(@"None")] None = 0,
|
||||
[InspectorName(@"Spline Element")] SplineElement = 1,
|
||||
[InspectorName(@"Spline Object")] SplineObject = 2,
|
||||
[InspectorName(@"World Space")] World = 3,
|
||||
}
|
||||
public enum EasingMode
|
||||
{
|
||||
[InspectorName(@"Ease In Only")] EaseIn = 1,
|
||||
[InspectorName(@"Ease In-Out")] EaseInOut = 3,
|
||||
[InspectorName(@"Ease Out Only")] EaseOut = 2,
|
||||
[InspectorName(@"None")] None = 0,
|
||||
}
|
||||
public enum LoopMode
|
||||
{
|
||||
[InspectorName(@"Loop Continuous")] Loop = 1,
|
||||
[InspectorName(@"Ease In Then Continuous")] LoopEaseInOnce = 2,
|
||||
[InspectorName(@"Once")] Once = 0,
|
||||
[InspectorName(@"Ping Pong")] PingPong = 3,
|
||||
}
|
||||
public enum Method
|
||||
{
|
||||
Speed = 1,
|
||||
Time = 0,
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SplineComponent : MonoBehaviour
|
||||
{
|
||||
protected SplineComponent() {}
|
||||
protected Unity.Mathematics.float3 GetAxis(SplineComponent.AlignAxis axis);
|
||||
public enum AlignAxis
|
||||
{
|
||||
[InspectorName(@"Object X-")] NegativeXAxis = 3,
|
||||
[InspectorName(@"Object Y-")] NegativeYAxis = 4,
|
||||
[InspectorName(@"Object Z-")] NegativeZAxis = 5,
|
||||
[InspectorName(@"Object X+")] XAxis = 0,
|
||||
[InspectorName(@"Object Y+")] YAxis = 1,
|
||||
[InspectorName(@"Object Z+")] ZAxis = 2,
|
||||
}
|
||||
}
|
||||
|
||||
public struct SplineComputeBufferScope<T> : System.IDisposable where T : ISpline
|
||||
{
|
||||
public ComputeBuffer CurveLengths { get; }
|
||||
public ComputeBuffer Curves { get; }
|
||||
public Vector4 Info { get; }
|
||||
public SplineComputeBufferScope(T spline) {}
|
||||
public void Bind(ComputeShader shader, int kernel, string info, string curves, string lengths);
|
||||
public void Dispose();
|
||||
public void Upload();
|
||||
}
|
||||
|
||||
[AddComponentMenu(@"Splines/Spline")] [ExecuteInEditMode] [Icon(@"Packages/com.unity.splines/Editor/Resources/Icons/SplineComponent.png")] public sealed class SplineContainer : MonoBehaviour, ISerializationCallbackReceiver, ISplineContainer
|
||||
{
|
||||
public static event System.Action<SplineContainer, int> SplineAdded;
|
||||
public static event System.Action<SplineContainer, int> SplineRemoved;
|
||||
public static event System.Action<SplineContainer, int, int> SplineReordered;
|
||||
public Spline this[int index] { get; }
|
||||
public KnotLinkCollection KnotLinkCollection { get; }
|
||||
public Spline Spline { get; set; }
|
||||
public System.Collections.Generic.IReadOnlyList<Spline> Splines { get; set; }
|
||||
public SplineContainer() {}
|
||||
public float CalculateLength();
|
||||
public float CalculateLength(int splineIndex);
|
||||
public bool Evaluate(float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector);
|
||||
public bool Evaluate(int splineIndex, float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector);
|
||||
public bool Evaluate<T>(T spline, float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector) where T : ISpline;
|
||||
public Unity.Mathematics.float3 EvaluateAcceleration(float t);
|
||||
public Unity.Mathematics.float3 EvaluateAcceleration(int splineIndex, float t);
|
||||
public Unity.Mathematics.float3 EvaluateAcceleration<T>(T spline, float t) where T : ISpline;
|
||||
public Unity.Mathematics.float3 EvaluatePosition(float t);
|
||||
public Unity.Mathematics.float3 EvaluatePosition(int splineIndex, float t);
|
||||
public Unity.Mathematics.float3 EvaluatePosition<T>(T spline, float t) where T : ISpline;
|
||||
public Unity.Mathematics.float3 EvaluateTangent(float t);
|
||||
public Unity.Mathematics.float3 EvaluateTangent(int splineIndex, float t);
|
||||
public Unity.Mathematics.float3 EvaluateTangent<T>(T spline, float t) where T : ISpline;
|
||||
public Unity.Mathematics.float3 EvaluateUpVector(float t);
|
||||
public Unity.Mathematics.float3 EvaluateUpVector(int splineIndex, float t);
|
||||
public Unity.Mathematics.float3 EvaluateUpVector<T>(T spline, float t) where T : ISpline;
|
||||
public void OnAfterDeserialize();
|
||||
public void OnBeforeSerialize();
|
||||
}
|
||||
|
||||
public class SplineData<T> : System.Collections.Generic.IEnumerable<UnityEngine.Splines.DataPoint<T>>, System.Collections.IEnumerable
|
||||
{
|
||||
[System.Obsolete(@"Use Changed instead.", false)] public event System.Action changed;
|
||||
public event System.Action Changed;
|
||||
public int Count { get; }
|
||||
public T DefaultValue { get; set; }
|
||||
public System.Collections.Generic.IEnumerable<float> Indexes { get; }
|
||||
public UnityEngine.Splines.DataPoint<T> this[int index] { get; set; }
|
||||
public PathIndexUnit PathIndexUnit { get; set; }
|
||||
public SplineData() {}
|
||||
public SplineData(System.Collections.Generic.IEnumerable<UnityEngine.Splines.DataPoint<T>> dataPoints) {}
|
||||
public SplineData(T init) {}
|
||||
public int Add(UnityEngine.Splines.DataPoint<T> dataPoint);
|
||||
public void Add(float t, T data);
|
||||
public int AddDataPointWithDefaultValue(float t, bool useDefaultValue = false);
|
||||
public void Clear();
|
||||
public void ConvertPathUnit<TSplineType>(TSplineType spline, PathIndexUnit toUnit) where TSplineType : ISpline;
|
||||
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, TInterpolator interpolator) where TSpline : ISpline where TInterpolator : UnityEngine.Splines.IInterpolator<T>;
|
||||
public T Evaluate<TSpline, TInterpolator>(TSpline spline, float t, PathIndexUnit indexUnit, TInterpolator interpolator) where TSpline : ISpline where TInterpolator : UnityEngine.Splines.IInterpolator<T>;
|
||||
public System.Collections.Generic.IEnumerator<UnityEngine.Splines.DataPoint<T>> GetEnumerator();
|
||||
public float GetNormalizedInterpolation<TSplineType>(TSplineType spline, float t) where TSplineType : ISpline;
|
||||
public int MoveDataPoint(int index, float newIndex);
|
||||
public void RemoveAt(int index);
|
||||
public bool RemoveDataPoint(float t);
|
||||
public void SetDataPoint(int index, UnityEngine.Splines.DataPoint<T> value);
|
||||
public void SetDataPointNoSort(int index, UnityEngine.Splines.DataPoint<T> value);
|
||||
public void SortIfNecessary();
|
||||
}
|
||||
|
||||
[System.AttributeUsage(System.AttributeTargets.Field)] [System.Obsolete(@"Use SplineDataHandles.DataPointHandles instead and EditorTools to interact with SplineData.", false)] public abstract class SplineDataHandleAttribute : System.Attribute
|
||||
{
|
||||
protected SplineDataHandleAttribute() {}
|
||||
}
|
||||
|
||||
[AddComponentMenu(@"Splines/Spline Extrude")] [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class SplineExtrude : MonoBehaviour
|
||||
{
|
||||
[System.Obsolete(@"Use Capped instead.", false)] public bool capped { get; }
|
||||
public bool Capped { get; set; }
|
||||
[System.Obsolete(@"Use Container instead.", false)] public SplineContainer container { get; }
|
||||
public SplineContainer Container { get; set; }
|
||||
[System.Obsolete(@"Use Radius instead.", false)] public float radius { get; }
|
||||
public float Radius { get; set; }
|
||||
[System.Obsolete(@"Use Range instead.", false)] public Vector2 range { get; }
|
||||
public Vector2 Range { get; set; }
|
||||
[System.Obsolete(@"Use RebuildFrequency instead.", false)] public int rebuildFrequency { get; }
|
||||
public int RebuildFrequency { get; set; }
|
||||
[System.Obsolete(@"Use RebuildOnSplineChange instead.", false)] public bool rebuildOnSplineChange { get; }
|
||||
public bool RebuildOnSplineChange { get; set; }
|
||||
[System.Obsolete(@"Use SegmentsPerUnit instead.", false)] public float segmentsPerUnit { get; }
|
||||
public float SegmentsPerUnit { get; set; }
|
||||
[System.Obsolete(@"Use Sides instead.", false)] public int sides { get; }
|
||||
public int Sides { get; set; }
|
||||
[System.Obsolete(@"Use Spline instead.", false)] public Spline spline { get; }
|
||||
public Spline Spline { get; }
|
||||
public System.Collections.Generic.IReadOnlyList<Spline> Splines { get; }
|
||||
public SplineExtrude() {}
|
||||
public void Rebuild();
|
||||
}
|
||||
|
||||
public static class SplineFactory
|
||||
{
|
||||
public static Spline CreateCatmullRom(System.Collections.Generic.IList<Unity.Mathematics.float3> positions, bool closed = false);
|
||||
public static Spline CreateCircle(float radius);
|
||||
public static Spline CreateHelix(float radius, float height, int revolutions);
|
||||
public static Spline CreateLinear(System.Collections.Generic.IList<Unity.Mathematics.float3> positions, bool closed = false);
|
||||
public static Spline CreateLinear(System.Collections.Generic.IList<Unity.Mathematics.float3> positions, System.Collections.Generic.IList<Unity.Mathematics.quaternion> rotations, bool closed = false);
|
||||
public static Spline CreatePolygon(float edgeSize, int sides);
|
||||
public static Spline CreateRoundedCornerSquare(float size, float cornerRadius);
|
||||
public static Spline CreateRoundedSquare(float radius, float rounding);
|
||||
public static Spline CreateSquare(float size);
|
||||
public static Spline CreateStarPolygon(float edgeSize, int corners, float concavity);
|
||||
}
|
||||
|
||||
public class SplineIndexAttribute : PropertyAttribute
|
||||
{
|
||||
public readonly string SplineContainerProperty;
|
||||
public SplineIndexAttribute(string splineContainerProperty) {}
|
||||
}
|
||||
|
||||
public struct SplineInfo : ISerializationCallbackReceiver, System.IEquatable<SplineInfo>
|
||||
{
|
||||
public ISplineContainer Container { get; set; }
|
||||
public int Index { get; set; }
|
||||
public Unity.Mathematics.float4x4 LocalToWorld { get; }
|
||||
public UnityEngine.Object Object { get; }
|
||||
public Spline Spline { get; }
|
||||
public Transform Transform { get; }
|
||||
public SplineInfo(ISplineContainer container, int index) {}
|
||||
public override bool Equals(object obj);
|
||||
public bool Equals(SplineInfo other);
|
||||
public override int GetHashCode();
|
||||
public void OnAfterDeserialize();
|
||||
public void OnBeforeSerialize();
|
||||
}
|
||||
|
||||
[AddComponentMenu(@"Splines/Spline Instantiate")] [ExecuteInEditMode] public class SplineInstantiate : SplineComponent
|
||||
{
|
||||
[System.Obsolete(@"Use Container instead.", false)] public SplineContainer container { get; }
|
||||
public SplineContainer Container { get; set; }
|
||||
public SplineInstantiate.Space CoordinateSpace { get; set; }
|
||||
[System.Obsolete(@"Use ForwardAxis instead.", false)] public SplineComponent.AlignAxis forwardAxis { get; }
|
||||
public SplineComponent.AlignAxis ForwardAxis { get; set; }
|
||||
public SplineInstantiate.Method InstantiateMethod { get; set; }
|
||||
public SplineInstantiate.InstantiableItem[] itemsToInstantiate { get; set; }
|
||||
[System.Obsolete(@"Use MaxPositionOffset instead.", false)] public Vector3 maxPositionOffset { get; }
|
||||
public Vector3 MaxPositionOffset { get; set; }
|
||||
[System.Obsolete(@"Use MaxRotationOffset instead.", false)] public Vector3 maxRotationOffset { get; }
|
||||
public Vector3 MaxRotationOffset { get; set; }
|
||||
[System.Obsolete(@"Use MaxScaleOffset instead.", false)] public Vector3 maxScaleOffset { get; }
|
||||
public Vector3 MaxScaleOffset { get; set; }
|
||||
public float MaxSpacing { get; set; }
|
||||
[System.Obsolete(@"Use InstantiateMethod instead.", false)] public SplineInstantiate.Method method { get; }
|
||||
[System.Obsolete(@"Use MinPositionOffset instead.", false)] public Vector3 minPositionOffset { get; }
|
||||
public Vector3 MinPositionOffset { get; set; }
|
||||
[System.Obsolete(@"Use MinRotationOffset instead.", false)] public Vector3 minRotationOffset { get; }
|
||||
public Vector3 MinRotationOffset { get; set; }
|
||||
[System.Obsolete(@"Use MinScaleOffset instead.", false)] public Vector3 minScaleOffset { get; }
|
||||
public Vector3 MinScaleOffset { get; set; }
|
||||
public float MinSpacing { get; set; }
|
||||
[System.Obsolete(@"Use PositionSpace instead.", false)] public SplineInstantiate.OffsetSpace positionSpace { get; }
|
||||
public SplineInstantiate.OffsetSpace PositionSpace { get; set; }
|
||||
[System.Obsolete(@"Use RotationSpace instead.", false)] public SplineInstantiate.OffsetSpace rotationSpace { get; }
|
||||
public SplineInstantiate.OffsetSpace RotationSpace { get; set; }
|
||||
[System.Obsolete(@"Use ScaleSpace instead.", false)] public SplineInstantiate.OffsetSpace scaleSpace { get; }
|
||||
public SplineInstantiate.OffsetSpace ScaleSpace { get; set; }
|
||||
[System.Obsolete(@"Use CoordinateSpace instead.", false)] public SplineInstantiate.Space space { get; }
|
||||
[System.Obsolete(@"Use UpAxis instead.", false)] public SplineComponent.AlignAxis upAxis { get; }
|
||||
public SplineComponent.AlignAxis UpAxis { get; set; }
|
||||
public SplineInstantiate() {}
|
||||
public void Clear();
|
||||
public void Randomize();
|
||||
public void SetDirty();
|
||||
public void UpdateInstances();
|
||||
public struct InstantiableItem
|
||||
{
|
||||
[HideInInspector] [System.Obsolete(@"Use Prefab instead.", false)] public GameObject prefab;
|
||||
[UnityEngine.Serialization.FormerlySerializedAs(@"prefab")] public GameObject Prefab;
|
||||
[HideInInspector] [System.Obsolete(@"Use Probability instead.", false)] public float probability;
|
||||
[UnityEngine.Serialization.FormerlySerializedAs(@"probability")] public float Probability;
|
||||
}
|
||||
public enum Method
|
||||
{
|
||||
[InspectorName(@"Instance Count")] InstanceCount = 0,
|
||||
[InspectorName(@"Linear Distance")] LinearDistance = 2,
|
||||
[InspectorName(@"Spline Distance")] SpacingDistance = 1,
|
||||
}
|
||||
public enum OffsetSpace
|
||||
{
|
||||
[InspectorName(@"Spline Object")] Local = 1,
|
||||
[InspectorName(@"Instantiated Object")] Object = 3,
|
||||
[InspectorName(@"Spline Element")] Spline = 0,
|
||||
[InspectorName(@"World Space")] World = 2,
|
||||
}
|
||||
public enum Space
|
||||
{
|
||||
[InspectorName(@"Spline Object")] Local = 1,
|
||||
[InspectorName(@"Spline Element")] Spline = 0,
|
||||
[InspectorName(@"World Space")] World = 2,
|
||||
}
|
||||
}
|
||||
|
||||
public static class SplineJobs
|
||||
{
|
||||
public static void EvaluatePosition<T>(T spline, Unity.Collections.NativeArray<Unity.Mathematics.float3> positions) where T : ISpline;
|
||||
public static void EvaluatePosition(NativeSpline spline, Unity.Collections.NativeArray<Unity.Mathematics.float3> positions);
|
||||
public static void EvaluatePositionTangentNormal<T>(T spline, Unity.Collections.NativeArray<Unity.Mathematics.float3> positions, Unity.Collections.NativeArray<Unity.Mathematics.float3> tangents, Unity.Collections.NativeArray<Unity.Mathematics.float3> normals) where T : ISpline;
|
||||
public static void EvaluatePositionTangentNormal(NativeSpline spline, Unity.Collections.NativeArray<Unity.Mathematics.float3> positions, Unity.Collections.NativeArray<Unity.Mathematics.float3> tangents, Unity.Collections.NativeArray<Unity.Mathematics.float3> normals);
|
||||
}
|
||||
|
||||
public struct SplineKnotIndex : System.IEquatable<SplineKnotIndex>
|
||||
{
|
||||
public int Knot;
|
||||
public int Spline;
|
||||
public SplineKnotIndex(int spline, int knot) {}
|
||||
public override bool Equals(object obj);
|
||||
public bool Equals(SplineKnotIndex otherIndex);
|
||||
public override int GetHashCode();
|
||||
public static bool operator ==(SplineKnotIndex indexA, SplineKnotIndex indexB);
|
||||
public static bool operator !=(SplineKnotIndex indexA, SplineKnotIndex indexB);
|
||||
public override string ToString();
|
||||
}
|
||||
|
||||
public static class SplineMath
|
||||
{
|
||||
public static float DistancePointLine(Unity.Mathematics.float3 p, Unity.Mathematics.float3 a, Unity.Mathematics.float3 b);
|
||||
public static Unity.Mathematics.float3 PointLineNearestPoint(Unity.Mathematics.float3 p, Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, out float lineParam);
|
||||
public static Unity.Mathematics.float3 RayLineDistance(Unity.Mathematics.float3 ro, Unity.Mathematics.float3 rd, Unity.Mathematics.float3 a, Unity.Mathematics.float3 b);
|
||||
public static System.ValueTuple<Unity.Mathematics.float3, Unity.Mathematics.float3> RayLineNearestPoint(Unity.Mathematics.float3 ro, Unity.Mathematics.float3 rd, Unity.Mathematics.float3 a, Unity.Mathematics.float3 b);
|
||||
public static System.ValueTuple<Unity.Mathematics.float3, Unity.Mathematics.float3> RayLineNearestPoint(Unity.Mathematics.float3 ro, Unity.Mathematics.float3 rd, Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, out float rayParam, out float lineParam);
|
||||
public static float RayLineParameter(Unity.Mathematics.float3 ro, Unity.Mathematics.float3 rd, Unity.Mathematics.float3 lineOrigin, Unity.Mathematics.float3 lineDir);
|
||||
}
|
||||
|
||||
public static class SplineMesh
|
||||
{
|
||||
public static void Extrude<T>(T spline, Mesh mesh, float radius, int sides, int segments, bool capped = true) where T : ISpline;
|
||||
public static void Extrude<T>(System.Collections.Generic.IReadOnlyList<T> splines, Mesh mesh, float radius, int sides, float segmentsPerUnit, bool capped, Unity.Mathematics.float2 range) where T : ISpline;
|
||||
public static void Extrude<T>(T spline, Mesh mesh, float radius, int sides, int segments, bool capped, Unity.Mathematics.float2 range) where T : 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 : ISpline where TVertexType : struct, SplineMesh.ISplineVertexData, new() where TIndexType : struct, new();
|
||||
public static void GetVertexAndIndexCount(int sides, int segments, bool capped, bool closed, Vector2 range, out int vertexCount, out int indexCount);
|
||||
public interface ISplineVertexData
|
||||
{
|
||||
public Vector3 normal { get; set; }
|
||||
public Vector3 position { get; set; }
|
||||
public Vector2 texture { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum SplineModification
|
||||
{
|
||||
ClosedModified = 1,
|
||||
Default = 0,
|
||||
KnotInserted = 3,
|
||||
KnotModified = 2,
|
||||
KnotRemoved = 4,
|
||||
KnotReordered = 5,
|
||||
}
|
||||
|
||||
public class SplinePath : UnityEngine.Splines.SplinePath<UnityEngine.Splines.SplineSlice<Spline>>
|
||||
{
|
||||
public SplinePath(System.Collections.Generic.IEnumerable<UnityEngine.Splines.SplineSlice<Spline>> slices) {}
|
||||
}
|
||||
|
||||
public class SplinePath<T> : IHasEmptyCurves, ISpline, System.Collections.Generic.IEnumerable<BezierKnot>, System.Collections.Generic.IReadOnlyCollection<BezierKnot>, System.Collections.Generic.IReadOnlyList<BezierKnot>, System.Collections.IEnumerable where T : ISpline
|
||||
{
|
||||
public bool Closed { get; }
|
||||
public int Count { get; }
|
||||
public System.Collections.Generic.IReadOnlyList<int> EmptyCurves { get; }
|
||||
public BezierKnot this[int index] { get; }
|
||||
public BezierKnot this[SplineKnotIndex index] { get; }
|
||||
public System.Collections.Generic.IReadOnlyList<T> Slices { get; set; }
|
||||
public SplinePath(System.Collections.Generic.IEnumerable<T> slices) {}
|
||||
public BezierCurve GetCurve(int knot);
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance);
|
||||
public float GetCurveLength(int index);
|
||||
public System.Collections.Generic.IEnumerator<BezierKnot> GetEnumerator();
|
||||
public float GetLength();
|
||||
}
|
||||
|
||||
public struct SplineRange : System.Collections.Generic.IEnumerable<int>, System.Collections.IEnumerable
|
||||
{
|
||||
public int Count { get; set; }
|
||||
public SliceDirection Direction { get; set; }
|
||||
public int End { get; }
|
||||
public int this[int index] { get; }
|
||||
public int Start { get; set; }
|
||||
public SplineRange(int start, int count) {}
|
||||
public SplineRange(int start, int count, SliceDirection direction) {}
|
||||
public System.Collections.Generic.IEnumerator<int> GetEnumerator();
|
||||
public override string ToString();
|
||||
public struct SplineRangeEnumerator : System.Collections.Generic.IEnumerator<int>, System.Collections.IEnumerator, System.IDisposable
|
||||
{
|
||||
public int Current { get; }
|
||||
public SplineRangeEnumerator(SplineRange range) {}
|
||||
public void Dispose();
|
||||
public bool MoveNext();
|
||||
public void Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public struct SplineSlice<T> : ISpline, System.Collections.Generic.IEnumerable<BezierKnot>, System.Collections.Generic.IReadOnlyCollection<BezierKnot>, System.Collections.Generic.IReadOnlyList<BezierKnot>, System.Collections.IEnumerable where T : ISpline
|
||||
{
|
||||
public SplineRange Range;
|
||||
public T Spline;
|
||||
public Unity.Mathematics.float4x4 Transform;
|
||||
public bool Closed { get; }
|
||||
public int Count { get; }
|
||||
public BezierKnot this[int index] { get; }
|
||||
public SplineSlice(T spline, SplineRange range) {}
|
||||
public SplineSlice(T spline, SplineRange range, Unity.Mathematics.float4x4 transform) {}
|
||||
public BezierCurve GetCurve(int index);
|
||||
public float GetCurveInterpolation(int curveIndex, float curveDistance);
|
||||
public float GetCurveLength(int index);
|
||||
public System.Collections.Generic.IEnumerator<BezierKnot> GetEnumerator();
|
||||
public float GetLength();
|
||||
}
|
||||
|
||||
[System.Obsolete(@"Replaced by GetTangentMode and SetTangentMode.")] public enum SplineType
|
||||
{
|
||||
Bezier = 1,
|
||||
CatmullRom = 0,
|
||||
Linear = 2,
|
||||
}
|
||||
|
||||
public static class SplineUtility
|
||||
{
|
||||
public const float CatmullRomTension = 0.5f;
|
||||
public const float DefaultTension = 0.5f;
|
||||
public const int DrawResolutionDefault = 10;
|
||||
public const int PickResolutionDefault = 4;
|
||||
public const int PickResolutionMax = 64;
|
||||
public const int PickResolutionMin = 2;
|
||||
public static Spline AddSpline<T>(this T container) where T : ISplineContainer;
|
||||
public static void AddSpline<T>(this T container, Spline spline) where T : ISplineContainer;
|
||||
public static float CalculateLength<T>(this T spline, Unity.Mathematics.float4x4 transform) where T : ISpline;
|
||||
public static float ConvertIndexUnit<T>(this T spline, float t, PathIndexUnit targetPathUnit) where T : ISpline;
|
||||
public static float ConvertIndexUnit<T>(this T spline, float t, PathIndexUnit fromPathUnit, PathIndexUnit targetPathUnit) where T : ISpline;
|
||||
public static void CopyKnotLinks<T>(this T container, int srcSplineIndex, int destSplineIndex) where T : ISplineContainer;
|
||||
public static float CurveToSplineT<T>(this T spline, float curve) where T : ISpline;
|
||||
public static bool Evaluate<T>(this T spline, float t, out Unity.Mathematics.float3 position, out Unity.Mathematics.float3 tangent, out Unity.Mathematics.float3 upVector) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 EvaluateAcceleration<T>(this T spline, float t) where T : ISpline;
|
||||
public static float EvaluateCurvature<T>(this T spline, float t) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 EvaluateCurvatureCenter<T>(this T spline, float t) where T : ISpline;
|
||||
public static bool EvaluateNurbs(float t, System.Collections.Generic.List<Unity.Mathematics.float3> controlPoints, System.Collections.Generic.List<double> knotVector, int order, out Unity.Mathematics.float3 position);
|
||||
public static Unity.Mathematics.float3 EvaluatePosition<T>(this T spline, float t) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 EvaluateTangent<T>(this T spline, float t) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 EvaluateUpVector<T>(this T spline, float t) where T : ISpline;
|
||||
public static bool FitSplineToPoints(System.Collections.Generic.List<Unity.Mathematics.float3> points, float errorThreshold, bool closed, out Spline spline);
|
||||
public static BezierKnot GetAutoSmoothKnot(Unity.Mathematics.float3 position, Unity.Mathematics.float3 previous, Unity.Mathematics.float3 next);
|
||||
public static BezierKnot GetAutoSmoothKnot(Unity.Mathematics.float3 position, Unity.Mathematics.float3 previous, Unity.Mathematics.float3 next, Unity.Mathematics.float3 normal);
|
||||
public static BezierKnot GetAutoSmoothKnot(Unity.Mathematics.float3 position, Unity.Mathematics.float3 previous, Unity.Mathematics.float3 next, Unity.Mathematics.float3 normal, float tension = 0.5f);
|
||||
public static Unity.Mathematics.float3 GetAutoSmoothTangent(Unity.Mathematics.float3 previous, Unity.Mathematics.float3 next, float tension = 0.5f);
|
||||
public static Unity.Mathematics.float3 GetAutoSmoothTangent(Unity.Mathematics.float3 previous, Unity.Mathematics.float3 current, Unity.Mathematics.float3 next, float tension = 0.5f);
|
||||
public static Bounds GetBounds<T>(this T spline) where T : ISpline;
|
||||
public static Bounds GetBounds<T>(this T spline, Unity.Mathematics.float4x4 transform) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 GetCatmullRomTangent(Unity.Mathematics.float3 previous, Unity.Mathematics.float3 next);
|
||||
public static int GetCurveCount<T>(this T spline) where T : 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 : ISpline;
|
||||
public static float GetNearestPoint<T>(T spline, Ray ray, out Unity.Mathematics.float3 nearest, out float t, int resolution = 4, int iterations = 2) where T : ISpline;
|
||||
public static float GetNormalizedInterpolation<T>(T spline, float t, PathIndexUnit originalPathUnit) where T : ISpline;
|
||||
public static Unity.Mathematics.float3 GetPointAtLinearDistance<T>(this T spline, float fromT, float relativeDistance, out float resultPointT) where T : ISpline;
|
||||
[System.Obsolete(@"Use GetSubdivisionCount instead.", false)] public static int GetSegmentCount(float length, int resolution);
|
||||
public static int GetSubdivisionCount(float length, int resolution);
|
||||
public static void LinkKnots<T>(this T container, SplineKnotIndex knotA, SplineKnotIndex knotB) where T : ISplineContainer;
|
||||
public static BezierKnot Next<T>(this T spline, int index) where T : ISpline;
|
||||
public static int NextIndex<T>(this T spline, int index) where T : ISpline;
|
||||
public static BezierKnot Previous<T>(this T spline, int index) where T : ISpline;
|
||||
public static int PreviousIndex<T>(this T spline, int index) where T : ISpline;
|
||||
public static System.Collections.Generic.List<Unity.Mathematics.float3> ReducePoints<T>(T line, float epsilon = 0.15f) where T : System.Collections.Generic.IList<Unity.Mathematics.float3>;
|
||||
public static void ReducePoints<T>(T line, System.Collections.Generic.List<Unity.Mathematics.float3> results, float epsilon = 0.15f) where T : System.Collections.Generic.IList<Unity.Mathematics.float3>;
|
||||
public static bool RemoveSpline<T>(this T container, Spline spline) where T : ISplineContainer;
|
||||
public static bool RemoveSplineAt<T>(this T container, int splineIndex) where T : ISplineContainer;
|
||||
public static bool ReorderSpline<T>(this T container, int previousSplineIndex, int newSplineIndex) where T : ISplineContainer;
|
||||
public static void SetLinkedKnotPosition<T>(this T container, SplineKnotIndex index) where T : ISplineContainer;
|
||||
public static void SetPivot(SplineContainer container, Vector3 position);
|
||||
public static int SplineToCurveT<T>(this T spline, float splineT, out float curveT) where T : ISpline;
|
||||
public static void UnlinkKnots<T>(this T container, System.Collections.Generic.IReadOnlyList<SplineKnotIndex> knots) where T : ISplineContainer;
|
||||
}
|
||||
|
||||
public enum TangentMode
|
||||
{
|
||||
AutoSmooth = 0,
|
||||
Broken = 4,
|
||||
Continuous = 3,
|
||||
Linear = 1,
|
||||
Mirrored = 2,
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnityEngine.Splines.Interpolators
|
||||
{
|
||||
public struct LerpColor : UnityEngine.Splines.IInterpolator<Color>
|
||||
{
|
||||
public Color Interpolate(Color a, Color b, float t);
|
||||
}
|
||||
|
||||
public struct LerpFloat : UnityEngine.Splines.IInterpolator<float>
|
||||
{
|
||||
public float Interpolate(float a, float b, float t);
|
||||
}
|
||||
|
||||
public struct LerpFloat2 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2>
|
||||
{
|
||||
public Unity.Mathematics.float2 Interpolate(Unity.Mathematics.float2 a, Unity.Mathematics.float2 b, float t);
|
||||
}
|
||||
|
||||
public struct LerpFloat3 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3>
|
||||
{
|
||||
public Unity.Mathematics.float3 Interpolate(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, float t);
|
||||
}
|
||||
|
||||
public struct LerpFloat4 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4>
|
||||
{
|
||||
public Unity.Mathematics.float4 Interpolate(Unity.Mathematics.float4 a, Unity.Mathematics.float4 b, float t);
|
||||
}
|
||||
|
||||
public struct LerpQuaternion : UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion>
|
||||
{
|
||||
public Unity.Mathematics.quaternion Interpolate(Unity.Mathematics.quaternion a, Unity.Mathematics.quaternion b, float t);
|
||||
}
|
||||
|
||||
public struct SlerpFloat2 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2>
|
||||
{
|
||||
public Unity.Mathematics.float2 Interpolate(Unity.Mathematics.float2 a, Unity.Mathematics.float2 b, float t);
|
||||
}
|
||||
|
||||
public struct SlerpFloat3 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3>
|
||||
{
|
||||
public Unity.Mathematics.float3 Interpolate(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, float t);
|
||||
}
|
||||
|
||||
public struct SlerpQuaternion : UnityEngine.Splines.IInterpolator<Unity.Mathematics.quaternion>
|
||||
{
|
||||
public Unity.Mathematics.quaternion Interpolate(Unity.Mathematics.quaternion a, Unity.Mathematics.quaternion b, float t);
|
||||
}
|
||||
|
||||
public struct SmoothStepFloat : UnityEngine.Splines.IInterpolator<float>
|
||||
{
|
||||
public float Interpolate(float a, float b, float t);
|
||||
}
|
||||
|
||||
public struct SmoothStepFloat2 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float2>
|
||||
{
|
||||
public Unity.Mathematics.float2 Interpolate(Unity.Mathematics.float2 a, Unity.Mathematics.float2 b, float t);
|
||||
}
|
||||
|
||||
public struct SmoothStepFloat3 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float3>
|
||||
{
|
||||
public Unity.Mathematics.float3 Interpolate(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, float t);
|
||||
}
|
||||
|
||||
public struct SmoothStepFloat4 : UnityEngine.Splines.IInterpolator<Unity.Mathematics.float4>
|
||||
{
|
||||
public 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,28 @@
|
||||
{
|
||||
"name": "Unity.Splines",
|
||||
"rootNamespace": "UnityEngine.Splines",
|
||||
"references": [
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.modules.physics",
|
||||
"expression": "",
|
||||
"define": "UNITY_PHYSICS_MODULE"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.burst",
|
||||
"expression": "1.7.4",
|
||||
"define": "UNITY_BURST_ENABLED"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21d1eb854b91ade49bc69a263d12bee2
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user