mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-17 11:50:11 +08:00
Retain components (e.g. PhysBones) at their original hierarchy path
This commit is contained in:
parent
f7c6a81a4e
commit
625878e698
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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";
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user