diff --git a/Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs b/Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs index 78b0f1e5f..7b2575416 100644 --- a/Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs +++ b/Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs @@ -1,359 +1,458 @@ -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 = "GameObject/UnityEditorScripts/MeshIntegrator"; - const int MENU_PRIORITY = 0; - const string ASSET_SUFFIX = ".mesh.asset"; - const string ASSET_WITH_BLENDSHAPE_SUFFIX = ".blendshape.asset"; - - [MenuItem(MENU_KEY, true, MENU_PRIORITY)] - private static bool ExportValidate() - { - return Selection.activeObject != null && Selection.activeObject is GameObject; - } - - [MenuItem(MENU_KEY, false, 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); - var prefab = PrefabUtility.GetPrefabParent(go); - if (prefab != null) - { - var prefabPath = AssetDatabase.GetAssetPath(prefab); - assetPath = string.Format("{0}/{1}{2}", - Path.GetDirectoryName(prefabPath), - Path.GetFileNameWithoutExtension(prefabPath), - 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(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 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; - } - } -} +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 = "GameObject/UnityEditorScripts/MeshIntegrator"; + const int MENU_PRIORITY = 0; + const string ASSET_SUFFIX = ".mesh.asset"; + const string ASSET_WITH_BLENDSHAPE_SUFFIX = ".blendshape.asset"; + + [MenuItem(MENU_KEY, true, MENU_PRIORITY)] + private static bool ExportValidate() + { + return Selection.activeObject != null && Selection.activeObject is GameObject; + } + + [MenuItem(MENU_KEY, false, 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); + var prefab = PrefabUtility.GetPrefabParent(go); + 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; + } + } +}