Retain components (e.g. PhysBones) at their original hierarchy path

This commit is contained in:
bd_ 2022-08-27 16:54:59 -07:00
parent f7c6a81a4e
commit 625878e698
8 changed files with 216 additions and 27 deletions

View File

@ -1,7 +1,7 @@
{
"dependencies": {
"com.unity.collab-proxy": "1.10.2",
"com.unity.ide.rider": "1.2.1",
"com.unity.ide.rider": "3.0.15",
"com.unity.ide.visualstudio": "2.0.11",
"com.unity.ide.vscode": "1.2.4",
"com.unity.test-framework": "1.1.29",
@ -10,6 +10,7 @@
"com.unity.ugui": "1.0.0",
"com.unity.xr.oculus.standalone": "2.38.4",
"com.unity.xr.openvr.standalone": "2.0.5",
"net.fushizen.modular-avatar.core": "0.0.1",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
@ -40,7 +41,6 @@
"com.unity.modules.video": "1.0.0",
"com.unity.modules.vr": "1.0.0",
"com.unity.modules.wind": "1.0.0",
"com.unity.modules.xr": "1.0.0",
"net.fushizen.modular-avatar.core": "0.0.1"
"com.unity.modules.xr": "1.0.0"
}
}

View File

@ -26,11 +26,19 @@ namespace net.fushizen.modular_avatar.core.editor
-999999,
(Action<VRCAvatarDescriptor>)(av => VRCBuildPipelineCallbacks.OnPreprocessAvatar(av.gameObject))
});
//EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
break;
}
}
}
private static void OnPlayModeStateChanged(PlayModeStateChange obj)
{
if (obj == PlayModeStateChange.ExitingPlayMode)
{
Util.DeleteTemporaryAssets();
}
}
}
}

View File

