From f697026530522e248870c0107712dbb88cdd0320 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Wed, 18 Jan 2023 20:45:20 +0900 Subject: [PATCH 1/4] Mitigate chibi sora problem --- .../Commands/AnbCommand.cs | 76 +++++++-- .../Commands/AnbExCommand.cs | 8 +- .../Commands/DumpNodeTreeCommand.cs | 127 ++++++++++++++ .../Commands/ExportArmatureCommand.cs | 39 +++++ .../Commands/RenderNodeTreeCommand.cs | 160 ++++++++++++++++++ .../Extensions/AssimpExtensions.cs | 8 +- .../OpenKh.Command.AnbMaker.csproj | 3 +- OpenKh.Command.AnbMaker/Program.cs | 2 + .../Utils/AssimpAnimSource/UseAssimp.cs | 6 +- OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs | 9 +- .../Utils/AssimpSupplemental/NodeRef.cs | 4 +- .../Builder/InterpolatedMotionBuilder.cs | 2 +- .../Utils/Builder/Models/BasicSourceMotion.cs | 1 + 13 files changed, 417 insertions(+), 28 deletions(-) create mode 100644 OpenKh.Command.AnbMaker/Commands/DumpNodeTreeCommand.cs create mode 100644 OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs create mode 100644 OpenKh.Command.AnbMaker/Commands/RenderNodeTreeCommand.cs diff --git a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs index ca1f1d7ba..45005e8be 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs @@ -1,5 +1,6 @@ using Assimp; using McMaster.Extensions.CommandLineUtils; +using McMaster.Extensions.CommandLineUtils.Conventions; using NLog; using OpenKh.Command.AnbMaker.Commands.Interfaces; using OpenKh.Command.AnbMaker.Commands.Utils; @@ -14,6 +15,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Transactions; using static OpenKh.Command.AnbMaker.Utils.Builder.RawMotionBuilder; using static OpenKh.Kh2.Motion; @@ -37,9 +39,12 @@ internal class AnbCommand : IFbxSourceItemSelector, IMsetInjector [Option(Description = "specify mesh name to read bone data", ShortName = "m")] public string MeshName { get; set; } - [Option(Description = "apply scaling to each source node", ShortName = "x")] + [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; + [Option(Description = "apply scaling to bone position", ShortName = "p", LongName = "position-scaling")] + public float PositionScaling { get; set; } = 1; + [Option(Description = "specify animation name to read bone data", ShortName = "a")] public string AnimationName { get; set; } @@ -62,7 +67,8 @@ protected int OnExecute(CommandLineApplication app) MeshName, RootName, AnimationName, - NodeScaling + NodeScaling, + PositionScaling ) .Parameters; @@ -111,10 +117,11 @@ public UseAssimpForRaw( string meshName, string rootName, string animationName, - float nodeScaling + float nodeScaling, + float positionScaling ) { - var assimp = new Assimp.AssimpContext(); + using var assimp = new Assimp.AssimpContext(); var scene = assimp.ImportFile(inputModel, Assimp.PostProcessSteps.None); var outputList = new List(); @@ -129,10 +136,33 @@ bool IsAnimationNameMatched(string it) => ? true : it == animationName; - foreach (var fbxMesh in scene.Meshes.Where(mesh => IsMeshNameMatched(mesh.Name))) + var hits = new List<(Mesh mesh, Matrix4x4 transform)>(); + + void WalkNode(Assimp.Node node, Matrix4x4 parentTransform) + { + var transform = node.Transform * parentTransform; + + foreach (var childNode in node.Children) + { + WalkNode(childNode, transform); + } + + foreach (var meshIdx in node.MeshIndices) + { + var fbxMesh = scene.Meshes[meshIdx]; + if (IsMeshNameMatched(fbxMesh.Name)) + { + hits.Add((fbxMesh, transform)); + } + } + } + + WalkNode(scene.RootNode, Matrix4x4.Identity); + + foreach (var hit in hits.Take(1)) { var fbxArmatureRoot = scene.RootNode.FindNode(rootName ?? "bone000"); //"kh_sk" - var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot); + var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot, hit.mesh); var fbxArmatureBoneCount = fbxArmatureNodes.Length; foreach (var fbxAnim in scene.Animations.Where(anim => IsAnimationNameMatched(anim.Name))) @@ -156,27 +186,45 @@ bool IsAnimationNameMatched(string it) => GetRelativeMatrix = (frameIdx, boneIdx) => { - var name = fbxArmatureNodes[boneIdx].ArmatureNode.Name; + var nodeRef = fbxArmatureNodes[boneIdx]; + var armatureNode = nodeRef.ArmatureNode; + var name = armatureNode.Name; + + var oldTransform = armatureNode.Transform; + + oldTransform.Decompose( + out Vector3D initialScaling, + out Quaternion initialQuaternion, + out Vector3D initialTranslation + ); var hit = fbxAnim.NodeAnimationChannels.FirstOrDefault(it => it.NodeName == name); var translation = (hit == null) - ? new Vector3D(0, 0, 0) + ? initialTranslation : hit.PositionKeys.GetInterpolatedVector(frameIdx); var rotation = (hit == null) - ? new Assimp.Quaternion(w: 1, 0, 0, 0) + ? initialQuaternion : hit.RotationKeys.GetInterpolatedQuaternion(frameIdx); var scale = (hit == null) - ? new Vector3D(1, 1, 1) + ? initialScaling : hit.ScalingKeys.GetInterpolatedVector(frameIdx); - return System.Numerics.Matrix4x4.Identity - * System.Numerics.Matrix4x4.CreateFromQuaternion(rotation.ToDotNetQuaternion()) - * System.Numerics.Matrix4x4.CreateScale(scale.ToDotNetVector3()) - * System.Numerics.Matrix4x4.CreateTranslation(translation.ToDotNetVector3()) + Vector3D FixScaling(Vector3D value) => + value; + + Vector3D FixPos(Vector3D value) => + value * positionScaling; + + var newTransform = Matrix4x4.Identity + * new Matrix4x4(rotation.GetMatrix()) + * Matrix4x4.FromScaling(FixScaling(scale)) + * Matrix4x4.FromTranslation(FixPos(translation)) ; + + return newTransform.ToDotNetMatrix4x4(); } } ); diff --git a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs index 69cf91ee4..9d5b51266 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs @@ -39,9 +39,12 @@ internal class AnbExCommand : IFbxSourceItemSelector, IMsetInjector [Option(Description = "specify animation name to read bone data", ShortName = "a")] public string AnimationName { get; set; } - [Option(Description = "apply scaling to each source node", ShortName = "x")] + [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; + [Option(Description = "apply scaling to bone position", ShortName = "p", LongName = "position-scaling")] + public float PositionScaling { get; set; } = 1; + [Option(Description = "optionally inject new motion into mset directly", ShortName = "w")] public string MsetFile { get; set; } @@ -75,7 +78,8 @@ protected int OnExecute(CommandLineApplication app) meshName: MeshName, rootName: RootName, animationName: AnimationName, - nodeScaling: NodeScaling + nodeScaling: NodeScaling, + positionScaling: PositionScaling ) .Parameters; } diff --git a/OpenKh.Command.AnbMaker/Commands/DumpNodeTreeCommand.cs b/OpenKh.Command.AnbMaker/Commands/DumpNodeTreeCommand.cs new file mode 100644 index 000000000..b8cdedbb2 --- /dev/null +++ b/OpenKh.Command.AnbMaker/Commands/DumpNodeTreeCommand.cs @@ -0,0 +1,127 @@ +using McMaster.Extensions.CommandLineUtils; +using OpenKh.Command.AnbMaker.Extensions; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Commands +{ + [HelpOption] + [Command(Description = "fbx file: dump node tree")] + internal class DumpNodeTreeCommand + { + [Required] + [FileExists] + [Argument(0, Description = "fbx input")] + public string? InputModel { get; set; } + + [Option(Description = "print decomposed scale-rotation-translation", ShortName = "d", LongName = "decompose")] + public bool Decompose { get; set; } + + [Option(Description = "print transform matrix", ShortName = "m", LongName = "matrix")] + public bool Matrix { get; set; } + + [Option(Description = "print absolute", ShortName = "a", LongName = "absolute")] + public bool Absolute { get; set; } + + protected int OnExecute(CommandLineApplication app) + { + var assimp = new Assimp.AssimpContext(); + var scene = assimp.ImportFile(InputModel, Assimp.PostProcessSteps.None); + + void Walk(Assimp.Node node, Matrix4x4 parent, int depth) + { + var more = ""; + + var thisMatrix = node.Transform.ToDotNetMatrix4x4() * parent; + + if (Decompose) + { + System.Numerics.Matrix4x4.Decompose( + thisMatrix, + out Vector3 scale, + out System.Numerics.Quaternion rotation, + out Vector3 translation + ); + + more += $" S{scale} R{(ToDegree(rotation.ToEulerAngles()))} T{translation}"; + } + + if (Matrix) + { + more += thisMatrix; + } + + Console.WriteLine(new string(' ', depth) + "+ " + node.Name + more); + + if (node.HasMeshes) + { + foreach (var meshIdx in node.MeshIndices) + { + var mesh = scene.Meshes[meshIdx]; + Console.WriteLine(new string(' ', depth + 1) + "$ " + mesh.Name); + + if (mesh.HasBones) + { + foreach (var bone in mesh.Bones) + { + var boneMore = ""; + + var boneMatrix = bone.OffsetMatrix.ToDotNetMatrix4x4(); + + if (Decompose) + { + System.Numerics.Matrix4x4.Decompose( + boneMatrix, + out Vector3 scale, + out System.Numerics.Quaternion rotation, + out Vector3 translation + ); + + boneMore += $" S{scale} R{(ToDegree(rotation.ToEulerAngles()))} T{translation}"; + } + + if (Matrix) + { + boneMore += " " + boneMatrix; + } + + Console.WriteLine(new string(' ', depth + 1) + " + " + bone.Name + boneMore); + } + } + } + } + + if (node.HasChildren) + { + foreach (var child in node.Children) + { + Walk( + child, + Absolute ? thisMatrix : Matrix4x4.Identity, + depth + 1 + ); + } + } + } + + Walk(scene.RootNode, Matrix4x4.Identity, 0); + + return 0; + } + + private static Vector3 ToDegree(Vector3 vec) + { + var f = (float)(1.0f / Math.PI * 180); + return new Vector3( + (int)(vec.X * f), + (int)(vec.Y * f), + (int)(vec.Z * f) + ); + } + } +} diff --git a/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs b/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs new file mode 100644 index 000000000..d148e08bc --- /dev/null +++ b/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs @@ -0,0 +1,39 @@ +using McMaster.Extensions.CommandLineUtils; +using OpenKh.Command.AnbMaker.Extensions; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Commands +{ + [HelpOption] + [Command(Description = "fbx file: fbx to skeleton dae")] + internal class ExportArmatureCommand + { + [Required] + [FileExists] + [Argument(0, Description = "fbx input")] + public string InputModel { get; set; } + + [Argument(1, Description = "dae output")] + public string Output { get; set; } + + protected int OnExecute(CommandLineApplication app) + { + var assimp = new Assimp.AssimpContext(); + var scene = assimp.ImportFile(InputModel, Assimp.PostProcessSteps.None); + + void Walk(Assimp.Node node, int depth) + { + } + + Walk(scene.RootNode, 0); + + return 0; + } + } +} diff --git a/OpenKh.Command.AnbMaker/Commands/RenderNodeTreeCommand.cs b/OpenKh.Command.AnbMaker/Commands/RenderNodeTreeCommand.cs new file mode 100644 index 000000000..c460856c4 --- /dev/null +++ b/OpenKh.Command.AnbMaker/Commands/RenderNodeTreeCommand.cs @@ -0,0 +1,160 @@ +using McMaster.Extensions.CommandLineUtils; +using OpenKh.Command.AnbMaker.Extensions; +using OpenKh.Common; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Commands +{ + [HelpOption] + [Command(Description = "fbx file: render node tree to bitmaps")] + internal class RenderNodeTreeCommand + { + [Required] + [FileExists] + [Argument(0, Description = "fbx input")] + public string? InputModel { get; set; } + + private class TreeRenderer : IDisposable + { + private readonly SKBitmap _bitmap; + private readonly SKCanvas _canvas; + private readonly SKPaint _pen; + + public TreeRenderer() + { + _bitmap = new SKBitmap(1000, 1000); + _canvas = new SKCanvas(_bitmap); + + _canvas.Clear(SKColors.White); + + _pen = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Blue, + StrokeWidth = 1, + StrokeCap = SKStrokeCap.Round, + }; + } + + public void Dispose() + { + _pen.Dispose(); + _canvas.Dispose(); + _bitmap.Dispose(); + } + + public void DrawParentToChild(SKPoint from, SKPoint to) + { + _canvas.DrawLine(from, to, _pen); + } + + public void SaveToPng(Stream stream) + { + _bitmap.Encode(SKEncodedImageFormat.Png, 100) + .SaveTo(stream); + } + } + + protected int OnExecute(CommandLineApplication app) + { + using var assimp = new Assimp.AssimpContext(); + var scene = assimp.ImportFile(InputModel, Assimp.PostProcessSteps.None); + + using var worldTreeRenderer = new TreeRenderer(); + using var bonesTreeRenderer = new TreeRenderer(); + + var boneTreeWritten = false; + + SKPoint GetPoint(Vector3 vec) => + new SKPoint( + 500 + vec.X, + 500 - vec.Y + ); + + void Walk(Assimp.Node node, Matrix4x4 parent, int depth) + { + var thisMatrix = node.Transform.ToDotNetMatrix4x4() * parent; + + { + Matrix4x4.Decompose( + thisMatrix, + out Vector3 thisScale, + out Quaternion thisRotation, + out Vector3 thisTranslation + ); + + Matrix4x4.Decompose( + parent, + out Vector3 parentScale, + out Quaternion parentRotation, + out Vector3 parentTranslation + ); + + worldTreeRenderer.DrawParentToChild(GetPoint(parentTranslation), GetPoint(thisTranslation)); + } + + if (node.HasChildren) + { + foreach (var child in node.Children) + { + Walk( + child, + thisMatrix, + depth + 1 + ); + } + } + + if (node.HasMeshes) + { + foreach (var meshIdx in node.MeshIndices) + { + var mesh = scene.Meshes[meshIdx]; + + if (!boneTreeWritten) + { + foreach (var bone in mesh.Bones) + { + Matrix4x4.Decompose( + bone.OffsetMatrix.ToDotNetMatrix4x4(), + out Vector3 thisScale, + out Quaternion thisRotation, + out Vector3 thisTranslation + ); + + bonesTreeRenderer.DrawParentToChild( + GetPoint(Vector3.Zero), + GetPoint(thisTranslation * 100) + ); + } + + boneTreeWritten = true; + } + } + } + } + + Walk(scene.RootNode, Matrix4x4.Identity, 0); + + Console.WriteLine("Saving something to world.png"); + File.Create("world.png").Using( + stream => worldTreeRenderer.SaveToPng(stream) + ); + + Console.WriteLine("Saving something to bones.png"); + File.Create("bones.png").Using( + stream => bonesTreeRenderer.SaveToPng(stream) + ); + + return 0; + } + + } +} diff --git a/OpenKh.Command.AnbMaker/Extensions/AssimpExtensions.cs b/OpenKh.Command.AnbMaker/Extensions/AssimpExtensions.cs index 6b8709d07..dcdc205e0 100644 --- a/OpenKh.Command.AnbMaker/Extensions/AssimpExtensions.cs +++ b/OpenKh.Command.AnbMaker/Extensions/AssimpExtensions.cs @@ -90,10 +90,10 @@ public static Vector3D GetInterpolatedVector(this List keys, double t public static System.Numerics.Matrix4x4 ToDotNetMatrix4x4(this Assimp.Matrix4x4 m) => new System.Numerics.Matrix4x4( - m.A1, m.A2, m.A3, m.A4, - m.B1, m.B2, m.B3, m.B4, - m.C1, m.C2, m.C3, m.C4, - m.D1, m.D2, m.D3, m.D4 + m.A1, m.B1, m.C1, m.D1, + m.A2, m.B2, m.C2, m.D2, + m.A3, m.B3, m.C3, m.D3, + m.A4, m.B4, m.C4, m.D4 ); } } diff --git a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj index 4d1e6a456..e4525463e 100644 --- a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj +++ b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj @@ -1,4 +1,4 @@ - + Exe @@ -12,6 +12,7 @@ + diff --git a/OpenKh.Command.AnbMaker/Program.cs b/OpenKh.Command.AnbMaker/Program.cs index 16ec05186..af30afa8b 100644 --- a/OpenKh.Command.AnbMaker/Program.cs +++ b/OpenKh.Command.AnbMaker/Program.cs @@ -14,6 +14,8 @@ namespace OpenKh.Command.AnbMaker [Subcommand(typeof(AnbCommand))] [Subcommand(typeof(ExportRawCommand))] [Subcommand(typeof(AnbExCommand))] + [Subcommand(typeof(DumpNodeTreeCommand))] + [Subcommand(typeof(RenderNodeTreeCommand))] internal class Program { private static string GetVersion() diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs index 1296e791e..1bd1d2576 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs @@ -19,7 +19,8 @@ public UseAssimp( string meshName, string rootName, string animationName, - float nodeScaling + float nodeScaling, + float positionScaling ) { var outputList = new List(); @@ -41,7 +42,7 @@ bool IsAnimationNameMatched(string it) => foreach (var fbxMesh in scene.Meshes.Where(mesh => IsMeshNameMatched(mesh.Name))) { var fbxArmatureRoot = scene.RootNode.FindNode(rootName ?? "bone000"); //"kh_sk" - var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot); + var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot, fbxMesh); var fbxArmatureBoneCount = fbxArmatureNodes.Length; foreach (var fbxAnim in scene.Animations.Where(anim => IsAnimationNameMatched(anim.Name))) @@ -59,6 +60,7 @@ AScalarKey GetAScalarKey(double time, float value) => TicksPerSecond = (float)fbxAnim.TicksPerSecond, BoneCount = fbxArmatureBoneCount, NodeScaling = nodeScaling, + PositionScaling = positionScaling, GetAChannel = boneIdx => { var name = fbxArmatureNodes[boneIdx].ArmatureNode.Name; diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs index 741127354..f03566cf6 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs @@ -10,12 +10,15 @@ namespace OpenKh.Command.AnbMaker.Utils { internal class AssimpHelper { - public static NodeRef[] FlattenNodes(Node topNode) + public static NodeRef[] FlattenNodes(Node topNode, Mesh mesh) { + var boneDict = mesh.Bones + .ToDictionary(bone => bone.Name, bone => bone); + var list = new List(); var stack = new Stack(); - stack.Push(new NodeRef(-1, topNode)); + stack.Push(new NodeRef(-1, topNode, boneDict[topNode.Name])); while (stack.Any()) { @@ -25,7 +28,7 @@ public static NodeRef[] FlattenNodes(Node topNode) foreach (var sub in nodeRef.ArmatureNode.Children.Reverse()) { - stack.Push(new NodeRef(idx, sub)); + stack.Push(new NodeRef(idx, sub, boneDict[topNode.Name])); } } diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpSupplemental/NodeRef.cs b/OpenKh.Command.AnbMaker/Utils/AssimpSupplemental/NodeRef.cs index 83ea4646d..db4c1d60a 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpSupplemental/NodeRef.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpSupplemental/NodeRef.cs @@ -11,11 +11,13 @@ internal record NodeRef { public int ParentIndex { get; set; } public Node ArmatureNode { get; set; } + public Bone? MeshBone { get; set; } - public NodeRef(int parentIndex, Node armatureNode) + public NodeRef(int parentIndex, Node armatureNode, Bone? meshBone) { ParentIndex = parentIndex; ArmatureNode = armatureNode; + MeshBone = meshBone; } } } diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs index f2b7ae30f..d85bd2a0c 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs @@ -93,7 +93,7 @@ float FixScaling(float value) float FixPos(float value) { - return value / ((boneIdx == 0) ? 1 : parm.NodeScaling); + return value / ((boneIdx == 0) ? 1 : parm.NodeScaling) * parm.PositionScaling; } float FixPosValue(float value) => GetLowerPrecisionValue(FixPos(value)); diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs index 86bc9ad7d..2bf0eb0f8 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs @@ -12,6 +12,7 @@ public class BasicSourceMotion public float TicksPerSecond { get; set; } public int BoneCount { get; set; } public float NodeScaling { get; set; } + public float PositionScaling { get; set; } public Func GetAChannel { get; set; } public ABone[] Bones { get; set; } } From 2b06e104afa2eeb7ebf482dfbd89e098f9819602 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Thu, 19 Jan 2023 19:34:02 +0900 Subject: [PATCH 2/4] Add initial pose for all bones --- .../Commands/AnbCommand.cs | 2 +- .../Commands/AnbExCommand.cs | 2 +- .../Utils/AssimpAnimSource/UseAssimp.cs | 10 +- .../Utils/Builder/GetInitialMatrixDelegate.cs | 11 ++ .../Builder/InterpolatedMotionBuilder.cs | 101 ++++++++++++++---- .../Utils/Builder/Models/BasicSourceMotion.cs | 1 + 6 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 OpenKh.Command.AnbMaker/Utils/Builder/GetInitialMatrixDelegate.cs diff --git a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs index 45005e8be..472f11edb 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs @@ -42,7 +42,7 @@ internal class AnbCommand : IFbxSourceItemSelector, IMsetInjector [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; - [Option(Description = "apply scaling to bone position", ShortName = "p", LongName = "position-scaling")] + [Option(Description = "apply scaling to each bone position", ShortName = "p", LongName = "position-scaling")] public float PositionScaling { get; set; } = 1; [Option(Description = "specify animation name to read bone data", ShortName = "a")] diff --git a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs index 9d5b51266..848f62878 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbExCommand.cs @@ -42,7 +42,7 @@ internal class AnbExCommand : IFbxSourceItemSelector, IMsetInjector [Option(Description = "apply scaling to each source node", ShortName = "x", LongName = "node-scaling")] public float NodeScaling { get; set; } = 1; - [Option(Description = "apply scaling to bone position", ShortName = "p", LongName = "position-scaling")] + [Option(Description = "apply scaling to each bone position", ShortName = "p", LongName = "position-scaling")] public float PositionScaling { get; set; } = 1; [Option(Description = "optionally inject new motion into mset directly", ShortName = "w")] diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs index 1bd1d2576..98b98fbfc 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs @@ -1,3 +1,4 @@ +using Assimp; using OpenKh.Command.AnbMaker.Extensions; using OpenKh.Command.AnbMaker.Utils.Builder; using OpenKh.Command.AnbMaker.Utils.Builder.Models; @@ -7,6 +8,7 @@ using System.Numerics; using System.Text; using System.Threading.Tasks; +using System.Transactions; namespace OpenKh.Command.AnbMaker.Utils.AssimpAnimSource { @@ -63,7 +65,8 @@ AScalarKey GetAScalarKey(double time, float value) => PositionScaling = positionScaling, GetAChannel = boneIdx => { - var name = fbxArmatureNodes[boneIdx].ArmatureNode.Name; + var armatureNode = fbxArmatureNodes[boneIdx].ArmatureNode; + var name = armatureNode.Name; var hit = fbxAnim.NodeAnimationChannels.FirstOrDefault(it => it.NodeName == name); if (hit != null) { @@ -126,6 +129,11 @@ AScalarKey GetAScalarKey(double time, float value) => } ) .ToArray(), + GetInitialMatrix = (boneIdx) => + { + return fbxArmatureNodes[boneIdx].ArmatureNode.Transform + .ToDotNetMatrix4x4(); + }, }; outputList.Add(output); diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/GetInitialMatrixDelegate.cs b/OpenKh.Command.AnbMaker/Utils/Builder/GetInitialMatrixDelegate.cs new file mode 100644 index 000000000..3b7e0bafe --- /dev/null +++ b/OpenKh.Command.AnbMaker/Utils/Builder/GetInitialMatrixDelegate.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Command.AnbMaker.Utils.Builder +{ + public delegate Matrix4x4 GetInitialMatrixDelegate(int boneIdx); +} diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs index d85bd2a0c..e10690ec0 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs @@ -1,4 +1,5 @@ using NLog; +using OpenKh.Command.AnbMaker.Extensions; using OpenKh.Command.AnbMaker.Utils.Builder.Models; using System; using System.Collections.Generic; @@ -79,25 +80,29 @@ short AddKeyValue(float keyValue) return (short)Convert.ToUInt16(idx); } + var initialPoseDict = new SortedDictionary( + ComparerOfInitialPoseKey.Instance + ); + for (int boneIdx = 0; boneIdx < parm.BoneCount; boneIdx++) { - var hit = parm.GetAChannel(boneIdx); - if (hit != null) + float FixScaling(float value) { - float FixScaling(float value) - { - return ((boneIdx == 0) ? parm.NodeScaling : 1f) * value; - } + return ((boneIdx == 0) ? parm.NodeScaling : 1f) * value; + } - float FixScalingValue(float value) => GetLowerPrecisionValue(FixScaling(value)); + float FixScalingValue(float value) => GetLowerPrecisionValue(FixScaling(value)); - float FixPos(float value) - { - return value / ((boneIdx == 0) ? 1 : parm.NodeScaling) * parm.PositionScaling; - } + float FixPos(float value) + { + return value / ((boneIdx == 0) ? 1 : parm.NodeScaling) * parm.PositionScaling; + } - float FixPosValue(float value) => GetLowerPrecisionValue(FixPos(value)); + float FixPosValue(float value) => GetLowerPrecisionValue(FixPos(value)); + var hit = parm.GetAChannel(boneIdx); + if (hit != null) + { var channels = new ChannelProvider[] { new ChannelProvider @@ -204,6 +209,36 @@ float FixPos(float value) } } + { + Matrix4x4.Decompose( + parm.GetInitialMatrix(boneIdx), + out Vector3 scale, + out Quaternion quaternion, + out Vector3 translation + ); + + Vector3 rotation; + if (quaternion.IsIdentity) + { + rotation = Vector3.Zero; + } + else + { + rotation = quaternion.ToEulerAngles(); + } + + + initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_X)] = FixScalingValue(scale.X); + initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_Y)] = FixScalingValue(scale.Y); + initialPoseDict[new InitialPoseKey(boneIdx, Channel.SCALE_Z)] = FixScalingValue(scale.Z); + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_X)] = rotation.X; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_Y)] = rotation.Y; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.ROTATATION_Z)] = rotation.Z; + initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_X)] = FixPosValue(translation.X); + initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_Y)] = FixPosValue(translation.Y); + initialPoseDict[new InitialPoseKey(boneIdx, Channel.TRANSLATION_Z)] = FixPosValue(translation.Z); + } + Ipm.Joints.Add( new Joint { @@ -239,14 +274,7 @@ float FixPos(float value) { numSames++; - Ipm.InitialPoses.Add( - new InitialPose - { - BoneId = fCurve.JointId, - ChannelValue = fCurve.ChannelValue, - Value = Ipm.KeyValues[sames.Single()], - } - ); + initialPoseDict[new InitialPoseKey(fCurve.JointId, fCurve.ChannelValue)] = Ipm.KeyValues[sames.Single()]; Ipm.FCurvesForward.Remove(fCurve); } @@ -275,6 +303,18 @@ float FixPos(float value) } } + foreach (var set in initialPoseDict) + { + Ipm.InitialPoses.Add( + new InitialPose + { + BoneId = Convert.ToInt16(set.Key.BoneIndex), + ChannelValue = set.Key.Type, + Value = set.Value, + } + ); + } + if (numSames != 0) { logger.Debug($"{numSames:#,##0} channels have only single value. They are converted to InitialPoses."); @@ -323,5 +363,26 @@ private class ChannelProvider internal AScalarKey[] keys; internal Func fixValue; } + + private record InitialPoseKey(int BoneIndex, Channel Type); + + private class ComparerOfInitialPoseKey : IComparer + { + public static readonly ComparerOfInitialPoseKey Instance = new ComparerOfInitialPoseKey(); + + private static readonly InitialPoseKey _fallback = new InitialPoseKey(-1, Channel.UNKNOWN); + + public int Compare(InitialPoseKey? left, InitialPoseKey? right) + { + left = left ?? _fallback; + right = right ?? _fallback; + var diff = left.BoneIndex.CompareTo(right.BoneIndex); + if (diff == 0) + { + diff = left.Type.CompareTo(right.Type); + } + return diff; + } + } } } diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs index 2bf0eb0f8..9d437be48 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/Models/BasicSourceMotion.cs @@ -15,5 +15,6 @@ public class BasicSourceMotion public float PositionScaling { get; set; } public Func GetAChannel { get; set; } public ABone[] Bones { get; set; } + public GetInitialMatrixDelegate GetInitialMatrix { get; set; } } } From 31572155985181d10b97084d3ea2d21762706dfb Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Thu, 19 Jan 2023 19:50:22 +0900 Subject: [PATCH 3/4] Tweak bad result of root node search --- .../Commands/AnbCommand.cs | 2 +- .../Utils/AssimpAnimSource/UseAssimp.cs | 2 +- OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs | 31 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs index 472f11edb..078f1d4cf 100644 --- a/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs +++ b/OpenKh.Command.AnbMaker/Commands/AnbCommand.cs @@ -161,7 +161,7 @@ void WalkNode(Assimp.Node node, Matrix4x4 parentTransform) foreach (var hit in hits.Take(1)) { - var fbxArmatureRoot = scene.RootNode.FindNode(rootName ?? "bone000"); //"kh_sk" + var fbxArmatureRoot = AssimpHelper.FindRootBone(scene.RootNode, rootName); var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot, hit.mesh); var fbxArmatureBoneCount = fbxArmatureNodes.Length; diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs index 98b98fbfc..bd2615251 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpAnimSource/UseAssimp.cs @@ -43,7 +43,7 @@ bool IsAnimationNameMatched(string it) => foreach (var fbxMesh in scene.Meshes.Where(mesh => IsMeshNameMatched(mesh.Name))) { - var fbxArmatureRoot = scene.RootNode.FindNode(rootName ?? "bone000"); //"kh_sk" + var fbxArmatureRoot = AssimpHelper.FindRootBone(scene.RootNode, rootName); var fbxArmatureNodes = AssimpHelper.FlattenNodes(fbxArmatureRoot, fbxMesh); var fbxArmatureBoneCount = fbxArmatureNodes.Length; diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs index f03566cf6..ada4b9eb3 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs @@ -34,5 +34,36 @@ public static NodeRef[] FlattenNodes(Node topNode, Mesh mesh) return list.ToArray(); } + + public static Node FindRootBone(Node rootNode, string rootName) + { + rootName = rootName ?? "bone000"; + + var found = rootNode.FindNode(rootName); + if (found == null) + { + static string DumpTree(Node target) + { + var writer = new StringWriter(); + + void VisitNode(Node at, int depth) + { + writer.WriteLine($"{new string(' ', 2 * depth)}- {at.Name}"); + + foreach (var child in at.Children) + { + VisitNode(child, depth + 1); + } + } + + VisitNode(target, 0); + + return writer.ToString(); + } + + throw new Exception($"Find node by name \"{rootName}\" not found. It can be one of the following node name:\n\n{DumpTree(rootNode)}"); + } + return found; + } } } From 96edc3a563a074bd72e04b7197fca98e2160667c Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Sun, 22 Jan 2023 20:57:52 +0900 Subject: [PATCH 4/4] WIP --- .../Commands/ExportArmatureCommand.cs | 39 ------------------- OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs | 12 +++++- .../Builder/InterpolatedMotionBuilder.cs | 3 ++ 3 files changed, 14 insertions(+), 40 deletions(-) delete mode 100644 OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs diff --git a/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs b/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs deleted file mode 100644 index d148e08bc..000000000 --- a/OpenKh.Command.AnbMaker/Commands/ExportArmatureCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using McMaster.Extensions.CommandLineUtils; -using OpenKh.Command.AnbMaker.Extensions; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; - -namespace OpenKh.Command.AnbMaker.Commands -{ - [HelpOption] - [Command(Description = "fbx file: fbx to skeleton dae")] - internal class ExportArmatureCommand - { - [Required] - [FileExists] - [Argument(0, Description = "fbx input")] - public string InputModel { get; set; } - - [Argument(1, Description = "dae output")] - public string Output { get; set; } - - protected int OnExecute(CommandLineApplication app) - { - var assimp = new Assimp.AssimpContext(); - var scene = assimp.ImportFile(InputModel, Assimp.PostProcessSteps.None); - - void Walk(Assimp.Node node, int depth) - { - } - - Walk(scene.RootNode, 0); - - return 0; - } - } -} diff --git a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs index ada4b9eb3..e6cdc0d0e 100644 --- a/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs +++ b/OpenKh.Command.AnbMaker/Utils/AssimpHelper.cs @@ -1,4 +1,5 @@ using Assimp; +using NLog; using OpenKh.Command.AnbMaker.Utils.AssimpSupplemental; using System; using System.Collections.Generic; @@ -12,6 +13,8 @@ internal class AssimpHelper { public static NodeRef[] FlattenNodes(Node topNode, Mesh mesh) { + var logger = LogManager.GetLogger("FlattenNodes"); + var boneDict = mesh.Bones .ToDictionary(bone => bone.Name, bone => bone); @@ -28,7 +31,14 @@ public static NodeRef[] FlattenNodes(Node topNode, Mesh mesh) foreach (var sub in nodeRef.ArmatureNode.Children.Reverse()) { - stack.Push(new NodeRef(idx, sub, boneDict[topNode.Name])); + if (boneDict.TryGetValue(sub.Name, out Bone? meshBone) && meshBone != null) + { + stack.Push(new NodeRef(idx, sub, meshBone)); + } + else + { + logger.Warn($"Node '{sub.Name}' does not belong to target mesh. Skipping."); + } } } diff --git a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs index e10690ec0..0265109b1 100644 --- a/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs +++ b/OpenKh.Command.AnbMaker/Utils/Builder/InterpolatedMotionBuilder.cs @@ -33,6 +33,9 @@ BasicSourceMotion parm // convert source animation's keyTime to KH2 internal frame rate 60 fps which is called GFR (Global Frame Rate) var keyTimeMultiplier = 60 / parm.TicksPerSecond; + logger.Info($"BoneCount {parm.BoneCount}"); + logger.Debug($"Frame {0} to {frameCount - 1}, framesPerSecond {parm.TicksPerSecond}, consumes {frameCount * 1.0f / parm.TicksPerSecond:0.0} seconds"); + Ipm.InterpolatedMotionHeader.BoneCount = Convert.ToInt16(parm.BoneCount); Ipm.InterpolatedMotionHeader.TotalBoneCount = Convert.ToInt16(parm.BoneCount); Ipm.InterpolatedMotionHeader.FrameCount = (int)(frameCount * keyTimeMultiplier); // in 1/60 seconds