using System.Collections.Generic; using System.Linq; using LibMMD.Model; using LibMMD.Reader; using UnityEngine; namespace LibMMD.Unity3D { // Pure-C# stage of model loading: parses the PMX/PMD file and builds the raw mesh arrays // (vertices/normals/uv/boneWeights/triangles) as plain arrays/lists. Never touches a // UnityEngine.Object (Mesh/GameObject/Texture/Material) - only the plain value structs // (Vector3/Vector2/BoneWeight) - so this is safe to run on a background thread via Task.Run // (see MmdGameObject.LoadModelAsync). MmdGameObject turns the Result into actual Mesh // object(s) back on the main thread. internal static class ModelLoadPreparer { // Unity's 16-bit index buffer limit for a single Mesh - above this the model has to be // split across multiple part Meshes instead of one combined Mesh (see Result.UsesParts). private const int MaxSingleMeshVertexCount = 65535; public sealed class PartMeshData { public Vector3[] Vertices; public Vector3[] Normals; public Vector2[] Uv; public BoneWeight[] BoneWeights; public int[] Triangles; public List VertexIndexes; } public sealed class Result { public MmdModel Model; public bool UsesParts; // Single-mesh path (UsesParts == false) public Vector3[] Vertices; public Vector3[] Normals; public Vector2[] Uv; public BoneWeight[] BoneWeights; public List[] SubMeshTriangles; // Part-mesh path (UsesParts == true) public List Parts; } public static Result Prepare(string filePath, ModelReadConfig config) { var model = ModelReader.LoadMmdModel(filePath, config); var result = new Result { Model = model }; var vertCount = model.Vertices.Length; var triangles = model.TriangleIndexes; var triangleCount = triangles.Length / 3; if (vertCount <= MaxSingleMeshVertexCount) { result.UsesParts = false; result.Vertices = model.Vertices.Select(x => x.Coordinate).ToArray(); result.Normals = model.Vertices.Select(x => x.Normal).ToArray(); var uv = ExtratUv(model.Vertices); var uvVec = new Vector2[vertCount]; Utils.MmdUvToUnityUv(uv, uvVec); result.Uv = uvVec; result.BoneWeights = model.Vertices.Select(x => MmdGameObject.ConvertBoneWeight(x.SkinningOperator)).ToArray(); result.SubMeshTriangles = BuildSubMeshTriangles(model, triangleCount, triangles); } else { result.UsesParts = true; var uv = ExtratUv(model.Vertices); result.Parts = BuildPartMeshes(model, triangleCount, triangles, uv); } return result; } private static float[] ExtratUv(Vertex[] verts) { var ret = new float[verts.Length * 2]; for (var i = 0; i < verts.Length; ++i) { ret[2 * i] = verts[i].UvCoordinate.x; ret[2 * i + 1] = verts[i].UvCoordinate.y; } return ret; } private static List[] BuildSubMeshTriangles(MmdModel model, int triangleCount, int[] triangles) { var result = new List[model.Parts.Length]; for (var i = 0; i < model.Parts.Length; i++) { var part = model.Parts[i]; if ((part.BaseShift + part.TriangleIndexNum) / 3 > triangleCount) { Debug.LogError("too many triangles in model part " + i); result[i] = new List(); continue; } result[i] = Utils.ArrayToList(triangles, part.BaseShift, part.TriangleIndexNum); } return result; } private static List BuildPartMeshes(MmdModel model, int triangleCount, int[] triangles, float[] uv) { var result = new List(); for (var i = 0; i < model.Parts.Length; i++) { var part = model.Parts[i]; if ((part.BaseShift + part.TriangleIndexNum) / 3 > triangleCount) { Debug.LogError("too many triangles in model part " + i); continue; } var vertexIndexes = new List(); var vertMap = GetVertMap(triangles, part.BaseShift, part.TriangleIndexNum, vertexIndexes); var partTriangles = GetPartTriangles(triangles, part.BaseShift, part.TriangleIndexNum, vertMap); var vertices = model.Vertices; result.Add(new PartMeshData { Vertices = PickupVecs3(vertexIndexes, idx => vertices[idx].Coordinate), Normals = PickupVecs3(vertexIndexes, idx => vertices[idx].Normal), BoneWeights = PickupBoneWeight(vertices, vertexIndexes), Uv = PickupUv(uv, vertexIndexes), Triangles = partTriangles.ToArray(), VertexIndexes = vertexIndexes, }); } return result; } private static Dictionary GetVertMap(int[] triangles, int fromIndex, int size, List indexList) { var ret = new Dictionary(); indexList.Clear(); for (var i = fromIndex; i < fromIndex + size; i++) { var src = triangles[i]; if (ret.ContainsKey(src)) continue; ret.Add(src, indexList.Count); indexList.Add(src); } return ret; } private static List GetPartTriangles(int[] triangles, int fromIndex, int size, Dictionary vertMap) { var ret = new List(size); for (var i = fromIndex; i < fromIndex + size; i++) { ret.Add(vertMap[triangles[i]]); } return ret; } private static BoneWeight[] PickupBoneWeight(Vertex[] vertices, List indexes) { var ret = new BoneWeight[indexes.Count]; for (var i = 0; i < indexes.Count; i++) { ret[i] = MmdGameObject.ConvertBoneWeight(vertices[indexes[i]].SkinningOperator); } return ret; } private static Vector3[] PickupVecs3(List indexes, System.Func select) { var ret = new Vector3[indexes.Count]; for (var i = 0; i < indexes.Count; i++) { ret[i] = select(indexes[i]); } return ret; } private static Vector2[] PickupUv(float[] uv, List indexes) { var ret = new Vector2[indexes.Count]; for (var i = 0; i < indexes.Count; i++) { var iCoord = indexes[i] * 2; ret[i] = new Vector2(uv[iCoord], 1.0f - uv[iCoord + 1]); } return ret; } } }