using Gallop; using LibMMD.Material; using LibMMD.Model; using LibMMD.Reader; using LibMMD.Unity3D; using LibMMD.Writer; using System; using System.Collections.Generic; using System.IO; using UnityEngine; using static LibMMD.Model.Morph; using static LibMMD.Model.SkinningOperator; //using UnityGLTF; public class ModelExporter { static string modelInfo = $"Generated by UmaViewer {Application.version}, Copyright Cygames \nLink : https://github.com/katboi01/UmaViewer"; public static void ExportModel(UmaContainerCharacter container, string path) { container.SetDynamicBoneEnable(false); container.EnablePhysics = false; container.UmaFaceAnimator?.Rebind(); container.UmaAnimator?.Rebind(); container.EnableEyeTracking = false; container.FaceDrivenKeyTarget?.FacialResetAll(); AddBlendShape(container); var textures = TextureExporter.ExportAllTexture(Path.GetDirectoryName(path), container.gameObject); var model = ReadPMXModel(container.CharaEntry, container, textures); FileStream fileStream = new FileStream(path, FileMode.Create); BinaryWriter writer = new BinaryWriter(fileStream); var config = new ModelConfig() { GlobalToonPath = "Toon" }; PMXWriter.Write(writer, model, config); writer.Close(); fileStream.Close(); UmaViewerUI.Instance.ShowMessage($"PMX Save at {path}", UIMessageType.Success); } public static RawMMDModel ReadPMXModel(CharaEntry entry, UmaContainer container, string[] textures) { RawMMDModel model = new RawMMDModel(); model.Name = entry.Name; model.NameEn = entry.GetName(); model.Description = model.DescriptionEn = $"{entry.GetName()}\n{modelInfo}"; //Rean Bones var rootBone = container.transform.Find("Position"); List bones = new List(rootBone.GetComponentsInChildren()); bones.RemoveAll(o => o.name.Contains("Col_")); //Read vertices And triangles List renderers = new List(container.GetComponentsInChildren()); List triangles = new List(); model.Vertices = ReadVerticesAndTriangles(renderers, bones, ref triangles, container.transform); model.TriangleIndexes = triangles.ToArray(); //Read Texture reference foreach (var texture in textures) { model.TextureList.Add(new MMDTexture(texture)); } model.Parts = ReadPartMaterials(renderers, model); model.Bones = ReadBones(bones); model.Morphs = ReadMorph(renderers); model.Rigidbodies = new MMDRigidBody[0]; model.Joints = new MMDJoint[0]; return model; } public static Vector3[] Vec4ToVec3(Vector4[] vector4s) { Vector3[] tmp = new Vector3[vector4s.Length]; for (int i = 0; i < vector4s.Length; i++) { tmp[i] = new Vector3(vector4s[i].x, vector4s[i].y, vector4s[i].z); } return tmp; } public static Vector3[] CalDelta(Vector3[] ori, Vector3[] end) { Vector3[] tmp = new Vector3[ori.Length]; for (int i = 0; i < ori.Length; i++) { tmp[i] = end[i] - ori[i]; } return tmp; } private static void AddBlendShape(UmaContainerCharacter container) { if (!container.FaceDrivenKeyTarget) return; SkinnedMeshRenderer faceMesh = null; SkinnedMeshRenderer eyebrowMesh = null; SkinnedMeshRenderer earMesh = null; foreach (SkinnedMeshRenderer s in container.GetComponentsInChildren()) { if (s.name.Contains("M_Face")) faceMesh = s; else if (s.name.Contains("M_Mayu")) eyebrowMesh = s; else if (s.name.Contains("M_Hair")) earMesh = s; } var facial = container.FaceDrivenKeyTarget; Action, Mesh> addBlendShapePart = delegate (SkinnedMeshRenderer skin, List morphs, Mesh baseMesh) { container.FaceOverrideData = null; foreach (var morph in morphs) { facial.ClearAllWeights(); morph.weight = 1; facial.ChangeMorph(); Mesh deltaMesh = new Mesh(); skin.BakeMesh(deltaMesh); skin.sharedMesh.AddBlendShapeFrame( $"{morph.name}({morph.tag})[{skin.name}]", 1, CalDelta(baseMesh.vertices, deltaMesh.vertices), CalDelta(baseMesh.normals, deltaMesh.normals), CalDelta(Vec4ToVec3(baseMesh.tangents), Vec4ToVec3(deltaMesh.tangents))); } }; if (faceMesh) { Mesh basefaceMesh = new Mesh(); faceMesh.BakeMesh(basefaceMesh); addBlendShapePart(faceMesh, facial.EyeBrowMorphs, basefaceMesh); addBlendShapePart(faceMesh, facial.EyeMorphs, basefaceMesh); addBlendShapePart(faceMesh, facial.MouthMorphs, basefaceMesh); } if (eyebrowMesh) { Mesh baseEyeBrowMesh = new Mesh(); eyebrowMesh.BakeMesh(baseEyeBrowMesh); addBlendShapePart(eyebrowMesh, facial.EyeBrowMorphs, baseEyeBrowMesh); } if (earMesh) { Mesh baseEarMesh = new Mesh(); earMesh.BakeMesh(baseEarMesh); addBlendShapePart(earMesh, facial.EarMorphs, baseEarMesh); } facial.ClearAllWeights(); facial.ChangeMorph(); } private static Morph[] ReadMorph(List renderers) { List morphs = new List(); int vertexOffset = 0; foreach (Renderer renderer in renderers) { Mesh mesh; if (renderer is MeshRenderer mr) { var meshfilter = mr.GetComponent(); mesh = meshfilter.mesh; } else { mesh = ((SkinnedMeshRenderer)renderer).sharedMesh; } var vertexCount = mesh.vertexCount; for (int i = 0; i < mesh.blendShapeCount; i++) { var deltaVertices = new Vector3[vertexCount]; var deltaNormals = new Vector3[vertexCount]; var deltaTangents= new Vector3[vertexCount]; mesh.GetBlendShapeFrameVertices(i, 0, deltaVertices, deltaNormals, deltaTangents); Morph morph = new Morph(); morph.Name = morph.NameEn = mesh.GetBlendShapeName(i); morph.Type = MorphType.MorphTypeVertex; var datas = new VertexMorphData[vertexCount]; for (int j = 0; j < vertexCount; j++) { var data = new VertexMorphData(); data.VertexIndex = vertexOffset + j; data.Offset = deltaVertices[j]; datas[j] = data; } morph.MorphDatas = datas; if (morph.Name.Contains("Mouth_")) { morph.Category = MorphCategory.MorphCatMouth; } else if(morph.Name.Contains("EyeBrow_")) { morph.Category = MorphCategory.MorphCatEyebrow; } else if(morph.Name.Contains("Eye_")) { morph.Category = MorphCategory.MorphCatEye; } else { morph.Category = MorphCategory.MorphCatOther; } morphs.Add(morph); } vertexOffset += mesh.vertexCount; } return morphs.ToArray(); } private static Bone[] ReadBones(List bonelist) { List pmxbones = new List(); foreach (var bone in bonelist) { Bone pmxbone = new Bone(); pmxbone.Name = pmxbone.NameEn = bone.name; pmxbone.Position = bone.position; pmxbone.ParentIndex = bonelist.IndexOf(bone.parent); pmxbone.TransformLevel = 0; pmxbone.Visible = true; pmxbone.Movable = true; pmxbone.Rotatable = true; pmxbone.Controllable = true; pmxbone.ChildBoneVal = new Bone.ChildBone() { ChildUseId = true, Index = (bone.childCount > 0 ? bonelist.IndexOf(bone.GetChild(0)) : -1) }; pmxbones.Add(pmxbone); } return pmxbones.ToArray(); } private static Part[] ReadPartMaterials(List renderers, RawMMDModel model) { List parts = new List(); int baseShift = 0; foreach (Renderer renderer in renderers) { Mesh mesh; if (renderer is MeshRenderer mr) { var meshfilter = mr.GetComponent(); mesh = meshfilter.mesh; } else { mesh = ((SkinnedMeshRenderer)renderer).sharedMesh; } var materials = new List(renderer.sharedMaterials); for (int i = 0; i < mesh.subMeshCount; i++) { var material = (i < materials.Count ? materials[i] : materials[materials.Count - 1]); var part = new Part(); var mat = new MMDMaterial(); part.Material = mat; mat.Name = mat.NameEn = material.name.Replace(" (Instance)",""); mat.DiffuseColor = Color.white; mat.SpecularColor = Color.clear; mat.AmbientColor = Color.white * 0.5f; mat.Shiness = 5; mat.CastSelfShadow = true; mat.DrawGroundShadow = true; mat.DrawSelfShadow = true; mat.EdgeColor = Color.black; mat.EdgeSize = 0.4f; var tex = model.TextureList.Find(t => t.TexturePath.Contains($"/{material.mainTexture.name}.png")); mat.Texture = (tex ?? model.TextureList[0]); mat.MetaInfo = ""; part.BaseShift = baseShift + mesh.GetSubMesh(i).indexStart; part.TriangleIndexNum = mesh.GetSubMesh(i).indexCount; parts.Add(part); } baseShift += mesh.triangles.Length; } return parts.ToArray(); } private static Vertex[] ReadVerticesAndTriangles(List renderers, List bones, ref List triangleList, Transform root) { List verticesList = new List(); int vertexOffset = 0; foreach (Renderer renderer in renderers) { if (renderer is MeshRenderer mr) { var meshfilter = mr.GetComponent(); //Cache this data to avoid copying arrays. var mesh = meshfilter.mesh; var vertices = mesh.vertices; var normals = mesh.normals; var uv = mesh.uv; var uv1 = mesh.uv2; var uv2 = mesh.uv3; var colors = mesh.colors; var triangles = mesh.triangles; for (int i = 0; i < vertices.Length; i++) { Vertex vertex = new Vertex(); vertex.Coordinate = renderer.transform.TransformPoint(vertices[i]); vertex.Normal = normals[i]; vertex.UvCoordinate = uv[i]; vertex.ExtraUvCoordinate = new Vector4[3] { uv1.Length > 0 ? uv1[i] : Vector2.zero, uv2.Length > 0 ? uv2[i] : Vector2.zero, colors.Length > 0 ? colors[i] : Color.clear }; vertex.SkinningOperator = new SkinningOperator() { Type = SkinningType.SkinningBdef1 }; vertex.SkinningOperator.Param = new Bdef1() { BoneId = bones.IndexOf(renderer.transform) }; vertex.EdgeScale = 1; verticesList.Add(vertex); } foreach (var triangle in mesh.triangles) { triangleList.Add(triangle + vertexOffset); } vertexOffset += vertices.Length; } else if (renderer is SkinnedMeshRenderer smr) { var mesh = smr.sharedMesh; var vertices = mesh.vertices; var normals = mesh.normals; var uv = mesh.uv; var uv1 = mesh.uv2; var uv2 = mesh.uv3; var colors = mesh.colors; var boneCounts = mesh.GetBonesPerVertex(); var skinbone = smr.bones; var skinbones = smr.bones; var weights = mesh.boneWeights; var bakemesh = new Mesh(); smr.BakeMesh(bakemesh,true); for (int i = 0; i < vertices.Length; i++) { Vertex vertex = new Vertex(); vertex.Coordinate = root.InverseTransformPoint(smr.transform.TransformPoint(bakemesh.vertices[i])); vertex.Normal = normals[i]; vertex.UvCoordinate = new Vector2(uv[i].x, 1 - uv[i].y); vertex.ExtraUvCoordinate = new Vector4[3] { uv1.Length > 0 ? new Vector2(uv1[i].x, 1 - uv1[i].y) : Vector2.zero, uv2.Length > 0 ? new Vector2(uv2[i].x, 1 - uv2[i].y) : Vector2.zero, colors.Length > 0 ? colors[i] : Color.clear }; var boneWeight = weights[i]; var boneCount = boneCounts[i]; switch (boneCount) { case 0: vertex.SkinningOperator = new SkinningOperator() { Type = SkinningType.SkinningBdef1 }; vertex.SkinningOperator.Param = new Bdef1() { BoneId = GetBoneIndex(bones, renderer.transform) }; break; default: case 1: vertex.SkinningOperator = new SkinningOperator() { Type = SkinningType.SkinningBdef1 }; vertex.SkinningOperator.Param = new Bdef1() { BoneId = GetBoneIndex(bones, skinbone[boneWeight.boneIndex0]) }; break; case 2: vertex.SkinningOperator = new SkinningOperator() { Type = SkinningType.SkinningBdef2 }; vertex.SkinningOperator.Param = new Bdef2() { BoneId = new int[]{ GetBoneIndex(bones, skinbone[boneWeight.boneIndex0]), GetBoneIndex(bones, skinbone[boneWeight.boneIndex1]), }, BoneWeight = boneWeight.weight0 }; break; case 3: case 4: vertex.SkinningOperator = new SkinningOperator() { Type = SkinningType.SkinningBdef4 }; vertex.SkinningOperator.Param = new Bdef4() { BoneId = new int[]{ GetBoneIndex(bones, skinbone[boneWeight.boneIndex0]), GetBoneIndex(bones, skinbone[boneWeight.boneIndex1]), GetBoneIndex(bones, skinbone[boneWeight.boneIndex2]), GetBoneIndex(bones, skinbone[boneWeight.boneIndex3]), }, BoneWeight = new float[] { boneWeight.weight0, boneWeight.weight1, boneWeight.weight2, boneWeight.weight3 } }; break; } vertex.EdgeScale = 1; verticesList.Add(vertex); } foreach (var triangle in mesh.triangles) { triangleList.Add(triangle + vertexOffset); } vertexOffset += vertices.Length; } } return verticesList.ToArray(); } private static int GetBoneIndex(List bones, Transform bone) { return bones.Contains(bone) ? bones.IndexOf(bone) : 0; } }