2023-03-04 13:12:53 +08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
2024-09-28 10:24:19 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
2023-03-04 13:12:53 +08:00
|
|
|
|
using VRC.SDK3.Dynamics.PhysBone.Components;
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#endif
|
2023-03-04 13:12:53 +08:00
|
|
|
|
|
|
|
|
|
namespace nadena.dev.modular_avatar.core.editor
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Remove all GameObjects which have no influence on the avatar.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GCGameObjectsPass
|
|
|
|
|
{
|
|
|
|
|
private readonly BuildContext _context;
|
|
|
|
|
private readonly GameObject _root;
|
|
|
|
|
private readonly HashSet<GameObject> referencedGameObjects = new HashSet<GameObject>();
|
|
|
|
|
|
|
|
|
|
internal GCGameObjectsPass(BuildContext context, GameObject root)
|
|
|
|
|
{
|
|
|
|
|
_context = context;
|
|
|
|
|
_root = root;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void OnPreprocessAvatar()
|
|
|
|
|
{
|
|
|
|
|
MarkAll();
|
|
|
|
|
Sweep();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MarkAll()
|
|
|
|
|
{
|
|
|
|
|
foreach (var obj in GameObjects(_root,
|
|
|
|
|
node =>
|
|
|
|
|
{
|
|
|
|
|
if (node.CompareTag("EditorOnly"))
|
|
|
|
|
{
|
|
|
|
|
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
|
|
|
|
{
|
|
|
|
|
// Retain EditorOnly objects (in case they contain camera fixtures or something),
|
|
|
|
|
// but ignore references _from_ them. (TODO: should we mark from them as well?)
|
|
|
|
|
MarkObject(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
))
|
|
|
|
|
{
|
|
|
|
|
foreach (var component in obj.GetComponents<Component>())
|
|
|
|
|
{
|
2023-06-06 22:40:46 +08:00
|
|
|
|
// component is null if script is missing
|
|
|
|
|
if (!component) continue;
|
2023-03-04 13:12:53 +08:00
|
|
|
|
switch (component)
|
|
|
|
|
{
|
|
|
|
|
case Transform t: break;
|
|
|
|
|
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
2023-03-04 13:12:53 +08:00
|
|
|
|
case VRCPhysBone pb:
|
|
|
|
|
MarkObject(obj);
|
|
|
|
|
MarkPhysBone(pb);
|
|
|
|
|
break;
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#endif
|
2023-03-04 13:12:53 +08:00
|
|
|
|
|
|
|
|
|
case AvatarTagComponent _:
|
|
|
|
|
// Tag components will not be retained at runtime, so pretend they're not there.
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
MarkObject(obj);
|
|
|
|
|
MarkAllReferencedObjects(component);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-21 15:49:02 +08:00
|
|
|
|
|
|
|
|
|
// Also retain humanoid bones
|
|
|
|
|
var animator = _root.GetComponent<Animator>();
|
2023-12-12 18:49:32 +08:00
|
|
|
|
if (animator != null && animator.isHuman)
|
2023-05-21 15:49:02 +08:00
|
|
|
|
{
|
2023-12-12 18:49:32 +08:00
|
|
|
|
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
|
2023-05-21 15:49:02 +08:00
|
|
|
|
{
|
|
|
|
|
if (bone == HumanBodyBones.LastBone) continue;
|
|
|
|
|
|
2023-12-12 18:49:32 +08:00
|
|
|
|
var transform = animator.GetBoneTransform(bone);
|
2023-05-21 15:49:02 +08:00
|
|
|
|
if (transform != null)
|
|
|
|
|
{
|
|
|
|
|
MarkObject(transform.gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-19 19:11:18 +08:00
|
|
|
|
|
|
|
|
|
// https://github.com/bdunderscore/modular-avatar/issues/332
|
|
|
|
|
// Retain transforms with names ending in "end" as these might be used for VRM spring bones
|
|
|
|
|
foreach (Transform t in _root.GetComponentsInChildren<Transform>())
|
|
|
|
|
{
|
|
|
|
|
if (t.name.ToLower().EndsWith("end"))
|
|
|
|
|
{
|
|
|
|
|
MarkObject(t.gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-15 20:00:08 +08:00
|
|
|
|
|
|
|
|
|
// https://github.com/bdunderscore/modular-avatar/issues/308
|
|
|
|
|
// If we have duplicate Armature bones, retain them all in order to deal with some horrible hacks that are
|
|
|
|
|
// in use in the wild.
|
2023-12-21 16:38:46 +08:00
|
|
|
|
if (animator != null && animator.isHuman)
|
2023-09-15 20:00:08 +08:00
|
|
|
|
{
|
2023-12-12 18:49:32 +08:00
|
|
|
|
try
|
2023-09-15 20:00:08 +08:00
|
|
|
|
{
|
2023-12-12 18:49:32 +08:00
|
|
|
|
var trueArmature = animator?.GetBoneTransform(HumanBodyBones.Hips)?.parent;
|
|
|
|
|
if (trueArmature != null)
|
2023-09-15 20:00:08 +08:00
|
|
|
|
{
|
2023-12-12 18:49:32 +08:00
|
|
|
|
foreach (Transform t in _root.transform)
|
2023-09-15 20:00:08 +08:00
|
|
|
|
{
|
2023-12-12 18:49:32 +08:00
|
|
|
|
if (t.name == trueArmature.name)
|
|
|
|
|
{
|
|
|
|
|
MarkObject(t.gameObject);
|
|
|
|
|
}
|
2023-09-15 20:00:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-28 10:24:19 +08:00
|
|
|
|
catch (MissingComponentException)
|
2023-12-12 18:49:32 +08:00
|
|
|
|
{
|
|
|
|
|
// No animator? weird. Move on.
|
|
|
|
|
}
|
2023-09-15 20:00:08 +08:00
|
|
|
|
}
|
2023-03-04 13:12:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#if MA_VRCSDK3_AVATARS
|
2023-03-04 13:12:53 +08:00
|
|
|
|
private void MarkPhysBone(VRCPhysBone pb)
|
|
|
|
|
{
|
|
|
|
|
var rootTransform = pb.GetRootTransform();
|
|
|
|
|
var ignoreTransforms = pb.ignoreTransforms ?? new List<Transform>();
|
|
|
|
|
|
|
|
|
|
foreach (var obj in GameObjects(rootTransform.gameObject,
|
|
|
|
|
obj => !obj.CompareTag("EditorOnly") && !ignoreTransforms.Contains(obj.transform)))
|
|
|
|
|
{
|
|
|
|
|
MarkObject(obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark colliders, etc
|
|
|
|
|
MarkAllReferencedObjects(pb);
|
|
|
|
|
}
|
2023-11-10 14:37:56 +08:00
|
|
|
|
#endif
|
2023-03-04 13:12:53 +08:00
|
|
|
|
|
|
|
|
|
private void MarkAllReferencedObjects(Component component)
|
|
|
|
|
{
|
|
|
|
|
var so = new SerializedObject(component);
|
|
|
|
|
var sp = so.GetIterator();
|
|
|
|
|
|
|
|
|
|
bool enterChildren = true;
|
|
|
|
|
while (sp.Next(enterChildren))
|
|
|
|
|
{
|
|
|
|
|
enterChildren = true;
|
|
|
|
|
|
|
|
|
|
switch (sp.propertyType)
|
|
|
|
|
{
|
|
|
|
|
case SerializedPropertyType.String:
|
|
|
|
|
enterChildren = false;
|
|
|
|
|
continue;
|
|
|
|
|
case SerializedPropertyType.ObjectReference:
|
|
|
|
|
if (sp.objectReferenceValue != null)
|
|
|
|
|
{
|
|
|
|
|
if (sp.objectReferenceValue is GameObject refObj)
|
|
|
|
|
{
|
|
|
|
|
MarkObject(refObj);
|
|
|
|
|
}
|
|
|
|
|
else if (sp.objectReferenceValue is Component comp)
|
|
|
|
|
{
|
|
|
|
|
MarkObject(comp.gameObject);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MarkObject(GameObject go)
|
|
|
|
|
{
|
|
|
|
|
while (go != null && referencedGameObjects.Add(go) && go != _root)
|
|
|
|
|
{
|
|
|
|
|
go = go.transform.parent?.gameObject;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Sweep()
|
|
|
|
|
{
|
|
|
|
|
foreach (var go in GameObjects())
|
|
|
|
|
{
|
|
|
|
|
if (!referencedGameObjects.Contains(go))
|
|
|
|
|
{
|
2024-09-28 10:24:19 +08:00
|
|
|
|
Object.DestroyImmediate(go);
|
2023-03-04 13:12:53 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<GameObject> GameObjects(GameObject node = null,
|
|
|
|
|
Func<GameObject, bool> shouldTraverse = null)
|
|
|
|
|
{
|
|
|
|
|
if (node == null) node = _root;
|
|
|
|
|
if (shouldTraverse == null) shouldTraverse = obj => !obj.CompareTag("EditorOnly");
|
|
|
|
|
|
|
|
|
|
if (!shouldTraverse(node)) yield break;
|
|
|
|
|
|
|
|
|
|
yield return node;
|
|
|
|
|
if (node == null) yield break;
|
|
|
|
|
|
|
|
|
|
// Guard against object deletion mid-traversal
|
|
|
|
|
List<Transform> children = new List<Transform>();
|
|
|
|
|
foreach (Transform t in node.transform)
|
|
|
|
|
{
|
|
|
|
|
children.Add(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var child in children)
|
|
|
|
|
{
|
|
|
|
|
foreach (var grandchild in GameObjects(child.gameObject, shouldTraverse))
|
|
|
|
|
{
|
|
|
|
|
yield return grandchild;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-19 19:11:18 +08:00
|
|
|
|
}
|