mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55: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.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using nadena.dev.modular_avatar.core.ArmatureAwase;
|
|
||||||
using nadena.dev.ndmf.preview;
|
using nadena.dev.ndmf.preview;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using nadena.dev.modular_avatar.animation;
|
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.core.editor.plugin;
|
||||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||||
using nadena.dev.ndmf;
|
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 System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Unity.Collections.LowLevel.Unsafe;
|
using Unity.Collections.LowLevel.Unsafe;
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -15,12 +14,19 @@ namespace nadena.dev.modular_avatar.core.armature_lock
|
|||||||
internal NativeArray<T> Array;
|
internal NativeArray<T> Array;
|
||||||
|
|
||||||
public static implicit operator NativeArray<T>(NativeArrayRef<T> arrayRef) => arrayRef.Array;
|
public static implicit operator NativeArray<T>(NativeArrayRef<T> arrayRef) => arrayRef.Array;
|
||||||
|
public int Length => Array.Length;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Array.Dispose();
|
Array.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T this[int key]
|
||||||
|
{
|
||||||
|
get => Array[key];
|
||||||
|
set => Array[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public void Resize(int n)
|
public void Resize(int n)
|
||||||
{
|
{
|
||||||
if (Array.Length == n) return;
|
if (Array.Length == n) return;
|
||||||
@ -144,6 +150,11 @@ namespace nadena.dev.modular_avatar.core.armature_lock
|
|||||||
// array). As such, we clamp the length, rather than throwing an exception.
|
// array). As such, we clamp the length, rather than throwing an exception.
|
||||||
length = Math.Min(length, InUseMask.Array.Length - offset);
|
length = Math.Min(length, InUseMask.Array.Length - offset);
|
||||||
|
|
||||||
|
if (length < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("negative length");
|
||||||
|
}
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
UnsafeUtility.MemSet((byte*)InUseMask.Array.GetUnsafePtr() + offset, value ? (byte)1 : (byte)0, length);
|
UnsafeUtility.MemSet((byte*)InUseMask.Array.GetUnsafePtr() + offset, value ? (byte)1 : (byte)0, length);
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using nadena.dev.modular_avatar.core.armature_lock;
|
using nadena.dev.modular_avatar.core.armature_lock;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
#if MA_VRCSDK3_AVATARS
|
#if MA_VRCSDK3_AVATARS
|
||||||
using VRC.SDKBase;
|
using VRC.SDKBase;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.ArmatureAwase
|
namespace nadena.dev.modular_avatar.core
|
||||||
{
|
{
|
||||||
[ExecuteInEditMode]
|
[ExecuteInEditMode]
|
||||||
[AddComponentMenu("Modular Avatar/MA Move Independently")]
|
[AddComponentMenu("Modular Avatar/MA Move Independently")]
|
||||||
@ -22,229 +23,35 @@ namespace nadena.dev.modular_avatar.core.ArmatureAwase
|
|||||||
|
|
||||||
public GameObject[] GroupedBones
|
public GameObject[] GroupedBones
|
||||||
{
|
{
|
||||||
get => m_groupedBones.Clone() as GameObject[];
|
get => m_groupedBones?.Clone() as GameObject[] ?? Array.Empty<GameObject>();
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
m_groupedBones = value.Clone() as GameObject[];
|
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()
|
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
|
#if UNITY_EDITOR
|
||||||
Undo.RecordObject(child, Undo.GetCurrentGroupName());
|
if (!PrefabUtility.IsPartOfPrefabAsset(this))
|
||||||
#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()
|
|
||||||
{
|
{
|
||||||
childLocalPos = localPosition,
|
EditorApplication.delayCall += () =>
|
||||||
childLocalRot = localRotation,
|
{
|
||||||
childLocalScale = localScale,
|
if (this != null) MaMoveIndependentlyManager.Instance.Activate(this);
|
||||||
childToRoot = parentToRoot * childTRS,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_children[child] = state;
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
UpdateLoopController.OnMoveIndependentlyUpdate += OnUpdate;
|
MaMoveIndependentlyManager.Instance.Activate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
UpdateLoopController.OnMoveIndependentlyUpdate -= OnUpdate;
|
MaMoveIndependentlyManager.Instance.Deactivate(this);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user