Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b8a22b0dc | |||
| 6c169a3a18 | |||
| 5ad44e128b | |||
| 7eb47677ef | |||
| 91029ccbe7 | |||
| 8301a1446a | |||
| 3418bea265 | |||
| d36f8355f5 | |||
| 46ddb283b2 | |||
| 291babd1c0 | |||
| 87b48441f8 |
@@ -1,6 +1,16 @@
|
||||
# DQ8 chr to glTF Converter
|
||||

|
||||
|
||||
<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.
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
if (line.StartsWith("MOTION"))
|
||||
{
|
||||
var match = Regex.Match(line, "\"([^\"]+)\"");
|
||||
if (match.Success)
|
||||
{
|
||||
Config.motion = match.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public class ModelConfig
|
||||
{
|
||||
public string name;
|
||||
public string model;
|
||||
public string motion;
|
||||
public string shadow;
|
||||
public List<Clip> clips;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
namespace dq8chr2glb.Core;
|
||||
|
||||
@@ -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
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user