From a7ef0d6635debc8cfb6cfb500e9eaeba320b0050 Mon Sep 17 00:00:00 2001 From: bd_ Date: Sun, 24 Nov 2024 18:24:38 -0800 Subject: [PATCH] fix: multiple issues with MAMoveIndependently (#1369) * fix: multiple issues with MAMoveIndependently Fixed issues with nested Move Independently components (Closes #1367). Fixed MAMoveIndependently not being saved (Supercedes #1366) Reduce jittering when moving MAMI bones. * chore: fix some namespaces * chore: fix non-editor buil --- .../MoveIndependentlyEditor.cs | 1 - Editor/PluginDefinition/PluginDefinition.cs | 1 - Runtime/ArmatureAwase/MoveIndep.meta | 3 + .../MoveIndep/MAMoveIndependentlyManager.cs | 647 ++++++++++++++++++ .../MAMoveIndependentlyManager.cs.meta | 3 + Runtime/ArmatureAwase/NativeMemoryManager.cs | 13 +- Runtime/MAMoveIndependently.cs | 219 +----- 7 files changed, 678 insertions(+), 209 deletions(-) create mode 100644 Runtime/ArmatureAwase/MoveIndep.meta create mode 100644 Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs create mode 100644 Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs.meta diff --git a/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs b/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs index 4b741d90..3bcc0b3f 100644 --- a/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs +++ b/Editor/Inspector/MoveIndependently/MoveIndependentlyEditor.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using nadena.dev.modular_avatar.core.ArmatureAwase; using nadena.dev.ndmf.preview; using UnityEditor; using UnityEngine; diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 4a31f17a..3bc7cf1b 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -2,7 +2,6 @@ using System; using nadena.dev.modular_avatar.animation; -using nadena.dev.modular_avatar.core.ArmatureAwase; using nadena.dev.modular_avatar.core.editor.plugin; using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.ndmf; diff --git a/Runtime/ArmatureAwase/MoveIndep.meta b/Runtime/ArmatureAwase/MoveIndep.meta new file mode 100644 index 00000000..da1c9674 --- /dev/null +++ b/Runtime/ArmatureAwase/MoveIndep.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: abec4f397dc74b2f9bba6f71b5e702f3 +timeCreated: 1732395066 \ No newline at end of file diff --git a/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs b/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs new file mode 100644 index 00000000..6332ce38 --- /dev/null +++ b/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs @@ -0,0 +1,647 @@ +using System; +using System.Collections.Generic; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using UnityEngine; +using UnityEngine.Jobs; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace nadena.dev.modular_avatar.core.armature_lock +{ + internal class MaMoveIndependentlyManager + { + internal static MaMoveIndependentlyManager Instance { get; } = new(); + + private MaMoveIndependentlyManager() + { + _nativeMemoryManager = new NativeMemoryManager(); + + _vpState = _nativeMemoryManager.CreateArray(); + _tpState = _nativeMemoryManager.CreateArray(); + _targetState = _nativeMemoryManager.CreateArray(); + _mappingStates = _nativeMemoryManager.CreateArray(); + + _errorFlags = _nativeMemoryManager.CreateArray(); + _enabled = _nativeMemoryManager.CreateArray(); + _sceneRootParent = _nativeMemoryManager.CreateArray(); + _falseArray = _nativeMemoryManager.CreateArray(); + + _anyError = new NativeArray(1, Allocator.Persistent); + _anyDirty = new NativeArray(1, Allocator.Persistent); + + _nativeMemoryManager.OnSegmentMove += MoveTransforms; +#if UNITY_EDITOR + AssemblyReloadEvents.beforeAssemblyReload += OnDomainUnload; +#endif + } + + private void OnDomainUnload() + { + Dispose(); + } + + private void Dispose() + { + _lastJob.Complete(); + +#if UNITY_EDITOR + AssemblyReloadEvents.beforeAssemblyReload -= OnDomainUnload; +#endif + + if (_virtualParents.isCreated) DeferDestroy.DeferDestroyObj(_virtualParents); + if (_trueParents.isCreated) DeferDestroy.DeferDestroyObj(_trueParents); + if (_targets.isCreated) DeferDestroy.DeferDestroyObj(_targets); + + _nativeMemoryManager.Dispose(); + + if (_anyError.IsCreated) _anyError.Dispose(); + if (_anyDirty.IsCreated) _anyDirty.Dispose(); + } + + private const float PosEpsilon = 0.0000001f; + private const float RotEpsilon = 0.0000001f; + private const float ScaleEpsilon = 0.0000001f; + + // Our basic strategy is to identify all children of MoveIndependently objects, and to find the first parent + // that is not a member of the same MoveIndependently group. We then compute the local transform of the child + // relative to that parent, and keep it constant (unless the true local transform of the child changes). + // + // If an active MAMoveIndep is a child of another MAMoveIndep, we consider it to be ungrouped (even if it's + // named in the parent). + + private readonly NativeMemoryManager _nativeMemoryManager; + + private Transform[] _virtualParentsT; + private Transform[] _trueParentsT; + private Transform[] _targetsT; + + private TransformAccessArray _virtualParents; + private TransformAccessArray _trueParents; + private TransformAccessArray _targets; + + private bool _transformAccessDirty; + + private readonly NativeArrayRef _vpState; + private readonly NativeArrayRef _tpState; + private readonly NativeArrayRef _targetState; + private readonly NativeArrayRef _mappingStates; + private readonly NativeArrayRef _errorFlags; + private NativeArray _anyError, _anyDirty; + private readonly NativeArrayRef _enabled; + private readonly NativeArrayRef _sceneRootParent, _falseArray; + private readonly Dictionary _slotToState = new(); + + private struct MappingState + { + // Our last observed local transform, relative to our actual parent transform + public TransformState TrueLocal; + + // Our last observed local transform, relative to our virtual parent transform + public Matrix4x4 VirtualLocal; + + // The position of our parent relative to our virtual parent + public Matrix4x4 TrueLocalToVirtualLocal; + public bool RequestWriteback, CacheValid; + } + + private class State + { + public MAMoveIndependently MoveIndep; + public ISegment Segment; + } + + private readonly Dictionary _moveIndeps = new(); + private JobHandle _lastJob; + + private bool _isRegistered; + private int _maxComputeDepth; + + private bool UpdateRegistered + { + get => _isRegistered; + set + { + if (value == _isRegistered) return; + if (value) + { + UpdateLoopController.OnMoveIndependentlyUpdate += Update; + } + else + { + UpdateLoopController.OnMoveIndependentlyUpdate -= Update; + } + + _isRegistered = value; + } + } + + private void EnsureTransformCapacity(int targetLength) + { + if (_virtualParentsT == null) + { + _virtualParentsT = new Transform[targetLength]; + _trueParentsT = new Transform[targetLength]; + _targetsT = new Transform[targetLength]; + return; + } + + if (targetLength <= _virtualParentsT.Length) return; + + var newCapacity = Mathf.Max(_virtualParentsT.Length * 2, targetLength); + Array.Resize(ref _virtualParentsT, newCapacity); + Array.Resize(ref _trueParentsT, newCapacity); + Array.Resize(ref _targetsT, newCapacity); + } + + private void MoveTransforms(int oldoffset, int newoffset, int length) + { + Array.Copy(_virtualParentsT, oldoffset, _virtualParentsT, newoffset, length); + Array.Copy(_trueParentsT, oldoffset, _trueParentsT, newoffset, length); + Array.Copy(_targetsT, oldoffset, _targetsT, newoffset, length); + + _transformAccessDirty = true; + } + + private void UpdateTransformAccess() + { + if (!_transformAccessDirty) return; + + UpdateTransformAccess(ref _virtualParents, _virtualParentsT); + UpdateTransformAccess(ref _trueParents, _trueParentsT); + UpdateTransformAccess(ref _targets, _targetsT); + + _transformAccessDirty = false; + } + + private void UpdateTransformAccess(ref TransformAccessArray arr, Transform[] t) + { + if (!arr.isCreated || arr.length != t.Length) + { + if (arr.isCreated) arr.Dispose(); + arr = new TransformAccessArray(t); + } + else + { + arr.SetTransforms(t); + } + } + + private void Update() + { + _lastJob.Complete(); + + UpdateTransformAccess(); + + _anyError[0] = false; + _anyDirty[0] = false; + + var clearErrors = new JClearErrorFlags + { + ErrorFlags = _errorFlags + }; + var clearErrorsHandle = clearErrors.Schedule(_errorFlags.Length, 16); + + var readVp = new JReadTransforms + { + States = _vpState, + Enabled = _enabled, + ErrorFlags = _errorFlags, + SceneRootParent = _sceneRootParent + }; + var readTp = new JReadTransforms + { + States = _tpState, + Enabled = _enabled, + ErrorFlags = _errorFlags, + SceneRootParent = _falseArray + }; + var readTarget = new JReadTransforms + { + States = _targetState, + Enabled = _enabled, + ErrorFlags = _errorFlags, + SceneRootParent = _falseArray + }; + + var readVpHandle = readVp.Schedule(_virtualParents, clearErrorsHandle); + var clearVpHandle = new JClearRootTransforms + { + States = _vpState, + SceneRootParent = _sceneRootParent + }.Schedule(_vpState.Length, 16, readVpHandle); + var readTpHandle = readTp.Schedule(_trueParents, clearErrorsHandle); + var readTargetHandle = readTarget.Schedule(_targets, clearErrorsHandle); + var readHandle = JobHandle.CombineDependencies(clearVpHandle, readTpHandle, readTargetHandle); + + var compute = new JCompute + { + VpState = _vpState, + TpState = _tpState, + TargetState = _targetState, + States = _mappingStates, + AnyDirty = _anyDirty, + AnyError = _anyError, + ErrorFlags = _errorFlags, + Enabled = _enabled + }; + + var computeHandle = compute.Schedule(_mappingStates.Length, 16, readHandle); + _lastJob = computeHandle; + + computeHandle.Complete(); + + List prefabRecord = null; + if (_anyDirty[0]) + { + prefabRecord = new List(); + for (var i = 0; i < _mappingStates.Length; i++) + { + if (_mappingStates[i].RequestWriteback) + { +#if UNITY_EDITOR + Undo.RecordObject(_targets[i], "Move Independently"); +#endif + prefabRecord.Add(_targets[i]); + } + } + } + + var writeback = new JWriteback + { + States = _mappingStates, + Errors = _errorFlags, + Enabled = _enabled, + AnyError = _anyError + }; + + var writebackHandle = writeback.Schedule(_targets, computeHandle); + + _lastJob = writebackHandle; + + writebackHandle.Complete(); + + if (prefabRecord != null) + { + foreach (var transform in prefabRecord) + { +#if UNITY_EDITOR + PrefabUtility.RecordPrefabInstancePropertyModifications(transform); +#endif + } + } + + if (_anyError[0]) + { + List reactivate = new(); + for (var i = 0; i < _mappingStates.Length; i++) + { + if (_errorFlags[i] && _slotToState.TryGetValue(i, out var state)) + { + Deactivate(state); + reactivate.Add(state.MoveIndep); + } + } + + foreach (var moveIndep in reactivate) + { + if (moveIndep != null) Activate(moveIndep); + } + } + } + + internal void Activate(MAMoveIndependently moveIndep) + { + if (!_anyDirty.IsCreated) return; // domain reload timing issues + + if (_moveIndeps.TryGetValue(moveIndep, out var state)) Deactivate(state); + + HashSet groupedTransforms = new(); + groupedTransforms.Add(moveIndep.transform); + + RegisterGroupedTransforms(moveIndep, groupedTransforms); + + List toReregister = new(); + + foreach (var t in groupedTransforms) + { + // If we have a direct child MAMI, we need it to change its virtual parent, so trigger a reregister + // on it. + if (t.TryGetComponent(out var mami) && mami != moveIndep) + { + toReregister.Add(mami); + } + } + + var ptr = moveIndep.transform.parent; + while (ptr != null) + { + var parentMoveIndep = ptr.GetComponentInParent(); + if (parentMoveIndep == null) break; + + RegisterGroupedTransforms(parentMoveIndep, groupedTransforms); + + ptr = parentMoveIndep.transform.parent; + } + + // Compute leaf transforms + List leafTransforms = new(); + Walk(moveIndep.transform); + + var segment = _nativeMemoryManager.Allocate(leafTransforms.Count); + EnsureTransformCapacity(segment.Offset + segment.Length); + _transformAccessDirty = true; + + var virtualParent = moveIndep.transform.parent; + while (virtualParent != null && groupedTransforms.Contains(virtualParent)) + virtualParent = virtualParent.parent; + + for (var i = 0; i < leafTransforms.Count; i++) + { + var j = i + segment.Offset; + _mappingStates[j] = new MappingState + { + + CacheValid = false + }; + + _virtualParentsT[j] = virtualParent; + _trueParentsT[j] = leafTransforms[i].parent; + _targetsT[j] = leafTransforms[i]; + _enabled[j] = true; + _sceneRootParent[j] = virtualParent == null; + _slotToState[j] = state; + } + + _moveIndeps[moveIndep] = new State + { + MoveIndep = moveIndep, + Segment = segment + }; + + UpdateRegistered = true; + + foreach (var mami in toReregister) + { + if (mami != null) Activate(mami); + } + + void Walk(Transform t) + { + foreach (Transform child in t) + { + if (groupedTransforms.Contains(child)) + { + Walk(child); + continue; + } + + leafTransforms.Add(child); + } + } + } + + private void RegisterGroupedTransforms(MAMoveIndependently moveIndep, HashSet groupedTransforms) + { + var candidates = new HashSet(moveIndep.GroupedBones); + candidates.Add(moveIndep.gameObject); + + Walk(moveIndep.transform); + + void Walk(Transform t) + { + if (!candidates.Contains(t.gameObject)) return; + + groupedTransforms.Add(t); + + foreach (Transform child in t) + { + if (child.TryGetComponent(out _)) continue; + + Walk(child); + } + } + } + + internal void Deactivate(MAMoveIndependently moveIndep) + { + if (_moveIndeps.TryGetValue(moveIndep, out var state)) Deactivate(state); + } + + private void Deactivate(State state) + { + if (!_anyDirty.IsCreated) return; // domain reload timing issues + + for (var i = 0; i < state.Segment.Length; i++) + { + var j = i + state.Segment.Offset; + _enabled[j] = false; + _virtualParents[j] = null; + _trueParents[j] = null; + _targets[j] = null; + _slotToState.Remove(j); + } + + _nativeMemoryManager.Free(state.Segment); + _moveIndeps.Remove(state.MoveIndep); + + if (_moveIndeps.Count == 0) UpdateRegistered = false; + } + + [BurstCompile] + private static bool MatDiffers(Matrix4x4 a, Matrix4x4 b) + { + var aPos = a.GetColumn(3); + var bPos = b.GetColumn(3); + + if ((aPos - bPos).sqrMagnitude > PosEpsilon) return true; + + var aRot = a.rotation; + var bRot = b.rotation; + + if (Quaternion.Angle(aRot, bRot) > RotEpsilon) return true; + + var aScale = a.lossyScale; + var bScale = b.lossyScale; + + return (aScale - bScale).sqrMagnitude > ScaleEpsilon; + } + + private struct JClearErrorFlags : IJobParallelFor + { + [WriteOnly] public NativeArray ErrorFlags; + + public void Execute(int index) + { + ErrorFlags[index] = false; + } + } + + // For some reason checking SceneRootParent in JReadTransforms was ignored...? + // Maybe IJobParallelForTransform doesn't execute on null transforms. + private struct JClearRootTransforms : IJobParallelFor + { + [WriteOnly] public NativeArray States; + [ReadOnly] public NativeArray SceneRootParent; + + public void Execute(int index) + { + if (SceneRootParent[index]) + { + States[index] = new TransformState + { + localToWorldMatrix = Matrix4x4.identity, + localRotation = Quaternion.identity, + localScale = Vector3.one, + localPosition = Vector3.zero + }; + } + } + } + + private struct JReadTransforms : IJobParallelForTransform + { + [WriteOnly] public NativeArray States; + + [ReadOnly] public NativeArray Enabled; + [ReadOnly] public NativeArray SceneRootParent; + + [NativeDisableContainerSafetyRestriction] [WriteOnly] + public NativeArray ErrorFlags; + + [BurstCompile] + public void Execute(int index, TransformAccess transform) + { + if (!Enabled[index]) return; + + if (SceneRootParent[index]) return; + + if (!transform.isValid) + { + ErrorFlags[index] = true; + return; + } + + States[index] = new TransformState + { + localToWorldMatrix = transform.localToWorldMatrix, + localRotation = transform.localRotation, + localScale = transform.localScale, + localPosition = transform.localPosition + }; + } + } + + private struct JCompute : IJobParallelFor + { + [ReadOnly] public NativeArray VpState, TpState, TargetState; + + [WriteOnly] [NativeDisableContainerSafetyRestriction] + public NativeArray AnyDirty; + + [WriteOnly] [NativeDisableContainerSafetyRestriction] + public NativeArray AnyError; + + public NativeArray States; + + public NativeArray ErrorFlags; + + [ReadOnly] public NativeArray Enabled; + + [BurstCompile] + public void Execute(int index) + { + if (!Enabled[index]) return; + + var state = States[index]; + var vp = VpState[index]; + var tp = TpState[index]; + var target = TargetState[index]; + + if (ErrorFlags[index]) + { + AnyError[0] = true; + return; + } + + // First, compute the virtual parent transform - we'll need it in any case. + var trueLocalToVirtualLocal = vp.worldToLocalMatrix * tp.localToWorldMatrix; + + state.RequestWriteback = false; + + if (TransformState.Differs(target, state.TrueLocal) || !state.CacheValid) + { + // Our local position changed, so don't try to make any corrections; just remember the new values. + state.CacheValid = true; + state.TrueLocal = target; + state.TrueLocalToVirtualLocal = trueLocalToVirtualLocal; + state.VirtualLocal = trueLocalToVirtualLocal * Matrix4x4.TRS( + state.TrueLocal.localPosition, + state.TrueLocal.localRotation, + state.TrueLocal.localScale + ); + } + else if (MatDiffers(trueLocalToVirtualLocal, state.TrueLocalToVirtualLocal)) + { + // Our local position didn't change, but our virtual parent did, so we need to correct. + // To do this, we take our _old_ virtual local transform, and use it to transform our old true local + // position into virtual local space; we then go from _current_ virtual local space to true local. + var virtualLocalToTrueLocal = trueLocalToVirtualLocal.inverse; + var trueLocal = virtualLocalToTrueLocal * state.VirtualLocal; + + state.TrueLocal = new TransformState + { + localPosition = trueLocal.GetColumn(3), + localRotation = trueLocal.rotation, + localScale = trueLocal.lossyScale + }; + + state.TrueLocalToVirtualLocal = trueLocalToVirtualLocal; + + state.RequestWriteback = true; + AnyDirty[0] = true; + } + + States[index] = state; + } + } + + private struct JWriteback : IJobParallelForTransform + { + [ReadOnly] public NativeArray States; + + [ReadOnly] public NativeArray Errors; + + [ReadOnly] public NativeArray Enabled; + + [NativeDisableContainerSafetyRestriction] [WriteOnly] + public NativeArray AnyError; + + [BurstCompile] + public void Execute(int index, TransformAccess transform) + { + var state = States[index]; + + if (!Enabled[index] || Errors[index] || !state.RequestWriteback) return; + + if (!transform.isValid) + { + Errors[index] = true; + AnyError[0] = true; + return; + } + + var pos = state.TrueLocal.localPosition; + var rot = state.TrueLocal.localRotation; + var scale = state.TrueLocal.localScale; + + transform.localPosition = pos; + transform.localRotation = rot; + transform.localScale = scale; + } + } + } +} \ No newline at end of file diff --git a/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs.meta b/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs.meta new file mode 100644 index 00000000..01c95468 --- /dev/null +++ b/Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 154891b009044835b43580e745f50a9e +timeCreated: 1732394861 \ No newline at end of file diff --git a/Runtime/ArmatureAwase/NativeMemoryManager.cs b/Runtime/ArmatureAwase/NativeMemoryManager.cs index 0f98b920..4f801aa9 100644 --- a/Runtime/ArmatureAwase/NativeMemoryManager.cs +++ b/Runtime/ArmatureAwase/NativeMemoryManager.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -using UnityEngine; #endregion @@ -15,12 +14,19 @@ namespace nadena.dev.modular_avatar.core.armature_lock internal NativeArray Array; public static implicit operator NativeArray(NativeArrayRef arrayRef) => arrayRef.Array; + public int Length => Array.Length; public void Dispose() { Array.Dispose(); } + public T this[int key] + { + get => Array[key]; + set => Array[key] = value; + } + public void Resize(int n) { if (Array.Length == n) return; @@ -143,6 +149,11 @@ namespace nadena.dev.modular_avatar.core.armature_lock // We perform trial creations of segments (and then immediately free them if they exceed the bounds of the // array). As such, we clamp the length, rather than throwing an exception. length = Math.Min(length, InUseMask.Array.Length - offset); + + if (length < 0) + { + throw new ArgumentException("negative length"); + } unsafe { diff --git a/Runtime/MAMoveIndependently.cs b/Runtime/MAMoveIndependently.cs index fc128baa..0ef36c6b 100644 --- a/Runtime/MAMoveIndependently.cs +++ b/Runtime/MAMoveIndependently.cs @@ -1,13 +1,14 @@ using System; -using System.Collections.Generic; using nadena.dev.modular_avatar.core.armature_lock; -using UnityEditor; using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif #if MA_VRCSDK3_AVATARS using VRC.SDKBase; #endif -namespace nadena.dev.modular_avatar.core.ArmatureAwase +namespace nadena.dev.modular_avatar.core { [ExecuteInEditMode] [AddComponentMenu("Modular Avatar/MA Move Independently")] @@ -22,229 +23,35 @@ namespace nadena.dev.modular_avatar.core.ArmatureAwase public GameObject[] GroupedBones { - get => m_groupedBones.Clone() as GameObject[]; + get => m_groupedBones?.Clone() as GameObject[] ?? Array.Empty(); set { m_groupedBones = value.Clone() as GameObject[]; - OnValidate(); + MaMoveIndependentlyManager.Instance.Activate(this); } } - struct ChildState - { - internal Vector3 childLocalPos; - internal Quaternion childLocalRot; - internal Vector3 childLocalScale; - - // The child world position, recorded when we first initialized (or after unexpected child movement) - internal Matrix4x4 childToRoot; - } - - private Dictionary _children = new Dictionary(); - private HashSet _excluded = new HashSet(); - - void Awake() - { - hideFlags = HideFlags.DontSave; - } - - // We need to reparent the TRS values of the children from our prior frame state to the current frame state. - // This is done by computing the world affine matrix for the child in the prior frame, then converting to - // a local affine matrix in the current frame. - private void OnValidate() { - hideFlags = HideFlags.DontSave; - _excluded = new HashSet(); - if (m_groupedBones == null) - { - m_groupedBones = Array.Empty(); - } - - foreach (var grouped in m_groupedBones) - { - if (grouped != null) - { - _excluded.Add(grouped.transform); - } - } - - _priorFramePos = transform.localPosition; - _priorFrameRot = transform.localRotation; - _priorFrameScale = transform.localScale; - - _children.Clear(); - CheckChildren(); - } - - HashSet _observed = new HashSet(); - - private void CheckChildren() - { - _observed.Clear(); - - CheckChildren(transform); - foreach (var obj in m_groupedBones) - { - CheckChildren(obj.transform); - } - - // Remove any children that are no longer children - var toRemove = new List(); - foreach (var child in _children) - { - if (child.Key == null || !_observed.Contains(child.Key)) - { - toRemove.Add(child.Key); - } - } - - foreach (var child in toRemove) - { - _children.Remove(child); - } - } - - private Matrix4x4 ParentTransformMatrix(Transform parent) - { - Matrix4x4 transform = Matrix4x4.TRS( - parent.localPosition, - parent.localRotation, - parent.localScale - ); - - if (_excluded.Contains(parent)) - { - transform = ParentTransformMatrix(parent.parent) * transform; - } - - return transform; - } - - private void CheckChildren(Transform parent) - { - Matrix4x4 parentToRoot = ParentTransformMatrix(parent); - Matrix4x4 rootToParent = parentToRoot.inverse; - - foreach (Transform child in parent) - { - if (_excluded.Contains(child)) continue; - - _observed.Add(child); - - var localPosition = child.localPosition; - var localRotation = child.localRotation; - var localScale = child.localScale; - - if (!ArmatureLockController.MovedThisFrame && _children.TryGetValue(child, out var state)) - { - var deltaPos = localPosition - state.childLocalPos; - var deltaRot = Quaternion.Angle(localRotation, state.childLocalRot); - var deltaScale = (localScale - state.childLocalScale).sqrMagnitude; - - if (deltaPos.magnitude > EPSILON || deltaRot > EPSILON || deltaScale > EPSILON) - { - // The child object was moved in between parent updates; reconstruct its childToRoot to correct - // for this. - var oldChildTRS = Matrix4x4.TRS( - state.childLocalPos, - state.childLocalRot, - state.childLocalScale - ); - - var newChildTRS = Matrix4x4.TRS( - localPosition, - localRotation, - localScale - ); - - state.childToRoot = state.childToRoot * oldChildTRS.inverse * newChildTRS; - } - - Matrix4x4 childNewLocal = rootToParent * state.childToRoot; - - var newPosition = childNewLocal.MultiplyPoint(Vector3.zero); - var newRotation = childNewLocal.rotation; - var newScale = childNewLocal.lossyScale; #if UNITY_EDITOR - Undo.RecordObject(child, Undo.GetCurrentGroupName()); -#endif - - child.localPosition = newPosition; - child.localRotation = newRotation; - child.localScale = newScale; - - state.childLocalPos = child.localPosition; - state.childLocalRot = child.localRotation; - state.childLocalScale = child.localScale; - - _children[child] = state; - - continue; - } - - Matrix4x4 childTRS = Matrix4x4.TRS(localPosition, localRotation, localScale); - - state = new ChildState() + if (!PrefabUtility.IsPartOfPrefabAsset(this)) + { + EditorApplication.delayCall += () => { - childLocalPos = localPosition, - childLocalRot = localRotation, - childLocalScale = localScale, - childToRoot = parentToRoot * childTRS, + if (this != null) MaMoveIndependentlyManager.Instance.Activate(this); }; - - _children[child] = state; } +#endif } private void OnEnable() { - UpdateLoopController.OnMoveIndependentlyUpdate += OnUpdate; + MaMoveIndependentlyManager.Instance.Activate(this); } private void OnDisable() { - UpdateLoopController.OnMoveIndependentlyUpdate -= OnUpdate; - } - - private Vector3 _priorFramePos, _priorFrameScale; - private Quaternion _priorFrameRot; - - void OnUpdate() - { - if (this == null) - { - UpdateLoopController.OnMoveIndependentlyUpdate -= OnUpdate; - return; - } - - if (transform.parent == null) return; - - var pos = transform.localPosition; - var rot = transform.localRotation; - var scale = transform.localScale; - - var deltaPos = transform.parent.localToWorldMatrix.MultiplyVector(pos - _priorFramePos); - var deltaRot = Quaternion.Angle(rot, _priorFrameRot); - - var deltaScaleX = Mathf.Abs((scale - _priorFrameScale).x) / _priorFrameScale.x; - var deltaScaleY = Mathf.Abs((scale - _priorFrameScale).y) / _priorFrameScale.y; - var deltaScaleZ = Mathf.Abs((scale - _priorFrameScale).z) / _priorFrameScale.z; - - if (float.IsNaN(deltaScaleX) || float.IsInfinity(deltaScaleX)) deltaScaleX = 1; - if (float.IsNaN(deltaScaleY) || float.IsInfinity(deltaScaleY)) deltaScaleY = 1; - if (float.IsNaN(deltaScaleZ) || float.IsInfinity(deltaScaleZ)) deltaScaleZ = 1; - - float maxDeltaScale = Mathf.Max(deltaScaleX, Mathf.Max(deltaScaleY, deltaScaleZ)); - - if (deltaPos.magnitude > EPSILON || deltaRot > EPSILON || maxDeltaScale > 0.001) - { - CheckChildren(); - - _priorFramePos = pos; - _priorFrameRot = rot; - _priorFrameScale = scale; - } + MaMoveIndependentlyManager.Instance.Deactivate(this); } } } \ No newline at end of file