424 lines
16 KiB
C#
424 lines
16 KiB
C#
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.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<Transform> bones = new List<Transform>(rootBone.GetComponentsInChildren<Transform>());
|
|
bones.RemoveAll(o => o.name.Contains("Col_"));
|
|
|
|
//Read vertices And triangles
|
|
List<Renderer> renderers = new List<Renderer>(container.GetComponentsInChildren<Renderer>());
|
|
List<int> triangles = new List<int>();
|
|
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;
|
|
foreach (SkinnedMeshRenderer s in container.GetComponentsInChildren<SkinnedMeshRenderer>())
|
|
{
|
|
if (s.name.Contains("Face")) faceMesh = s;
|
|
else if (s.name.Contains("Mayu")) eyebrowMesh = s;
|
|
}
|
|
|
|
var facial = container.FaceDrivenKeyTarget;
|
|
Action<SkinnedMeshRenderer, List<FacialMorph>, Mesh> addBlendShapePart = delegate (SkinnedMeshRenderer skin, List<FacialMorph> 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);
|
|
}
|
|
|
|
facial.ClearAllWeights();
|
|
facial.ChangeMorph();
|
|
}
|
|
|
|
private static Morph[] ReadMorph(List<Renderer> renderers)
|
|
{
|
|
List<Morph> morphs = new List<Morph>();
|
|
int vertexOffset = 0;
|
|
foreach (Renderer renderer in renderers)
|
|
{
|
|
Mesh mesh;
|
|
if (renderer is MeshRenderer mr)
|
|
{
|
|
var meshfilter = mr.GetComponent<MeshFilter>();
|
|
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<Transform> bonelist)
|
|
{
|
|
List<Bone> pmxbones = new List<Bone>();
|
|
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<Renderer> renderers, RawMMDModel model)
|
|
{
|
|
List<Part> parts = new List<Part>();
|
|
int baseShift = 0;
|
|
|
|
foreach (Renderer renderer in renderers)
|
|
{
|
|
Mesh mesh;
|
|
if (renderer is MeshRenderer mr)
|
|
{
|
|
var meshfilter = mr.GetComponent<MeshFilter>();
|
|
mesh = meshfilter.mesh;
|
|
}
|
|
else
|
|
{
|
|
mesh = ((SkinnedMeshRenderer)renderer).sharedMesh;
|
|
}
|
|
|
|
var materials = new List<Material>(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<Renderer> renderers, List<Transform> bones, ref List<int> triangleList, Transform root)
|
|
{
|
|
List<Vertex> verticesList = new List<Vertex>();
|
|
int vertexOffset = 0;
|
|
|
|
foreach (Renderer renderer in renderers)
|
|
{
|
|
if (renderer is MeshRenderer mr)
|
|
{
|
|
var meshfilter = mr.GetComponent<MeshFilter>();
|
|
//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<Transform> bones, Transform bone)
|
|
{
|
|
return bones.Contains(bone) ? bones.IndexOf(bone) : 0;
|
|
}
|
|
}
|