3 Commits

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