推进度!
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ad0f216fa5499793e06998613a4133
|
||||
timeCreated: 1688923970
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea8f0f82da6648faac53810f1e6f932c
|
||||
timeCreated: 1690424194
|
||||
@@ -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
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace UI_Spline_Renderer
|
||||
{
|
||||
public enum LineTexturePreset
|
||||
{
|
||||
Default,
|
||||
UVTest,
|
||||
Custom
|
||||
}
|
||||
|
||||
public enum StartEndImagePreset
|
||||
{
|
||||
None,
|
||||
Triangle,
|
||||
Arrow,
|
||||
EmptyCircle,
|
||||
FilledCircle,
|
||||
Custom
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b198343f753f4ee19e85f927f5633d59
|
||||
timeCreated: 1690038740
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4eaaadddd7604d6e828c503c60d805f7
|
||||
timeCreated: 1690239751
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f24f501fa8e1fb48a03f1d7e6d03db4
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a21f54b05b414b95874ba3e272a365f0
|
||||
timeCreated: 1690055132
|
||||
Reference in New Issue
Block a user