2023-09-24 15:59:43 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2024-02-17 18:20:08 +08:00
|
|
|
|
using nadena.dev.modular_avatar.ui;
|
2023-09-24 15:59:43 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
namespace nadena.dev.modular_avatar.core.armature_lock
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
internal class ArmatureLockConfig
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
: UnityEditor.ScriptableSingleton<ArmatureLockConfig>
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
#if !UNITY_EDITOR
|
|
|
|
|
internal static ArmatureLockConfig instance { get; } = new ArmatureLockConfig();
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-02-26 17:03:16 +08:00
|
|
|
|
[SerializeField] private bool _globalEnable = true;
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
internal bool GlobalEnable
|
|
|
|
|
{
|
|
|
|
|
get => _globalEnable;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (value == _globalEnable) return;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
2024-02-17 18:20:08 +08:00
|
|
|
|
Undo.RecordObject(this, "Toggle Edit Mode Bone Sync");
|
|
|
|
|
Menu.SetChecked(UnityMenuItems.TopMenu_EditModeBoneSync, value);
|
2024-02-26 17:03:16 +08:00
|
|
|
|
#endif
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
_globalEnable = value;
|
|
|
|
|
|
|
|
|
|
if (!value)
|
|
|
|
|
{
|
|
|
|
|
// Run prepare one last time to dispose of lock structures
|
|
|
|
|
UpdateLoopController.InvokeArmatureLockPrepare();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
[InitializeOnLoadMethod]
|
|
|
|
|
static void Init()
|
|
|
|
|
{
|
2024-02-26 17:03:16 +08:00
|
|
|
|
EditorApplication.delayCall += () =>
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
Menu.SetChecked(UnityMenuItems.TopMenu_EditModeBoneSync, instance._globalEnable);
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
[MenuItem(UnityMenuItems.TopMenu_EditModeBoneSync, false, UnityMenuItems.TopMenu_EditModeBoneSyncOrder)]
|
|
|
|
|
static void ToggleBoneSync()
|
|
|
|
|
{
|
|
|
|
|
instance.GlobalEnable = !instance.GlobalEnable;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
internal class ArmatureLockController : IDisposable
|
|
|
|
|
{
|
2023-09-27 19:24:42 +08:00
|
|
|
|
private static long lastMovedFrame = 0;
|
|
|
|
|
public static bool MovedThisFrame => Time.frameCount == lastMovedFrame;
|
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
// Undo operations can reinitialize the MAMA component, which destroys critical lock controller state.
|
|
|
|
|
// Avoid this issue by keeping a static reference to the controller for each MAMA component.
|
|
|
|
|
private static Dictionary<ModularAvatarMergeArmature, ArmatureLockController>
|
|
|
|
|
_controllers = new Dictionary<ModularAvatarMergeArmature, ArmatureLockController>();
|
|
|
|
|
|
|
|
|
|
public delegate IReadOnlyList<(Transform, Transform)> GetTransformsDelegate();
|
|
|
|
|
|
|
|
|
|
private readonly ModularAvatarMergeArmature _mama;
|
|
|
|
|
private readonly GetTransformsDelegate _getTransforms;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
private ArmatureLock _lock;
|
2023-09-24 15:59:43 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
private bool GlobalEnable => ArmatureLockConfig.instance.GlobalEnable;
|
2023-09-24 15:59:43 +08:00
|
|
|
|
private bool _updateActive;
|
|
|
|
|
|
|
|
|
|
private bool UpdateActive
|
|
|
|
|
{
|
|
|
|
|
get => _updateActive;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (UpdateActive == value) return;
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
if (value)
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
UpdateLoopController.OnArmatureLockPrepare += UpdateLoopPrepare;
|
|
|
|
|
UpdateLoopController.OnArmatureLockUpdate += UpdateLoopFinish;
|
2023-09-24 15:59:43 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
UpdateLoopController.OnArmatureLockPrepare -= UpdateLoopPrepare;
|
|
|
|
|
UpdateLoopController.OnArmatureLockUpdate -= UpdateLoopFinish;
|
2023-09-24 15:59:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateActive = value;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ArmatureLockMode _curMode, _mode;
|
|
|
|
|
|
|
|
|
|
public ArmatureLockMode Mode
|
|
|
|
|
{
|
|
|
|
|
get => _mode;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (value == _mode) return;
|
|
|
|
|
|
|
|
|
|
_mode = value;
|
|
|
|
|
|
|
|
|
|
UpdateActive = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool _enabled;
|
|
|
|
|
|
|
|
|
|
public bool Enabled
|
|
|
|
|
{
|
|
|
|
|
get => _enabled;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (Enabled == value) return;
|
2024-02-17 18:20:08 +08:00
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
_enabled = value;
|
|
|
|
|
if (_enabled) UpdateActive = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ArmatureLockController(ModularAvatarMergeArmature mama, GetTransformsDelegate getTransforms)
|
|
|
|
|
{
|
2023-09-25 21:57:45 +08:00
|
|
|
|
#if UNITY_EDITOR
|
2024-02-26 17:03:16 +08:00
|
|
|
|
AssemblyReloadEvents.beforeAssemblyReload += OnDomainUnload;
|
2023-09-25 21:57:45 +08:00
|
|
|
|
#endif
|
2023-09-24 15:59:43 +08:00
|
|
|
|
|
|
|
|
|
this._mama = mama;
|
|
|
|
|
this._getTransforms = getTransforms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static ArmatureLockController ForMerge(ModularAvatarMergeArmature mama,
|
|
|
|
|
GetTransformsDelegate getTransforms)
|
|
|
|
|
{
|
|
|
|
|
if (!_controllers.TryGetValue(mama, out var controller))
|
|
|
|
|
{
|
|
|
|
|
controller = new ArmatureLockController(mama, getTransforms);
|
|
|
|
|
_controllers[mama] = controller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return controller;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsStable()
|
|
|
|
|
{
|
|
|
|
|
if (Mode == ArmatureLockMode.NotLocked) return true;
|
|
|
|
|
|
|
|
|
|
if (_curMode == _mode && _lock?.IsStable() == true) return true;
|
|
|
|
|
return RebuildLock() && (_lock?.IsStable() ?? false);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
private void VoidPrepare()
|
2023-09-24 15:59:43 +08:00
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
UpdateLoopPrepare();
|
|
|
|
|
}
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
private void UpdateLoopFinish()
|
|
|
|
|
{
|
|
|
|
|
DoFinish();
|
2023-09-24 15:59:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal bool Update()
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
UpdateLoopPrepare();
|
|
|
|
|
return DoFinish();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsPrepared = false;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
private void UpdateLoopPrepare()
|
|
|
|
|
{
|
|
|
|
|
if (_mama == null || !_mama.gameObject.scene.IsValid())
|
|
|
|
|
{
|
|
|
|
|
UpdateActive = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
if (!Enabled)
|
|
|
|
|
{
|
|
|
|
|
UpdateActive = false;
|
2024-02-17 18:20:08 +08:00
|
|
|
|
_lock?.Dispose();
|
|
|
|
|
_lock = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!GlobalEnable)
|
|
|
|
|
{
|
|
|
|
|
_lock?.Dispose();
|
|
|
|
|
_lock = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
if (_curMode == _mode)
|
|
|
|
|
{
|
|
|
|
|
_lock?.Prepare();
|
|
|
|
|
IsPrepared = _lock != null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool DoFinish()
|
|
|
|
|
{
|
|
|
|
|
LockResult result;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
if (!GlobalEnable)
|
|
|
|
|
{
|
2023-09-24 15:59:43 +08:00
|
|
|
|
_lock?.Dispose();
|
|
|
|
|
_lock = null;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
var wasPrepared = IsPrepared;
|
|
|
|
|
IsPrepared = false;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
if (!Enabled) return true;
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
if (_curMode == _mode)
|
|
|
|
|
{
|
2024-02-17 18:20:08 +08:00
|
|
|
|
if (!wasPrepared) _lock?.Prepare();
|
2023-09-24 15:59:43 +08:00
|
|
|
|
result = _lock?.Execute() ?? LockResult.Failed;
|
2023-09-27 19:24:42 +08:00
|
|
|
|
if (result == LockResult.Success)
|
|
|
|
|
{
|
|
|
|
|
lastMovedFrame = Time.frameCount;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-24 15:59:43 +08:00
|
|
|
|
if (result != LockResult.Failed) return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!RebuildLock()) return false;
|
|
|
|
|
|
2024-02-17 18:20:08 +08:00
|
|
|
|
_lock?.Prepare();
|
2023-09-24 15:59:43 +08:00
|
|
|
|
result = (_lock?.Execute() ?? LockResult.Failed);
|
|
|
|
|
|
|
|
|
|
return result != LockResult.Failed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool RebuildLock()
|
|
|
|
|
{
|
|
|
|
|
_lock?.Dispose();
|
|
|
|
|
_lock = null;
|
|
|
|
|
|
|
|
|
|
var xforms = _getTransforms();
|
|
|
|
|
if (xforms == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
switch (Mode)
|
|
|
|
|
{
|
|
|
|
|
case ArmatureLockMode.BidirectionalExact:
|
|
|
|
|
_lock = new BidirectionalArmatureLock(_getTransforms());
|
|
|
|
|
break;
|
|
|
|
|
case ArmatureLockMode.BaseToMerge:
|
|
|
|
|
_lock = new OnewayArmatureLock(_getTransforms());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
UpdateActive = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
_lock = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_curMode = _mode;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_lock?.Dispose();
|
|
|
|
|
_lock = null;
|
|
|
|
|
|
2024-02-26 17:03:16 +08:00
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
AssemblyReloadEvents.beforeAssemblyReload -= OnDomainUnload;
|
|
|
|
|
#endif
|
2023-09-24 15:59:43 +08:00
|
|
|
|
|
|
|
|
|
_controllers.Remove(_mama);
|
|
|
|
|
UpdateActive = false;
|
|
|
|
|
}
|
2024-02-26 17:03:16 +08:00
|
|
|
|
|
|
|
|
|
private void OnDomainUnload()
|
|
|
|
|
{
|
|
|
|
|
// Unity 2019 does not call deferred callbacks before domain unload completes,
|
|
|
|
|
// so we need to make sure to immediately destroy all our TransformAccessArrays.
|
|
|
|
|
DeferDestroy.DestroyImmediate(this);
|
|
|
|
|
}
|
2023-09-24 15:59:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|