mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-30 18:22:52 +08:00
Add pose rebinding support
This commit is contained in:
parent
b64dc0496f
commit
f7c6a81a4e
@ -0,0 +1,9 @@
|
||||
namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
internal static class HookSequence
|
||||
{
|
||||
public const int SEQ_RESETTERS = -90000;
|
||||
public const int SEQ_MERGE_ARMATURE = -80001;
|
||||
public const int SEQ_RETARGET_MESH = -80000;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e6b3680d07242d38d5b2c6b00951ca0
|
||||
timeCreated: 1661632859
|
@ -7,11 +7,11 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
public class MergeArmatureHook : IVRCSDKPreprocessAvatarCallback
|
||||
{
|
||||
public int callbackOrder => -3000;
|
||||
public int callbackOrder => HookSequence.SEQ_MERGE_ARMATURE;
|
||||
|
||||
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||
{
|
||||
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>();
|
||||
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
||||
|
||||
foreach (var mergeArmature in mergeArmatures)
|
||||
{
|
||||
@ -32,6 +32,7 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
|
||||
private void RecursiveMerge(ModularAvatarMergeArmature config, GameObject src, GameObject target)
|
||||
{
|
||||
BoneDatabase.AddMergedBone(src.transform);
|
||||
src.transform.SetParent(target.transform, true);
|
||||
List<Transform> children = new List<Transform>();
|
||||
foreach (Transform child in src.transform)
|
||||
|
@ -0,0 +1,176 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase.Editor.BuildPipeline;
|
||||
|
||||
namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
internal class MeshRetargeterResetHook : IVRCSDKPreprocessAvatarCallback
|
||||
{
|
||||
public int callbackOrder => HookSequence.SEQ_RESETTERS;
|
||||
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||
{
|
||||
BoneDatabase.ResetBones();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class BoneDatabase
|
||||
{
|
||||
private static Dictionary<Transform, bool> IsRetargetable = new Dictionary<Transform, bool>();
|
||||
|
||||
internal static void ResetBones()
|
||||
{
|
||||
IsRetargetable.Clear();
|
||||
}
|
||||
|
||||
internal static void AddMergedBone(Transform bone)
|
||||
{
|
||||
IsRetargetable[bone] = true;
|
||||
}
|
||||
|
||||
internal static void MarkNonRetargetable(Transform bone)
|
||||
{
|
||||
if (IsRetargetable.ContainsKey(bone)) IsRetargetable[bone] = false;
|
||||
}
|
||||
|
||||
internal static Transform GetRetargetedBone(Transform bone)
|
||||
{
|
||||
if (!IsRetargetable.ContainsKey(bone)) return null;
|
||||
|
||||
while (bone != null && IsRetargetable.ContainsKey(bone) && IsRetargetable[bone]) bone = bone.parent;
|
||||
|
||||
if (IsRetargetable.ContainsKey(bone)) return null;
|
||||
return bone;
|
||||
}
|
||||
|
||||
internal static IEnumerable<KeyValuePair<Transform, Transform>> GetRetargetedBones()
|
||||
{
|
||||
return IsRetargetable.Where((kvp) => kvp.Value)
|
||||
.Select(kvp => new KeyValuePair<Transform, Transform>(kvp.Key, GetRetargetedBone(kvp.Key)))
|
||||
.Where(kvp => kvp.Value != null);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RetargetMeshes : IVRCSDKPreprocessAvatarCallback
|
||||
{
|
||||
public int callbackOrder => HookSequence.SEQ_RETARGET_MESH;
|
||||
public bool OnPreprocessAvatar(GameObject avatarGameObject)
|
||||
{
|
||||
foreach (var renderer in avatarGameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||
{
|
||||
bool isRetargetable = false;
|
||||
foreach (var bone in renderer.bones)
|
||||
{
|
||||
if (BoneDatabase.GetRetargetedBone(bone) != null)
|
||||
{
|
||||
isRetargetable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRetargetable)
|
||||
{
|
||||
new MeshRetargeter(renderer).Retarget();
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove retargeted bones
|
||||
foreach (var bonePair in BoneDatabase.GetRetargetedBones())
|
||||
{
|
||||
var sourceBone = bonePair.Key;
|
||||
var destBone = bonePair.Value;
|
||||
|
||||
foreach (Transform child in sourceBone)
|
||||
{
|
||||
child.SetParent(destBone, true);
|
||||
}
|
||||
|
||||
UnityEngine.Object.DestroyImmediate(sourceBone.gameObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class processes a given mesh, adjusting the bind poses for any bones that are to be merged to instead match
|
||||
* the bind pose of the original avatar's bone.
|
||||
*/
|
||||
public class MeshRetargeter
|
||||
{
|
||||
private readonly SkinnedMeshRenderer renderer;
|
||||
private Mesh src, dst;
|
||||
|
||||
struct BindInfo
|
||||
{
|
||||
public Matrix4x4 priorLocalToBone;
|
||||
public Matrix4x4 localToBone;
|
||||
public Matrix4x4 priorToNew;
|
||||
}
|
||||
|
||||
public MeshRetargeter(SkinnedMeshRenderer renderer)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public void Retarget()
|
||||
{
|
||||
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(renderer.transform);
|
||||
if (avatar == null) throw new System.Exception("Could not find avatar in parents of " + renderer.name);
|
||||
var avatarTransform = avatar.transform;
|
||||
|
||||
var avPos = avatarTransform.position;
|
||||
var avRot = avatarTransform.rotation;
|
||||
var avScale = avatarTransform.lossyScale;
|
||||
|
||||
avatarTransform.position = Vector3.zero;
|
||||
avatarTransform.rotation = Quaternion.identity;
|
||||
avatarTransform.localScale = Vector3.one;
|
||||
|
||||
src = renderer.sharedMesh;
|
||||
dst = Mesh.Instantiate(src);
|
||||
dst.name = "RETARGETED: " + src.name;
|
||||
|
||||
RetargetBones();
|
||||
AdjustShapeKeys();
|
||||
|
||||
avatarTransform.position = avPos;
|
||||
avatarTransform.rotation = avRot;
|
||||
avatarTransform.localScale = avScale;
|
||||
|
||||
AssetDatabase.CreateAsset(dst, Util.GenerateAssetPath());
|
||||
}
|
||||
|
||||
private void AdjustShapeKeys()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
private void RetargetBones()
|
||||
{
|
||||
var originalBindPoses = src.bindposes;
|
||||
var originalBones = renderer.bones;
|
||||
|
||||
var newBones = (Transform[]) originalBones.Clone();
|
||||
var newBindPoses = (Matrix4x4[]) originalBindPoses.Clone();
|
||||
|
||||
for (int i = 0; i < originalBones.Length; i++)
|
||||
{
|
||||
Transform newBindTarget = BoneDatabase.GetRetargetedBone(originalBones[i]);
|
||||
if (newBindTarget == null) continue;
|
||||
|
||||
Matrix4x4 Bp = newBindTarget.worldToLocalMatrix * originalBones[i].localToWorldMatrix * originalBindPoses[i];
|
||||
|
||||
newBones[i] = newBindTarget;
|
||||
newBindPoses[i] = Bp;
|
||||
}
|
||||
|
||||
dst.bindposes = newBindPoses;
|
||||
renderer.bones = newBones;
|
||||
renderer.sharedMesh = dst;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71c5610d7a73420da0a5b40e315e2500
|
||||
timeCreated: 1661632791
|
47
Packages/net.fushizen.modular-avatar.core/Editor/Util.cs
Normal file
47
Packages/net.fushizen.modular-avatar.core/Editor/Util.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
|
||||
namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
public static class Util
|
||||
{
|
||||
internal const string generatedAssetsSubdirectory = "999_Modular_Avatar_Generated";
|
||||
internal const string generatedAssetsPath = "Assets/" + generatedAssetsSubdirectory;
|
||||
|
||||
static internal AnimatorController CreateContainer()
|
||||
{
|
||||
var container = new AnimatorController();
|
||||
AssetDatabase.CreateAsset(container, GenerateAssetPath());
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
internal static string GenerateAssetPath()
|
||||
{
|
||||
return GetGeneratedAssetsFolder() + "/" + GUID.Generate() + ".asset";
|
||||
}
|
||||
|
||||
internal static string GetGeneratedAssetsFolder()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder(generatedAssetsPath))
|
||||
{
|
||||
AssetDatabase.CreateFolder("Assets", generatedAssetsSubdirectory);
|
||||
}
|
||||
|
||||
return generatedAssetsPath;
|
||||
}
|
||||
|
||||
static internal void DeleteTemporaryAssets()
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
var subdir = generatedAssetsPath;
|
||||
|
||||
AssetDatabase.DeleteAsset(subdir);
|
||||
//FileUtil.DeleteFileOrDirectory(subdir);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 287488046f2f4b898159f7cbe91b3771
|
||||
timeCreated: 1661635336
|
Loading…
Reference in New Issue
Block a user