mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
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
This commit is contained in:
parent
0606311f51
commit
a7ef0d6635
@ -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;
|
||||
|
@ -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;
|
||||
|
3
Runtime/ArmatureAwase/MoveIndep.meta
Normal file
3
Runtime/ArmatureAwase/MoveIndep.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abec4f397dc74b2f9bba6f71b5e702f3
|
||||
timeCreated: 1732395066
|
647
Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs
Normal file
647
Runtime/ArmatureAwase/MoveIndep/MAMoveIndependentlyManager.cs
Normal file
@ -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<TransformState>();
|
||||
_tpState = _nativeMemoryManager.CreateArray<TransformState>();
|
||||
_targetState = _nativeMemoryManager.CreateArray<TransformState>();
|
||||
_mappingStates = _nativeMemoryManager.CreateArray<MappingState>();
|
||||
|
||||
_errorFlags = _nativeMemoryManager.CreateArray<bool>();
|
||||
_enabled = _nativeMemoryManager.CreateArray<bool>();
|
||||
_sceneRootParent = _nativeMemoryManager.CreateArray<bool>();
|
||||
_falseArray = _nativeMemoryManager.CreateArray<bool>();
|
||||
|
||||
_anyError = new NativeArray<bool>(1, Allocator.Persistent);
|
||||
_anyDirty = new NativeArray<bool>(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<TransformState> _vpState;
|
||||
private readonly NativeArrayRef<TransformState> _tpState;
|
||||
private readonly NativeArrayRef<TransformState> _targetState;
|
||||
private readonly NativeArrayRef<MappingState> _mappingStates;
|
||||
private readonly NativeArrayRef<bool> _errorFlags;
|
||||
private NativeArray<bool> _anyError, _anyDirty;
|
||||
private readonly NativeArrayRef<bool> _enabled;
|
||||
private readonly NativeArrayRef<bool> _sceneRootParent, _falseArray;
|
||||
private readonly Dictionary<int, State> _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<MAMoveIndependently, State> _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<Transform> prefabRecord = null;
|
||||
if (_anyDirty[0])
|
||||
{
|
||||
prefabRecord = new List<Transform>();
|
||||
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<MAMoveIndependently> 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<Transform> groupedTransforms = new();
|
||||
groupedTransforms.Add(moveIndep.transform);
|
||||
|
||||
RegisterGroupedTransforms(moveIndep, groupedTransforms);
|
||||
|
||||
List<MAMoveIndependently> 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<MAMoveIndependently>(out var mami) && mami != moveIndep)
|
||||
{
|
||||
toReregister.Add(mami);
|
||||
}
|
||||
}
|
||||
|
||||
var ptr = moveIndep.transform.parent;
|
||||
while (ptr != null)
|
||||
{
|
||||
var parentMoveIndep = ptr.GetComponentInParent<MAMoveIndependently>();
|
||||
if (parentMoveIndep == null) break;
|
||||
|
||||
RegisterGroupedTransforms(parentMoveIndep, groupedTransforms);
|
||||
|
||||
ptr = parentMoveIndep.transform.parent;
|
||||
}
|
||||
|
||||
// Compute leaf transforms
|
||||
List<Transform> 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<Transform> groupedTransforms)
|
||||
{
|
||||
var candidates = new HashSet<GameObject>(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<MAMoveIndependently>(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<bool> 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<TransformState> States;
|
||||
[ReadOnly] public NativeArray<bool> 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<TransformState> States;
|
||||
|
||||
[ReadOnly] public NativeArray<bool> Enabled;
|
||||
[ReadOnly] public NativeArray<bool> SceneRootParent;
|
||||
|
||||
[NativeDisableContainerSafetyRestriction] [WriteOnly]
|
||||
public NativeArray<bool> 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<TransformState> VpState, TpState, TargetState;
|
||||
|
||||
[WriteOnly] [NativeDisableContainerSafetyRestriction]
|
||||
public NativeArray<bool> AnyDirty;
|
||||
|
||||
[WriteOnly] [NativeDisableContainerSafetyRestriction]
|
||||
public NativeArray<bool> AnyError;
|
||||
|
||||
public NativeArray<MappingState> States;
|
||||
|
||||
public NativeArray<bool> ErrorFlags;
|
||||
|
||||
[ReadOnly] public NativeArray<bool> 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<MappingState> States;
|
||||
|
||||
[ReadOnly] public NativeArray<bool> Errors;
|
||||
|
||||
[ReadOnly] public NativeArray<bool> Enabled;
|
||||
|
||||
[NativeDisableContainerSafetyRestriction] [WriteOnly]
|
||||
public NativeArray<bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 154891b009044835b43580e745f50a9e
|
||||
timeCreated: 1732394861
|
@ -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<T> Array;
|
||||
|
||||
public static implicit operator NativeArray<T>(NativeArrayRef<T> 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
|
||||
{
|
||||
|
@ -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<GameObject>();
|
||||
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<Transform, ChildState> _children = new Dictionary<Transform, ChildState>();
|
||||
private HashSet<Transform> _excluded = new HashSet<Transform>();
|
||||
|
||||
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<Transform>();
|
||||
if (m_groupedBones == null)
|
||||
{
|
||||
m_groupedBones = Array.Empty<GameObject>();
|
||||
}
|
||||
|
||||
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<Transform> _observed = new HashSet<Transform>();
|
||||
|
||||
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<Transform>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user