fix node index mapping in skinning

This commit is contained in:
Boris Nikolaev
2026-02-14 12:04:52 +03:00
parent 7c669b6635
commit 92b615f077
5 changed files with 120 additions and 87 deletions
+20 -19
View File
@@ -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<int, Node> _nodeMap = new();
private readonly List<Node> _boneNodes = new();
private readonly Dictionary<string, MaterialBuilder> _materialCache = new();
private Dictionary<string, ImageBuilder> _textureBuilders = new Dictionary<string, ImageBuilder>();
private Dictionary<string, ImageBuilder> _textureBuilders = new();
private const float FRAMERATE = 16f;
@@ -32,7 +33,7 @@ public class MDSConverter
_gltfModel = ModelRoot.CreateModel();
}
public void Convert(MDSScene scene, List<TM2Format.Texture> images, string name)
public void Convert(MDSScene scene, List<Texture> images, string name)
{
var gltfScene = _gltfModel.UseScene("DefaultScene");
@@ -78,6 +79,17 @@ public class MDSConverter
}
}
var nodesMapping = new Dictionary<int, int>();
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<MDSMesh>().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<int, int> 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(
+45 -20
View File
@@ -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<string, MaterialBuilder> materialCache)
Dictionary<string, MaterialBuilder> materialCache, Dictionary<int, int> 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<VertexPositionNormal, VertexTexture1, VertexJoints4>(
_root, mdsMesh, materials, materialCache);
_root, mdsMesh, materials, materialCache, nodesMap);
}
else
{
return CreateMeshWithAllAttributes<VertexPositionNormal, VertexEmpty, VertexJoints4>(
_root, mdsMesh, materials, materialCache);
_root, mdsMesh, materials, materialCache, nodesMap);
}
}
else
@@ -35,12 +39,12 @@ namespace dq8chr2glb.Converter
if (hasUvs)
{
return CreateMeshWithAllAttributes<VertexPositionNormal, VertexTexture1, VertexEmpty>(
_root, mdsMesh, materials, materialCache);
_root, mdsMesh, materials, materialCache, nodesMap);
}
else
{
return CreateMeshWithAllAttributes<VertexPositionNormal, VertexEmpty, VertexEmpty>(
_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<string, MaterialBuilder> materialCache)
Dictionary<string, MaterialBuilder> materialCache,
Dictionary<int, int> nodesMap)
where TvG : struct, IVertexGeometry
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
@@ -71,10 +75,10 @@ namespace dq8chr2glb.Converter
var normals = new List<Vector3>();
var texCoords = new List<Vector2>();
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<TvG, TvM, TvS>(
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<TvG, TvM, TvS>(
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);
}
}
+4 -4
View File
@@ -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;
+26 -44
View File
@@ -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<short[], float[]>(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<byte[], float[]>(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<byte[], float[]>(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<ushort[], float[]>(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<ushort[], float[]>(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<short[], int[]>(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<short[], int[]>(data, bOffset, vCount, 4,
s => Array.ConvertAll(s, x => (int)x));
var submeshes = new List<SubMesh>();
var triangleGroupOffset = offset + mdtHeader.vertexAttrSize;
@@ -131,8 +107,16 @@ public class Reader
for (var submeshIndex = 0; submeshIndex < mdtHeader.triangleGroupCount; submeshIndex++)
{
var triangleGroupHeader = Utils.ReadStruct<TriangleGroupHeader>(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}");
}
}
+25
View File
@@ -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}");
}
}