diff --git a/dq8chr2glb/Converter/MDSConverter.cs b/dq8chr2glb/Converter/MDSConverter.cs index 4d4f62f..75ed4d6 100644 --- a/dq8chr2glb/Converter/MDSConverter.cs +++ b/dq8chr2glb/Converter/MDSConverter.cs @@ -6,6 +6,7 @@ using System.Numerics; using dq8chr2glb.Core.InfoCfg; using dq8chr2glb.Core.MDSFormat; using dq8chr2glb.Core.MOTFormat; +using dq8chr2glb.Logger; using SharpGLTF.Materials; using SharpGLTF.Memory; using SharpGLTF.Schema2; @@ -22,7 +23,7 @@ public class MDSConverter private readonly Dictionary _nodeMap = new(); private readonly List _boneNodes = new(); private readonly Dictionary _materialCache = new(); - private Dictionary _textureBuilders = new Dictionary(); + private Dictionary _textureBuilders = new(); private const float FRAMERATE = 16f; @@ -32,7 +33,7 @@ public class MDSConverter _gltfModel = ModelRoot.CreateModel(); } - public void Convert(MDSScene scene, List images, string name) + public void Convert(MDSScene scene, List images, string name) { var gltfScene = _gltfModel.UseScene("DefaultScene"); @@ -78,6 +79,17 @@ public class MDSConverter } } + var nodesMapping = new Dictionary(); + for (int i = 0; i < _boneNodes.Count; i++) + { + var boneNode = _boneNodes[i]; + var mdsNode = Array.Find(scene.nodes, n => n.name == boneNode.Name); + if (mdsNode != null) + { + nodesMapping[mdsNode.index] = i; + } + } + CreateMaterials(scene.materials, images); var meshes = scene.nodes.Where(n => n is MDSMesh).Cast().ToArray(); @@ -85,7 +97,7 @@ public class MDSConverter { if (mdsMesh?.vertices == null || mdsMesh.triangles == null) continue; - var mesh = CreateGltfMesh(mdsMesh, scene.materials); + var mesh = CreateGltfMesh(mdsMesh, scene.materials, nodesMapping); if (_nodeMap.TryGetValue(mdsMesh.index, out var meshNode)) { meshNode.WithMesh(mesh); @@ -178,9 +190,9 @@ public class MDSConverter } } - private Mesh CreateGltfMesh(MDSMesh mdsMesh, MDSMaterial[] materials) + private Mesh CreateGltfMesh(MDSMesh mdsMesh, MDSMaterial[] materials, Dictionary nodesMapping) { - return UniversalMeshBuilder.CreateMesh(_gltfModel, mdsMesh, materials, _materialCache); + return UniversalMeshBuilder.CreateMesh(_gltfModel, mdsMesh, materials, _materialCache, nodesMapping); } private void SetupSkeleton(MDSNode[] nodes, string name) @@ -196,14 +208,9 @@ public class MDSConverter } } - if (rootBone == null && _boneNodes.Count > 0) - { - rootBone = _boneNodes[0]; - } - if (_boneNodes.Count == 0) { - Console.WriteLine("Skip mesh with 0 bones"); + Log.Line("Skip mesh with 0 bones"); return; } @@ -222,8 +229,7 @@ public class MDSConverter var bindposeMatrix = ToNumericsMatrix4x4(b.bindpose); if (Matrix4x4.Invert(bindposeMatrix, out var bindposeInverse)) { - var multiplied = Matrix4x4.Multiply(bindposeInverse, Matrix4x4.Identity); - bindMatrix = SanitizeBindMatrix(multiplied); + bindMatrix = Matrix4x4.Multiply(bindposeInverse, Matrix4x4.Identity); } else { @@ -240,7 +246,7 @@ public class MDSConverter { if (node.Mesh != null) { - if (node.Mesh.Primitives.All(i => i.VertexAccessors.ContainsKey("WEIGHTS_0"))) + if (node.Mesh.Primitives.Any(i => i.VertexAccessors.ContainsKey("WEIGHTS_0"))) { if (Quaternion.Dot(node.LocalTransform.GetDecomposed().Rotation, Quaternion.Identity) >= 0.999f) { @@ -253,11 +259,6 @@ public class MDSConverter } } - private Matrix4x4 SanitizeBindMatrix(Matrix4x4 matrix) - { - return matrix; - } - private Matrix4x4 ToNumericsMatrix4x4(MDSMatrix m) { return new Matrix4x4( diff --git a/dq8chr2glb/Converter/UniversalMeshBuilder.cs b/dq8chr2glb/Converter/UniversalMeshBuilder.cs index 00ea047..99802d0 100644 --- a/dq8chr2glb/Converter/UniversalMeshBuilder.cs +++ b/dq8chr2glb/Converter/UniversalMeshBuilder.cs @@ -1,20 +1,24 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using dq8chr2glb.Core.MDSFormat; +using dq8chr2glb.Logger; using SharpGLTF.Geometry; using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.Materials; using SharpGLTF.Schema2; +using Node = SharpGLTF.Schema2.Node; namespace dq8chr2glb.Converter { public static class UniversalMeshBuilder { public static Mesh CreateMesh(ModelRoot _root, MDSMesh mdsMesh, MDSMaterial[] materials, - Dictionary materialCache) + Dictionary materialCache, Dictionary nodesMap) { - var (hasUvs, hasSkinning) = AnalyzeMeshAttributes(mdsMesh); + var hasUvs = (mdsMesh.features & MeshFeatures.UVs) != 0; + var hasSkinning = (mdsMesh.features & MeshFeatures.Weights) != 0; // Выбираем подходящий тип вершины на основе атрибутов if (hasSkinning) @@ -22,12 +26,12 @@ namespace dq8chr2glb.Converter if (hasUvs) { return CreateMeshWithAllAttributes( - _root, mdsMesh, materials, materialCache); + _root, mdsMesh, materials, materialCache, nodesMap); } else { return CreateMeshWithAllAttributes( - _root, mdsMesh, materials, materialCache); + _root, mdsMesh, materials, materialCache, nodesMap); } } else @@ -35,12 +39,12 @@ namespace dq8chr2glb.Converter if (hasUvs) { return CreateMeshWithAllAttributes( - _root, mdsMesh, materials, materialCache); + _root, mdsMesh, materials, materialCache, nodesMap); } else { return CreateMeshWithAllAttributes( - _root, mdsMesh, materials, materialCache); + _root, mdsMesh, materials, materialCache, nodesMap); } } } @@ -50,8 +54,7 @@ namespace dq8chr2glb.Converter var hasUvs = mdsMesh.uv != null && mdsMesh.uv.Length > 0 && Array.Exists(mdsMesh.uv, uv => uv != null && uv.Length >= 2); var hasSkinning = mdsMesh.weights != null && mdsMesh.bones != null && - mdsMesh.bones.Length > 0 && - Array.Exists(mdsMesh.bones, b => b != null); + mdsMesh.bones.Length > 0; // && Array.Exists(mdsMesh.bones, b => b != null) return (hasUvs, hasSkinning); } @@ -60,7 +63,8 @@ namespace dq8chr2glb.Converter ModelRoot root, MDSMesh mdsMesh, MDSMaterial[] materials, - Dictionary materialCache) + Dictionary materialCache, + Dictionary nodesMap) where TvG : struct, IVertexGeometry where TvM : struct, IVertexMaterial where TvS : struct, IVertexSkinning @@ -71,10 +75,10 @@ namespace dq8chr2glb.Converter var normals = new List(); var texCoords = new List(); var skinningData = new List<(int, float)[]>(); - var hasUvs = mdsMesh.uv != null && mdsMesh.uv.Length > 0; - var hasSkinning = mdsMesh.weights != null && mdsMesh.bones != null; + var hasUvs = (mdsMesh.features & MeshFeatures.UVs) != 0; + var hasSkinning = (mdsMesh.features & MeshFeatures.Weights) != 0; - for (int i = 0; i < mdsMesh.vertices.Length; i++) + for (var i = 0; i < mdsMesh.vertices.Length; i++) { positions.Add(new Vector3( mdsMesh.vertices[i][0], @@ -92,15 +96,25 @@ namespace dq8chr2glb.Converter texCoords.Add(Vector2.Zero); } - if (hasSkinning && i < mdsMesh.bones.Length && mdsMesh.bones[i] != null) + if (hasSkinning && i < mdsMesh.bones.Length) { var boneIndices = mdsMesh.bones[i]; var boneWeights = mdsMesh.weights[i]; - var bindings = new (int JointIndex, float Weight)[Math.Min(8, boneIndices.Length)]; + var bindings = new (int JointIndex, float Weight)[Math.Min(4, boneIndices.Length)]; for (var b = 0; b < bindings.Length; b++) { - bindings[b] = (boneIndices[b], boneWeights[b]); + var mdsBoneIndex = boneIndices[b]; + if (nodesMap.ContainsKey(mdsBoneIndex)) + { + var gltfBoneIndex = nodesMap[mdsBoneIndex]; + bindings[b] = (Math.Max(gltfBoneIndex, 0), boneWeights[b]); + } + else + { + // Обработка случая, когда MDS-индекс не найден в маппинге + bindings[b] = (0, boneWeights[b]); // или другое значение по умолчанию + } } skinningData.Add(bindings); @@ -134,6 +148,15 @@ namespace dq8chr2glb.Converter indices.Add(mdsMesh.triangles[i + 2]); } + var maxBoneIndex = 0; + foreach (var bones in mdsMesh.bones) + { + foreach (var boneID in bones) + { + maxBoneIndex = Math.Max(boneID, maxBoneIndex); + } + } + for (var i = 0; i < indices.Count; i += 3) { var idxA = indices[i]; @@ -144,19 +167,19 @@ namespace dq8chr2glb.Converter positions[idxA], normals[idxA], texCoords.Count > idxA ? texCoords[idxA] : Vector2.Zero, - skinningData.Count > idxA ? skinningData[idxA] : null); + skinningData.Count != 0 ? skinningData[idxA] : null); var vertexB = CreateVertex( positions[idxB], normals[idxB], texCoords.Count > idxB ? texCoords[idxB] : Vector2.Zero, - skinningData.Count > idxB ? skinningData[idxB] : null); + skinningData.Count != 0 ? skinningData[idxB] : null); var vertexC = CreateVertex( positions[idxC], normals[idxC], texCoords.Count > idxC ? texCoords[idxC] : Vector2.Zero, - skinningData.Count > idxC ? skinningData[idxC] : null); + skinningData.Count != 0 ? skinningData[idxC] : null); primitive.AddTriangle(vertexA, vertexB, vertexC); } @@ -185,8 +208,9 @@ namespace dq8chr2glb.Converter { material.SetTexCoord(0, texCoord); } - catch + catch (Exception e) { + Log.Error(e); } } @@ -197,8 +221,9 @@ namespace dq8chr2glb.Converter { skinning.SetBindings(skinningData); } - catch + catch (Exception e) { + Log.Error(e); } } diff --git a/dq8chr2glb/Core/MDSFormat/Headers.cs b/dq8chr2glb/Core/MDSFormat/Headers.cs index 4b98840..a38b177 100644 --- a/dq8chr2glb/Core/MDSFormat/Headers.cs +++ b/dq8chr2glb/Core/MDSFormat/Headers.cs @@ -31,10 +31,10 @@ public struct MDTHeader [FieldOffset(20)] public int value2; // always 4104? [FieldOffset(24)] public int value3; // always 4112? - [FieldOffset(32)] public float scaleFactorX; - [FieldOffset(36)] public float scaleFactorY; + [FieldOffset(32)] public float scaleX; + [FieldOffset(36)] public float scaleY; - [FieldOffset(40)] public float scaleFactorZ; + [FieldOffset(40)] public float scaleZ; // [FieldOffset(64)] public Float3 someScale; // always 1.079? [FieldOffset(80)] public float uvScaleX; @@ -61,7 +61,7 @@ public struct MDTHeader [FieldOffset(192)] public AABB bounds; } -[StructLayout(LayoutKind.Explicit, Size = 40)] +[StructLayout(LayoutKind.Explicit, Size = 48)] public struct AABB { [FieldOffset(00)] public float minX; diff --git a/dq8chr2glb/Core/MDSFormat/Reader.cs b/dq8chr2glb/Core/MDSFormat/Reader.cs index 7c882be..05a1186 100644 --- a/dq8chr2glb/Core/MDSFormat/Reader.cs +++ b/dq8chr2glb/Core/MDSFormat/Reader.cs @@ -2,11 +2,14 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using dq8chr2glb.Logger; namespace dq8chr2glb.Core.MDSFormat; public class Reader { + private const float gScale = 0.00003f; // Global Scale Constant + public static MDSScene Read(byte[] data, string filename) { var offset = 0; @@ -43,26 +46,17 @@ public class Reader mesh.features = features; mesh.bounds = mdtHeader.bounds; - // Console.WriteLine($"Features {features}"); - - var gScale = 0.00003f; var scale = new float[3] - { - mdtHeader.scaleFactorX * gScale, mdtHeader.scaleFactorY * gScale, mdtHeader.scaleFactorZ * gScale - }; + { mdtHeader.scaleX * gScale, mdtHeader.scaleY * gScale, mdtHeader.scaleZ * gScale }; var uvScale = new float[2] { mdtHeader.uvScaleX, mdtHeader.uvScaleY }; // read vertices array var vOffset = offset + mdtHeader.toVertexCount + mdtHeader.bytesToFirstVertex; var vCount = mdtHeader.vertsCount; var vertices = Utils.ReadArray(data, vOffset, vCount, 3, - s => + s => new[] { - return new[] - { - s[0] * scale[0], s[1] * scale[1], - s[2] * scale[2] - }; + s[0] * scale[0], s[1] * scale[1], s[2] * scale[2] }); // read colors array @@ -71,51 +65,33 @@ public class Reader { var cOffset = offset + mdtHeader.toVertexCount + mdtHeader.bytesToFirstColor; var cCount = mdtHeader.colorsCount; + var maxByte = (float)byte.MaxValue; colors = Utils.ReadArray(data, cOffset, cCount, 3, - s => + s => new[] { - return new[] - { s[0] / 256f, s[1] / 256f, s[2] / 256f }; + s[0] / maxByte, s[1] / maxByte, s[2] / maxByte }); } // read texcoords array var tOffset = offset + mdtHeader.toVertexCount + mdtHeader.uvOffset; var tCount = mdtHeader.uvCount; + var maxSByte = (float)sbyte.MaxValue; var uvs = Utils.ReadArray(data, tOffset, tCount, 2, - s => + s => new[] { - return new[] - { - s[0] / 127f * uvScale[0], s[1] / 127f * uvScale[1] - }; + s[0] / maxSByte * uvScale[0], s[1] / maxSByte * uvScale[1] }); // read weights array var wOffset = offset + mdtHeader.toVertexCount + mdtHeader.toWeightsOffset; - var weights = Utils.ReadArray(data, wOffset, vCount, 4, s => - { - var output = new float[4]; - for (var i = 0; i < 4; i++) - { - output[i] = s[i] / 32768f; - } - - return output; - }); + var weights = Utils.ReadArray(data, wOffset, vCount, 4, + s => Array.ConvertAll(s, x => (float)x / short.MaxValue)); // read bones per vertex array var bOffset = offset + mdtHeader.toVertexCount + mdtHeader.toBoneIndicesOffset; - var boneIndices = Utils.ReadArray(data, bOffset, vCount, 4, s => - { - var output = new int[4]; - for (var i = 0; i < 4; i++) - { - output[i] = s[i]; - } - - return output; - }); + var boneIndices = Utils.ReadArray(data, bOffset, vCount, 4, + s => Array.ConvertAll(s, x => (int)x)); var submeshes = new List(); var triangleGroupOffset = offset + mdtHeader.vertexAttrSize; @@ -131,8 +107,16 @@ public class Reader for (var submeshIndex = 0; submeshIndex < mdtHeader.triangleGroupCount; submeshIndex++) { var triangleGroupHeader = Utils.ReadStruct(data, ref triangleGroupOffset); - if (triangleGroupHeader.headerSize == 32) + if (triangleGroupHeader.headerSize == 32 && triangleGroupHeader.toNext == 0) { + // This is normal. Often, such blocks complete a sequence. + continue; + } + + if (triangleGroupHeader.headerSize == 32 && triangleGroupHeader.toNext != 0) + { + // This might be weird. + Log.Line($"Skip 32-bit triangle group header on {triangleGroupOffset}"); continue; } @@ -221,7 +205,6 @@ public class Reader if (type == NodeType.Bone) { var boneNode = new MDSBone(); - mdsNode = new MDSNode(); mdsNode = boneNode; } else @@ -317,8 +300,7 @@ public class Reader } catch (Exception e) { - Console.WriteLine($"Bad index [{triangleVertexIndex}] in {content} array. Array start index: {address}, elements: {attribute.Length}"); - break; + Log.Line($"Bad index [{triangleVertexIndex}] in {content} array. Array start index: {address}, elements: {attribute.Length}"); } } diff --git a/dq8chr2glb/Logger/Log.cs b/dq8chr2glb/Logger/Log.cs new file mode 100644 index 0000000..43ae183 --- /dev/null +++ b/dq8chr2glb/Logger/Log.cs @@ -0,0 +1,25 @@ +using System; + +namespace dq8chr2glb.Logger; + +public enum LogLevel +{ + Debug, + Info, + Warning, + Error, + None +} + +public static class Log +{ + public static void Line(object data, LogLevel level = LogLevel.Debug) + { + Console.WriteLine(data.ToString()); + } + + public static void Error(Exception e, LogLevel level = LogLevel.Error) + { + Console.WriteLine($"{e.Message}\n From:\n{e.Source}\nStacktrace:\n{e.StackTrace}"); + } +}