From 1edd1a40543834f43174fdc809d6b7c4e21188f2 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Sat, 8 Jun 2024 13:14:33 +0900 Subject: [PATCH 1/4] Implement command `dump-srt` --- .../Commands/DumpSrtCommand.cs | 241 ++++++++++++++++++ .../OpenKh.Command.AnbMaker.csproj | 1 + OpenKh.Command.AnbMaker/Program.cs | 1 + 3 files changed, 243 insertions(+) create mode 100644 OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs diff --git a/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs new file mode 100644 index 000000000..9d478c5c9 --- /dev/null +++ b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs @@ -0,0 +1,241 @@ +using libcsv; +using McMaster.Extensions.CommandLineUtils; +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static OpenKh.Kh2.Motion; + +namespace OpenKh.Command.AnbMaker.Commands +{ + [HelpOption] + [Command(Description = "anb file: dump scale rotation translation to csv")] + internal class DumpSrtCommand + { + [Required] + [FileExists] + [Argument(0, Description = "anb input")] + public string InputMotion { get; set; } = null!; + + [Argument(1, Description = "csv output")] + public string? OutputCsv { get; set; } + + [Option(Description = "use radians instead of degrees", ShortName = "r", LongName = "use-radians")] + public bool UseRadians { get; set; } + + private record MotionSource( + InterpolatedMotion Motion, + string Name); + + protected int OnExecute(CommandLineApplication app) + { + OutputCsv = Path.GetFullPath(OutputCsv ?? Path.GetFileNameWithoutExtension(InputMotion) + ".csv"); + + Console.WriteLine($"Writing to: {OutputCsv}"); + + var fileStream = new MemoryStream( + File.ReadAllBytes(InputMotion) + ); + + var motionSourceList = new List(); + + string FilterName(string name) => name.Trim(); + + var barFile = Bar.Read(fileStream); + if (barFile.Any(it => it.Type == Bar.EntryType.Anb)) + { + // this is mset + motionSourceList.AddRange( + barFile + .Where(barEntry => barEntry.Type == Bar.EntryType.Anb && barEntry.Stream.Length >= 16) + .SelectMany( + (barEntry, barEntryIndex) => + Bar.Read(barEntry.Stream) + .Where(subBarEntry => subBarEntry.Type == Bar.EntryType.Motion) + .Select( + (subBarEntry, subBarEntryIndex) => new MotionSource( + new InterpolatedMotion(subBarEntry.Stream), + $"{barEntryIndex}_{FilterName(barEntry.Name)}_{subBarEntryIndex}_{FilterName(subBarEntry.Name)}" + ) + ) + ) + ); + } + else if (barFile.Any(barEntry => barEntry.Type == Bar.EntryType.Motion)) + { + // this is anb + motionSourceList.AddRange( + barFile + .Where(barEntry => barEntry.Type == Bar.EntryType.Motion) + .Select( + (barEntry, barEntryIndex) => + new MotionSource( + new InterpolatedMotion(barEntry.Stream), + $"{barEntryIndex}_{FilterName(barEntry.Name)}" + ) + ) + .ToArray() + ); + } + else + { + Console.Error.WriteLine("Error. Specify valid file of either mset or anb."); + return 1; + } + + Func consumeRadians = UseRadians switch + { + true => radians => radians, + false => radians => radians * 180 / MathF.PI, + }; + + var perMotionListAll = new List(); + + foreach (var motionSource in motionSourceList) + { + var keys = motionSource.Motion.FCurveKeys; + var keyValues = motionSource.Motion.KeyValues; + + var perMotionList = new List(); + + foreach (var curvesInput in new CurvesInput[0] + .Append(new CurvesInput( + FCurves: motionSource.Motion.FCurvesForward, + BaseJointIndex: 0, + GetFK: index => index, + GetIK: index => null)) + .Append(new CurvesInput( + FCurves: motionSource.Motion.FCurvesInverse, + BaseJointIndex: motionSource.Motion.InterpolatedMotionHeader.BoneCount, + GetFK: index => null, + GetIK: index => index)) + ) + { + var baseJointId = curvesInput.BaseJointIndex; + + foreach (var fCurve in curvesInput.FCurves) + { + Action setter = fCurve.ChannelValue switch + { + Channel.SCALE_X => (it, value) => it.ScaleX = value, + Channel.SCALE_Y => (it, value) => it.ScaleY = value, + Channel.SCALE_Z => (it, value) => it.ScaleZ = value, + Channel.ROTATATION_X => (it, value) => it.RotationX = consumeRadians(value), + Channel.ROTATATION_Y => (it, value) => it.RotationY = consumeRadians(value), + Channel.ROTATATION_Z => (it, value) => it.RotationZ = consumeRadians(value), + Channel.TRANSLATION_X => (it, value) => it.TranslationX = value, + Channel.TRANSLATION_Y => (it, value) => it.TranslationY = value, + Channel.TRANSLATION_Z => (it, value) => it.TranslationZ = value, + _ => throw new NotImplementedException("" + fCurve.ChannelValue), + }; + + var jointId = fCurve.JointId; + + PerMotion AllocatePerMotion(float time) + { + var hit = perMotionList.SingleOrDefault( + it => it.Time == time && it.Joint == baseJointId + jointId + ); + if (hit == null) + { + perMotionList.Add( + hit = new PerMotion( + motionSource.Name, + baseJointId + jointId, + curvesInput.GetFK(jointId), + curvesInput.GetIK(jointId), + time + ) + ); + } + + return hit; + } + + var baseIdx = (ushort)fCurve.KeyStartId; + for (int idx = 0; idx < fCurve.KeyCount; idx++) + { + var key = keys[baseIdx + idx]; + + setter( + AllocatePerMotion(keyValues[key.Time]), + keyValues[key.ValueId] + ); + } + } + } + + perMotionListAll.AddRange(perMotionList); + } + + { + using var writer = new StreamWriter(OutputCsv, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); + var csv = new Csvw(writer, ',', '"'); + + { + csv.Write("Name"); + csv.Write("Joint"); + csv.Write("FK"); + csv.Write("IK"); + csv.Write("Time"); + csv.Write("ScaleX"); + csv.Write("ScaleY"); + csv.Write("ScaleZ"); + csv.Write("RotationX"); + csv.Write("RotationY"); + csv.Write("RotationZ"); + csv.Write("TranslationX"); + csv.Write("TranslationY"); + csv.Write("TranslationZ"); + } + foreach (var row in perMotionListAll) + { + csv.NextRow(); + csv.Write(row.Name); + csv.Write(row.Joint + ""); + csv.Write(row.FK + ""); + csv.Write(row.IK + ""); + csv.Write(row.Time + ""); + csv.Write(row.ScaleX + ""); + csv.Write(row.ScaleY + ""); + csv.Write(row.ScaleZ + ""); + csv.Write(row.RotationX + ""); + csv.Write(row.RotationY + ""); + csv.Write(row.RotationZ + ""); + csv.Write(row.TranslationX + ""); + csv.Write(row.TranslationY + ""); + csv.Write(row.TranslationZ + ""); + } + } + + return 0; + } + + private record CurvesInput( + IEnumerable FCurves, + int BaseJointIndex, + Func GetFK, + Func GetIK); + + private record PerMotion( + string Name, + int Joint, + int? FK, + int? IK, + float Time) + { + public float? ScaleX { get; set; } + public float? ScaleY { get; set; } + public float? ScaleZ { get; set; } + public float? RotationX { get; set; } + public float? RotationY { get; set; } + public float? RotationZ { get; set; } + public float? TranslationX { get; set; } + public float? TranslationY { get; set; } + public float? TranslationZ { get; set; } + } + } +} diff --git a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj index b3e2c62cb..3a417b24a 100644 --- a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj +++ b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj @@ -13,6 +13,7 @@ + diff --git a/OpenKh.Command.AnbMaker/Program.cs b/OpenKh.Command.AnbMaker/Program.cs index af30afa8b..7fdd6aec0 100644 --- a/OpenKh.Command.AnbMaker/Program.cs +++ b/OpenKh.Command.AnbMaker/Program.cs @@ -15,6 +15,7 @@ namespace OpenKh.Command.AnbMaker [Subcommand(typeof(ExportRawCommand))] [Subcommand(typeof(AnbExCommand))] [Subcommand(typeof(DumpNodeTreeCommand))] + [Subcommand(typeof(DumpSrtCommand))] [Subcommand(typeof(RenderNodeTreeCommand))] internal class Program { From 120eea79feb59b34a4fb1ebd71994b131c115dfb Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Sat, 8 Jun 2024 13:46:51 +0900 Subject: [PATCH 2/4] Fix bug of Time column --- OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs index 9d478c5c9..53e831995 100644 --- a/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs @@ -98,6 +98,7 @@ protected int OnExecute(CommandLineApplication app) { var keys = motionSource.Motion.FCurveKeys; var keyValues = motionSource.Motion.KeyValues; + var keyTimes = motionSource.Motion.KeyTimes; var perMotionList = new List(); @@ -161,7 +162,7 @@ PerMotion AllocatePerMotion(float time) var key = keys[baseIdx + idx]; setter( - AllocatePerMotion(keyValues[key.Time]), + AllocatePerMotion(keyTimes[key.Time]), keyValues[key.ValueId] ); } From f067a603bfec33c1abd3eb1e33608624196efa98 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Tue, 31 Dec 2024 19:54:51 +0900 Subject: [PATCH 3/4] Changes WIP: - Make some reference types to nullable due to comformance with `enable`. - Adding `--use-gltf` option WIP. It is said that the representation of rotation of glLF is quaternion. It is not an Euler angle. So glTF may not help. - Adding `dump-srt` command for inspection purpose, for anb/mset. --- .../Commands/AnbCommand.cs | 18 ++-- .../Commands/AnbExCommand.cs | 96 +++++++++++++++---- .../Commands/DumpSrtCommand.cs | 6 +- .../Interfaces/IFbxSourceItemSelector.cs | 6 +- .../Commands/Interfaces/IMsetInjector.cs | 2 +- .../OpenKh.Command.AnbMaker.csproj | 1 + .../Utils/AssimpAnimSource/UseAssimp.cs | 6 +- OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs | 2 +- .../Utils/Builder/Models/BasicSourceMotion.cs | 2 +- .../Utils/GltfAnimSource/UseGltf.cs | 63 ++++++++++++ .../Utils/JsonAnimSource/UseJson.cs | 11 ++- 11 files changed, 171 insertions(+), 42 deletions(-) create mode 100644 OpenKh.Command.AnbMaker/Utils/GltfAnimSource/UseGltf.cs diff --git a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs index 01a20a44c..a739e8645 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs @@ -21,16 +21,16 @@ internal class AnbCommand : IFbxSourceItemSelector, IMsetInjector [Required] [FileExists] [Argument(0, Description = "fbx input")] - public string InputModel { get; set; } + public string InputModel { get; set; } = null!; [Argument(1, Description = "anb output")] - public string Output { get; set; } + public string? Output { get; set; } [Option(Description = "specify root armature node name", ShortName = "r")] - public string RootName { get; set; } + public string? RootName { get; set; } [Option(Description = "specify mesh name to read bone data", ShortName = "m")] - public string MeshName { get; set; } + public string? MeshName { get; set; } [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; @@ -39,10 +39,10 @@ internal class AnbCommand : IFbxSourceItemSelector, IMsetInjector public float PositionScaling { get; set; } = 1; [Option(Description = "specify animation name to read bone data", ShortName = "a")] - public string AnimationName { get; set; } + public string? AnimationName { get; set; } [Option(Description = "optionally inject new motion into mset directly", ShortName = "w")] - public string MsetFile { get; set; } + public string? MsetFile { get; set; } [Option(Description = "zero based target index of bar entry in mset file", ShortName = "i")] public int MsetIndex { get; set; } @@ -107,9 +107,9 @@ internal class UseAssimpForRaw public UseAssimpForRaw( string inputModel, - string meshName, - string rootName, - string animationName, + string? meshName, + string? rootName, + string? animationName, float nodeScaling, float positionScaling ) diff --git a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs index 71ee4db28..dd7f1fa3a 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs @@ -5,6 +5,7 @@ using OpenKh.Command.AnbMaker.Utils.AssimpAnimSource; using OpenKh.Command.AnbMaker.Utils.Builder; using OpenKh.Command.AnbMaker.Utils.Builder.Models; +using OpenKh.Command.AnbMaker.Utils.GltfAnimSource; using OpenKh.Command.AnbMaker.Utils.JsonAnimSource; using OpenKh.Kh2; using System.ComponentModel.DataAnnotations; @@ -18,20 +19,19 @@ internal class AnbExCommand : IFbxSourceItemSelector, IMsetInjector [Required] [FileExists] [Argument(0, Description = "fbx input")] - public string InputModel { get; set; } + public string InputModel { get; set; } = null!; [Argument(1, Description = "anb output")] - public string Output { get; set; } - public string OutputMset { get; set; } + public string? OutputMset { get; set; } [Option(Description = "specify root armature node name", ShortName = "r")] - public string RootName { get; set; } + public string? RootName { get; set; } [Option(Description = "specify mesh name to read bone data", ShortName = "m")] - public string MeshName { get; set; } + public string? MeshName { get; set; } [Option(Description = "specify animation name to read bone data", ShortName = "a")] - public string AnimationName { get; set; } + public string? AnimationName { get; set; } [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; @@ -40,23 +40,30 @@ internal class AnbExCommand : IFbxSourceItemSelector, IMsetInjector public float PositionScaling { get; set; } = 1; [Option(Description = "optionally inject new motion into mset directly", ShortName = "w")] - public string MsetFile { get; set; } + public string? MsetFile { get; set; } [Option(Description = "zero based target index of bar entry in mset file", ShortName = "i")] public int MsetIndex { get; set; } + [Option(Description = "prefer UseJson as BasicSourceMotion", LongName = "use-json", ShortName = "j")] + public bool UseJson { get; set; } + + [Option(Description = "prefer UseGltf as BasicSourceMotion", LongName = "use-gltf", ShortName = "g")] + public bool UseGltf { get; set; } + + [Option(Description = "prefer UseAssimp as BasicSourceMotion", LongName = "use-assimp", ShortName = "s")] + public bool UseAssimp { get; set; } + protected int OnExecute(CommandLineApplication app) { var logger = LogManager.GetLogger("InterpolatedMotionMaker"); - Output = Path.GetFullPath(Output ?? Path.GetFileNameWithoutExtension(InputModel) + ".anb"); - OutputMset = Path.GetFullPath(OutputMset ?? Path.GetFileNameWithoutExtension(InputModel) + ".mset"); - Console.WriteLine($"Writing to: {Output}"); - IEnumerable parms; - if (Path.GetExtension(InputModel).ToLowerInvariant() == ".json") + OutputMset = Path.GetFullPath(OutputMset ?? Path.GetFileNameWithoutExtension(InputModel) + ".mset"); + IEnumerable LoadByUseJson() { - parms = new UseJson( + logger.Debug("UseJson as BasicSourceMotion"); + return new UseJson( inputModel: InputModel, meshName: MeshName, rootName: RootName, @@ -65,9 +72,11 @@ protected int OnExecute(CommandLineApplication app) ) .Parameters; } - else + + IEnumerable LoadByUseAssimp() { - parms = new UseAssimp( + logger.Debug("UseAssimp as BasicSourceMotion"); + return new UseAssimp( inputModel: InputModel, meshName: MeshName, rootName: RootName, @@ -78,8 +87,59 @@ protected int OnExecute(CommandLineApplication app) .Parameters; } + IEnumerable LoadByUseGltf() + { + logger.Debug("UseGltf as BasicSourceMotion"); + return new UseGltf().Load( + inputModel: InputModel, + meshName: MeshName, + rootName: RootName, + animationName: AnimationName, + nodeScaling: 1 + ); + } + + var fileExtension = Path.GetExtension(InputModel).ToLowerInvariant(); + + if (false) + { } + else if (UseAssimp) + { + parms = LoadByUseAssimp(); + } + else if (UseJson) + { + parms = LoadByUseJson(); + } + else if (UseGltf) + { + parms = LoadByUseGltf(); + } + else if (fileExtension == ".json") + { + parms = LoadByUseJson(); + } + else + { + parms = LoadByUseAssimp(); + } + + OutputMset = Path.GetFullPath(OutputMset ?? Path.GetFileNameWithoutExtension(InputModel) + ".anb"); + + logger.Info($"Writing to: {0}", OutputMset); + foreach (var parm in parms.Take(1)) { + logger.Debug("Printing summary of BasicSourceMotion"); + + logger.Debug($"DurationInTicks = {parm.DurationInTicks}"); + logger.Debug($"TicksPerSecond = {parm.TicksPerSecond}"); + logger.Debug($"BoneCount = {parm.BoneCount}"); + logger.Debug($"NodeScaling = {parm.NodeScaling}"); + logger.Debug($"PositionScaling = {parm.PositionScaling}"); + + logger.Debug("Invoking InterpolatedMotionBuilder"); + var builder = new InterpolatedMotionBuilder(parm); var ipm = builder.Ipm; @@ -135,11 +195,11 @@ protected int OnExecute(CommandLineApplication app) } ); - File.WriteAllBytes(Output, anbBarStream.ToArray()); - File.WriteAllBytes(Output + ".raw", motionStream.ToArray()); + File.WriteAllBytes(OutputMset, anbBarStream.ToArray()); + File.WriteAllBytes(OutputMset + ".raw", motionStream.ToArray()); File.WriteAllBytes(OutputMset, msetBarStream.ToArray()); - logger.Debug($"Motion data generation successful"); + logger.Info($"Motion data generation successful"); new MsetInjector().InjectMotionTo(this, motionStream.ToArray()); } diff --git a/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs index 53e831995..86b5c45ae 100644 --- a/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/DumpSrtCommand.cs @@ -124,9 +124,9 @@ protected int OnExecute(CommandLineApplication app) Channel.SCALE_X => (it, value) => it.ScaleX = value, Channel.SCALE_Y => (it, value) => it.ScaleY = value, Channel.SCALE_Z => (it, value) => it.ScaleZ = value, - Channel.ROTATATION_X => (it, value) => it.RotationX = consumeRadians(value), - Channel.ROTATATION_Y => (it, value) => it.RotationY = consumeRadians(value), - Channel.ROTATATION_Z => (it, value) => it.RotationZ = consumeRadians(value), + Channel.ROTATION_X => (it, value) => it.RotationX = consumeRadians(value), + Channel.ROTATION_Y => (it, value) => it.RotationY = consumeRadians(value), + Channel.ROTATION_Z => (it, value) => it.RotationZ = consumeRadians(value), Channel.TRANSLATION_X => (it, value) => it.TranslationX = value, Channel.TRANSLATION_Y => (it, value) => it.TranslationY = value, Channel.TRANSLATION_Z => (it, value) => it.TranslationZ = value, diff --git a/OpenKh.Command.AnbMaker/Commands/Interfaces/IFbxSourceItemSelector.cs b/OpenKh.Command.AnbMaker/Commands/Interfaces/IFbxSourceItemSelector.cs index 7c715aaf3..db517afa3 100644 --- a/OpenKh.Command.AnbMaker/Commands/Interfaces/IFbxSourceItemSelector.cs +++ b/OpenKh.Command.AnbMaker/Commands/Interfaces/IFbxSourceItemSelector.cs @@ -2,8 +2,8 @@ namespace OpenKh.Command.AnbMaker.Commands.Interfaces { internal interface IFbxSourceItemSelector { - string RootName { get; } - string MeshName { get; } - string AnimationName { get; } + string? RootName { get; } + string? MeshName { get; } + string? AnimationName { get; } } } diff --git a/OpenKh.Command.AnbMaker/Commands/Interfaces/IMsetInjector.cs b/OpenKh.Command.AnbMaker/Commands/Interfaces/IMsetInjector.cs index f4e8cc19e..d301597bd 100644 --- a/OpenKh.Command.AnbMaker/Commands/Interfaces/IMsetInjector.cs +++ b/OpenKh.Command.AnbMaker/Commands/Interfaces/IMsetInjector.cs @@ -2,7 +2,7 @@ namespace OpenKh.Command.AnbMaker.Commands.Interfaces { internal interface IMsetInjector { - string MsetFile { get; } + string? MsetFile { get; } int MsetIndex { get; } } } diff --git a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj index 3a417b24a..602faea6b 100644 --- a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj +++ b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj @@ -14,6 +14,7 @@ + diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs index 1cdb1ab02..b29ca4b7e 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs @@ -10,9 +10,9 @@ public class UseAssimp public UseAssimp( string inputModel, - string meshName, - string rootName, - string animationName, + string? meshName, + string? rootName, + string? animationName, float nodeScaling, float positionScaling ) diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs index 3791b0ad2..893e8892a 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs @@ -40,7 +40,7 @@ public static NodeRef[] FlattenNodes(Node topNode, Mesh mesh) return list.ToArray(); } - public static Node FindRootBone(Node rootNode, string rootName) + public static Node FindRootBone(Node rootNode, string? rootName) { rootName = rootName ?? "bone000"; diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs index e72970870..6a603fb6f 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs @@ -7,7 +7,7 @@ public class BasicSourceMotion public int BoneCount { get; set; } public float NodeScaling { get; set; } public float PositionScaling { get; set; } - public Func GetAChannel { get; set; } + public Func GetAChannel { get; set; } public ABone[] Bones { get; set; } public GetInitialMatrixDelegate GetInitialMatrix { get; set; } } diff --git a/OpenKh.Command.AnbMaker/Utils/GltfAnimSource/UseGltf.cs b/OpenKh.Command.AnbMaker/Utils/GltfAnimSource/UseGltf.cs new file mode 100644 index 000000000..948d4226d --- /dev/null +++ b/OpenKh.Command.AnbMaker/Utils/GltfAnimSource/UseGltf.cs @@ -0,0 +1,63 @@ +using NLog; +using OpenKh.Command.AnbMaker.Utils.Builder.Models; +using SharpGLTF.Schema2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Utils.GltfAnimSource +{ + public class UseGltf + { + public IEnumerable Load( + string inputModel, + string? meshName, + string? rootName, + string? animationName, + float nodeScaling) + { + var logger = LogManager.GetCurrentClassLogger(); + + logger.Warn("Gltf model importer is still in experimental stage."); + + var glb = ModelRoot.Load(inputModel); + + if (glb.LogicalNodes.FirstOrDefault(it => it.Name == rootName) is Node glbRootNode && glbRootNode is Node glbArmatureRoot) + { + // found + } + else + { + throw new Exception($"LogicalNode `{rootName}` not found. Choose one of {string.Join(", ", glb.LogicalNodes.Select(one => $"`{one.Name}`"))}."); + } + + if (glb.LogicalAnimations.FirstOrDefault(it => it.Name == animationName) is Animation glbAnimation) + { + // found + } + else + { + throw new Exception($"LogicalAnimation `{animationName}` not found. Choose one of {string.Join(", ", glb.LogicalAnimations.Select(one => $"`{one.Name}`"))}."); + } + + if (glb.LogicalMeshes.FirstOrDefault(it => it.Name == meshName) is Mesh glbMesh) + { + // found + } + else + { + throw new Exception($"LogicalMesh `{meshName}` not found. Choose one of {string.Join(", ", glb.LogicalMeshes.Select(one => $"`{one.Name}`"))}."); + } + + yield return new BasicSourceMotion + { + BoneCount = glbRootNode.VisualChildren.Count(), + DurationInTicks = (int)(30 * glbAnimation.Duration), + TicksPerSecond = 30, + //TODO + }; + } + } +} diff --git a/OpenKh.Command.AnbMaker/Utils/JsonAnimSource/UseJson.cs b/OpenKh.Command.AnbMaker/Utils/JsonAnimSource/UseJson.cs index 1c67b7906..6fa5314f8 100644 --- a/OpenKh.Command.AnbMaker/Utils/JsonAnimSource/UseJson.cs +++ b/OpenKh.Command.AnbMaker/Utils/JsonAnimSource/UseJson.cs @@ -12,9 +12,9 @@ public class UseJson public UseJson( string inputModel, - string meshName, - string rootName, - string animationName, + string? meshName, + string? rootName, + string? animationName, float nodeScaling ) { @@ -30,6 +30,11 @@ float nodeScaling } ); + if (model == null) + { + throw new NullReferenceException("model"); + } + if (model.Version != "1") { throw new Exception("Invalid JSON source version"); From d96a3abf4f334ee16b3b6b132f8393b7f7c3fb64 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Thu, 2 Jan 2025 20:54:02 +0900 Subject: [PATCH 4/4] Adding `dump-mdlx-srt` command for debug something. --- .../Commands/DumpMdlxSrtCommand.cs | 97 +++++++++++++++++++ OpenKh.Command.AnbMaker/Program.cs | 1 + 2 files changed, 98 insertions(+) create mode 100644 OpenKh.Command.AnbMaker/Commands/DumpMdlxSrtCommand.cs diff --git a/OpenKh.Command.AnbMaker/Commands/DumpMdlxSrtCommand.cs b/OpenKh.Command.AnbMaker/Commands/DumpMdlxSrtCommand.cs new file mode 100644 index 000000000..f7aa92c59 --- /dev/null +++ b/OpenKh.Command.AnbMaker/Commands/DumpMdlxSrtCommand.cs @@ -0,0 +1,97 @@ +using libcsv; +using McMaster.Extensions.CommandLineUtils; +using OpenKh.Kh2; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Commands +{ + [HelpOption] + [Command(Description = "mdlx file: dump scale rotation translation to csv")] + internal class DumpMdlxSrtCommand + { + [Required] + [FileExists] + [Argument(0, Description = "mdlx input")] + public string InputModel { get; set; } = null!; + + [Argument(1, Description = "csv output")] + public string? OutputCsv { get; set; } + + [Option(Description = "use radians instead of degrees", ShortName = "r", LongName = "use-radians")] + public bool UseRadians { get; set; } + + protected int OnExecute(CommandLineApplication app) + { + OutputCsv = Path.GetFullPath(OutputCsv ?? Path.GetFileNameWithoutExtension(InputModel) + ".csv"); + + Console.WriteLine($"Writing to: {OutputCsv}"); + + using Stream mdlxFile = File.OpenRead(InputModel); + + var bar = Bar.Read(mdlxFile); + + var modelEntry = bar.SingleOrDefault(it => it.Type == Bar.EntryType.Model) + ?? throw new Exception("Model entry not found"); + + var mdlx = Mdlx.Read(modelEntry.Stream); + if (mdlx.Model is ModelSkeleton modelSkeleton) + { + var subModel = mdlx.SubModels.SingleOrDefault(it => it.Type == 3) + ?? throw new Exception("SubModel entry not found"); + + using var writer = new StreamWriter(OutputCsv, false, new UTF8Encoding(true)); + var csv = new Csvw(writer, ',', '"'); + + string FormatScale(float value) => value.ToString(); + + string FormatRotation(float value) => + UseRadians + ? value.ToString() + : (value * 180 / MathF.PI).ToString(); + + string FormatTranslation(float value) => value.ToString(); + + { + csv.Write("BoneIndex"); + csv.Write("BoneParent"); + csv.Write("Scale X"); + csv.Write("Scale Y"); + csv.Write("Scale Z"); + csv.Write("Rotation X"); + csv.Write("Rotation Y"); + csv.Write("Rotation Z"); + csv.Write("Translation X"); + csv.Write("Translation Y"); + csv.Write("Translation Z"); + } + + foreach (var bone in subModel.Bones) + { + csv.NextRow(); + csv.Write(bone.Index.ToString()); + csv.Write(bone.Parent.ToString()); + csv.Write(FormatScale(bone.ScaleX)); + csv.Write(FormatScale(bone.ScaleY)); + csv.Write(FormatScale(bone.ScaleZ)); + csv.Write(FormatRotation(bone.RotationX)); + csv.Write(FormatRotation(bone.RotationY)); + csv.Write(FormatRotation(bone.RotationZ)); + csv.Write(FormatTranslation(bone.TranslationX)); + csv.Write(FormatTranslation(bone.TranslationY)); + csv.Write(FormatTranslation(bone.TranslationZ)); + } + } + else + { + throw new Exception($"Model type {mdlx.Model?.GetType()?.FullName} is not a skeleton"); + } + + return 0; + } + } +} diff --git a/OpenKh.Command.AnbMaker/Program.cs b/OpenKh.Command.AnbMaker/Program.cs index 15c1a9339..49a9c96ad 100644 --- a/OpenKh.Command.AnbMaker/Program.cs +++ b/OpenKh.Command.AnbMaker/Program.cs @@ -13,6 +13,7 @@ namespace OpenKh.Command.AnbMaker [Subcommand(typeof(DumpNodeTreeCommand))] [Subcommand(typeof(DumpSrtCommand))] [Subcommand(typeof(RenderNodeTreeCommand))] + [Subcommand(typeof(DumpMdlxSrtCommand))] internal class Program { private static string GetVersion()