fix: visible head accessory editor breaks on prefabs (#543)

Closes: #534
This commit is contained in:
bd_ 2023-11-26 20:52:36 +09:00 committed by GitHub
parent 1dacdc4b37
commit 409501cb39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 51 deletions

View File

@ -5,14 +5,14 @@ namespace nadena.dev.modular_avatar.core.editor
[CustomEditor(typeof(ModularAvatarVisibleHeadAccessory))] [CustomEditor(typeof(ModularAvatarVisibleHeadAccessory))]
internal class FirstPersonVisibleEditor : MAEditorBase internal class FirstPersonVisibleEditor : MAEditorBase
{ {
private VisibleHeadAccessoryProcessor _processor; private VisibleHeadAccessoryValidation _validation;
private void OnEnable() private void OnEnable()
{ {
var target = (ModularAvatarVisibleHeadAccessory) this.target; var target = (ModularAvatarVisibleHeadAccessory) this.target;
var avatar = RuntimeUtil.FindAvatarTransformInParents(target.transform); var avatar = RuntimeUtil.FindAvatarTransformInParents(target.transform);
if (avatar != null) _processor = new VisibleHeadAccessoryProcessor(new BuildContext(avatar.gameObject)); if (avatar != null) _validation = new VisibleHeadAccessoryValidation(avatar.gameObject);
} }
protected override void OnInnerInspectorGUI() protected override void OnInnerInspectorGUI()
@ -25,14 +25,14 @@ namespace nadena.dev.modular_avatar.core.editor
#else #else
if (_processor != null) if (_validation != null)
{ {
var status = _processor.Validate(target); var status = _validation.Validate(target);
switch (status) switch (status)
{ {
case VisibleHeadAccessoryProcessor.ReadyStatus.Ready: case VisibleHeadAccessoryValidation.ReadyStatus.Ready:
case VisibleHeadAccessoryProcessor.ReadyStatus.ParentMarked: case VisibleHeadAccessoryValidation.ReadyStatus.ParentMarked:
EditorGUILayout.HelpBox(Localization.S("fpvisible.normal"), MessageType.Info); EditorGUILayout.HelpBox(Localization.S("fpvisible.normal"), MessageType.Info);
break; break;
default: default:

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using nadena.dev.modular_avatar.editor.ErrorReporting; using nadena.dev.modular_avatar.editor.ErrorReporting;
using UnityEngine; using UnityEngine;
using UnityEngine.Animations; using UnityEngine.Animations;
@ -9,9 +10,10 @@ using VRC.SDK3.Dynamics.PhysBone.Components;
namespace nadena.dev.modular_avatar.core.editor namespace nadena.dev.modular_avatar.core.editor
{ {
internal class VisibleHeadAccessoryProcessor internal class VisibleHeadAccessoryValidation
{ {
private const double EPSILON = 0.01; internal ImmutableHashSet<Transform> ActiveBones { get; }
internal Transform HeadBone { get; }
internal enum ReadyStatus internal enum ReadyStatus
{ {
@ -21,24 +23,14 @@ namespace nadena.dev.modular_avatar.core.editor
InPhysBoneChain InPhysBoneChain
} }
private BuildContext _context; public VisibleHeadAccessoryValidation(GameObject avatarRoot)
private Transform _avatarTransform;
private HashSet<Transform> _activeBones = new HashSet<Transform>();
private Transform _headBone;
private HashSet<Transform> _visibleBones = new HashSet<Transform>();
private Transform _proxyHead;
public VisibleHeadAccessoryProcessor(BuildContext context)
{ {
_context = context; var animator = avatarRoot.GetComponent<Animator>();
_avatarTransform = context.AvatarRootTransform; HeadBone = animator != null ? animator.GetBoneTransform(HumanBodyBones.Head) : null;
var animator = _avatarTransform.GetComponent<Animator>(); var activeBones = ImmutableHashSet.CreateBuilder<Transform>();
_headBone = animator != null ? animator.GetBoneTransform(HumanBodyBones.Head) : null;
#if MA_VRCSDK3_AVATARS #if MA_VRCSDK3_AVATARS
foreach (var physBone in _avatarTransform.GetComponentsInChildren<VRCPhysBone>(true)) foreach (var physBone in avatarRoot.GetComponentsInChildren<VRCPhysBone>(true))
{ {
var boneRoot = physBone.rootTransform != null ? physBone.rootTransform : physBone.transform; var boneRoot = physBone.rootTransform != null ? physBone.rootTransform : physBone.transform;
var ignored = new HashSet<Transform>(physBone.ignoreTransforms); var ignored = new HashSet<Transform>(physBone.ignoreTransforms);
@ -49,10 +41,12 @@ namespace nadena.dev.modular_avatar.core.editor
} }
} }
ActiveBones = activeBones.ToImmutable();
void Traverse(Transform bone, HashSet<Transform> ignored) void Traverse(Transform bone, HashSet<Transform> ignored)
{ {
if (ignored.Contains(bone)) return; if (ignored.Contains(bone)) return;
_activeBones.Add(bone); activeBones.Add(bone);
foreach (Transform child in bone) foreach (Transform child in bone)
{ {
@ -62,6 +56,53 @@ namespace nadena.dev.modular_avatar.core.editor
#endif #endif
} }
internal ReadyStatus Validate(ModularAvatarVisibleHeadAccessory target)
{
ReadyStatus status = ReadyStatus.NotUnderHead;
Transform node = target.transform.parent;
if (ActiveBones.Contains(target.transform)) return ReadyStatus.InPhysBoneChain;
while (node != null)
{
if (node.GetComponent<ModularAvatarVisibleHeadAccessory>()) return ReadyStatus.ParentMarked;
if (ActiveBones.Contains(node)) return ReadyStatus.InPhysBoneChain;
if (node == HeadBone)
{
status = ReadyStatus.Ready;
break;
}
node = node.parent;
}
return status;
}
}
internal class VisibleHeadAccessoryProcessor
{
private const double EPSILON = 0.01;
private BuildContext _context;
private VisibleHeadAccessoryValidation _validator;
private Transform _avatarTransform;
private ImmutableHashSet<Transform> _activeBones => _validator.ActiveBones;
private Transform _headBone => _validator.HeadBone;
private HashSet<Transform> _visibleBones = new HashSet<Transform>();
private Transform _proxyHead;
public VisibleHeadAccessoryProcessor(BuildContext context)
{
_context = context;
_avatarTransform = context.AvatarRootTransform;
_validator = new VisibleHeadAccessoryValidation(context.AvatarRootObject);
}
public void Process() public void Process()
{ {
bool didWork = false; bool didWork = false;
@ -91,8 +132,8 @@ namespace nadena.dev.modular_avatar.core.editor
#endif #endif
bool didWork = false; bool didWork = false;
if (Validate(target) == ReadyStatus.Ready) if (_validator.Validate(target) == VisibleHeadAccessoryValidation.ReadyStatus.Ready)
{ {
var proxy = CreateProxy(); var proxy = CreateProxy();
@ -146,29 +187,5 @@ namespace nadena.dev.modular_avatar.core.editor
return obj.transform; return obj.transform;
} }
internal ReadyStatus Validate(ModularAvatarVisibleHeadAccessory target)
{
ReadyStatus status = ReadyStatus.NotUnderHead;
Transform node = target.transform.parent;
if (_activeBones.Contains(target.transform)) return ReadyStatus.InPhysBoneChain;
while (node != null)
{
if (node.GetComponent<ModularAvatarVisibleHeadAccessory>()) return ReadyStatus.ParentMarked;
if (_activeBones.Contains(node)) return ReadyStatus.InPhysBoneChain;
if (node == _headBone)
{
status = ReadyStatus.Ready;
break;
}
node = node.parent;
}
return status;
}
} }
} }