modular-avatar/Runtime/AvatarObjectReference.cs
bd_ 8e7526e711
Connect reactive components to MenuItems (#944)
* refactor: generalize support for arbitrary parameters as conditions

* feat: automatically assign Menu Item parameters

* feat: ReactiveComponents respond to MenuItems

* feat: AvatarObjectReference tracks both paths and direct object references

* feat: set isSaved/isSynced/default values from MenuItem

* feat: Object Toggle preview supports menu items and manipulating parent objects

* feat: reactive previews respond to menu item default value states

* chore: update NDMF dependency
2024-08-04 19:31:43 -07:00

122 lines
4.1 KiB
C#

using System;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
[Serializable]
public class AvatarObjectReference
{
private long ReferencesLockedAtFrame = long.MinValue;
public static string AVATAR_ROOT = "$$$AVATAR_ROOT$$$";
public string referencePath;
[SerializeField] internal GameObject targetObject;
private bool _cacheValid;
private string _cachedPath;
private GameObject _cachedReference;
public GameObject Get(Component container)
{
bool cacheValid = _cacheValid || ReferencesLockedAtFrame == Time.frameCount;
if (cacheValid && _cachedPath == referencePath && _cachedReference != null) return _cachedReference;
_cacheValid = true;
_cachedPath = referencePath;
if (string.IsNullOrEmpty(referencePath))
{
_cachedReference = null;
return _cachedReference;
}
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
RuntimeUtil.OnHierarchyChanged += InvalidateCache;
var avatarTransform = RuntimeUtil.FindAvatarTransformInParents(container.transform);
if (avatarTransform == null) return (_cachedReference = null);
if (targetObject != null && targetObject.transform.IsChildOf(avatarTransform))
return _cachedReference = targetObject;
if (referencePath == AVATAR_ROOT)
{
_cachedReference = avatarTransform.gameObject;
return _cachedReference;
}
_cachedReference = avatarTransform.Find(referencePath)?.gameObject;
if (_cachedReference == null) return null;
// https://github.com/bdunderscore/modular-avatar/issues/308
// Some avatars have multiple "Armature" objects in order to confuse VRChat into changing the avatar eye
// position. We need to be smarter than VRChat and find the "true" armature in this case.
var targetName = _cachedReference.name;
var parent = _cachedReference.transform.parent;
if (targetName == "Armature" && parent != null && _cachedReference.transform.childCount == 0)
{
foreach (Transform possibleTarget in parent)
{
if (possibleTarget.gameObject.name == targetName && possibleTarget.childCount > 0)
{
_cachedReference = possibleTarget.gameObject;
break;
}
}
}
return _cachedReference;
}
public void Set(GameObject target)
{
if (target == null)
{
referencePath = "";
}
else if (RuntimeUtil.IsAvatarRoot(target.transform))
{
referencePath = AVATAR_ROOT;
}
else
{
referencePath = RuntimeUtil.AvatarRootPath(target);
}
_cachedReference = target;
_cacheValid = true;
targetObject = target;
}
private void InvalidateCache()
{
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
_cacheValid = false;
}
protected bool Equals(AvatarObjectReference other)
{
return GetDirectTarget() == other.GetDirectTarget() && referencePath == other.referencePath;
}
private GameObject GetDirectTarget()
{
return targetObject != null ? targetObject : null;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((AvatarObjectReference) obj);
}
public override int GetHashCode()
{
return (referencePath != null ? referencePath.GetHashCode() : 0);
}
}
}