fix node index mapping in skinning
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user