@ -1,39 +1,171 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Numerics;
using Codice.CM.Common.Merge;
using UnityEditor;
using UnityEngine;
using UnityEngine.Animations;
using VRC.Dynamics;
using VRC.SDK3.Dynamics.Contact.Components;
using VRC.SDK3.Dynamics.PhysBone.Components;
using VRC.SDKBase.Editor.BuildPipeline;
using Matrix4x4 = UnityEngine.Matrix4x4;
using Vector3 = UnityEngine.Vector3;
namespace net.fushizen.modular_avatar.core.editor
{
public class MergeArmatureHook : IVRCSDKPreprocessAvatarCallback
{
public int callbackOrder => HookSequence.SEQ_MERGE_ARMATURE;
private Dictionary<Transform, Transform> BoneRemappings = new Dictionary<Transform, Transform>();
private List<GameObject> ToDelete = new List<GameObject>();
public bool OnPreprocessAvatar(GameObject avatarGameObject)
{
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
BoneRemappings.Clear();
ToDelete.Clear();
foreach (var mergeArmature in mergeArmatures)
{
MergeArmature(mergeArmature);
UnityEngine.Object.DestroyImmediate(mergeArmature);
}
foreach (var renderer in avatarGameObject.transform.GetComponentsInChildren<SkinnedMeshRenderer>())
{
var bones = renderer.bones;
for (int i = 0; i < bones.Length; i++) bones[i] = MapBoneReference(bones[i]);
renderer.bones = bones;
renderer.rootBone = MapBoneReference(renderer.rootBone);
renderer.probeAnchor = MapBoneReference(renderer.probeAnchor);
}
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>())
{
if (c.rootTransform == null) c.rootTransform = c.transform;
UpdateBoneReferences(c);
}
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBoneCollider>())
{
if (c.rootTransform == null) c.rootTransform = c.transform;
UpdateBoneReferences(c);
}
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<ContactBase>())
{
if (c.rootTransform == null) c.rootTransform = c.transform;
UpdateBoneReferences(c);
}
foreach (var bone in ToDelete) UnityEngine.Object.DestroyImmediate(bone);
return true;
}
private void UpdateBoneReferences(Component c)
{
SerializedObject so = new SerializedObject(c);
SerializedProperty iter = so.GetIterator();
bool enterChildren = true;
while (iter.Next(enterChildren))
{
enterChildren = true;
switch (iter.propertyType)
{
case SerializedPropertyType.String: enterChildren = false;
break;
case SerializedPropertyType.ObjectReference:
if (iter.objectReferenceValue is Transform t)
{
iter.objectReferenceValue = MapBoneReference(t);
}
break;
}
}
so.ApplyModifiedPropertiesWithoutUndo();
}
private Transform MapBoneReference(Transform bone)
{
if (bone != null && BoneRemappings.TryGetValue(bone, out var newBone))
{
BoneDatabase.MarkNonRetargetable(newBone);
bone = newBone;
}
return bone;
}
private bool HasAdditionalComponents(GameObject go, out bool needsConstraint)
{
bool hasComponents = false;
needsConstraint = false;
foreach (Component c in go.GetComponents<Component>())
{
switch (c)
{
case Transform _: break;
case ModularAvatarMergeArmature _: break;
case VRCPhysBone _: case VRCPhysBoneCollider _: hasComponents = true;
break;
default:
hasComponents = true;
needsConstraint = true;
break;
}
}
return hasComponents;
}
private void MergeArmature(ModularAvatarMergeArmature mergeArmature)
{
// TODO: error reporting framework?
if (mergeArmature.mergeTarget == null) return;
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeArmature.mergeTarget.gameObject);
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeArmature.mergeTarget.gameObject, true);
}
private void RecursiveMerge(ModularAvatarMergeArmature config, GameObject src, GameObject target)
/**
* (Attempts to) merge the source gameobject into the target gameobject. Returns true if the merged source
* object must be retained.
*/
private bool RecursiveMerge(ModularAvatarMergeArmature config, GameObject src, GameObject newParent, bool zipMerge)
{
BoneDatabase.AddMergedBone(src.transform);
src.transform.SetParent(target.transform, true);
GameObject mergedSrcBone = new GameObject(src.name + "@" + GUID.Generate());
mergedSrcBone.transform.SetParent(src.transform.parent);
mergedSrcBone.transform.localPosition = src.transform.localPosition;
mergedSrcBone.transform.localRotation = src.transform.localRotation;
mergedSrcBone.transform.localScale = src.transform.localScale;
if (zipMerge) BoneDatabase.AddMergedBone(mergedSrcBone.transform);
mergedSrcBone.transform.SetParent(newParent.transform, true);
BoneRemappings[src.transform] = mergedSrcBone.transform;
bool retain = HasAdditionalComponents(src, out bool needsConstraint);
if (needsConstraint)
{
ParentConstraint constraint = src.AddComponent<ParentConstraint>();
constraint.AddSource(new ConstraintSource()
{
weight = 1,
sourceTransform = mergedSrcBone.transform
});
Matrix4x4 targetToSrc = src.transform.worldToLocalMatrix * newParent.transform.localToWorldMatrix;
constraint.translationOffsets = new Vector3[] {targetToSrc.MultiplyPoint(Vector3.zero)};
constraint.rotationOffsets = new Vector3[] {targetToSrc.rotation.eulerAngles};
constraint.locked = true;
constraint.constraintActive = true;
}
List<Transform> children = new List<Transform>();
foreach (Transform child in src.transform)
{
@ -43,17 +175,31 @@ namespace net.fushizen.modular_avatar.core.editor
{
var childGameObject = child.gameObject;
var childName = childGameObject.name;
if (childName.StartsWith(config.prefix) && childName.EndsWith(config.suffix))
GameObject childNewParent = mergedSrcBone;
bool shouldZip = zipMerge;
if (shouldZip && childName.StartsWith(config.prefix) && childName.EndsWith(config.suffix))
{
var targetObjectName = childName.Substring(config.prefix.Length,
var targetObjectName = childName.Substring(config.prefix.Length,
childName.Length - config.prefix.Length - config.suffix.Length);
var targetObject = target.transform.Find(targetObjectName);
var targetObject = newParent.transform.Find(targetObjectName);
if (targetObject != null)
{
RecursiveMerge(config, childGameObject, targetObject.gameObject);
childNewParent = targetObject.gameObject;
}
else
{
shouldZip = false;
}
}
var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
retain = retain || retainChild;
}
if (!retain) ToDelete.Add(src);
return retain;
}
}
}

