diff --git a/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs new file mode 100644 index 000000000..4a4af9177 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + + +namespace UniGLTF.MeshUtility +{ + /// + /// - Freeze + /// - Integration + /// - Split + /// + /// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab. + /// - Implement undo + /// + /// + public class GltfMeshUtility + { + /// + /// GameObject 名が重複している場合にリネームする。 + /// 最初に実行(Avatar生成時のエラーを回避?) + /// + public bool ForceUniqueName = false; + + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeBlendShape = false; + + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeScaling = true; + + /// + /// Same as VRM-0 normalization + /// - Mesh + /// - Node + /// - InverseBindMatrices + /// + public bool FreezeRotation = false; + + public List MeshIntegrationGroups = new List(); + + /// + /// Create a headless model and solve VRM.FirstPersonFlag.Auto + /// + public bool GenerateMeshForFirstPersonAuto = false; + + /// + /// Split into having and not having BlendShape + /// + public bool SplitByBlendShape = false; + + public void IntegrateAll(GameObject root) + { + if (root == null) + { + return; + } + MeshIntegrationGroups.Add(new MeshIntegrationGroup + { + Name = "ALL", + Renderers = root.GetComponentsInChildren().ToList(), + }); + } + + void RemoveComponent(T c) where T : Component + { + if (c == null) + { + return; + } + var t = c.transform; + if (Application.isPlaying) + { + GameObject.Destroy(c); + } + else + { + GameObject.DestroyImmediate(c); + } + + if (t.childCount == 0) + { + var list = t.GetComponents(); + // Debug.Log($"{list[0].GetType().Name}"); + if (list.Length == 1 && list[0] == t) + { + if (Application.isPlaying) + { + GameObject.Destroy(t.gameObject); + } + else + { + GameObject.DestroyImmediate(t.gameObject); + } + } + } + } + + static GameObject GetOrCreateEmpty(GameObject go, string name) + { + foreach (var child in go.transform.GetChildren()) + { + if (child.name == name + && child.localPosition == Vector3.zero + && child.localScale == Vector3.one + && child.localRotation == Quaternion.identity) + { + return child.gameObject; + } + } + var empty = new GameObject(name); + empty.transform.SetParent(go.transform, false); + return empty; + } + + protected virtual List CopyMeshIntegrationGroups() + { + return MeshIntegrationGroups.ToList(); + } + + public virtual List Process(GameObject go) + { + // TODO unpack prefab + + // 正規化されたヒエラルキーを作る + if (FreezeBlendShape || FreezeRotation || FreezeScaling) + { + var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, + removeScaling: FreezeScaling, + removeRotation: FreezeRotation, + freezeBlendShape: FreezeBlendShape); + + // write back normalized transform to boneMap + BoneNormalizer.WriteBackResult(go, normalized, boneMap); + if (Application.isPlaying) + { + GameObject.Destroy(normalized); + } + else + { + GameObject.DestroyImmediate(normalized); + } + } + + var copy = CopyMeshIntegrationGroups(); + + var newGo = new List(); + { + var empty = GetOrCreateEmpty(go, "mesh"); + + var results = new List(); + foreach (var group in copy) + { + Integrate(newGo, empty, results, group); + } + + foreach (var result in results) + { + foreach (var r in result.SourceMeshRenderers) + { + RemoveComponent(r); + } + foreach (var r in result.SourceSkinnedMeshRenderers) + { + RemoveComponent(r); + } + } + } + + MeshIntegrationGroups.Clear(); + + return newGo; + } + + protected virtual MeshIntegrationResult Integrate(List newGo, + GameObject empty, List results, MeshIntegrationGroup group) + { + var result = MeshIntegrator.Integrate(group, SplitByBlendShape + ? MeshIntegrator.BlendShapeOperation.Split + : MeshIntegrator.BlendShapeOperation.Use); + results.Add(result); + + foreach (var created in result.AddIntegratedRendererTo(empty)) + { + newGo.Add(created); + } + + return result; + } + } +} diff --git a/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta new file mode 100644 index 000000000..0ad3bff31 --- /dev/null +++ b/Assets/UniGLTF/Runtime/MeshUtility/GltfMeshUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2425cf6ac1f2434986968ff0d4a4755 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs index 64a49c3e3..7d7158691 100644 --- a/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs +++ b/Assets/UniGLTF/Runtime/MeshUtility/MeshIntegrationGroup.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; namespace UniGLTF.MeshUtility @@ -8,5 +8,10 @@ namespace UniGLTF.MeshUtility { public string Name; public List Renderers = new List(); + + public static List ToList() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs index e66253d80..48b79d617 100644 --- a/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs +++ b/Assets/VRM10/Runtime/MeshUtility/Vrm10MeshUtility.cs @@ -1,93 +1,120 @@ using System; using System.Collections.Generic; using System.Linq; -using UniGLTF.MeshUtility; using UniHumanoid; using UnityEngine; -using VRMShaders; namespace UniVRM10 { - /// - /// - Freeze - /// - Integration - /// - Split - /// - /// - Implement runtime logic => Process a hierarchy in scene. Do not process prefab. - /// - Implement undo - /// - /// - public class Vrm10MeshUtility + public class Vrm10MeshUtility : UniGLTF.MeshUtility.GltfMeshUtility { - /// - /// GameObject 名が重複している場合にリネームする。 - /// 最初に実行(Avatar生成時のエラーを回避?) - /// - public bool ForceUniqueName = false; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeBlendShape = false; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeScaling = true; - - /// - /// Same as VRM-0 normalization - /// - Mesh - /// - Node - /// - InverseBindMatrices - /// - public bool FreezeRotation = false; - - public List MeshIntegrationGroups = new List(); - - /// - /// Create a headless model and solve VRM.FirstPersonFlag.Auto - /// - public bool GenerateMeshForFirstPersonAuto = false; - - /// - /// Split into having and not having BlendShape - /// - public bool SplitByBlendShape = false; - - public void IntegrateAll(GameObject root) + bool _generateFirstPerson = false; + protected override List CopyMeshIntegrationGroups() { - if (root == null) + var copy = new List(); + _generateFirstPerson = false; + if (GenerateMeshForFirstPersonAuto) { - return; - } - MeshIntegrationGroups.Add(new MeshIntegrationGroup - { - Name = "ALL", - Renderers = root.GetComponentsInChildren().ToList(), - }); - } - - MeshIntegrationGroup GetOrCreateGroup(string name) - { - foreach (var g in MeshIntegrationGroups) - { - if (g.Name == name) + foreach (var g in MeshIntegrationGroups) { - return g; + if (g.Name == "auto") + { + _generateFirstPerson = true; + // 元のメッシュを三人称に変更 + copy.Add(new UniGLTF.MeshUtility.MeshIntegrationGroup + { + Name = UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly.ToString(), + Renderers = g.Renderers.ToList(), + }); + } + copy.Add(g); } } - MeshIntegrationGroups.Add(new MeshIntegrationGroup + else { - Name = name, - }); - return MeshIntegrationGroups.Last(); + copy.AddRange(MeshIntegrationGroups); + } + return copy; + } + + protected override UniGLTF.MeshUtility.MeshIntegrationResult Integrate(List newGo, + GameObject empty, + List results, + UniGLTF.MeshUtility.MeshIntegrationGroup group) + { + var result = base.Integrate(newGo, empty, results, group); + + if (_generateFirstPerson && group.Name == "auto") + { + Debug.Log("generateFirstPerson"); + if (result.Integrated.Mesh != null) + { + _ProcessFirstPerson(_vrmInstance, result.Integrated); + } + if (result.IntegratedNoBlendShape.Mesh != null) + { + _ProcessFirstPerson(_vrmInstance, result.IntegratedNoBlendShape); + } + } + + return result; + } + + Vrm10Instance _vrmInstance = null; + /// + /// glTF に比べて Humanoid や FirstPerson の処理が追加される + /// + public override List Process(GameObject go) + { + _vrmInstance = go.GetComponent(); + if (_vrmInstance == null) + { + throw new ArgumentException(); + } + + if (ForceUniqueName) + { + throw new NotImplementedException(); + + // 必用? + var animator = go.GetComponent(); + var newAvatar = AvatarDescription.RecreateAvatar(animator); + animator.avatar = newAvatar; + } + + // TODO: update: spring + // TODO: update: constraint + // TODO: update: firstPerson offset + var list = base.Process(go); + + if (FreezeBlendShape || FreezeRotation || FreezeScaling) + { + var animator = go.GetComponent(); + var newAvatar = AvatarDescription.RecreateAvatar(animator); + animator.avatar = newAvatar; + } + + return list; + } + + void _ProcessFirstPerson(Vrm10Instance vrmInstance, UniGLTF.MeshUtility.MeshInfo info) + { + var firstPersonBone = vrmInstance.Humanoid.Head; + var task = VRM10ObjectFirstPerson.CreateErasedMeshAsync( + info.IntegratedRenderer, + firstPersonBone, + new VRMShaders.ImmediateCaller()); + task.Wait(); + + if (task.Result != null) + { + info.IntegratedRenderer.sharedMesh = task.Result; + info.IntegratedRenderer.name = "auto.headless"; + } + else + { + Debug.LogWarning("no result"); + } } public void IntegrateFirstPerson(GameObject root) @@ -113,202 +140,25 @@ namespace UniVRM10 } foreach (var a in fp.Renderers) { - var g = GetOrCreateGroup(a.FirstPersonFlag.ToString()); + var g = _GetOrCreateGroup(a.FirstPersonFlag.ToString()); g.Renderers.Add(a.GetRenderer(root.transform)); } } - void RemoveComponent(T c) where T : Component + UniGLTF.MeshUtility.MeshIntegrationGroup _GetOrCreateGroup(string name) { - if (c == null) + foreach (var g in MeshIntegrationGroups) { - return; - } - var t = c.transform; - if (Application.isPlaying) - { - GameObject.Destroy(c); - } - else - { - GameObject.DestroyImmediate(c); - } - - if (t.childCount == 0) - { - var list = t.GetComponents(); - // Debug.Log($"{list[0].GetType().Name}"); - if (list.Length == 1 && list[0] == t) + if (g.Name == name) { - if (Application.isPlaying) - { - GameObject.Destroy(t.gameObject); - } - else - { - GameObject.DestroyImmediate(t.gameObject); - } + return g; } } - } - - static GameObject GetOrCreateEmpty(GameObject go, string name) - { - foreach (var child in go.transform.GetChildren()) + MeshIntegrationGroups.Add(new UniGLTF.MeshUtility.MeshIntegrationGroup { - if (child.name == name - && child.localPosition == Vector3.zero - && child.localScale == Vector3.one - && child.localRotation == Quaternion.identity) - { - return child.gameObject; - } - } - var empty = new GameObject(name); - empty.transform.SetParent(go.transform, false); - return empty; - } - - public List Process(GameObject go) - { - var vrmInstance = go.GetComponent(); - if (vrmInstance == null) - { - throw new ArgumentException(); - } - - // TODO unpack prefab - - if (ForceUniqueName) - { - throw new NotImplementedException(); - - // 必用? - var animator = go.GetComponent(); - var newAvatar = AvatarDescription.RecreateAvatar(animator); - animator.avatar = newAvatar; - } - - // 正規化されたヒエラルキーを作る - if (FreezeBlendShape || FreezeRotation || FreezeScaling) - { - // TODO: UNDO - var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go, - removeScaling: FreezeScaling, - removeRotation: FreezeRotation, - freezeBlendShape: FreezeBlendShape); - - // TODO: update: spring - // TODO: update: constraint - // TODO: update: firstPerson offset - - // write back normalized transform to boneMap - BoneNormalizer.WriteBackResult(go, normalized, boneMap); - if (Application.isPlaying) - { - GameObject.Destroy(normalized); - } - else - { - GameObject.DestroyImmediate(normalized); - } - - var animator = go.GetComponent(); - var newAvatar = AvatarDescription.RecreateAvatar(animator); - animator.avatar = newAvatar; - } - - var copy = new List(); - var generateFirstPerson = false; - if (GenerateMeshForFirstPersonAuto) - { - foreach (var g in MeshIntegrationGroups) - { - if (g.Name == "auto") - { - generateFirstPerson = true; - // 元のメッシュを三人称に変更 - copy.Add(new MeshIntegrationGroup - { - Name = UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly.ToString(), - Renderers = g.Renderers.ToList(), - }); - } - copy.Add(g); - } - } - else - { - copy.AddRange(MeshIntegrationGroups); - } - - var newGo = new List(); - { - var empty = GetOrCreateEmpty(go, "mesh"); - - var results = new List(); - foreach (var group in copy) - { - var result = MeshIntegrator.Integrate(group, SplitByBlendShape - ? MeshIntegrator.BlendShapeOperation.Split - : MeshIntegrator.BlendShapeOperation.Use); - results.Add(result); - - foreach (var created in result.AddIntegratedRendererTo(empty)) - { - newGo.Add(created); - } - - if (generateFirstPerson && group.Name == "auto") - { - Debug.Log("generateFirstPerson"); - if (result.Integrated.Mesh != null) - { - ProcessFirstPerson(vrmInstance, result.Integrated); - } - if (result.IntegratedNoBlendShape.Mesh != null) - { - ProcessFirstPerson(vrmInstance, result.IntegratedNoBlendShape); - } - } - } - - foreach (var result in results) - { - foreach (var r in result.SourceMeshRenderers) - { - RemoveComponent(r); - } - foreach (var r in result.SourceSkinnedMeshRenderers) - { - RemoveComponent(r); - } - } - } - - MeshIntegrationGroups.Clear(); - - return newGo; - } - - void ProcessFirstPerson(Vrm10Instance vrmInstance, MeshInfo info) - { - var firstPersonBone = vrmInstance.Humanoid.Head; - var task = VRM10ObjectFirstPerson.CreateErasedMeshAsync( - info.IntegratedRenderer, - firstPersonBone, - new ImmediateCaller()); - task.Wait(); - - if (task.Result != null) - { - info.IntegratedRenderer.sharedMesh = task.Result; - info.IntegratedRenderer.name = "auto.headless"; - } - else - { - Debug.LogWarning("no result"); - } + Name = name, + }); + return MeshIntegrationGroups.Last(); } } -} +} \ No newline at end of file