diff --git a/Assets/VRM/UniGLTF/Scripts/IO/UnityPath.cs b/Assets/VRM/UniGLTF/Scripts/IO/UnityPath.cs index 0a19f33af..bc04c4ba8 100644 --- a/Assets/VRM/UniGLTF/Scripts/IO/UnityPath.cs +++ b/Assets/VRM/UniGLTF/Scripts/IO/UnityPath.cs @@ -57,6 +57,11 @@ namespace UniGLTF } } + public string FileName + { + get { return Path.GetFileName(Value); } + } + public string FileNameWithoutExtension { get { return Path.GetFileNameWithoutExtension(Value); } diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs index 09e068124..081118cbc 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/BoneMeshEraserWizard.cs @@ -168,11 +168,7 @@ namespace VRM // save mesh to Assets var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX); -#if UNITY_2018_2_OR_NEWER - var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go); -#else - var prefab = PrefabUtility.GetPrefabParent(go); -#endif + var prefab = SkinnedMeshUtility.GetPrefab(go); if (prefab != null) { var prefabPath = AssetDatabase.GetAssetPath(prefab); diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegrator.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegrator.cs deleted file mode 100644 index 81893a8fb..000000000 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegrator.cs +++ /dev/null @@ -1,461 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UnityEditor; -using UnityEngine; - - -namespace VRM -{ - /// - /// 複数のメッシュをまとめる - /// - [DisallowMultipleComponent] - public static class MeshIntegrator - { - const string MENU_KEY = SkinnedMeshUtility.MENU_KEY + "MeshIntegrator"; - const string ASSET_SUFFIX = ".mesh.asset"; - const string ASSET_WITH_BLENDSHAPE_SUFFIX = ".blendshape.asset"; - - [MenuItem(MENU_KEY, true, SkinnedMeshUtility.MENU_PRIORITY)] - private static bool ExportValidate() - { - return Selection.activeObject != null && Selection.activeObject is GameObject; - } - - [MenuItem(MENU_KEY, false, SkinnedMeshUtility.MENU_PRIORITY)] - private static void ExportFromMenu() - { - var go = Selection.activeObject as GameObject; - - Integrate(go); - } - - public static SkinnedMeshRenderer Integrate(GameObject go) - { - var without_blendshape = _Integrate(go, false); - if (without_blendshape == null) - { - return null; - - } - - // save mesh to Assets - var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX); -#if UNITY_2018_2_OR_NEWER - var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go); -#else - var prefab = PrefabUtility.GetPrefabParent(go); -#endif - if (prefab != null) - { - var prefabPath = AssetDatabase.GetAssetPath(prefab); - assetPath = string.Format("{0}/{1}_{2}{3}", - Path.GetDirectoryName(prefabPath), - Path.GetFileNameWithoutExtension(prefabPath), - go.name, - ASSET_SUFFIX - ); - } - else - { - assetPath = string.Format("Assets/{0}{1}", go.name, ASSET_SUFFIX); - } - - Debug.LogFormat("CreateAsset: {0}", assetPath); - AssetDatabase.CreateAsset(without_blendshape.sharedMesh, assetPath); - return without_blendshape; - } - - struct SubMesh - { - public List Indices; - public Material Material; - } - - class BlendShape - { - public int VertexOffset; - public string Name; - public float FrameWeight; - public Vector3[] Positions; - public Vector3[] Normals; - public Vector3[] Tangents; - } - - class Integrator - { -// public List Renderers { get; private set; } - public List Positions { get; private set; } - public List Normals { get; private set; } - public List UV { get; private set; } - public List Tangents { get; private set; } - public List BoneWeights { get; private set; } - - public List SubMeshes - { - get; - private set; - } - - public List BindPoses { get; private set; } - public List Bones { get; private set; } - - public List BlendShapes { get; private set; } - public void AddBlendShapesToMesh(Mesh mesh) - { - Dictionary map = new Dictionary(); - - foreach (var x in BlendShapes) - { - BlendShape bs = null; - if (!map.TryGetValue(x.Name, out bs)) - { - bs = new BlendShape(); - bs.Positions = Positions.ToArray(); - bs.Normals = Normals.ToArray(); - bs.Tangents = Tangents.Select(y => (Vector3)y).ToArray(); - bs.Name = x.Name; - bs.FrameWeight = x.FrameWeight; - map.Add(x.Name, bs); - } - - var j = x.VertexOffset; - for (int i = 0; i < x.Positions.Length; ++i, ++j) - { - bs.Positions[j] = x.Positions[i]; - bs.Normals[j] = x.Normals[i]; - bs.Tangents[j] = x.Tangents[i]; - } - } - - foreach (var kv in map) - { - //Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key); - mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight, - kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents); - } - } - - public Integrator() - { -// Renderers = new List(); - - Positions = new List(); - Normals = new List(); - UV = new List(); - Tangents = new List(); - BoneWeights = new List(); - - SubMeshes = new List(); - - BindPoses = new List(); - Bones = new List(); - - BlendShapes = new List(); - } - - static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset) - { - if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset; - if (bw.weight1 > 0) bw.boneIndex1 += boneIndexOffset; - if (bw.weight2 > 0) bw.boneIndex2 += boneIndexOffset; - if (bw.weight3 > 0) bw.boneIndex3 += boneIndexOffset; - return bw; - } - - public void Push(MeshRenderer renderer) - { - var meshFilter = renderer.GetComponent(); - if (meshFilter == null) - { - Debug.LogWarningFormat("{0} has no mesh filter", renderer.name); - return; - } - var mesh = meshFilter.sharedMesh; - if (mesh == null) - { - Debug.LogWarningFormat("{0} has no mesh", renderer.name); - return; - } - - var indexOffset = Positions.Count; - var boneIndexOffset = Bones.Count; - - Positions.AddRange(mesh.vertices - .Select(x => renderer.transform.TransformPoint(x)) - ); - Normals.AddRange(mesh.normals - .Select(x => renderer.transform.TransformVector(x)) - ); - UV.AddRange(mesh.uv); - Tangents.AddRange(mesh.tangents - .Select(t => - { - var v = renderer.transform.TransformVector(t.x, t.y, t.z); - return new Vector4(v.x, v.y, v.z, t.w); - }) - ); - - var self = renderer.transform; - var bone = self.parent; - if (bone == null) - { - Debug.LogWarningFormat("{0} is root gameobject.", self.name); - return; - } - var bindpose = bone.worldToLocalMatrix; - - BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length) - .Select(x => new BoneWeight() - { - boneIndex0 = Bones.Count, - weight0 = 1, - }) - ); - - BindPoses.Add(bindpose); - Bones.Add(bone); - - for (int i = 0; i < mesh.subMeshCount; ++i) - { - var indices = mesh.GetIndices(i).Select(x => x + indexOffset); - var mat = renderer.sharedMaterials[i]; - var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat)); - if (sameMaterialSubMeshIndex >= 0) - { - SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices); - } - else - { - SubMeshes.Add(new SubMesh - { - Indices = indices.ToList(), - Material = mat, - }); - } - } - } - - public void Push(SkinnedMeshRenderer renderer) - { - var mesh = renderer.sharedMesh; - if (mesh == null) - { - Debug.LogWarningFormat("{0} has no mesh", renderer.name); - return; - } - -// Renderers.Add(renderer); - - var indexOffset = Positions.Count; - var boneIndexOffset = Bones.Count; - - Positions.AddRange(mesh.vertices); - Normals.AddRange(mesh.normals); - UV.AddRange(mesh.uv); - Tangents.AddRange(mesh.tangents); - - if (mesh.vertexCount == mesh.boneWeights.Length) - { - BoneWeights.AddRange(mesh.boneWeights.Select(x => AddBoneIndexOffset(x, boneIndexOffset)).ToArray()); - } - else - { - BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => new BoneWeight()).ToArray()); - } - - BindPoses.AddRange(mesh.bindposes); - Bones.AddRange(renderer.bones); - - for (int i = 0; i < mesh.subMeshCount; ++i) - { - var indices = mesh.GetIndices(i).Select(x => x + indexOffset); - var mat = renderer.sharedMaterials[i]; - var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat)); - if (sameMaterialSubMeshIndex >= 0) - { - SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices); - } - else - { - SubMeshes.Add(new SubMesh - { - Indices = indices.ToList(), - Material = mat, - }); - } - } - - for (int i = 0; i < mesh.blendShapeCount; ++i) - { - var positions = (Vector3[])mesh.vertices.Clone(); - var normals = (Vector3[])mesh.normals.Clone(); - var tangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); - - mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents); - BlendShapes.Add(new BlendShape - { - VertexOffset = indexOffset, - FrameWeight = mesh.GetBlendShapeFrameWeight(i, 0), - Name = mesh.GetBlendShapeName(i), - Positions = positions, - Normals = normals, - Tangents = tangents, - }); - } - } - } - - static IEnumerable Traverse(Transform parent) - { - if (parent.gameObject.activeSelf) - { - yield return parent; - - foreach (Transform child in parent) - { - foreach (var x in Traverse(child)) - { - yield return x; - } - } - } - } - - static public IEnumerable EnumerateRenderer(Transform root, bool hasBlendShape) - { - foreach (var x in Traverse(root)) - { - var renderer = x.GetComponent(); - if (renderer != null) - { - if (renderer.sharedMesh != null) - { - if (renderer.gameObject.activeSelf) - { - if (renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape) - { - yield return renderer; - } - } - } - } - } - } - - static IEnumerable EnumerateMeshRenderer(Transform root) - { - foreach (var x in Traverse(root)) - { - var renderer = x.GetComponent(); - if (renderer != null) - { - var filter = x.GetComponent(); - if (filter != null && filter.sharedMesh != null && renderer.gameObject.activeSelf) - { - yield return renderer; - } - } - } - } - - static IEnumerable Ancestors(Transform self) - { - yield return self; - - if (self.parent != null) - { - foreach (var x in Ancestors(self.parent)) - { - yield return x; - } - } - } - - static SkinnedMeshRenderer _Integrate(GameObject go, bool hasBlendShape) - { - var meshNode = new GameObject(); - if (hasBlendShape) - { - meshNode.name = "MeshIntegrator(BlendShape)"; - } - else - { - meshNode.name = "MeshIntegrator"; - } - meshNode.transform.SetParent(go.transform, false); - - var renderers = EnumerateRenderer(go.transform, hasBlendShape).ToArray(); - - // Root objectを選出する - var root = renderers.Select(x => x.rootBone != null ? x.rootBone : x.transform) - .Select(x => Ancestors(x).Reverse().ToArray()) - .Aggregate((a, b) => - { - int i = 0; - for(; i ushort.MaxValue) - { -#if UNITY_2017_3_OR_NEWER - Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count); - mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; -#else - throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString())); -#endif - } - - mesh.vertices = integrator.Positions.ToArray(); - mesh.normals = integrator.Normals.ToArray(); - mesh.uv = integrator.UV.ToArray(); - mesh.tangents = integrator.Tangents.ToArray(); - mesh.boneWeights = integrator.BoneWeights.ToArray(); - mesh.subMeshCount = integrator.SubMeshes.Count; - for (var i = 0; i < integrator.SubMeshes.Count; ++i) - { - mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i); - } - mesh.bindposes = integrator.BindPoses.ToArray(); - - if (hasBlendShape) - { - integrator.AddBlendShapesToMesh(mesh); - } - - var integrated = meshNode.AddComponent(); - integrated.sharedMesh = mesh; - integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray(); - integrated.bones = integrator.Bones.ToArray(); - - return integrated; - } - } -} diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs new file mode 100644 index 000000000..23518027b --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UniGLTF; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + + +namespace VRM +{ + /// + /// 複数のメッシュをまとめる + /// + [DisallowMultipleComponent] + public static class MeshIntegratorEditor + { + const string MENU_KEY = "Assets/UnityEditorScripts/MeshIntegrator"; + const string ASSET_SUFFIX = ".mesh.asset"; + + [MenuItem(MENU_KEY, true)] + private static bool ExportValidate() + { + return Selection.activeObject != null && + Selection.activeObject is GameObject && + SkinnedMeshUtility.IsPrefab(Selection.activeObject); + } + + [MenuItem(MENU_KEY, false)] + private static void ExportFromMenu() + { + var go = Selection.activeObject as GameObject; + + Integrate(go); + } + + [MenuItem("Assets/fooa", false)] + private static void Foo() + { + var go = Selection.activeObject as GameObject; + + Debug.Log(SkinnedMeshUtility.IsPrefab(go)); + } + + + public static List Integrate(GameObject prefab) + { + Undo.RecordObject(prefab, "Mesh Integration"); + var instance = SkinnedMeshUtility.InstantiatePrefab(prefab); + + + var clips = new List(); + var proxy = instance.GetComponent(); + if (proxy != null && proxy.BlendShapeAvatar != null) + { + clips = proxy.BlendShapeAvatar.Clips; + } + foreach (var clip in clips) + { + Undo.RecordObject(clip, "Mesh Integration"); + } + + // Backup Exists + BackupVrmPrefab(prefab); + + // Execute + var results = MeshIntegratorUtility.Integrate(instance, clips); + + foreach (var res in results) + { + if (res.IntegratedRenderer == null) continue; + + SaveMeshAsset(res.IntegratedRenderer.sharedMesh, instance, res.IntegratedRenderer.gameObject.name); + Undo.RegisterCreatedObjectUndo(res.IntegratedRenderer.gameObject, "Integrate Renderers"); + } + + // destroy source renderers + foreach (var res in results) + { + foreach (var renderer in res.SourceSkinnedMeshRenderers) + { + Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); + renderer.gameObject.SetActive(false); + } + + foreach (var renderer in res.SourceMeshRenderers) + { + Undo.RecordObject(renderer.gameObject, "Deactivate old renderer"); + renderer.gameObject.SetActive(false); + } + } + + // Apply to Prefab + SkinnedMeshUtility.ApplyChangesToPrefab(instance); + Object.DestroyImmediate(instance); + + return results; + } + + private static void BackupVrmPrefab(GameObject rootPrefab) + { + var proxy = rootPrefab.GetComponent(); + + var srcAvatar = proxy.BlendShapeAvatar; + var dstAvatar = (BlendShapeAvatar) BackupAsset(srcAvatar, rootPrefab); + + var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip) BackupAsset(x, rootPrefab)); + dstAvatar.Clips = clipMapper.Values.ToList(); + + var dstPrefab = BackupAsset(rootPrefab, rootPrefab); + var dstInstance = SkinnedMeshUtility.InstantiatePrefab(dstPrefab); + dstInstance.GetComponent().BlendShapeAvatar = dstAvatar; + SkinnedMeshUtility.ApplyChangesToPrefab(dstInstance); + Object.DestroyImmediate(dstInstance); + } + + private static T BackupAsset(T asset, GameObject rootPrefab) where T : UnityEngine.Object + { + var srcAssetPath = UnityPath.FromAsset(asset); + var assetName = srcAssetPath.FileName; + + var backupDir = "MeshIntegratorBackup"; + var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(backupDir); + backupPath.EnsureFolder(); + var dstAssetPath = backupPath.Child(assetName); + + AssetDatabase.CopyAsset(srcAssetPath.Value, dstAssetPath.Value); + return dstAssetPath.LoadAsset(); + } + + private static string GetRootPrefabPath(GameObject go) + { + var prefab = SkinnedMeshUtility.IsPrefab(go) ? go : SkinnedMeshUtility.GetPrefab(go); + var assetPath = ""; + if (prefab != null) + { + var prefabPath = AssetDatabase.GetAssetPath(prefab); + assetPath = string.Format("{0}/", Path.GetDirectoryName(prefabPath)); + } + else + { + assetPath = string.Format("Assets/"); + } + assetPath = assetPath.Replace(@"\", @"/"); + return assetPath; + } + + private static void SaveMeshAsset(Mesh mesh, GameObject go, string name) + { + var assetPath = Path.Combine(GetRootPrefabPath(go), string.Format("{0}{1}", + name, + ASSET_SUFFIX + )); + + Debug.LogFormat("CreateAsset: {0}", assetPath); + AssetDatabase.CreateAsset(mesh, assetPath); + } + + } +} diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegrator.cs.meta b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta similarity index 100% rename from Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegrator.cs.meta rename to Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorEditor.cs.meta diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs index 9e45543b0..cd93e3237 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/MeshIntegratorWizard.cs @@ -10,6 +10,8 @@ namespace VRM { public class MeshIntegratorWizard : ScriptableWizard { + const string MENU_KEY = "Assets/UnityEditorScripts/MeshIntegratorWizard"; + [SerializeField] GameObject m_root; @@ -56,9 +58,9 @@ namespace VRM MaterialList[] m_duplicateMaterials; [Header("Result")] - public Mesh integrated; + public MeshIntegratorUtility.MeshIntegrationResult[] integrationResults; - [MenuItem(SkinnedMeshUtility.MENU_KEY + "MeshIntegrator Wizard", priority = SkinnedMeshUtility.MENU_PRIORITY)] + [MenuItem(MENU_KEY)] static void CreateWizard() { ScriptableWizard.DisplayWizard("MeshIntegrator", "Integrate and close window", "Integrate"); @@ -122,7 +124,7 @@ namespace VRM return; } - m_uniqueMaterials = MeshIntegrator.EnumerateRenderer(m_root.transform, false) + m_uniqueMaterials = MeshIntegratorUtility.EnumerateSkinnedMeshRenderer(m_root.transform, false) .SelectMany(x => x.sharedMaterials) .Distinct() .ToArray(); @@ -148,13 +150,7 @@ namespace VRM return; } - var renderer = MeshIntegrator.Integrate(m_root); - if (renderer == null) - { - return; - } - - integrated = renderer.sharedMesh; + integrationResults = MeshIntegratorEditor.Integrate(m_root).ToArray(); } void OnWizardCreate() diff --git a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs index baf387791..c8f9c143c 100644 --- a/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs +++ b/Assets/VRM/UniVRM/Editor/SkinnedMeshUtility/SkinnedMeshUtility.cs @@ -1,8 +1,42 @@ +using System; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + namespace VRM { public static class SkinnedMeshUtility { public const string MENU_KEY = "GameObject/UnityEditorScripts/"; public const int MENU_PRIORITY = 11; + + public static Object GetPrefab(GameObject instance) + { +#if UNITY_2018_2_OR_NEWER + return PrefabUtility.GetCorrespondingObjectFromSource(instance); +#else + return PrefabUtility.GetPrefabParent(go); +#endif + } + + public static bool IsPrefab(Object instance) + { + return instance != null && PrefabUtility.GetPrefabType(instance) == PrefabType.Prefab; + } + + public static void ApplyChangesToPrefab(GameObject instance) + { + var prefab = GetPrefab(instance); + if (prefab == null) return; + + PrefabUtility.ReplacePrefab(instance, prefab, ReplacePrefabOptions.ConnectToPrefab); + } + + public static GameObject InstantiatePrefab(GameObject prefab) + { + if (!IsPrefab(prefab)) return null; + + return (GameObject) PrefabUtility.InstantiatePrefab(prefab); + } } } \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs new file mode 100644 index 000000000..8c0dd6f72 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs @@ -0,0 +1,253 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace VRM +{ + public class MeshIntegrator + { + public struct SubMesh + { + public List Indices; + public Material Material; + } + + public class BlendShape + { + public int VertexOffset; + public string Name; + public float FrameWeight; + public Vector3[] Positions; + public Vector3[] Normals; + public Vector3[] Tangents; + } + +// public List Renderers { get; private set; } + public List Positions { get; private set; } + public List Normals { get; private set; } + public List UV { get; private set; } + public List Tangents { get; private set; } + public List BoneWeights { get; private set; } + + public List SubMeshes + { + get; + private set; + } + + public List BindPoses { get; private set; } + public List Bones { get; private set; } + + public List BlendShapes { get; private set; } + public void AddBlendShapesToMesh(Mesh mesh) + { + Dictionary map = new Dictionary(); + + foreach (var x in BlendShapes) + { + BlendShape bs = null; + if (!map.TryGetValue(x.Name, out bs)) + { + bs = new BlendShape(); + bs.Positions = new Vector3[Positions.Count]; + bs.Normals = new Vector3[Normals.Count]; + bs.Tangents = new Vector3[Tangents.Count]; + bs.Name = x.Name; + bs.FrameWeight = x.FrameWeight; + map.Add(x.Name, bs); + } + + var j = x.VertexOffset; + for (int i = 0; i < x.Positions.Length; ++i, ++j) + { + bs.Positions[j] = x.Positions[i]; + bs.Normals[j] = x.Normals[i]; + bs.Tangents[j] = x.Tangents[i]; + } + } + + foreach (var kv in map) + { + //Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key); + mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight, + kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents); + } + } + + public MeshIntegrator() + { +// Renderers = new List(); + + Positions = new List(); + Normals = new List(); + UV = new List(); + Tangents = new List(); + BoneWeights = new List(); + + SubMeshes = new List(); + + BindPoses = new List(); + Bones = new List(); + + BlendShapes = new List(); + } + + static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset) + { + if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset; + if (bw.weight1 > 0) bw.boneIndex1 += boneIndexOffset; + if (bw.weight2 > 0) bw.boneIndex2 += boneIndexOffset; + if (bw.weight3 > 0) bw.boneIndex3 += boneIndexOffset; + return bw; + } + + public void Push(MeshRenderer renderer) + { + var meshFilter = renderer.GetComponent(); + if (meshFilter == null) + { + Debug.LogWarningFormat("{0} has no mesh filter", renderer.name); + return; + } + var mesh = meshFilter.sharedMesh; + if (mesh == null) + { + Debug.LogWarningFormat("{0} has no mesh", renderer.name); + return; + } + + var indexOffset = Positions.Count; + var boneIndexOffset = Bones.Count; + + Positions.AddRange(mesh.vertices + .Select(x => renderer.transform.TransformPoint(x)) + ); + Normals.AddRange(mesh.normals + .Select(x => renderer.transform.TransformVector(x)) + ); + UV.AddRange(mesh.uv); + Tangents.AddRange(mesh.tangents + .Select(t => + { + var v = renderer.transform.TransformVector(t.x, t.y, t.z); + return new Vector4(v.x, v.y, v.z, t.w); + }) + ); + + var self = renderer.transform; + var bone = self.parent; + if (bone == null) + { + Debug.LogWarningFormat("{0} is root gameobject.", self.name); + return; + } + var bindpose = bone.worldToLocalMatrix; + + BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length) + .Select(x => new BoneWeight() + { + boneIndex0 = Bones.Count, + weight0 = 1, + }) + ); + + BindPoses.Add(bindpose); + Bones.Add(bone); + + for (int i = 0; i < mesh.subMeshCount; ++i) + { + var indices = mesh.GetIndices(i).Select(x => x + indexOffset); + var mat = renderer.sharedMaterials[i]; + var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat)); + if (sameMaterialSubMeshIndex >= 0) + { + SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices); + } + else + { + SubMeshes.Add(new SubMesh + { + Indices = indices.ToList(), + Material = mat, + }); + } + } + } + + public void Push(SkinnedMeshRenderer renderer) + { + var mesh = renderer.sharedMesh; + if (mesh == null) + { + Debug.LogWarningFormat("{0} has no mesh", renderer.name); + return; + } + +// Renderers.Add(renderer); + + var indexOffset = Positions.Count; + var boneIndexOffset = Bones.Count; + + Positions.AddRange(mesh.vertices); + Normals.AddRange(mesh.normals); + UV.AddRange(mesh.uv); + Tangents.AddRange(mesh.tangents); + + if (mesh.vertexCount == mesh.boneWeights.Length) + { + BoneWeights.AddRange(mesh.boneWeights.Select(x => AddBoneIndexOffset(x, boneIndexOffset)).ToArray()); + BindPoses.AddRange(mesh.bindposes); + Bones.AddRange(renderer.bones); + } + else + { + // Bone Count 0 の SkinnedMeshRenderer + var rigidBoneWeight = new BoneWeight + { + boneIndex0 = boneIndexOffset, + weight0 = 1f, + }; + BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => rigidBoneWeight).ToArray()); + BindPoses.Add(renderer.transform.localToWorldMatrix); + Bones.Add(renderer.transform); + } + + for (int i = 0; i < mesh.subMeshCount; ++i) + { + var indices = mesh.GetIndices(i).Select(x => x + indexOffset); + var mat = renderer.sharedMaterials[i]; + var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat)); + if (sameMaterialSubMeshIndex >= 0) + { + SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices); + } + else + { + SubMeshes.Add(new SubMesh + { + Indices = indices.ToList(), + Material = mat, + }); + } + } + + for (int i = 0; i < mesh.blendShapeCount; ++i) + { + var positions = (Vector3[])mesh.vertices.Clone(); + var normals = (Vector3[])mesh.normals.Clone(); + var tangents = mesh.tangents.Select(x => (Vector3)x).ToArray(); + + mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents); + BlendShapes.Add(new BlendShape + { + VertexOffset = indexOffset, + FrameWeight = mesh.GetBlendShapeFrameWeight(i, 0), + Name = mesh.GetBlendShapeName(i), + Positions = positions, + Normals = normals, + Tangents = tangents, + }); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs.meta b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs.meta new file mode 100644 index 000000000..15921c899 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegrator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 547dd57b50bf4820a570336659345084 +timeCreated: 1560168946 \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs new file mode 100644 index 000000000..e9ab1dc38 --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using System.Linq; +using UniGLTF; +using UnityEngine; + +namespace VRM +{ + public static class MeshIntegratorUtility + { + [System.Serializable] + public class MeshIntegrationResult + { + public List SourceSkinnedMeshRenderers = new List(); + public List SourceMeshRenderers = new List(); + public SkinnedMeshRenderer IntegratedRenderer; + } + + public static bool IntegrateRuntime(GameObject vrmRootObject) + { + if (vrmRootObject == null) return false; + var proxy = vrmRootObject.GetComponent(); + if (proxy == null) return false; + var avatar = proxy.BlendShapeAvatar; + if (avatar == null) return false; + var clips = avatar.Clips; + + var results = Integrate(vrmRootObject, clips); + if (results.Any(x => x.IntegratedRenderer == null)) return false; + + foreach (var result in results) + { + foreach (var renderer in result.SourceSkinnedMeshRenderers) + { + Object.Destroy(renderer); + } + + foreach (var renderer in result.SourceMeshRenderers) + { + Object.Destroy(renderer); + } + } + + return true; + } + + public static List Integrate(GameObject root, List blendshapeClips) + { + var result = new List(); + + var withoutBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: false); + if (withoutBlendShape.IntegratedRenderer != null) + { + result.Add(withoutBlendShape); + } + + var onlyBlendShape = IntegrateInternal(root, onlyBlendShapeRenderers: true); + if (onlyBlendShape.IntegratedRenderer != null) + { + result.Add(onlyBlendShape); + FollowBlendshapeRendererChange(blendshapeClips, onlyBlendShape, root); + } + + return result; + } + + private static void FollowBlendshapeRendererChange(List clips, MeshIntegrationResult result, GameObject root) + { + if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return; + + var rendererDict = result.SourceSkinnedMeshRenderers + .ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x); + + var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform); + + foreach (var clip in clips) + { + if (clip == null) continue; + + for (var i = 0; i < clip.Values.Length; ++i) + { + var val = clip.Values[i]; + if (rendererDict.ContainsKey(val.RelativePath)) + { + var srcRenderer = rendererDict[val.RelativePath]; + var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index); + var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name); + + val.RelativePath = dstPath; + val.Index = newIndex; + } + + clip.Values[i] = val; + } + } + } + + private static MeshIntegrationResult IntegrateInternal(GameObject go, bool onlyBlendShapeRenderers) + { + var result = new MeshIntegrationResult(); + +#if UNITY_2017_3_OR_NEWER +#else + return result; +#endif + + var meshNode = new GameObject(); + if (onlyBlendShapeRenderers) + { + meshNode.name = "MeshIntegrator(BlendShape)"; + } + else + { + meshNode.name = "MeshIntegrator"; + } + meshNode.transform.SetParent(go.transform, false); + + // レンダラから情報を集める + var integrator = new MeshIntegrator(); + + if (onlyBlendShapeRenderers) + { + foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, true)) + { + integrator.Push(x); + result.SourceSkinnedMeshRenderers.Add(x); + } + } + else + { + foreach (var x in EnumerateSkinnedMeshRenderer(go.transform, false)) + { + integrator.Push(x); + result.SourceSkinnedMeshRenderers.Add(x); + } + + foreach (var x in EnumerateMeshRenderer(go.transform)) + { + integrator.Push(x); + result.SourceMeshRenderers.Add(x); + } + } + + var mesh = new Mesh(); + mesh.name = "integrated"; + + if (integrator.Positions.Count > ushort.MaxValue) + { +#if UNITY_2017_3_OR_NEWER + Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count); + mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; +#else + throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString())); +#endif + } + + mesh.vertices = integrator.Positions.ToArray(); + mesh.normals = integrator.Normals.ToArray(); + mesh.uv = integrator.UV.ToArray(); + mesh.tangents = integrator.Tangents.ToArray(); + mesh.boneWeights = integrator.BoneWeights.ToArray(); + mesh.subMeshCount = integrator.SubMeshes.Count; + for (var i = 0; i < integrator.SubMeshes.Count; ++i) + { + mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i); + } + mesh.bindposes = integrator.BindPoses.ToArray(); + + if (onlyBlendShapeRenderers) + { + integrator.AddBlendShapesToMesh(mesh); + } + + var integrated = meshNode.AddComponent(); + integrated.sharedMesh = mesh; + integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray(); + integrated.bones = integrator.Bones.ToArray(); + result.IntegratedRenderer = integrated; + + return result; + } + + public static IEnumerable EnumerateSkinnedMeshRenderer(Transform root, bool hasBlendShape) + { + foreach (var x in Traverse(root)) + { + var renderer = x.GetComponent(); + if (renderer != null && + renderer.gameObject.activeInHierarchy && + renderer.sharedMesh != null && + renderer.enabled && + renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape) + { + yield return renderer; + } + } + } + + public static IEnumerable EnumerateMeshRenderer(Transform root) + { + foreach (var x in Traverse(root)) + { + var renderer = x.GetComponent(); + var filter = x.GetComponent(); + + if (renderer != null && + filter != null && + renderer.gameObject.activeInHierarchy && + filter.sharedMesh != null) + { + yield return renderer; + } + } + } + + private static IEnumerable Traverse(Transform parent) + { + if (parent.gameObject.activeSelf) + { + yield return parent; + + foreach (Transform child in parent) + { + foreach (var x in Traverse(child)) + { + yield return x; + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs.meta b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs.meta new file mode 100644 index 000000000..b9075e71b --- /dev/null +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/MeshIntegratorUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a982d9d30c0145038245b0214dc2f2e4 +timeCreated: 1560190306 \ No newline at end of file