View File

@ -37,7 +37,7 @@ namespace net.fushizen.modular_avatar.core.editor
internal static Transform GetRetargetedBone(Transform bone)
{
if (!IsRetargetable.ContainsKey(bone)) return null;
if (bone == null || !IsRetargetable.ContainsKey(bone)) return null;
while (bone != null && IsRetargetable.ContainsKey(bone) && IsRetargetable[bone]) bone = bone.parent;
@ -51,6 +51,13 @@ namespace net.fushizen.modular_avatar.core.editor
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
.Where(kvp => kvp.Value != null);
}
public static Transform GetRetargetedBone(Transform bone, bool fallbackToOriginal)
{
Transform retargeted = GetRetargetedBone(bone);
return retargeted ? retargeted : (fallbackToOriginal ? bone : null);
}
}
internal class RetargetMeshes : IVRCSDKPreprocessAvatarCallback
@ -77,19 +84,30 @@ namespace net.fushizen.modular_avatar.core.editor
}
// Now remove retargeted bones
foreach (var bonePair in BoneDatabase.GetRetargetedBones())
if (true)
{
var sourceBone = bonePair.Key;
var destBone = bonePair.Value;
foreach (Transform child in sourceBone)
foreach (var bonePair in BoneDatabase.GetRetargetedBones())
{
child.SetParent(destBone, true);
if (BoneDatabase.GetRetargetedBone(bonePair.Key) == null) continue;
var sourceBone = bonePair.Key;
var destBone = bonePair.Value;
var children = new List<Transform>();
foreach (Transform child in sourceBone)
{
children.Add(child);
}
foreach (Transform child in children) {
child.SetParent(destBone, true);
}
UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
}
UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
}
return true;
}
}
@ -171,6 +189,8 @@ namespace net.fushizen.modular_avatar.core.editor
dst.bindposes = newBindPoses;
renderer.bones = newBones;
renderer.sharedMesh = dst;
renderer.rootBone = BoneDatabase.GetRetargetedBone(renderer.rootBone, true);
renderer.probeAnchor = BoneDatabase.GetRetargetedBone(renderer.probeAnchor, true);
}
}
}

View File

@ -1,8 +1,18 @@
using UnityEditor;
using UnityEditor.Animations;
using VRC.SDKBase.Editor.BuildPipeline;
namespace net.fushizen.modular_avatar.core.editor
{
internal class CleanupTempAssets : IVRCSDKPostprocessAvatarCallback
{
public int callbackOrder => 99999;
public void OnPostprocessAvatar()
{
Util.DeleteTemporaryAssets();
}
}
public static class Util
{
internal const string generatedAssetsSubdirectory = "999_Modular_Avatar_Generated";

View File

@ -13,6 +13,7 @@ namespace net.fushizen.modular_avatar.core
public string prefix;
public string suffix;
#if UNITY_EDITOR
void OnValidate()
{
EditorApplication.delayCall += () =>
@ -37,5 +38,6 @@ namespace net.fushizen.modular_avatar.core
}
};
}
#endif
}
}

View File

@ -24,11 +24,11 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
"version": "1.2.1",
"version": "3.0.15",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.test-framework": "1.1.1"
"com.unity.ext.nunit": "1.0.6"
},
"url": "https://packages.unity.com"
},

View File

@ -35,6 +35,9 @@ GraphicsSettings:
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0}
m_PreloadedShaders: []
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
type: 0}
@ -45,7 +48,7 @@ GraphicsSettings:
m_DefaultMobileRenderingPath: 1
m_TierSettings: []
m_LightmapStripping: 0
m_FogStripping: 0
m_FogStripping: 1
m_InstancingStripping: 0
m_LightmapKeepPlain: 1
m_LightmapKeepDirCombined: 1