Files
Continentis/Assets/OtherPlugins/UI Spline Renderer/Runtime/Scripts/InternalUtility.cs
SoulliesOfficial ad4948207e 推进度!
2025-11-25 21:49:03 -05:00

418 lines
17 KiB
C#

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