344 lines
12 KiB
C#
344 lines
12 KiB
C#
using UnityEngine;
|
|
|
|
namespace BezierSolution
|
|
{
|
|
public enum SplineAutoConstructMode { None = 0, Linear = 1, Smooth1 = 2, Smooth2 = 3 };
|
|
|
|
[System.Flags]
|
|
internal enum InternalDirtyFlags
|
|
{
|
|
None = 0,
|
|
EndPointTransformChange = 1 << 1,
|
|
ControlPointPositionChange = 1 << 2,
|
|
NormalChange = 1 << 3,
|
|
NormalOffsetChange = 1 << 4,
|
|
ExtraDataChange = 1 << 5,
|
|
All = EndPointTransformChange | ControlPointPositionChange | NormalChange | NormalOffsetChange | ExtraDataChange
|
|
};
|
|
|
|
[System.Flags]
|
|
public enum DirtyFlags
|
|
{
|
|
None = 0,
|
|
SplineShapeChanged = 1 << 1,
|
|
NormalsChanged = 1 << 2,
|
|
ExtraDataChanged = 1 << 3,
|
|
All = SplineShapeChanged | NormalsChanged | ExtraDataChanged
|
|
};
|
|
|
|
[System.Flags]
|
|
public enum PointCacheFlags
|
|
{
|
|
None = 0,
|
|
Positions = 1 << 1,
|
|
Normals = 1 << 2,
|
|
Tangents = 1 << 3,
|
|
Bitangents = 1 << 4,
|
|
ExtraDatas = 1 << 5,
|
|
All = Positions | Normals | Tangents | Bitangents | ExtraDatas
|
|
};
|
|
|
|
public delegate void SplineChangeDelegate( BezierSpline spline, DirtyFlags dirtyFlags );
|
|
public delegate BezierPoint.ExtraData ExtraDataLerpFunction( BezierPoint.ExtraData data1, BezierPoint.ExtraData data2, float normalizedT );
|
|
|
|
public partial class BezierSpline
|
|
{
|
|
private static readonly ExtraDataLerpFunction defaultExtraDataLerpFunction = BezierPoint.ExtraData.LerpUnclamped;
|
|
|
|
public struct Segment
|
|
{
|
|
public readonly BezierPoint point1, point2;
|
|
public readonly float localT;
|
|
|
|
public Segment( BezierPoint point1, BezierPoint point2, float localT )
|
|
{
|
|
this.point1 = point1;
|
|
this.point2 = point2;
|
|
this.localT = localT;
|
|
}
|
|
|
|
public float GetNormalizedT() { return GetNormalizedT( localT ); }
|
|
public float GetNormalizedT( float localT )
|
|
{
|
|
BezierSpline spline = point1.spline;
|
|
return ( point1.index + localT ) / ( spline.m_loop ? spline.Count : ( spline.Count - 1 ) );
|
|
}
|
|
|
|
public Vector3 GetPoint() { return GetPoint( localT ); }
|
|
public Vector3 GetPoint( float localT )
|
|
{
|
|
float oneMinusLocalT = 1f - localT;
|
|
|
|
return oneMinusLocalT * oneMinusLocalT * oneMinusLocalT * point1.position +
|
|
3f * oneMinusLocalT * oneMinusLocalT * localT * point1.followingControlPointPosition +
|
|
3f * oneMinusLocalT * localT * localT * point2.precedingControlPointPosition +
|
|
localT * localT * localT * point2.position;
|
|
}
|
|
|
|
public Vector3 GetTangent() { return GetTangent( localT ); }
|
|
public Vector3 GetTangent( float localT )
|
|
{
|
|
float oneMinusLocalT = 1f - localT;
|
|
|
|
return 3f * oneMinusLocalT * oneMinusLocalT * ( point1.followingControlPointPosition - point1.position ) +
|
|
6f * oneMinusLocalT * localT * ( point2.precedingControlPointPosition - point1.followingControlPointPosition ) +
|
|
3f * localT * localT * ( point2.position - point2.precedingControlPointPosition );
|
|
}
|
|
|
|
public Vector3 GetNormal() { return GetNormal( localT ); }
|
|
public Vector3 GetNormal( float localT )
|
|
{
|
|
Vector3[] intermediateNormals = point1.intermediateNormals;
|
|
if( intermediateNormals != null && intermediateNormals.Length > 0 )
|
|
{
|
|
localT = Mathf.Clamp01( localT ) * ( intermediateNormals.Length - 1 );
|
|
int localStartIndex = (int) localT;
|
|
|
|
return ( localStartIndex < intermediateNormals.Length - 1 ) ? Vector3.LerpUnclamped( intermediateNormals[localStartIndex], intermediateNormals[localStartIndex + 1], localT - localStartIndex ) : intermediateNormals[localStartIndex];
|
|
}
|
|
|
|
Vector3 startNormal = point1.normal;
|
|
Vector3 endNormal = point2.normal;
|
|
|
|
Vector3 normal = Vector3.LerpUnclamped( startNormal, endNormal, localT );
|
|
if( normal.y == 0f && normal.x == 0f && normal.z == 0f )
|
|
{
|
|
// Don't return Vector3.zero as normal
|
|
normal = Vector3.LerpUnclamped( startNormal, endNormal, localT > 0.01f ? ( localT - 0.01f ) : ( localT + 0.01f ) );
|
|
if( normal.y == 0f && normal.x == 0f && normal.z == 0f )
|
|
normal = Vector3.up;
|
|
}
|
|
|
|
return normal;
|
|
}
|
|
|
|
public BezierPoint.ExtraData GetExtraData() { return defaultExtraDataLerpFunction( point1.extraData, point2.extraData, localT ); }
|
|
public BezierPoint.ExtraData GetExtraData( float localT ) { return defaultExtraDataLerpFunction( point1.extraData, point2.extraData, localT ); }
|
|
public BezierPoint.ExtraData GetExtraData( ExtraDataLerpFunction lerpFunction ) { return lerpFunction( point1.extraData, point2.extraData, localT ); }
|
|
public BezierPoint.ExtraData GetExtraData( float localT, ExtraDataLerpFunction lerpFunction ) { return lerpFunction( point1.extraData, point2.extraData, localT ); }
|
|
}
|
|
|
|
public class EvenlySpacedPointsHolder
|
|
{
|
|
public readonly BezierSpline spline;
|
|
public readonly float splineLength;
|
|
public readonly float[] uniformNormalizedTs;
|
|
|
|
public EvenlySpacedPointsHolder( BezierSpline spline, float splineLength, float[] uniformNormalizedTs )
|
|
{
|
|
this.spline = spline;
|
|
this.splineLength = splineLength;
|
|
this.uniformNormalizedTs = uniformNormalizedTs;
|
|
}
|
|
|
|
public float GetNormalizedTAtDistance( float distance )
|
|
{
|
|
return GetNormalizedTAtPercentage( distance / splineLength );
|
|
}
|
|
|
|
public float GetNormalizedTAtPercentage( float percentage )
|
|
{
|
|
if( !spline.loop )
|
|
{
|
|
if( percentage <= 0f )
|
|
return 0f;
|
|
else if( percentage >= 1f )
|
|
return 1f;
|
|
}
|
|
else
|
|
{
|
|
while( percentage < 0f )
|
|
percentage += 1f;
|
|
while( percentage >= 1f )
|
|
percentage -= 1f;
|
|
}
|
|
|
|
float indexRaw = ( uniformNormalizedTs.Length - 1 ) * percentage;
|
|
int index = (int) indexRaw;
|
|
return Mathf.LerpUnclamped( uniformNormalizedTs[index], uniformNormalizedTs[index + 1], indexRaw - index );
|
|
}
|
|
|
|
public float GetPercentageAtNormalizedT( float normalizedT )
|
|
{
|
|
if( !spline.loop )
|
|
{
|
|
if( normalizedT <= 0f )
|
|
return 0f;
|
|
else if( normalizedT >= 1f )
|
|
return 1f;
|
|
}
|
|
else
|
|
{
|
|
if( normalizedT < 0f )
|
|
normalizedT += 1f;
|
|
if( normalizedT >= 1f )
|
|
normalizedT -= 1f;
|
|
}
|
|
|
|
// Perform binary search
|
|
int lowerBound = 0;
|
|
int upperBound = uniformNormalizedTs.Length - 1;
|
|
while( lowerBound <= upperBound )
|
|
{
|
|
int index = lowerBound + ( ( upperBound - lowerBound ) >> 1 );
|
|
float arrElement = uniformNormalizedTs[index];
|
|
if( arrElement < normalizedT )
|
|
lowerBound = index + 1;
|
|
else if( arrElement > normalizedT )
|
|
upperBound = index - 1;
|
|
else
|
|
return index / (float) ( uniformNormalizedTs.Length - 1 );
|
|
}
|
|
|
|
float inverseLerp = ( normalizedT - uniformNormalizedTs[lowerBound] ) / ( uniformNormalizedTs[lowerBound - 1] - uniformNormalizedTs[lowerBound] );
|
|
return ( lowerBound - inverseLerp ) / ( uniformNormalizedTs.Length - 1 );
|
|
}
|
|
}
|
|
|
|
public class PointCache
|
|
{
|
|
public readonly Vector3[] positions, normals, tangents, bitangents;
|
|
public readonly BezierPoint.ExtraData[] extraDatas;
|
|
public readonly bool loop;
|
|
|
|
public PointCache( Vector3[] positions, Vector3[] normals, Vector3[] tangents, Vector3[] bitangents, BezierPoint.ExtraData[] extraDatas, bool loop )
|
|
{
|
|
this.positions = positions;
|
|
this.normals = normals;
|
|
this.tangents = tangents;
|
|
this.bitangents = bitangents;
|
|
this.extraDatas = extraDatas;
|
|
this.loop = loop;
|
|
}
|
|
|
|
public Vector3 GetPoint( float percentage ) { return LerpArray( positions, percentage ); }
|
|
public Vector3 GetNormal( float percentage ) { return LerpArray( normals, percentage ); }
|
|
public Vector3 GetTangent( float percentage ) { return LerpArray( tangents, percentage ); }
|
|
public Vector3 GetBitangent( float percentage ) { return LerpArray( bitangents, percentage ); }
|
|
|
|
public BezierPoint.ExtraData GetExtraData( float percentage ) { return GetExtraData( percentage, defaultExtraDataLerpFunction ); }
|
|
public BezierPoint.ExtraData GetExtraData( float percentage, ExtraDataLerpFunction lerpFunction )
|
|
{
|
|
if( !loop )
|
|
{
|
|
if( percentage <= 0f )
|
|
return extraDatas[0];
|
|
else if( percentage >= 1f )
|
|
return extraDatas[extraDatas.Length - 1];
|
|
}
|
|
else
|
|
{
|
|
if( percentage < 0f )
|
|
percentage += 1f;
|
|
if( percentage >= 1f )
|
|
percentage -= 1f;
|
|
}
|
|
|
|
float t = percentage * ( loop ? extraDatas.Length : ( extraDatas.Length - 1 ) );
|
|
|
|
int startIndex = (int) t;
|
|
int endIndex = startIndex + 1;
|
|
|
|
if( endIndex == extraDatas.Length )
|
|
endIndex = 0;
|
|
|
|
return lerpFunction( extraDatas[startIndex], extraDatas[endIndex], t - startIndex );
|
|
}
|
|
|
|
private Vector3 LerpArray( Vector3[] array, float percentage )
|
|
{
|
|
if( !loop )
|
|
{
|
|
if( percentage <= 0f )
|
|
return array[0];
|
|
else if( percentage >= 1f )
|
|
return array[array.Length - 1];
|
|
}
|
|
else
|
|
{
|
|
if( percentage < 0f )
|
|
percentage += 1f;
|
|
if( percentage >= 1f )
|
|
percentage -= 1f;
|
|
}
|
|
|
|
float t = percentage * ( loop ? array.Length : ( array.Length - 1 ) );
|
|
|
|
int startIndex = (int) t;
|
|
int endIndex = startIndex + 1;
|
|
|
|
if( endIndex == array.Length )
|
|
endIndex = 0;
|
|
|
|
return Vector3.LerpUnclamped( array[startIndex], array[endIndex], t - startIndex );
|
|
}
|
|
}
|
|
}
|
|
|
|
public partial class BezierPoint
|
|
{
|
|
public enum HandleMode { Free, Aligned, Mirrored };
|
|
|
|
[System.Serializable]
|
|
public struct ExtraData
|
|
{
|
|
public float c1, c2, c3, c4;
|
|
|
|
public ExtraData( float c1 = 0f, float c2 = 0f, float c3 = 0f, float c4 = 0f )
|
|
{
|
|
this.c1 = c1;
|
|
this.c2 = c2;
|
|
this.c3 = c3;
|
|
this.c4 = c4;
|
|
}
|
|
|
|
public static ExtraData Lerp( ExtraData a, ExtraData b, float t )
|
|
{
|
|
t = Mathf.Clamp01( t );
|
|
return new ExtraData(
|
|
a.c1 + ( b.c1 - a.c1 ) * t,
|
|
a.c2 + ( b.c2 - a.c2 ) * t,
|
|
a.c3 + ( b.c3 - a.c3 ) * t,
|
|
a.c4 + ( b.c4 - a.c4 ) * t );
|
|
}
|
|
|
|
public static ExtraData LerpUnclamped( ExtraData a, ExtraData b, float t )
|
|
{
|
|
return new ExtraData(
|
|
a.c1 + ( b.c1 - a.c1 ) * t,
|
|
a.c2 + ( b.c2 - a.c2 ) * t,
|
|
a.c3 + ( b.c3 - a.c3 ) * t,
|
|
a.c4 + ( b.c4 - a.c4 ) * t );
|
|
}
|
|
|
|
public static implicit operator ExtraData( Vector2 v ) { return new ExtraData( v.x, v.y ); }
|
|
public static implicit operator ExtraData( Vector3 v ) { return new ExtraData( v.x, v.y, v.z ); }
|
|
public static implicit operator ExtraData( Vector4 v ) { return new ExtraData( v.x, v.y, v.z, v.w ); }
|
|
public static implicit operator ExtraData( Quaternion q ) { return new ExtraData( q.x, q.y, q.z, q.w ); }
|
|
public static implicit operator ExtraData( Rect r ) { return new ExtraData( r.xMin, r.yMin, r.width, r.height ); }
|
|
#if UNITY_2017_2_OR_NEWER
|
|
public static implicit operator ExtraData( Vector2Int v ) { return new ExtraData( v.x, v.y ); }
|
|
public static implicit operator ExtraData( Vector3Int v ) { return new ExtraData( v.x, v.y, v.z ); }
|
|
public static implicit operator ExtraData( RectInt r ) { return new ExtraData( r.xMin, r.yMin, r.width, r.height ); }
|
|
#endif
|
|
|
|
public static implicit operator Vector2( ExtraData v ) { return new Vector2( v.c1, v.c2 ); }
|
|
public static implicit operator Vector3( ExtraData v ) { return new Vector3( v.c1, v.c2, v.c3 ); }
|
|
public static implicit operator Vector4( ExtraData v ) { return new Vector4( v.c1, v.c2, v.c3, v.c4 ); }
|
|
public static implicit operator Quaternion( ExtraData v ) { return new Quaternion( v.c1, v.c2, v.c3, v.c4 ); }
|
|
public static implicit operator Rect( ExtraData v ) { return new Rect( v.c1, v.c2, v.c3, v.c4 ); }
|
|
#if UNITY_2017_2_OR_NEWER
|
|
public static implicit operator Vector2Int( ExtraData v ) { return new Vector2Int( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ) ); }
|
|
public static implicit operator Vector3Int( ExtraData v ) { return new Vector3Int( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ), Mathf.RoundToInt( v.c3 ) ); }
|
|
public static implicit operator RectInt( ExtraData v ) { return new RectInt( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ), Mathf.RoundToInt( v.c3 ), Mathf.RoundToInt( v.c4 ) ); }
|
|
#endif
|
|
|
|
public static bool operator ==( ExtraData d1, ExtraData d2 ) { return d1.c1 == d2.c1 && d1.c2 == d2.c2 && d1.c3 == d2.c3 && d1.c4 == d2.c4; }
|
|
public static bool operator !=( ExtraData d1, ExtraData d2 ) { return d1.c1 != d2.c1 || d1.c2 != d2.c2 || d1.c3 != d2.c3 || d1.c4 != d2.c4; }
|
|
|
|
public override bool Equals( object obj ) { return obj is ExtraData && this == (ExtraData) obj; }
|
|
public override int GetHashCode() { return unchecked((int) ( ( ( ( 17 * 23 + c1 ) * 23 + c2 ) * 23 + c3 ) * 23 + c4 )); }
|
|
public override string ToString() { return ( (Vector4) this ).ToString(); }
|
|
}
|
|
}
|
|
} |