11 Commits

Author SHA1 Message Date
BorisNikolaev 5b8a22b0dc Update README.md 2026-06-18 23:09:06 +03:00
Boris Nikolaev 6c169a3a18 chg: clean-up preview 2026-06-08 01:01:37 +03:00
Boris Nikolaev 5ad44e128b chg: preview path 2026-06-08 00:48:39 +03:00
Boris Nikolaev 7eb47677ef add: sandbox attr 2026-06-08 00:30:46 +03:00
Boris Nikolaev 91029ccbe7 chg: second preview 2026-06-08 00:17:16 +03:00
Boris Nikolaev 8301a1446a chg: preview path 2026-06-08 00:12:52 +03:00
Boris Nikolaev 3418bea265 chg: preview path 2026-06-08 00:02:32 +03:00
Boris Nikolaev d36f8355f5 add: preview 2026-06-08 00:01:40 +03:00
Boris Nikolaev 46ddb283b2 add: 3d preview 2026-06-07 10:50:22 +03:00
Boris Nikolaev 291babd1c0 fix codestyle 2026-05-10 17:05:25 +03:00
Boris Nikolaev 87b48441f8 fix error messages 2026-02-16 12:19:53 +03:00
23 changed files with 604 additions and 515 deletions
+10
View File
@@ -1,6 +1,16 @@
# DQ8 chr to glTF Converter
![](preview.jpg)
<iframe
src="https://boris-code.ru/files/ap002-preview/?src=https://s3.boris-code.ru/public/projects/dq8/chr2glb/screenshots/model.glb&clip=%E8%B5%B0%E3%82%8A&view=unlit"
width="100%"
height="500"
style="border: 1px solid #30363d; border-radius: 8px;"
allow="autoplay"
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
title="3D Preview">
</iframe>
Convert Dragon Quest VIII (PS2) character models to modern 3D format.
A command-line tool for converting .CHR character model files from Dragon Quest VIII: Journey of the Cursed King (PlayStation 2) into glTF/GLB 3D formats for use in modern 3D applications, game engines, and viewers.
+62
View File
@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using dq8chr2glb.Converter;
using dq8chr2glb.Converter.GLTF;
using dq8chr2glb.Core.InfoCfg;
using dq8chr2glb.Core.MOTFormat;
using dq8chr2glb.Logger;
using SharpGLTF.Schema2;
namespace dq8chr2glb;
public class AppendAnimations
{
public void Append(string glbPath, string extractedPath)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var modelName = Path.GetFileNameWithoutExtension(glbPath);
var glbDir = Path.GetDirectoryName(glbPath);
var model = ModelRoot.Load(glbPath);
var mdsConverter = new MDSConverter(modelName, model);
var ctx = new Context();
Context.Current.MdsConverters = new List<MDSConverter>();
Context.Current.MdsConverters.Add(mdsConverter);
foreach (var dataFile in Directory.GetFiles(extractedPath))
{
if (dataFile.ToLower().EndsWith(".cfg"))
{
var directory = Path.GetDirectoryName(dataFile);
ReadConfig(dataFile);
var motionFile = Path.Combine(directory, Context.Current.InfoCfg.motion);
ProcessMOTFile(motionFile);
}
}
mdsConverter.Save(@"C:\Users\Boris\Desktop\yangus");
}
private void ReadConfig(string path)
{
Log.Line(path);
var bytes = File.ReadAllBytes(path);
var data = Encoding.GetEncoding("shift_jis").GetString(bytes).TrimEnd('\0');
var configFile = new ConfigFile(data);
var info = configFile.ReadConfig();
Context.Current.InfoCfg = info;
}
private void ProcessMOTFile(string path)
{
Log.Line(path);
var bytes = File.ReadAllBytes(path);
var motImporter = new Importer();
var animation = motImporter.Import(bytes);
var converter = Context.Current.MdsConverters[0];
converter.AddAnimation(animation, Context.Current.InfoCfg);
}
}
+59 -59
View File
@@ -16,29 +16,29 @@ namespace dq8chr2glb;
public class ChrFile
{
public bool extract;
public bool convert;
public bool textFormat;
public bool Extract;
public bool Convert;
public bool TextFormat;
public void Process(string inputPath, string outputPath)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var ctx = new Context();
ctx.inputPath = inputPath;
ctx.modelName = Path.GetFileNameWithoutExtension(ctx.inputPath);
ctx.outputPath = Path.Combine(outputPath, ctx.modelName);
ctx.InputPath = inputPath;
ctx.ModelName = Path.GetFileNameWithoutExtension(ctx.InputPath);
ctx.OutputPath = Path.Combine(outputPath, ctx.ModelName);
var chrData = File.ReadAllBytes(ctx.inputPath);
var container = ChrContainer.FromBytes(chrData);
var chrData = File.ReadAllBytes(ctx.InputPath);
var container = Container.ChrFile.FromBytes(chrData);
EnsurePath(ctx.outputPath);
EnsurePath(ctx.OutputPath);
foreach (var file in container)
{
PrintTask(file);
switch (file.extension)
switch (file.Extension)
{
case FileExtension.CFG:
ProcessConfig(file);
@@ -61,39 +61,39 @@ public class ChrFile
}
}
if (convert)
if (Convert)
{
foreach (var converter in Context.current.mdsConverters)
foreach (var converter in Context.Current.MdsConverters)
{
converter.Save(ctx.outputPath, textFormat);
converter.Save(ctx.OutputPath, TextFormat);
}
}
foreach (var err in Context.current.errors)
foreach (var err in Context.Current.Errors)
{
Log.Line($"{err.name}, {err.text}", LogLevel.Error);
if (err.exception != null)
Log.Line($"{err.Name}, {err.Text}", LogLevel.Error);
if (err.Exception != null)
{
Log.Error(err.exception);
Log.Error(err.Exception);
}
}
foreach (var err in Context.current.messages)
foreach (var err in Context.Current.Messages)
{
Log.Line($"{err.name}, {err.text}");
Log.Line($"{err.Name}, {err.Text}");
}
}
private void PrintTask(IncludedFile file)
{
var spaces = 30 - file.name.Length;
var spaces = 30 - file.Name.Length;
if (spaces <= 0)
{
spaces = 1;
}
var spacer = new string('.', spaces);
Log.Line($" Process: {file.name + spacer} {file.data.Length} bytes");
Log.Line($" Process: {file.Name + spacer} {file.Data.Length} bytes");
}
private void EnsurePath(string path)
@@ -106,65 +106,65 @@ public class ChrFile
private void ProcessMOTFile(IncludedFile file)
{
if (extract)
if (Extract)
{
ProcessRawFile(file);
}
if (convert)
if (Convert)
{
foreach (var converter in Context.current.mdsConverters)
foreach (var converter in Context.Current.MdsConverters)
{
var motImporter = new Importer();
var animation = motImporter.Import(file.data);
converter.CreateAnimation(animation, Context.current.infoCfg);
var animation = motImporter.Import(file.Data);
converter.AddAnimation(animation, Context.Current.InfoCfg);
}
}
}
private void ProcessMDSFile(IncludedFile file)
{
if (extract)
if (Extract)
{
ProcessRawFile(file);
}
var mdsScene = Reader.Read(file.data, file.name);
var mdsScene = Reader.Read(file.Data, file.Name);
if (mdsScene == null)
{
Context.current.errors.Add(new Error(file.name, "mdsScene is empty!"));
Context.Current.Errors.Add(new Error(file.Name, "mdsScene is empty!"));
return;
}
if (convert)
if (Convert)
{
var converter = new MDSConverter(file.name);
var rootName = Path.GetFileNameWithoutExtension(file.name);
converter.Convert(mdsScene, Context.current.textures, rootName);
Context.current.mdsConverters.Add(converter);
var converter = new MDSConverter(file.Name);
var rootName = Path.GetFileNameWithoutExtension(file.Name);
converter.Convert(mdsScene, Context.Current.Textures, rootName);
Context.Current.MdsConverters.Add(converter);
}
}
private void ProcessConfig(IncludedFile file)
{
if (extract)
if (Extract)
{
var isSecond = Context.current.infoCfg != null;
var isSecond = Context.Current.InfoCfg != null;
if (isSecond)
{
file.name = file.name.Replace(".cfg", "_2.cfg");
file.Name = file.Name.Replace(".cfg", "_2.cfg");
}
ProcessTextFile(file);
}
var data = Encoding.GetEncoding("shift_jis").GetString(file.data).TrimEnd('\0');
var data = Encoding.GetEncoding("shift_jis").GetString(file.Data).TrimEnd('\0');
var configFile = new ConfigFile(data);
var info = configFile.ReadConfig();
if (Context.current.infoCfg == null)
if (Context.Current.InfoCfg == null)
{
Context.current.infoCfg = info;
Context.Current.InfoCfg = info;
}
}
@@ -172,18 +172,18 @@ public class ChrFile
{
try
{
var root = Path.GetDirectoryName(file.name);
var fileName = Path.GetFileName(file.name);
var outputPath = Path.Combine(Context.current.outputPath, root);
var text = Encoding.GetEncoding("shift_jis").GetString(file.data).TrimEnd('\0');
var root = Path.GetDirectoryName(file.Name);
var fileName = Path.GetFileName(file.Name);
var outputPath = Path.Combine(Context.Current.OutputPath, root);
var text = Encoding.GetEncoding("shift_jis").GetString(file.Data).TrimEnd('\0');
if (string.IsNullOrEmpty(text))
{
Context.current.errors.Add(new Error(file.name, "Text data is empty!"));
Context.Current.Errors.Add(new Error(file.Name, "Text data is empty!"));
return;
}
if (extract)
if (Extract)
{
EnsurePath(outputPath);
File.WriteAllText(Path.Combine(outputPath, fileName), text);
@@ -191,7 +191,7 @@ public class ChrFile
}
catch (Exception e)
{
Context.current.errors.Add(new Error(file.name, "Error then process text file", e));
Context.Current.Errors.Add(new Error(file.Name, "Error while process text file", e));
}
}
@@ -199,35 +199,35 @@ public class ChrFile
{
try
{
var root = Path.GetDirectoryName(file.name);
var fileName = Path.GetFileName(file.name);
var outputPath = Path.Combine(Context.current.outputPath, root);
var root = Path.GetDirectoryName(file.Name);
var fileName = Path.GetFileName(file.Name);
var outputPath = Path.Combine(Context.Current.OutputPath, root);
if (extract)
if (Extract)
{
EnsurePath(outputPath);
File.WriteAllBytes(Path.Combine(outputPath, fileName), file.data);
File.WriteAllBytes(Path.Combine(outputPath, fileName), file.Data);
}
}
catch (Exception e)
{
Context.current.errors.Add(new Error(file.name, $"File extraction failed", e));
Context.Current.Errors.Add(new Error(file.Name, $"File extraction failed", e));
}
}
private void ProcessTextures(IncludedFile file)
{
var root = Path.GetDirectoryName(file.name);
var fileName = Path.GetFileNameWithoutExtension(file.name);
var outputPath = Path.Combine(Context.current.outputPath, root);
var root = Path.GetDirectoryName(file.Name);
var fileName = Path.GetFileNameWithoutExtension(file.Name);
var outputPath = Path.Combine(Context.Current.OutputPath, root);
var image = TM2Format.TM2Format.GetImage(file, fileName);
if (extract)
if (Extract && image != null && image.Data != null)
{
EnsurePath(outputPath);
image.data.SaveAsPng(Path.Combine(outputPath, fileName + ".png"));
image.Data.SaveAsPng(Path.Combine(outputPath, fileName + ".png"));
}
Context.current.textures.Add(image);
Context.Current.Textures.Add(image);
}
}
@@ -6,7 +6,7 @@ using System.Text;
namespace dq8chr2glb.Container;
public class ChrContainer
public class ChrFile
{
public static List<IncludedFile> FromBytes(byte[] chrFile)
{
@@ -52,15 +52,15 @@ public class ChrContainer
else
{
var file = new IncludedFile();
file.name = name;
file.data = data;
file.Name = name;
file.Data = data;
subContainers.Add(file);
}
foreach (var file in subContainers)
{
var extension = file.name.ToLower().Split(".")[^1];
file.extension = extension switch
var extension = file.Name.ToLower().Split(".")[^1];
file.Extension = extension switch
{
"mds" => FileExtension.MDS,
"mot" => FileExtension.MOT,
@@ -70,9 +70,9 @@ public class ChrContainer
_ => FileExtension.TEXT
};
if (file.name == "info.cfg")
if (file.Name == "info.cfg")
{
file.extension = FileExtension.CFG;
file.Extension = FileExtension.CFG;
}
}
@@ -109,13 +109,13 @@ public class ChrContainer
name = Path.Combine(rootFolderName, name);
file.extension = isTexture ? FileExtension.TM2 : FileExtension.TEXT;
file.name = name + (isTexture ? ".tm2" : ".cfg");
file.Extension = isTexture ? FileExtension.TM2 : FileExtension.TEXT;
file.Name = name + (isTexture ? ".tm2" : ".cfg");
var fileData = new byte[imgFile.FileSize];
Array.Copy(imgData, imgFile.DataOffset, fileData, 0, imgFile.FileSize);
file.data = fileData;
file.Data = fileData;
files.Add(file);
+3 -3
View File
@@ -4,7 +4,7 @@ namespace dq8chr2glb.Container;
public class IncludedFile
{
public string name;
public FileExtension extension;
public byte[] data = Array.Empty<byte>();
public string Name;
public FileExtension Extension;
public byte[] Data = Array.Empty<byte>();
}
+1 -1
View File
@@ -14,7 +14,7 @@ public static class Utils
foreach (var file in files)
{
switch (file.extension)
switch (file.Extension)
{
case FileExtension.TEXT:
configs.Add(file);
+20 -20
View File
@@ -7,45 +7,45 @@ namespace dq8chr2glb.Converter;
public class Message
{
public readonly string name;
public readonly string text;
public readonly string Name;
public readonly string Text;
public Message(string name, string text)
{
this.name = name;
this.text = text;
Name = name;
Text = text;
}
}
public class Error
{
public readonly string name;
public readonly string text;
public readonly Exception? exception;
public readonly string Name;
public readonly string Text;
public readonly Exception? Exception;
public Error(string name, string text, Exception? exception = null)
{
this.name = name;
this.text = text;
this.exception = exception;
Name = name;
Text = text;
Exception = exception;
}
}
public class Context
{
public static Context current;
public static Context Current;
public string modelName;
public string inputPath;
public string outputPath;
public ModelConfig infoCfg;
public List<TM2Format.Texture> textures = new();
public List<MDSConverter> mdsConverters = new();
public List<Error> errors = new();
public List<Message> messages = new();
public string ModelName;
public string InputPath;
public string OutputPath;
public ModelConfig InfoCfg;
public List<TM2Format.Texture> Textures = new();
public List<MDSConverter> MdsConverters = new();
public List<Error> Errors = new();
public List<Message> Messages = new();
public Context()
{
current = this;
Current = this;
}
}
+96 -53
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text.Json.Nodes;
using dq8chr2glb.Core.InfoCfg;
using dq8chr2glb.Core.MDSFormat;
using dq8chr2glb.Core.MOTFormat;
@@ -33,56 +34,96 @@ public class MDSConverter
_gltfModel = ModelRoot.CreateModel();
}
public MDSConverter(string name, ModelRoot model)
{
_name = name;
_gltfModel = model;
foreach (var scene in _gltfModel.LogicalScenes)
{
foreach (var node in scene.VisualChildren)
{
ProcessIndicesRecursive(node);
}
}
foreach (var item in _nodeMap)
{
Log.Line(item.Value.Name);
}
}
private void ProcessIndicesRecursive(Node? node)
{
if (node == null)
{
return;
}
_nodeMap.Add(node.Extras["mds_index"]!.GetValue<int>(), node);
foreach (var child in node.VisualChildren)
{
ProcessIndicesRecursive(child);
}
}
public void Convert(MDSScene scene, List<Texture> images, string name)
{
var gltfScene = _gltfModel.UseScene("DefaultScene");
var nodesWithParents = new HashSet<int>();
if (scene.nodes == null || scene.nodes.Length == 0)
if (scene.Nodes == null || scene.Nodes.Length == 0)
{
Log.Line($"There is nothing to convert in {name}", LogLevel.Info);
return;
}
foreach (var node in scene.nodes)
foreach (var node in scene.Nodes)
{
if (node.parentIndex >= 0)
if (node.ParentIndex >= 0)
{
nodesWithParents.Add(node.index);
nodesWithParents.Add(node.Index);
}
}
foreach (var node in scene.nodes)
foreach (var node in scene.Nodes)
{
if (node.parentIndex < 0)
if (node.ParentIndex < 0)
{
var gltfNode = gltfScene.CreateNode(node.name);
_nodeMap[node.index] = gltfNode;
var gltfNode = gltfScene.CreateNode(node.Name);
gltfNode.Extras = new JsonObject();
gltfNode.Extras["mds_index"] = JsonValue.Create(node.Index);
_nodeMap[node.Index] = gltfNode;
if (node is MDSBone)
{
_boneNodes.Add(gltfNode);
}
gltfNode.WithLocalTransform(ToNumericsMatrix4x4(node.transform));
gltfNode.WithLocalTransform(ToNumericsMatrix4x4(node.Transform));
}
}
foreach (var node in scene.nodes)
foreach (var node in scene.Nodes)
{
if (node.parentIndex >= 0 && _nodeMap.ContainsKey(node.parentIndex))
if (node.ParentIndex >= 0 && _nodeMap.ContainsKey(node.ParentIndex))
{
var parent = _nodeMap[node.parentIndex];
var gltfNode = parent.CreateNode(node.name);
_nodeMap[node.index] = gltfNode;
var parent = _nodeMap[node.ParentIndex];
var gltfNode = parent.CreateNode(node.Name);
gltfNode.Extras = new JsonObject();
gltfNode.Extras["mds_index"] = JsonValue.Create(node.Index);
_nodeMap[node.Index] = gltfNode;
if (node is MDSBone)
{
_boneNodes.Add(gltfNode);
}
gltfNode.WithLocalTransform(ToNumericsMatrix4x4(node.transform));
gltfNode.WithLocalTransform(ToNumericsMatrix4x4(node.Transform));
}
}
@@ -90,22 +131,22 @@ public class MDSConverter
for (int i = 0; i < _boneNodes.Count; i++)
{
var boneNode = _boneNodes[i];
var mdsNode = Array.Find(scene.nodes, n => n.name == boneNode.Name);
var mdsNode = Array.Find(scene.Nodes, n => n.Name == boneNode.Name);
if (mdsNode != null)
{
nodesMapping[mdsNode.index] = i;
nodesMapping[mdsNode.Index] = i;
}
}
CreateMaterials(scene.materials, images);
CreateMaterials(scene.Materials, images);
var meshes = scene.nodes.Where(n => n is MDSMesh).Cast<MDSMesh>().ToArray();
var meshes = scene.Nodes.Where(n => n is MDSMesh).Cast<MDSMesh>().ToArray();
foreach (var mdsMesh in meshes)
{
if (mdsMesh?.vertices == null || mdsMesh.triangles == null) continue;
if (mdsMesh?.Vertices == null || mdsMesh.Triangles == null) continue;
var mesh = CreateGltfMesh(mdsMesh, scene.materials, nodesMapping);
if (_nodeMap.TryGetValue(mdsMesh.index, out var meshNode))
var mesh = CreateGltfMesh(mdsMesh, scene.Materials, nodesMapping);
if (_nodeMap.TryGetValue(mdsMesh.Index, out var meshNode))
{
meshNode.WithMesh(mesh);
}
@@ -113,11 +154,11 @@ public class MDSConverter
if (_boneNodes.Count > 0)
{
SetupSkeleton(scene.nodes, name);
SetupSkeleton(scene.Nodes, name);
}
}
public void CreateAnimation(List<MotionCurve> motionCurves, ModelConfig config)
public void AddAnimation(List<MotionCurve> motionCurves, ModelConfig config)
{
foreach (var clip in config.clips)
{
@@ -125,29 +166,29 @@ public class MDSConverter
var translationCurves = new List<(Node node, Dictionary<float, Vector3> items)>();
foreach (var curve in motionCurves)
{
if (_nodeMap.TryGetValue(curve.boneIndex, out var node))
if (_nodeMap.TryGetValue(curve.BoneIndex, out var node))
{
var rotationKeyframes = new Dictionary<float, Quaternion>();
var translationKeyframes = new Dictionary<float, Vector3>();
for (var i = clip.startFrame; i < clip.endFrame; i++)
for (var i = clip.StartFrame; i < clip.EndFrame; i++)
{
if (i >= curve.keyframes.Count || curve.keyframes[i] == null)
if (i >= curve.Keyframes.Count || curve.Keyframes[i] == null)
{
continue;
}
var keyframe = curve.keyframes[i];
var keyframe = curve.Keyframes[i];
if (curve.curveType == KeyframeType.Quaternion)
if (curve.CurveType == KeyframeType.Quaternion)
{
var time = keyframe.frame / FRAMERATE / clip.speed;
rotationKeyframes[time] = keyframe.rotation;
var time = keyframe.Frame / FRAMERATE / clip.Speed;
rotationKeyframes[time] = keyframe.Rotation;
}
else if (curve.curveType == KeyframeType.Translation)
else if (curve.CurveType == KeyframeType.Translation)
{
var time = keyframe.frame / FRAMERATE / clip.speed;
translationKeyframes[time] = keyframe.translation;
var time = keyframe.Frame / FRAMERATE / clip.Speed;
translationKeyframes[time] = keyframe.Translation;
}
}
@@ -165,7 +206,7 @@ public class MDSConverter
if (rotationCurves.Count > 0 || translationCurves.Count > 0)
{
var animation = _gltfModel.CreateAnimation(clip.name);
var animation = _gltfModel.CreateAnimation(clip.Name);
foreach (var (node, items) in rotationCurves)
{
@@ -198,12 +239,12 @@ public class MDSConverter
byte[] imageBytes;
using (var ms = new MemoryStream())
{
tex.data.SaveAsPng(ms);
tex.Data.SaveAsPng(ms);
imageBytes = ms.ToArray();
}
var imageBuilder = ImageBuilder.From(new MemoryImage(imageBytes), tex.name);
_textureBuilders[tex.name] = imageBuilder;
var imageBuilder = ImageBuilder.From(new MemoryImage(imageBytes), tex.Name);
_textureBuilders[tex.Name] = imageBuilder;
}
var defaultMat = new MaterialBuilder();
@@ -212,15 +253,15 @@ public class MDSConverter
foreach (var mat in materials)
{
var materialBuilder = new MaterialBuilder();
materialBuilder.Name = mat.name;
materialBuilder.Name = mat.Name;
if (!string.IsNullOrEmpty(mat.textureName) && _textureBuilders.ContainsKey(mat.textureName))
if (!string.IsNullOrEmpty(mat.TextureName) && _textureBuilders.ContainsKey(mat.TextureName))
{
var textureImage = _textureBuilders[mat.textureName];
var textureImage = _textureBuilders[mat.TextureName];
materialBuilder.WithBaseColor(textureImage);
}
_materialCache[mat.name] = materialBuilder;
_materialCache[mat.Name] = materialBuilder;
}
}
@@ -234,8 +275,8 @@ public class MDSConverter
Node rootBone = null;
foreach (var boneNode in _boneNodes)
{
var mdsNode = Array.Find(nodes, n => n.name == boneNode.Name);
if (mdsNode is MDSBone bone && bone.parentIndex < 0)
var mdsNode = Array.Find(nodes, n => n.Name == boneNode.Name);
if (mdsNode is MDSBone bone && bone.ParentIndex < 0)
{
rootBone = boneNode;
break;
@@ -255,12 +296,12 @@ public class MDSConverter
for (var i = 0; i < _boneNodes.Count; i++)
{
var boneNode = _boneNodes[i];
var mdsNode = Array.Find(nodes, n => n.name == boneNode.Name);
var mdsNode = Array.Find(nodes, n => n.Name == boneNode.Name);
Matrix4x4 bindMatrix;
if (mdsNode is MDSBone b)
{
var bindposeMatrix = ToNumericsMatrix4x4(b.bindpose);
var bindposeMatrix = ToNumericsMatrix4x4(b.Bindpose);
if (Matrix4x4.Invert(bindposeMatrix, out var bindposeInverse))
{
bindMatrix = Matrix4x4.Multiply(bindposeInverse, Matrix4x4.Identity);
@@ -299,7 +340,7 @@ public class MDSConverter
}
catch (Exception e)
{
Context.current.errors.Add(new Error(name, "Can't set Skin", e));
Context.Current.Errors.Add(new Error(name, "Can't set Skin", e));
}
}
}
@@ -317,16 +358,18 @@ public class MDSConverter
private Matrix4x4 ToNumericsMatrix4x4(MDSMatrix m)
{
return new Matrix4x4(
m.m00, m.m01, m.m02, m.m03,
m.m10, m.m11, m.m12, m.m13,
m.m20, m.m21, m.m22, m.m23,
m.m30, m.m31, m.m32, m.m33
m.M00, m.M01, m.M02, m.M03,
m.M10, m.M11, m.M12, m.M13,
m.M20, m.M21, m.M22, m.M23,
m.M30, m.M31, m.M32, m.M33
);
}
public void Save(string filePath, bool textFormat = true)
public void Save(string filePath, bool textFormat = false)
{
var path = Path.Combine(filePath, _name + (textFormat ? ".gltf" : ".glb"));
var name = Path.GetFileNameWithoutExtension(_name);
var path = Path.Combine(filePath, name + (textFormat ? ".gltf" : ".glb"));
Log.Line(path);
try
{
@@ -15,8 +15,8 @@ namespace dq8chr2glb.Converter.GLTF
public static Mesh CreateMesh(ModelRoot _root, MDSMesh mdsMesh, MDSMaterial[] materials,
Dictionary<string, MaterialBuilder> materialCache, Dictionary<int, int> nodesMap)
{
var hasUvs = (mdsMesh.features & MeshFeatures.UVs) != 0;
var hasSkinning = (mdsMesh.features & MeshFeatures.Weights) != 0;
var hasUvs = (mdsMesh.Features & MeshFeatures.UVs) != 0;
var hasSkinning = (mdsMesh.Features & MeshFeatures.Weights) != 0;
if (hasSkinning)
{
@@ -48,10 +48,10 @@ namespace dq8chr2glb.Converter.GLTF
private static (bool hasUvs, bool hasSkinning) AnalyzeMeshAttributes(MDSMesh mdsMesh)
{
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;
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;
return (hasUvs, hasSkinning);
}
@@ -66,37 +66,37 @@ namespace dq8chr2glb.Converter.GLTF
where TvM : struct, IVertexMaterial
where TvS : struct, IVertexSkinning
{
var meshBuilder = new MeshBuilder<MaterialBuilder, TvG, TvM, TvS>(mdsMesh.name);
var meshBuilder = new MeshBuilder<MaterialBuilder, TvG, TvM, TvS>(mdsMesh.Name);
var positions = new List<Vector3>();
var normals = new List<Vector3>();
var texCoords = new List<Vector2>();
var skinningData = new List<(int, float)[]>();
var hasUvs = (mdsMesh.features & MeshFeatures.UVs) != 0;
var hasSkinning = (mdsMesh.features & MeshFeatures.Weights) != 0;
var hasUvs = (mdsMesh.Features & MeshFeatures.UVs) != 0;
var hasSkinning = (mdsMesh.Features & MeshFeatures.Weights) != 0;
for (var i = 0; i < mdsMesh.vertices.Length; i++)
for (var i = 0; i < mdsMesh.Vertices.Length; i++)
{
positions.Add(new Vector3(
mdsMesh.vertices[i][0],
mdsMesh.vertices[i][1],
mdsMesh.vertices[i][2]));
mdsMesh.Vertices[i][0],
mdsMesh.Vertices[i][1],
mdsMesh.Vertices[i][2]));
normals.Add(Vector3.Zero);
if (hasUvs && i < mdsMesh.uv.Length && mdsMesh.uv[i] != null && mdsMesh.uv[i].Length >= 2)
if (hasUvs && i < mdsMesh.Uv.Length && mdsMesh.Uv[i] != null && mdsMesh.Uv[i].Length >= 2)
{
texCoords.Add(new Vector2(mdsMesh.uv[i][0], mdsMesh.uv[i][1]));
texCoords.Add(new Vector2(mdsMesh.Uv[i][0], mdsMesh.Uv[i][1]));
}
else
{
texCoords.Add(Vector2.Zero);
}
if (hasSkinning && i < mdsMesh.bones.Length)
if (hasSkinning && i < mdsMesh.Bones.Length)
{
var boneIndices = mdsMesh.bones[i];
var boneWeights = mdsMesh.weights[i];
var boneIndices = mdsMesh.Bones[i];
var boneWeights = mdsMesh.Weights[i];
var bindings = new (int JointIndex, float Weight)[Math.Min(4, boneIndices.Length)];
@@ -135,14 +135,14 @@ namespace dq8chr2glb.Converter.GLTF
}
}
MeshUtils.ComputeNormals(positions, mdsMesh.triangles, normals);
MeshUtils.ComputeNormals(positions, mdsMesh.Triangles, normals);
foreach (var submesh in mdsMesh.submeshes)
foreach (var submesh in mdsMesh.Submeshes)
{
var materialBuilder = materialCache.TryGetValue(
submesh.materialIndex >= 0 &&
submesh.materialIndex < materials.Length
? materials[submesh.materialIndex].name
submesh.MaterialIndex >= 0 &&
submesh.MaterialIndex < materials.Length
? materials[submesh.MaterialIndex].Name
: "default",
out var mat)
? mat
@@ -151,15 +151,15 @@ namespace dq8chr2glb.Converter.GLTF
var primitive = meshBuilder.UsePrimitive(materialBuilder);
var indices = new List<int>();
for (var i = submesh.startIndex; i < submesh.startIndex + submesh.indexCount; i += 3)
for (var i = submesh.StartIndex; i < submesh.StartIndex + submesh.IndexCount; i += 3)
{
indices.Add(mdsMesh.triangles[i]);
indices.Add(mdsMesh.triangles[i + 1]);
indices.Add(mdsMesh.triangles[i + 2]);
indices.Add(mdsMesh.Triangles[i]);
indices.Add(mdsMesh.Triangles[i + 1]);
indices.Add(mdsMesh.Triangles[i + 2]);
}
var maxBoneIndex = 0;
foreach (var bones in mdsMesh.bones)
foreach (var bones in mdsMesh.Bones)
{
foreach (var boneID in bones)
{
@@ -201,7 +201,7 @@ namespace dq8chr2glb.Converter.GLTF
}
catch (Exception e)
{
Log.Line($"Create Mesh Error: {mdsMesh.name}", LogLevel.Error);
Log.Line($"Create Mesh Error: {mdsMesh.Name}", LogLevel.Error);
Log.Error(e);
return null;
}
+4 -4
View File
@@ -2,8 +2,8 @@ namespace dq8chr2glb.Core.InfoCfg;
public class Clip
{
public string name;
public int startFrame;
public int endFrame;
public float speed;
public string Name;
public int StartFrame;
public int EndFrame;
public float Speed;
}
+30 -19
View File
@@ -6,43 +6,54 @@ namespace dq8chr2glb.Core.InfoCfg;
public class ConfigFile
{
public string text;
public string Text;
public ModelConfig Config;
public ConfigFile(string text)
{
this.text = text;
Text = text;
}
public ModelConfig ReadConfig()
{
var config = new ModelConfig();
config.model = GetModelFileName();
config.clips = GetClips();
return config;
Config = new ModelConfig();
GetModelFileName();
GetClips();
return Config;
}
public string GetModelFileName()
public void GetModelFileName()
{
var lines = text.Split('\n');
var lines = Text.Split('\n');
foreach (var line in lines)
{
if (line.StartsWith("MODEL"))
{
var match = Regex.Match(line, @"MODEL\s+""([^""]+)""");
return match.Groups[1].Value;
var match = Regex.Match(line, "\"([^\"]+)\"");
if (match.Success)
{
Config.model = match.Groups[1].Value;
}
}
if (line.StartsWith("MOTION"))
{
var match = Regex.Match(line, "\"([^\"]+)\"");
if (match.Success)
{
Config.motion = match.Groups[1].Value;
}
}
}
return null;
}
public List<Clip> GetClips()
public void GetClips()
{
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
var clips = new List<Clip>();
var lines = text.Split('\n');
var lines = Text.Split('\n');
var insideKeyBlock = false;
foreach (var line in lines)
@@ -70,13 +81,13 @@ public class ConfigFile
clips.Add(new Clip
{
name = parts[0][1..^1],
startFrame = int.Parse(parts[1]),
endFrame = int.Parse(parts[2]),
speed = float.Parse(parts[3])
Name = parts[0][1..^1],
StartFrame = int.Parse(parts[1]),
EndFrame = int.Parse(parts[2]),
Speed = float.Parse(parts[3])
});
}
return clips;
Config.clips = clips;
}
}
+1
View File
@@ -6,6 +6,7 @@ public class ModelConfig
{
public string name;
public string model;
public string motion;
public string shadow;
public List<Clip> clips;
}
+54 -99
View File
@@ -6,130 +6,85 @@ namespace dq8chr2glb.Core.MDSFormat;
public struct MDSHeader
{
[FieldOffset(00)] public int MagicCode;
[FieldOffset(04)] public int version;
[FieldOffset(08)] public int headerSize;
[FieldOffset(12)] public int nodesCount;
[FieldOffset(20)] public int nodeSize;
[FieldOffset(24)] public int offsetToNodes;
[FieldOffset(32)] public int meshCount;
[FieldOffset(36)] public int offsetToMDT;
[FieldOffset(44)] public int materialsCount;
[FieldOffset(48)] public int materialSize;
[FieldOffset(52)] public int offsetToMaterials;
[FieldOffset(56)] public int offsetToNames;
[FieldOffset(60)] public int namesBlockSize;
[FieldOffset(04)] public int Version;
[FieldOffset(08)] public int HeaderSize;
[FieldOffset(12)] public int NodesCount;
[FieldOffset(20)] public int NodeSize;
[FieldOffset(24)] public int OffsetToNodes;
[FieldOffset(32)] public int MeshCount;
[FieldOffset(36)] public int OffsetToMDT;
[FieldOffset(44)] public int MaterialsCount;
[FieldOffset(48)] public int MaterialSize;
[FieldOffset(52)] public int OffsetToMaterials;
[FieldOffset(56)] public int OffsetToNames;
[FieldOffset(60)] public int NamesBlockSize;
}
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct MDTHeader
{
[FieldOffset(00)] public int MagicCode;
[FieldOffset(08)] public int toVertexCount;
[FieldOffset(12)] public int totalSize;
[FieldOffset(16)] public int value1; // always 2064?
[FieldOffset(20)] public int value2; // always 4104?
[FieldOffset(24)] public int value3; // always 4112?
[FieldOffset(32)] public float scaleX;
[FieldOffset(36)] public float scaleY;
[FieldOffset(40)] public float scaleZ;
// [FieldOffset(64)] public Float3 someScale; // always 1.079?
[FieldOffset(80)] public float uvScaleX;
[FieldOffset(84)] public float uvScaleY;
[FieldOffset(96)] public int value4; // always 144?
[FieldOffset(112)] public int triangleGroupCount;
[FieldOffset(116)] public int vertexAttrSize;
[FieldOffset(128)] public int value5; // some offset
[FieldOffset(144)] public int vertsCount;
[FieldOffset(148)] public int bytesToFirstVertex;
[FieldOffset(152)] public int colorsCount;
[FieldOffset(156)] public int bytesToFirstColor;
[FieldOffset(160)] public int uvCount;
[FieldOffset(164)] public int uvOffset;
[FieldOffset(176)] public int bonesPerVertex;
[FieldOffset(180)] public int toWeightsOffset;
[FieldOffset(184)] public int toBoneIndicesOffset;
[FieldOffset(188)] public int value6; // always 1?
[FieldOffset(192)] public AABB bounds;
[FieldOffset(08)] public int ToVertexCount;
[FieldOffset(12)] public int TotalSize;
[FieldOffset(32)] public float ScaleX;
[FieldOffset(36)] public float ScaleY;
[FieldOffset(40)] public float ScaleZ;
[FieldOffset(80)] public float UvScaleX;
[FieldOffset(84)] public float UvScaleY;
[FieldOffset(112)] public int TriangleGroupCount;
[FieldOffset(116)] public int VertexAttrSize;
[FieldOffset(144)] public int VertsCount;
[FieldOffset(148)] public int BytesToFirstVertex;
[FieldOffset(152)] public int ColorsCount;
[FieldOffset(156)] public int BytesToFirstColor;
[FieldOffset(160)] public int UvCount;
[FieldOffset(164)] public int UvOffset;
[FieldOffset(176)] public int BonesPerVertex;
[FieldOffset(180)] public int ToWeightsOffset;
[FieldOffset(184)] public int ToBoneIndicesOffset;
[FieldOffset(192)] public AABB Bounds;
}
[StructLayout(LayoutKind.Explicit, Size = 48)]
public struct AABB
{
[FieldOffset(00)] public float minX;
[FieldOffset(04)] public float minY;
[FieldOffset(08)] public float minZ;
[FieldOffset(12)] public int value1; // corner flag?
[FieldOffset(16)] public float maxX;
[FieldOffset(20)] public float maxY;
[FieldOffset(24)] public float maxZ;
[FieldOffset(28)] public int value2; // corner flag?
[FieldOffset(32)] public float centerX;
[FieldOffset(36)] public float centerY;
[FieldOffset(40)] public float centerZ;
[FieldOffset(44)] public float floatValue1; // ?
[FieldOffset(00)] public float MinX;
[FieldOffset(04)] public float MinY;
[FieldOffset(08)] public float MinZ;
[FieldOffset(16)] public float MaxX;
[FieldOffset(20)] public float MaxY;
[FieldOffset(24)] public float MaxZ;
[FieldOffset(32)] public float CenterX;
[FieldOffset(36)] public float CenterY;
[FieldOffset(40)] public float CenterZ;
}
[StructLayout(LayoutKind.Explicit, Size = 160)]
public struct Node
{
[FieldOffset(00)] public int nameOffset;
[FieldOffset(12)] public int meshIndex;
[FieldOffset(16)] public int parentIndex;
[FieldOffset(32)] public MDSMatrix matrix;
[FieldOffset(96)] public MDSMatrix bindpose;
[FieldOffset(00)] public int NameOffset;
[FieldOffset(12)] public int MeshIndex;
[FieldOffset(16)] public int ParentIndex;
[FieldOffset(32)] public MDSMatrix Matrix;
[FieldOffset(96)] public MDSMatrix Bindpose;
}
[StructLayout(LayoutKind.Explicit, Size = 96)]
public struct Material
{
[FieldOffset(00)] public float value1;
[FieldOffset(04)] public float value2;
[FieldOffset(08)] public float value3;
[FieldOffset(12)] public float value4;
[FieldOffset(16)] public float value5;
[FieldOffset(20)] public float value6;
[FieldOffset(24)] public float value7;
[FieldOffset(28)] public float value8;
[FieldOffset(32)] public float value9;
[FieldOffset(36)] public float value10;
[FieldOffset(40)] public float value11;
[FieldOffset(44)] public float value12;
[FieldOffset(48)] public int nameOffset;
[FieldOffset(52)] public int textureNameOffset;
[FieldOffset(64)] public int value13; // 0 для обычных материалов, 1 для материалов без имён
[FieldOffset(68)] public int value14;
[FieldOffset(72)] public int value15;
[FieldOffset(76)] public int value16;
[FieldOffset(80)] public int value17;
[FieldOffset(84)] public int value18;
[FieldOffset(88)] public int value19;
[FieldOffset(92)] public int value20;
[FieldOffset(48)] public int NameOffset;
[FieldOffset(52)] public int TextureNameOffset;
[FieldOffset(64)] public int NoName;
[FieldOffset(80)] public int MaterialType;
}
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct TriangleGroupHeader
{
[FieldOffset(00)] public short toNext;
[FieldOffset(04)] public short readMode;
[FieldOffset(00)] public short ToNext;
[FieldOffset(04)] public short TriangleReadMode;
[FieldOffset(06)] public short IdsCount;
[FieldOffset(16)] public short HeaderSize;
[FieldOffset(06)] public short idsCount;
// [FieldOffset(06)]public short dataSize;
[FieldOffset(16)] public short headerSize;
public TriangleReadMode ReadMode => (TriangleReadMode)readMode;
public TriangleReadMode ReadMode => (TriangleReadMode)TriangleReadMode;
}
+65 -65
View File
@@ -8,16 +8,16 @@ namespace dq8chr2glb.Core.MDSFormat;
public class Reader
{
private const float gScale = 0.00003f; // Global Scale Constant
private const float GLOBAL_SCALE = 0.00003f; // Global Scale Constant
public static MDSScene Read(byte[] data, string filename)
{
var offset = 0;
var header = Utils.ReadStruct<MDSHeader>(data, ref offset, true);
if (header.version != 200)
if (header.Version != 200)
{
Log.Line($"Unknown version: {header.version}.");
Log.Line($"Unknown version: {header.Version}.");
return null;
}
@@ -27,32 +27,32 @@ public class Reader
ReadNodes(scene, header, data, names);
ReadMaterials(scene, header, data, names);
var mdtHeaders = new MDTHeader[header.meshCount];
for (var meshIndex = 0; meshIndex < header.meshCount; meshIndex++)
var mdtHeaders = new MDTHeader[header.MeshCount];
for (var meshIndex = 0; meshIndex < header.MeshCount; meshIndex++)
{
offset = header.offsetToMDT + mdtHeaders.Sum(h => h.totalSize);
offset = header.OffsetToMDT + mdtHeaders.Sum(h => h.TotalSize);
var mdtHeader = Utils.ReadStruct<MDTHeader>(data, ref offset);
mdtHeaders[meshIndex] = mdtHeader;
var mesh = Utils.GetMeshByIndex(scene, meshIndex);
var hasVertices = mdtHeader.vertsCount != 0;
var hasColors = mdtHeader.colorsCount != 0;
var hasUVs = mdtHeader.uvCount != 0;
var isSkinned = mdtHeader.bonesPerVertex != 0;
var hasVertices = mdtHeader.VertsCount != 0;
var hasColors = mdtHeader.ColorsCount != 0;
var hasUVs = mdtHeader.UvCount != 0;
var isSkinned = mdtHeader.BonesPerVertex != 0;
var features = Utils.GetFeaturesFlags(hasVertices, hasColors, hasUVs, isSkinned);
mesh.features = features;
mesh.bounds = mdtHeader.bounds;
mesh.Features = features;
mesh.Bounds = mdtHeader.Bounds;
var scale = new float[3]
{ mdtHeader.scaleX * gScale, mdtHeader.scaleY * gScale, mdtHeader.scaleZ * gScale };
var uvScale = new float[2] { mdtHeader.uvScaleX, mdtHeader.uvScaleY };
{ mdtHeader.ScaleX * GLOBAL_SCALE, mdtHeader.ScaleY * GLOBAL_SCALE, mdtHeader.ScaleZ * GLOBAL_SCALE };
var uvScale = new float[2] { mdtHeader.UvScaleX, mdtHeader.UvScaleY };
// read vertices array
var vOffset = offset + mdtHeader.toVertexCount + mdtHeader.bytesToFirstVertex;
var vCount = mdtHeader.vertsCount;
var vOffset = offset + mdtHeader.ToVertexCount + mdtHeader.BytesToFirstVertex;
var vCount = mdtHeader.VertsCount;
var vertices = Utils.ReadArray<short[], float[]>(data, vOffset, vCount, 3,
s => new[]
{
@@ -60,11 +60,11 @@ public class Reader
});
// read colors array
var colors = new float[mdtHeader.colorsCount][];
var colors = new float[mdtHeader.ColorsCount][];
if ((features & MeshFeatures.Colors) == MeshFeatures.Colors)
{
var cOffset = offset + mdtHeader.toVertexCount + mdtHeader.bytesToFirstColor;
var cCount = mdtHeader.colorsCount;
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 => new[]
@@ -74,8 +74,8 @@ public class Reader
}
// read texcoords array
var tOffset = offset + mdtHeader.toVertexCount + mdtHeader.uvOffset;
var tCount = mdtHeader.uvCount;
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 => new[]
@@ -84,17 +84,17 @@ public class Reader
});
// read weights array
var wOffset = offset + mdtHeader.toVertexCount + mdtHeader.toWeightsOffset;
var wOffset = offset + mdtHeader.ToVertexCount + mdtHeader.ToWeightsOffset;
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 bOffset = offset + mdtHeader.ToVertexCount + mdtHeader.ToBoneIndicesOffset;
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;
var triangleGroupOffset = offset + mdtHeader.VertexAttrSize;
var lastEndIndex = 0;
var verticesList = new List<float[]>();
@@ -104,16 +104,16 @@ public class Reader
var weightsList = new List<float[]>();
var bonesList = new List<int[]>();
for (var submeshIndex = 0; submeshIndex < mdtHeader.triangleGroupCount; submeshIndex++)
for (var submeshIndex = 0; submeshIndex < mdtHeader.TriangleGroupCount; submeshIndex++)
{
var triangleGroupHeader = Utils.ReadStruct<TriangleGroupHeader>(data, ref triangleGroupOffset);
if (triangleGroupHeader.headerSize == 32 && triangleGroupHeader.toNext == 0)
if (triangleGroupHeader.HeaderSize == 32 && triangleGroupHeader.ToNext == 0)
{
// This is normal. Often, such blocks complete a sequence.
continue;
}
if (triangleGroupHeader.headerSize == 32 && triangleGroupHeader.toNext != 0)
if (triangleGroupHeader.HeaderSize == 32 && triangleGroupHeader.ToNext != 0)
{
// This might be weird.
// Log.Line($"Skip 32-bit triangle group header on {triangleGroupOffset}");
@@ -122,7 +122,7 @@ public class Reader
var submesh = new SubMesh();
var triangleIndexOffset = triangleGroupOffset + triangleGroupHeader.headerSize;
var triangleIndexOffset = triangleGroupOffset + triangleGroupHeader.HeaderSize;
var triangles = ReadTriangles(triangleGroupHeader, triangleIndexOffset, data);
var cornerVerts = GetCornerAttribute(vertices, triangles, triangleIndexOffset, "vertices");
verticesList.AddRange(cornerVerts);
@@ -136,9 +136,9 @@ public class Reader
bonesList.AddRange(cornerBoneIndices);
}
submesh.materialIndex = BitConverter.ToInt32(data, triangleGroupOffset + 32);
submesh.startIndex = lastEndIndex;
submesh.indexCount = triangles.Length;
submesh.MaterialIndex = BitConverter.ToInt32(data, triangleGroupOffset + 32);
submesh.StartIndex = lastEndIndex;
submesh.IndexCount = triangles.Length;
lastEndIndex += cornerVerts.Length;
submeshes.Add(submesh);
@@ -168,16 +168,16 @@ public class Reader
}
}
triangleGroupOffset += triangleGroupHeader.toNext;
triangleGroupOffset += triangleGroupHeader.ToNext;
}
mesh.vertices = verticesList.ToArray();
mesh.triangles = trianglesList.ToArray();
mesh.color = colorsList.ToArray();
mesh.uv = uvList.ToArray();
mesh.weights = weightsList.ToArray();
mesh.bones = bonesList.ToArray();
mesh.submeshes = submeshes.ToArray();
mesh.Vertices = verticesList.ToArray();
mesh.Triangles = trianglesList.ToArray();
mesh.Color = colorsList.ToArray();
mesh.Uv = uvList.ToArray();
mesh.Weights = weightsList.ToArray();
mesh.Bones = bonesList.ToArray();
mesh.Submeshes = submeshes.ToArray();
}
return scene;
@@ -185,21 +185,21 @@ public class Reader
private static string ReadNames(MDSHeader header, byte[] data)
{
var offset = header.offsetToNames;
var decodedString = Encoding.UTF8.GetString(data, offset, header.namesBlockSize);
var offset = header.OffsetToNames;
var decodedString = Encoding.UTF8.GetString(data, offset, header.NamesBlockSize);
return decodedString.Replace("\0", " ");
}
private static void ReadNodes(MDSScene scene, MDSHeader header, byte[] data, string names)
{
scene.nodes = new MDSNode[header.nodesCount];
scene.Nodes = new MDSNode[header.NodesCount];
var offset = header.offsetToNodes;
for (var i = 0; i < header.nodesCount; i++)
var offset = header.OffsetToNodes;
for (var i = 0; i < header.NodesCount; i++)
{
var node = Utils.ReadStruct<Node>(data, ref offset, true);
var type = node.meshIndex < 0 ? NodeType.Bone : NodeType.Mesh;
var type = node.MeshIndex < 0 ? NodeType.Bone : NodeType.Mesh;
MDSNode mdsNode;
if (type == NodeType.Bone)
@@ -212,42 +212,42 @@ public class Reader
mdsNode = new MDSMesh();
}
mdsNode.bindpose = node.bindpose;
mdsNode.name = Utils.GetName(names, node.nameOffset);
mdsNode.transform = node.matrix;
mdsNode.index = i;
mdsNode.parentIndex = node.parentIndex;
mdsNode.type = type;
mdsNode.meshIndex = node.meshIndex;
mdsNode.Bindpose = node.Bindpose;
mdsNode.Name = Utils.GetName(names, node.NameOffset);
mdsNode.Transform = node.Matrix;
mdsNode.Index = i;
mdsNode.ParentIndex = node.ParentIndex;
mdsNode.Type = type;
mdsNode.MeshIndex = node.MeshIndex;
scene.nodes[i] = mdsNode;
scene.Nodes[i] = mdsNode;
}
}
private static void ReadMaterials(MDSScene scene, MDSHeader header, byte[] data, string names)
{
var offset = header.offsetToMaterials;
var offset = header.OffsetToMaterials;
scene.materials = new MDSMaterial[header.materialsCount];
scene.Materials = new MDSMaterial[header.MaterialsCount];
for (var i = 0; i < header.materialsCount; i++)
for (var i = 0; i < header.MaterialsCount; i++)
{
var rawMaterial = Utils.ReadStruct<Material>(data, ref offset, true);
var mdsMaterial = new MDSMaterial();
mdsMaterial.materialType = rawMaterial.value17;
mdsMaterial.MaterialType = rawMaterial.MaterialType;
if (rawMaterial.value13 == 1)
if (rawMaterial.NoName == 1)
{
mdsMaterial.name = "NONE";
mdsMaterial.textureName = "NONE";
mdsMaterial.Name = "NONE";
mdsMaterial.TextureName = "NONE";
}
else
{
mdsMaterial.name = Utils.GetName(names, rawMaterial.nameOffset);
mdsMaterial.textureName = Utils.GetName(names, rawMaterial.textureNameOffset);
mdsMaterial.Name = Utils.GetName(names, rawMaterial.NameOffset);
mdsMaterial.TextureName = Utils.GetName(names, rawMaterial.TextureNameOffset);
}
scene.materials[i] = mdsMaterial;
scene.Materials[i] = mdsMaterial;
}
}
@@ -256,7 +256,7 @@ public class Reader
var triangles = new List<int>();
if (header.ReadMode == TriangleReadMode.Triangle)
{
for (var i = 0; i < header.idsCount; i += 3)
for (var i = 0; i < header.IdsCount; i += 3)
{
var triangle = Utils.ReadArray<short, int>(data, offset, 3, 0, i => i);
offset += 6;
@@ -271,7 +271,7 @@ public class Reader
if (header.ReadMode == TriangleReadMode.TriangleStrip)
{
var isDefaultOrder = true;
while (readCount < header.idsCount - 2)
while (readCount < header.IdsCount - 2)
{
var triangle = Utils.ReadArray<short, int>(data, offset, 3, 0, i => i);
readCount++;
+45 -45
View File
@@ -4,19 +4,19 @@ namespace dq8chr2glb.Core.MDSFormat;
public class MDSScene
{
public MDSNode[] nodes;
public MDSMaterial[] materials;
public MDSNode[] Nodes;
public MDSMaterial[] Materials;
}
public class MDSNode
{
public string name;
public int index;
public int parentIndex;
public MDSMatrix transform;
public MDSMatrix bindpose;
public NodeType type;
public int meshIndex;
public string Name;
public int Index;
public int ParentIndex;
public MDSMatrix Transform;
public MDSMatrix Bindpose;
public NodeType Type;
public int MeshIndex;
}
public class MDSBone : MDSNode
@@ -25,57 +25,57 @@ public class MDSBone : MDSNode
public class MDSMesh : MDSNode
{
public SubMesh[] submeshes;
public MeshFeatures features;
public AABB bounds;
public SubMesh[] Submeshes;
public MeshFeatures Features;
public AABB Bounds;
// merged data
public float[][] vertices;
public float[][] color;
public float[][] uv;
public float[][] weights;
public int[][] bones;
public int[] triangles;
public float[][] Vertices;
public float[][] Color;
public float[][] Uv;
public float[][] Weights;
public int[][] Bones;
public int[] Triangles;
}
public class SubMesh
{
public int materialIndex;
public int startIndex;
public int indexCount;
public int MaterialIndex;
public int StartIndex;
public int IndexCount;
}
public class MDSMaterial
{
public string name;
public string textureName;
public int materialType;
public string Name;
public string TextureName;
public int MaterialType;
}
[StructLayout(LayoutKind.Explicit, Size=64)]
public struct MDSMatrix
{
[FieldOffset(00)]public float m00;
[FieldOffset(04)]public float m01;
[FieldOffset(08)]public float m02;
[FieldOffset(12)]public float m03;
[FieldOffset(16)]public float m10;
[FieldOffset(20)]public float m11;
[FieldOffset(24)]public float m12;
[FieldOffset(28)]public float m13;
[FieldOffset(32)]public float m20;
[FieldOffset(36)]public float m21;
[FieldOffset(40)]public float m22;
[FieldOffset(44)]public float m23;
[FieldOffset(48)]public float m30;
[FieldOffset(52)]public float m31;
[FieldOffset(56)]public float m32;
[FieldOffset(60)]public float m33;
[FieldOffset(00)]public float M00;
[FieldOffset(04)]public float M01;
[FieldOffset(08)]public float M02;
[FieldOffset(12)]public float M03;
[FieldOffset(16)]public float M10;
[FieldOffset(20)]public float M11;
[FieldOffset(24)]public float M12;
[FieldOffset(28)]public float M13;
[FieldOffset(32)]public float M20;
[FieldOffset(36)]public float M21;
[FieldOffset(40)]public float M22;
[FieldOffset(44)]public float M23;
[FieldOffset(48)]public float M30;
[FieldOffset(52)]public float M31;
[FieldOffset(56)]public float M32;
[FieldOffset(60)]public float M33;
public float[] elements => new[]
public float[] Elements => new[]
{
m00, m01, m02, m03,
m10, m11, m12, m13,
m20, m21, m22, m23,
m30, m31, m32, m33
M00, M01, M02, M03,
M10, M11, M12, M13,
M20, M21, M22, M23,
M30, M31, M32, M33
};
}
+62 -62
View File
@@ -12,9 +12,9 @@ public class Importer
{
var offset = 0;
var header = Utils.ReadStruct<MOTHeader>(data, ref offset);
offset += header.bonesOffset;
offset += header.BonesOffset;
var boneCount = header.boneCount;
var boneCount = header.BoneCount;
var animation = new List<MotionCurve>();
for (var i = 0; i < boneCount; i++)
{
@@ -27,7 +27,7 @@ public class Importer
private List<TransformGroup> GetTransformGroups(byte[] data, BoneHeader header, int offset, out int offsetAfter)
{
var attribCount = TransformFlagsHelper.GetAttribCount(header.transformFlags);
var attribCount = TransformFlagsHelper.GetAttribCount(header.TransformFlags);
var attribHeadersOffset = attribCount > 1 ? 32 : 16;
var transformGroups = new List<TransformGroup>();
@@ -45,31 +45,31 @@ public class Importer
private void ReadRotationKeyframes(byte[] data, MotionCurve curve, int offset)
{
curve.curveType = KeyframeType.Quaternion;
curve.CurveType = KeyframeType.Quaternion;
var headerOffset = offset;
var header = Utils.ReadStruct<KeyframesBlockHeader>(data, ref headerOffset);
var valuesOffset = headerOffset + header.valuesOffset;
var valuesOffset = headerOffset + header.ValuesOffset;
if (header.toFramesOffset == 0)
if (header.ToFramesOffset == 0)
{
var count = header.framesCount;
var count = header.FramesCount;
for (var i = 0; i < count; i++)
{
var keyframe = new KeyFrame();
keyframe.frame = i;
keyframe.Frame = i;
var scale = new Vector4(header.scaleX, header.scaleY,
header.scaleZ, header.scaleW);
var scale = new Vector4(header.ScaleX, header.ScaleY,
header.ScaleZ, header.ScaleW);
keyframe.rotation = ReadQuaternion(data, valuesOffset, scale);
curve.keyframes.Add(keyframe);
keyframe.Rotation = ReadQuaternion(data, valuesOffset, scale);
curve.Keyframes.Add(keyframe);
valuesOffset += 8;
}
}
else
{
var framesCount = header.framesCount;
var toFramesOffset = headerOffset + header.toFramesOffset;
var framesCount = header.FramesCount;
var toFramesOffset = headerOffset + header.ToFramesOffset;
var frames = ReadFrames(data, toFramesOffset, framesCount);
if (frames.Length == 0)
{
@@ -77,26 +77,26 @@ public class Importer
}
var maxFrame = frames.Max();
curve.keyframes = new List<KeyFrame>();
curve.Keyframes = new List<KeyFrame>();
for (var i = 0; i < maxFrame + 1; i++)
{
curve.keyframes.Add(null);
curve.Keyframes.Add(null);
}
var toValues = header.valuesOffset;
var toValues = header.ValuesOffset;
foreach (var frame in frames)
{
var scale = new Vector4(header.scaleX, header.scaleY,
header.scaleZ, header.scaleW);
var scale = new Vector4(header.ScaleX, header.ScaleY,
header.ScaleZ, header.ScaleW);
var keyframe = new KeyFrame();
keyframe.frame = frame;
keyframe.type = KeyframeType.Quaternion;
keyframe.rotation = ReadQuaternion(data, valuesOffset, scale);
keyframe.Frame = frame;
keyframe.Type = KeyframeType.Quaternion;
keyframe.Rotation = ReadQuaternion(data, valuesOffset, scale);
try
{
curve.keyframes[frame] = keyframe;
curve.Keyframes[frame] = keyframe;
}
catch (Exception e)
{
@@ -110,33 +110,33 @@ public class Importer
private void ReadPositionKeyframes(byte[] data, MotionCurve curve, int offset)
{
curve.curveType = KeyframeType.Translation;
curve.CurveType = KeyframeType.Translation;
var headerOffset = offset;
var header = Utils.ReadStruct<KeyframesBlockHeader>(data, ref offset, false);
var valuesOffset = headerOffset + header.valuesOffset;
var valuesOffset = headerOffset + header.ValuesOffset;
if (header.toFramesOffset == 0)
if (header.ToFramesOffset == 0)
{
var count = header.framesCount;
var count = header.FramesCount;
for (var i = 0; i < count; i++)
{
var keyframe = new KeyFrame();
keyframe.frame = i;
keyframe.Frame = i;
var scale = new Vector3(header.scaleX, header.scaleY,
header.scaleZ);
var scale = new Vector3(header.ScaleX, header.ScaleY,
header.ScaleZ);
var translation = ReadVector3(data, valuesOffset, scale);
keyframe.translation = translation;
curve.keyframes.Add(keyframe);
keyframe.Translation = translation;
curve.Keyframes.Add(keyframe);
valuesOffset += 6;
}
}
else
{
var framesCount = header.framesCount;
var toFramesOffset = headerOffset + header.toFramesOffset;
var framesCount = header.FramesCount;
var toFramesOffset = headerOffset + header.ToFramesOffset;
var frames = ReadFrames(data, toFramesOffset, framesCount);
if (frames.Length == 0)
{
@@ -144,13 +144,13 @@ public class Importer
}
var maxFrame = frames.Max();
curve.keyframes = new List<KeyFrame>();
curve.Keyframes = new List<KeyFrame>();
for (var i = 0; i < maxFrame + 1; i++)
{
curve.keyframes.Add(null);
curve.Keyframes.Add(null);
}
var toValues = header.valuesOffset;
var toValues = header.ValuesOffset;
foreach (var frame in frames)
{
if (frame == null || frame < 0)
@@ -158,15 +158,15 @@ public class Importer
continue;
}
var scale = new Vector3(header.scaleX, header.scaleY,
header.scaleZ);
var scale = new Vector3(header.ScaleX, header.ScaleY,
header.ScaleZ);
var keyframe = new KeyFrame();
keyframe.frame = frame;
keyframe.type = KeyframeType.Translation;
keyframe.translation = ReadVector3(data, valuesOffset, scale);
keyframe.Frame = frame;
keyframe.Type = KeyframeType.Translation;
keyframe.Translation = ReadVector3(data, valuesOffset, scale);
curve.keyframes[frame] = keyframe;
curve.Keyframes[frame] = keyframe;
valuesOffset += 6;
}
}
@@ -184,39 +184,39 @@ public class Importer
foreach (var group in transformGroups)
{
var curve = new MotionCurve();
curve.boneIndex = header.boneIndex;
curve.keyframes = new List<KeyFrame>();
curve.BoneIndex = header.BoneIndex;
curve.Keyframes = new List<KeyFrame>();
switch (group.keyframeType)
switch (group.KeyframeType)
{
case KeyframeType.Quaternion:
ReadRotationKeyframes(data, curve, headerPosition + group.offset);
ReadRotationKeyframes(data, curve, headerPosition + group.Offset);
break;
case KeyframeType.Translation:
ReadPositionKeyframes(data, curve, headerPosition + group.offset);
ReadPositionKeyframes(data, curve, headerPosition + group.Offset);
break;
default:
// Debug.Log($"[UNK KF TYPE]: {group.keyframeType}");
Log.Line($"[UNKNOWN KF TYPE]: {group.KeyframeType}");
break;
}
boneCurves.Add(curve);
}
switch (header.transformFlags)
{
case TransformFlags.Rotation:
// Debug.Log($"Rotation: {(int)header.transformFlags}.");
break;
case TransformFlags.TR:
// Debug.Log($"Rot/Pos: {(int)header.transformFlags}");
break;
default:
// Debug.Log($"Unknown flag: {(int)header.transformFlags}, {header.transformFlags}");
break;
}
// switch (header.transformFlags)
// {
// case TransformFlags.Rotation:
// // Debug.Log($"Rotation: {(int)header.transformFlags}.");
// break;
// case TransformFlags.TR:
// // Debug.Log($"Rot/Pos: {(int)header.transformFlags}");
// break;
// default:
// // Debug.Log($"Unknown flag: {(int)header.transformFlags}, {header.transformFlags}");
// break;
// }
offset = headerPosition + header.toNext;
offset = headerPosition + header.ToNext;
return boneCurves;
}
+26 -26
View File
@@ -9,39 +9,39 @@ namespace dq8chr2glb.Core.MOTFormat
public struct MOTHeader
{
[FieldOffset(00)] public int MagicCode;
[FieldOffset(08)] public int headerSize;
[FieldOffset(28)] public int boneCount;
[FieldOffset(32)] public int bonesOffset;
[FieldOffset(48)] public int fileSizeClaim;
[FieldOffset(08)] public int HeaderSize;
[FieldOffset(28)] public int BoneCount;
[FieldOffset(32)] public int BonesOffset;
[FieldOffset(48)] public int FileSize;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct BoneHeader
{
[FieldOffset(00)] public int toNext;
[FieldOffset(04)] public int boneIndex;
[FieldOffset(12)] public TransformFlags transformFlags;
[FieldOffset(00)] public int ToNext;
[FieldOffset(04)] public int BoneIndex;
[FieldOffset(12)] public TransformFlags TransformFlags;
}
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct TransformGroup
{
[FieldOffset(00)] public KeyframeType keyframeType;
[FieldOffset(04)] public int offset;
[FieldOffset(00)] public KeyframeType KeyframeType;
[FieldOffset(04)] public int Offset;
}
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct KeyframesBlockHeader
{
[FieldOffset(00)] public short flag0;
[FieldOffset(02)] public short flag1;
[FieldOffset(06)] public short framesCount;
[FieldOffset(08)] public short toFramesOffset;
[FieldOffset(12)] public short valuesOffset;
[FieldOffset(16)] public float scaleX;
[FieldOffset(20)] public float scaleY;
[FieldOffset(24)] public float scaleZ;
[FieldOffset(28)] public float scaleW;
[FieldOffset(00)] public short Flag0;
[FieldOffset(02)] public short Flag1;
[FieldOffset(06)] public short FramesCount;
[FieldOffset(08)] public short ToFramesOffset;
[FieldOffset(12)] public short ValuesOffset;
[FieldOffset(16)] public float ScaleX;
[FieldOffset(20)] public float ScaleY;
[FieldOffset(24)] public float ScaleZ;
[FieldOffset(28)] public float ScaleW;
}
[Flags]
@@ -68,18 +68,18 @@ namespace dq8chr2glb.Core.MOTFormat
public class MotionCurve
{
public int boneIndex;
public KeyframeType curveType;
public List<KeyFrame> keyframes;
public int BoneIndex;
public KeyframeType CurveType;
public List<KeyFrame> Keyframes;
}
public class KeyFrame
{
public int frame;
public KeyframeType type;
public Quaternion rotation;
public Vector3 translation;
public Vector3 scale;
public int Frame;
public KeyframeType Type;
public Quaternion Rotation;
public Vector3 Translation;
public Vector3 Scale;
}
public static class TransformFlagsHelper
-2
View File
@@ -1,2 +0,0 @@
namespace dq8chr2glb.Core;
+3 -3
View File
@@ -68,12 +68,12 @@ public static class Utils
public static MDSMesh GetMeshByIndex(MDSScene scene, int index)
{
foreach (var node in scene.nodes)
foreach (var node in scene.Nodes)
{
if (node.type == NodeType.Mesh)
if (node.Type == NodeType.Mesh)
{
var mesh = node as MDSMesh;
if (mesh.meshIndex == index)
if (mesh.MeshIndex == index)
{
return mesh;
}
+14 -5
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using dq8chr2glb.Converter;
@@ -15,24 +14,34 @@ public class Program
{
new Option<string>("-i", "--input") { Description = "Input path", HelpName = "input path"},
new Option<string>("-o", "--output") { Description = "Output path", HelpName = "output path"},
new Option<string>("-d", "--data") { Description = "Data for append animations", HelpName = "Data"},
new Option<OutputFormat>("-f", "--format") {Description = "Output format", DefaultValueFactory = _ => OutputFormat.GLB},
new Option<bool>("-e", "--extract") {Description = "Extract only - unpack .chr without conversion"},
new Option<bool>("-b", "--batch") {Description = "Batch mode - process all .chr files in the input directory"},
new Option<LogMode>("-l", "--log") { Description = "Log level", DefaultValueFactory = _ => LogMode.MINIMAL },
// new Option<bool>("-a", "--append") { Description = "Append animations to input .glb/glTF file"},
};
var parser = rootCommand.Parse(args);
var inputPath = parser.GetValue<string>("-i");
var outputPath = parser.GetValue<string>("-o");
var dataPath = parser.GetValue<string>("-d");
var extractOnly = parser.GetValue<bool>("-e");
var textFormat = parser.GetValue<OutputFormat>("-f") == OutputFormat.GLTF;
var batchMode = parser.GetValue<bool>("-b");
// var appendMode = parser.GetValue<bool>("-a");
// if (appendMode)
// {
// var appendAnimations = new AppendAnimations();
// appendAnimations.Append(inputPath, dataPath);
// return;
// }
var chrFile = new ChrFile();
chrFile.convert = !extractOnly;
chrFile.extract = extractOnly;
chrFile.textFormat = textFormat;
chrFile.Convert = !extractOnly;
chrFile.Extract = extractOnly;
chrFile.TextFormat = textFormat;
if (string.IsNullOrEmpty(inputPath))
{
+4 -4
View File
@@ -10,12 +10,12 @@ namespace dq8chr2glb.TM2Format;
// Взято из https://github.com/Souzooka/TM2Unswizzler
public static class TM2Format
{
public static IncludedFile currentFile;
public static IncludedFile CurrentFile;
public static Texture GetImage(IncludedFile file, string name)
{
currentFile = file;
var data = file.data;
CurrentFile = file;
var data = file.Data;
var header = TM2Header.GetHeader(data);
var image = GetImageBuffer(header, data);
@@ -73,7 +73,7 @@ public static class TM2Format
}
catch (Exception e)
{
Context.current.errors.Add(new Error(currentFile.name, "Error then import texture", e));
Context.Current.Errors.Add(new Error(CurrentFile.Name, "Error while import texture", e));
palette = null;
}
+2 -2
View File
@@ -8,8 +8,8 @@ public struct TM2Header
public int Label;
public byte Version;
public byte Format;
public int padding0;
public int padding1;
public int Padding0;
public int Padding1;
public uint TotalSize;
public uint ClutSize;
public uint ImageSize;
+4 -4
View File
@@ -5,12 +5,12 @@ namespace dq8chr2glb.TM2Format;
public class Texture
{
public readonly string name;
public readonly Image<Rgba32> data;
public readonly string Name;
public readonly Image<Rgba32> Data;
public Texture(string name, Image<Rgba32> data)
{
this.name = name;
this.data = data;
Name = name;
Data = data;
}
}