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); #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; } } }