推进度!

This commit is contained in:
SoulliesOfficial
2025-11-25 21:49:03 -05:00
parent f0c06f0337
commit ad4948207e
1068 changed files with 418853 additions and 1047 deletions

View File

@@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Burst;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace UI_Spline_Renderer
{
[BurstCompile]
internal static class InternalUtility
{
struct FrenetFrame
{
public float3 origin;
public float3 tangent;
public float3 normal;
public float3 binormal;
}
const int k_NormalsPerCurve = 16;
const float k_Epsilon = 0.0001f;
// source of this methods
// https://forum.unity.com/threads/moving-just-recttransform-pivot-in-world-space.1380249/
// by halley
internal static Vector3 GetPivotInWorldSpace(this RectTransform source)
{
var pivot = new Vector2(
source.rect.xMin + source.pivot.x * source.rect.width,
source.rect.yMin + source.pivot.y * source.rect.height);
return source.TransformPoint(new Vector3(pivot.x, pivot.y, 0f));
}
// source of this methods
// https://forum.unity.com/threads/moving-just-recttransform-pivot-in-world-space.1380249/
// by halley
internal static void SetPivotWithoutRect(this RectTransform source, Vector3 pivot)
{
var rect = source.rect;
if(float.IsNaN(rect.x) || float.IsNaN(rect.y) || float.IsNaN(rect.size.x) || float.IsNaN(rect.size.y)) return;
pivot = source.InverseTransformPoint(pivot);
var pivot2 = new Vector2(
(pivot.x - rect.xMin) / rect.width,
(pivot.y - rect.yMin) / rect.height);
var offset = pivot2 - source.pivot;
offset.Scale(source.rect.size);
var worldPos = source.position + source.TransformVector(offset);
source.pivot = pivot2;
source.position = worldPos;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float Remap(this float value, float beforeRangeMin, float beforeRangeMax, float targetRangeMin, float targetRangeMax)
{
var denominator = beforeRangeMax - beforeRangeMin;
if (denominator == 0) return targetRangeMin;
var ratio = (value - beforeRangeMin) / denominator;
var result = (targetRangeMax - targetRangeMin) * ratio + targetRangeMin;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float Remap(this int value, float beforeRangeMin, float beforeRangeMax, float targetRangeMin, float targetRangeMax)
{
return Remap((float)value, beforeRangeMin, beforeRangeMax, targetRangeMin, targetRangeMax);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float sq(this float value) => value * value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float LerpPoint(float value, float min, float max)
{
return (value - min) / (max - min);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Angle(this float3 a, float3 b)
{
float dot = math.dot(math.normalizesafe(a), math.normalizesafe(b));
dot = math.clamp(dot, -1f, 1f); // 클램핑을 하지 않고 acos를 실행하면 NaN을 반환함.
return math.degrees(math.acos(dot));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SignedAngle(this float3 a, float3 b)
{
var angle = math.acos(math.dot(math.normalizesafe(a), math.normalizesafe(b)));
var cross = math.cross(a, b);
angle *= math.sign(math.dot(math.up(), cross));
return math.degrees(angle);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float RadAngle(this float3 a, float3 b)
{
var angle = math.acos(math.dot(math.normalizesafe(a), math.normalizesafe(b)));
var cross = math.cross(a, b);
angle *= math.sign(math.dot(math.up(), cross));
return angle;
}
internal static StartEndImagePreset GetCurrentStartImagePreset(Sprite target)
{
if (target == null) return StartEndImagePreset.None;
if (target == UISplineRendererSettings.Instance.triangleHead) return StartEndImagePreset.Triangle;
if (target == UISplineRendererSettings.Instance.arrowHead) return StartEndImagePreset.Arrow;
if (target == UISplineRendererSettings.Instance.emptyCircleHead) return StartEndImagePreset.EmptyCircle;
if (target == UISplineRendererSettings.Instance.filledCircleHead) return StartEndImagePreset.FilledCircle;
return StartEndImagePreset.Custom;
}
public static float Repeat(float t, float length)
{
return math.clamp(t - math.floor(t / length) * length, 0.0f, length);
}
public static void CalculateCurveLengths(BezierCurve curve, NativeArray<DistanceToInterpolation> lookupTable)
{
var resolution = lookupTable.Length;
float magnitude = 0f;
float3 prev = CurveUtility.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 = CurveUtility.EvaluatePosition(curve, t);
var dir = point - prev;
magnitude += math.length(dir);
lookupTable[i] = new DistanceToInterpolation() { Distance = magnitude , T = t};
prev = point;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int CalcVertexCount(this Spline spline, float segmentLength, float2 renderRange)
{
var length = spline.GetLength() * (renderRange.y - renderRange.x);
return math.max((int)math.ceil(length * segmentLength), 1) * 2 + 4;
}
internal static int GetNearestKnotIndex(this CopiedNativeSpline spline, float t, out float distanceSq)
{
var p = spline.EvaluatePosition(t);
distanceSq = float.MaxValue;
var minIdx = -1;
for (int i = 0; i < spline.Knots.Length; i++)
{
var knot = spline.Knots[i];
var distSq = math.distancesq(knot.Position, p);
if (distSq < distanceSq)
{
minIdx = i;
distanceSq = distSq;
}
}
return minIdx;
}
internal static float CircularDistance(this float a, float b, float max = 1)
{
var half = max * 0.5f;
var aIsSmallSide = a < half;
var bIsSmallSide = b < half;
if (aIsSmallSide == bIsSmallSide)
{
return a > b ? a - b : b - a;
}
else
{
if (a > b)
{
b += max;
return b - a;
}
else
{
a += max;
return a - b;
}
}
}
internal static NativeColorGradient ToNative (this Gradient gradient, Allocator allocator = Allocator.TempJob)
{
var aKeys = new NativeArray<float2>(gradient.alphaKeys.Length, allocator);
for (int i = 0; i < gradient.alphaKeys.Length; i++)
{
var key = gradient.alphaKeys[i];
aKeys[i] = new float2(key.alpha, key.time);
}
var cKeys = new NativeArray<float4>(gradient.colorKeys.Length, allocator);
for (int i = 0; i < gradient.colorKeys.Length; i++)
{
var key = gradient.colorKeys[i];
cKeys[i] = new float4(key.color.r, key.color.g, key.color.b, key.time);
}
var native = new NativeColorGradient()
{
alphaKeyFrames = aKeys,
colorKeyFrames = cKeys
};
return native;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[BurstCompile]
internal static void ExtrudeEdge(
float w, float V, in Color clr, ref float3 pos, in float3 tan, in float3 up,
bool keepBillboard, bool keepZeroZ, in float2 uvMultiplier, in float2 uvOffset,
out UIVertex v0, out UIVertex v1)
{
var perpendicular =
keepBillboard ? math.normalizesafe(math.cross(tan, new float3(0, 0, -1))) : math.normalizesafe(math.cross(tan, up));
if (keepZeroZ)
{
pos.z = 0;
}
var uv = new float2(0, V) * uvMultiplier - uvOffset;
var vert = new UIVertex
{
position = pos + perpendicular * w * 0.5f,
uv0 = new Vector4(uv.x, uv.y),
color = clr
};
v0 = vert;
uv = new float2(1, V) * uvMultiplier - uvOffset;
vert = new UIVertex
{
position = pos - perpendicular * w * 0.5f,
uv0 = new Vector4(uv.x, uv.y),
color = clr
};
v1 = vert;
}
internal static float3 GetExplicitLinearTangent(float3 point, float3 to)
{
return (to - point) / 3.0f;
}
internal static float3 CalculateUpVector<T>(this T spline, int curveIndex, float curveT) where T : ISpline
{
if (spline.Count < 1)
return float3.zero;
var curve = spline.GetCurve(curveIndex);
var curveStartRotation = spline[curveIndex].Rotation;
var curveStartUp = math.rotate(curveStartRotation, math.up());
if (curveT == 0f)
return curveStartUp;
var endKnotIndex = spline.NextIndex(curveIndex);
var curveEndRotation = spline[endKnotIndex].Rotation;
var curveEndUp = math.rotate(curveEndRotation, math.up());
if (curveT == 1f)
return curveEndUp;
var up = EvaluateUpVector(curve, curveT, curveStartUp, curveEndUp);
return up;
}
internal static void EvaluateUpVectors(BezierCurve curve, float3 startUp, float3 endUp, Vector3[] 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)
{
// Ensure we have workable tangents by linearizing ones that are of zero length
var linearTangentLen = math.length(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 (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(CurveUtility.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 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);
}
static FrenetFrame GetNextRotationMinimizingFrame(BezierCurve curve, FrenetFrame previousRMFrame, float nextRMFrameT)
{
FrenetFrame nextRMFrame;
// Evaluate position and tangent for next RM frame
nextRMFrame.origin = CurveUtility.EvaluatePosition(curve, nextRMFrameT);
nextRMFrame.tangent = CurveUtility.EvaluateTangent(curve, nextRMFrameT);
// Mirror the rotational axis and tangent
float3 toCurrentFrame = nextRMFrame.origin - previousRMFrame.origin;
float c1 = math.dot(toCurrentFrame, toCurrentFrame);
float3 riL = previousRMFrame.binormal - toCurrentFrame * 2f / c1 * math.dot(toCurrentFrame, previousRMFrame.binormal);
float3 tiL = previousRMFrame.tangent - toCurrentFrame * 2f / c1 * math.dot(toCurrentFrame, previousRMFrame.tangent);
// Compute a more stable binormal
float3 v2 = nextRMFrame.tangent - tiL;
float c2 = math.dot(v2, v2);
// Fix binormal's axis
nextRMFrame.binormal = math.normalize(riL - v2 * 2f / c2 * math.dot(v2, riL));
nextRMFrame.normal = math.normalize(math.cross(nextRMFrame.binormal, nextRMFrame.tangent));
return nextRMFrame;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 70ad0f216fa5499793e06998613a4133
timeCreated: 1688923970

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea8f0f82da6648faac53810f1e6f932c
timeCreated: 1690424194

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea2e3adba3aa4b25a044aa103d6fc756
timeCreated: 1713945997

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: baf1300b266e4ca887cad7e70d1a70cb
timeCreated: 1690408892

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a04bbc35d484a798423531879e71d9c
timeCreated: 1696727209

View File

@@ -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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c38d6e1e178241fca1d16a519603f5f2
timeCreated: 1690397815

View File

@@ -0,0 +1,19 @@
namespace UI_Spline_Renderer
{
public enum LineTexturePreset
{
Default,
UVTest,
Custom
}
public enum StartEndImagePreset
{
None,
Triangle,
Arrow,
EmptyCircle,
FilledCircle,
Custom
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b198343f753f4ee19e85f927f5633d59
timeCreated: 1690038740

View File

@@ -0,0 +1,63 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace UI_Spline_Renderer
{
public static class SplineExtensions
{
/// <summary>
/// Reorient a single knot to screen direction.
/// </summary>
public static void ReorientKnot(this Spline spline, int index, bool withoutNotify = false)
{
var knot = spline[index];
var rot = (Quaternion)knot.Rotation;
var forward = rot * Vector3.forward;
var projected = Vector3.ProjectOnPlane(forward, Vector3.back);
var targetRotation = Quaternion.LookRotation(projected, Vector3.back);
knot.Rotation = targetRotation;
if(withoutNotify)spline.SetKnotNoNotify(index, knot);
else spline.SetKnot(index, knot);
}
/// <summary>
/// Reorient all knots to screen direction.
/// </summary>
public static void ReorientKnots(this SplineContainer container, bool withoutNotify = false)
{
foreach (var spline in container.Splines)
{
for (int i = 0; i < spline.Count; i++)
{
ReorientKnot(spline, i, withoutNotify);
}
}
}
/// <summary>
/// Reorient all knots to screen direction and make all knots AutoSmooth.
/// </summary>
/// <param name="container"></param>
public static void ReorientKnotsAndSmooth(this SplineContainer container)
{
foreach (var spline in container.Splines)
{
for (int i = 0; i < spline.Count; i++)
{
var knot = spline[i];
var prev = i == 0 ? knot.Position : spline[i - 1].Position;
var next = i == spline.Count - 1 ? spline[i].Position : spline[i + 1].Position;
knot = SplineUtility.GetAutoSmoothKnot(knot.Position, prev, next, new float3(0, 0, -1));
spline.SetKnot(i, knot);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4eaaadddd7604d6e828c503c60d805f7
timeCreated: 1690239751

View File

@@ -0,0 +1,46 @@
{
"name": "UISplineRenderer",
"rootNamespace": "UI_Spline_Renderer",
"references": [
"GUID:21d1eb854b91ade49bc69a263d12bee2",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:7ab3663edede26740845931880bf22af",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:2665a8d13d1b3f18800f46e256720795"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.splines",
"expression": "",
"define": "ENABLE_SPLINES"
},
{
"name": "com.unity.collections",
"expression": "",
"define": "ENABLE_COLLECTIONS"
},
{
"name": "com.unity.mathematics",
"expression": "",
"define": "ENABLE_MATHEMATICS"
},
{
"name": "com.unity.mathematics",
"expression": "1.3.1",
"define": "ENABLE_MATHMATICS__1_3_1_OR_NEWER"
},
{
"name": "com.unity.burst",
"expression": "",
"define": "ENABLE_BURST"
}
],
"noEngineReferences": false
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88c6b92abab562a45acc40bff1984b00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c3499b63a6dc8424f8ca9c03466edd59, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace UI_Spline_Renderer
{
public class UISplineRendererSettings : ScriptableObject
{
public static UISplineRendererSettings Instance
{
get
{
if (_instance == null)
{
_instance = Resources.Load<UISplineRendererSettings>("UISplineRenderer Settings");
}
return _instance;
}
}
static UISplineRendererSettings _instance;
public Texture defaultLineTexture;
public Texture uvTestLineTexture;
public Sprite triangleHead;
public Sprite arrowHead;
public Sprite emptyCircleHead;
public Sprite filledCircleHead;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a21f54b05b414b95874ba3e272a365f0
timeCreated: 1690055132