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

View File

@ -26,11 +26,19 @@ namespace net.fushizen.modular_avatar.core.editor
-999999, -999999,
(Action<VRCAvatarDescriptor>)(av => VRCBuildPipelineCallbacks.OnPreprocessAvatar(av.gameObject)) (Action<VRCAvatarDescriptor>)(av => VRCBuildPipelineCallbacks.OnPreprocessAvatar(av.gameObject))
}); });
//EditorApplication.playModeStateChanged += OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
break; 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 Codice.CM.Common.Merge;
using UnityEditor;
using UnityEngine; 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 VRC.SDKBase.Editor.BuildPipeline;
using Matrix4x4 = UnityEngine.Matrix4x4;
using Vector3 = UnityEngine.Vector3;
namespace net.fushizen.modular_avatar.core.editor namespace net.fushizen.modular_avatar.core.editor
{ {
public class MergeArmatureHook : IVRCSDKPreprocessAvatarCallback public class MergeArmatureHook : IVRCSDKPreprocessAvatarCallback
{ {
public int callbackOrder => HookSequence.SEQ_MERGE_ARMATURE; 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) public bool OnPreprocessAvatar(GameObject avatarGameObject)
{ {
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true); var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
BoneRemappings.Clear();
ToDelete.Clear();
foreach (var mergeArmature in mergeArmatures) foreach (var mergeArmature in mergeArmatures)
{ {
MergeArmature(mergeArmature); MergeArmature(mergeArmature);
UnityEngine.Object.DestroyImmediate(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; 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) private void MergeArmature(ModularAvatarMergeArmature mergeArmature)
{ {
// TODO: error reporting framework? // TODO: error reporting framework?
if (mergeArmature.mergeTarget == null) return; 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); GameObject mergedSrcBone = new GameObject(src.name + "@" + GUID.Generate());
src.transform.SetParent(target.transform, true); 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>(); List<Transform> children = new List<Transform>();
foreach (Transform child in src.transform) foreach (Transform child in src.transform)
{ {
@ -43,17 +175,31 @@ namespace net.fushizen.modular_avatar.core.editor
{ {
var childGameObject = child.gameObject; var childGameObject = child.gameObject;
var childName = childGameObject.name; 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); childName.Length - config.prefix.Length - config.suffix.Length);
var targetObject = target.transform.Find(targetObjectName); var targetObject = newParent.transform.Find(targetObjectName);
if (targetObject != null) 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) 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; 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))) .Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
.Where(kvp => kvp.Value != null); .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 internal class RetargetMeshes : IVRCSDKPreprocessAvatarCallback
@ -77,19 +84,30 @@ namespace net.fushizen.modular_avatar.core.editor
} }
// Now remove retargeted bones // Now remove retargeted bones
foreach (var bonePair in BoneDatabase.GetRetargetedBones()) if (true)
{ {
var sourceBone = bonePair.Key; foreach (var bonePair in BoneDatabase.GetRetargetedBones())
var destBone = bonePair.Value;
foreach (Transform child in sourceBone)
{ {
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; return true;
} }
} }
@ -171,6 +189,8 @@ namespace net.fushizen.modular_avatar.core.editor
dst.bindposes = newBindPoses; dst.bindposes = newBindPoses;
renderer.bones = newBones; renderer.bones = newBones;
renderer.sharedMesh = dst; 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;
using UnityEditor.Animations; using UnityEditor.Animations;
using VRC.SDKBase.Editor.BuildPipeline;
namespace net.fushizen.modular_avatar.core.editor namespace net.fushizen.modular_avatar.core.editor
{ {
internal class CleanupTempAssets : IVRCSDKPostprocessAvatarCallback
{
public int callbackOrder => 99999;
public void OnPostprocessAvatar()
{
Util.DeleteTemporaryAssets();
}
}
public static class Util public static class Util
{ {
internal const string generatedAssetsSubdirectory = "999_Modular_Avatar_Generated"; 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 prefix;
public string suffix; public string suffix;
#if UNITY_EDITOR
void OnValidate() void OnValidate()
{ {
EditorApplication.delayCall += () => EditorApplication.delayCall += () =>
@ -37,5 +38,6 @@ namespace net.fushizen.modular_avatar.core
} }
}; };
} }
#endif
} }
} }

View File

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

View File

@ -35,6 +35,9 @@ GraphicsSettings:
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
- {fileID: 10770, 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_PreloadedShaders: []
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
type: 0} type: 0}
@ -45,7 +48,7 @@ GraphicsSettings:
m_DefaultMobileRenderingPath: 1 m_DefaultMobileRenderingPath: 1
m_TierSettings: [] m_TierSettings: []
m_LightmapStripping: 0 m_LightmapStripping: 0
m_FogStripping: 0 m_FogStripping: 1
m_InstancingStripping: 0 m_InstancingStripping: 0
m_LightmapKeepPlain: 1 m_LightmapKeepPlain: 1
m_LightmapKeepDirCombined: 1 m_LightmapKeepDirCombined: 1