推进度!
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UI_Spline_Renderer
|
||||
{
|
||||
public struct CopiedNativeSpline : ISpline, IDisposable
|
||||
{
|
||||
[ReadOnly]
|
||||
NativeArray<BezierKnot> m_Knots;
|
||||
|
||||
[ReadOnly]
|
||||
NativeArray<BezierCurve> m_Curves;
|
||||
|
||||
[ReadOnly]
|
||||
NativeArray<DistanceToInterpolation> m_SegmentLengthsLookupTable;
|
||||
|
||||
bool m_Closed;
|
||||
float m_Length;
|
||||
const int k_SegmentResolution = 30;
|
||||
|
||||
public NativeArray<BezierKnot> Knots => m_Knots;
|
||||
|
||||
public NativeArray<BezierCurve> Curves => m_Curves;
|
||||
|
||||
public bool Closed => m_Closed;
|
||||
public int Count => m_Knots.Length;
|
||||
|
||||
public readonly float GetLength() => m_Length;
|
||||
public BezierKnot this[int index] => m_Knots[index];
|
||||
public IEnumerator<BezierKnot> GetEnumerator() => m_Knots.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
|
||||
public CopiedNativeSpline(NativeArray<BezierKnot> knots, bool closed, float4x4 transform, Allocator allocator = Allocator.Temp)
|
||||
{
|
||||
int kc = knots.Length;
|
||||
m_Knots = knots;
|
||||
m_Curves = new NativeArray<BezierCurve>(kc, allocator);
|
||||
m_SegmentLengthsLookupTable = new NativeArray<DistanceToInterpolation>(kc * k_SegmentResolution, allocator);
|
||||
m_Closed = closed;
|
||||
m_Length = 0f;
|
||||
|
||||
NativeArray<DistanceToInterpolation> distanceToTimes = new NativeArray<DistanceToInterpolation>(k_SegmentResolution, Allocator.Temp);
|
||||
|
||||
if (knots.Length > 0)
|
||||
{
|
||||
BezierKnot cur = knots[0].Transform(transform);
|
||||
for (int i = 0; i < kc; ++i)
|
||||
{
|
||||
BezierKnot next = knots[(i + 1) % kc].Transform(transform);
|
||||
m_Knots[i] = cur;
|
||||
|
||||
m_Curves[i] = new BezierCurve(cur, next);
|
||||
InternalUtility.CalculateCurveLengths(m_Curves[i], distanceToTimes);
|
||||
|
||||
if (m_Closed || i < kc - 1)
|
||||
m_Length += distanceToTimes[k_SegmentResolution - 1].Distance;
|
||||
|
||||
for (int index = 0; index < k_SegmentResolution; index++)
|
||||
{
|
||||
m_SegmentLengthsLookupTable[i * k_SegmentResolution + index] = distanceToTimes[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;
|
||||
}
|
||||
|
||||
public float3 GetCurveUpVector(int index, float t)
|
||||
{
|
||||
return this.CalculateUpVector(index, t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release allocated resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
m_Knots.Dispose();
|
||||
m_Curves.Dispose();
|
||||
m_SegmentLengthsLookupTable.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: ea2e3adba3aa4b25a044aa103d6fc756
|
||||
timeCreated: 1713945997
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI_Spline_Renderer
|
||||
{
|
||||
internal struct NativeColorGradient : IDisposable
|
||||
{
|
||||
// y is time.
|
||||
[ReadOnly]
|
||||
public NativeArray<float2> alphaKeyFrames;
|
||||
// w is time.
|
||||
[ReadOnly]
|
||||
public NativeArray<float4> colorKeyFrames;
|
||||
|
||||
public Color Evaluate(float t)
|
||||
{
|
||||
var nextAlphaIdx = -1;
|
||||
for (int i = 0; i < alphaKeyFrames.Length; i++)
|
||||
{
|
||||
if(t > alphaKeyFrames[i].y) continue;
|
||||
nextAlphaIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
var nextColorKeyIdx = -1;
|
||||
for (int i = 0; i < colorKeyFrames.Length; i++)
|
||||
{
|
||||
if (t > colorKeyFrames[i].w) continue;
|
||||
nextColorKeyIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
float alpha;
|
||||
if (nextAlphaIdx == -1)
|
||||
{
|
||||
alpha = alphaKeyFrames[^1].x;
|
||||
}
|
||||
else if (nextAlphaIdx == 0)
|
||||
{
|
||||
alpha = alphaKeyFrames[0].x;
|
||||
}
|
||||
else
|
||||
{
|
||||
var preAlpha = alphaKeyFrames[nextAlphaIdx - 1];
|
||||
var nextAlpha = alphaKeyFrames[nextAlphaIdx];
|
||||
var remappedT = t.Remap(0, 1, preAlpha.y, nextAlpha.y);
|
||||
alpha = math.lerp(preAlpha, nextAlpha, remappedT).x;
|
||||
}
|
||||
|
||||
|
||||
Color color;
|
||||
if(nextColorKeyIdx == -1)
|
||||
{
|
||||
color = toColor(colorKeyFrames[^1]);
|
||||
}
|
||||
else if(nextColorKeyIdx == 0)
|
||||
{
|
||||
color = toColor(colorKeyFrames[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var preColor = toColor(colorKeyFrames[nextColorKeyIdx - 1]);
|
||||
var nextKey = toColor(colorKeyFrames[nextColorKeyIdx]);
|
||||
var remappedT = (t - preColor.a) / (nextKey.a - preColor.a);
|
||||
color = Color.Lerp(preColor, nextKey, remappedT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
color.a = alpha;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
Color toColor(float4 f)
|
||||
{
|
||||
return new Color(f.x, f.y, f.z, f.w);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
alphaKeyFrames.Dispose();
|
||||
colorKeyFrames.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: baf1300b266e4ca887cad7e70d1a70cb
|
||||
timeCreated: 1690408892
|
||||
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI_Spline_Renderer
|
||||
{
|
||||
internal readonly struct NativeCurve : IDisposable
|
||||
{
|
||||
private const int TRUE = 1;
|
||||
private const int FALSE = 1;
|
||||
public readonly NativeArray<Keyframe> Keys;
|
||||
private readonly int owner;
|
||||
|
||||
public NativeCurve(AnimationCurve c, Allocator alloc)
|
||||
{
|
||||
Keys = new NativeArray<Keyframe>(c.keys, alloc);
|
||||
owner = TRUE;
|
||||
}
|
||||
|
||||
public NativeCurve(int size, Allocator alloc)
|
||||
{
|
||||
Keys = new NativeArray<Keyframe>(size, alloc);
|
||||
owner = TRUE;
|
||||
}
|
||||
|
||||
public NativeCurve(Keyframe[] keyframes, Allocator alloc)
|
||||
{
|
||||
Keys = new NativeArray<Keyframe>(keyframes, alloc);
|
||||
owner = TRUE;
|
||||
}
|
||||
|
||||
public NativeCurve(NativeArray<Keyframe> keyframes, Allocator alloc)
|
||||
{
|
||||
Keys = new NativeArray<Keyframe>(keyframes, alloc);
|
||||
owner = TRUE;
|
||||
}
|
||||
|
||||
public NativeCurve(NativeArray<Keyframe> keyframes)
|
||||
{
|
||||
Keys = keyframes;
|
||||
owner = FALSE;
|
||||
}
|
||||
|
||||
public NativeCurve(NativeCurve other)
|
||||
{
|
||||
Keys = other.Keys;
|
||||
owner = FALSE;
|
||||
}
|
||||
|
||||
public NativeCurve(NativeCurve other, Allocator alloc)
|
||||
{
|
||||
Keys = new NativeArray<Keyframe>(other.Keys, alloc);
|
||||
owner = TRUE;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (owner != 0)
|
||||
{
|
||||
Keys.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public float Evaluate(float time)
|
||||
{
|
||||
return CurveSampling.ThreadSafe.Evaluate(Keys, time);
|
||||
}
|
||||
|
||||
public int Length => Keys.Length;
|
||||
public float Duration => Keys[Length - 1].time - Keys[0].time;
|
||||
}
|
||||
|
||||
public static class CurveSampling
|
||||
{
|
||||
const float DefaultWeight = 0;
|
||||
|
||||
public static class ThreadSafe
|
||||
{
|
||||
public static float Evaluate(NativeArray<Keyframe> keys, float curveT)
|
||||
{
|
||||
return EvaluateWithinRange(keys, curveT, 0, keys.Length - 1);
|
||||
}
|
||||
|
||||
public static float EvaluateWithHint(NativeArray<Keyframe> keys, float curveT, ref int hintIndex)
|
||||
{
|
||||
int startIndex = 0;
|
||||
int endIndex = keys.Length - 1;
|
||||
if (endIndex <= hintIndex)
|
||||
return keys[hintIndex].value;
|
||||
|
||||
// wrap time
|
||||
curveT = math.clamp(curveT, keys[hintIndex].time, keys[endIndex].time);
|
||||
FindIndexForSampling(keys, curveT, startIndex, endIndex, hintIndex, out int lhsIndex, out int rhsIndex);
|
||||
|
||||
Keyframe lhs = keys[hintIndex];
|
||||
Keyframe rhs = keys[rhsIndex];
|
||||
return InterpolateKeyframe(lhs, rhs, curveT);
|
||||
}
|
||||
|
||||
public static float EvaluateWithinRange(NativeArray<Keyframe> keys, float curveT, int startIndex,
|
||||
int endIndex)
|
||||
{
|
||||
if (endIndex <= startIndex)
|
||||
return keys[startIndex].value;
|
||||
|
||||
// wrap time
|
||||
curveT = math.clamp(curveT, keys[startIndex].time, keys[endIndex].time);
|
||||
FindIndexForSampling(keys, curveT, startIndex, endIndex, -1, out int lhsIndex, out int rhsIndex);
|
||||
|
||||
Keyframe lhs = keys[lhsIndex];
|
||||
Keyframe rhs = keys[rhsIndex];
|
||||
return InterpolateKeyframe(lhs, rhs, curveT);
|
||||
}
|
||||
|
||||
static void FindIndexForSampling(NativeArray<Keyframe> keys, float curveT, int start, int end,
|
||||
int hint,
|
||||
out int lhs, out int rhs)
|
||||
{
|
||||
if (hint != -1)
|
||||
{
|
||||
hint = math.clamp(hint, start, end);
|
||||
// We can not use the cache time or time end since that is in unwrapped time space!
|
||||
float time = keys[hint].time;
|
||||
|
||||
if (curveT > time)
|
||||
{
|
||||
const int kMaxLookahead = 3;
|
||||
for (int i = 0; i < kMaxLookahead; i++)
|
||||
{
|
||||
int index = hint + i;
|
||||
if (index + 1 < end && keys[index + 1].time > curveT)
|
||||
{
|
||||
lhs = index;
|
||||
rhs = math.min(lhs + 1, end);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to using binary search
|
||||
// upper bound (first value larger than curveT)
|
||||
int __len = end - start;
|
||||
int __half;
|
||||
int __middle;
|
||||
int __first = start;
|
||||
while (__len > 0)
|
||||
{
|
||||
__half = __len >> 1;
|
||||
__middle = __first + __half;
|
||||
|
||||
var mid = keys[__middle];
|
||||
if (curveT < mid.time)
|
||||
__len = __half;
|
||||
else
|
||||
{
|
||||
__first = __middle;
|
||||
++__first;
|
||||
__len = __len - __half - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If not within range, we pick the last element twice
|
||||
lhs = __first - 1;
|
||||
rhs = math.min(end, __first);
|
||||
}
|
||||
|
||||
public static float InterpolateKeyframe(Keyframe lhs, Keyframe rhs, float curveT)
|
||||
{
|
||||
float output;
|
||||
|
||||
if ((lhs.weightedMode & WeightedMode.Out) != 0 || (rhs.weightedMode & WeightedMode.In) != 0)
|
||||
output = BezierInterpolate(curveT, lhs, rhs);
|
||||
else
|
||||
output = HermiteInterpolate(curveT, lhs, rhs);
|
||||
|
||||
HandleSteppedCurve(lhs, rhs, ref output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static float HermiteInterpolate(float curveT, Keyframe lhs, Keyframe rhs)
|
||||
{
|
||||
float dx = rhs.time - lhs.time;
|
||||
float m1;
|
||||
float m2;
|
||||
float t;
|
||||
if (dx != 0.0F)
|
||||
{
|
||||
t = (curveT - lhs.time) / dx;
|
||||
m1 = lhs.outTangent * dx;
|
||||
m2 = rhs.inTangent * dx;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = 0.0F;
|
||||
m1 = 0;
|
||||
m2 = 0;
|
||||
}
|
||||
|
||||
return HermiteInterpolate(t, lhs.value, m1, m2, rhs.value);
|
||||
}
|
||||
|
||||
static float HermiteInterpolate(float t, float p0, float m0, float m1, float p1)
|
||||
{
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
|
||||
float a = 2.0F * t3 - 3.0F * t2 + 1.0F;
|
||||
float b = t3 - 2.0F * t2 + t;
|
||||
float c = t3 - t2;
|
||||
float d = -2.0F * t3 + 3.0F * t2;
|
||||
|
||||
return a * p0 + b * m0 + c * m1 + d * p1;
|
||||
}
|
||||
|
||||
static float BezierInterpolate(float curveT, Keyframe lhs, Keyframe rhs)
|
||||
{
|
||||
float lhsOutWeight = (lhs.weightedMode & WeightedMode.Out) != 0 ? lhs.outWeight : DefaultWeight;
|
||||
float rhsInWeight = (rhs.weightedMode & WeightedMode.In) != 0 ? rhs.inWeight : DefaultWeight;
|
||||
|
||||
float dx = rhs.time - lhs.time;
|
||||
if (dx == 0.0F)
|
||||
return lhs.value;
|
||||
|
||||
return BezierInterpolate((curveT - lhs.time) / dx, lhs.value, lhs.outTangent * dx, lhsOutWeight,
|
||||
rhs.value, rhs.inTangent * dx, rhsInWeight);
|
||||
}
|
||||
|
||||
static float FAST_CBRT_POSITIVE(float x)
|
||||
{
|
||||
return math.exp(math.log(x) / 3.0f);
|
||||
}
|
||||
|
||||
static float FAST_CBRT(float x)
|
||||
{
|
||||
return (((x) < 0) ? -math.exp(math.log(-(x)) / 3.0f) : math.exp(math.log(x) / 3.0f));
|
||||
}
|
||||
|
||||
static float BezierExtractU(float t, float w1, float w2)
|
||||
{
|
||||
float a = 3.0F * w1 - 3.0F * w2 + 1.0F;
|
||||
float b = -6.0F * w1 + 3.0F * w2;
|
||||
float c = 3.0F * w1;
|
||||
float d = -t;
|
||||
|
||||
if (math.abs(a) > 1e-3f)
|
||||
{
|
||||
float p = -b / (3.0F * a);
|
||||
float p2 = p * p;
|
||||
float p3 = p2 * p;
|
||||
|
||||
float q = p3 + (b * c - 3.0F * a * d) / (6.0F * a * a);
|
||||
float q2 = q * q;
|
||||
|
||||
float r = c / (3.0F * a);
|
||||
float rmp2 = r - p2;
|
||||
|
||||
float s = q2 + rmp2 * rmp2 * rmp2;
|
||||
|
||||
if (s < 0.0F)
|
||||
{
|
||||
float ssi = math.sqrt(-s);
|
||||
float r_1 = math.sqrt(-s + q2);
|
||||
float phi = math.atan2(ssi, q);
|
||||
|
||||
float r_3 = FAST_CBRT_POSITIVE(r_1);
|
||||
float phi_3 = phi / 3.0F;
|
||||
|
||||
// Extract cubic roots.
|
||||
float u1 = 2.0F * r_3 * math.cos(phi_3) + p;
|
||||
float u2 = 2.0F * r_3 * math.cos(phi_3 + 2.0F * (float)math.PI / 3.0f) + p;
|
||||
float u3 = 2.0F * r_3 * math.cos(phi_3 - 2.0F * (float)math.PI / 3.0f) + p;
|
||||
|
||||
if (u1 >= 0.0F && u1 <= 1.0F)
|
||||
return u1;
|
||||
else if (u2 >= 0.0F && u2 <= 1.0F)
|
||||
return u2;
|
||||
else if (u3 >= 0.0F && u3 <= 1.0F)
|
||||
return u3;
|
||||
|
||||
// Aiming at solving numerical imprecision when u is outside [0,1].
|
||||
return (t < 0.5F) ? 0.0F : 1.0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
float ss = math.sqrt(s);
|
||||
float u = FAST_CBRT(q + ss) + FAST_CBRT(q - ss) + p;
|
||||
|
||||
if (u >= 0.0F && u <= 1.0F)
|
||||
return u;
|
||||
|
||||
// Aiming at solving numerical imprecision when u is outside [0,1].
|
||||
return (t < 0.5F) ? 0.0F : 1.0F;
|
||||
}
|
||||
}
|
||||
|
||||
if (math.abs(b) > 1e-3f)
|
||||
{
|
||||
float s = c * c - 4.0F * b * d;
|
||||
float ss = math.sqrt(s);
|
||||
|
||||
float u1 = (-c - ss) / (2.0F * b);
|
||||
float u2 = (-c + ss) / (2.0F * b);
|
||||
|
||||
if (u1 >= 0.0F && u1 <= 1.0F)
|
||||
return u1;
|
||||
else if (u2 >= 0.0F && u2 <= 1.0F)
|
||||
return u2;
|
||||
|
||||
// Aiming at solving numerical imprecision when u is outside [0,1].
|
||||
return (t < 0.5F) ? 0.0F : 1.0F;
|
||||
}
|
||||
|
||||
if (math.abs(c) > 1e-3f)
|
||||
{
|
||||
return (-d / c);
|
||||
}
|
||||
|
||||
return 0.0F;
|
||||
}
|
||||
|
||||
static float BezierInterpolate(float t, float v1, float m1, float w1, float v2, float m2, float w2)
|
||||
{
|
||||
float u = BezierExtractU(t, w1, 1.0F - w2);
|
||||
return BezierInterpolate(u, v1, w1 * m1 + v1, v2 - w2 * m2, v2);
|
||||
}
|
||||
|
||||
static float BezierInterpolate(float t, float p0, float p1, float p2, float p3)
|
||||
{
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
float omt = 1.0F - t;
|
||||
float omt2 = omt * omt;
|
||||
float omt3 = omt2 * omt;
|
||||
|
||||
return omt3 * p0 + 3.0F * t * omt2 * p1 + 3.0F * t2 * omt * p2 + t3 * p3;
|
||||
}
|
||||
|
||||
static void HandleSteppedCurve(Keyframe lhs, Keyframe rhs, ref float value)
|
||||
{
|
||||
if (float.IsInfinity(lhs.outTangent) || float.IsInfinity(rhs.inTangent))
|
||||
value = lhs.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a04bbc35d484a798423531879e71d9c
|
||||
timeCreated: 1696727209
|
||||
@@ -0,0 +1,571 @@
|
||||
#if ENABLE_SPLINES
|
||||
#if ENABLE_COLLECTIONS
|
||||
#if ENABLE_MATHEMATICS
|
||||
#if ENABLE_BURST
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UI_Spline_Renderer
|
||||
{
|
||||
[BurstCompile]
|
||||
internal struct SplineExtrudeJob : IJob
|
||||
{
|
||||
[ReadOnly] public NativeSpline spline;
|
||||
|
||||
[ReadOnly] public NativeCurve widthCurve;
|
||||
[ReadOnly] public float width;
|
||||
[ReadOnly] public bool keepZeroZ;
|
||||
[ReadOnly] public bool keepBillboard;
|
||||
[ReadOnly] public float2 clipRange;
|
||||
|
||||
[ReadOnly] public float2 uvMultiplier;
|
||||
[ReadOnly] public float2 uvOffset;
|
||||
[ReadOnly] public UVMode uvMode;
|
||||
[ReadOnly] public Color color;
|
||||
[ReadOnly] internal NativeColorGradient colorGradient;
|
||||
|
||||
[ReadOnly] public int resolution;
|
||||
[ReadOnly] public bool smooth;
|
||||
[ReadOnly] public bool roundEnds;
|
||||
|
||||
public NativeList<UIVertex> vertices;
|
||||
public NativeList<int3> triangles;
|
||||
public int addedEdgeCount;
|
||||
|
||||
int sampleCount => (int)(resolution * length * 0.03f);
|
||||
float v;
|
||||
float length;
|
||||
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
if (spline.Count < 2) return;
|
||||
|
||||
var edgePoints = new NativeList<EdgePoint>(Allocator.Temp);
|
||||
if(smooth)
|
||||
{
|
||||
SmoothSample(ref edgePoints);
|
||||
Extrude(in edgePoints);
|
||||
}
|
||||
else
|
||||
{
|
||||
UniformEvaluate(ref edgePoints);
|
||||
Extrude(in edgePoints);
|
||||
}
|
||||
|
||||
if (roundEnds && ((spline.Closed && (clipRange.x > math.EPSILON || clipRange.y < 1)) || !spline.Closed))
|
||||
{
|
||||
MakeRoundEdges(in edgePoints);
|
||||
}
|
||||
}
|
||||
|
||||
void SmoothSample(ref NativeList<EdgePoint> edgePoints)
|
||||
{
|
||||
var samples = new NativeList<EdgePoint>(Allocator.Temp);
|
||||
|
||||
// 전체 스플라인 샘플링
|
||||
length = spline.GetLength();
|
||||
for (int i = 0; i < spline.Count; i++)
|
||||
{
|
||||
var t = spline.CurveToSplineT(i);
|
||||
var unitT = (GetWidthAt(t) / length) * 0.5f;
|
||||
var knot = spline[i];
|
||||
add_smooth_sample_point(in spline, samples, t, unitT, math.length(knot.TangentIn), math.length(knot.TangentOut));
|
||||
}
|
||||
|
||||
|
||||
var sampleUnitT = 1f / sampleCount;
|
||||
for (int i = 0; i < spline.Count; i++)
|
||||
{
|
||||
var t = spline.CurveToSplineT(i);
|
||||
|
||||
var t_1 = spline.CurveToSplineT(i - 1);
|
||||
var t1 = spline.CurveToSplineT(i + 1);
|
||||
|
||||
var leftT = math.lerp(t, t_1, 0.5f);
|
||||
var rightT = math.lerp(t, t1, 0.5f);
|
||||
|
||||
var leftCount = (int)(math.abs(t - leftT) / sampleUnitT);
|
||||
var rightCount = (int)(math.abs(rightT - t) / sampleUnitT);
|
||||
|
||||
if (spline.Closed)
|
||||
{
|
||||
if(i == 0)
|
||||
{
|
||||
t_1 = spline.CurveToSplineT(spline.Count - 1);
|
||||
leftT = math.lerp(1, t_1, 0.5f);
|
||||
leftCount = (int)(math.abs(1 - leftT) / sampleUnitT);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == 0) leftCount = 0;
|
||||
if (i == spline.Count - 1) rightCount = 0;
|
||||
}
|
||||
|
||||
for (int j = 1; j < leftCount+1; j++)
|
||||
{
|
||||
var tt = t - sampleUnitT*j;
|
||||
if (i == 0 && spline.Closed) tt += 1;
|
||||
add_smooth_sample_point(in spline, samples, tt, sampleUnitT, 1, 1);
|
||||
}
|
||||
for (int j = 1; j < rightCount+1; j++)
|
||||
{
|
||||
var tt = t + sampleUnitT*j;
|
||||
add_smooth_sample_point(in spline, samples, tt, sampleUnitT, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// SortByT(ref samples);
|
||||
samples.Sort();
|
||||
|
||||
// 꼭짓점 스무딩
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
var sample = samples[i];
|
||||
if(sample.angle < 10) continue;
|
||||
|
||||
// 열린 스플라인의 시작점과 끝점에선 스무딩을 안함.
|
||||
if(!spline.Closed && (i == 0 || i == samples.Length - 1)) continue;
|
||||
|
||||
var preSampleIdx = previous_index(i, samples.Length);
|
||||
var preSample = samples[preSampleIdx];
|
||||
var nextSampleIdx = next_index(i, samples.Length);
|
||||
var nextSample = samples[nextSampleIdx];
|
||||
|
||||
var mp0 = sample.pos;
|
||||
var mp1 = math.lerp(preSample.pos, nextSample.pos, 0.5f);
|
||||
var tanLength = (sample.tanInLength + sample.tanOutLength);
|
||||
var curvature = tanLength < math.EPSILON ? 0 : tanLength / GetWidthAt(sample.t);
|
||||
curvature = 1 - math.clamp(curvature, 0, 1);
|
||||
|
||||
var lerp = InternalUtility.Remap(sample.angle, 0, 180, 0, curvature);
|
||||
var middlePos = math.lerp(mp0, mp1, lerp);
|
||||
|
||||
|
||||
sample.pos = middlePos;
|
||||
samples[i] = sample;
|
||||
}
|
||||
|
||||
var knots = new NativeArray<BezierKnot>(samples.Length, Allocator.Temp);
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
var sample = samples[i];
|
||||
var preSamplePos = samples[previous_index(i, samples.Length)].pos;
|
||||
var nextSamplePos = samples[next_index(i, samples.Length)].pos;
|
||||
var knot = SplineUtility.GetAutoSmoothKnot(sample.pos, preSamplePos, nextSamplePos, sample.up);
|
||||
knots[i] = knot;
|
||||
}
|
||||
|
||||
var nSpline = new CopiedNativeSpline(knots, spline.Closed, float4x4.identity);
|
||||
|
||||
for (int i = 0; i < nSpline.Count; i++)
|
||||
{
|
||||
var knot = nSpline[i];
|
||||
var t = nSpline.CurveToSplineT(i);
|
||||
// nSpline.Evaluate(t, out var pos, out var tan, out var up);
|
||||
var tan = nSpline.EvaluateTangent(t);
|
||||
var up = keepBillboard ? new float3(0,0,-1) : nSpline.EvaluateUpVector(t);
|
||||
|
||||
var sample = samples[i];
|
||||
sample.t = t;
|
||||
sample.pos = knot.Position;
|
||||
sample.tan = tan;
|
||||
sample.up = up;
|
||||
samples[i] = sample;
|
||||
}
|
||||
|
||||
var tempEdgePoints = new NativeList<EdgePoint>(Allocator.Temp);
|
||||
// 각 세그먼트의 EdgePoint 계산
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
var isClosingSegment = i == samples.Length - 1 && spline.Closed;
|
||||
|
||||
var sample0 = samples[i];
|
||||
var sample1 = isClosingSegment ? samples[0] : samples[i + 1];
|
||||
|
||||
// 열린 스플라인의 마지막 세그먼트에선 포인트를 추가하기만 함.
|
||||
// (length - 1)은 마지막 ep라서 열린 스플라인에서 마지막 세그먼트가 아님
|
||||
if (i == samples.Length - 2 && !spline.Closed)
|
||||
{
|
||||
tempEdgePoints.Add(sample0);
|
||||
tempEdgePoints.Add(sample1);
|
||||
break;
|
||||
}
|
||||
|
||||
var inTan = sample0.tan;
|
||||
var outTan = sample1.tan;
|
||||
|
||||
var angle = InternalUtility.Angle(inTan, outTan);
|
||||
|
||||
var edgePointCount = (int)math.round((angle / 30f) * 5);
|
||||
|
||||
// 실제 Extrude에 사용할 EdgePoints 추가
|
||||
tempEdgePoints.Add(sample0);
|
||||
for (int j = 0; j < edgePointCount; j++)
|
||||
{
|
||||
var t = (float)(j + 1) / (edgePointCount + 1);
|
||||
t = t.Remap(0, 1, sample0.t, isClosingSegment ? 1 : sample1.t);
|
||||
|
||||
var ep = GetEdgePoint(nSpline, t);
|
||||
tempEdgePoints.Add(ep);
|
||||
}
|
||||
if(i == samples.Length - 1 && spline.Closed) tempEdgePoints.Add(sample1);
|
||||
}
|
||||
|
||||
var clipStartAdded = false;
|
||||
var clipEndAdded = false;
|
||||
var shouldClipping = clipRange.x > math.EPSILON || clipRange.y < 1;
|
||||
for (int i = 0; i < tempEdgePoints.Length; i++)
|
||||
{
|
||||
var point = tempEdgePoints[i];
|
||||
if (spline.Closed && shouldClipping && point.t == 0 && clipRange.y < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(shouldClipping && (point.t < clipRange.x || point.t > clipRange.y)) continue;
|
||||
if (point.t == clipRange.x)
|
||||
{
|
||||
edgePoints.Add(point);
|
||||
clipStartAdded = true;
|
||||
continue;
|
||||
}
|
||||
if (point.t == clipRange.y)
|
||||
{
|
||||
edgePoints.Add(point);
|
||||
clipEndAdded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (clipRange.x < point.t && point.t < clipRange.y)
|
||||
{
|
||||
if(!clipStartAdded)
|
||||
{
|
||||
var ep = GetEdgePoint(nSpline, clipRange.x);
|
||||
edgePoints.Add(ep);
|
||||
clipStartAdded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
edgePoints.Add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clipEndAdded)
|
||||
{
|
||||
var ep = GetEdgePoint(nSpline, clipRange.y);
|
||||
edgePoints.Add(ep);
|
||||
}
|
||||
}
|
||||
|
||||
static void SortByT(ref NativeList<EdgePoint> samples)
|
||||
{
|
||||
for (int i = 0; i < samples.Length - 1; i++)
|
||||
{
|
||||
for (int j = i + 1; j < samples.Length; j++)
|
||||
{
|
||||
if (samples[i].t > samples[j].t)
|
||||
{
|
||||
// 두 객체의 위치를 교환
|
||||
(samples[i], samples[j]) = (samples[j], samples[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void add_smooth_sample_point(in NativeSpline s, NativeList<EdgePoint> samples, float t, float unitT, float tanInLength, float tanOutLength)
|
||||
{
|
||||
var preT = InternalUtility.Repeat(t - unitT, 1);
|
||||
var preTTan = s.EvaluateTangent(preT);
|
||||
|
||||
var nextT = InternalUtility.Repeat(t + unitT, 1);
|
||||
var nextTTan = s.EvaluateTangent(nextT);
|
||||
|
||||
var angle = InternalUtility.Angle(preTTan, nextTTan);
|
||||
|
||||
var ep = GetEdgePoint(s, t, angle, tanInLength, tanOutLength);
|
||||
samples.Add(ep);
|
||||
}
|
||||
int previous_index(int i, int maxLength)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
if (spline.Closed) return maxLength - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return i - 1;
|
||||
}
|
||||
int next_index(int i, int maxLength)
|
||||
{
|
||||
if (i == maxLength - 1)
|
||||
{
|
||||
if (spline.Closed) return 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
bool is_used(float t, float unitT, in NativeList<EdgePoint> list)
|
||||
{
|
||||
for (int i = 0; i < list.Length; i++)
|
||||
{
|
||||
var diff = t.CircularDistance(list[i].t, list.Length - 1);
|
||||
if (diff < unitT) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
void UniformEvaluate(ref NativeList<EdgePoint> edgePoints)
|
||||
{
|
||||
length = spline.GetLength();
|
||||
|
||||
var clippedLength = length * (clipRange.y - clipRange.x);
|
||||
var edgeCount = math.max((int)math.ceil(clippedLength * resolution * 0.05f), 1) + 2;
|
||||
|
||||
|
||||
for (int i = 0; i < edgeCount; i++)
|
||||
{
|
||||
var t = (float)i / (edgeCount - 1);
|
||||
t = t.Remap(0, 1, clipRange.x, clipRange.y);
|
||||
var ep = GetEdgePoint(spline, t);
|
||||
edgePoints.Add(ep);
|
||||
}
|
||||
}
|
||||
|
||||
EdgePoint GetEdgePoint(in NativeSpline s, float t, float angle = 0, float tanInLength = 0, float tanOutLength = 0)
|
||||
{
|
||||
if (keepBillboard)
|
||||
{
|
||||
var pos = s.EvaluatePosition(t);
|
||||
var tan = s.EvaluateTangent(t);
|
||||
return new EdgePoint(t, angle, pos, tan, new float3(0,0,-1), tanInLength, tanOutLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
s.Evaluate(t, out var pos, out var tan, out var up);
|
||||
|
||||
return new EdgePoint(t, angle, pos, tan, up, tanInLength, tanOutLength);
|
||||
}
|
||||
}
|
||||
EdgePoint GetEdgePoint(in CopiedNativeSpline s, float t)
|
||||
{
|
||||
if (keepBillboard)
|
||||
{
|
||||
var pos = s.EvaluatePosition(t);
|
||||
var tan = s.EvaluateTangent(t);
|
||||
return new EdgePoint(t, 0, pos, tan, new float3(0,0,-1));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.Evaluate(t, out var pos, out var tan, out var up);
|
||||
|
||||
return new EdgePoint(t, 0, pos, tan, up);
|
||||
}
|
||||
}
|
||||
|
||||
void Extrude(in NativeList<EdgePoint> edgePoints)
|
||||
{
|
||||
for (int i = 0; i < edgePoints.Length; i++)
|
||||
{
|
||||
var ep = edgePoints[i];
|
||||
var t = ep.t;
|
||||
var pos = ep.pos;
|
||||
var tan = ep.tan;
|
||||
var up = ep.up;
|
||||
|
||||
// resolve (0,0,0) tangent
|
||||
if (tan is { x: 0, y: 0 })
|
||||
{
|
||||
var prev = i == 0 ? pos : edgePoints[^2].pos;
|
||||
var next = i == edgePoints.Length - 1 ? pos : edgePoints[i + 1].pos;
|
||||
tan = next - prev;
|
||||
}
|
||||
|
||||
|
||||
InternalUtility.ExtrudeEdge(
|
||||
GetWidthAt(t), GetVAt(t, i), GetColorAt(t), ref pos, tan, up,
|
||||
keepBillboard, keepZeroZ, uvMultiplier, uvOffset, out var v0, out var v1);
|
||||
|
||||
AddVert(in v0, in v1);
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
AddQuadUsingLastVertices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddVert(in UIVertex left, in UIVertex right)
|
||||
{
|
||||
vertices.Add(left);
|
||||
vertices.Add(right);
|
||||
}
|
||||
|
||||
void AddQuadUsingLastVertices()
|
||||
{
|
||||
var vi = vertices.Length;
|
||||
triangles.Add(new int3
|
||||
(
|
||||
vi - 2,
|
||||
vi - 3,
|
||||
vi - 4
|
||||
));
|
||||
triangles.Add(new int3
|
||||
(
|
||||
vi - 2,
|
||||
vi - 1,
|
||||
vi - 3
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
float GetWidthAt(float t)
|
||||
{
|
||||
return width * widthCurve.Evaluate(t);
|
||||
}
|
||||
|
||||
Color GetColorAt(float t)
|
||||
{
|
||||
return color * colorGradient.Evaluate(t);
|
||||
}
|
||||
|
||||
float GetVAt(float t, int i)
|
||||
{
|
||||
switch (uvMode)
|
||||
{
|
||||
case UVMode.Tile:
|
||||
return length / width * t;
|
||||
case UVMode.RepeatPerSegment:
|
||||
return i;
|
||||
case UVMode.Stretch:
|
||||
return t;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
void MakeRoundEdges(in NativeList<EdgePoint> edgePoints)
|
||||
{
|
||||
var sl = vertices[0];
|
||||
var sr = vertices[1];
|
||||
|
||||
var el = vertices[^2];
|
||||
var er = vertices[^1];
|
||||
{
|
||||
// at start
|
||||
var start = edgePoints[0];
|
||||
var t = start.t;
|
||||
var pos = start.pos;
|
||||
var tan = start.tan;
|
||||
var up = start.up;
|
||||
|
||||
var V = GetVAt(t, 0);
|
||||
var uv = new float2(0, V) * uvMultiplier - uvOffset;
|
||||
var clr = GetColorAt(t);
|
||||
|
||||
MakeRoundEdge(in pos, sl.position, sr.position, false, up, uv, clr);
|
||||
}
|
||||
|
||||
{
|
||||
// at end
|
||||
var end = edgePoints[^1];
|
||||
var t = end.t;
|
||||
var pos = end.pos;
|
||||
var tan = end.tan;
|
||||
var up = end.up;
|
||||
|
||||
var V = GetVAt(t, edgePoints.Length - 1);
|
||||
var uv = new float2(0, V) * uvMultiplier - uvOffset;
|
||||
var clr = GetColorAt(t);
|
||||
|
||||
MakeRoundEdge(in pos, el.position, er.position, true, up, uv, clr);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
void MakeRoundEdge(in float3 center, in float3 left, in float3 right, bool invert,
|
||||
in float3 up, in float2 uv, in Color clr)
|
||||
{
|
||||
var vertexCount = resolution + 7;
|
||||
var marginAngle = 180f / vertexCount;
|
||||
var radius = math.length(center - left);
|
||||
var axis = keepBillboard ? math.back() : up;
|
||||
var arm = invert ? left - center : right - center;
|
||||
|
||||
var centerVertex = new UIVertex();
|
||||
centerVertex.position = center;
|
||||
centerVertex.uv0 = new Vector4(0.5f, 0);
|
||||
centerVertex.color = clr;
|
||||
vertices.Add(centerVertex);
|
||||
|
||||
var startIndex = vertices.Length - 1;
|
||||
|
||||
var toRadians =
|
||||
#if ENABLE_MATHMATICS__1_3_1_OR_NEWER
|
||||
math.TORADIANS;
|
||||
#else
|
||||
Mathf.Deg2Rad;
|
||||
#endif
|
||||
for (int i = 0; i <= vertexCount; i++)
|
||||
{
|
||||
var vector = math.rotate(quaternion.AxisAngle(axis, marginAngle * i * toRadians), arm);
|
||||
vector = math.normalizesafe(vector);
|
||||
var ratio = (float)i / vertexCount;
|
||||
var vert = new UIVertex
|
||||
{
|
||||
position = center + vector * radius,
|
||||
uv0 = new Vector4(0, ratio),
|
||||
color = clr
|
||||
};
|
||||
vertices.Add(vert);
|
||||
if(i > 0) triangles.Add(new int3(startIndex, startIndex + i, startIndex + i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public struct EdgePoint : IComparable<EdgePoint>
|
||||
{
|
||||
public float t;
|
||||
public readonly float angle;
|
||||
public float3 pos;
|
||||
public float3 tan;
|
||||
public float3 up;
|
||||
public readonly float tanInLength;
|
||||
public readonly float tanOutLength;
|
||||
public bool smoothed;
|
||||
|
||||
public EdgePoint(float t, float angle, float3 pos, float3 tan, float3 up, float tanInLength = 0, float tanOutLength = 0)
|
||||
{
|
||||
this.t = t;
|
||||
this.pos = pos;
|
||||
this.tan = tan;
|
||||
this.up = up;
|
||||
this.angle = angle;
|
||||
this.tanInLength = tanInLength;
|
||||
this.tanOutLength = tanOutLength;
|
||||
smoothed = false;
|
||||
}
|
||||
|
||||
public int CompareTo(EdgePoint other)
|
||||
{
|
||||
return t.CompareTo(other.t);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c38d6e1e178241fca1d16a519603f5f2
|
||||
timeCreated: 1690397815
|
||||
Reference in New Issue
Block a user