RoboticArms/Library/PackageCache/com.unity.splines@d3e1e500c9a0/Editor/Utilities/SplineCacheUtility.cs
2025-11-17 15:16:36 +07:00

331 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class SplineCacheUtility
{
const int k_SegmentsCount = 32;
static Dictionary<Spline, Vector3[]> s_SplineCacheTable = new Dictionary<Spline, Vector3[]>();
[InitializeOnLoadMethod]
static void Initialize()
{
Spline.Changed += ClearCache;
Undo.undoRedoPerformed += ClearAllCache;
PrefabStage.prefabStageClosing += _ => ClearAllCache();
#if UNITY_2022_3_OR_NEWER
PrefabUtility.prefabInstanceReverting += _ => ClearAllCache();
#else
// PrefabUtility.prefabInstanceReverting is unfortunately not backported yet.
ObjectChangeEvents.changesPublished += (ref ObjectChangeEventStream stream) =>
{
bool cacheCleared = false;
for (int i = 0; i < stream.length; ++i)
{
if (cacheCleared)
break;
int instanceID = 0;
switch (stream.GetEventType(i))
{
// Called after prefab instance override Revert All.
case ObjectChangeKind.ChangeGameObjectStructureHierarchy:
stream.GetChangeGameObjectStructureHierarchyEvent(i, out var changeGameObjectStructureHierarchyEvt);
instanceID = changeGameObjectStructureHierarchyEvt.instanceId;
break;
// Called after prefab instance override Revert on field or component.
case ObjectChangeKind.ChangeGameObjectOrComponentProperties:
stream.GetChangeGameObjectOrComponentPropertiesEvent(i, out var changeGameObjectOrComponentPropertiesEvt);
instanceID = changeGameObjectOrComponentPropertiesEvt.instanceId;
break;
}
if (instanceID != 0)
{
var obj = EditorUtility.InstanceIDToObject(instanceID);
if(obj == null)
continue;
if (PrefabUtility.GetPrefabInstanceStatus(obj) == PrefabInstanceStatus.Connected)
{
SplineContainer splineContainer = null;
GameObject splineGO = obj as GameObject;
if (splineGO != null)
splineContainer = splineGO.GetComponent<SplineContainer>();
else
splineContainer = obj as SplineContainer;
if (splineContainer != null)
{
foreach (var spline in splineContainer.Splines)
{
if (s_SplineCacheTable.ContainsKey(spline))
{
ClearAllCache();
cacheCleared = true;
break;
}
}
}
}
}
}
};
#endif
}
internal static void ClearAllCache()
{
s_SplineCacheTable.Clear();
s_CurvesBuffers?.Clear();
s_TangentsCache?.Clear();
}
public static void GetCachedPositions(Spline spline, out Vector3[] positions)
{
if(!s_SplineCacheTable.ContainsKey(spline))
s_SplineCacheTable.Add(spline, null);
int count = spline.Closed ? spline.Count : spline.Count - 1;
if(s_SplineCacheTable[spline] == null)
{
s_SplineCacheTable[spline] = new Vector3[count * k_SegmentsCount];
CacheCurvePositionsTable(spline, count);
}
positions = s_SplineCacheTable[spline];
}
static void CacheCurvePositionsTable(Spline spline, int curveCount)
{
float inv = 1f / (k_SegmentsCount - 1);
for(int i = 0; i < curveCount; ++i)
{
var curve = spline.GetCurve(i);
var startIndex = i * k_SegmentsCount;
for(int n = 0; n < k_SegmentsCount; n++)
(s_SplineCacheTable[spline])[startIndex + n] = CurveUtility.EvaluatePosition(curve, n * inv);
}
}
internal struct CurveBufferData
{
internal Vector3[] positions;
internal (Vector3[] positions, Matrix4x4 trs) flowArrow;
}
//internal for tests
internal static Dictionary<BezierCurve, CurveBufferData> s_CurvesBuffers = null;
//internal for tests
internal static Dictionary<SelectableTangent, (float3 position, quaternion rotation)> s_TangentsCache = null;
const int k_CurveDrawResolution = 64;
internal static int CurveDrawResolution => k_CurveDrawResolution;
static void ClearCache(Spline spline, int knotIndex, SplineModification modificationType)
{
if (knotIndex == -1 || modificationType != SplineModification.KnotModified)
{
s_CurvesBuffers?.Clear();
s_SplineCacheTable.Remove(spline);
}
else // If Knot modified by the tools
{
// If knots are in auto mode they can influence up to the 4 curves around them
var knotIndexBefore = SplineUtility.PreviousIndex(knotIndex, spline.Count, spline.Closed);
var knotIndexBeforeBefore = SplineUtility.PreviousIndex(knotIndexBefore, spline.Count, spline.Closed);
var knotIndexAfter = SplineUtility.NextIndex(knotIndex, spline.Count, spline.Closed);
UpdateCachePosition(spline, knotIndexBeforeBefore);
UpdateCachePosition(spline, knotIndexBefore);
UpdateCachePosition(spline, knotIndex);
UpdateCachePosition(spline, knotIndexAfter);
}
//We don't have access to the spline container here so we cannot detect which specific SelectableTangents are impacted
s_TangentsCache?.Clear();
}
static void UpdateCachePosition(Spline spline, int curveIndex)
{
if (curveIndex > spline.Count)
return;
var curve = spline.GetCurve(curveIndex);
if (s_CurvesBuffers != null)
{
// Update curve cache
if (s_CurvesBuffers.ContainsKey(curve))
s_CurvesBuffers.Remove(curve);
}
if (s_SplineCacheTable == null || !s_SplineCacheTable.ContainsKey(spline) || s_SplineCacheTable[spline] == null)
return;
// Update position cache
float inv = 1f / (k_SegmentsCount - 1);
var startIndex = curveIndex * k_SegmentsCount;
for (int n = 0; n < k_SegmentsCount; n++)
{
if(startIndex + n < (s_SplineCacheTable[spline]).Length)
(s_SplineCacheTable[spline])[startIndex + n] = CurveUtility.EvaluatePosition(curve, n * inv);
}
}
internal static void InitializeCache()
{
s_CurvesBuffers = new Dictionary<BezierCurve, CurveBufferData>();
s_TangentsCache = new Dictionary<SelectableTangent, (float3, quaternion)>();
}
internal static void ClearCache()
{
s_TangentsCache?.Clear();
s_CurvesBuffers?.Clear();
s_TangentsCache = null;
s_CurvesBuffers = null;
}
static (float3, quaternion) InitTangentEntry(SelectableTangent tangent)
{
var pos = tangent.Position;
var rot = EditorSplineUtility.GetElementRotation(math.length(tangent.LocalPosition) > 0 ? (ISelectableElement)tangent : tangent.Owner);
s_TangentsCache.Add(tangent, (pos, rot));
return (pos, rot);
}
internal static (float3 position, quaternion rotation) GetTangentPositionAndRotation(SelectableTangent tangent)
{
if (s_TangentsCache == null)
return (tangent.Position, EditorSplineUtility.GetElementRotation(math.length(tangent.LocalPosition) > 0 ? (ISelectableElement)tangent : tangent.Owner));
if (!s_TangentsCache.TryGetValue(tangent, out var tuple))
tuple = InitTangentEntry(tangent);
return tuple;
}
internal static quaternion GetTangentRotation(SelectableTangent tangent)
{
if (s_TangentsCache == null)
return EditorSplineUtility.GetElementRotation(math.length(tangent.LocalPosition) > 0 ? (ISelectableElement)tangent : tangent.Owner);
if (!s_TangentsCache.TryGetValue(tangent, out var tuple))
tuple = InitTangentEntry(tangent);
return tuple.rotation;
}
internal static float3 GetTangentPosition(SelectableTangent tangent)
{
if (s_TangentsCache == null)
return tangent.Position;
if (!s_TangentsCache.TryGetValue(tangent, out var tuple))
tuple = InitTangentEntry(tangent);
return tuple.position;
}
internal static void GetCurvePositions(BezierCurve curve, Vector3[] buffer)
{
if (s_CurvesBuffers == null)
{
ComputeCurveBuffer(curve, buffer);
return;
}
if (!s_CurvesBuffers.TryGetValue(curve, out var curveBufferData))
{
curveBufferData = new CurveBufferData() { positions = null, flowArrow = new(null, new Matrix4x4()) };
s_CurvesBuffers.Add(curve, curveBufferData);
}
if(curveBufferData.positions == null)
{
curveBufferData.positions = new Vector3[buffer.Length];
ComputeCurveBuffer(curve, curveBufferData.positions);
s_CurvesBuffers[curve] = curveBufferData;
}
Array.Copy(curveBufferData.positions, buffer, curveBufferData.positions.Length);
}
internal static void ComputeCurveBuffer(BezierCurve curve, Vector3[] buffer)
{
const float segmentPercentage = 1f / k_CurveDrawResolution;
for (int i = 0; i <= k_CurveDrawResolution; ++i)
{
buffer[i] = CurveUtility.EvaluatePosition(curve, i * segmentPercentage);
}
}
internal static (Vector3[] positions, Matrix4x4 trs) GetCurveArrow(ISpline spline, int curveIndex, BezierCurve curve)
{
if (s_CurvesBuffers == null)
return ComputeArrowPositions(spline, curveIndex, curve);
if (!s_CurvesBuffers.TryGetValue(curve, out var curveBufferData))
{
curveBufferData = new CurveBufferData() { positions = null, flowArrow = new(null, new Matrix4x4()) };
s_CurvesBuffers.Add(curve, curveBufferData);
}
if (curveBufferData.flowArrow.positions == null)
{
var arrow = ComputeArrowPositions(spline, curveIndex, curve);
curveBufferData.flowArrow.positions = arrow.positions;
curveBufferData.flowArrow.trs = arrow.trs;
s_CurvesBuffers[curve] = curveBufferData;
}
return curveBufferData.flowArrow;
}
internal static (Vector3[] positions, Matrix4x4 trs) ComputeArrowPositions(ISpline spline, int curveIndex, BezierCurve curve)
{
var t = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, curveIndex);
var position = CurveUtility.EvaluatePosition(curve, t);
var tangent = math.normalizesafe(CurveUtility.EvaluateTangent(curve, t));
var up = spline.GetCurveUpVector(curveIndex, t);
var rotation = quaternion.LookRotationSafe(tangent, up);
var arrowMaxSpline = .05f * CurveUtility.ApproximateLength(curve);
var size = HandleUtility.GetHandleSize(position) * .5f;
tangent = new float3(0, 0, .1f) * size;
var right = new float3(0.075f, 0, 0) * size;
var magnitude = math.length(tangent);
if(magnitude > arrowMaxSpline)
{
var ratio = arrowMaxSpline / magnitude;
tangent *= ratio;
right *= ratio;
}
var a = tangent;
var b = -tangent + right;
var c = -tangent - right;
var positions = new Vector3[] { a, b, c};
return (positions, Matrix4x4.TRS(position, rotation, Vector3.one));
}
}
}