diff --git a/OpenKh.Bbs/Scd.cs b/OpenKh.Bbs/Scd.cs index 4ea66aa10..4e4960ee2 100644 --- a/OpenKh.Bbs/Scd.cs +++ b/OpenKh.Bbs/Scd.cs @@ -43,7 +43,7 @@ public class StreamHeader [Data] public uint StreamSize { get; set; } [Data] public uint ChannelCount { get; set; } [Data] public uint SampleRate { get; set; } - [Data] public uint Codec { get; set; } + [Data] public uint Codec { get; set; } //6 = .ogg, 12 = MSADPCM .wav [Data] public uint LoopStart { get; set; } [Data] public uint LoopEnd { get; set; } [Data] public uint ExtraDataSize { get; set; } @@ -63,44 +63,95 @@ public class VAGHeader [Data(Count = 16)] public string Name { get; set; } } - public static Header header = new Header(); - public static TableOffsetHeader tableOffsetHeader = new TableOffsetHeader(); - public static List StreamFiles = new List(); + public Header FileHeader = new(); + public TableOffsetHeader tableOffsetHeader = new(); + public List StreamHeaders = []; + public List StreamFiles = []; + public List MediaFiles = []; //6 = .ogg file, everything else is a .wav with msadpcm codec, throw it at ffmpeg /shrug public static Scd Read(Stream stream) { - Scd scd = new Scd(); - - header = BinaryMapping.ReadObject
(stream); - tableOffsetHeader = BinaryMapping.ReadObject(stream); + stream.Seek(0, SeekOrigin.Begin); + + var scd = new Scd + { + FileHeader = BinaryMapping.ReadObject
(stream), + tableOffsetHeader = BinaryMapping.ReadObject(stream), + }; - stream.Seek(tableOffsetHeader.Table1Offset, SeekOrigin.Begin); + stream.Seek(scd.tableOffsetHeader.Table1Offset, SeekOrigin.Begin); - List SoundOffsets = new List(); - for (int i = 0; i < tableOffsetHeader.Table1ElementCount; i++) - { - SoundOffsets.Add(stream.ReadUInt32()); - } + var soundOffsets = new List(); + for (var i = 0; i < scd.tableOffsetHeader.Table1ElementCount; i++) soundOffsets.Add(stream.ReadUInt32()); - foreach(uint off in SoundOffsets) + for (var index = 0; index < soundOffsets.Count; index++) { + var off = soundOffsets[index]; + var next = (int)(index == soundOffsets.Count - 1 ? stream.Length : soundOffsets[index + 1] - off); stream.Seek(off, SeekOrigin.Begin); var streamInfo = BinaryMapping.ReadObject(stream); - - byte[] st = stream.ReadBytes((int)streamInfo.StreamSize); - StreamFiles.Add(st); - - // Convert to VAG Streams. - /*VAGHeader vagHeader = new VAGHeader(); - vagHeader.ChannelCount = (byte)streamInfo.ChannelCount; - vagHeader.SamplingFrequency = (byte)streamInfo.SampleRate; - vagHeader.Version = 3; - vagHeader.Magic = 0x70474156; - vagHeader.Name = p.ToString();*/ + scd.StreamHeaders.Add(streamInfo); + + var st = stream.ReadBytes(next - 0x20); + scd.StreamFiles.Add(st); + + //https://github.com/Leinxad/KHPCSoundTools/blob/main/SCDInfo/Program.cs#L109 + switch (streamInfo.Codec) + { + case 6: + { + var extradataOffset = 0u; + if (streamInfo.AuxChunkCount > 0) extradataOffset += BitConverter.ToUInt32(st.Skip(0x04).Take(4).ToArray(), 0); + + var encryptionKey = st[extradataOffset + 0x02]; + var seekTableSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x10).Take(4).ToArray(), 0); + var vorbHeaderSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x14).Take(4).ToArray(), 0); + + var startOffset = extradataOffset + 0x20 + seekTableSize; + + var decryptedFile = st.ToArray(); + + var endPosition = startOffset + vorbHeaderSize; + + for (var i = startOffset; i < endPosition; i++) decryptedFile[i] = (byte)(decryptedFile[i]^encryptionKey); + + var oggSize = vorbHeaderSize + streamInfo.StreamSize; + + scd.MediaFiles.Add(decryptedFile.Skip((int)startOffset).Take((int)oggSize).ToArray()); + break; + } + case 12: + { + var streamSize = streamInfo.StreamSize; + var channelCount = streamInfo.ChannelCount; + var sampleRate = streamInfo.SampleRate; + + var length = streamSize + (0x4e - 0x8); + + var msadpcm = Array.Empty() + .Concat(BitConverter.GetBytes(0x46464952)) //"RIFF" + .Concat(BitConverter.GetBytes(length)) //overall file size - 8 + .Concat(BitConverter.GetBytes(0x45564157)) //"WAVE" + .Concat(BitConverter.GetBytes(0x20746D66)) //"fmt " + .Concat(BitConverter.GetBytes(0x32)) + .Concat(st.Take(0x32)) + .Concat(BitConverter.GetBytes(0x61746164)) //"data" + .Concat(BitConverter.GetBytes((int)streamSize)) + .Concat(st.Skip(0x32)) + .ToArray(); + + scd.MediaFiles.Add(msadpcm); + break; + } + default: + { + scd.MediaFiles.Add(null); + break; + } + } } - return scd; } diff --git a/OpenKh.Egs/EgsTools.cs b/OpenKh.Egs/EgsTools.cs index 6284e2c67..f7a285a6d 100644 --- a/OpenKh.Egs/EgsTools.cs +++ b/OpenKh.Egs/EgsTools.cs @@ -69,7 +69,13 @@ public class EgsTools #region Extract - public static void Extract(string inputHed, string output, bool doNotExtractAgain = false) + public enum ExtractFileOutputMode + { + SeparateRoot, + Adjacent, + } + + public static void Extract(string inputHed, string output, bool doNotExtractAgain = false, ExtractFileOutputMode outputMode = ExtractFileOutputMode.SeparateRoot) { var outputDir = output ?? Path.GetFileNameWithoutExtension(inputHed); using var hedStream = File.OpenRead(inputHed); @@ -81,7 +87,11 @@ public static void Extract(string inputHed, string output, bool doNotExtractAgai if (!Names.TryGetValue(hash, out var fileName)) fileName = $"{hash}.dat"; - var outputFileName = Path.Combine(outputDir, ORIGINAL_FILES_FOLDER_NAME, fileName); + var outputFileName = outputMode switch + { + ExtractFileOutputMode.Adjacent => Path.Combine(outputDir, fileName), + _ => Path.Combine(outputDir, ORIGINAL_FILES_FOLDER_NAME, fileName), + }; if (doNotExtractAgain && File.Exists(outputFileName)) continue; @@ -93,7 +103,11 @@ public static void Extract(string inputHed, string output, bool doNotExtractAgai File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); + outputFileName = outputMode switch + { + ExtractFileOutputMode.Adjacent => Path.Combine(outputDir, $"{fileName}.remastered.d"), + _ => Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName), + }; foreach (var asset in hdAsset.Assets) { diff --git a/OpenKh.Godot/.gitattributes b/OpenKh.Godot/.gitattributes new file mode 100644 index 000000000..8ad74f78d --- /dev/null +++ b/OpenKh.Godot/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/OpenKh.Godot/.gitignore b/OpenKh.Godot/.gitignore new file mode 100644 index 000000000..1b4e8db28 --- /dev/null +++ b/OpenKh.Godot/.gitignore @@ -0,0 +1,20 @@ +# Godot 4+ specific ignores +.godot/ +/android/ + +# Jetbrains +.idea/ + +# Kingdom Hearts extracted and imported assets + +Extracted/ + +Imported/**/*.mdlx +Imported/**/*.mdls +Imported/**/*.mset +Imported/**/*.dds +Imported/**/*.scd +Imported/**/*.map +Imported/**/*.png +Imported/**/*.2dd +Imported/**/*.2ld diff --git a/OpenKh.Godot/Animation/IBoneCollisionProvider.cs b/OpenKh.Godot/Animation/IBoneCollisionProvider.cs new file mode 100644 index 000000000..f9a085f50 --- /dev/null +++ b/OpenKh.Godot/Animation/IBoneCollisionProvider.cs @@ -0,0 +1,7 @@ +namespace OpenKh.Godot.Animation +{ + public interface IBoneCollisionProvider + { + + } +} diff --git a/OpenKh.Godot/Animation/InterpolatedAnimation.cs b/OpenKh.Godot/Animation/InterpolatedAnimation.cs new file mode 100644 index 000000000..1f94200be --- /dev/null +++ b/OpenKh.Godot/Animation/InterpolatedAnimation.cs @@ -0,0 +1,16 @@ +using Godot; + +namespace OpenKh.Godot.Animation +{ + public partial class InterpolatedAnimation : Resource + { + [Export] public float TargetFPS; //within this class, this does nothing. this is intended for eventually serializing back to an anb + [Export] public int BoneCount; + [Export] public int AdditionalBoneCount; + [Export] public float TimeStart; + [Export] public float TimeEnd; + [Export] public float TimeReturn; + + //TODO + } +} diff --git a/OpenKh.Godot/Animation/InterpolatedAnimationCurve.cs b/OpenKh.Godot/Animation/InterpolatedAnimationCurve.cs new file mode 100644 index 000000000..7c0c05711 --- /dev/null +++ b/OpenKh.Godot/Animation/InterpolatedAnimationCurve.cs @@ -0,0 +1,9 @@ +using Godot; + +namespace OpenKh.Godot.Animation +{ + public partial class InterpolatedAnimationCurve : Resource + { + //TODO + } +} diff --git a/OpenKh.Godot/Animation/KH2Bone.cs b/OpenKh.Godot/Animation/KH2Bone.cs new file mode 100644 index 000000000..fc1bfbed8 --- /dev/null +++ b/OpenKh.Godot/Animation/KH2Bone.cs @@ -0,0 +1,15 @@ +using System.Numerics; + +namespace OpenKh.Godot.Animation +{ + public struct KH2Bone + { + public int Sibling; + public int Parent; + public int Child; + public int Flags; + public Vector4 Scale; + public Vector4 Rotation; + public Vector4 Position; + } +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2AnimatedShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2AnimatedShader.gdshader new file mode 100644 index 000000000..53451c0c4 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2AnimatedShader.gdshader @@ -0,0 +1,31 @@ +shader_type spatial; +render_mode unshaded; + +#include "res://Assets/Shaders/KH2TextureAnimationCommon.gdshaderinc" + +void fragment() +{ + if (Scissor) + { + ALPHA_SCISSOR_THRESHOLD = 0.5; + } + vec4 sample; + if ( + TextureAnimationOverlay(UseSprite0, UV, TextureOriginalUV0, TextureFrame0, Sprite0, sample) || + TextureAnimationOverlay(UseSprite1, UV, TextureOriginalUV1, TextureFrame1, Sprite1, sample) || + TextureAnimationOverlay(UseSprite2, UV, TextureOriginalUV2, TextureFrame2, Sprite2, sample) || + TextureAnimationOverlay(UseSprite3, UV, TextureOriginalUV3, TextureFrame3, Sprite3, sample) + ) + { + + } + else + { + sample = texture(Texture, UV); + } + ALBEDO = sample.rgb; + if (Scissor || Alpha) + { + ALPHA = sample.a; + } +} \ No newline at end of file diff --git a/OpenKh.Godot/Assets/Shaders/KH2BasicShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2BasicShader.gdshader new file mode 100644 index 000000000..1d9a75e71 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2BasicShader.gdshader @@ -0,0 +1,18 @@ +shader_type spatial; +render_mode unshaded; + +#include "res://Assets/Shaders/KH2Common.gdshaderinc" + +void fragment() +{ + if (Scissor) + { + ALPHA_SCISSOR_THRESHOLD = 0.5; + } + vec4 sample = texture(Texture, UV); + ALBEDO = sample.rgb; + if (Scissor || Alpha) + { + ALPHA = sample.a; + } +} \ No newline at end of file diff --git a/OpenKh.Godot/Assets/Shaders/KH2Common.gdshaderinc b/OpenKh.Godot/Assets/Shaders/KH2Common.gdshaderinc new file mode 100644 index 000000000..9e30a5687 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2Common.gdshaderinc @@ -0,0 +1,3 @@ +uniform sampler2D Texture : hint_default_white, source_color; +uniform bool Scissor; +uniform bool Alpha; \ No newline at end of file diff --git a/OpenKh.Godot/Assets/Shaders/KH2InterfaceAddShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2InterfaceAddShader.gdshader new file mode 100644 index 000000000..86402695c --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2InterfaceAddShader.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; +render_mode unshaded, blend_add; + +#include "KH2InterfaceCommon.gdshaderinc" + +void vertex() +{ + vertColor = COLOR; + minimaxi = CUSTOM0; + uvAdj = CUSTOM1.rg; +} + +void fragment() +{ + COLOR = Compute(vertColor, UV, TEXTURE); +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2InterfaceCommon.gdshaderinc b/OpenKh.Godot/Assets/Shaders/KH2InterfaceCommon.gdshaderinc new file mode 100644 index 000000000..92aa0658a --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2InterfaceCommon.gdshaderinc @@ -0,0 +1,16 @@ +varying vec4 vertColor; +varying vec4 minimaxi; +varying vec2 uvAdj; + +vec2 wrap(vec2 value, vec2 minimum, vec2 maximum) +{ + vec2 s = maximum - minimum; + return minimum + mod(mod(value - minimum, s) + s, s); +} +vec4 Compute(vec4 color, vec2 uv, sampler2D tex) +{ + vec2 uvAdjusted = wrap((uv + (uvAdj * TIME)), minimaxi.xy, minimaxi.zw); + vec2 finalUv = vec2(uvAdj.x != 0.0 ? uvAdjusted.x : uv.x, uvAdj.y != 0.0 ? uvAdjusted.y : uv.y); + return texture(tex, finalUv) * color; + //return texture(tex, uv) * color; +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2InterfaceMixShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2InterfaceMixShader.gdshader new file mode 100644 index 000000000..ec765ec95 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2InterfaceMixShader.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; +render_mode unshaded, blend_mix; + +#include "KH2InterfaceCommon.gdshaderinc" + +void vertex() +{ + vertColor = COLOR; + minimaxi = CUSTOM0; + uvAdj = CUSTOM1.rg; +} + +void fragment() +{ + COLOR = Compute(vertColor, UV, TEXTURE); +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2InterfaceSubShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2InterfaceSubShader.gdshader new file mode 100644 index 000000000..2384d13ac --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2InterfaceSubShader.gdshader @@ -0,0 +1,16 @@ +shader_type canvas_item; +render_mode unshaded, blend_sub; + +#include "KH2InterfaceCommon.gdshaderinc" + +void vertex() +{ + vertColor = COLOR; + minimaxi = CUSTOM0; + uvAdj = CUSTOM1.rg; +} + +void fragment() +{ + COLOR = Compute(vertColor, UV, TEXTURE); +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2TextureAnimationCommon.gdshaderinc b/OpenKh.Godot/Assets/Shaders/KH2TextureAnimationCommon.gdshaderinc new file mode 100644 index 000000000..61261788a --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2TextureAnimationCommon.gdshaderinc @@ -0,0 +1,52 @@ +#include "res://Assets/Shaders/KH2Common.gdshaderinc" + +uniform sampler2D Sprite0 : hint_default_transparent, source_color; +uniform sampler2D Sprite1 : hint_default_transparent, source_color; +uniform sampler2D Sprite2 : hint_default_transparent, source_color; +uniform sampler2D Sprite3 : hint_default_transparent, source_color; + +instance uniform bool UseSprite0 = false; +instance uniform bool UseSprite1 = false; +instance uniform bool UseSprite2 = false; +instance uniform bool UseSprite3 = false; + +uniform vec4 TextureOriginalUV0 = vec4(0); +uniform vec4 TextureOriginalUV1 = vec4(0); +uniform vec4 TextureOriginalUV2 = vec4(0); +uniform vec4 TextureOriginalUV3 = vec4(0); + +instance uniform vec2 TextureFrame0 = vec2(0); +instance uniform vec2 TextureFrame1 = vec2(0); +instance uniform vec2 TextureFrame2 = vec2(0); +instance uniform vec2 TextureFrame3 = vec2(0); + +float lerp(float from, float to, float weight) +{ + return from + (to - from) * weight; +} +float inverselerp(float from, float to, float weight) +{ + return (weight - from) / (to - from); +} +float remap(float value, float inFrom, float inTo, float outFrom, float outTo) +{ + return lerp(outFrom, outTo, inverselerp(inFrom, inTo, value)); +} + +bool TextureAnimationOverlay(bool use, vec2 uv, vec4 TextureOriginalUV, vec2 TextureFrame, sampler2D Sprite, out vec4 sample) +{ + if (use && uv.x >= TextureOriginalUV.x && uv.x <= TextureOriginalUV.z && uv.y >= TextureOriginalUV.y && uv.y <= TextureOriginalUV.w) + { + vec2 newUv = vec2( + remap(uv.x, TextureOriginalUV.x, TextureOriginalUV.z, 0, 1), + remap(uv.y, TextureOriginalUV.y, TextureOriginalUV.w, 0, 1) + ); + float newY = remap(newUv.y, 0, 1, TextureFrame.x, TextureFrame.y); + sample = texture(Sprite, vec2(newUv.x, newY)); + return true; + } + else + { + return false; + } +} \ No newline at end of file diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaAddShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaAddShader.gdshader new file mode 100644 index 000000000..5210913fe --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaAddShader.gdshader @@ -0,0 +1,11 @@ +shader_type spatial; +render_mode unshaded, blend_add; + +#include "res://Assets/Shaders/KH2WorldCommon.gdshaderinc" + +void fragment() +{ + vec4 sample = Run(UV); + ALBEDO = sample.rgb * COLOR.rgb; + ALPHA = sample.a * COLOR.a; +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMixShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMixShader.gdshader new file mode 100644 index 000000000..ba32a4931 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMixShader.gdshader @@ -0,0 +1,11 @@ +shader_type spatial; +render_mode unshaded, blend_mix; + +#include "res://Assets/Shaders/KH2WorldCommon.gdshaderinc" + +void fragment() +{ + vec4 sample = Run(UV); + ALBEDO = sample.rgb * COLOR.rgb; + ALPHA = sample.a * COLOR.a; +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMulShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMulShader.gdshader new file mode 100644 index 000000000..bceac6e4d --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaMulShader.gdshader @@ -0,0 +1,11 @@ +shader_type spatial; +render_mode unshaded, blend_mul; + +#include "res://Assets/Shaders/KH2WorldCommon.gdshaderinc" + +void fragment() +{ + vec4 sample = Run(UV); + ALBEDO = sample.rgb * COLOR.rgb; + ALPHA = sample.a * COLOR.a; +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaSubShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaSubShader.gdshader new file mode 100644 index 000000000..c00f5ac00 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldAlphaSubShader.gdshader @@ -0,0 +1,11 @@ +shader_type spatial; +render_mode unshaded, blend_sub; + +#include "res://Assets/Shaders/KH2WorldCommon.gdshaderinc" + +void fragment() +{ + vec4 sample = Run(UV); + ALBEDO = sample.rgb * COLOR.rgb; + ALPHA = sample.a * COLOR.a; +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldCommon.gdshaderinc b/OpenKh.Godot/Assets/Shaders/KH2WorldCommon.gdshaderinc new file mode 100644 index 000000000..11b70449e --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldCommon.gdshaderinc @@ -0,0 +1,9 @@ +uniform sampler2D Texture : hint_default_white, source_color; +uniform vec2 UVScrollSpeed = vec2(0); +uniform float UVScrollEnabled = 0; + +vec4 Run(vec2 uv) +{ + vec2 scrolledPos = UVScrollSpeed * TIME * UVScrollEnabled; + return texture(Texture, uv + scrolledPos); +} diff --git a/OpenKh.Godot/Assets/Shaders/KH2WorldOpaqueShader.gdshader b/OpenKh.Godot/Assets/Shaders/KH2WorldOpaqueShader.gdshader new file mode 100644 index 000000000..e34f8c614 --- /dev/null +++ b/OpenKh.Godot/Assets/Shaders/KH2WorldOpaqueShader.gdshader @@ -0,0 +1,10 @@ +shader_type spatial; +render_mode unshaded; + +#include "res://Assets/Shaders/KH2WorldCommon.gdshaderinc" + +void fragment() +{ + vec4 sample = Run(UV); + ALBEDO = sample.rgb * COLOR.rgb; +} diff --git a/OpenKh.Godot/Configuration/Config.cs b/OpenKh.Godot/Configuration/Config.cs new file mode 100644 index 000000000..a9b7b1698 --- /dev/null +++ b/OpenKh.Godot/Configuration/Config.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Configuration; + +public enum Platform +{ + None, + Steam //TODO +} +public static class PlatformHelpers +{ + public static System.Collections.Generic.Dictionary Names = Enum.GetValues().ToDictionary(i => i, i => i.ToString()); +} + +public class GamePathConfig +{ + public readonly string Identifier; + public string GamePath = ""; + public Platform GamePlatform = Platform.None; + public GamePathConfig(string identifier) + { + Identifier = identifier; + Load(); + } + public void Save() + { + var file = FileAccess.Open($"user://{Identifier}.khcfg", FileAccess.ModeFlags.Write); + + file.StorePascalString(Json.Stringify(new Dictionary + { + {"path", GamePath}, + {"platform", GamePlatform.ToString()}, + {"version", 1}, + })); + + file.Close(); + } + public void Load() + { + var file = FileAccess.Open($"user://{Identifier}.khcfg", FileAccess.ModeFlags.Read); + + if (file == null) return; + + var str = file.GetPascalString(); + + file.Close(); + + var json = Json.ParseString(str); + + if (json.VariantType != Variant.Type.Dictionary) return; + + var jsonDict = json.AsGodotDictionary(); + + if (jsonDict.TryGetValue("path", out var path)) GamePath = path.AsString(); + if (jsonDict.TryGetValue("platform", out var platform)) GamePlatform = Enum.Parse(platform.AsString()); + } +} +public static class Config +{ + public static GamePathConfig HDRemixConfig = new("HDRemix"); + + public static IReadOnlyCollection Configs = new List{ HDRemixConfig }.AsReadOnly(); +} diff --git a/OpenKh.Godot/Conversion/Converters.cs b/OpenKh.Godot/Conversion/Converters.cs new file mode 100644 index 000000000..4dee6dcff --- /dev/null +++ b/OpenKh.Godot/Conversion/Converters.cs @@ -0,0 +1,320 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Intrinsics.Arm; +using System.Security.Cryptography; +using System.Text; +using FFMpegCore; +using FFMpegCore.Pipes; +using Godot; +using Godot.Collections; +using OpenKh.Bbs; +using OpenKh.Common; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using OpenKh.Kh2.TextureFooter; +using OpenKh.Ps2; +using Array = Godot.Collections.Array; +using Environment = Godot.Environment; +using FileAccess = Godot.FileAccess; + +namespace OpenKh.Godot.Conversion; + +public static class Converters +{ + public static Node2D FromInterfaceSequence(Bar barFile, List hdTexs = null) + { + var root = new Node2D(); + + var names = barFile.Select(i => i.Name).Distinct().ToList(); + + var mapper = new TextureMapper(hdTexs); + + foreach (var n in names) + { + var image = barFile.FirstOrDefault(i => i.Name == n && i.Type == Bar.EntryType.Imgd); + var sequence = barFile.FirstOrDefault(i => i.Name == n && i.Type == Bar.EntryType.Seqd); + + if (image is null || sequence is null) continue; + + var img = Imgd.Read(image.Stream); + var seq = Sequence.Read(sequence.Stream); + + var sequenceResource = FromSequence(seq, TextureConverters.FromImgd(img), mapper.GetNextTexture(null)); + + var sequenceNode = new KH2InterfaceSequencePlayer(); + + sequenceNode.Sequence = sequenceResource; + + root.AddChild(sequenceNode); + sequenceNode.Name = n; + } + + return root; + } + public static KH2InterfaceSpriteSequence FromSequence(Sequence sequence, Texture2D nativeTex, Texture2D hdTex = null) + { + const float framerate = 60; + + var sequenceNode = new KH2InterfaceSpriteSequence(); + + sequenceNode.Texture = hdTex ?? nativeTex; + + var nativeTexSize = nativeTex.GetSize(); + + foreach (var spriteGroup in sequence.SpriteGroups) + { + var mesh = new ArrayMesh(); + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + + var positions = new List(); + var uvs = new List(); + var adjustments = new List(); + var miniMaxis = new List(); + var colors = new List(); + + foreach (var spritePart in spriteGroup) + { + var sprite = sequence.Sprites[spritePart.SpriteIndex]; + + var posTopLeft = new Vector2(spritePart.Left, spritePart.Top); + var posTopRight = new Vector2(spritePart.Right, spritePart.Top); + var posBottomLeft = new Vector2(spritePart.Left, spritePart.Bottom); + var posBottomRight = new Vector2(spritePart.Right, spritePart.Bottom); + + var uvTopLeft = new Vector2(sprite.Left, sprite.Top); + var uvTopRight = new Vector2(sprite.Right, sprite.Top); + var uvBottomLeft = new Vector2(sprite.Left, sprite.Bottom); + var uvBottomRight = new Vector2(sprite.Right, sprite.Bottom); + + var min = new Vector2(Mathf.Min(sprite.Left, sprite.Right), Mathf.Min(sprite.Top, sprite.Bottom)) / nativeTexSize; + var max = new Vector2(Mathf.Max(sprite.Left, sprite.Right), Mathf.Max(sprite.Top, sprite.Bottom)) / nativeTexSize; + + var miniMaxi = new Vector4(min.X, min.Y, max.X, max.Y); + + var colorTopLeft = sprite.ColorLeft.ConvertColor(); + var colorTopRight = sprite.ColorTop.ConvertColor(); + var colorBottomLeft = sprite.ColorRight.ConvertColor(); + var colorBottomRight = sprite.ColorBottom.ConvertColor(); + + var movement = new Vector2(sprite.UTranslation, sprite.VTranslation) * 60; + + positions.AddRange( + [ + posTopLeft, posTopRight, posBottomLeft, + posBottomLeft, posTopRight, posBottomRight, + ]); + uvs.AddRange( + [ + uvTopLeft, uvTopRight, uvBottomLeft, + uvBottomLeft, uvTopRight, uvBottomRight, + ]); + adjustments.AddRange( + [ + movement, movement, movement, + movement, movement, movement, + ]); + miniMaxis.AddRange( + [ + miniMaxi, miniMaxi, miniMaxi, + miniMaxi, miniMaxi, miniMaxi, + ]); + colors.AddRange( + [ + colorTopLeft, colorTopRight, colorBottomLeft, + colorBottomLeft, colorTopRight, colorBottomRight, + ]); + } + + array[(int)Mesh.ArrayType.Vertex] = positions.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = uvs.Select(u => u / nativeTexSize).ToArray(); + array[(int)Mesh.ArrayType.Color] = colors.ToArray(); + array[(int)Mesh.ArrayType.Custom0] = miniMaxis.SelectMany(i => new[] { i.X, i.Y, i.Z, i.W }).ToArray(); + array[(int)Mesh.ArrayType.Custom1] = adjustments.SelectMany(i => new[] { i.X, i.Y, }).ToArray(); + + const Mesh.ArrayFormat miniMaxiShift = (Mesh.ArrayFormat)((int)Mesh.ArrayCustomFormat.RgbaFloat << (int)Mesh.ArrayFormat.FormatCustom0Shift); + const Mesh.ArrayFormat adjustmentShift = (Mesh.ArrayFormat)((int)Mesh.ArrayCustomFormat.RgFloat << (int)Mesh.ArrayFormat.FormatCustom1Shift); + + if (spriteGroup.Count > 0) + mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array, + flags: Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatTexUV | Mesh.ArrayFormat.FormatColor | Mesh.ArrayFormat.FlagUse2DVertices | miniMaxiShift | adjustmentShift); + + sequenceNode.Sprites.Add(mesh); + } + + foreach (var animationGroup in sequence.AnimationGroups) + { + var groupResource = new KH2InterfaceSequenceAnimationGroup(); + groupResource.Loop = animationGroup.DoNotLoop == 0; + groupResource.LoopStart = animationGroup.LoopStart / framerate; + groupResource.LoopEnd = animationGroup.LoopEnd / framerate; + if (groupResource.Loop && groupResource.LoopEnd == 0) groupResource.LoopEnd = animationGroup.Animations.Max(i => i.FrameEnd) / framerate; + + foreach (var currentAnim in animationGroup.Animations) + { + var anim = new KH2InterfaceSequenceAnimation + { + SpriteIndex = currentAnim.SpriteGroupIndex, + AnimationFlags = (InterfaceAnimationFlags)currentAnim.Flags, + TimeStart = currentAnim.FrameStart / framerate, + TimeEnd = currentAnim.FrameEnd / framerate, + TranslateStart = new Vector2(currentAnim.TranslateXStart, currentAnim.TranslateYStart), + TranslateEnd = new Vector2(currentAnim.TranslateXEnd, currentAnim.TranslateYEnd), + RotationStart = new Vector3(currentAnim.RotationXStart, currentAnim.RotationYStart, currentAnim.RotationZStart), + RotationEnd = new Vector3(currentAnim.RotationXEnd, currentAnim.RotationYEnd, currentAnim.RotationZEnd), + ScaleStart = new Vector2(currentAnim.ScaleXStart, currentAnim.ScaleYStart), + ScaleEnd = new Vector2(currentAnim.ScaleXEnd, currentAnim.ScaleYEnd), + ScaleUniformStart = currentAnim.ScaleStart, + ScaleUniformEnd = currentAnim.ScaleEnd, + PivotStart = new Vector2(currentAnim.PivotXStart, currentAnim.PivotYStart), + PivotEnd = new Vector2(currentAnim.PivotXEnd, currentAnim.PivotYEnd), + CurveStart = new Vector2(currentAnim.CurveXStart, currentAnim.CurveYStart), + CurveEnd = new Vector2(currentAnim.CurveXEnd, currentAnim.CurveYEnd), + BounceStart = new Vector2(currentAnim.BounceXStart, currentAnim.BounceYStart), + BounceEnd = new Vector2(currentAnim.BounceXEnd, currentAnim.BounceYEnd), + BounceCount = new Vector2I(currentAnim.BounceXCount, currentAnim.BounceYCount), + ColorBlend = currentAnim.ColorBlend, + }; + + anim.ColorStart = currentAnim.ColorStart.ConvertColor(); + anim.ColorEnd = currentAnim.ColorEnd.ConvertColor(); + + groupResource.Animations.Add(anim); + } + sequenceNode.AnimationList.Add(groupResource); + } + return sequenceNode; + } + + public static Node2D FromInterfaceLayout(Bar layout, List hdTexs = null) + { + var mapper = new TextureMapper(hdTexs); + + var root = new KH2InterfaceLayoutPlayer(); + + var imageEntry = layout.FirstOrDefault(i => i.Type == Bar.EntryType.Imgz); + var layoutEntry = layout.FirstOrDefault(i => i.Type == Bar.EntryType.Layout); + + if (imageEntry is null || layoutEntry is null) return null; + + var nativeImages = new Imgz(imageEntry.Stream); + var nativeImageList = nativeImages.Images.Select(TextureConverters.FromImgd).ToList(); + + for (var i = 0; i < nativeImageList.Count; i++) mapper.GetTexture(i, null); + + var nativeLayout = Layout.Read(layoutEntry.Stream); + + var layoutResource = new KH2InterfaceLayout(); + + foreach (var group in nativeLayout.SequenceGroups) + { + var groupResource = new KH2InterfaceSequenceGroup(); + + foreach (var sequence in group.Sequences) + { + var nativeTex = nativeImageList[sequence.TextureIndex]; + + var sequenceResource = FromSequence(nativeLayout.SequenceItems[sequence.SequenceIndex], nativeTex, mapper.GetTexture(sequence.TextureIndex, null)); + + groupResource.Sequences.Add(sequenceResource); + groupResource.SequenceIndices.Add(sequence.AnimationGroup); + groupResource.SequencePositions.Add(new Vector2(sequence.PositionX, sequence.PositionY)); + groupResource.ShowAtTime = sequence.ShowAtFrame / 60f; + } + + layoutResource.Groups.Add(groupResource); + } + + root.Layout = layoutResource; + + return root; + } + public static SoundContainer FromScd(Scd scd) + { + var container = new SoundContainer(); + + for (var index = 0; index < scd.StreamFiles.Count; index++) + { + var media = scd.MediaFiles[index]; + var header = scd.StreamHeaders[index]; + //var streamFile = scd.StreamFiles[index]; + + switch (header.Codec) + { + case 6: + container.Sounds.Add(new SoundResource + { + Sound = AudioStreamOggVorbis.LoadFromBuffer(media), + SampleRate = header.SampleRate, + LoopStartRaw = header.LoopStart, + LoopEndRaw = header.LoopEnd, + LoopStart = (header.LoopStart * header.ChannelCount) / (float)header.SampleRate, + LoopEnd = ((header.LoopEnd - header.LoopStart) * header.ChannelCount) / (float)header.SampleRate, + Loop = header.LoopStart != 0 && header.LoopEnd != 0, + ChannelCount = header.ChannelCount, + OriginalCodec = SoundResource.Codec.Ogg, + }); + break; + case 12: + { + //convert from MSADPCM to ogg, godot doesnt support MSADPCM wav files + var hash = SHA256.HashData(media); + var hashString = Convert.ToHexString(hash); + var path = $"user://khcache/msadpcm/{hashString}.cache"; + + byte[] result; + + if (FileAccess.FileExists(path)) + { + using var file = FileAccess.OpenCompressed(path, FileAccess.ModeFlags.Read, FileAccess.CompressionMode.GZip); + result = file.GetBuffer((long)file.GetLength()); + } + else + { + var output = new MemoryStream(); + FFMpegArguments + .FromPipeInput(new StreamPipeSource(new MemoryStream(media))) + .OutputToPipe(new StreamPipeSink(output), ffmpegOptions => + ffmpegOptions.ForceFormat("ogg")) + .ProcessSynchronously(); + + result = output.GetBuffer(); + + DirAccess.MakeDirRecursiveAbsolute("user://khcache/msadpcm/"); + + using var file = FileAccess.OpenCompressed(path, FileAccess.ModeFlags.Write, FileAccess.CompressionMode.GZip); + file.StoreBuffer(result); + } + + container.Sounds.Add(new SoundResource + { + Sound = AudioStreamOggVorbis.LoadFromBuffer(result), + SampleRate = header.SampleRate, + LoopStartRaw = header.LoopStart, + LoopEndRaw = header.LoopEnd, + LoopStart = (header.LoopStart * header.ChannelCount) / (float)header.SampleRate, + LoopEnd = ((header.LoopEnd - header.LoopStart) * header.ChannelCount) / (float)header.SampleRate, + Loop = header.LoopStart != 0 && header.LoopEnd != 0, + ChannelCount = header.ChannelCount, + OriginalCodec = SoundResource.Codec.msadpcm, + }); + break; + } + default: + { + container.Sounds.Add(null); + break; + } + } + } + + return container; + } +} diff --git a/OpenKh.Godot/Conversion/DDSConverter.cs b/OpenKh.Godot/Conversion/DDSConverter.cs new file mode 100644 index 000000000..90fe26de8 --- /dev/null +++ b/OpenKh.Godot/Conversion/DDSConverter.cs @@ -0,0 +1,636 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Common; + +//Godot's DDS importer + mipmap patch, converted to c# + +namespace OpenKh.Godot.Conversion +{ + public static class DDSConverter + { + //private static uint PF_FOURCC(string s) => unchecked((uint)(s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])); + //#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0]))) + + private enum DDSFourCC : uint + { + DDFCC_DXT1 = /*PF_FOURCC("DXT1")*/ 0x44585431, + DDFCC_DXT2 = /*PF_FOURCC("DXT2")*/ 0x44585432, + DDFCC_DXT3 = /*PF_FOURCC("DXT3")*/ 0x44585433, + DDFCC_DXT4 = /*PF_FOURCC("DXT4")*/ 0x44585434, + DDFCC_DXT5 = /*PF_FOURCC("DXT5")*/ 0x44585435, + DDFCC_ATI1 = /*PF_FOURCC("ATI1")*/ 0x41544931, + DDFCC_BC4U = /*PF_FOURCC("BC4U")*/ 0x42433455, + DDFCC_ATI2 = /*PF_FOURCC("ATI2")*/ 0x41544932, + DDFCC_BC5U = /*PF_FOURCC("BC5U")*/ 0x42433555, + DDFCC_A2XY = /*PF_FOURCC("A2XY")*/ 0x41325859, + DDFCC_DX10 = /*PF_FOURCC("DX10")*/ 0x44583130, + DDFCC_R16F = 111, + DDFCC_RG16F = 112, + DDFCC_RGBA16F = 113, + DDFCC_R32F = 114, + DDFCC_RG32F = 115, + DDFCC_RGBA32F = 116 + } + + private static uint DDS_MAGIC = 0x20534444; + private static uint DDSD_PITCH = 0x00000008; + private static uint DDSD_LINEARSIZE = 0x00080000; + private static uint DDSD_MIPMAPCOUNT = 0x00020000; + private static uint DDPF_ALPHAPIXELS = 0x00000001; + private static uint DDPF_ALPHAONLY = 0x00000002; + private static uint DDPF_FOURCC = 0x00000004; + private static uint DDPF_RGB = 0x00000040; + private static uint DDPF_RG_SNORM = 0x00080000; + + private enum DXGIFormat + { + DXGI_R32G32B32A32_FLOAT = 2, + DXGI_R32G32B32_FLOAT = 6, + DXGI_R16G16B16A16_FLOAT = 10, + DXGI_R32G32_FLOAT = 16, + DXGI_R10G10B10A2_UNORM = 24, + DXGI_R8G8B8A8_UNORM = 28, + DXGI_R8G8B8A8_UNORM_SRGB = 29, + DXGI_R16G16_FLOAT = 34, + DXGI_R32_FLOAT = 41, + DXGI_R8G8_UNORM = 49, + DXGI_R16_FLOAT = 54, + DXGI_R8_UNORM = 61, + DXGI_A8_UNORM = 65, + DXGI_R9G9B9E5 = 67, + DXGI_BC1_UNORM = 71, + DXGI_BC1_UNORM_SRGB = 72, + DXGI_BC2_UNORM = 74, + DXGI_BC2_UNORM_SRGB = 75, + DXGI_BC3_UNORM = 77, + DXGI_BC3_UNORM_SRGB = 78, + DXGI_BC4_UNORM = 80, + DXGI_BC5_UNORM = 83, + DXGI_B5G6R5_UNORM = 85, + DXGI_B5G5R5A1_UNORM = 86, + DXGI_B8G8R8A8_UNORM = 87, + DXGI_BC6H_UF16 = 95, + DXGI_BC6H_SF16 = 96, + DXGI_BC7_UNORM = 98, + DXGI_BC7_UNORM_SRGB = 99, + DXGI_B4G4R4A4_UNORM = 115 + } + + private enum DDSFormat + { + DDS_DXT1, + DDS_DXT3, + DDS_DXT5, + DDS_ATI1, + DDS_ATI2, + DDS_BC6U, + DDS_BC6S, + DDS_BC7, + DDS_R16F, + DDS_RG16F, + DDS_RGBA16F, + DDS_R32F, + DDS_RG32F, + DDS_RGB32F, + DDS_RGBA32F, + DDS_RGB9E5, + DDS_RGB8, + DDS_RGBA8, + DDS_BGR8, + DDS_BGRA8, + DDS_BGR5A1, + DDS_BGR565, + DDS_B2GR3, + DDS_B2GR3A8, + DDS_BGR10A2, + DDS_RGB10A2, + DDS_BGRA4, + DDS_LUMINANCE, + DDS_LUMINANCE_ALPHA, + DDS_LUMINANCE_ALPHA_4, + DDS_MAX + } + + private struct DDSFormatInfo + { + public string name = null; + public bool compressed = false; + public uint divisor = 0; + public uint block_size = 0; + public Image.Format format = Image.Format.BptcRgba; + + public DDSFormatInfo() + { + } + public DDSFormatInfo(string n, bool c, uint d, uint b, Image.Format f) + { + name = n; + compressed = c; + divisor = d; + block_size = b; + format = f; + } + } + + private static readonly DDSFormatInfo[] dds_format_info = + [ + new("DXT1/BC1", true, 4, 8, Image.Format.Dxt1), + new("DXT2/DXT3/BC2", true, 4, 16, Image.Format.Dxt3), + new("DXT4/DXT5/BC3", true, 4, 16, Image.Format.Dxt5), + new("ATI1/BC4", true, 4, 8, Image.Format.RgtcR), + new("ATI2/A2XY/BC5", true, 4, 16, Image.Format.RgtcRg), + new("BC6UF", true, 4, 16, Image.Format.BptcRgbfu), + new("BC6SF", true, 4, 16, Image.Format.BptcRgbf), + new("BC7", true, 4, 16, Image.Format.BptcRgba), + new("R16F", false, 1, 2, Image.Format.Rh), + new("RG16F", false, 1, 4, Image.Format.Rgh), + new("RGBA16F", false, 1, 8, Image.Format.Rgbah), + new("R32F", false, 1, 4, Image.Format.Rf), + new("RG32F", false, 1, 8, Image.Format.Rgf), + new("RGB32F", false, 1, 12, Image.Format.Rgbf), + new("RGBA32F", false, 1, 16, Image.Format.Rgbaf), + new("RGB9E5", false, 1, 4, Image.Format.Rgbe9995), + new("RGB8", false, 1, 3, Image.Format.Rgb8), + new("RGBA8", false, 1, 4, Image.Format.Rgba8), + new("BGR8", false, 1, 3, Image.Format.Rgb8), + new("BGRA8", false, 1, 4, Image.Format.Rgba8), + new("BGR5A1", false, 1, 2, Image.Format.Rgba8), + new("BGR565", false, 1, 2, Image.Format.Rgb8), + new("B2GR3", false, 1, 1, Image.Format.Rgb8), + new("B2GR3A8", false, 1, 2, Image.Format.Rgba8), + new("BGR10A2", false, 1, 4, Image.Format.Rgba8), + new("RGB10A2", false, 1, 4, Image.Format.Rgba8), + new("BGRA4", false, 1, 2, Image.Format.Rgba8), + new("GRAYSCALE", false, 1, 1, Image.Format.L8), + new("GRAYSCALE_ALPHA", false, 1, 2, Image.Format.La8), + new("GRAYSCALE_ALPHA_4", false, 1, 1, Image.Format.La8), + ]; + + private static DDSFormat dxgi_to_dds_format(DXGIFormat p_dxgi_format) => + p_dxgi_format switch + { + DXGIFormat.DXGI_R32G32B32A32_FLOAT => DDSFormat.DDS_RGBA32F, + DXGIFormat.DXGI_R32G32B32_FLOAT => DDSFormat.DDS_RGB32F, + DXGIFormat.DXGI_R16G16B16A16_FLOAT => DDSFormat.DDS_RGBA16F, + DXGIFormat.DXGI_R32G32_FLOAT => DDSFormat.DDS_RG32F, + DXGIFormat.DXGI_R10G10B10A2_UNORM => DDSFormat.DDS_RGB10A2, + DXGIFormat.DXGI_R8G8B8A8_UNORM or DXGIFormat.DXGI_R8G8B8A8_UNORM_SRGB => DDSFormat.DDS_RGBA8, + DXGIFormat.DXGI_R16G16_FLOAT => DDSFormat.DDS_RG16F, + DXGIFormat.DXGI_R32_FLOAT => DDSFormat.DDS_R32F, + DXGIFormat.DXGI_R8_UNORM or DXGIFormat.DXGI_A8_UNORM => DDSFormat.DDS_LUMINANCE, + DXGIFormat.DXGI_R16_FLOAT => DDSFormat.DDS_R16F, + DXGIFormat.DXGI_R8G8_UNORM => DDSFormat.DDS_LUMINANCE_ALPHA, + DXGIFormat.DXGI_R9G9B9E5 => DDSFormat.DDS_RGB9E5, + DXGIFormat.DXGI_BC1_UNORM or DXGIFormat.DXGI_BC1_UNORM_SRGB => DDSFormat.DDS_DXT1, + DXGIFormat.DXGI_BC2_UNORM or DXGIFormat.DXGI_BC2_UNORM_SRGB => DDSFormat.DDS_DXT3, + DXGIFormat.DXGI_BC3_UNORM or DXGIFormat.DXGI_BC3_UNORM_SRGB => DDSFormat.DDS_DXT5, + DXGIFormat.DXGI_BC4_UNORM => DDSFormat.DDS_ATI1, + DXGIFormat.DXGI_BC5_UNORM => DDSFormat.DDS_ATI2, + DXGIFormat.DXGI_B5G6R5_UNORM => DDSFormat.DDS_BGR565, + DXGIFormat.DXGI_B5G5R5A1_UNORM => DDSFormat.DDS_BGR5A1, + DXGIFormat.DXGI_B8G8R8A8_UNORM => DDSFormat.DDS_BGRA8, + DXGIFormat.DXGI_BC6H_UF16 => DDSFormat.DDS_BC6U, + DXGIFormat.DXGI_BC6H_SF16 => DDSFormat.DDS_BC6S, + DXGIFormat.DXGI_BC7_UNORM or DXGIFormat.DXGI_BC7_UNORM_SRGB => DDSFormat.DDS_BC7, + DXGIFormat.DXGI_B4G4R4A4_UNORM => DDSFormat.DDS_BGRA4, + _ => throw new ArgumentOutOfRangeException(nameof(p_dxgi_format), p_dxgi_format, null) + }; + + public static Image GetImage(byte[] bytes) + { + var f = new MemoryStream(bytes); + + var magic = f.ReadUInt32(); + var hsize = f.ReadUInt32(); + var flags = f.ReadUInt32(); + var height = f.ReadUInt32(); + var width = f.ReadUInt32(); + var pitch = f.ReadUInt32(); + /* uint32_t depth = */ + f.ReadUInt32(); + var mipmaps = f.ReadUInt32(); + + for (var i = 0; i < 11; i++) f.ReadUInt32(); + + if (magic != DDS_MAGIC || hsize != 124) return null; + + /* uint32_t format_size = */ + f.ReadUInt32(); + var format_flags = f.ReadUInt32(); + var format_fourcc = f.ReadUInt32(); + var format_rgb_bits = f.ReadUInt32(); + var format_red_mask = f.ReadUInt32(); + var format_green_mask = f.ReadUInt32(); + var format_blue_mask = f.ReadUInt32(); + var format_alpha_mask = f.ReadUInt32(); + + /* uint32_t caps_1 = */ + f.ReadUInt32(); + /* uint32_t caps_2 = */ + f.ReadUInt32(); + /* uint32_t caps_3 = */ + f.ReadUInt32(); + /* uint32_t caps_4 = */ + f.ReadUInt32(); + + f.ReadUInt32(); + + if (f.Position < 128) f.Seek(128, SeekOrigin.Begin); + + var dds_format = DDSFormat.DDS_MAX; + + if ((format_flags & DDPF_FOURCC) > 0) + { + switch ((DDSFourCC)format_fourcc) + { + case DDSFourCC.DDFCC_DXT1: + dds_format = DDSFormat.DDS_DXT1; + break; + case DDSFourCC.DDFCC_DXT2: + case DDSFourCC.DDFCC_DXT3: + dds_format = DDSFormat.DDS_DXT3; + break; + case DDSFourCC.DDFCC_DXT4: + case DDSFourCC.DDFCC_DXT5: + dds_format = DDSFormat.DDS_DXT5; + break; + case DDSFourCC.DDFCC_ATI1: + case DDSFourCC.DDFCC_BC4U: + dds_format = DDSFormat.DDS_ATI1; + break; + case DDSFourCC.DDFCC_ATI2: + case DDSFourCC.DDFCC_BC5U: + case DDSFourCC.DDFCC_A2XY: + dds_format = DDSFormat.DDS_ATI2; + break; + case DDSFourCC.DDFCC_R16F: + dds_format = DDSFormat.DDS_R16F; + break; + case DDSFourCC.DDFCC_RG16F: + dds_format = DDSFormat.DDS_RG16F; + break; + case DDSFourCC.DDFCC_RGBA16F: + dds_format = DDSFormat.DDS_RGBA16F; + break; + case DDSFourCC.DDFCC_R32F: + dds_format = DDSFormat.DDS_R32F; + break; + case DDSFourCC.DDFCC_RG32F: + dds_format = DDSFormat.DDS_RG32F; + break; + case DDSFourCC.DDFCC_RGBA32F: + dds_format = DDSFormat.DDS_RGBA32F; + break; + case DDSFourCC.DDFCC_DX10: + { + var dxgi_format = f.ReadUInt32(); + /* uint32_t dimension = */ + f.ReadUInt32(); + /* uint32_t misc_flags_1 = */ + f.ReadUInt32(); + /* uint32_t array_size = */ + f.ReadUInt32(); + /* uint32_t misc_flags_2 = */ + f.ReadUInt32(); + dds_format = dxgi_to_dds_format((DXGIFormat)dxgi_format); + } + break; + default: return null; + } + } + else if ((format_flags & DDPF_RGB) > 0) + { + //thanks rider, i can't read this lol + dds_format = (format_flags & DDPF_ALPHAPIXELS) switch + { + > 0 => format_rgb_bits switch + { + 32 when format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000 => DDSFormat.DDS_BGRA8, + 32 when format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000 => DDSFormat.DDS_RGBA8, + 16 when format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000 => DDSFormat.DDS_BGR5A1, + 32 when format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000 => DDSFormat.DDS_BGR10A2, + 32 when format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000 => DDSFormat.DDS_RGB10A2, + 16 when format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000 => DDSFormat.DDS_BGRA4, + 16 when format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00 => DDSFormat.DDS_B2GR3A8, + _ => dds_format, + }, + _ => format_rgb_bits switch + { + 24 when format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff => DDSFormat.DDS_BGR8, + 24 when format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 => DDSFormat.DDS_RGB8, + 16 when format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f => DDSFormat.DDS_BGR565, + 8 when format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 => DDSFormat.DDS_B2GR3, + _ => dds_format, + }, + }; + } + else if ((format_flags & DDPF_ALPHAONLY) > 0 && format_rgb_bits == 8 && format_alpha_mask == 0xff) dds_format = DDSFormat.DDS_LUMINANCE; + + if (dds_format == DDSFormat.DDS_MAX) + { + if ((format_flags & DDPF_ALPHAPIXELS) > 0) + dds_format = format_rgb_bits switch + { + 16 when format_red_mask == 0xff && format_alpha_mask == 0xff00 => DDSFormat.DDS_LUMINANCE_ALPHA, + 8 when format_red_mask == 0xf && format_alpha_mask == 0xf0 => DDSFormat.DDS_LUMINANCE_ALPHA_4, + _ => dds_format + }; + else if (format_rgb_bits == 8 && format_red_mask == 0xff) dds_format = DDSFormat.DDS_LUMINANCE; + } + + if (dds_format == DDSFormat.DDS_MAX) return null; + + if (!((flags & DDSD_MIPMAPCOUNT) > 0)) mipmaps = 1; + + List src_data; + + var info = dds_format_info[(int)dds_format]; + + var w = width; + var h = height; + + if (info.compressed) + { + var size = Math.Max(info.divisor, w) / info.divisor * Math.Max(info.divisor, h) / info.divisor * info.block_size; + if ((flags & DDSD_LINEARSIZE) > 0) + { + if (size != pitch) return null; + } + else if (pitch != 0) return null; + // + var last_mip_offset = 0u; + // + for (uint i = 1; i < mipmaps; i++) + { + w = Math.Max(1u, w >> 1); + h = Math.Max(1u, h >> 1); + + var bsize = Math.Max(info.divisor, w) / info.divisor * Math.Max(info.divisor, h) / info.divisor * info.block_size; + // + last_mip_offset = size; + // + size += bsize; + } + src_data = f.ReadBytes((int)size).ToList(); + + // + var last_mip_w = w; + var last_mip_h = h; + if (mipmaps >= 2u && (last_mip_w >= 2u || last_mip_h >= 2u)) { + + var mip_gen_offset = size; + while ((w >= 2u) || (h >= 2u)) + { + w = Math.Max(1u, w >> 1); + h = Math.Max(1u, h >> 1); + + var bsize = Math.Max(info.divisor, w) / info.divisor * Math.Max(info.divisor, h) / info.divisor * info.block_size; + size += bsize; + } + + var toAdd = size - src_data.Count; + src_data.AddRange(Enumerable.Repeat((byte)0, (int)toAdd)); + + w = last_mip_w; + h = last_mip_h; + while ((w >= 2u) || (h >= 2u)) + { + w = Math.Max(1u, w >> 1); + h = Math.Max(1u, h >> 1); + + var blocks_width = Math.Max(info.divisor, w) / info.divisor; + var blocks_height = Math.Max(info.divisor, h) / info.divisor; + for (var idx = 0u; idx < (blocks_width * blocks_height); idx++) + { + foreach (var i in Enumerable.Range(0, (int)info.block_size)) src_data[i + (int)last_mip_offset] = src_data[i + (int)mip_gen_offset]; + + mip_gen_offset += info.block_size; + } + } + } + // + } + else + { + var size = width * height * info.block_size; + + for (uint i = 1; i < mipmaps; i++) + { + w = w + 1 >> 1; + h = h + 1 >> 1; + size += w * h * info.block_size; + } + + switch (dds_format) + { + case DDSFormat.DDS_BGR565: + size = size * 3 / 2; + break; + + case DDSFormat.DDS_BGR5A1: + case DDSFormat.DDS_BGRA4: + case DDSFormat.DDS_B2GR3A8: + case DDSFormat.DDS_LUMINANCE_ALPHA_4: + size = size * 2; + break; + + case DDSFormat.DDS_B2GR3: + size = size * 3; + break; + default: + break; + } + src_data = f.ReadBytes((int)size).ToList(); + + switch (dds_format) + { + case DDSFormat.DDS_BGR5A1: + { + var colcount = (int)(size / 4); + + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i * 2; + var dst_ofs = i * 4; + + var a = (byte)(src_data[src_ofs + 1] & 0x80); + var b = (byte)(src_data[src_ofs] & 0x1F); + var g = (byte)(src_data[src_ofs] >> 5 | (src_data[src_ofs + 1] & 0x3) << 3); + var r = (byte)(src_data[src_ofs + 1] >> 2 & 0x1F); + + src_data[dst_ofs + 0] = (byte)(r << 3); + src_data[dst_ofs + 1] = (byte)(g << 3); + src_data[dst_ofs + 2] = (byte)(b << 3); + src_data[dst_ofs + 3] = (byte)(a > 0 ? 255 : 0); + } + } break; + case DDSFormat.DDS_BGR565: + { + var colcount = (int)(size / 3); + + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i * 2; + var dst_ofs = i * 3; + + var b = (byte)(src_data[src_ofs] & 0x1F); + var g = (byte)(src_data[src_ofs] >> 5 | (src_data[src_ofs + 1] & 0x7) << 3); + var r = (byte)(src_data[src_ofs + 1] >> 3); + + src_data[dst_ofs + 0] = (byte)(r << 3); + src_data[dst_ofs + 1] = (byte)(g << 2); + src_data[dst_ofs + 2] = (byte)(b << 3); + } + } break; + case DDSFormat.DDS_BGRA4: + { + var colcount = (int)(size / 4); + + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i * 2; + var dst_ofs = i * 4; + + var b = (byte)(src_data[src_ofs] & 0x0F); + var g = (byte)(src_data[src_ofs] & 0xF0); + var r = (byte)(src_data[src_ofs + 1] & 0x0F); + var a = (byte)(src_data[src_ofs + 1] & 0xF0); + + src_data[dst_ofs] = (byte)(r << 4 | r); + src_data[dst_ofs + 1] = (byte)(g | g >> 4); + src_data[dst_ofs + 2] = (byte)(b << 4 | b); + src_data[dst_ofs + 3] = (byte)(a | a >> 4); + } + } break; + case DDSFormat.DDS_B2GR3: + { + var colcount = (int)(size / 3); + + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i; + var dst_ofs = i * 3; + + var b = (byte)((src_data[src_ofs] & 0x3) << 6); + var g = (byte)((src_data[src_ofs] & 0x1C) << 3); + var r = (byte)(src_data[src_ofs] & 0xE0); + + src_data[dst_ofs] = r; + src_data[dst_ofs + 1] = g; + src_data[dst_ofs + 2] = b; + } + } break; + case DDSFormat.DDS_B2GR3A8: + { + var colcount = (int)(size / 4); + + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i * 2; + var dst_ofs = i * 4; + + var b = (byte)((src_data[src_ofs] & 0x3) << 6); + var g = (byte)((src_data[src_ofs] & 0x1C) << 3); + var r = (byte)(src_data[src_ofs] & 0xE0); + var a = src_data[src_ofs + 1]; + + src_data[dst_ofs] = r; + src_data[dst_ofs + 1] = g; + src_data[dst_ofs + 2] = b; + src_data[dst_ofs + 3] = a; + } + } break; + case DDSFormat.DDS_RGB10A2: + { + // To RGBA8. + var colcount = (int)(size / 4); + + for (var i = 0; i < colcount; i++) + { + var ofs = i * 4; + + uint w32; + + unchecked + { + w32 = src_data[ofs + 0] | (uint)src_data[ofs + 1] << 8 | (uint)src_data[ofs + 2] << 16 | (uint)src_data[ofs + 3] << 24; + } + + // This method follows the 'standard' way of decoding 10-bit dds files, + // which means the ones created with DirectXTex will be loaded incorrectly. + var a = (byte)((w32 & 0xc0000000) >> 24); + var r = (byte)((w32 & 0x3ff) >> 2); + var g = (byte)((w32 & 0xffc00) >> 12); + var b = (byte)((w32 & 0x3ff00000) >> 22); + + src_data[ofs + 0] = r; + src_data[ofs + 1] = g; + src_data[ofs + 2] = b; + src_data[ofs + 3] = (byte)(a == 0xc0 ? 255 : a); // 0xc0 should be opaque. + } + } break; + case DDSFormat.DDS_BGR10A2: + { + var colcount = (int)(size / 4); + for (var i = 0; i < colcount; i++) + { + var ofs = i * 4; + + uint w32; + + unchecked + { + w32 = src_data[ofs + 0] | (uint)src_data[ofs + 1] << 8 | (uint)src_data[ofs + 2] << 16 | (uint)src_data[ofs + 3] << 24; + } + + var a = (byte)((w32 & 0xc0000000) >> 24); + var r = (byte)((w32 & 0x3ff00000) >> 22); + var g = (byte)((w32 & 0xffc00) >> 12); + var b = (byte)((w32 & 0x3ff) >> 2); + + src_data[ofs + 0] = r; + src_data[ofs + 1] = g; + src_data[ofs + 2] = b; + src_data[ofs + 3] = (byte)(a == 0xc0 ? 255 : a); + } + } break; + + case DDSFormat.DDS_BGRA8: + { + var colcount = (int)(size / 4); + for (var i = 0; i < colcount; i++) (src_data[i * 4 + 0], src_data[i * 4 + 2]) = (src_data[i * 4 + 2], src_data[i * 4 + 0]); + //SWAP(src_data[i * 4 + 0], src_data[i * 4 + 2]); + } break; + case DDSFormat.DDS_BGR8: + { + var colcount = (int)(size / 3); + for (var i = 0; i < colcount; i++) (src_data[i * 3 + 0], src_data[i * 3 + 2]) = (src_data[i * 3 + 2], src_data[i * 3 + 0]); + //SWAP(src_data[i * 3 + 0], src_data[i * 3 + 2]); + } break; + + case DDSFormat.DDS_LUMINANCE_ALPHA_4: + { + var colcount = (int)(size / 2); + for (var i = colcount - 1; i >= 0; i--) + { + var src_ofs = i; + var dst_ofs = i * 2; + + var l = (byte)(src_data[src_ofs] & 0x0F); + var a = (byte)(src_data[src_ofs] & 0xF0); + + src_data[dst_ofs] = (byte)(l << 4 | l); + src_data[dst_ofs + 1] = (byte)(a | a >> 4); + } + } break; + } + } + var image = Image.CreateFromData((int)width, (int)height, mipmaps > 1, info.format, src_data.ToArray()); + + return image; + } + } +} diff --git a/OpenKh.Godot/Conversion/ModelConverters.cs b/OpenKh.Godot/Conversion/ModelConverters.cs new file mode 100644 index 000000000..4d739232c --- /dev/null +++ b/OpenKh.Godot/Conversion/ModelConverters.cs @@ -0,0 +1,735 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Common; +using Godot.Collections; +using OpenKh.Godot.Animation; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using OpenKh.Kh2.TextureFooter; +using OpenKh.Ps2; +using Array = Godot.Collections.Array; +using Environment = Godot.Environment; + +namespace OpenKh.Godot.Conversion +{ + public static class ModelConverters + { + private const float SortingMultiplier = 0.001f; + + //KH2 entity shaders + private static Shader BasicShader = ResourceLoader.Load("res://Assets/Shaders/KH2BasicShader.gdshader"); + private static Shader AnimatedShader = ResourceLoader.Load("res://Assets/Shaders/KH2AnimatedShader.gdshader"); + + //KH2 world shaders + private static Shader WorldOpaqueShader = ResourceLoader.Load("res://Assets/Shaders/KH2WorldOpaqueShader.gdshader"); + private static Shader WorldAlphaMixShader = ResourceLoader.Load("res://Assets/Shaders/KH2WorldAlphaMixShader.gdshader"); + private static Shader WorldAlphaAddShader = ResourceLoader.Load("res://Assets/Shaders/KH2WorldAlphaAddShader.gdshader"); + private static Shader WorldAlphaSubShader = ResourceLoader.Load("res://Assets/Shaders/KH2WorldAlphaSubShader.gdshader"); + private static Shader WorldAlphaMulShader = ResourceLoader.Load("res://Assets/Shaders/KH2WorldAlphaMulShader.gdshader"); + + private enum AlphaType + { + Mix, + Add, + Sub, + Mul, + Scissor, + Opaque, + } + + public static KH2Mdlx FromMdlx(Bar bar, List hdTexs = null) + { + var usesHdTextures = hdTexs is not null; + + var root = new KH2Mdlx(); + + var skeletalModels = new List(); + var textures = new List(); + var collisions = new List(); + + foreach (var barEntry in bar) + { + try + { + switch (barEntry.Type) + { + case Bar.EntryType.Model: + { + barEntry.Stream.Seek(0x90, SeekOrigin.Begin); + var modelType = barEntry.Stream.ReadInt32(); + barEntry.Stream.Seek(0x0, SeekOrigin.Begin); + + switch (modelType) + { + //TODO: shadow models? i don't need them, but i presume if someone wants to make a tool targeting ps2 or pc port they'll need it + case 3: + skeletalModels.Add(ModelSkeletal.Read(barEntry.Stream)); + break; + } + break; + } + case Bar.EntryType.ModelTexture: + { + textures.Add(ModelTexture.Read(barEntry.Stream)); + break; + } + case Bar.EntryType.ModelCollision: + { + collisions.Add(barEntry.Stream.ReadAllBytes()); + break; + } + } + } + catch + { + // ignored + } + } + + var mapper = new TextureMapper(usesHdTextures ? hdTexs : null); + + GD.Print($"skl {skeletalModels.Count}"); + + if (skeletalModels.Count == 0 || textures.Count == 0) return null; + + var skeleton = FromModelSkeletal(skeletalModels.First(), textures.First(), mapper); + root.AddChild(skeleton); + + root.Skeleton = skeleton; + if (collisions.Count != 0) + { + skeleton.ModelCollisions = new ModelCollisionResource(); + skeleton.ModelCollisions.Binary = collisions.First(); + } + + return root; + } + public static KH2Skeleton3D FromModelSkeletal(ModelSkeletal model, ModelTexture texImg, TextureMapper mapper) + { + mapper.ResetMap(); + + var images = texImg.Images? + .Select(texture => Image.CreateFromData(texture.Size.Width, texture.Size.Height, false, Image.Format.Rgba8, texture.ToBgra32().BGRAToRGBA())) + .Select(ImageTexture.CreateFromImage) + .ToList() ?? []; + + var arrayMesh = new ArrayMesh(); + var skeleton = new KH2Skeleton3D(); + var khmesh = new KH2MeshInstance3D(); + skeleton.AddChild(khmesh); + khmesh.Name = "Model"; + + foreach (var bone in model.Bones) skeleton.AddBone(bone.Index.ToString()); + + var boneCount = skeleton.GetBoneCount(); + + foreach (var bone in model.Bones.Where(i => i.ParentIndex < boneCount && i.ParentIndex >= 0)) skeleton.SetBoneParent(bone.Index, bone.ParentIndex); + foreach (var bone in model.Bones) skeleton.SetBoneRest(bone.Index, bone.Transform()); + + skeleton.BoneList = model.Bones.Select(i => new KH2Bone + { + Sibling = i.SiblingIndex, + Parent = i.ParentIndex, + Child = i.ChildIndex, + Flags = i.Flags, + Scale = new System.Numerics.Vector4(i.ScaleX, i.ScaleY, i.ScaleZ, i.ScaleW), + Rotation = new System.Numerics.Vector4(i.RotationX,i.RotationY,i.RotationZ,i.RotationW), + Position = new System.Numerics.Vector4(i.TranslationX,i.TranslationY,i.TranslationZ,i.TranslationW), + }); + + skeleton.ResetBonePoses(); + + var textureAnimations = 0; + + var animList = new List(); + + for (var m = 0; m < model.Groups.Count; m++) + { + var group = model.Groups[m]; + + var header = group.Header; + var mesh = group.Mesh; + var material = new ShaderMaterial(); + + var texIndex = (int)group.Header.TextureIndex; + + var tex = mapper.GetTexture(texIndex, images[texIndex]); + + var texSize = texImg.Images[texIndex].Size; + var originalSize = new Vector2(texSize.Width, texSize.Height); + + if (texImg.TextureFooterData.TextureAnimationList.Any(i => i.TextureIndex == texIndex)) + { + material.Shader = AnimatedShader; + var find = texImg.TextureFooterData.TextureAnimationList.Where(i => i.TextureIndex == texIndex).ToArray(); + foreach (var animation in find) + { + var uvFront = new Vector2(animation.UOffsetInBaseImage, animation.VOffsetInBaseImage) / originalSize; + var uvBack = new Vector2(animation.UOffsetInBaseImage + animation.SpriteWidth, animation.VOffsetInBaseImage + animation.SpriteHeight) / originalSize; + + var data = ImageDataHelpers.FromIndexed8ToBitmap32(animation.SpriteImage, texImg.Images[texIndex].GetClut(), ImageDataHelpers.RGBA).BGRAToRGBA(); + + var sprite = Image.CreateFromData(animation.SpriteWidth, animation.SpriteHeight * animation.NumSpritesInImageData, false, Image.Format.Rgba8, data); + + var animatedTex = mapper.GetNextTexture(ImageTexture.CreateFromImage(sprite)); + + khmesh.AnimatedTextures.Add(animatedTex); + + material.SetShaderParameter($"Sprite{textureAnimations}", animatedTex); + material.SetShaderParameter($"TextureOriginalUV{textureAnimations}", new Vector4(uvFront.X, uvFront.Y, uvBack.X, uvBack.Y)); + + animList.Add(new KH2TextureAnimation + { + SpriteFrameCount = animation.NumSpritesInImageData, + TextureIndex = texIndex, + DefaultAnimationIndex = animation.DefaultAnimationIndex, + AnimationList = new Array(animation.FrameGroupList.Select(i => new KH2TextureAnimations + { + Frames = new Array(i.IndexedFrameList.Select(j => new KH2TextureAnimationFrame + { + ImageIndex = j.Value.SpriteImageIndex, + JumpDelta = j.Value.FrameIndexDelta, + MaxTime = j.Value.MaximumLength / 60f, + MinTime = j.Value.MinimumLength / 60f, + Operation = new Func(() => j.Value.FrameControl switch + { + TextureFrameControl.EnableSprite => KH2TextureAnimationOperation.EnableSprite, + TextureFrameControl.DisableSprite => KH2TextureAnimationOperation.DisableSprite, + TextureFrameControl.Jump => KH2TextureAnimationOperation.Jump, + TextureFrameControl.Stop => KH2TextureAnimationOperation.Stop, + _ => throw new ArgumentOutOfRangeException(), + }).Invoke(), + ResourceLocalToScene = true, + })), + ResourceLocalToScene = true, + })), + ResourceLocalToScene = true, + }); + + textureAnimations++; + } + } + else + { + material.Shader = BasicShader; + } + + //TODO: + //I believe Alpha refers to Alpha Scissor, as this mode is used on Sora's crown and various strap textures + //I presume AlphaAdd and Sub set the transparency, with Add and Sub modes + //I presume AlphaEx refers to 'alpha extended', and is true alpha blending - ie 'mix' + //Verify if these assumptions are correct, and what happens if they're stacked + + if (header.Alpha) + { + material.SetShaderParameter("Alpha", true); + material.SetShaderParameter("Scissor", true); + } + if (header.AlphaAdd) + { + material.SetShaderParameter("Alpha", true); + } + if (header.AlphaSub) + { + material.SetShaderParameter("Alpha", true); + } + if (header.AlphaEx) + { + material.SetShaderParameter("Alpha", true); + } + + material.SetShaderParameter("Texture", tex); + + var positions = new List(); + var normals = new List(); + var colors = new List(); + var uvs = new List(); + var bones = new List(); + var weights = new List(); + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + + foreach (var verts in mesh.Triangles) + { + foreach (var index in verts.ToArray().Reverse()) + { + var vert = mesh.Vertices[index]; + var pos = vert.Position; + + var normal = vert.Normal is not null ? new Vector3(vert.Normal.X, vert.Normal.Y, vert.Normal.Z) : Vector3.Up; + var color = vert.Color is not null ? new Color(vert.Color.R, vert.Color.G, vert.Color.B, vert.Color.A) : Colors.White; + + var uv = new Vector2(vert.U / 4096, vert.V / 4096); + + positions.Add(new Vector3(pos.X, pos.Y, pos.Z) * ImportHelpers.KH2PositionScale); + normals.Add(normal); + uvs.Add(uv); + colors.Add(color); + + for (var i = 0; i < 4; i++) + { + if (vert.BPositions.Count > i) + { + var p = vert.BPositions[i]; + bones.Add(p.BoneIndex); + weights.Add(p.Position.W == 0 ? 1 : p.Position.W); + } + else + { + bones.Add(0); + weights.Add(0); + } + } + } + } + + array[(int)Mesh.ArrayType.Vertex] = positions.ToArray(); + array[(int)Mesh.ArrayType.Normal] = normals.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = uvs.ToArray(); + array[(int)Mesh.ArrayType.Bones] = bones.ToArray(); + array[(int)Mesh.ArrayType.Weights] = weights.ToArray(); + + arrayMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array, flags: + Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatBones | Mesh.ArrayFormat.FormatNormal | Mesh.ArrayFormat.FormatTexUV | + Mesh.ArrayFormat.FormatWeights); + arrayMesh.SurfaceSetMaterial(m, material); + } + + var maxIndex = mapper.TextureMap.Max(i => i.Key); + + for (var i = 0; i <= maxIndex; i++) + { + var tex = mapper.GetTexture(i, images[i]); + khmesh.Textures.Add(tex); + } + + khmesh.Mesh = arrayMesh; + skeleton.CreateSkinFromRestTransforms(); + skeleton.Mesh = khmesh; + foreach (var anim in animList) khmesh.TextureAnimations.Add(anim); + + return skeleton; + } + public static Node3D FromCoct(Coct collision) + { + var root = new Node3D(); + for (var index = 0; index < collision.Nodes.Count; index++) + { + var node = collision.Nodes[index]; + var body = new Node3D(); + + root.AddChild(body); + + body.Name = $"Node_{index}"; + + for (var index1 = 0; index1 < node.Meshes.Count; index1++) + { + var mesh = node.Meshes[index1]; + + var meshNode = new Node3D(); + body.AddChild(meshNode); + + meshNode.Name = $"Mesh_{index1}"; + + var collisions = new System.Collections.Generic.Dictionary(); + + //staticBody.CollisionLayer = mesh.Group; //TODO + + foreach (var c in mesh.Collisions) + { + var flags = c.Attributes.Flags; + + if (!collisions.TryGetValue(flags, out var shape)) + { + var staticBody = new StaticBody3D(); + meshNode.AddChild(staticBody); + staticBody.Name = $"Collisions_{flags:B16}"; + staticBody.CollisionLayer = (uint)flags; + + var bodyShape = new CollisionShape3D(); + staticBody.AddChild(bodyShape); + bodyShape.Name = "Shape"; + + shape = new ConcavePolygonShape3D(); + shape.SetBackfaceCollisionEnabled(true); + + bodyShape.Shape = shape; + + collisions.Add(flags, shape); + } + + var one = collision.VertexList[c.Vertex1]; + var two = collision.VertexList[c.Vertex2]; + var three = collision.VertexList[c.Vertex3]; + + var vec1 = new Vector3(one.X, -one.Y, -one.Z) * ImportHelpers.KH2PositionScale; + var vec2 = new Vector3(two.X, -two.Y, -two.Z) * ImportHelpers.KH2PositionScale; + var vec3 = new Vector3(three.X, -three.Y, -three.Z) * ImportHelpers.KH2PositionScale; + + var quad = collision.VertexList.Count > c.Vertex4 && c.Vertex4 >= 0; + + if (quad) + { + var four = collision.VertexList[c.Vertex4]; + var vec4 = new Vector3(four.X, -four.Y, -four.Z) * ImportHelpers.KH2PositionScale; + + shape.Data = shape.Data.Concat(new[] + { + vec1, + vec2, + vec3, + vec1, + vec3, + vec4, + }).ToArray(); + } + else + { + shape.Data = shape.Data.Concat(new[] + { + vec1, + vec2, + vec3, + }).ToArray(); + } + } + } + } + return root; + } + public static Node3D FromModelBackground(ModelBackground background, ModelTexture texImg, TextureMapper mapper) + { + var meshRoot = new Node3D(); + var chunkIndex = 0; + + mapper.ResetMap(); + + var images = texImg.Images + .Select(texture => Image.CreateFromData(texture.Size.Width, texture.Size.Height, false, Image.Format.Rgba8, texture.ToBgra32().BGRAToRGBA())) + .Select(ImageTexture.CreateFromImage) + .ToList(); + + var maxId = background.Chunks.Max(i => i.TextureId); + + for (var i = 0; i <= maxId; i++) mapper.GetTexture(i, null); + + foreach (var chunk in background.Chunks) + { + var mesh = new MeshInstance3D(); + meshRoot.AddChild(mesh); + mesh.Name = $"Chunk_{chunkIndex}"; + mesh.CastShadow = GeometryInstance3D.ShadowCastingSetting.Off; + + mesh.SortingOffset = ((chunk.DrawPriority * background.Chunks.Count) + chunkIndex) * SortingMultiplier; + + var arrayMesh = new ArrayMesh(); + + var texIndex = chunk.TextureId; + var alphaType = AlphaType.Opaque; + if (chunk.IsAlphaAdd) alphaType = AlphaType.Add; + else if (chunk.IsAlphaSubtract) alphaType = AlphaType.Sub; + //else if (chunk.IsMulti) alphaType = AlphaType.Mul; + else if (chunk.IsAlpha) alphaType = AlphaType.Mix; + + var vertices = new List<(Vector3 pos, Vector2 uv, Color color)>(); + + var positions = new List(); + var color = new List(); + var uvs = new List(); + + var unpacker = new VifUnpacker(chunk.VifPacket); + while (unpacker.Run() != VifUnpacker.State.End) + { + var currentIndex = vertices.Count; + + using var str = new MemoryStream(unpacker.Memory); + + var vpu = VpuPacket.Read(str); + var useColor = vpu.Colors.Length > 0; + + var colors = useColor + ? vpu.Colors.Select(i => new Color(i.R / 128f, i.G / 128f, i.B / 128f, i.A / 128f)).ToList() + : Enumerable.Repeat(new Color(0.5f, 0.5f, 0.5f), vpu.Indices.Length).ToList(); + + for (var i = 0; i < vpu.Indices.Length; i++) + { + var ind = vpu.Indices[i]; + var pos = vpu.Vertices[ind.Index]; + vertices.Add((new Vector3(pos.X, pos.Y, pos.Z) * ImportHelpers.KH2PositionScale, new Vector2((short)(ushort)ind.U, (short)(ushort)ind.V) / 4096f, colors[i])); + } + + var indices = vpu.Indices; + + var resultPos = new List(); + var resultColor = new List(); + var resultUv = new List(); + + for (var i = 0; i < vpu.Indices.Length; i++) + { + var index = indices[i]; + var func = index.Function; + + var cur = currentIndex + i; + + if (func is VpuPacket.VertexFunction.DrawTriangle or VpuPacket.VertexFunction.DrawTriangleDoubleSided) + { + var first = cur - 1; + var second = cur - 2; + var third = cur; + + resultPos.AddRange([vertices[first].pos, vertices[second].pos, vertices[third].pos]); + resultUv.AddRange([vertices[first].uv, vertices[second].uv, vertices[third].uv]); + resultColor.AddRange([vertices[first].color, vertices[second].color, vertices[third].color]); + } + if (func is VpuPacket.VertexFunction.DrawTriangleInverse or VpuPacket.VertexFunction.DrawTriangleDoubleSided) + { + var first = cur - 2; + var second = cur - 1; + var third = cur; + + resultPos.AddRange([vertices[first].pos, vertices[second].pos, vertices[third].pos]); + resultUv.AddRange([vertices[first].uv, vertices[second].uv, vertices[third].uv]); + resultColor.AddRange([vertices[first].color, vertices[second].color, vertices[third].color]); + } + } + + positions.AddRange(resultPos); + color.AddRange(resultColor); + uvs.AddRange(resultUv); + } + + var nativeImage = images[texIndex]; + + var tex = mapper.GetTexture(texIndex, nativeImage); + + var material = new ShaderMaterial(); + + material.Shader = alphaType switch + { + AlphaType.Mix => WorldAlphaMixShader, + AlphaType.Add => WorldAlphaAddShader, + AlphaType.Sub => WorldAlphaSubShader, + AlphaType.Mul => WorldAlphaMulShader, + _ => WorldOpaqueShader, + }; + + var uvsc = chunk.UVScrollIndex; + + var shiftedUvsc = uvsc >> 1; + + var nativeSize = nativeImage.GetSize(); + + if (shiftedUvsc < texImg.TextureFooterData.UvscList.Count && shiftedUvsc >= 0) + { + var findUvsc = texImg.TextureFooterData.UvscList[shiftedUvsc]; + + var uSpeed = (findUvsc.UScrollSpeed / 20000f) / nativeSize.X; + var vSpeed = (findUvsc.VScrollSpeed / 20000f) / nativeSize.Y; + + material.SetShaderParameter("UVScrollSpeed", new Vector2(uSpeed, vSpeed)); + material.SetShaderParameter("UVScrollEnabled", (float)(uvsc & 1)); + } + + material.SetShaderParameter("Texture", tex); + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + + array[(int)Mesh.ArrayType.Vertex] = positions.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = uvs.ToArray(); + array[(int)Mesh.ArrayType.Color] = color.ToArray(); + + arrayMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array, flags: Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatTexUV | Mesh.ArrayFormat.FormatColor); + arrayMesh.SurfaceSetMaterial(0, material); + + mesh.Mesh = arrayMesh; + + chunkIndex++; + } + + return meshRoot; + } + public static Node3D FromMap(Bar map, List hdTexs = null) + { + foreach (var entry in map) + { + GD.Print($"{entry.Name}, {entry.Type}"); + } + + var usesHdTextures = hdTexs is not null; + + var mainModel = map.FirstOrDefault(i => i.Name == "MAP" && i.Type == Bar.EntryType.Model); + if (mainModel is null) return null; + + var mainModelTextures = map.FirstOrDefault(i => i.Name == "MAP" && i.Type == Bar.EntryType.ModelTexture); + if (mainModelTextures is null) return null; + + var root = new Node3D(); + + var environment = new WorldEnvironment(); + root.AddChild(environment); + environment.Name = "Environment"; + var env = new Environment(); + env.BackgroundMode = Environment.BGMode.Color; + env.BackgroundColor = Colors.Black; + + environment.Environment = env; + + var background = new ModelBackground(new MemoryStream(mainModel.Stream.ReadAllBytes().Skip(0x90).ToArray())); + var texImg = ModelTexture.Read(mainModelTextures.Stream); + + var mapper = new TextureMapper(usesHdTextures ? hdTexs : null); + + var modelBackground = FromModelBackground(background, texImg, mapper); + + root.AddChild(modelBackground); + modelBackground.Name = "Background"; + + var sky0Model = map.FirstOrDefault(i => i.Name == "SK0" && i.Type == Bar.EntryType.Model); + var sky0Textures = map.FirstOrDefault(i => i.Name == "SK0" && i.Type == Bar.EntryType.ModelTexture); + + if (sky0Model is not null && sky0Textures is not null) + { + var sky0Background = new ModelBackground(new MemoryStream(sky0Model.Stream.ReadAllBytes().Skip(0x90).ToArray())); + var sky0TexImg = ModelTexture.Read(sky0Textures.Stream); + + var modelSky0 = FromModelBackground(sky0Background, sky0TexImg, mapper); + root.AddChild(modelSky0); + modelSky0.Name = "Skybox_0"; + } + + var sky1Model = map.FirstOrDefault(i => i.Name == "SK1" && i.Type == Bar.EntryType.Model); + var sky1Textures = map.FirstOrDefault(i => i.Name == "SK1" && i.Type == Bar.EntryType.ModelTexture); + + if (sky1Model is not null && sky1Textures is not null) + { + var sky1Background = new ModelBackground(new MemoryStream(sky1Model.Stream.ReadAllBytes().Skip(0x90).ToArray())); + var sky1TexImg = ModelTexture.Read(sky1Textures.Stream); + + var modelSky1 = FromModelBackground(sky1Background, sky1TexImg, mapper); + root.AddChild(modelSky1); + modelSky1.Name = "Skybox_1"; + } + + var collision = map.FirstOrDefault(i => i.Type == Bar.EntryType.CollisionOctalTree); + + if (collision is not null) + { + var coll = FromCoct(Coct.Read(collision.Stream)); + root.AddChild(coll); + coll.Name = "Collision"; + } + + var bobList = new List<(ModelSkeletal model, ModelTexture texture, byte[] anim)>(); + + for (var i = 0; i < map.Count - 2; i++) + { + var a = map[i]; + var b = map[i + 1]; + var c = map[i + 2]; + + if (a is null || b is null || c is null) continue; + if (a.Name != "BOB" || b.Name != "BOB" || c.Name != "BOB") continue; + if (a.Type != Bar.EntryType.Model || b.Type != Bar.EntryType.ModelTexture || c.Type != Bar.EntryType.AnimationMap) continue; + + var model = ModelSkeletal.Read(a.Stream); + var texture = ModelTexture.Read(b.Stream); + var anim = c.Stream.Length > 0 ? c.Stream.ReadAllBytes() : null; + + var mesh = FromModelSkeletal(model, texture, mapper); + var meshRoot = new Node3D(); + meshRoot.AddChild(mesh); + + bobList.Add((model, texture, anim)); + } + + var bobPlacement = map.FirstOrDefault(i => i.Name == "out" && i.Type == Bar.EntryType.BgObjPlacement); + if (bobPlacement is not null) + { + var bobNode = new Node3D(); + root.AddChild(bobNode); + bobNode.Name = "BackgroundObjects"; + + bobPlacement.Stream.Seek(0, SeekOrigin.Begin); + var bop = Bop.Read(bobPlacement.Stream); + + for (var index = 0; index < bop.Entries.Count; index++) + { + var p = bop.Entries[index]; + if (p.BobIndex >= bobList.Count) continue; + + var bob = bobList[(int)p.BobIndex]; + + var obj = FromModelSkeletal(bob.model, bob.texture, mapper); + + var pos = new Vector3(p.PositionX, -p.PositionY, -p.PositionZ) * ImportHelpers.KH2PositionScale; + var rotation = new Vector3(p.RotationX, p.RotationY, p.RotationZ); + var scale = new Vector3(p.ScaleX, p.ScaleY, p.ScaleZ); + + bobNode.AddChild(obj); + obj.Transform = ImportHelpers.CreateTransform(pos, rotation, scale); + obj.Name = index.ToString(); + + if (bob.anim is not null) //TODO + { + var stream = new MemoryStream(bob.anim); + if (Bar.IsValid(stream)) + { + var animBar = Bar.Read(stream); + var animationEntry = animBar.FirstOrDefault(i => i.Type == Bar.EntryType.Motion); + if (animationEntry is not null) + { + var motion = new InterpolatedMotionResource + { + Binary = animationEntry.Stream.ReadAllBytes() + }; + obj.CurrentAnimation = motion; + obj.Animating = true; + obj.AnimationTime = p.MotionOffset / 60f; //TODO: 60 or 30? + } + } + + /* + obj.CurrentAnimation = new AnimationBinaryResource(){ Binary = bob.anim }; + obj.Animating = true; + */ + } + } + } + + return root; + } + + public static KH2Moveset FromMoveset(Bar mset) + { + var container = new KH2Moveset(); + + + + container.Entries = new Array(mset.Select(entry => entry?.Stream is null || entry.Stream.Length == 0 + ? null + : new KH2MovesetEntry + { + Motion = new Func(() => + { + var bar = Bar.Read(entry.Stream); + var a = new InterpolatedMotionResource + { + Binary = bar.FirstOrDefault(i => i.Type == Bar.EntryType.Motion)?.Stream.ReadAllBytes(), + }; + return a; + }).Invoke(), + })); + + return container; + } + } +} diff --git a/OpenKh.Godot/Conversion/TextureConverters.cs b/OpenKh.Godot/Conversion/TextureConverters.cs new file mode 100644 index 000000000..1d347049d --- /dev/null +++ b/OpenKh.Godot/Conversion/TextureConverters.cs @@ -0,0 +1,36 @@ +using Godot; +using OpenKh.Godot.Helpers; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Extensions; + +namespace OpenKh.Godot.Conversion +{ + public static class TextureConverters + { + public static Texture2D FromImgd(Imgd imgd) + { + var data = imgd.GetData(); + + switch (imgd.PixelFormat) + { + case PixelFormat.Indexed4: + { + var img = ImageDataHelpers.FromIndexed4ToBitmap32(data, imgd.GetClut(), ImageDataHelpers.RGBA); + return ImageTexture.CreateFromImage(Image.CreateFromData(imgd.Size.Width, imgd.Size.Height, false, Image.Format.Rgba8, img.BGRAToRGBA())); + } + case PixelFormat.Indexed8: + { + var img = ImageDataHelpers.FromIndexed8ToBitmap32(data, imgd.GetClut(), ImageDataHelpers.RGBA); + return ImageTexture.CreateFromImage(Image.CreateFromData(imgd.Size.Width, imgd.Size.Height, false, Image.Format.Rgba8, img.BGRAToRGBA())); + } + case PixelFormat.Rgba8888: + { + return ImageTexture.CreateFromImage(Image.CreateFromData(imgd.Size.Width, imgd.Size.Height, false, Image.Format.Rgba8, data.BGRAToRGBA())); + } + } + return null; + } + public static Texture2D FromTm2(Tm2 tm2) => FromImgd(tm2.AsImgd()); //TODO: theres a better way to do this but im lazy + } +} diff --git a/OpenKh.Godot/Helpers/AnimationHelpers.cs b/OpenKh.Godot/Helpers/AnimationHelpers.cs new file mode 100644 index 000000000..0be357f43 --- /dev/null +++ b/OpenKh.Godot/Helpers/AnimationHelpers.cs @@ -0,0 +1,660 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Godot; +using OpenKh.Godot.Nodes; +using OpenKh.Kh2; +using Quaternion = Godot.Quaternion; +using Vector3 = Godot.Vector3; +using Vector4 = System.Numerics.Vector4; + +namespace OpenKh.Godot.Helpers +{ + public static class AnimationHelpers + { + public static bool IsPositionChannel(this Motion.Channel channel) => channel is Motion.Channel.TRANSLATION_X or Motion.Channel.TRANSLATION_Y or Motion.Channel.TRANSLATION_Z; + public static bool IsRotationChannel(this Motion.Channel channel) => channel is Motion.Channel.ROTATION_X or Motion.Channel.ROTATION_Y or Motion.Channel.ROTATION_Z; + public static bool IsScaleChannel(this Motion.Channel channel) => channel is Motion.Channel.SCALE_X or Motion.Channel.SCALE_Y or Motion.Channel.SCALE_Z; + + public static bool IsXChannel(this Motion.Channel channel) => channel is Motion.Channel.TRANSLATION_X or Motion.Channel.ROTATION_X or Motion.Channel.SCALE_X; + public static bool IsYChannel(this Motion.Channel channel) => channel is Motion.Channel.TRANSLATION_Y or Motion.Channel.ROTATION_Y or Motion.Channel.SCALE_Y; + public static bool IsZChannel(this Motion.Channel channel) => channel is Motion.Channel.TRANSLATION_Z or Motion.Channel.ROTATION_Z or Motion.Channel.SCALE_Z; + + public static float CubicHermite(float t, float p0, float p1, float m0, float m1) + { + var t2 = t * t; + var t3 = t2 * t; + return (2 * t3 - 3 * t2 + 1) * p0 + (t3 - 2 * t2 + t) * m0 + (-2 * t3 + 3 * t2) * p1 + (t3 - t2) * m1; + } + + public static Quaternion AnimationRotation(Vector3 rotation) => ImportHelpers.CommonRotation(rotation.X, rotation.Y, rotation.Z); + public static Transform3D CreateTransform(Vector3 pos, Vector3 rot, Vector3 scale) + { + var scaleTransform = Transform3D.Identity.Scaled(scale); + var rotationTransform = new Transform3D(new Basis(AnimationRotation(rot).Normalized()), Vector3.Zero); + var translationTransform = Transform3D.Identity.Translated(pos); + + return translationTransform * rotationTransform * scaleTransform; + } + + private class AnimationSimulation + { + private Vector4[] Positions; + private Vector4[] Rotations; + private Vector4[] Scales; + private int[] Parent; + private int[] Siblings; + private int[] Children; + private Transform3D[] Transforms; + private int BoneCount; + private int IKCount; + private int TotalCount; + private Motion.InterpolatedMotion MotionFile; + private float Time; + private bool IgnoreScale; + + private Transform3D GetGlobalTransform(int index) + { + if (index < 0) return Transform3D.Identity; + var currentTransform = Transforms[index]; + var parent = Parent[index]; + if (parent < 0) return currentTransform; + return GetGlobalTransform(parent) * currentTransform; + } + private void SetGlobalTransform(int index, Transform3D value) + { + var parent = Parent[index]; + if (parent < 0) Transforms[index] = value; + var parentTransform = GetGlobalTransform(parent); + var newTransform = parentTransform.AffineInverse() * value; + Transforms[index] = newTransform; + } + private void SetBoneValue(Motion.Channel channel, int index, float value) + { + Vector4[] array; + if (channel.IsPositionChannel()) array = Positions; + else if (channel.IsRotationChannel()) array = Rotations; + else array = Scales; + + if (channel.IsXChannel()) array[index].X = value; + else if (channel.IsYChannel()) array[index].Y = value; + else array[index].Z = value; + } + private void ExpressionSetBoneValue(Motion.Channel channel, int index, float value) + { + var val = value; + Vector4[] array; + if (channel.IsPositionChannel()) array = Positions; + else if (channel.IsRotationChannel()) + { + array = Rotations; + val = Mathf.DegToRad(val); + } + else array = Scales; + + if (channel.IsXChannel()) array[index].X = val; + else if (channel.IsYChannel()) array[index].Y = val; + else array[index].Z = val; + } + private void CalculateMatrices() + { + for (var i = 0; i < TotalCount; i++) CalculateMatrix(i); + } + private void CalculateMatrix(int i) => Transforms[i] = CreateTransform(Positions[i].ToGodot().XYZ(), Rotations[i].ToGodot().XYZ(), Scales[i].ToGodot().XYZ()); + + public AnimationSimulation(Motion.InterpolatedMotion motion, KH2Skeleton3D skeleton, float time) + { + MotionFile = motion; + Time = time; + + IgnoreScale = motion.MotionHeader.SubType == 1; + + var skeletonBoneCount = skeleton.GetBoneCount(); + + var totalCount = motion.Joints.Count; + + BoneCount = motion.InterpolatedMotionHeader.BoneCount; + TotalCount = motion.InterpolatedMotionHeader.TotalBoneCount; + IKCount = TotalCount - BoneCount; + + Positions = Enumerable.Repeat(Vector4.Zero, totalCount).ToArray(); + Rotations = Enumerable.Repeat(Vector4.Zero, totalCount).ToArray(); + Scales = Enumerable.Repeat(Vector4.One, totalCount).ToArray(); + Parent = Enumerable.Repeat(-1, totalCount).ToArray(); + Siblings = Enumerable.Repeat(-1, totalCount).ToArray(); + Children = Enumerable.Repeat(-1, totalCount).ToArray(); + Transforms = Enumerable.Repeat(Transform3D.Identity, totalCount).ToArray(); + + var kh2Skeleton = skeleton.BoneList.ToList(); + + for (var i = 0; i < motion.InterpolatedMotionHeader.BoneCount; i++) + { + if (i >= skeletonBoneCount) break; + var khBone = kh2Skeleton[i]; + + Positions[i] = khBone.Position; + Rotations[i] = khBone.Rotation; + Scales[i] = khBone.Scale; + Parent[i] = khBone.Parent; + Children[i] = khBone.Child; + Siblings[i] = khBone.Sibling; + } + + foreach (var ikHelper in motion.IKHelpers) + { + var index = ikHelper.Index; //TODO + Positions[index] = new Vector4(ikHelper.TranslateX, ikHelper.TranslateY, ikHelper.TranslateZ, ikHelper.TranslateW); + Rotations[index] = new Vector4(ikHelper.RotateX, ikHelper.RotateY, ikHelper.RotateZ, ikHelper.RotateW); + Scales[index] = new Vector4(ikHelper.ScaleX, ikHelper.ScaleY, ikHelper.ScaleZ, ikHelper.ScaleW); + Parent[index] = ikHelper.ParentId; + Children[index] = ikHelper.ChildId; + Siblings[index] = ikHelper.SiblingId; + } + + //apply rest values + foreach (var rest in motion.InitialPoses) SetBoneValue(rest.ChannelValue, rest.BoneId, rest.Value); + + //apply fcurves + foreach (var fCurve in motion.FCurvesForward) HandleCurve(fCurve); + foreach (var fCurve in motion.FCurvesInverse) HandleCurve(fCurve, true); + + CalculateMatrices(); + + var currentExpression = 0; + var currentConstraint = 0; + foreach (var joint in motion.Joints) + { + var i = joint.JointId; + while (true) + { + if (currentExpression >= motion.Expressions.Count) break; + var expression = motion.Expressions[currentExpression]; + if (expression.TargetId != i) break; + ExpressionSetBoneValue((Motion.Channel)expression.TargetChannel, i, Expression(motion.ExpressionNodes, expression.NodeId)); + CalculateMatrix(i); + currentExpression++; + } + while (true) + { + if (currentConstraint >= motion.Constraints.Count) break; + var constraint = motion.Constraints[currentConstraint]; + if (constraint.ConstrainedJointId != i) break; + CalculateConstraint(constraint); + //CalculateMatrix(i); + currentConstraint++; + } + } + + /* + foreach (var expression in motion.Expressions) + { + ExpressionSetBoneValue((Motion.Channel)expression.TargetChannel, expression.TargetId, Expression(motion.ExpressionNodes, expression.NodeId)); + CalculateMatrix(expression.TargetId); + } + foreach (var constraint in motion.Constraints) CalculateConstraint(constraint); + */ + + //apply to skeleton + for (var i = 0; i < TotalCount; i++) + { + DebugDraw3D.DrawPosition(GetGlobalTransform(i).Scaled(Vector3.One * ImportHelpers.KH2PositionScale), i < motion.InterpolatedMotionHeader.BoneCount ? Colors.Aqua : Colors.DarkRed); + + if (i >= BoneCount) continue; + + var transform = Transforms[i]; + transform = transform with + { + Origin = transform.Origin * ImportHelpers.KH2PositionScale, + }; + skeleton.SetBonePose(i, transform); + } + + /* + var collisionBones = new List(); + + if (skeleton.ModelCollisions is not null) + { + foreach (var collision in skeleton.ModelCollisions.Value.EntryList.Where(collision => (ObjectCollision.TypeEnum)collision.Type == ObjectCollision.TypeEnum.IK)) + { + //TODO: actual collision + var centerBone = collision.Bone; + var firstBone = Parent[centerBone]; + var tipBone = Children[centerBone]; + + collisionBones.AddRange([centerBone, firstBone, tipBone]); + + CalculateIK(skeleton, firstBone, centerBone, tipBone, false); + } + } + + for (var i = 0; i < motion.Joints.Count - 2; i++) + { + var a = motion.Joints[i]; //first + var b = motion.Joints[i + 1]; //center + var c = motion.Joints[i + 2]; //tip + + var firstBone = a.JointId; + var centerBone = b.JointId; + var tipBone = c.JointId; + + if ((a.CalcMatrix2Rot && b.Trans && c.Calculated) && + (firstBone < centerBone && centerBone < tipBone) && + (!collisionBones.Contains(firstBone) && !collisionBones.Contains(centerBone) && !collisionBones.Contains(tipBone))) + { + CalculateIK(skeleton, firstBone, centerBone, tipBone, true); + } + } + */ + } + private void CalculateConstraint(Motion.Constraint constraint) + { + var motion = MotionFile; + var source = constraint.SourceJointId; + var target = constraint.ConstrainedJointId; + + var limiter = constraint.LimiterId == -1 ? null : motion.Limiters[constraint.LimiterId]; + + if (constraint.ActivationCount > 0) + { + var activators = motion.ConstraintActivations.Skip(constraint.ActivationStartId - 1).Take(constraint.ActivationCount); + var current = activators.Where(i => i.Time <= Time).MaxBy(i => i.Time); + var currentActive = current is not null && current.Active > 0; + if (!currentActive) return; + } + + var type = constraint.Type; + // 0 Position Set global position to another bone + // 1 Path Follow a path TODO: uhhhhh? + // 2 Orientation Set global rotation to another bone + // 3 Direction Point an object at another object, using a specified axis. TODO how do we know what axis, it's default is X + // 4 Up Vector Point an object at another object, using the Y axis. + // 5 Two Points Set global position between two other bones + // 6 Scale Set global scale to another bone + // 7 Camera Custom, no idea, probably put the bone directly on the camera? + // 8 Camera Path Custom, no idea + // 9 Int Path Custom, no idea + // 10 Int Custom, no idea + // 11 Camera Up Vector Custom, no idea + // 12 Position Limit + // 13 Rotation Limit + + switch (type) + { + case 0: + { + SetGlobalTransform(target, GetGlobalTransform(target) with { Origin = GetGlobalTransform(source).Origin }); + break; + } + case 2: + { + var targetRot = GetGlobalTransform(source).Basis.GetRotationQuaternion(); + + var current = GetGlobalTransform(target); + SetGlobalTransform(target, current with { Basis = new Basis(targetRot).Scaled(current.Basis.Scale) }); + break; + } + case 3: + case 4: + { + var sourceTransform = GetGlobalTransform(source); + var currentParentTransform = GetGlobalTransform(Parent[target]); + var current = GetGlobalTransform(target); + var up = (currentParentTransform.Basis * Vector3.Up).Normalized(); + + if (sourceTransform.Origin.Normalized().Abs().IsEqualApprox(up)) + { + //TODO: point upward or downward + GD.Print("IMPLEMENT: point upward"); + } + else + { + var offset = type == 3 ? Quaternion.FromEuler(new Vector3(0, Mathf.Pi / 2, 0)) : Quaternion.FromEuler(new Vector3(Mathf.Pi / 2, 0, 0)); + + var newTransform = current.LookingAt(sourceTransform.Origin, up) * new Transform3D(new Basis(offset), Vector3.Zero); //TODO: is this correct? + SetGlobalTransform(target, newTransform); + } + + break; + } + case 6: + { + var targetScale = GetGlobalTransform(source).Basis.Scale; + + var current = GetGlobalTransform(target); + SetGlobalTransform(target, current with { Basis = new Basis(current.Basis.GetRotationQuaternion()).Scaled(targetScale) }); + break; + } + case 12: + { + if (limiter is null) return; + + var sourceTransform = GetGlobalTransform(source); + var targetTransform = GetGlobalTransform(target); + + var targetRelative = sourceTransform.AffineInverse() * targetTransform; + + //GD.Print($"position type {(int)limiter.Type} min {limiter.MinX} {limiter.MinY} {limiter.MinZ} {limiter.MinW} max {limiter.MaxX} {limiter.MaxY} {limiter.MaxZ} {limiter.MaxW}"); + + //if (limiter.Type == Motion.LimiterType.Box) + if (true) + { + var pos = targetRelative.Origin.Max(new Vector3(limiter.MinX, limiter.MinY, limiter.MinZ)).Min(new Vector3(limiter.MaxX, limiter.MaxY, limiter.MaxZ)); + var newRelative = targetRelative with { Origin = pos }; + var rel = sourceTransform * newRelative; + //SetGlobalTransform(target, rel); + } + else //sphere + { + var length = targetRelative.Origin.Length(); + + } + break; + } + case 13: + { + if (limiter is null) return; + + var transform = Transforms[target]; + var rotation = transform.Basis.GetRotationQuaternion().GetEuler(EulerOrder.Zyx); + rotation = rotation.Clamp(new Vector3(limiter.MinX, limiter.MinY, limiter.MinZ), new Vector3(limiter.MaxX, limiter.MaxY, limiter.MaxZ)); + + //GD.Print($"rotation type {(int)limiter.Type} min {limiter.MinX} {limiter.MinY} {limiter.MinZ} {limiter.MinW} max {limiter.MaxX} {limiter.MaxY} {limiter.MaxZ} {limiter.MaxW}"); + + Transforms[target] = CreateTransform(transform.Origin, rotation, transform.Basis.Scale); + + //TODO: ehhhh? + + break; + } + default: + { + GD.Print($"not implemented constraint: {type}"); + break; + } + } + } + private void CalculateIK(Skeleton3D skeleton, int first, int center, int tip, bool flip) + { + { + var sourceTransform = GetGlobalTransform(tip); + var currentParentTransform = GetGlobalTransform(Parent[first]); + var current = GetGlobalTransform(first); + var up = (currentParentTransform.Basis * Vector3.Up).Normalized(); + + if (sourceTransform.Origin.Normalized().Abs().IsEqualApprox(up)) + { + } + else + { + var offset = Quaternion.FromEuler(new Vector3(Mathf.Pi / 2, 0, 0)); + + var newTransform = current.LookingAt(sourceTransform.Origin, up) * new Transform3D(new Basis(offset), Vector3.Zero); //TODO: is this correct? + SetGlobalTransform(first, newTransform); + } + } + + var targetTransform = skeleton.GetBoneGlobalPose(tip); + skeleton.SetBonePoseRotation(center, Quaternion.Identity); + skeleton.ResetBonePose(center); + skeleton.ResetBonePose(tip); + + var firstGlobalPose = skeleton.GetBoneGlobalPose(first); + var centerGlobalPose = skeleton.GetBoneGlobalPose(center); + var tipGlobalPose = skeleton.GetBoneGlobalPose(tip); + + var firstToTarget = firstGlobalPose.Origin.DistanceTo(targetTransform.Origin); + var firstBoneLength = firstGlobalPose.Origin.DistanceTo(centerGlobalPose.Origin); + var secondBoneLength = centerGlobalPose.Origin.DistanceTo(tipGlobalPose.Origin); + + if (firstBoneLength + secondBoneLength < firstToTarget) return; + + var aAngle = Mathf.Acos(((firstBoneLength*firstBoneLength)+(secondBoneLength*secondBoneLength)-(firstToTarget*firstToTarget)) / (2 * firstBoneLength * secondBoneLength)); + var bAngle = Mathf.Acos(((firstToTarget*firstToTarget)+(secondBoneLength*secondBoneLength)-(firstBoneLength*firstBoneLength)) / (2 * firstToTarget * secondBoneLength)); + var cAngle = Mathf.Acos(((firstToTarget*firstToTarget)+(firstBoneLength*firstBoneLength)-(secondBoneLength*secondBoneLength)) / (2 * firstToTarget * firstBoneLength)); + + var firstAngle = -cAngle; + var centerAngle = Mathf.Pi - aAngle; + //var transformAngle = bAngle; + + if (flip) + { + centerAngle *= -1; + firstAngle *= -1; + } + + skeleton.SetBonePoseRotation(center, Quaternion.FromEuler(new Vector3(0,0,centerAngle))); + + //skeleton.SetBoneGlobalPose(first, new Transform3D(new Basis(targetTransform.Basis.GetRotationQuaternion()).Scaled(firstGlobalPose.Basis.Scale), firstGlobalPose.Origin)); + + skeleton.SetBonePoseRotation(first, skeleton.GetBonePoseRotation(first) * Quaternion.FromEuler(new Vector3(0,0,firstAngle))); + skeleton.SetBoneGlobalPose(tip, targetTransform); + + //GD.Print($"{rotationDiff}, {rotationDiff2}, {rotationDiff3}, {rotationDiff4}"); + } + private float GetCurveValue(Motion.FCurve fCurve, float time) + { + var keyCount = fCurve.KeyCount; + var keys = MotionFile.FCurveKeys; + + for (var i = keyCount - 1; i >= 0; i--) + { + var keyId = fCurve.KeyStartId + i; + + var key = keys[keyId]; + var keyTime = MotionFile.KeyTimes[key.Time]; + + if (keyTime > time) continue; + + var nextKey = i < keyCount - 1 ? keys[keyId + 1] : keys[fCurve.KeyStartId]; + var nextKeyTime = MotionFile.KeyTimes[nextKey.Time]; + + var preType = fCurve.Pre; + var postType = fCurve.Post; + + var n = Mathf.Remap(time, keyTime, nextKeyTime, 0, 1); + + var value = key.Type switch + { + Motion.Interpolation.Nearest => MotionFile.KeyValues[key.ValueId], + Motion.Interpolation.Linear => Mathf.Lerp(MotionFile.KeyValues[key.ValueId], MotionFile.KeyValues[nextKey.ValueId], n), + Motion.Interpolation.Hermite or Motion.Interpolation.Hermite3 or Motion.Interpolation.Hermite4 => CubicHermite(n, MotionFile.KeyValues[key.ValueId], + MotionFile.KeyValues[nextKey.ValueId], MotionFile.KeyTangents[key.LeftTangentId], MotionFile.KeyTangents[key.RightTangentId]), + _ => 0, + }; + + return value; + } + return 0; + } + private void HandleCurve(Motion.FCurve fCurve, bool ik = false) + { + var bone = ik ? fCurve.JointId + BoneCount : fCurve.JointId; + var channel = fCurve.ChannelValue; + + Vector4[] array; + if (channel.IsPositionChannel()) array = Positions; + else if (channel.IsRotationChannel()) array = Rotations; + else array = Scales; + + var value = GetCurveValue(fCurve, Time); + + if (channel.IsXChannel()) array[bone].X = value; + else if (channel.IsYChannel()) array[bone].Y = value; + else array[bone].Z = value; + } + + //GD.Print($"{(Motion.ExpressionType)node.Type}, {node.Element}, {node.IsGlobal}, {node.CAR}, {node.CDR}, {node.Value}"); + //GD.Print($"{boneCount}, {motion.InterpolatedMotionHeader.TotalBoneCount}"); + + private float Expression(List expressionNodes, int index) + { + if (index < 0 || index >= expressionNodes.Count) return 0; + var node = expressionNodes[index]; + var nodeType = (Motion.ExpressionType)node.Type; + + switch (nodeType) + { + case Motion.ExpressionType.FUNC_SIN: return Mathf.Sin(Mathf.DegToRad(Expression(expressionNodes, node.CAR))); + case Motion.ExpressionType.FUNC_COS: return Mathf.Cos(Mathf.DegToRad(Expression(expressionNodes, node.CAR))); + case Motion.ExpressionType.FUNC_TAN: return Mathf.Tan(Mathf.DegToRad(Expression(expressionNodes, node.CAR))); + case Motion.ExpressionType.FUNC_ASIN: + { + var val = Expression(expressionNodes, node.CAR); + return val switch + { + >= 1 => 90, + <= -1 => -90, + _ => Mathf.RadToDeg(Mathf.Asin(val)), + }; + } + case Motion.ExpressionType.FUNC_ACOS: + { + var val = Expression(expressionNodes, node.CAR); + return val switch + { + >= 1 => 0, + <= -1 => 180, + _ => Mathf.RadToDeg(Mathf.Acos(val)), + }; + } + case Motion.ExpressionType.FUNC_ATAN: return Mathf.RadToDeg(Mathf.Atan(Expression(expressionNodes, node.CAR))); + case Motion.ExpressionType.FUNC_LOG: return Mathf.Log(Expression(expressionNodes, node.CAR)); + case Motion.ExpressionType.FUNC_EXP: return Mathf.Exp(Expression(expressionNodes, node.CAR)); + case Motion.ExpressionType.FUNC_ABS: return Mathf.Abs(Expression(expressionNodes, node.CAR)); + case Motion.ExpressionType.FUNC_POW: return Mathf.Pow(Expression(expressionNodes, node.CAR), Expression(expressionNodes, node.CDR)); + case Motion.ExpressionType.FUNC_SQRT: return Mathf.Sqrt(Mathf.Abs(Expression(expressionNodes, node.CAR))); //according to debug symbols, we abs before doing sqrt + case Motion.ExpressionType.FUNC_MIN: return Mathf.Min(Expression(expressionNodes, node.CAR), Expression(expressionNodes, node.CDR)); + case Motion.ExpressionType.FUNC_MAX: return Mathf.Max(Expression(expressionNodes, node.CAR), Expression(expressionNodes, node.CDR)); + //case Motion.ExpressionType.FUNC_AV: return (Expression(expressionNodes, node.CAR) + Expression(expressionNodes, node.CDR)) * 0.5f; + case Motion.ExpressionType.FUNC_AV: + { + var a = node.CAR; + var b = node.CDR; + if (a >= 0 && (Motion.ExpressionType)expressionNodes[a].Type == Motion.ExpressionType.LIST && b < 0) + { + var list = ListExpression(expressionNodes, a); + if (list.Count == 0) return 0; + return list.Sum() / list.Count; + } + break; + } + case Motion.ExpressionType.FUNC_COND: + { + var a = node.CAR; + var b = node.CDR; + if (a >= 0 && (Motion.ExpressionType)expressionNodes[a].Type == Motion.ExpressionType.LIST && b < 0) + { + var list = ListExpression(expressionNodes, a); + if (list.Count == 3) return list[0] >= 0 ? list[1] : list[2]; + GD.Print($"List count 3 expected, actual: {list.Count}"); + } + break; + } + case Motion.ExpressionType.FUNC_AT_FRAME: + case Motion.ExpressionType.FUNC_AT_FRAME_ROT: + { + var time = Expression(expressionNodes, node.CAR); + time = (time * 60) / MotionFile.InterpolatedMotionHeader.FrameData.FramesPerSecond; + + var curve = (int)Expression(expressionNodes, node.CDR); + var value = GetCurveValue(curve < MotionFile.FCurvesForward.Count ? + MotionFile.FCurvesForward[curve] : + MotionFile.FCurvesInverse[curve - MotionFile.FCurvesForward.Count], + time); + + return nodeType == Motion.ExpressionType.FUNC_AT_FRAME_ROT ? Mathf.RadToDeg(value) : value; + } + case Motion.ExpressionType.FUNC_CTR_DIST: + { + var bone1 = node.CAR; + var bone2 = node.CDR; + if (bone1 >= TotalCount) bone1 = 0; + if (bone2 >= TotalCount) bone2 = 0; + + CalculateMatrices(); + + var trans1 = GetGlobalTransform(bone1); + var trans2 = GetGlobalTransform(bone2); + + return trans1.Origin.DistanceTo(trans2.Origin); + } + case Motion.ExpressionType.FUNC_FMOD: return Expression(expressionNodes, node.CAR) % Expression(expressionNodes, node.CDR); //fmod returns the mod without casting + case Motion.ExpressionType.OP_PLUS: return Expression(expressionNodes, node.CAR) + Expression(expressionNodes, node.CDR); + case Motion.ExpressionType.OP_MINUS: return Expression(expressionNodes, node.CAR) - Expression(expressionNodes, node.CDR); + case Motion.ExpressionType.OP_MUL: return Expression(expressionNodes, node.CAR) * Expression(expressionNodes, node.CDR); + case Motion.ExpressionType.OP_DIV: return Expression(expressionNodes, node.CAR) / Expression(expressionNodes, node.CDR); //according to debug symbols, throw on divide by 0 + case Motion.ExpressionType.OP_MOD: return Mathf.RoundToInt(Expression(expressionNodes, node.CAR)) % Mathf.RoundToInt(Expression(expressionNodes, node.CDR)); //mod rounds before modulo, in softimage this is described as a "cast to the nearest int", TODO is this a floor, round, or ceil? + case Motion.ExpressionType.OP_EQ: return Expression(expressionNodes, node.CAR) == Expression(expressionNodes, node.CDR) ? 1 : 0; + case Motion.ExpressionType.OP_GT: return Expression(expressionNodes, node.CAR) > Expression(expressionNodes, node.CDR) ? 1 : 0; + case Motion.ExpressionType.OP_GE: return Expression(expressionNodes, node.CAR) >= Expression(expressionNodes, node.CDR) ? 1 : 0; + case Motion.ExpressionType.OP_LT: return Expression(expressionNodes, node.CAR) < Expression(expressionNodes, node.CDR) ? 1 : 0; + case Motion.ExpressionType.OP_LE: return Expression(expressionNodes, node.CAR) <= Expression(expressionNodes, node.CDR) ? 1 : 0; + case Motion.ExpressionType.OP_AND: return Expression(expressionNodes, node.CAR) >= 1 && Expression(expressionNodes, node.CDR) >= 1 ? 1 : 0; + case Motion.ExpressionType.OP_OR: return Expression(expressionNodes, node.CAR) >= 1 || Expression(expressionNodes, node.CDR) >= 1 ? 1 : 0; + case Motion.ExpressionType.VARIABLE_FC: return Time; + case Motion.ExpressionType.CONSTANT_NUM: return node.Value; + case Motion.ExpressionType.FCURVE_ETRNX: return Positions[node.Element].X; + case Motion.ExpressionType.FCURVE_ETRNY: return Positions[node.Element].Y; + case Motion.ExpressionType.FCURVE_ETRNZ: return Positions[node.Element].Z; + case Motion.ExpressionType.FCURVE_ROTX: return Mathf.RadToDeg(Rotations[node.Element].X); + case Motion.ExpressionType.FCURVE_ROTY: return Mathf.RadToDeg(Rotations[node.Element].Y); + case Motion.ExpressionType.FCURVE_ROTZ: return Mathf.RadToDeg(Rotations[node.Element].Z); + case Motion.ExpressionType.FCURVE_SCALX: return Scales[node.Element].X; + case Motion.ExpressionType.FCURVE_SCALY: return Scales[node.Element].Y; + case Motion.ExpressionType.FCURVE_SCALZ: return Scales[node.Element].Z; + case Motion.ExpressionType.LIST: + //this is a special case, don't handle it here, if this case is met something went wrong + break; + case Motion.ExpressionType.ELEMENT_NAME: return 0; //this actually does literally nothing lmao + default: + { + GD.Print( + $"Unknown node: (Type: {(Motion.ExpressionType)node.Type}, Element Value: {node.Element}, Global: {node.IsGlobal}, First connection index: {node.CAR}, second connection index: {node.CDR}, float value: {node.Value})"); + break; + } + } + return 0; + } + private List ListExpression(List expressionNodes, int index) + { + var node = expressionNodes[index]; + var a = node.CAR; + var b = node.CDR; + var list = new List(); + if (a >= 0) + { + var aNode = expressionNodes[a]; + if ((Motion.ExpressionType)aNode.Type == Motion.ExpressionType.LIST) list.AddRange(ListExpression(expressionNodes, a)); + else list.Add(Expression(expressionNodes, a)); + } + if (b >= 0) + { + var bNode = expressionNodes[b]; + if ((Motion.ExpressionType)bNode.Type == Motion.ExpressionType.LIST) list.AddRange(ListExpression(expressionNodes, b)); + else list.Add(Expression(expressionNodes, b)); + } + return list; + } + } + + public static float Loop(Motion.InterpolatedMotion motion, float time) + { + var frameTime = time * motion.InterpolatedMotionHeader.FrameData.FramesPerSecond; + var diff = 60 / motion.InterpolatedMotionHeader.FrameData.FramesPerSecond; + + var actualFrame = Mathf.Wrap(frameTime * diff, motion.InterpolatedMotionHeader.FrameData.FrameStart * diff, motion.InterpolatedMotionHeader.FrameData.FrameEnd * diff); + + return (actualFrame / diff) / motion.InterpolatedMotionHeader.FrameData.FramesPerSecond; + } + public static void ApplyInterpolatedMotion(Motion.InterpolatedMotion motion, KH2Skeleton3D skeleton, float time) + { + var frameTime = time * motion.InterpolatedMotionHeader.FrameData.FramesPerSecond; + var diff = 60 / motion.InterpolatedMotionHeader.FrameData.FramesPerSecond; + + var actualFrame = Mathf.Wrap(frameTime * diff, motion.InterpolatedMotionHeader.FrameData.FrameStart * diff, motion.InterpolatedMotionHeader.FrameData.FrameEnd * diff); + + var sim = new AnimationSimulation(motion, skeleton, actualFrame); + } + } +} diff --git a/OpenKh.Godot/Helpers/CastingHelpers.cs b/OpenKh.Godot/Helpers/CastingHelpers.cs new file mode 100644 index 000000000..504a40586 --- /dev/null +++ b/OpenKh.Godot/Helpers/CastingHelpers.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace OpenKh.Godot.Helpers; + +public static class CastingHelpers +{ + public static Vector4 ToGodot(this System.Numerics.Vector4 vec) => new(vec.X, vec.Y, vec.Z, vec.W); + public static System.Numerics.Vector4 ToSystem(this Vector4 vec) => new(vec.X, vec.Y, vec.Z, vec.W); + + public static Vector3 ToGodot(this System.Numerics.Vector3 vec) => new(vec.X, vec.Y, vec.Z); + public static System.Numerics.Vector3 ToSystem(this Vector3 vec) => new(vec.X, vec.Y, vec.Z); + + public static Vector2 ToGodot(this System.Numerics.Vector2 vec) => new(vec.X, vec.Y); + public static System.Numerics.Vector2 ToSystem(this Vector2 vec) => new(vec.X, vec.Y); + + //TODO: more swizzles + public static Vector3 XYZ(this Vector4 vec) => new(vec.X, vec.Y, vec.Z); + public static Vector2 XY(this Vector3 vec) => new(vec.X, vec.Y); + public static Vector2 XZ(this Vector3 vec) => new(vec.X, vec.Z); + public static System.Numerics.Vector3 XYZ(this System.Numerics.Vector4 vec) => new(vec.X, vec.Y, vec.Z); + public static System.Numerics.Vector2 XY(this System.Numerics.Vector3 vec) => new(vec.X, vec.Y); + public static System.Numerics.Vector2 XZ(this System.Numerics.Vector3 vec) => new(vec.X, vec.Z); +} diff --git a/OpenKh.Godot/Helpers/CollisionHelpers.cs b/OpenKh.Godot/Helpers/CollisionHelpers.cs new file mode 100644 index 000000000..27d264ee3 --- /dev/null +++ b/OpenKh.Godot/Helpers/CollisionHelpers.cs @@ -0,0 +1,33 @@ +using System; + +namespace OpenKh.Godot.Helpers +{ + public static class CollisionHelpers + { + [Flags] + public enum CoctFlags : uint + { + PartyStand = 1 << 0, + EntityFallOverride = 1 << 1, + UnknownFallFlag1 = 1 << 2, + UnknownFallFlag2 = 1 << 3, + HitPlayer = 1 << 4, + HitEnemy = 1 << 5, + HitFlyEnemy = 1 << 6, + HitAttack = 1 << 7, + HitSafety = 1 << 8, + IK = 1 << 9, + Dangle = 1 << 10, + Barrier = 1 << 11, + MsgWall = 1 << 12, + Callback = 1 << 13, + CaribDisp0 = 1 << 14, + CaribDisp1 = 1 << 15, + Belt = 1 << 16, + PolygonSe0 = 1 << 17, + PolygonSe1 = 1 << 18, + HitRtn = 1 << 19, + NoHitFloor = 1 << 20, + } + } +} diff --git a/OpenKh.Godot/Helpers/ImportHelpers.cs b/OpenKh.Godot/Helpers/ImportHelpers.cs new file mode 100644 index 000000000..97c0332c3 --- /dev/null +++ b/OpenKh.Godot/Helpers/ImportHelpers.cs @@ -0,0 +1,123 @@ +using System; +using System.IO; +using System.Numerics; +using Godot; +using OpenKh.Kh1; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using Quaternion = Godot.Quaternion; +using Vector3 = Godot.Vector3; + +namespace OpenKh.Godot.Helpers; + +public static class ImportHelpers +{ + //Common + public static readonly string ExtractPath = Path.Combine(System.Environment.CurrentDirectory, "Extracted"); + public static readonly string ImportPath = Path.Combine(System.Environment.CurrentDirectory, "Imported"); + + public static readonly string Kh1Path = Path.Combine(ExtractPath, "kh1"); + public static readonly string Kh1OriginalPath = Path.Combine(Kh1Path, "original"); + public static readonly string Kh1RemasteredPath = Path.Combine(Kh1Path, "remastered"); + + public static readonly string Kh1ImportPath = Path.Combine(ImportPath, "kh1"); + public static readonly string Kh1ImportOriginalPath = Path.Combine(Kh1ImportPath, "original"); + public static readonly string Kh1ImportRemasteredPath = Path.Combine(Kh1ImportPath, "remastered"); + + public static readonly string Kh2Path = Path.Combine(ExtractPath, "kh2"); + public static readonly string Kh2OriginalPath = Path.Combine(Kh2Path, "original"); + public static readonly string Kh2RemasteredPath = Path.Combine(Kh2Path, "remastered"); + + public static readonly string Kh2ImportPath = Path.Combine(ImportPath, "kh2"); + public static readonly string Kh2ImportOriginalPath = Path.Combine(Kh2ImportPath, "original"); + public static readonly string Kh2ImportRemasteredPath = Path.Combine(Kh2ImportPath, "remastered"); + public static void SetOwner(this Node node) + { + foreach (var c in node.FindChildren("*", "", true, false)) c.Owner = node; + //foreach (var c in node.GetChildren()) SetOwnerRecursive(c, node); + } + private static void SetOwnerRecursive(this Node node, Node owner) + { + node.Owner = owner; + foreach (var c in node.GetChildren()) SetOwnerRecursive(c, owner); + } + + public static Transform3D CreateTransform(Vector3 pos, Vector3 rot, Vector3 scale) + { + var scaleTransform = Transform3D.Identity.Scaled(scale); + var rotationTransform = new Transform3D(new Basis(CommonRotation(rot.X, rot.Y, rot.Z)), Vector3.Zero); + var translationTransform = Transform3D.Identity.Translated(pos); + + return translationTransform * rotationTransform * scaleTransform; + } + public static Quaternion CommonRotation(float x, float y, float z) + { + var rotationMatrix = RotationMatrix(x, y, z); + Matrix4x4.Decompose(rotationMatrix, out _, out var rot, out _); + return new Quaternion(rot.X, rot.Y, rot.Z, rot.W); + } + public static Matrix4x4 RotationMatrix(float x, float y, float z) + { + var rotationMatrixX = Matrix4x4.CreateRotationX(x); + var rotationMatrixY = Matrix4x4.CreateRotationY(y); + var rotationMatrixZ = Matrix4x4.CreateRotationZ(z); + return rotationMatrixX * rotationMatrixY * rotationMatrixZ; + } + public static byte[] BGRAToRGBA(this byte[] data) + { + var result = new byte[data.Length]; + for (var i = 0; i < data.Length; i += 4) + { + result[i] = data[i + 2]; + result[i + 1] = data[i + 1]; + result[i + 2] = data[i]; + result[i + 3] = data[i + 3]; + } + return result; + } + + //KH1 + public const float KH1PositionScale = 1f / 200f; + public static Vector3 FromKH1Position(float x, float y, float z) => new Vector3(x, y, z) * KH1PositionScale; + public static Vector3 Position(this Mdls.MdlsJoint joint) => FromKH1Position(joint.TranslateX, joint.TranslateY, joint.TranslateZ); + public static Vector3 Scale(this Mdls.MdlsJoint joint) => new(joint.ScaleX, joint.ScaleY, joint.ScaleZ); + public static Transform3D Transform(this Mdls.MdlsJoint joint) => new(new Basis(joint.Rotation()).Scaled(joint.Scale()), joint.Position()); + public static Quaternion Rotation(this Mdls.MdlsJoint joint) => CommonRotation(joint.RotateX, joint.RotateY, joint.RotateZ); + public static Vector3 Position(this Mdls.MdlsVertex vert) => FromKH1Position(vert.TranslateX, vert.TranslateY, vert.TranslateZ); + + //KH2 + + public const float KH2PositionScale = 1f / 200f; + public static Vector3 FromKH2Position(float x, float y, float z) => new Vector3(x, y, z) * KH2PositionScale; + public static Vector3 Position(this ModelCommon.Bone bone) => FromKH2Position(bone.TranslationX, bone.TranslationY, bone.TranslationZ); + + public static Quaternion Rotation(this ModelCommon.Bone bone) => CommonRotation(bone.RotationX, bone.RotationY, bone.RotationZ); + + public static Vector3 Scale(this ModelCommon.Bone bone) => new(bone.ScaleX, bone.ScaleY, bone.ScaleZ); + public static Transform3D Transform(this ModelCommon.Bone bone) + { + var scaleTransform = Transform3D.Identity.Scaled(bone.Scale()); + var rotationTransform = new Transform3D(new Basis(bone.Rotation()), Vector3.Zero); + var translationTransform = Transform3D.Identity.Translated(bone.Position()); + + return translationTransform * rotationTransform * scaleTransform; + } + public static void PrintEntries(this Bar bar) + { + foreach (var entry in bar) + { + GD.Print($"{entry.Index}, {entry.Name}, {entry.Type}"); + } + } + public static Color ConvertColor(this uint c) + { + var bytes = BitConverter.GetBytes(c); + + var red = bytes[0] / 128f; + var green = bytes[1] / 128f; + var blue = bytes[2] / 128f; + var alpha = bytes[3] / 128f; + + return new Color(red, green, blue, alpha); + } +} diff --git a/OpenKh.Godot/Helpers/PackedInstance.cs b/OpenKh.Godot/Helpers/PackedInstance.cs new file mode 100644 index 000000000..a8c951113 --- /dev/null +++ b/OpenKh.Godot/Helpers/PackedInstance.cs @@ -0,0 +1,11 @@ +using Godot; + +namespace OpenKh.Godot.Helpers +{ + public class PackedInstance where T : Node + { + public readonly PackedScene Scene; + public PackedInstance(string path) => Scene = ResourceLoader.Load(path); + public T Create() => Scene.Instantiate(); + } +} diff --git a/OpenKh.Godot/Helpers/TextureMapper.cs b/OpenKh.Godot/Helpers/TextureMapper.cs new file mode 100644 index 000000000..1f2ea6086 --- /dev/null +++ b/OpenKh.Godot/Helpers/TextureMapper.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Godot; + +namespace OpenKh.Godot.Helpers +{ + public class TextureMapper + { + public readonly List HdTextures = []; + public readonly Dictionary TextureMap = new(); + public int CurrentIndex { get; private set; } + + public TextureMapper(IEnumerable hdTextures, Dictionary map = null, int index = 0) + { + if (hdTextures is not null) HdTextures.AddRange(hdTextures); + if (map is not null) TextureMap = map; + CurrentIndex = index; + } + public void ResetMap() => TextureMap.Clear(); + + public TextureMapper(TextureMapper old) + { + HdTextures = old.HdTextures; + CurrentIndex = old.CurrentIndex; + } + + public Texture2D GetTexture(int index, Texture2D fallback) + { + if (!TextureMap.TryGetValue(index, out var value)) + { + value = CurrentIndex; + TextureMap[index] = value; + CurrentIndex++; + } + if (value >= HdTextures.Count) return fallback; + + return HdTextures[value] ?? fallback; + } + + public Texture2D GetNextTexture(Texture2D fallback) + { + var value = CurrentIndex; + CurrentIndex++; + if (value >= HdTextures.Count) return fallback; + + return HdTextures[value] ?? fallback; + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2Entity.cs b/OpenKh.Godot/Nodes/KH2Entity.cs new file mode 100644 index 000000000..8b651a109 --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2Entity.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using Godot; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Resources; +using OpenKh.Godot.Storage; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2Entity : CharacterBody3D +{ + public struct Input + { + public Vector2 Movement; + } + public enum EntityStatus + { + Default, + } + [Export] + public int ObjectEntry + { + get => _entry; + set + { + if (_entry == value) return; + if (!KH2ObjectEntryTable.Entries.TryGetValue(value, out var objEntry)) return; + + _entry = value; + + if (Model is not null) + { + RemoveChild(Model); + Model.QueueFree(); + Model = null; + } + Model = PackAssetLoader.GetMdlx(objEntry); + Moveset = PackAssetLoader.GetMoveset(objEntry); + + + } + } + private int _entry = -1; + + public Objentry ObjEntry => KH2ObjectEntryTable.Entries.GetValueOrDefault(_entry); + + [Export] public KH2Mdlx Model; + [Export] public KH2Moveset Moveset; + [Export] public CollisionShape3D CollisionShape; + [Export] public CapsuleShape3D CapsuleShape; + [Export] public float FacingRotation; + [Export] public EntityStatus Status; + public bool Grounded { get; private set; } + + public override void _Ready() + { + base._Ready(); + + if (CollisionShape is null) + { + CollisionShape = new CollisionShape3D(); + AddChild(CollisionShape); + } + if (CapsuleShape is null) CapsuleShape = new CapsuleShape3D(); + CollisionShape.Shape = CapsuleShape; + } + + public override void _PhysicsProcess(double delta) + { + base._PhysicsProcess(delta); + var deltaf = (float)delta; + + if (Engine.IsEditorHint()) return; + + var obj = ObjEntry; + UpdateGrounded(); + MotionMode = Grounded ? MotionModeEnum.Grounded : MotionModeEnum.Floating; + + var input = CurrentInput; + + switch (Status) + { + case EntityStatus.Default: + { + + break; + } + } + + var horizontalVel = Velocity.XZ(); + + MoveAndSlide(); + } + + protected virtual Input CurrentInput => default; + + private void UpdateGrounded() + { + Grounded = false; + var collisions = GetSlideCollisionCount(); + for (var i = 0; i < collisions; i++) + { + var collision = GetSlideCollision(i); + var multiCollisions = collision.GetCollisionCount(); + for (var j = 0; j < multiCollisions; j++) + { + var other = collision.GetCollider(j); + if (other is not CollisionObject3D col) continue; + var layer = (CollisionHelpers.CoctFlags)col.CollisionLayer; + if ((layer & (CollisionHelpers.CoctFlags.PartyStand | CollisionHelpers.CoctFlags.HitPlayer)) > 0) + { + Grounded = true; + return; + } + } + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2InterfaceLayoutPlayer.cs b/OpenKh.Godot/Nodes/KH2InterfaceLayoutPlayer.cs new file mode 100644 index 000000000..9de7742b7 --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2InterfaceLayoutPlayer.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using Godot; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2InterfaceLayoutPlayer : Node2D +{ + [Export] public KH2InterfaceLayout Layout; + [Export] + public int GroupIndex + { + get => _groupIndex; + set + { + if (Layout is null) return; + if (value == _groupIndex) return; + if (value >= Layout.Groups.Count) return; + if (value < 0) return; + + _groupIndex = value; + } + } + + private int _groupIndex; + protected int PreviousGroupIndex = -1; + public List DeployedSequences = new(); + + public override void _Process(double delta) + { + base._Process(delta); + + var deltaf = (float)delta; + + var currentGroup = Layout.Groups[GroupIndex]; + + if (PreviousGroupIndex != GroupIndex) + { + PreviousGroupIndex = GroupIndex; + + foreach (var s in DeployedSequences) s.QueueFree(); + DeployedSequences.Clear(); + for (var index = 0; index < currentGroup.Sequences.Count; index++) + { + var sequence = currentGroup.Sequences[index]; + var animIndex = currentGroup.SequenceIndices[index]; + var position = currentGroup.SequencePositions[index]; + + var sequenceNode = new KH2InterfaceSequencePlayer(); + AddChild(sequenceNode); + + sequenceNode.Sequence = sequence; + sequenceNode.AnimationIndex = animIndex; + sequenceNode.Position = position; + sequenceNode.CurrentTime = currentGroup.ShowAtTime; + + DeployedSequences.Add(sequenceNode); + + sequenceNode.Update(deltaf); + } + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2InterfaceSequencePlayer.cs b/OpenKh.Godot/Nodes/KH2InterfaceSequencePlayer.cs new file mode 100644 index 000000000..e666c309c --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2InterfaceSequencePlayer.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2InterfaceSequencePlayer : Node2D +{ + [Export] public bool UpdateTime = true; + [Export] public float CurrentTime; + [Export] public KH2InterfaceSpriteSequence Sequence; + [Export] + public int AnimationIndex + { + get => _animationIndex; + set + { + if (Sequence is null) return; + if (value == _animationIndex) return; + if (value >= Sequence.AnimationList.Count) return; + if (value < 0) return; + _animationIndex = value; + + if (Engine.IsEditorHint()) CurrentTime = 0; + } + } + private int _animationIndex; + protected int LastAnimationIndex = -1; + public List DeployedSprites = new(); + public float CurrentTimeRounded; + + public override void _Process(double delta) + { + base._Process(delta); + Update((float)delta); + } + public void Update(float deltaf) + { + var currentAnimationGroup = Sequence.AnimationList[AnimationIndex]; + + if (LastAnimationIndex != AnimationIndex) + { + LastAnimationIndex = AnimationIndex; + foreach (var s in DeployedSprites) s.QueueFree(); + DeployedSprites.Clear(); + foreach (var anim in currentAnimationGroup.Animations) + { + var sprite = KH2InterfaceSprite.Create(Sequence.Sprites[anim.SpriteIndex], Sequence.Texture); + AddChild(sprite); + sprite.Animation = anim; + DeployedSprites.Add(sprite); + } + } + + if (UpdateTime) + { + CurrentTime += deltaf; + if (currentAnimationGroup.Loop && CurrentTime >= currentAnimationGroup.LoopEnd) CurrentTime -= (currentAnimationGroup.LoopEnd - currentAnimationGroup.LoopStart); + CurrentTimeRounded = Mathf.Snapped(CurrentTime, 1f / 60f); + } + foreach (var sprite in DeployedSprites) sprite.Animation.Apply(this, sprite); + } +} diff --git a/OpenKh.Godot/Nodes/KH2InterfaceSprite.cs b/OpenKh.Godot/Nodes/KH2InterfaceSprite.cs new file mode 100644 index 000000000..d207a49ac --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2InterfaceSprite.cs @@ -0,0 +1,49 @@ +using Godot; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.Nodes +{ + [Tool] + public partial class KH2InterfaceSprite : Node2D + { + [Export] public KH2InterfaceSequenceAnimation Animation; + [Export] public MeshInstance2D Mesh; + [Export] public Node2D RotationNode1; + [Export] public Node2D RotationNode2; + [Export] public Node2D PositionNode; + [Export] public Node2D ScaleNode; + [Export] public Node2D PivotNode; + + public static KH2InterfaceSprite Create(Mesh mesh, Texture2D texture) + { + var node = new KH2InterfaceSprite(); + + var rotationNode1 = new Node2D(); + var rotationNode2 = new Node2D(); + var positionNode = new Node2D(); + var scaleNode = new Node2D(); + var pivotNode = new Node2D(); + + node.PivotNode = pivotNode; + node.PositionNode = positionNode; + node.ScaleNode = scaleNode; + node.RotationNode1 = rotationNode1; + node.RotationNode2 = rotationNode2; + + node.AddChild(positionNode); + positionNode.AddChild(scaleNode); + scaleNode.AddChild(rotationNode1); + rotationNode1.AddChild(rotationNode2); + rotationNode2.AddChild(pivotNode); + + var meshInstance = new MeshInstance2D(); + pivotNode.AddChild(meshInstance); + node.Mesh = meshInstance; + + meshInstance.Mesh = mesh; + meshInstance.Texture = texture; + + return node; + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2Map.cs b/OpenKh.Godot/Nodes/KH2Map.cs new file mode 100644 index 000000000..f7ec14526 --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2Map.cs @@ -0,0 +1,10 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Nodes +{ + public partial class KH2Map : Node3D + { + [Export] public Dictionary> Groups = new(); + } +} diff --git a/OpenKh.Godot/Nodes/KH2Mdlx.cs b/OpenKh.Godot/Nodes/KH2Mdlx.cs new file mode 100644 index 000000000..4c45ba2f9 --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2Mdlx.cs @@ -0,0 +1,12 @@ +using Godot; +using Godot.Collections; +using OpenKh.Godot.Resources; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2Mdlx : Node3D +{ + [Export] public KH2Skeleton3D Skeleton; +} diff --git a/OpenKh.Godot/Nodes/KH2MeshInstance3D.cs b/OpenKh.Godot/Nodes/KH2MeshInstance3D.cs new file mode 100644 index 000000000..f4a92a84c --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2MeshInstance3D.cs @@ -0,0 +1,80 @@ +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2MeshInstance3D : MeshInstance3D +{ + [Export] public Array TextureAnimations = new(); + + [Export] public Array TextureAnimationsCurrentAnimation = new(); + [Export] public Array TextureAnimationsCurrentAnimationFrame = new(); + [Export] public Array TextureAnimationsAnimationTimer = new(); + [Export] public Array TextureAnimationsRandomAnimationTime = new(); + + [Export] public Array Textures = new(); + [Export] public Array AnimatedTextures = new(); + public KH2TextureAnimationFrame CurrentFrame(int index) + { + var anim = TextureAnimations[index]; + return anim.AnimationList[TextureAnimationsCurrentAnimation[index]].Frames[TextureAnimationsCurrentAnimationFrame[index]]; + } + public void SetTextureAnimationFrame(int index, int frame) + { + if (index > TextureAnimations.Count) return; + var anim = TextureAnimations[index]; + if (frame >= anim.SpriteFrameCount) return; + var pos = anim.GetMaterialFrameParameter(frame); + SetInstanceShaderParameter($"TextureFrame{index}", pos); + } + public override void _Ready() + { + base._Ready(); + //GD.Print(TextureAnimations.Count); + for (var index = 0; index < TextureAnimations.Count; index++) SetTextureAnimationFrame(index, 0); + TextureAnimationsCurrentAnimation = new Array(TextureAnimations.Select(i => i.DefaultAnimationIndex)); + TextureAnimationsCurrentAnimationFrame = new Array(Enumerable.Repeat(0, TextureAnimations.Count)); + TextureAnimationsAnimationTimer = new Array(Enumerable.Repeat(0f, TextureAnimations.Count)); + TextureAnimationsRandomAnimationTime = new Array(Enumerable.Repeat(0f, TextureAnimations.Count)); + } + public override void _Process(double delta) + { + base._Process(delta); + UpdateAnimatedTextures((float)delta); + } + private void SetAnimationStateFrame(int index, int frame) + { + TextureAnimationsAnimationTimer[index] = 0; + TextureAnimationsRandomAnimationTime[index] = -1; + TextureAnimationsCurrentAnimationFrame[index] = frame; + + var currentFrame = CurrentFrame(index); + switch (currentFrame.Operation) + { + case KH2TextureAnimationOperation.EnableSprite: + SetInstanceShaderParameter($"UseSprite{index}", true); + SetTextureAnimationFrame(index, currentFrame.ImageIndex); + break; + case KH2TextureAnimationOperation.DisableSprite: + SetInstanceShaderParameter($"UseSprite{index}", false); + break; + case KH2TextureAnimationOperation.Jump: + SetAnimationStateFrame(index, TextureAnimationsCurrentAnimationFrame[index] + currentFrame.JumpDelta); + break; + } + } + private void UpdateAnimatedTextures(float delta) + { + for (var index = 0; index < TextureAnimations.Count; index++) + { + var currentFrame = CurrentFrame(index); + if (currentFrame.Operation == KH2TextureAnimationOperation.Stop) continue; + if (TextureAnimationsRandomAnimationTime[index] == -1) TextureAnimationsRandomAnimationTime[index] = (float)GD.RandRange(currentFrame.MinTime, currentFrame.MaxTime); + TextureAnimationsAnimationTimer[index] += delta; + if (TextureAnimationsAnimationTimer[index] >= TextureAnimationsRandomAnimationTime[index]) SetAnimationStateFrame(index, TextureAnimationsCurrentAnimationFrame[index] + 1); + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2PlayerEntity.cs b/OpenKh.Godot/Nodes/KH2PlayerEntity.cs new file mode 100644 index 000000000..a51232682 --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2PlayerEntity.cs @@ -0,0 +1,19 @@ +using Godot; + +namespace OpenKh.Godot.Nodes +{ + public partial class KH2PlayerEntity : KH2Entity + { + [Export] public Camera3D Camera { get; private set; } + + public override void _Ready() + { + base._Ready(); + if (Camera is null) + { + Camera = new Camera3D(); + AddChild(Camera); + } + } + } +} diff --git a/OpenKh.Godot/Nodes/KH2Skeleton3D.cs b/OpenKh.Godot/Nodes/KH2Skeleton3D.cs new file mode 100644 index 000000000..09a9e1b8c --- /dev/null +++ b/OpenKh.Godot/Nodes/KH2Skeleton3D.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Animation; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Resources; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Nodes; + +[Tool] +public partial class KH2Skeleton3D : Skeleton3D +{ + [Export] public KH2MeshInstance3D Mesh; + [Export] public ModelCollisionResource ModelCollisions; + + [Export] public int BoneCount = -1; + [Export] public Array BoneSibling = new(); + [Export] public Array BoneParent = new(); + [Export] public Array BoneChild = new(); + [Export] public Array BoneFlags = new(); + [Export] public Array BoneRestScale = new(); + [Export] public Array BoneRestRotation = new(); + [Export] public Array BoneRestPosition = new(); + + [Export] public InterpolatedMotionResource CurrentAnimation; + [Export] public bool Animating = false; + [Export] public float AnimationTime; + [Export] public float AnimationDeltaMultiplier = 1; + [Export] public bool AnimateLoop = true; + + public IEnumerable BoneList + { + get + { + return Enumerable.Range(0, BoneCount).Select(i => new KH2Bone + { + Sibling = BoneSibling[i], + Parent = BoneParent[i], + Child = BoneChild[i], + Flags = BoneFlags[i], + Scale = BoneRestScale[i].ToSystem(), + Rotation = BoneRestRotation[i].ToSystem(), + Position = BoneRestPosition[i].ToSystem(), + }); + } + set + { + if (value is null) return; + var list = value.ToList(); + var count = list.Count; + if (count != BoneCount) + { + BoneCount = count; + BoneSibling = new Array(Enumerable.Repeat(0, count)); + BoneParent = new Array(Enumerable.Repeat(0, count)); + BoneChild = new Array(Enumerable.Repeat(0, count)); + BoneFlags = new Array(Enumerable.Repeat(0, count)); + BoneRestScale = new Array(Enumerable.Repeat(Vector4.Zero, count)); + BoneRestRotation = new Array(Enumerable.Repeat(Vector4.Zero, count)); + BoneRestPosition = new Array(Enumerable.Repeat(Vector4.Zero, count)); + } + for (var i = 0; i < list.Count; i++) + { + var v = list[i]; + BoneSibling[i] = v.Sibling; + BoneParent[i] = v.Parent; + BoneChild[i] = v.Child; + BoneFlags[i] = v.Flags; + BoneRestScale[i] = v.Scale.ToGodot(); + BoneRestRotation[i] = v.Rotation.ToGodot(); + BoneRestPosition[i] = v.Position.ToGodot(); + } + } + } + + public override void _Process(double delta) + { + base._Process(delta); + + if (Animating && Mesh is not null && CurrentAnimation is not null) + { + AnimationTime += ((float)delta) * AnimationDeltaMultiplier; + if (AnimateLoop) AnimationTime = AnimationHelpers.Loop(CurrentAnimation.Value, AnimationTime); + + AnimationHelpers.ApplyInterpolatedMotion(CurrentAnimation.Value, this, AnimationTime); + } + } +} diff --git a/OpenKh.Godot/OpenKh.Godot.csproj b/OpenKh.Godot/OpenKh.Godot.csproj new file mode 100644 index 000000000..a1aa07319 --- /dev/null +++ b/OpenKh.Godot/OpenKh.Godot.csproj @@ -0,0 +1,16 @@ + + + net8.0 + true + + + + + + + + + + + + diff --git a/OpenKh.Godot/Resources/AnimationBinaryResource.cs b/OpenKh.Godot/Resources/AnimationBinaryResource.cs new file mode 100644 index 000000000..b8401d6cb --- /dev/null +++ b/OpenKh.Godot/Resources/AnimationBinaryResource.cs @@ -0,0 +1,13 @@ +using System.IO; +using Godot; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Resources +{ + [Tool] + public partial class AnimationBinaryResource : BinaryResource + { + public override AnimationBinary Value => _value ??= new AnimationBinary(new MemoryStream(Binary)); + private AnimationBinary _value; + } +} diff --git a/OpenKh.Godot/Resources/BinaryResource.cs b/OpenKh.Godot/Resources/BinaryResource.cs new file mode 100644 index 000000000..486fbacfb --- /dev/null +++ b/OpenKh.Godot/Resources/BinaryResource.cs @@ -0,0 +1,11 @@ +using Godot; + +namespace OpenKh.Godot.Resources +{ + [Tool] + public abstract partial class BinaryResource : Resource + { + [Export] public byte[] Binary; + public abstract T Value { get; } + } +} diff --git a/OpenKh.Godot/Resources/InterpolatedMotionResource.cs b/OpenKh.Godot/Resources/InterpolatedMotionResource.cs new file mode 100644 index 000000000..b565b6f1a --- /dev/null +++ b/OpenKh.Godot/Resources/InterpolatedMotionResource.cs @@ -0,0 +1,13 @@ +using System.IO; +using Godot; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Resources +{ + [Tool] + public partial class InterpolatedMotionResource : BinaryResource + { + public override Motion.InterpolatedMotion Value => _value ??= new Motion.InterpolatedMotion(new MemoryStream(Binary)); + private Motion.InterpolatedMotion _value; + } +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceLayout.cs b/OpenKh.Godot/Resources/KH2InterfaceLayout.cs new file mode 100644 index 000000000..07a4fb5d0 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceLayout.cs @@ -0,0 +1,10 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2InterfaceLayout : Resource +{ + [Export] public Array Groups = new(); +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceSequence.cs b/OpenKh.Godot/Resources/KH2InterfaceSequence.cs new file mode 100644 index 000000000..3afda55ff --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceSequence.cs @@ -0,0 +1,9 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +public partial class KH2InterfaceSequence : Resource +{ + [Export] public Dictionary Sequences = new(); +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimation.cs b/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimation.cs new file mode 100644 index 000000000..639f7a155 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimation.cs @@ -0,0 +1,130 @@ +using System; +using Godot; +using OpenKh.Godot.Nodes; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2InterfaceSequenceAnimation : Resource +{ + [Export] public int SpriteIndex; + [Export] public InterfaceAnimationFlags AnimationFlags; + [Export] public float TimeStart; + [Export] public float TimeEnd; + [ExportSubgroup("Translate")] + [Export] public Vector2 TranslateStart; + [Export] public Vector2 TranslateEnd; + [ExportSubgroup("Rotation")] + [Export] public Vector3 RotationStart; + [Export] public Vector3 RotationEnd; + [ExportSubgroup("Scale")] + [Export] public Vector2 ScaleStart; + [Export] public Vector2 ScaleEnd; + [Export] public float ScaleUniformStart; + [Export] public float ScaleUniformEnd; + [ExportSubgroup("Pivot")] + [Export] public Vector2 PivotStart; + [Export] public Vector2 PivotEnd; + [ExportSubgroup("Curve")] + [Export] public Vector2 CurveStart; + [Export] public Vector2 CurveEnd; + [ExportSubgroup("Bounce")] + [Export] public Vector2 BounceStart; + [Export] public Vector2 BounceEnd; + [Export] public Vector2I BounceCount; + [ExportSubgroup("Color")] + [Export] public int ColorBlend; + [Export] public Color ColorStart; + [Export] public Color ColorEnd; + + private static readonly ShaderMaterial MixAlphaMaterial = new(){ Shader = ResourceLoader.Load("res://Assets/Shaders/KH2InterfaceMixShader.gdshader") }; + private static readonly ShaderMaterial AddAlphaMaterial = new(){ Shader = ResourceLoader.Load("res://Assets/Shaders/KH2InterfaceAddShader.gdshader") }; + private static readonly ShaderMaterial SubAlphaMaterial = new(){ Shader = ResourceLoader.Load("res://Assets/Shaders/KH2InterfaceSubShader.gdshader") }; + + //TODO + private static readonly float Epsilon = (1f / 60f) * 0.5f; + //private static readonly float Epsilon = 0; + + public void Apply(KH2InterfaceSequencePlayer sequence, KH2InterfaceSprite sprite) + { + var time = sequence.CurrentTime; + sprite.Mesh.Visible = true; + + if (time < (TimeStart - Epsilon) || time > (TimeEnd + Epsilon)) + { + sprite.Mesh.Visible = false; + return; + } + + var material = ColorBlend switch + { + 1 => AddAlphaMaterial, + 2 => SubAlphaMaterial, + _ => MixAlphaMaterial + }; + if (sprite.Mesh.Material != material) sprite.Mesh.Material = material; + + if (time < TimeStart) time = TimeStart; + else if (time > TimeEnd) time = TimeEnd; + + var delta = Mathf.Remap(time, TimeStart, TimeEnd, 0, 1); + + var t = AnimationFlags.HasFlag(InterfaceAnimationFlags.DisableCurve) ? delta : (Mathf.Sin(delta * Mathf.Pi - Mathf.Pi / 2.0f) + 1.0f) / 2.0f; + + sprite.PositionNode.Position = AnimationFlags.HasFlag(InterfaceAnimationFlags.TranslationInterpolation) ? TranslateStart.Lerp(TranslateEnd, t) : TranslateStart; + sprite.ScaleNode.Scale = AnimationFlags.HasFlag(InterfaceAnimationFlags.ScalingDisable) ? Vector2.One : Mathf.Lerp(ScaleUniformStart, ScaleUniformEnd, t) * ScaleStart.Lerp(ScaleEnd, t); + + sprite.Mesh.Modulate = !AnimationFlags.HasFlag(InterfaceAnimationFlags.ColorMask) + ? !AnimationFlags.HasFlag(InterfaceAnimationFlags.ColorInterpolation) ? ColorStart.Lerp(ColorEnd, t) : ColorStart + : Colors.White; + + if (!AnimationFlags.HasFlag(InterfaceAnimationFlags.PivotDisable)) + { + sprite.PivotNode.Position = -PivotStart.Lerp(PivotEnd, t); + } + + if (!AnimationFlags.HasFlag(InterfaceAnimationFlags.RotationDisable)) + { + var rot = RotationStart.Lerp(RotationEnd, t); + sprite.RotationNode1.Scale = sprite.RotationNode1.Scale with + { + X = Mathf.Cos(rot.Y), + Y = Mathf.Cos(rot.X) + }; + sprite.RotationNode2.Rotation = rot.Z; + } + + if (!AnimationFlags.HasFlag(InterfaceAnimationFlags.BounceDisable)) + { + var bounceXValue = Mathf.Sin(delta * BounceCount.X * Mathf.Pi); + var bounceYValue = Mathf.Sin(delta * BounceCount.Y * Mathf.Pi); + + sprite.PositionNode.Position += new Vector2(bounceXValue, bounceYValue) * BounceStart.Lerp(BounceEnd, t); + } + } +} + +[Flags] +public enum InterfaceAnimationFlags +{ + DisableCurve = 1 << 0, + IsActive = 1 << 1, + DisableBilinear = 1 << 2, + BounceDelay = 1 << 3, + BounceDisable = 1 << 4, + RotationDisable = 1 << 5, + ScalingDisable = 1 << 6, + ColorInterpolation = 1 << 7, + RotationInterpolation = 1 << 8, + ScalingInterpolation = 1 << 9, + ColorMask = 1 << 10, + BounceInterpolation = 1 << 11, + TranslationInterpolation = 1 << 12, + PivotInterpolation = 1 << 13, + PivotDisable = 1 << 14, + LastCut = 1 << 15, + TranslationDisable = 1 << 16, + TranslationOffsetDisable = 1 << 17, + PositionDisable = 1 << 18, + Tag = 1 << 19, +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimationGroup.cs b/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimationGroup.cs new file mode 100644 index 000000000..e650471b8 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceSequenceAnimationGroup.cs @@ -0,0 +1,13 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2InterfaceSequenceAnimationGroup : Resource +{ + [Export] public Array Animations = new(); + [Export] public bool Loop; + [Export] public float LoopStart = -1; + [Export] public float LoopEnd = -1; +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceSequenceGroup.cs b/OpenKh.Godot/Resources/KH2InterfaceSequenceGroup.cs new file mode 100644 index 000000000..99b566b35 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceSequenceGroup.cs @@ -0,0 +1,13 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2InterfaceSequenceGroup : Resource +{ + [Export] public Array Sequences = new(); + [Export] public Array SequenceIndices = new(); + [Export] public Array SequencePositions = new(); + [Export] public float ShowAtTime; +} diff --git a/OpenKh.Godot/Resources/KH2InterfaceSpriteSequence.cs b/OpenKh.Godot/Resources/KH2InterfaceSpriteSequence.cs new file mode 100644 index 000000000..e0c075fd5 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2InterfaceSpriteSequence.cs @@ -0,0 +1,12 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2InterfaceSpriteSequence : Resource +{ + [Export] public Array Sprites = new(); + [Export] public Texture2D Texture; + [Export] public Array AnimationList = new(); +} diff --git a/OpenKh.Godot/Resources/KH2Moveset.cs b/OpenKh.Godot/Resources/KH2Moveset.cs new file mode 100644 index 000000000..9a4a62cbe --- /dev/null +++ b/OpenKh.Godot/Resources/KH2Moveset.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Resources; + +[Tool, GlobalClass] +public partial class KH2Moveset : Resource +{ + public enum MovesetType + { + Default, + Player, + Raw + } + public enum PlayerAnimationStatus + { + InBattleHasWeapon, + InBattleNoWeapon, + OutBattleNoWeapon, + OutBattleHasWeapon, + } + + public static readonly IReadOnlyDictionary AnimationTryOrder = + new System.Collections.Generic.Dictionary + { + { PlayerAnimationStatus.InBattleHasWeapon, [0, 1, 3, 2] }, + { PlayerAnimationStatus.InBattleNoWeapon, [1, 0, 2, 3] }, + { PlayerAnimationStatus.OutBattleNoWeapon, [2, 3, 1, 0] }, + { PlayerAnimationStatus.OutBattleHasWeapon, [3, 2, 0, 1] }, + }.AsReadOnly(); + + [Export] public MovesetType Type; + [Export] public Array Entries = new(); + + public KH2MovesetEntry GetEntry(int index, PlayerAnimationStatus status = PlayerAnimationStatus.InBattleHasWeapon) => + Type == MovesetType.Player ? AnimationTryOrder[status].Select(o => Entries[(index * 4) + o]).FirstOrDefault(get => get is not null) : Entries[index]; +} diff --git a/OpenKh.Godot/Resources/KH2MovesetEntry.cs b/OpenKh.Godot/Resources/KH2MovesetEntry.cs new file mode 100644 index 000000000..22e3aebb0 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2MovesetEntry.cs @@ -0,0 +1,9 @@ +using Godot; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2MovesetEntry : Resource +{ + [Export] public InterpolatedMotionResource Motion; +} diff --git a/OpenKh.Godot/Resources/KH2TextureAnimation.cs b/OpenKh.Godot/Resources/KH2TextureAnimation.cs new file mode 100644 index 000000000..b290e000c --- /dev/null +++ b/OpenKh.Godot/Resources/KH2TextureAnimation.cs @@ -0,0 +1,37 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2TextureAnimation : Resource +{ + [Export] public int TextureIndex; + [Export] public int SpriteFrameCount; + [Export] public int DefaultAnimationIndex; + [Export] public Array AnimationList; + + /* + [Export] public int CurrentAnimation; + [Export] public int CurrentAnimationFrame; + [Export] public float AnimationTimer; + [Export] public float RandomAnimationTime = -1; + */ + + /* + [Export] + public int SetCurrentAnimation + { + get => CurrentAnimation; + set + { + AnimationTimer = 0; + RandomAnimationTime = -1; + CurrentAnimationFrame = 0; + var index = Mathf.Clamp(value, 0, AnimationList.Count - 1); + CurrentAnimation = index; + } + } + */ + public Vector2 GetMaterialFrameParameter(int frame) => new(frame / (float)SpriteFrameCount, (frame+1) / (float)SpriteFrameCount); +} diff --git a/OpenKh.Godot/Resources/KH2TextureAnimationFrame.cs b/OpenKh.Godot/Resources/KH2TextureAnimationFrame.cs new file mode 100644 index 000000000..933df28c9 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2TextureAnimationFrame.cs @@ -0,0 +1,26 @@ +using Godot; + +namespace OpenKh.Godot.Resources; + +public enum KH2TextureAnimationOperation +{ + EnableSprite = 0, + DisableSprite = 1, + Jump = 2, + Stop = 3, +} +[Tool] +public partial class KH2TextureAnimationFrame : Resource +{ + [Export] public int ImageIndex; + /// + /// The operation for the sprite animation + /// + [Export] public KH2TextureAnimationOperation Operation; + /// + /// For jump, what index offset to jump to + /// + [Export] public int JumpDelta; + [Export] public float MinTime; + [Export] public float MaxTime; +} diff --git a/OpenKh.Godot/Resources/KH2TextureAnimations.cs b/OpenKh.Godot/Resources/KH2TextureAnimations.cs new file mode 100644 index 000000000..04e374901 --- /dev/null +++ b/OpenKh.Godot/Resources/KH2TextureAnimations.cs @@ -0,0 +1,10 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class KH2TextureAnimations : Resource +{ + [Export] public Array Frames; +} diff --git a/OpenKh.Godot/Resources/ModelCollisionResource.cs b/OpenKh.Godot/Resources/ModelCollisionResource.cs new file mode 100644 index 000000000..86448d3ec --- /dev/null +++ b/OpenKh.Godot/Resources/ModelCollisionResource.cs @@ -0,0 +1,13 @@ +using System.IO; +using Godot; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Resources +{ + [Tool] + public partial class ModelCollisionResource : BinaryResource + { + public override ModelCollision Value => _value ??= new ModelCollision(new MemoryStream(Binary)); + private ModelCollision _value; + } +} diff --git a/OpenKh.Godot/Resources/SoundContainer.cs b/OpenKh.Godot/Resources/SoundContainer.cs new file mode 100644 index 000000000..038c81c5c --- /dev/null +++ b/OpenKh.Godot/Resources/SoundContainer.cs @@ -0,0 +1,10 @@ +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class SoundContainer : Resource +{ + [Export] public Array Sounds = new(); +} diff --git a/OpenKh.Godot/Resources/SoundResource.cs b/OpenKh.Godot/Resources/SoundResource.cs new file mode 100644 index 000000000..74ac8f809 --- /dev/null +++ b/OpenKh.Godot/Resources/SoundResource.cs @@ -0,0 +1,22 @@ +using Godot; + +namespace OpenKh.Godot.Resources; + +[Tool] +public partial class SoundResource : Resource +{ + public enum Codec + { + Ogg, + msadpcm, + } + [Export] public AudioStream Sound; + [Export] public uint SampleRate; + [Export] public uint ChannelCount; + [Export] public uint LoopStartRaw; + [Export] public uint LoopEndRaw; + [Export] public float LoopStart; + [Export] public float LoopEnd; + [Export] public bool Loop; + [Export] public Codec OriginalCodec; +} diff --git a/OpenKh.Godot/Scenes/Root.tscn b/OpenKh.Godot/Scenes/Root.tscn new file mode 100644 index 000000000..0a7baf5a5 --- /dev/null +++ b/OpenKh.Godot/Scenes/Root.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=3 uid="uid://def10rjbmb04j"] + +[ext_resource type="PackedScene" uid="uid://pdwj5kona0kg" path="res://Scenes/Tools/Launcher/Launcher.tscn" id="1_aoyju"] + +[node name="Root" type="Node"] + +[node name="Launcher" parent="." instance=ExtResource("1_aoyju")] diff --git a/OpenKh.Godot/Scenes/Test/ImportTest.tscn b/OpenKh.Godot/Scenes/Test/ImportTest.tscn new file mode 100644 index 000000000..a434b2629 --- /dev/null +++ b/OpenKh.Godot/Scenes/Test/ImportTest.tscn @@ -0,0 +1,8 @@ +[gd_scene load_steps=2 format=3 uid="uid://o5m6y4dntncp"] + +[ext_resource type="Script" path="res://Test/TestAssetExtractor.cs" id="1_y7kp3"] + +[node name="ImportTest" type="Node3D"] + +[node name="Node" type="Node" parent="."] +script = ExtResource("1_y7kp3") diff --git a/OpenKh.Godot/Scenes/Tools/KHExplorer/ExplorerRoot.tscn b/OpenKh.Godot/Scenes/Tools/KHExplorer/ExplorerRoot.tscn new file mode 100644 index 000000000..da6b2fe8c --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/KHExplorer/ExplorerRoot.tscn @@ -0,0 +1,87 @@ +[gd_scene load_steps=2 format=3 uid="uid://bt5kr0h3y274a"] + +[ext_resource type="Script" path="res://Tools/KHExplorer/KHExplorerRoot.cs" id="1_3qpc6"] + +[node name="ExplorerRoot" type="PanelContainer" node_paths=PackedStringArray("FileTree", "PreviewArea", "AudioPlayer", "GameSelector", "SearchBar", "FileNameDisplay")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_3qpc6") +FileTree = NodePath("HBoxContainer/HSplitContainer/VBoxContainer/Tree") +PreviewArea = NodePath("HBoxContainer/HSplitContainer/VBoxContainer2/ScrollContainer/PanelContainer") +AudioPlayer = NodePath("AudioStreamPlayer") +GameSelector = NodePath("HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/HBoxContainer2/OptionButton") +SearchBar = NodePath("HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/SearchBar") +FileNameDisplay = NodePath("HBoxContainer/HSplitContainer/VBoxContainer2/FileNameLabel") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="HSplitContainer" type="HSplitContainer" parent="HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer/HSplitContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = " KHExplorer" +horizontal_alignment = 1 + +[node name="VSeparator" type="VSeparator" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="SearchBar" type="LineEdit" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Search..." +clear_button_enabled = true + +[node name="VSeparator2" type="VSeparator" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "Game: " + +[node name="OptionButton" type="OptionButton" parent="HBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Tree" type="Tree" parent="HBoxContainer/HSplitContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="VBoxContainer2" type="VBoxContainer" parent="HBoxContainer/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="FileNameLabel" type="Label" parent="HBoxContainer/HSplitContainer/VBoxContainer2"] +layout_mode = 2 +text = "---" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="HBoxContainer/HSplitContainer/VBoxContainer2"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer/HSplitContainer/VBoxContainer2"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer/HSplitContainer/VBoxContainer2/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] diff --git a/OpenKh.Godot/Scenes/Tools/KHExplorer/ImageViewer.tscn b/OpenKh.Godot/Scenes/Tools/KHExplorer/ImageViewer.tscn new file mode 100644 index 000000000..ab2a345e5 --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/KHExplorer/ImageViewer.tscn @@ -0,0 +1,47 @@ +[gd_scene load_steps=2 format=3 uid="uid://dss5ih5ovyyug"] + +[ext_resource type="Script" path="res://Tools/KHExplorer/ImageViewer.cs" id="1_iyujk"] + +[node name="ImageViewer" type="PanelContainer" node_paths=PackedStringArray("RemasteredToggle", "Display", "MetadataLabel")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_iyujk") +RemasteredToggle = NodePath("MarginContainer/VBoxContainer/HBoxContainer/CheckButton") +Display = NodePath("MarginContainer/VBoxContainer/TextureRect") +MetadataLabel = NodePath("MarginContainer/VBoxContainer/HBoxContainer/MetadataLabel") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +expand_mode = 1 +stretch_mode = 4 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="MetadataLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="CheckButton" type="CheckButton" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +text = "Remastered" diff --git a/OpenKh.Godot/Scenes/Tools/KHExplorer/MdlxViewer.tscn b/OpenKh.Godot/Scenes/Tools/KHExplorer/MdlxViewer.tscn new file mode 100644 index 000000000..6e0ccc696 --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/KHExplorer/MdlxViewer.tscn @@ -0,0 +1,84 @@ +[gd_scene load_steps=2 format=3 uid="uid://bop66gxvgp0vc"] + +[ext_resource type="Script" path="res://Tools/KHExplorer/MdlxViewer.cs" id="1_0vaws"] + +[node name="MdlxViewer" type="PanelContainer" node_paths=PackedStringArray("SubViewport", "ModelParent", "Camera", "TexturePanel", "AnimatedTexturePanel", "RemasteredToggle")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_0vaws") +SubViewport = NodePath("VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport") +ModelParent = NodePath("VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport/ModelParent") +Camera = NodePath("VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport/Camera3D") +TexturePanel = NodePath("VBoxContainer/VSplitContainer/TabContainer/Textures") +AnimatedTexturePanel = NodePath("VBoxContainer/VSplitContainer/TabContainer/Animated Textures") +RemasteredToggle = NodePath("VBoxContainer/VSplitContainer/PanelContainer/MarginContainer/RemasteredButton") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="VSplitContainer" type="VSplitContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/VSplitContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="SubViewportContainer" type="SubViewportContainer" parent="VBoxContainer/VSplitContainer/PanelContainer"] +layout_mode = 2 +size_flags_vertical = 3 +stretch = true + +[node name="SubViewport" type="SubViewport" parent="VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer"] +handle_input_locally = false +size = Vector2i(1152, 318) +render_target_update_mode = 4 + +[node name="ModelParent" type="Node3D" parent="VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport"] + +[node name="Camera3D" type="Camera3D" parent="VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.23077) + +[node name="test" type="Label3D" parent="VBoxContainer/VSplitContainer/PanelContainer/SubViewportContainer/SubViewport"] +transform = Transform3D(2.76655, -2.1048, 1.9789, 1.71192, 3.40145, 1.22453, -2.32713, 0, 3.25338, 0, 0, 0) +text = "fuck" +outline_size = 2 +autowrap_mode = 3 +justification_flags = 161 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/VSplitContainer/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="ModelInfoLabel" type="Label" parent="VBoxContainer/VSplitContainer/PanelContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 0 +text = "Vertices: " + +[node name="RemasteredButton" type="CheckButton" parent="VBoxContainer/VSplitContainer/PanelContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 0 +button_pressed = true +text = "Remastered" + +[node name="TabContainer" type="TabContainer" parent="VBoxContainer/VSplitContainer"] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="Textures" type="TabContainer" parent="VBoxContainer/VSplitContainer/TabContainer"] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Animated Textures" type="TabContainer" parent="VBoxContainer/VSplitContainer/TabContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 diff --git a/OpenKh.Godot/Scenes/Tools/KHExplorer/ScdViewer.tscn b/OpenKh.Godot/Scenes/Tools/KHExplorer/ScdViewer.tscn new file mode 100644 index 000000000..064b81acc --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/KHExplorer/ScdViewer.tscn @@ -0,0 +1,109 @@ +[gd_scene load_steps=3 format=3 uid="uid://bcocer1uwxjiq"] + +[ext_resource type="Script" path="res://Tools/KHExplorer/ScdViewer.cs" id="1_eo6tx"] +[ext_resource type="Script" path="res://Tools/KHExplorer/AudioVisualizer.cs" id="2_4pm6i"] + +[node name="ScdViewer" type="PanelContainer" node_paths=PackedStringArray("IndexDropdown", "PlayButton", "PauseButton", "StopButton", "VolumeSlider", "ProgressSlider", "LoopButton", "MetadataLabel", "ProgressFrontLabel", "ProgressBackLabel", "Visualizer")] +script = ExtResource("1_eo6tx") +IndexDropdown = NodePath("MarginContainer/VBoxContainer/HBoxContainer/IndexOption") +PlayButton = NodePath("MarginContainer/VBoxContainer/HBoxContainer/PlayButton") +PauseButton = NodePath("MarginContainer/VBoxContainer/HBoxContainer/PauseButton") +StopButton = NodePath("MarginContainer/VBoxContainer/HBoxContainer/StopButton") +VolumeSlider = NodePath("MarginContainer/VBoxContainer/HBoxContainer/VolumeBar") +ProgressSlider = NodePath("MarginContainer/VBoxContainer/HBoxContainer2/PlaybackBar") +LoopButton = NodePath("MarginContainer/VBoxContainer/HBoxContainer/LoopButton") +MetadataLabel = NodePath("MarginContainer/VBoxContainer/MetadataLabel") +ProgressFrontLabel = NodePath("MarginContainer/VBoxContainer/HBoxContainer2/FrontLabel") +ProgressBackLabel = NodePath("MarginContainer/VBoxContainer/HBoxContainer2/BackLabel") +Visualizer = NodePath("MarginContainer/VBoxContainer/PanelContainer/AudioVisualizer") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer"] +custom_minimum_size = Vector2(0, 256) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="AudioVisualizer" type="Control" parent="MarginContainer/VBoxContainer/PanelContainer"] +layout_mode = 2 +script = ExtResource("2_4pm6i") + +[node name="HSeparator3" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +alignment = 1 + +[node name="IndexOption" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="PlayButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = " ⏵ " + +[node name="PauseButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = " ⏸ " + +[node name="StopButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = " ⏹ " + +[node name="VSeparator" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="VolumeBar" type="HSlider" parent="MarginContainer/VBoxContainer/HBoxContainer"] +custom_minimum_size = Vector2(128, 0) +layout_mode = 2 +size_flags_vertical = 4 +max_value = 1.0 +step = 0.01 +tick_count = 5 +ticks_on_borders = true + +[node name="VSeparator2" type="VSeparator" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 + +[node name="LoopButton" type="CheckBox" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "🔁" + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="FrontLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "00:00" + +[node name="PlaybackBar" type="HSlider" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +step = 0.0 +tick_count = 2 +ticks_on_borders = true + +[node name="BackLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +text = "00:00" + +[node name="HSeparator2" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="MetadataLabel" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Length: {} Sample Rate: {} Codec: {} +Loop Start: {} Loop End: {} +" diff --git a/OpenKh.Godot/Scenes/Tools/Launcher/GameConfigurator.tscn b/OpenKh.Godot/Scenes/Tools/Launcher/GameConfigurator.tscn new file mode 100644 index 000000000..fd293840d --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/Launcher/GameConfigurator.tscn @@ -0,0 +1,50 @@ +[gd_scene load_steps=2 format=3 uid="uid://cv6fpnmhhenwc"] + +[ext_resource type="Script" path="res://Tools/Launcher/GameConfigurator.cs" id="1_1ntx0"] + +[node name="GameConfigurator" type="PanelContainer" node_paths=PackedStringArray("PlatformSelector", "ManualPath", "PathOpen", "Label")] +script = ExtResource("1_1ntx0") +PlatformSelector = NodePath("MarginContainer/VBoxContainer/PlatformHbox/Platform") +ManualPath = NodePath("MarginContainer/VBoxContainer/PathHBox/LineEdit") +PathOpen = NodePath("MarginContainer/VBoxContainer/PathHBox/Button") +Label = NodePath("MarginContainer/VBoxContainer/Label") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "GameTitle" +horizontal_alignment = 1 + +[node name="PlatformHbox" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="PlatformLabel" type="Label" parent="MarginContainer/VBoxContainer/PlatformHbox"] +layout_mode = 2 +text = "Platform" + +[node name="Platform" type="OptionButton" parent="MarginContainer/VBoxContainer/PlatformHbox"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="PathHBox" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Button" type="Button" parent="MarginContainer/VBoxContainer/PathHBox"] +layout_mode = 2 +text = "Open Path" + +[node name="LineEdit" type="LineEdit" parent="MarginContainer/VBoxContainer/PathHBox"] +custom_minimum_size = Vector2(192, 0) +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "path goes here..." diff --git a/OpenKh.Godot/Scenes/Tools/Launcher/Launcher.tscn b/OpenKh.Godot/Scenes/Tools/Launcher/Launcher.tscn new file mode 100644 index 000000000..2917aa039 --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/Launcher/Launcher.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=2 format=3 uid="uid://pdwj5kona0kg"] + +[ext_resource type="PackedScene" uid="uid://cv6fpnmhhenwc" path="res://Scenes/Tools/Launcher/GameConfigurator.tscn" id="1_yxu03"] + +[node name="Launcher" type="PanelContainer"] + +[node name="Configurator" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="Configurator"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="Configurator/HBoxContainer"] +layout_mode = 2 + +[node name="HDRemix" parent="Configurator/HBoxContainer/VBoxContainer" instance=ExtResource("1_yxu03")] +layout_mode = 2 +GameIdentifier = "HDRemix" +LabelText = "HD 1.5+2.5 ReMIX" + +[node name="VBoxContainer2" type="VBoxContainer" parent="Configurator/HBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="Configurator/HBoxContainer/VBoxContainer2"] +layout_mode = 2 +text = "Tools" +horizontal_alignment = 1 + +[node name="Button" type="Button" parent="Configurator/HBoxContainer/VBoxContainer2"] +layout_mode = 2 +text = "Explorer" diff --git a/OpenKh.Godot/Scenes/Tools/MsadpcmCache/MsadpcmCacheRoot.tscn b/OpenKh.Godot/Scenes/Tools/MsadpcmCache/MsadpcmCacheRoot.tscn new file mode 100644 index 000000000..03c356f01 --- /dev/null +++ b/OpenKh.Godot/Scenes/Tools/MsadpcmCache/MsadpcmCacheRoot.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=3 format=3 uid="uid://dw36qbiajqif2"] + +[ext_resource type="Script" path="res://Tools/MsadpcmCache/MsadpcmCacheRoot.cs" id="1_7pq5l"] + +[sub_resource type="LabelSettings" id="LabelSettings_p32hr"] +font_size = 32 + +[node name="MsadpcmCacheRoot" type="PanelContainer" node_paths=PackedStringArray("DisplayLabel", "StartButton")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_7pq5l") +DisplayLabel = NodePath("VBoxContainer/VBoxContainer/ProgressLabel") +StartButton = NodePath("VBoxContainer/VBoxContainer/Button") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "MsadpcmCache" +label_settings = SubResource("LabelSettings_p32hr") +horizontal_alignment = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +alignment = 1 + +[node name="Label" type="Label" parent="VBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "Kingdom Hearts uses MSADPCM WAV files in some instances for audio playback +Godot does not support MSADPCM, but it can be converted to a format that Godot can use via FFMpeg +This conversion process only needs to happen once, but takes a noticable amount of time +This tool loads all sounds from every game to ensure every WAV file is converted and cached" +horizontal_alignment = 1 + +[node name="Button" type="Button" parent="VBoxContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_font_sizes/font_size = 32 +text = "Start" + +[node name="ProgressLabel" type="Label" parent="VBoxContainer/VBoxContainer"] +layout_mode = 2 +text = "test" +horizontal_alignment = 1 diff --git a/OpenKh.Godot/Storage/KH2ObjectEntryTable.cs b/OpenKh.Godot/Storage/KH2ObjectEntryTable.cs new file mode 100644 index 000000000..91a3ef119 --- /dev/null +++ b/OpenKh.Godot/Storage/KH2ObjectEntryTable.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Storage +{ + public static class KH2ObjectEntryTable + { + public static Dictionary Entries; + + static KH2ObjectEntryTable() + { + Entries = Objentry.Read(new MemoryStream(PackFileSystem.Open(Game.Kh2, "00objentry.bin").OriginalData)).ToDictionary(i => (int)i.ObjectId); + } + } +} diff --git a/OpenKh.Godot/Storage/KH2Preferences.cs b/OpenKh.Godot/Storage/KH2Preferences.cs new file mode 100644 index 000000000..7d53fa263 --- /dev/null +++ b/OpenKh.Godot/Storage/KH2Preferences.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Godot.Helpers; +using OpenKh.Kh2; +using OpenKh.Kh2.SystemData; + +namespace OpenKh.Godot.Storage +{ + public static class KH2Preferences + { + public static readonly Cmd Commands; + + //PREF + public static readonly List PlayerPreferences; + public static readonly List FormAbilitiesPreferences; + public static readonly List PartyPreferences; + public static readonly List SystemPreferences; + public static readonly List MagicPreferences; + + static KH2Preferences() + { + var systemData = Bar.Read(new MemoryStream(PackFileSystem.Open(Game.Kh2, "03system.bin").OriginalData)); + + var preferences = systemData.FirstOrDefault(i => i.Name.Equals("pref", System.StringComparison.OrdinalIgnoreCase)); + if (preferences is not null) + { + var preferencesBar = Bar.Read(preferences.Stream); + //TODO + + preferencesBar.PrintEntries(); + } + } + } +} diff --git a/OpenKh.Godot/Storage/PackAssetLoader.cs b/OpenKh.Godot/Storage/PackAssetLoader.cs new file mode 100644 index 000000000..3add50aa6 --- /dev/null +++ b/OpenKh.Godot/Storage/PackAssetLoader.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Bbs; +using OpenKh.Egs; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Storage +{ + public static class PackAssetLoader + { + private class Cache + { + public readonly Dictionary Storage = new(); + public T this[string s] + { + get => Storage.GetValueOrDefault(s); + set => Storage[s] = value; + } + public void Clear() => Storage.Clear(); + + public void ClearBlacklist(ICollection blacklist) + { + var keys = Storage.Select(i => i.Key).Distinct().Where(i => !blacklist.Contains(i)).ToList(); + foreach (var key in keys) Storage.Remove(key); + } + } + private class GameCache + { + public readonly IReadOnlyDictionary> Storage = Enum.GetValues().ToDictionary(i => i, i => new Dictionary()).AsReadOnly(); + public T this[Game g, string s] + { + get => !Storage.TryGetValue(g, out var dict) ? default : dict.GetValueOrDefault(s); + set => Storage[g][s] = value; + } + public void Clear() + { + foreach (var entry in Storage) entry.Value.Clear(); + } + public void ClearBlacklist(ICollection blacklist) + { + foreach (var dict in Storage.Select(i => i.Value)) + { + var keys = dict.Select(i => i.Key).Distinct().Where(i => !blacklist.Contains(i)).ToList(); + foreach (var key in keys) dict.Remove(key); + } + } + } + + public class OriginalRemasteredPair + { + public readonly T Original; + public readonly T Remastered; + + public OriginalRemasteredPair(T o, T r) + { + Original = o; + Remastered = r; + } + } + + //temporary caches are cleared when ClearCache is called + //non-temporary caches are not cleared, and are always persistent, should only be used for assets that are never unloaded + + private static Cache _kh2MdlxCache = new(); + private static Cache _kh2MsetCache = new(); + private static GameCache _imageCache = new(); + private static GameCache> _imageOriginalRemasteredCache = new(); + private static GameCache _soundCache = new(); + + public static void ClearCache() + { + _kh2MdlxCache.Clear(); + _kh2MsetCache.Clear(); + _imageCache.Clear(); + _soundCache.Clear(); + } + public static void ClearCacheBlacklist(ICollection blacklist) + { + _kh2MdlxCache.ClearBlacklist(blacklist); + _kh2MsetCache.ClearBlacklist(blacklist); + _imageCache.ClearBlacklist(blacklist); + _soundCache.ClearBlacklist(blacklist); + } + public static List GetHDTextures(EgsHdAsset file) + { + if (file.Assets.Length <= 0) return null; + var images = new List(); + + var order = new Dictionary(); + + foreach (var filePath in file.Assets) + { + if (!filePath.StartsWith('-') || !int.TryParse(filePath[1..^4], out var number)) continue; + order[number] = filePath; + } + foreach (var filePath in order.OrderBy(i => i.Key).Select(i => i.Value)) + { + if (!IsImage(filePath)) + { + images.Add(null); + continue; + } + images.Add(GetTextureFromNameAndData(filePath, file.RemasteredAssetsDecompressedData[filePath])); + } + return images; + } + public static PackedScene CacheMdlx(string path, bool close = true) + { + if (string.IsNullOrEmpty(path)) return null; + var tryCache = _kh2MdlxCache[path]; + if (tryCache is not null) return tryCache; + + var file = PackFileSystem.Open(Game.Kh2, path, close); + if (file is null) return null; + + var originalStream = new MemoryStream(file.OriginalData); + var textures = GetHDTextures(file); + var barFile = Bar.Read(originalStream); + + var result = ModelConverters.FromMdlx(barFile, textures); + result.Name = path; + result.SetOwner(); + + var packed = new PackedScene(); + packed.Pack(result); + result.QueueFree(); + _kh2MdlxCache[path] = packed; + + return packed; + } + public static KH2Mdlx GetMdlx(Objentry obj, bool close = true) => GetMdlx($"obj/{obj.ModelName}.mdlx", close); + public static KH2Mdlx GetMdlx(string path, bool close = true) => CacheMdlx(path, close)?.Instantiate(); + + public static KH2Moveset GetMoveset(Objentry obj, bool close = true) => GetMoveset($"obj/{obj.AnimationName}", close); + public static KH2Moveset GetMoveset(string path, bool close = true) + { + if (string.IsNullOrEmpty(path)) return null; + + var tryGet = _kh2MsetCache[path]; + if (tryGet is not null) return tryGet; + + var file = PackFileSystem.Open(Game.Kh2, path, close); + if (file is null) return null; + + var originalStream = new MemoryStream(file.OriginalData); + + var barFile = Bar.Read(originalStream); + + var result = ModelConverters.FromMoveset(barFile); + + _kh2MsetCache[path] = result; + + return result; + } + public static bool IsImage(string path) + { + var lower = path.ToLower(); + return lower.EndsWith(".png") || lower.EndsWith(".imd") || lower.EndsWith(".dds"); + } + public static Texture2D GetImage(string path, Game game = Game.Kh2, bool close = true) + { + if (string.IsNullOrEmpty(path)) return null; + + var tryGet = _imageCache[game, path]; + if (tryGet is not null) return tryGet; + + var file = PackFileSystem.Open(game, path, close); + if (file is null) return null; + + var fileName = path; + var data = file.OriginalData; + + if (file.Assets.Length > 0) + { + fileName = file.Assets.First(); + data = file.RemasteredAssetsDecompressedData[fileName]; + } + + var tex = GetTextureFromNameAndData(fileName, data); + + if (tex is null) return null; + + _imageCache[game, path] = tex; + + return tex; + } + public static Texture2D GetTextureFromNameAndData(string name, byte[] data) + { + Texture2D tex = null; + if (name.EndsWith(".png")) + { + var image = new Image(); + image.LoadPngFromBuffer(data); + tex = ImageTexture.CreateFromImage(image); + } + else if (name.EndsWith(".imd")) tex = TextureConverters.FromImgd(Imgd.Read(new MemoryStream(data))); + else if (name.EndsWith(".dds")) tex = ImageTexture.CreateFromImage(DDSConverter.GetImage(data)); + return tex; + } + public static SoundContainer GetSoundContainer(string path, Game game = Game.Kh2, bool close = true) + { + if (string.IsNullOrEmpty(path)) return null; + + var tryGet = _soundCache[game, path]; + if (tryGet is not null) return tryGet; + + var file = PackFileSystem.Open(game, path, close); + if (file is null) return null; + + var scd = Converters.FromScd(Scd.Read(new MemoryStream(file.OriginalData))); + + _soundCache[game, path] = scd; + + return scd; + } + } +} diff --git a/OpenKh.Godot/Storage/PackFileSystem.cs b/OpenKh.Godot/Storage/PackFileSystem.cs new file mode 100644 index 000000000..59f329851 --- /dev/null +++ b/OpenKh.Godot/Storage/PackFileSystem.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Common; +using OpenKh.Egs; +using OpenKh.Godot.Configuration; + +namespace OpenKh.Godot.Storage +{ + public enum Game + { + Kh1, + Kh2, + Recom, + Bbs, + /* + Theater, + SettingsMenu, + Mare, + */ + } + public static class PackFileSystem + { + private static readonly Dictionary _1525gameFileNames = new() + { + { + Game.Kh1, + [ + "kh1_first", + "kh1_second", + "kh1_third", + "kh1_fourth", + "kh1_fifth", + ] + }, + { + Game.Kh2, + [ + "kh2_first", + "kh2_second", + "kh2_third", + "kh2_fourth", + "kh2_fifth", + "kh2_sixth", + ] + }, + { + Game.Bbs, + [ + "bbs_first", + "bbs_second", + "bbs_third", + "bbs_fourth", + ] + }, + { + Game.Recom, + [ + "Recom", + ] + }, + }; + + private class GameFileSystem + { + public Dictionary Entries = new(); + public IPakReader Reader; + } + private interface IPakReader + { + public void Close(); + public void Open(); + public Stream Stream { get; } + public bool Status { get; } + } + private class FilePakReader : IPakReader + { + private string FilePath; + private FileStream _str; + public FilePakReader(string path) => FilePath = path; + public bool Status => _str is not null; + public void Close() + { + if (_str is null) return; + _str.Close(); + _str = null; + } + public void Open() => _str = File.OpenRead(FilePath); + public Stream Stream => _str; + } + + private static readonly Dictionary _gameFileSystems = new(); + public static bool HD1525Initialized = false; + + static PackFileSystem() + { + if (!PackFileSystemSettings.AutomaticInitialization) return; + + var remixCfg = Config.HDRemixConfig; + switch (remixCfg.GamePlatform) + { + case Platform.Steam: + { + InitializeSteam1525(remixCfg.GamePath); + break; + } + } + } + public static void InitializeSteam1525(string steamGamePath) + { + if (HD1525Initialized) return; + HD1525Initialized = true; + foreach (var pair in _1525gameFileNames) + { + var heds = pair.Value.Select(i => + (File.OpenRead(Path.Combine(steamGamePath, "Image", "dt", $"{i}.hed")) as Stream, + Path.Combine(steamGamePath, "Image", "dt", $"{i}.pkg"))).ToArray(); + + Initialize(pair.Key, heds); + } + } + public static void Initialize(Game game, params (Stream hed, string pakPath)[] files) + { + var fileSystems = new List(); + foreach (var f in files) + { + var hedObject = Hed.Read(f.hed).ToList(); + + var fs = new GameFileSystem + { + Reader = new FilePakReader(f.pakPath), //TODO + }; + + foreach (var h in hedObject) + { + var hash = Egs.Helpers.ToString(h.MD5); + if (!EgsTools.Names.TryGetValue(hash, out var fileName)) + fileName = $"{hash}.dat"; + fs.Entries.Add(fileName, h); + } + fileSystems.Add(fs); + } + _gameFileSystems.Add(game, fileSystems.ToArray()); + } + public static IEnumerable GetFiles(Game game) => _gameFileSystems[game].SelectMany(i => i.Entries.Select(j => j.Key)); + public static EgsHdAsset Open(Game game, string path, bool close = true) + { + var fsList = _gameFileSystems[game]; + foreach (var fs in fsList) + { + if (!fs.Entries.TryGetValue(path, out var entry)) continue; + + var reader = fs.Reader; + if (!reader.Status) reader.Open(); + var stream = reader.Stream; + + var hdAsset = new EgsHdAsset(stream.SetPosition(entry.Offset)); + if (close) reader.Close(); + return hdAsset; + } + return null; + } + public static void Close(Game game) + { + foreach (var fs in _gameFileSystems[game]) fs.Reader.Close(); + } + } +} diff --git a/OpenKh.Godot/Storage/PackFileSystemSettings.cs b/OpenKh.Godot/Storage/PackFileSystemSettings.cs new file mode 100644 index 000000000..a8eaddb42 --- /dev/null +++ b/OpenKh.Godot/Storage/PackFileSystemSettings.cs @@ -0,0 +1,9 @@ +using OpenKh.Godot.Configuration; + +namespace OpenKh.Godot.Storage +{ + public static class PackFileSystemSettings + { + public static bool AutomaticInitialization = true; + } +} diff --git a/OpenKh.Godot/Test/ConsoleRedirect.cs b/OpenKh.Godot/Test/ConsoleRedirect.cs new file mode 100644 index 000000000..42acc7783 --- /dev/null +++ b/OpenKh.Godot/Test/ConsoleRedirect.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Text; + +namespace OpenKh.Godot.Test; + +public class ConsoleRedirect : TextWriter +{ + public static ConcurrentStack MsgStack = new(); + public override Encoding Encoding => Encoding.Default; + public override void WriteLine(string value) => MsgStack.Push(value); + public override void WriteLine(uint value) => MsgStack.Push(value.ToString()); + public override void WriteLine(char[] buffer, int index, int count) => MsgStack.Push(new string(buffer).Substring(index, count)); + public override void WriteLine(long value) => MsgStack.Push(value.ToString()); + public override void WriteLine(int value) => MsgStack.Push(value.ToString()); + public override void WriteLine(object value) => MsgStack.Push(value?.ToString()); + public override void WriteLine(double value) => MsgStack.Push(value.ToString()); + public override void WriteLine(decimal value) => MsgStack.Push(value.ToString()); + public override void WriteLine(ReadOnlySpan buffer) => MsgStack.Push(new string(buffer)); + public override void WriteLine(char[] buffer) => MsgStack.Push(new string(buffer)); + public override void WriteLine(char value) => MsgStack.Push(value.ToString()); + public override void WriteLine(bool value) => MsgStack.Push(value.ToString()); + public override void WriteLine(float value) => MsgStack.Push(value.ToString()); + public override void WriteLine(string format, object arg0, object arg1) => MsgStack.Push(string.Format(format, arg0, arg1)); + public override void WriteLine(string format, object arg0, object arg1, object arg2) => MsgStack.Push(string.Format(format, arg0, arg1, arg2)); + public override void WriteLine(ulong value) => MsgStack.Push(value.ToString()); + public override void WriteLine(StringBuilder value) => MsgStack.Push(value?.ToString()); + public override void WriteLine(string format, params object[] arg) => MsgStack.Push(string.Format(format, arg)); + public override void WriteLine(string format, object arg0) => MsgStack.Push(string.Format(format, arg0)); + public override void WriteLine() { } +} diff --git a/OpenKh.Godot/Test/TestAssetPackImporter.cs b/OpenKh.Godot/Test/TestAssetPackImporter.cs new file mode 100644 index 000000000..53ffd1a4d --- /dev/null +++ b/OpenKh.Godot/Test/TestAssetPackImporter.cs @@ -0,0 +1,16 @@ +using Godot; +using OpenKh.Godot.Storage; + +namespace OpenKh.Godot.Test; + + +[Tool] +public partial class TestAssetPackImporter : Node3D +{ + public override void _Ready() + { + base._Ready(); + var mdlx = PackAssetLoader.GetMdlx("obj/P_EX100.mdlx"); + AddChild(mdlx); + } +} diff --git a/OpenKh.Godot/Test/TestAssetPrinter.cs b/OpenKh.Godot/Test/TestAssetPrinter.cs new file mode 100644 index 000000000..325af5ac2 --- /dev/null +++ b/OpenKh.Godot/Test/TestAssetPrinter.cs @@ -0,0 +1,14 @@ +using Godot; +using OpenKh.Godot.Storage; + +namespace OpenKh.Godot.Test +{ + public partial class TestAssetPrinter : Node + { + public override void _Ready() + { + base._Ready(); + foreach (var f in PackFileSystem.GetFiles(Game.Kh2)) GD.Print(f); + } + } +} diff --git a/OpenKh.Godot/Test/TestEntityModelLoader.cs b/OpenKh.Godot/Test/TestEntityModelLoader.cs new file mode 100644 index 000000000..157899625 --- /dev/null +++ b/OpenKh.Godot/Test/TestEntityModelLoader.cs @@ -0,0 +1,60 @@ +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Godot.Storage; + +namespace OpenKh.Godot.Test; + +[Tool] +public partial class TestEntityModelLoader : Node3D +{ + [Export] + public int Entity + { + get => _entity; + set + { + if (value == _entity) return; + if (value < 0) return; + + var diff = value - _entity; + + if (KH2ObjectEntryTable.Entries.ContainsKey(value)) _entity = value; + else + { + var direction = diff > 0; + _entity = KH2ObjectEntryTable.Entries.FirstOrDefault(i => direction ? i.Key > value : i.Key < value).Key; + } + + foreach (var c in GetChildren()) + { + RemoveChild(c); + c.QueueFree(); + } + + var objEntry = KH2ObjectEntryTable.Entries[_entity]; + + var modelPath = $"obj/{objEntry.ModelName}.mdlx"; + var animationPath = $"obj/{objEntry.AnimationName}"; + + var mdlx = PackAssetLoader.GetMdlx(modelPath); + var moveset = PackAssetLoader.GetMoveset(animationPath); + + AddChild(mdlx); + + mdlx.Skeleton.CurrentAnimation = moveset.Entries.FirstOrDefault(i => i?.Motion is not null)?.Motion; + mdlx.Skeleton.Animating = true; + + //GD.Print(objEntry.ModelName); + //GD.Print(objEntry.AnimationName); + } + } + private int _entity; + + public override void _Ready() + { + base._Ready(); + + var a = KH2Preferences.Commands is null; + } +} diff --git a/OpenKh.Godot/Tools/KHExplorer/AudioVisualizer.cs b/OpenKh.Godot/Tools/KHExplorer/AudioVisualizer.cs new file mode 100644 index 000000000..204bd9f8e --- /dev/null +++ b/OpenKh.Godot/Tools/KHExplorer/AudioVisualizer.cs @@ -0,0 +1,63 @@ +using System; +using Godot; + +namespace OpenKh.Godot.Tools.KHExplorer +{ + public partial class AudioVisualizer : Control + { + public enum ColorMode + { + Default, + HalloweenTown, + TimelessRiver, + } + [Export] public float FrequencyMax; + [Export] public uint BarCount = 24; + [Export] public AudioEffectSpectrumAnalyzerInstance Analyzer; + [Export] public ColorMode VisualizerColorMode; + + private float[] _maxValues; + private float[] _lastValue; + public override void _Draw() + { + base._Draw(); + + if (Analyzer is null) return; + + if (_maxValues is null || _maxValues.Length != BarCount) _maxValues = new float[BarCount]; + if (_lastValue is null || _lastValue.Length != BarCount) _lastValue = new float[BarCount]; + + var segmentSize = Size.X / BarCount; + + for (var i = 0; i < BarCount; i++) + { + var from = (float)i / BarCount; + var to = (float)(i + 1) / BarCount; + var result = Analyzer.GetMagnitudeForFrequencyRange(from * FrequencyMax, to * FrequencyMax); + + var avg = (result.X + result.Y) * 0.5f; + if (avg > _maxValues[i]) _maxValues[i] = avg; + var max = _maxValues[i]; + var value = max > 0 ? avg / _maxValues[i] : 0; + + var lerp = Damp(_lastValue[i], value, 15, (float)GetProcessDeltaTime()); + _lastValue[i] = lerp; + + var color = VisualizerColorMode switch + { + ColorMode.HalloweenTown => Color.FromHsv(from, 0.75f, 0.75f), + ColorMode.TimelessRiver => Color.FromHsv(0, 0, from), + _ => Color.FromHsv(from, 1, 1), + }; + + DrawRect(new Rect2(i * segmentSize, Size.Y - (lerp * Size.Y), segmentSize, lerp * Size.Y), color); + } + } + private static float Damp(float a, float b, float lambda, float dt) => Mathf.Lerp(a, b, 1 - Mathf.Exp(-lambda * dt)); + public override void _Process(double delta) + { + base._Process(delta); + QueueRedraw(); + } + } +} diff --git a/OpenKh.Godot/Tools/KHExplorer/ImageViewer.cs b/OpenKh.Godot/Tools/KHExplorer/ImageViewer.cs new file mode 100644 index 000000000..6716cddc1 --- /dev/null +++ b/OpenKh.Godot/Tools/KHExplorer/ImageViewer.cs @@ -0,0 +1,59 @@ +using System; +using Godot; +using OpenKh.Godot.Helpers; + +namespace OpenKh.Godot.Tools.KHExplorer +{ + public partial class ImageViewer : PanelContainer + { + [Export] public Texture2D Texture; + [Export] public Texture2D RemasteredTexture; + + [Export] public CheckButton RemasteredToggle; + [Export] public TextureRect Display; + [Export] public Label MetadataLabel; + + public static PackedInstance Packed = new("res://Scenes/Tools/KHExplorer/ImageViewer.tscn"); + + public override void _Ready() + { + base._Ready(); + + if (RemasteredTexture is not null) + { + RemasteredToggle.Visible = true; + RemasteredToggle.ProcessMode = ProcessModeEnum.Inherit; + RemasteredToggle.ButtonPressed = true; + } + else + { + RemasteredToggle.Visible = false; + RemasteredToggle.ProcessMode = ProcessModeEnum.Disabled; + RemasteredToggle.ButtonPressed = false; + } + + Display.Texture = RemasteredToggle.ButtonPressed ? RemasteredTexture : Texture; + + UpdateMetadataLabel(); + + RemasteredToggle.Toggled += RemasteredToggleOnToggled; + } + private void RemasteredToggleOnToggled(bool toggledon) + { + Display.Texture = toggledon ? RemasteredTexture : Texture; + UpdateMetadataLabel(); + } + private void UpdateMetadataLabel() + { + var tex = Display.Texture; + + if (tex is null) + { + MetadataLabel.Text = Random.Shared.Next(32) == 0 ? "I made a boo boo yeah" : "Something went wrong"; + return; + } + var size = tex.GetSize(); + MetadataLabel.Text = $"Size: [{size.X}, {size.Y}]"; + } + } +} diff --git a/OpenKh.Godot/Tools/KHExplorer/KHExplorerRoot.cs b/OpenKh.Godot/Tools/KHExplorer/KHExplorerRoot.cs new file mode 100644 index 000000000..1f4ea0050 --- /dev/null +++ b/OpenKh.Godot/Tools/KHExplorer/KHExplorerRoot.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Bbs; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Storage; +using OpenKh.Kh2; + +namespace OpenKh.Godot.Tools.KHExplorer +{ + public partial class KHExplorerRoot : PanelContainer + { + [Export] public Tree FileTree; + [Export] public Control PreviewArea; + [Export] public AudioStreamPlayer AudioPlayer; + [Export] public OptionButton GameSelector; + [Export] public LineEdit SearchBar; + [Export] public Label FileNameDisplay; + [Export] public Game ActiveGame = Game.Kh2; + + public static AudioEffectSpectrumAnalyzerInstance Analyzer { get; private set; } + + public static readonly PackedScene Packed = ResourceLoader.Load("res://Scenes/Tools/KHExplorer/ExplorerRoot.tscn"); + + public override void _Ready() + { + base._Ready(); + + CreateFileSystem(); + + AudioPlayer.VolumeDb = Mathf.LinearToDb(0.5f); + + var analyzer = new AudioEffectSpectrumAnalyzer(); + AudioServer.AddBusEffect(0, analyzer); + Analyzer = AudioServer.GetBusEffectInstance(0, 0) as AudioEffectSpectrumAnalyzerInstance; + GD.Print(Analyzer is not null); + + foreach (var value in Enum.GetValues()) GameSelector.AddItem(value.ToString().ToUpper()); + GameSelector.Select((int)Game.Kh2); + + GameSelector.ItemSelected += GameSelectorOnItemSelected; + FileTree.ItemActivated += FileTreeOnItemActivated; + SearchBar.TextSubmitted += SearchBarOnTextSubmitted; + } + private void SearchBarOnTextSubmitted(string newtext) => RefreshSearchBar(); + private void GameSelectorOnItemSelected(long index) + { + FileTree.Clear(); + SearchBar.Text = ""; + RefreshSearchBar(); + ActiveGame = (Game)index; + CreateFileSystem(); + } + private void RefreshSearchBar() => RecursiveShow(FileTree.GetRoot(), SearchBar.Text); + private bool RecursiveShow(TreeItem item, string text) + { + var children = item.GetChildren(); + var result = children.Select(i => RecursiveShow(i, text)).ToList(); + if (string.IsNullOrWhiteSpace(text) || result.Any(i => i) || item.GetText(0).Contains(text)) + { + item.Visible = true; + return true; + } + item.Visible = false; + return false; + } + private void CreateFileSystem() + { + var files = PackFileSystem.GetFiles(ActiveGame).ToList(); + + var dirMap = new DirectoryNode { Name = "Root" }; + + foreach (var file in files) dirMap.Map(file); + + CreateTree(dirMap, FileTree.CreateItem()); + FileTree.HideRoot = true; + FileTree.GetRoot().Collapsed = false; + } + private void FileTreeOnItemActivated() + { + var selected = FileTree.GetSelected(); + var meta = selected.GetMetadata(0); + if (meta.VariantType == Variant.Type.String) ShowPreview(meta.AsString()); + } + private void ShowPreview(string file) + { + foreach (var child in PreviewArea.GetChildren()) + { + PreviewArea.RemoveChild(child); + child.QueueFree(); + } + //PackAssetLoader.ClearCache(); //don't leak memory + AudioPlayer.Stop(); + AudioPlayer.Stream = null; + AudioPlayer.StreamPaused = false; + + var f = PackFileSystem.Open(ActiveGame, file); + var remasteredTextures = PackAssetLoader.GetHDTextures(f); + + if (PackAssetLoader.IsImage(file)) + { + var viewer = ImageViewer.Packed.Create(); + + var original = PackAssetLoader.GetTextureFromNameAndData(file, f.OriginalData); + viewer.Texture = original; + if (remasteredTextures.Count > 0) viewer.RemasteredTexture = remasteredTextures.First(); + + PreviewArea.AddChild(viewer); + } + else if (file.EndsWith(".scd")) + { + var player = ScdViewer.Packed.Create(); + player.Playback = AudioPlayer; + + var scd = Converters.FromScd(Scd.Read(new MemoryStream(f.OriginalData))); + + player.SoundContainer = scd; + if (ActiveGame == Game.Kh2) + { + if (_trNames.Any(file.Contains)) player.Visualizer.VisualizerColorMode = AudioVisualizer.ColorMode.TimelessRiver; + else if (_htNames.Any(file.Contains)) player.Visualizer.VisualizerColorMode = AudioVisualizer.ColorMode.HalloweenTown; + } + PreviewArea.AddChild(player); + } + else if (file.EndsWith(".mdlx")) + { + var viewer = MdlxViewer.Packed.Create(); + + var bar = Bar.Read(new MemoryStream(f.OriginalData)); + var originalMdlx = ModelConverters.FromMdlx(bar); + viewer.Mdlx = originalMdlx; + + if (remasteredTextures.Count > 0) + { + var remasteredMdlx = ModelConverters.FromMdlx(bar, remasteredTextures); + viewer.RemasteredMdlx = remasteredMdlx; + } + + PreviewArea.AddChild(viewer); + } + + FileNameDisplay.Text = file; + } + //:) + private static readonly string[] _trNames = [ "music189", "music190", "music517", "music521" ]; + private static readonly string[] _htNames = [ "music064", "music065", "music144", "music149" ]; + private static void CreateTree(DirectoryNode node, TreeItem item) + { + item.SetText(0, node.Name); + item.Collapsed = true; + foreach (var dir in node.Subdirectories) CreateTree(dir, item.CreateChild()); + foreach (var file in node.Files.OrderBy(i => i.str)) + { + var fileItem = item.CreateChild(); + fileItem.SetText(0, file.str); + fileItem.SetMetadata(0, file.realStr); + } + } + private class DirectoryNode + { + public string Name; + public List Subdirectories = new(); + public List<(string str, string realStr)> Files = new(); + public void Map(string str, string realStr = null) + { + realStr ??= str; + if (str.Contains('/')) + { + var split = str.Split('/'); + var nextDir = split.First(); + + var sub = Subdirectories.FirstOrDefault(i => i.Name == nextDir); + if (sub is null) + { + sub = new DirectoryNode { Name = nextDir }; + Subdirectories.Add(sub); + } + sub.Map(string.Join("/", split.Skip(1)), realStr); + } + else if (Files.All(i => i.realStr != realStr)) Files.Add((str, realStr)); + } + } + } +} diff --git a/OpenKh.Godot/Tools/KHExplorer/MdlxViewer.cs b/OpenKh.Godot/Tools/KHExplorer/MdlxViewer.cs new file mode 100644 index 000000000..390aca45f --- /dev/null +++ b/OpenKh.Godot/Tools/KHExplorer/MdlxViewer.cs @@ -0,0 +1,62 @@ +using Godot; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; + +namespace OpenKh.Godot.Tools.KHExplorer +{ + public partial class MdlxViewer : PanelContainer + { + [Export] public SubViewport SubViewport; + [Export] public Node3D ModelParent; + [Export] public Camera3D Camera; + [Export] public Control TexturePanel; + [Export] public Control AnimatedTexturePanel; + [Export] public KH2Mdlx Mdlx; + [Export] public KH2Mdlx RemasteredMdlx; + [Export] public Button RemasteredToggle; + + public static PackedInstance Packed = new("res://Scenes/Tools/KHExplorer/MdlxViewer.tscn"); + + public override void _Ready() + { + base._Ready(); + ModelParent.AddChild(Mdlx); + var hasRemastered = RemasteredMdlx is not null; + if (hasRemastered) + { + ModelParent.AddChild(RemasteredMdlx); + RemasteredToggle.Toggled += RemasteredToggleOnToggled; + } + RemasteredToggle.Visible = hasRemastered; + Mdlx.Visible = !hasRemastered; + + var textures = Mdlx.Skeleton.Mesh.Textures; + var remasteredTextures = RemasteredMdlx?.Skeleton.Mesh.Textures; + + var texturesAnimated = Mdlx.Skeleton.Mesh.AnimatedTextures; + var remasteredTexturesAnimated = RemasteredMdlx?.Skeleton.Mesh.AnimatedTextures; + + for (var i = 0; i < textures.Count; i++) + { + var texViewer = ImageViewer.Packed.Create(); + texViewer.Texture = textures[i]; + texViewer.RemasteredTexture = remasteredTextures?[i]; + TexturePanel.AddChild(texViewer); + texViewer.Name = i.ToString(); + } + for (var i = 0; i < texturesAnimated.Count; i++) + { + var texViewer = ImageViewer.Packed.Create(); + texViewer.Texture = texturesAnimated[i]; + texViewer.RemasteredTexture = remasteredTexturesAnimated?[i]; + TexturePanel.AddChild(texViewer); + texViewer.Name = i.ToString(); + } + } + private void RemasteredToggleOnToggled(bool toggledon) + { + Mdlx.Visible = !toggledon; + RemasteredMdlx.Visible = toggledon; + } + } +} diff --git a/OpenKh.Godot/Tools/KHExplorer/ScdViewer.cs b/OpenKh.Godot/Tools/KHExplorer/ScdViewer.cs new file mode 100644 index 000000000..277501a4b --- /dev/null +++ b/OpenKh.Godot/Tools/KHExplorer/ScdViewer.cs @@ -0,0 +1,186 @@ +using System.Linq; +using Godot; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.Tools.KHExplorer +{ + public partial class ScdViewer : PanelContainer + { + [Export] public SoundContainer SoundContainer; + [Export] public OptionButton IndexDropdown; + [Export] public Button PlayButton; + [Export] public Button PauseButton; + [Export] public Button StopButton; + [Export] public HSlider VolumeSlider; + [Export] public HSlider ProgressSlider; + [Export] public Button LoopButton; + [Export] public AudioStreamPlayer Playback; + [Export] public Label MetadataLabel; + [Export] public Label ProgressFrontLabel; + [Export] public Label ProgressBackLabel; + [Export] public AudioVisualizer Visualizer; + + private float _lastPlaybackPos; + private float _audioLength; + private int _lastDropDown; + + public static PackedInstance Packed = new("res://Scenes/Tools/KHExplorer/ScdViewer.tscn"); + public override void _Ready() + { + base._Ready(); + + var firstValid = SoundContainer.Sounds.FirstOrDefault(i => i is not null); + if (firstValid is null) { QueueFree(); return; } + + Playback.Stop(); + Playback.Stream = firstValid.Sound; + Playback.Play(); + + var len = Playback.Stream.GetLength(); + + _audioLength = (float)len; + + var currentIndex = SoundContainer.Sounds.IndexOf(SoundContainer.Sounds.First(i => i is not null && i.Sound == Playback.Stream)); + _lastDropDown = currentIndex; + + ProgressSlider.MinValue = 0; + ProgressSlider.MaxValue = len; + + VolumeSlider.Value = Mathf.DbToLinear(Playback.VolumeDb); + VolumeSlider.ValueChanged += VolumeSliderOnValueChanged; + + ProgressSlider.ValueChanged += ProgressSliderOnValueChanged; + + Visualizer.Analyzer = KHExplorer.KHExplorerRoot.Analyzer; + Visualizer.FrequencyMax = 24000; + + var single = SoundContainer.Sounds.Where(i => i is not null).ToList().Count == 1; + + if (!single) + { + for (var index = 0; index < SoundContainer.Sounds.Count; index++) + { + var entry = SoundContainer.Sounds[index]; + if (entry is null) IndexDropdown.AddSeparator("---"); + else IndexDropdown.AddItem($"{index}"); + } + IndexDropdown.Select(currentIndex); + } + else + { + IndexDropdown.ProcessMode = ProcessModeEnum.Disabled; + IndexDropdown.Visible = false; + } + + if (firstValid.Loop) + { + LoopButton.ProcessMode = ProcessModeEnum.Inherit; + LoopButton.Visible = true; + } + else + { + LoopButton.ProcessMode = ProcessModeEnum.Disabled; + LoopButton.Visible = false; + } + + IndexDropdown.ItemSelected += IndexDropdownOnItemSelected; + PlayButton.Pressed += PlayButtonOnPressed; + PauseButton.Pressed += PauseButtonOnPressed; + StopButton.Pressed += StopButtonOnPressed; + + SetMetadataLabel(); + } + private void StopButtonOnPressed() => Playback.Stop(); + private void PauseButtonOnPressed() => Playback.StreamPaused = !Playback.StreamPaused; + private void PlayButtonOnPressed() + { + Playback.StreamPaused = false; + Playback.Play(); + } + private void VolumeSliderOnValueChanged(double value) => Playback.VolumeDb = Mathf.LinearToDb((float)value); + private void IndexDropdownOnItemSelected(long index) + { + var sound = SoundContainer.Sounds[(int)index]; + + Playback.Stop(); + Playback.Stream = sound.Sound; + Playback.Play(); + _lastDropDown = (int)index; + _lastPlaybackPos = 0; + + var len = sound.Sound.GetLength(); + + _audioLength = (float)len; + ProgressSlider.MaxValue = len; + + if (sound.Loop) + { + LoopButton.ProcessMode = ProcessModeEnum.Inherit; + LoopButton.Visible = true; + } + else + { + LoopButton.ProcessMode = ProcessModeEnum.Disabled; + LoopButton.Visible = false; + } + + SetMetadataLabel(); + } + private void ProgressSliderOnValueChanged(double value) + { + Playback.Stop(); + Playback.Play((float)value); + } + private void SetMetadataLabel() + { + var sound = SoundContainer.Sounds[_lastDropDown]; + var lengthTextBase = (float)sound.Sound.GetLength(); + MetadataLabel.Text = $"Length: {FormatTimeUpperMiddleLower(lengthTextBase)} Sample Rate: {sound.SampleRate} Codec: {sound.OriginalCodec.ToString()}"; + if (sound.Loop) MetadataLabel.Text += $"\nLoop Start: {FormatTimeUpperMiddleLower(sound.LoopStart)} Loop End: {FormatTimeUpperMiddleLower(sound.LoopEnd)}"; + } + private static string FormatTimeUpperMiddleLower(float time) + { + var lower = Mathf.FloorToInt(time * 60) % 60; + var middle = Mathf.FloorToInt(time) % 60; + var upper = Mathf.FloorToInt(time) / 60; + return $"{upper:00}:{middle:00}:{lower:00}"; + } + public override void _Process(double delta) + { + base._Process(delta); + + var playbackPos = Playback.GetPlaybackPosition(); + + if (_lastPlaybackPos != playbackPos) + { + _lastPlaybackPos = playbackPos; + + var pos = playbackPos + (Playback.Playing ? (float)AudioServer.GetTimeSinceLastMix() : 0); + + var inversePos = _audioLength - playbackPos; + + ProgressSlider.SetValueNoSignal(pos); + ProgressFrontLabel.Text = FormatTimeUpperMiddleLower(playbackPos); + ProgressBackLabel.Text = FormatTimeUpperMiddleLower(inversePos); + } + + if (LoopButton.ButtonPressed && Playback.Playing) + { + var sound = SoundContainer.Sounds[_lastDropDown]; + + //GD.Print($"{sound.SampleRate}, {sound.LoopStartRaw}, {sound.LoopEndRaw}, {sound.Sound.GetLength()}"); + + if (sound.Loop) + { + if (playbackPos > sound.LoopEnd) + { + var diff = sound.LoopStart - sound.LoopEnd; + //Playback.Seek(playbackPos + diff); + Playback.Seek(sound.LoopStart); + } + } + } + } + } +} diff --git a/OpenKh.Godot/Tools/Launcher/GameConfigurator.cs b/OpenKh.Godot/Tools/Launcher/GameConfigurator.cs new file mode 100644 index 000000000..9a9db5849 --- /dev/null +++ b/OpenKh.Godot/Tools/Launcher/GameConfigurator.cs @@ -0,0 +1,82 @@ +using System.Linq; +using Godot; +using OpenKh.Godot.Configuration; + +namespace OpenKh.Godot.Tools.Launcher; + +public partial class GameConfigurator : PanelContainer +{ + [Export] public OptionButton PlatformSelector; + [Export] public LineEdit ManualPath; + [Export] public Button PathOpen; + [Export] public Label Label; + [Export] public string GameIdentifier; + [Export] public string LabelText; + + public static readonly PackedScene Packed = ResourceLoader.Load("res://Scenes/Tools/Launcher/GameConfigurator.tscn"); + public static GameConfigurator Create(string identifier, string labelText) + { + var result = Packed.Instantiate(); + result.GameIdentifier = identifier; + result.LabelText = labelText; + return result; + } + public override void _Ready() + { + base._Ready(); + + var cfg = Config.Configs.FirstOrDefault(i => i.Identifier == GameIdentifier); + if (cfg is null) return; + + Label.Text = LabelText; + + PlatformSelector.Clear(); + foreach (var pair in PlatformHelpers.Names) PlatformSelector.AddItem(pair.Value); + + PlatformSelector.Selected = (int)cfg.GamePlatform; + ManualPath.Text = cfg.GamePath; + + PathOpen.Pressed += PathOpenOnPressed; + } + private void PathOpenOnPressed() + { + var window = new FileDialog(); + window.Access = FileDialog.AccessEnum.Filesystem; + window.FileMode = FileDialog.FileModeEnum.OpenDir; + window.UseNativeDialog = true; + AddChild(window); + window.PopupCentered(); + window.DirSelected += WindowOnDirSelected; + window.DirSelected += _ => + { + RemoveChild(window); + window.QueueFree(); + }; + window.Canceled += () => + { + RemoveChild(window); + window.QueueFree(); + }; + } + private void WindowOnDirSelected(string dir) + { + var steam = dir.Contains("SteamLibrary") || dir.Contains("steamapps"); + //THANKS SQUARE + if (dir.Contains("KINGDOM HEARTS -HD 1.5 2.5 ReMIX-") && (steam)) dir = dir.Replace("KINGDOM HEARTS -HD 1.5 2.5 ReMIX-", "KINGDOM HEARTS -HD 1.5+2.5 ReMIX-"); + + var cfg = Config.Configs.FirstOrDefault(i => i.Identifier == GameIdentifier); + + if (cfg is null) return; + + cfg.GamePath = dir; + ManualPath.Text = dir; + + if (steam) + { + cfg.GamePlatform = Platform.Steam; + PlatformSelector.Selected = (int)Platform.Steam; + } + + cfg.Save(); + } +} diff --git a/OpenKh.Godot/Tools/Launcher/LauncherRoot.cs b/OpenKh.Godot/Tools/Launcher/LauncherRoot.cs new file mode 100644 index 000000000..cebe40a2d --- /dev/null +++ b/OpenKh.Godot/Tools/Launcher/LauncherRoot.cs @@ -0,0 +1,16 @@ +using Godot; + +namespace OpenKh.Godot.Tools.Launcher +{ + public partial class LauncherRoot : PanelContainer + { + [ExportGroup("Config")] + [Export] public GameConfigurator HDRemixConfig; + + public override void _Ready() + { + base._Ready(); + + } + } +} diff --git a/OpenKh.Godot/Tools/MsadpcmCache/MsadpcmCacheRoot.cs b/OpenKh.Godot/Tools/MsadpcmCache/MsadpcmCacheRoot.cs new file mode 100644 index 000000000..872b6d5ee --- /dev/null +++ b/OpenKh.Godot/Tools/MsadpcmCache/MsadpcmCacheRoot.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using OpenKh.Bbs; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Storage; + +namespace OpenKh.Godot.Tools.MsadpcmCache +{ + public partial class MsadpcmCacheRoot : PanelContainer + { + [Export] public Label DisplayLabel; + [Export] public Button StartButton; + + private Stack<(string file, Game game)> _remainingPaths = new(); + private int _count; + + public override void _Ready() + { + base._Ready(); + StartButton.Pressed += StartButtonOnPressed; + } + private void StartButtonOnPressed() + { + foreach (var game in Enum.GetValues()) + { + var files = PackFileSystem.GetFiles(game).Where(i => i.ToLower().EndsWith(".scd")).Distinct().ToList(); + foreach (var file in files) _remainingPaths.Push((file, game)); + } + _count = _remainingPaths.Count; + StartButton.Disabled = true; + } + public override void _Process(double delta) + { + base._Process(delta); + + var currentCount = _remainingPaths.Count; + + if (currentCount == 0) + { + DisplayLabel.Text = _count > 0 ? $"Done\n{_count}/{_count}" : ""; + return; + } + + var next = _remainingPaths.Pop(); + + var file = PackFileSystem.Open(next.game, next.file); + if (file is null) return; + + var scd = Scd.Read(new MemoryStream(file.OriginalData)); + + if (scd.StreamHeaders.Any(i => i.Codec == 12)) _ = Converters.FromScd(Scd.Read(new MemoryStream(file.OriginalData))); + + DisplayLabel.Text = $"{next.game.ToString().ToUpper()} {next.file}\n{_count - currentCount}/{_count}"; + } + } +} diff --git a/OpenKh.Godot/Tools/Root/RootManager.cs b/OpenKh.Godot/Tools/Root/RootManager.cs new file mode 100644 index 000000000..bddbc1072 --- /dev/null +++ b/OpenKh.Godot/Tools/Root/RootManager.cs @@ -0,0 +1,33 @@ +using Godot; +using OpenKh.Godot.Tools.Launcher; + +namespace OpenKh.Godot.Tools.Root +{ + public partial class RootManager : Node + { + [Export] public LauncherRoot Launcher; + public Node CurrentTool + { + get => _currentTool; + set + { + if (_currentTool == value) return; + RemoveChild(_currentTool); + _currentTool.QueueFree(); + _currentTool = value; + if (value == null) + { + Launcher.ProcessMode = ProcessModeEnum.Inherit; + Launcher.Visible = true; + } + else + { + Launcher.ProcessMode = ProcessModeEnum.Disabled; + Launcher.Visible = false; + AddChild(_currentTool); + } + } + } + private Node _currentTool; + } +} diff --git a/OpenKh.Godot/addons/OpenKHImporter/CvblImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/CvblImporter.cs new file mode 100644 index 000000000..f5c5622d0 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/CvblImporter.cs @@ -0,0 +1,199 @@ +#if TOOLS + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Helpers; +using OpenKh.Kh1; +using Array = Godot.Collections.Array; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +public partial class CvblImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + public override string _GetImporterName() => "CvblImporter"; + public override string _GetVisibleName() => "KH1 Remastered Model Importer"; + public override string[] _GetRecognizedExtensions() => ["cvbl"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + return Error.Bug; //TODO: remove this once i actually start focusing on KH1 assets, for now im focused on KH2 assets and this reimporting every bootup causes a giant headache + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var name = new DirectoryInfo(Path.GetDirectoryName(realPath)).Name; + + var root = new Node3D(); + root.Name = /*Path.GetFileNameWithoutExtension(realPath)*/ name; + + var relativePath = Path.GetRelativePath(ImportHelpers.Kh1ImportRemasteredPath, Path.GetFullPath(Path.Combine(Path.GetDirectoryName(realPath), ".."))); + + var mdlsPath = Path.GetFullPath(Path.Combine(ImportHelpers.Kh1ImportOriginalPath, relativePath, name)); + + //GD.Print(relativePath); + + Mdls mdls = null; + + if (File.Exists(mdlsPath)) mdls = new Mdls(mdlsPath); + + var joints = mdls?.Joints; + + var cvbl = new Cvbl(File.OpenRead(realPath), /*joints*/ null); + + var arrayMesh = new ArrayMesh(); + var skeleton = new Skeleton3D(); + + root.AddChild(skeleton); + + skeleton.Owner = root; + skeleton.Name = "Skeleton"; + + if (mdls is not null) + { + foreach (var joint in joints) + { + //TODO: for certain models, like sora, create name mappings + skeleton.AddBone(joint.Index.ToString()); + } + foreach (var joint in joints.Where(joint => joint.ParentId != 1023)) skeleton.SetBoneParent((int)joint.Index, (int)joint.ParentId); //1023 = no parent + foreach (var joint in joints) skeleton.SetBoneRest((int)joint.Index, joint.Transform()); + } + skeleton.ResetBonePoses(); + + for (var meshIndex = 0; meshIndex < cvbl.Submeshes.Count; meshIndex++) + { + var submesh = cvbl.Submeshes[meshIndex]; + var material = submesh.Material; + + var mat = new StandardMaterial3D(); + mat.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; + + if (cvbl.MeshEntries[meshIndex].Unk1 == 1) + { + mat.Transparency = BaseMaterial3D.TransparencyEnum.AlphaScissor; + mat.AlphaScissorThreshold = 0.5f; + } + + GD.Print($"{meshIndex}, {material}, {cvbl.MeshEntries[meshIndex].Unk1}, {cvbl.MeshEntries[meshIndex].Unk2}"); + + var positions = new List(); + var normals = new List(); + var uvs = new List(); + var bones = new List(); + var weights = new List(); + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + + foreach (var face in submesh.Faces) + { + foreach (var index in face.Reverse()) + { + var vert = submesh.Vertices[(int)index]; + + var p = vert.Position; + var pos = ImportHelpers.FromKH1Position(p.X, p.Y, p.Z); + var nor = vert.Normal; + var normal = new Vector3(nor.X, nor.Y, nor.Z); + var uv = vert.UV; + + if (vert.Joints.Length == 1) + { + var transform = skeleton.GetBoneGlobalPose(vert.Joints.First()); + + pos = transform * pos; + //normal = transform.Basis.GetRotationQuaternion() * normal; + } + else + { + GD.Print("More than 1 weight"); + } + + var boneList = new List(); + var weightList = new List(); + + if (mdls is not null) + { + var boneCount = vert.Joints.Length; + + switch (boneCount) + { + case < 4: + { + boneList.AddRange(vert.Joints.Select(b => (int)b)); + weightList.AddRange(vert.Weights); + while (boneList.Count < 4) + { + boneList.Add(0); + weightList.Add(0); + } + break; + } + case > 4: + { + boneList.AddRange(vert.Joints.Take(4).Select(b => (int)b)); + weightList.AddRange(vert.Weights.Take(4)); + break; + } + default: + { + boneList.AddRange(vert.Joints.Select(b => (int)b)); + weightList.AddRange(vert.Weights); + break; + } + } + } + else while (boneList.Count < 4) + { + boneList.Add(0); + weightList.Add(0); + } + + positions.Add(pos); + normals.Add(normal); + uvs.Add(new Vector2(uv.X, uv.Y)); + bones.AddRange(boneList); + weights.AddRange(weightList); + } + } + + array[(int)Mesh.ArrayType.Vertex] = positions.ToArray(); + array[(int)Mesh.ArrayType.Normal] = normals.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = uvs.ToArray(); + array[(int)Mesh.ArrayType.Bones] = bones.ToArray(); + array[(int)Mesh.ArrayType.Weights] = weights.ToArray(); + + arrayMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array, flags: + Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatBones | Mesh.ArrayFormat.FormatNormal | Mesh.ArrayFormat.FormatTexUV | Mesh.ArrayFormat.FormatWeights); + arrayMesh.SurfaceSetMaterial(meshIndex, mat); + } + + var model = new MeshInstance3D(); + skeleton.AddChild(model); + model.Name = "Model"; + model.Mesh = arrayMesh; + model.Owner = root; + skeleton.CreateSkinFromRestTransforms(); + + var packed = new PackedScene(); + packed.Pack(root); + ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/InterfaceLayoutImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/InterfaceLayoutImporter.cs new file mode 100644 index 000000000..9e22baa28 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/InterfaceLayoutImporter.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Helpers; +using OpenKh.Kh2; +using FileAccess = System.IO.FileAccess; + +namespace OpenKh.Godot.addons.OpenKHImporter +{ + public partial class InterfaceLayoutImporter : EditorImportPlugin + { + public enum Presets + { + Default, + } + + public override string _GetImporterName() => "InterfaceLayoutImporter"; + public override string _GetVisibleName() => "KH2 Interface Layout Importer"; + public override string[] _GetRecognizedExtensions() => ["2ld"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var name = Path.GetFileNameWithoutExtension(realPath); + + var relativePath = Path.GetRelativePath(ImportHelpers.Kh2ImportOriginalPath, realPath); + + var hdTexturesPath = Path.Combine(ImportHelpers.Kh2ImportRemasteredPath, relativePath); + + var usesHdTextures = false; + var hdTextures = new System.Collections.Generic.Dictionary(); + + if (Directory.Exists(hdTexturesPath)) + { + usesHdTextures = true; + foreach (var filePath in Directory.GetFiles(hdTexturesPath)) + { + var textureNameNoExtension = Path.GetFileNameWithoutExtension(filePath); + + var match = InterfaceTextureIndexMatch().Match(textureNameNoExtension); + + if (!match.Success || match.Groups.Count < 2) continue; + if (!int.TryParse(match.Groups[2].Value, out var number)) continue; + + + var localPath = ProjectSettings.LocalizePath(filePath); + hdTextures[number] = localPath; + } + } + + using var stream = File.Open(realPath, FileMode.Open, FileAccess.Read); + + stream.Seek(0, SeekOrigin.Begin); + + if (!Bar.IsValid(stream)) + return Error.Failed; + + var barFile = Bar.Read(stream); + + var images = usesHdTextures ? Enumerable.Repeat((Texture2D)null, hdTextures.Max(i => i.Key) + 1).ToList() : []; + + if (usesHdTextures) + { + foreach (var index in hdTextures) + { + try + { + var texture = ResourceLoader.Load(index.Value); + images[index.Key] = texture; + } + catch + { + images[index.Key] = null; + // ignored + } + } + } + + var result = Converters.FromInterfaceLayout(barFile, images); + + result.Name = name; + result.SetOwner(); + + var packed = new PackedScene(); + packed.Pack(result); + ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } + [GeneratedRegex("(.+?)([0-9]+)$")] + private static partial Regex InterfaceTextureIndexMatch(); + } +} diff --git a/OpenKh.Godot/addons/OpenKHImporter/InterfaceSequenceImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/InterfaceSequenceImporter.cs new file mode 100644 index 000000000..004a22a59 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/InterfaceSequenceImporter.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Helpers; +using OpenKh.Kh2; + +namespace OpenKh.Godot.addons.OpenKHImporter +{ + public partial class InterfaceSequenceImporter : EditorImportPlugin + { + public enum Presets + { + Default, + } + + public override string _GetImporterName() => "InterfaceSequenceImporter"; + public override string _GetVisibleName() => "KH2 Interface Sequence Importer"; + public override string[] _GetRecognizedExtensions() => ["2dd"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var name = Path.GetFileNameWithoutExtension(realPath); + + var relativePath = Path.GetRelativePath(ImportHelpers.Kh2ImportOriginalPath, realPath); + + var hdTexturesPath = Path.Combine(ImportHelpers.Kh2ImportRemasteredPath, relativePath); + + var usesHdTextures = false; + var hdTextures = new System.Collections.Generic.Dictionary(); + + if (Directory.Exists(hdTexturesPath)) + { + usesHdTextures = true; + foreach (var filePath in Directory.GetFiles(hdTexturesPath)) + { + var textureNameNoExtension = Path.GetFileNameWithoutExtension(filePath); + + var match = InterfaceTextureIndexMatch().Match(textureNameNoExtension); + + if (!match.Success || match.Groups.Count < 2) continue; + if (!int.TryParse(match.Groups[2].Value, out var number)) continue; + + + var localPath = ProjectSettings.LocalizePath(filePath); + hdTextures[number] = localPath; + } + } + + using var stream = File.Open(realPath, FileMode.Open, System.IO.FileAccess.Read); + + stream.Seek(0, SeekOrigin.Begin); + + if (!Bar.IsValid(stream)) + return Error.Failed; + + var barFile = Bar.Read(stream); + + var images = usesHdTextures ? Enumerable.Repeat((Texture2D)null, hdTextures.Max(i => i.Key) + 1).ToList() : null; + + if (usesHdTextures) + { + foreach (var index in hdTextures) + { + try + { + var texture = ResourceLoader.Load(index.Value); + images[index.Key] = texture; + } + catch + { + images[index.Key] = null; + // ignored + } + } + } + + GD.Print(name); + + barFile.PrintEntries(); + + var result = Converters.FromInterfaceSequence(barFile, images); + result.Name = name; + result.SetOwner(); + + var packed = new PackedScene(); + packed.Pack(result); + ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } + [GeneratedRegex("(.+?)([0-9]+)$")] + private static partial Regex InterfaceTextureIndexMatch(); + } +} diff --git a/OpenKh.Godot/addons/OpenKHImporter/MapImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/MapImporter.cs new file mode 100644 index 000000000..3e2a6286b --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/MapImporter.cs @@ -0,0 +1,109 @@ +#if TOOLS + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Common; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using OpenKh.Kh2.TextureFooter; +using Array = Godot.Collections.Array; +using FileAccess = System.IO.FileAccess; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +public partial class MapImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + + public override string _GetImporterName() => "MapImporter"; + public override string _GetVisibleName() => "KH2 Map Importer"; + public override string[] _GetRecognizedExtensions() => ["map"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var name = Path.GetFileNameWithoutExtension(realPath); + + var relativePath = Path.GetRelativePath(ImportHelpers.Kh2ImportOriginalPath, realPath); + + var hdTexturesPath = Path.Combine(ImportHelpers.Kh2ImportRemasteredPath, relativePath); + + var usesHdTextures = false; + var hdTextures = new System.Collections.Generic.Dictionary(); + + if (Directory.Exists(hdTexturesPath)) + { + usesHdTextures = true; + foreach (var filePath in Directory.GetFiles(hdTexturesPath)) + { + var textureName = Path.GetFileName(filePath); + + if (!textureName.StartsWith('-') || !int.TryParse(textureName[1..^4], out var number)) continue; + + var localPath = ProjectSettings.LocalizePath(filePath); + hdTextures[number] = localPath; + } + } + + using var stream = File.Open(realPath, FileMode.Open, FileAccess.Read); + + stream.Seek(0, SeekOrigin.Begin); + + if (!Bar.IsValid(stream)) + return Error.Failed; + + var barFile = Bar.Read(stream); + + var images = usesHdTextures ? Enumerable.Repeat((Texture2D)null, hdTextures.Max(i => i.Key) + 1).ToList() : null; + + if (usesHdTextures) + { + foreach (var index in hdTextures) + { + try + { + var texture = ResourceLoader.Load(index.Value); + images[index.Key] = texture; + } + catch + { + images[index.Key] = null; + // ignored + } + } + } + + var result = ModelConverters.FromMap(barFile, images); + result.Name = name; + result.SetOwner(); + + var packed = new PackedScene(); + packed.Pack(result); + ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/MdlsImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/MdlsImporter.cs new file mode 100644 index 000000000..350935e1a --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/MdlsImporter.cs @@ -0,0 +1,157 @@ +#if TOOLS + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Godot.Helpers; +using OpenKh.Kh1; +using Array = Godot.Collections.Array; +using Vector2 = Godot.Vector2; +using Vector3 = Godot.Vector3; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +public partial class MdlsImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + public override string _GetImporterName() => "MdlsImporter"; + public override string _GetVisibleName() => "KH1 Model Importer"; + public override string[] _GetRecognizedExtensions() => ["mdls"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var root = new Node3D(); + root.Name = Path.GetFileNameWithoutExtension(realPath); + + var mdls = new Mdls(realPath); + + var arrayMesh = new ArrayMesh(); + var skeleton = new Skeleton3D(); + + root.AddChild(skeleton); + + skeleton.Owner = root; + skeleton.Name = "Skeleton"; + + var joints = mdls.Joints; + + foreach (var joint in joints) + { + //TODO: for certain models, like sora, create name mappings + skeleton.AddBone(joint.Index.ToString()); + } + foreach (var joint in joints.Where(joint => joint.ParentId != 1023)) skeleton.SetBoneParent((int)joint.Index, (int)joint.ParentId); //1023 = no parent + foreach (var joint in joints) skeleton.SetBoneRest((int)joint.Index, joint.Transform()); + + skeleton.ResetBonePoses(); + + //thanks rider + var images = mdls.Images + .Select(texture => Image.CreateFromData(texture.bitmap.Width, texture.bitmap.Height, false, Image.Format.Rgb8, texture.bitmap.GetRGBBuffer())) + .Select(ImageTexture.CreateFromImage).ToList(); + + for (var m = 0; m < mdls.Meshes.Count; m++) + { + var mesh = mdls.Meshes[m]; + + var material = new StandardMaterial3D(); + material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; + + var tex = images[mesh.Header.TextureIndex]; + + //var image = mdls.Images[mesh.Header.TextureIndex]; + //GD.Print($"{m}, {image.Info.Unk[0]}, {image.Info.Unk[1]}"); + + material.AlbedoTexture = tex; + + var packet = mesh.packet; + + var positions = new List(); + var normals = new List(); + var uvs = new List(); + var uv2s = new List(); + var bones = new List(); + var weights = new List(); + + var array = new Array(); + array.Resize((int)Mesh.ArrayType.Max); + + foreach (var verts in packet.Faces) + { + foreach (var index in verts.Reverse()) + { + var vert = packet.Vertices.First(j => j.Index == index); + + var transform = skeleton.GetBoneGlobalPose(vert.JointId); + + var pos = vert.Position(); + var normal = new Vector3(vert.NormalX, vert.NormalY, vert.NormalZ); + + pos = transform * pos; + normal = transform.Basis.GetRotationQuaternion() * normal; + + positions.Add(pos); + normals.Add(normal); + uvs.Add(new Vector2(vert.TexCoordU, vert.TexCoordV)); + uv2s.Add(new Vector2(vert.TexCoord1, 0)); + bones.AddRange(new[] + { + vert.JointId, + 0, + 0, + 0, + }); + weights.AddRange(new[] + { + vert.Weight, + 0, + 0, + 0, + }); + } + } + + array[(int)Mesh.ArrayType.Vertex] = positions.ToArray(); + array[(int)Mesh.ArrayType.Normal] = normals.ToArray(); + array[(int)Mesh.ArrayType.TexUV] = uvs.ToArray(); + array[(int)Mesh.ArrayType.TexUV2] = uv2s.ToArray(); + array[(int)Mesh.ArrayType.Bones] = bones.ToArray(); + array[(int)Mesh.ArrayType.Weights] = weights.ToArray(); + + arrayMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array, flags: + Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatBones | Mesh.ArrayFormat.FormatNormal | Mesh.ArrayFormat.FormatTexUV | Mesh.ArrayFormat.FormatTexUV2 | + Mesh.ArrayFormat.FormatWeights); + arrayMesh.SurfaceSetMaterial(m, material); + } + + var model = new MeshInstance3D(); + skeleton.AddChild(model); + model.Name = "Model"; + model.Mesh = arrayMesh; + model.Owner = root; + skeleton.CreateSkinFromRestTransforms(); + + var packed = new PackedScene(); + packed.Pack(root); + ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/MdlxImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/MdlxImporter.cs new file mode 100644 index 000000000..b13aac695 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/MdlxImporter.cs @@ -0,0 +1,113 @@ +#if TOOLS + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Godot; +using Godot.Collections; +using OpenKh.Common; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Imaging; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using OpenKh.Kh2.TextureFooter; +using Array = Godot.Collections.Array; +using FileAccess = System.IO.FileAccess; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +public partial class MdlxImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + + public override string _GetImporterName() => "MdlxImporter"; + public override string _GetVisibleName() => "KH2 Model Importer"; + public override string[] _GetRecognizedExtensions() => ["mdlx"]; + public override string _GetSaveExtension() => "scn"; + public override string _GetResourceType() => "PackedScene"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + private static Shader BasicShader = ResourceLoader.Load("res://Assets/Shaders/KH2BasicShader.gdshader"); + private static Shader AnimatedShader = ResourceLoader.Load("res://Assets/Shaders/KH2AnimatedShader.gdshader"); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + var fileName = Path.GetFileName(realPath); + + var name = Path.GetFileNameWithoutExtension(realPath); + + var root = new KH2Mdlx(); + root.Name = name; + + var relativePath = Path.GetRelativePath(ImportHelpers.Kh2ImportOriginalPath, realPath); + + var hdTexturesPath = Path.Combine(ImportHelpers.Kh2ImportRemasteredPath, relativePath); + + var usesHdTextures = false; + var hdTextures = new System.Collections.Generic.Dictionary(); + + if (Directory.Exists(hdTexturesPath)) + { + usesHdTextures = true; + foreach (var filePath in Directory.GetFiles(hdTexturesPath)) + { + var textureName = Path.GetFileName(filePath); + + //GD.Print(textureName[1..^4]); + + if (!textureName.StartsWith('-') || !int.TryParse(textureName[1..^4], out var number)) continue; + + var localPath = ProjectSettings.LocalizePath(filePath); + hdTextures[number] = localPath; + } + } + + using var stream = File.Open(realPath, FileMode.Open, FileAccess.Read); + + stream.Seek(0, SeekOrigin.Begin); + + if (!Bar.IsValid(stream)) + return Error.Failed; + + var barFile = Bar.Read(stream); + + var images = usesHdTextures ? Enumerable.Repeat((Texture2D)null, hdTextures.Max(i => i.Key) + 1).ToList() : null; + + if (usesHdTextures) + { + foreach (var index in hdTextures) + { + var texture = ResourceLoader.Load(index.Value); + images[index.Key] = texture; + } + } + + var result = ModelConverters.FromMdlx(barFile, images); + result.Name = name; + result.SetOwner(); + + GD.Print(result.GetChildren().Count); + + var packed = new PackedScene(); + packed.Pack(result); + GD.Print(ResourceSaver.Save(packed, $"{savePath}.{_GetSaveExtension()}")); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/MsetImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/MsetImporter.cs new file mode 100644 index 000000000..3e8e8c4b2 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/MsetImporter.cs @@ -0,0 +1,73 @@ +#if TOOLS + +using System; +using System.IO; +using System.Linq; +using System.Numerics; +using Godot; +using Godot.Collections; +using OpenKh.Common; +using OpenKh.Godot.Helpers; +using OpenKh.Godot.Nodes; +using OpenKh.Godot.Resources; +using OpenKh.Kh2; +using OpenKh.Kh2.Models; +using OpenKh.Kh2Anim.Mset; +using Quaternion = Godot.Quaternion; +using Vector3 = Godot.Vector3; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +//TODO: this takes wayyyy too long to import one file +public partial class MsetImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + public override string _GetImporterName() => "MsetImporter"; + public override string _GetVisibleName() => "KH2 Moveset Importer"; + public override string[] _GetRecognizedExtensions() => ["mset"]; + public override string _GetSaveExtension() => "tres"; + public override string _GetResourceType() => "KH2Moveset"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + var realPath = ProjectSettings.GlobalizePath(sourceFile); + + using var stream = File.Open(realPath, FileMode.Open, System.IO.FileAccess.Read); + + if (!BinaryArchive.IsValid(stream)) return Error.InvalidData; + + var msetBinarc = BinaryArchive.Read(stream); + + var container = new KH2Moveset(); + + container.Entries = new Array(msetBinarc.Entries.Select(entry => entry.Link < 0 || msetBinarc.Subfiles[entry.Link].Length == 0 + ? null + : new KH2MovesetEntry + { + Motion = new Func(() => + { + var bar = Bar.Read(new MemoryStream(msetBinarc.Subfiles[entry.Link])); + var a = new InterpolatedMotionResource + { + Binary = bar.First(i => i.Type == Bar.EntryType.Motion).Stream.ReadAllBytes(), + }; + return a; + }).Invoke(), + })); + + ResourceSaver.Save(container, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/Plugin.cs b/OpenKh.Godot/addons/OpenKHImporter/Plugin.cs new file mode 100644 index 000000000..113be5ec4 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/Plugin.cs @@ -0,0 +1,42 @@ +/* +#if TOOLS +using System; +using Godot; +using Godot.Collections; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +[Tool] +public partial class Plugin : EditorPlugin +{ + private static readonly Type[] ImporterTypes = + [ + typeof(MdlsImporter), + typeof(MdlxImporter), + typeof(ScdImporter), + typeof(CvblImporter), + typeof(MsetImporter), + typeof(MapImporter), + typeof(InterfaceLayoutImporter), + typeof(InterfaceSequenceImporter), + ]; + private Array Importers = new(); + public override void _EnterTree() + { + foreach (var t in ImporterTypes) + { + var importer = Activator.CreateInstance(t) as EditorImportPlugin; + Importers.Add(importer); + AddImportPlugin(importer); + } + } + + public override void _ExitTree() + { + foreach (var importer in Importers) RemoveImportPlugin(importer); + Importers.Clear(); + } +} +#endif +*/ +//deprecated diff --git a/OpenKh.Godot/addons/OpenKHImporter/ScdImporter.cs b/OpenKh.Godot/addons/OpenKHImporter/ScdImporter.cs new file mode 100644 index 000000000..f8f2c3f47 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/ScdImporter.cs @@ -0,0 +1,46 @@ +#if TOOLS + +using System; +using System.IO; +using System.Linq; +using FFMpegCore; +using FFMpegCore.Pipes; +using Godot; +using Godot.Collections; +using OpenKh.Bbs; +using OpenKh.Godot.Conversion; +using OpenKh.Godot.Resources; + +namespace OpenKh.Godot.addons.OpenKHImporter; + +public partial class ScdImporter : EditorImportPlugin +{ + public enum Presets + { + Default, + } + public override string _GetImporterName() => "ScdImporter"; + public override string _GetVisibleName() => "KH Sound Container Importer"; + public override string[] _GetRecognizedExtensions() => ["scd"]; + public override string _GetSaveExtension() => "tres"; + public override string _GetResourceType() => "SoundContainer"; + public override float _GetPriority() => 1; + public override int _GetImportOrder() => 0; + public override int _GetPresetCount() => Enum.GetValues().Length; + public override string _GetPresetName(int presetIndex) => ((Presets)presetIndex).ToString(); + public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override Array _GetImportOptions(string path, int presetIndex) => base._GetImportOptions(path, presetIndex); + + public override Error _Import(string sourceFile, string savePath, Dictionary options, Array platformVariants, Array genFiles) + { + using var stream = File.Open(ProjectSettings.GlobalizePath(sourceFile), FileMode.Open, System.IO.FileAccess.Read); + + var container = Converters.FromScd(Scd.Read(stream)); + + ResourceSaver.Save(container, $"{savePath}.{_GetSaveExtension()}"); + + return Error.Ok; + } +} + +#endif diff --git a/OpenKh.Godot/addons/OpenKHImporter/plugin.cfg b/OpenKh.Godot/addons/OpenKHImporter/plugin.cfg new file mode 100644 index 000000000..a2f87d1d4 --- /dev/null +++ b/OpenKh.Godot/addons/OpenKHImporter/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="OpenKHImporter" +description="" +author="Frozenreflex" +version="" +script="Plugin.cs" diff --git a/OpenKh.Godot/addons/debug_draw_3d/LICENSE b/OpenKh.Godot/addons/debug_draw_3d/LICENSE new file mode 100644 index 000000000..617a15b3d --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 DmitriySalnikov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the Software), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, andor sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/OpenKh.Godot/addons/debug_draw_3d/README.md b/OpenKh.Godot/addons/debug_draw_3d/README.md new file mode 100644 index 000000000..6bb3942a4 --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/README.md @@ -0,0 +1,158 @@ +![icon](/images/icon_3d_128.png) + +# Debug drawing utility for Godot + +This is an add-on for debug drawing in 3D and for some 2D overlays, which is written in `C++` and can be used with `GDScript` or `C#`. + +Based on my previous addon, which was developed [only for C#](https://github.com/DmitriySalnikov/godot_debug_draw_cs), and which was inspired by [Zylann's GDScript addon](https://github.com/Zylann/godot_debug_draw) + +## [Documentation](https://dd3d.dmitriysalnikov.ru/docs/) + +## [Godot 3 version](https://github.com/DmitriySalnikov/godot_debug_draw_3d/tree/godot_3) + +## Support me + +Your support adds motivation to develop my public projects. + +Boosty + +USDT-TRC20 + +USDT-TRC20 TEw934PrsffHsAn5M63SoHYRuZo984EF6v + +## Features + +3D: + +* Arrow +* Billboard opaque square +* Box +* Camera Frustum +* Cylinder +* Gizmo +* Grid +* Line +* Line Path +* Line with Arrow +* Plane +* Points +* Position 3D (3 crossing axes) +* Sphere + +2D: + +* **[Work in progress]** + +Overlay: + +* Text (with grouping and coloring) +* FPS Graph +* Custom Graphs + +Precompiled for: + +* Windows +* Linux (built on Ubuntu 20.04) +* macOS (10.14+) +* Android (5.0+) +* iOS +* Web (Firefox not supported) + +This addon supports working with several World3D and different Viewports. +There is also a no depth test mode and other settings that can be changed for each instance. + +## [Interactive Web Demo](https://dd3d.dmitriysalnikov.ru/demo/) + +[![screenshot_web](/images/screenshot_web.png)](https://dd3d.dmitriysalnikov.ru/demo/) + +> [!WARNING] +> +> * Firefox most likely can't run this demo + +## Download + +To download, use the [Godot Asset Library](https://godotengine.org/asset-library/asset/1766) or download the archive by clicking the button at the top of the main repository page: `Code -> Download ZIP`, then unzip it to your project folder. Or use one of the stable versions from the [GitHub Releases](https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases) page (just download one of the `Source Codes` in assets). + +### Installation + +* Close editor +* Copy `addons/debug_draw_3d` to your `addons` folder, create it if the folder doesn't exist +* Launch editor + +## Examples + +More examples can be found in the `examples_dd3d/` folder. + +Simple test: + +```gdscript +func _process(delta: float) -> void: + var _time = Time.get_ticks_msec() / 1000.0 + var box_pos = Vector3(0, sin(_time * 4), 0) + var line_begin = Vector3(-1, sin(_time * 4), 0) + var line_end = Vector3(1, cos(_time * 4), 0) + + DebugDraw3D.draw_box(box_pos, Vector3(1, 2, 1), Color(0, 1, 0)) + DebugDraw3D.draw_line(line_begin, line_end, Color(1, 1, 0)) + DebugDraw2D.set_text("Time", _time) + DebugDraw2D.set_text("Frames drawn", Engine.get_frames_drawn()) + DebugDraw2D.set_text("FPS", Engine.get_frames_per_second()) + DebugDraw2D.set_text("delta", delta) +``` + +![screenshot_1](/images/screenshot_1.png) + +An example of using scoped configs: + +```gdscript +@tool +extends Node3D + +func _ready(): + # Set the base scoped_config. + # Each frame will be reset to these scoped values. + DebugDraw3D.scoped_config().set_thickness(0.1).set_center_brightness(0.6) + +func _process(delta): + # Draw using the base scoped config. + DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE * 2, Color.CORNFLOWER_BLUE) + if true: + # Create a scoped config that will exist until exiting this if. + var _s = DebugDraw3D.new_scoped_config().set_thickness(0).set_center_brightness(0.1) + # Draw with a thickness of 0 + DebugDraw3D.draw_box(Vector3.ZERO, Quaternion.IDENTITY, Vector3.ONE, Color.RED) + # If necessary, the values inside this scope can be changed + # even before each call to draw_*. + _s.set_thickness(0.05) + DebugDraw3D.draw_box(Vector3(1,0,1), Quaternion.IDENTITY, Vector3.ONE * 1, Color.BLUE_VIOLET) +``` + +![screenshot_5](/images/screenshot_5.png) + +> [!TIP] +> +> If you want to use a non-standard Viewport for rendering a 3d scene, then do not forget to specify it in the scoped config! + +## API + +This project has a separate [documentation](https://dd3d.dmitriysalnikov.ru/docs/) page. + +Also, a list of all functions is available in the documentation inside the editor (see `DebugDraw3D` and `DebugDraw2D`). + +![screenshot_4](/images/screenshot_4.png) + +## Known issues and limitations + +The text in the keys and values of a text group cannot contain multi-line strings. + +The entire text overlay can only be placed in one corner, unlike `DataGraphs`. + +[Frustum of Camera3D does not take into account the window size from ProjectSettings](https://github.com/godotengine/godot/issues/70362). + +## More screenshots + +`DebugDrawDemoScene.tscn` in editor +![screenshot_2](/images/screenshot_2.png) + +`DebugDrawDemoScene.tscn` in play mode +![screenshot_3](/images/screenshot_3.png) diff --git a/OpenKh.Godot/addons/debug_draw_3d/debug_draw_3d.gdextension b/OpenKh.Godot/addons/debug_draw_3d/debug_draw_3d.gdextension new file mode 100644 index 000000000..523bd4a04 --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/debug_draw_3d.gdextension @@ -0,0 +1,103 @@ +[configuration] + +entry_symbol = "debug_draw_3d_library_init" +compatibility_minimum = "4.1.4" +reloadable = false + +[dependencies] + +; example.x86_64 = { "relative or absolute path to the dependency" : "the path relative to the exported project", } +; ------------------------------------- +; debug + +macos = { } +windows.x86_64 = { } +linux.x86_64 = { } + +; by default godot is using threads +web.wasm32.nothreads = {} +web.wasm32 = {} + +android.arm32 = { } +android.arm64 = { } +android.x86_32 = { } +android.x86_64 = { } + +ios = {} + +; ------------------------------------- +; release no debug draw + +macos.template_release = { } +windows.template_release.x86_64 = { } +linux.template_release.x86_64 = { } + +web.template_release.wasm32.nothreads = { } +web.template_release.wasm32 = { } + +android.template_release.arm32 = { } +android.template_release.arm64 = { } +android.template_release.x86_32 = { } +android.template_release.x86_64 = { } + +ios.template_release = {} + +; ------------------------------------- +; release forced debug draw + +macos.template_release.forced_dd3d = { } +windows.template_release.x86_64.forced_dd3d = { } +linux.template_release.x86_64.forced_dd3d = { } + +web.template_release.wasm32.nothreads.forced_dd3d = { } +web.template_release.wasm32.forced_dd3d = { } + +ios.template_release.forced_dd3d = {} + +[libraries] + +; ------------------------------------- +; debug + +macos = "libs/libdd3d.macos.editor.universal.framework" +windows.x86_64 = "libs/libdd3d.windows.editor.x86_64.dll" +linux.x86_64 = "libs/libdd3d.linux.editor.x86_64.so" + +web.wasm32.nothreads = "libs/libdd3d.web.template_debug.wasm32.wasm" +web.wasm32 = "libs/libdd3d.web.template_debug.wasm32.threads.wasm" + +android.arm32 = "libs/libdd3d.android.template_debug.arm32.so" +android.arm64 = "libs/libdd3d.android.template_debug.arm64.so" +android.x86_32 = "libs/libdd3d.android.template_debug.x86_32.so" +android.x86_64 = "libs/libdd3d.android.template_debug.x86_64.so" + +ios = "libs/libdd3d.ios.template_debug.universal.dylib" + +; ------------------------------------- +; release no debug draw + +macos.template_release = "libs/libdd3d.macos.template_release.universal.framework" +windows.template_release.x86_64 = "libs/libdd3d.windows.template_release.x86_64.dll" +linux.template_release.x86_64 = "libs/libdd3d.linux.template_release.x86_64.so" + +web.template_release.wasm32.nothreads = "libs/libdd3d.web.template_release.wasm32.wasm" +web.template_release.wasm32 = "libs/libdd3d.web.template_release.wasm32.threads.wasm" + +android.template_release.arm32 = "libs/libdd3d.android.template_release.arm32.so" +android.template_release.arm64 = "libs/libdd3d.android.template_release.arm64.so" +android.template_release.x86_32 = "libs/libdd3d.android.template_release.x86_32.so" +android.template_release.x86_64 = "libs/libdd3d.android.template_release.x86_64.so" + +ios.template_release = "libs/libdd3d.ios.template_release.universal.dylib" + +; ------------------------------------- +; release forced debug draw + +macos.template_release.forced_dd3d = "libs/libdd3d.macos.template_release.universal.enabled.framework" +windows.template_release.x86_64.forced_dd3d = "libs/libdd3d.windows.template_release.x86_64.enabled.dll" +linux.template_release.x86_64.forced_dd3d = "libs/libdd3d.linux.template_release.x86_64.enabled.so" + +web.template_release.wasm32.nothreads.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.enabled.wasm" +web.template_release.wasm32.forced_dd3d = "libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm" + +ios.template_release.forced_dd3d = "libs/libdd3d.ios.template_release.universal.enabled.dylib" diff --git a/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/DebugDrawGeneratedAPI.generated.cs b/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/DebugDrawGeneratedAPI.generated.cs new file mode 100644 index 000000000..89940e8b6 --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/DebugDrawGeneratedAPI.generated.cs @@ -0,0 +1,1789 @@ +/// 1.4.4 +/// //////////////////////////////////////////////// +/// THIS FILE HAS BEEN GENERATED. +/// THE CHANGES IN THIS FILE WILL BE OVERWRITTEN +/// AFTER THE UPDATE OR AFTER THE RESTART! +/// //////////////////////////////////////////////// + +using Godot; +using System; +using System.Linq; + +static internal class DebugDraw2D +{ + private static GodotObject _instance; + public static GodotObject Instance + { + get + { + if (!GodotObject.IsInstanceValid(_instance)) + { + _instance = Engine.GetSingleton("DebugDraw2D"); + } + return _instance; + } + } + + private static readonly StringName __clear_all = "clear_all"; + private static readonly StringName __begin_text_group = "begin_text_group"; + private static readonly StringName __end_text_group = "end_text_group"; + private static readonly StringName __set_text = "set_text"; + private static readonly StringName __clear_texts = "clear_texts"; + private static readonly StringName __create_graph = "create_graph"; + private static readonly StringName __create_fps_graph = "create_fps_graph"; + private static readonly StringName __graph_update_data = "graph_update_data"; + private static readonly StringName __remove_graph = "remove_graph"; + private static readonly StringName __clear_graphs = "clear_graphs"; + private static readonly StringName __get_graph = "get_graph"; + private static readonly StringName __get_graph_names = "get_graph_names"; + private static readonly StringName __get_render_stats = "get_render_stats"; + + public static void ClearAll() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__clear_all); +#endif + } + } + + public static void BeginTextGroup(string group_title, int group_priority = 0, Color? group_color = null, bool show_title = true, int title_size = -1, int text_size = -1) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__begin_text_group, group_title, group_priority, group_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_0, show_title, title_size, text_size); +#endif + } + } + + public static void EndTextGroup() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__end_text_group); +#endif + } + } + + public static void SetText(string key, Variant? value = null, int priority = 0, Color? color_of_value = null, float duration = -1f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__set_text, key, value ?? _DebugDrawUtils_.DefaultArgumentsData.arg_1, priority, color_of_value ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void ClearTexts() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__clear_texts); +#endif + } + } + + public static DebugDraw2DGraph CreateGraph(StringName title) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw2DGraph)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__create_graph, title)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static DebugDraw2DGraph CreateFpsGraph(StringName title) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw2DGraph)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__create_fps_graph, title)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static void GraphUpdateData(StringName title, float data) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__graph_update_data, title, data); +#endif + } + } + + public static void RemoveGraph(StringName title) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__remove_graph, title); +#endif + } + } + + public static void ClearGraphs() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__clear_graphs); +#endif + } + } + + public static DebugDraw2DGraph GetGraph(StringName title) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw2DGraph)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__get_graph, title)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static string[] GetGraphNames() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (string[])(Instance?.Call(__get_graph_names)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static DebugDraw2DStats GetRenderStats() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw2DStats)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__get_render_stats)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + private static readonly StringName __prop_empty_color = "empty_color"; + private static readonly StringName __prop_debug_enabled = "debug_enabled"; + private static readonly StringName __prop_config = "config"; + private static readonly StringName __prop_custom_canvas = "custom_canvas"; + + public static Color EmptyColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_empty_color); + set => ClassDB.ClassSetProperty(Instance, __prop_empty_color, value); + } + + public static bool DebugEnabled + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_debug_enabled); + set => ClassDB.ClassSetProperty(Instance, __prop_debug_enabled, value); + } + + public static DebugDraw2DConfig Config + { + get => (DebugDraw2DConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)ClassDB.ClassGetProperty(Instance, __prop_config)); + set => ClassDB.ClassSetProperty(Instance, __prop_config, value.Instance); + } + + public static Control CustomCanvas + { + get => (Control)ClassDB.ClassGetProperty(Instance, __prop_custom_canvas); + set => ClassDB.ClassSetProperty(Instance, __prop_custom_canvas, value); + } + +} + +internal class DebugDraw2DStats : _DebugDrawInstanceWrapper_ +{ + public DebugDraw2DStats(GodotObject _instance) : base (_instance) {} + + public DebugDraw2DStats() : this((GodotObject)ClassDB.Instantiate("DebugDraw2DStats")) { } + + private static readonly StringName __prop_overlay_text_groups = "overlay_text_groups"; + private static readonly StringName __prop_overlay_text_lines = "overlay_text_lines"; + private static readonly StringName __prop_overlay_graphs_enabled = "overlay_graphs_enabled"; + private static readonly StringName __prop_overlay_graphs_total = "overlay_graphs_total"; + + public int OverlayTextGroups + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_overlay_text_groups); + set => ClassDB.ClassSetProperty(Instance, __prop_overlay_text_groups, value); + } + + public int OverlayTextLines + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_overlay_text_lines); + set => ClassDB.ClassSetProperty(Instance, __prop_overlay_text_lines, value); + } + + public int OverlayGraphsEnabled + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_overlay_graphs_enabled); + set => ClassDB.ClassSetProperty(Instance, __prop_overlay_graphs_enabled, value); + } + + public int OverlayGraphsTotal + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_overlay_graphs_total); + set => ClassDB.ClassSetProperty(Instance, __prop_overlay_graphs_total, value); + } + +} + +internal class DebugDraw2DConfig : _DebugDrawInstanceWrapper_ +{ + public DebugDraw2DConfig(GodotObject _instance) : base (_instance) {} + + public DebugDraw2DConfig() : this((GodotObject)ClassDB.Instantiate("DebugDraw2DConfig")) { } + + public enum BlockPosition : long + { + LeftTop = 0, + RightTop = 1, + LeftBottom = 2, + RightBottom = 3, + } + + private static readonly StringName __prop_graphs_base_offset = "graphs_base_offset"; + private static readonly StringName __prop_text_block_position = "text_block_position"; + private static readonly StringName __prop_text_block_offset = "text_block_offset"; + private static readonly StringName __prop_text_padding = "text_padding"; + private static readonly StringName __prop_text_default_duration = "text_default_duration"; + private static readonly StringName __prop_text_default_size = "text_default_size"; + private static readonly StringName __prop_text_foreground_color = "text_foreground_color"; + private static readonly StringName __prop_text_background_color = "text_background_color"; + private static readonly StringName __prop_text_custom_font = "text_custom_font"; + + public Vector2I GraphsBaseOffset + { + get => (Vector2I)ClassDB.ClassGetProperty(Instance, __prop_graphs_base_offset); + set => ClassDB.ClassSetProperty(Instance, __prop_graphs_base_offset, value); + } + + public DebugDraw2DConfig.BlockPosition TextBlockPosition + { + get => (DebugDraw2DConfig.BlockPosition)(long)ClassDB.ClassGetProperty(Instance, __prop_text_block_position); + set => ClassDB.ClassSetProperty(Instance, __prop_text_block_position, (long)value); + } + + public Vector2I TextBlockOffset + { + get => (Vector2I)ClassDB.ClassGetProperty(Instance, __prop_text_block_offset); + set => ClassDB.ClassSetProperty(Instance, __prop_text_block_offset, value); + } + + public Vector2I TextPadding + { + get => (Vector2I)ClassDB.ClassGetProperty(Instance, __prop_text_padding); + set => ClassDB.ClassSetProperty(Instance, __prop_text_padding, value); + } + + public float TextDefaultDuration + { + get => (float)ClassDB.ClassGetProperty(Instance, __prop_text_default_duration); + set => ClassDB.ClassSetProperty(Instance, __prop_text_default_duration, value); + } + + public int TextDefaultSize + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_text_default_size); + set => ClassDB.ClassSetProperty(Instance, __prop_text_default_size, value); + } + + public Color TextForegroundColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_text_foreground_color); + set => ClassDB.ClassSetProperty(Instance, __prop_text_foreground_color, value); + } + + public Color TextBackgroundColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_text_background_color); + set => ClassDB.ClassSetProperty(Instance, __prop_text_background_color, value); + } + + public Font TextCustomFont + { + get => (Font)ClassDB.ClassGetProperty(Instance, __prop_text_custom_font); + set => ClassDB.ClassSetProperty(Instance, __prop_text_custom_font, value); + } + +} + +internal class DebugDraw2DGraph : _DebugDrawInstanceWrapper_ +{ + public DebugDraw2DGraph(GodotObject _instance) : base (_instance) {} + + public DebugDraw2DGraph() : this((GodotObject)ClassDB.Instantiate("DebugDraw2DGraph")) { } + + public enum GraphPosition : long + { + LeftTop = 0, + RightTop = 1, + LeftBottom = 2, + RightBottom = 3, + } + + public enum GraphSide : long + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3, + } + + public enum TextFlags : long + { + Current = 1, + Avg = 2, + Max = 4, + Min = 8, + All = 15, + } + + private static readonly StringName __get_title = "get_title"; + private static readonly StringName __set_parent = "set_parent"; + + public StringName GetTitle() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (StringName)(Instance?.Call(__get_title)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public void SetParent(StringName parent, DebugDraw2DGraph.GraphSide side = (DebugDraw2DGraph.GraphSide)3) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__set_parent, parent, (long)side); +#endif + } + } + + private static readonly StringName __prop_enabled = "enabled"; + private static readonly StringName __prop_upside_down = "upside_down"; + private static readonly StringName __prop_show_title = "show_title"; + private static readonly StringName __prop_show_text_flags = "show_text_flags"; + private static readonly StringName __prop_size = "size"; + private static readonly StringName __prop_buffer_size = "buffer_size"; + private static readonly StringName __prop_offset = "offset"; + private static readonly StringName __prop_corner = "corner"; + private static readonly StringName __prop_line_width = "line_width"; + private static readonly StringName __prop_line_color = "line_color"; + private static readonly StringName __prop_background_color = "background_color"; + private static readonly StringName __prop_border_color = "border_color"; + private static readonly StringName __prop_text_suffix = "text_suffix"; + private static readonly StringName __prop_custom_font = "custom_font"; + private static readonly StringName __prop_title_size = "title_size"; + private static readonly StringName __prop_text_size = "text_size"; + private static readonly StringName __prop_title_color = "title_color"; + private static readonly StringName __prop_text_color = "text_color"; + private static readonly StringName __prop_text_precision = "text_precision"; + private static readonly StringName __prop_parent_graph = "parent_graph"; + private static readonly StringName __prop_parent_graph_side = "parent_graph_side"; + private static readonly StringName __prop_data_getter = "data_getter"; + + public bool Enabled + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_enabled); + set => ClassDB.ClassSetProperty(Instance, __prop_enabled, value); + } + + public bool UpsideDown + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_upside_down); + set => ClassDB.ClassSetProperty(Instance, __prop_upside_down, value); + } + + public bool ShowTitle + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_show_title); + set => ClassDB.ClassSetProperty(Instance, __prop_show_title, value); + } + + public DebugDraw2DGraph.TextFlags ShowTextFlags + { + get => (DebugDraw2DGraph.TextFlags)(long)ClassDB.ClassGetProperty(Instance, __prop_show_text_flags); + set => ClassDB.ClassSetProperty(Instance, __prop_show_text_flags, (long)value); + } + + public Vector2I Size + { + get => (Vector2I)ClassDB.ClassGetProperty(Instance, __prop_size); + set => ClassDB.ClassSetProperty(Instance, __prop_size, value); + } + + public int BufferSize + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_buffer_size); + set => ClassDB.ClassSetProperty(Instance, __prop_buffer_size, value); + } + + public Vector2I Offset + { + get => (Vector2I)ClassDB.ClassGetProperty(Instance, __prop_offset); + set => ClassDB.ClassSetProperty(Instance, __prop_offset, value); + } + + public DebugDraw2DGraph.GraphPosition Corner + { + get => (DebugDraw2DGraph.GraphPosition)(long)ClassDB.ClassGetProperty(Instance, __prop_corner); + set => ClassDB.ClassSetProperty(Instance, __prop_corner, (long)value); + } + + public float LineWidth + { + get => (float)ClassDB.ClassGetProperty(Instance, __prop_line_width); + set => ClassDB.ClassSetProperty(Instance, __prop_line_width, value); + } + + public Color LineColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_line_color); + set => ClassDB.ClassSetProperty(Instance, __prop_line_color, value); + } + + public Color BackgroundColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_background_color); + set => ClassDB.ClassSetProperty(Instance, __prop_background_color, value); + } + + public Color BorderColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_border_color); + set => ClassDB.ClassSetProperty(Instance, __prop_border_color, value); + } + + public string TextSuffix + { + get => (string)ClassDB.ClassGetProperty(Instance, __prop_text_suffix); + set => ClassDB.ClassSetProperty(Instance, __prop_text_suffix, value); + } + + public Font CustomFont + { + get => (Font)ClassDB.ClassGetProperty(Instance, __prop_custom_font); + set => ClassDB.ClassSetProperty(Instance, __prop_custom_font, value); + } + + public int TitleSize + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_title_size); + set => ClassDB.ClassSetProperty(Instance, __prop_title_size, value); + } + + public int TextSize + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_text_size); + set => ClassDB.ClassSetProperty(Instance, __prop_text_size, value); + } + + public Color TitleColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_title_color); + set => ClassDB.ClassSetProperty(Instance, __prop_title_color, value); + } + + public Color TextColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_text_color); + set => ClassDB.ClassSetProperty(Instance, __prop_text_color, value); + } + + public int TextPrecision + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_text_precision); + set => ClassDB.ClassSetProperty(Instance, __prop_text_precision, value); + } + + public StringName ParentGraph + { + get => (StringName)ClassDB.ClassGetProperty(Instance, __prop_parent_graph); + set => ClassDB.ClassSetProperty(Instance, __prop_parent_graph, value); + } + + public DebugDraw2DGraph.GraphSide ParentGraphSide + { + get => (DebugDraw2DGraph.GraphSide)(long)ClassDB.ClassGetProperty(Instance, __prop_parent_graph_side); + set => ClassDB.ClassSetProperty(Instance, __prop_parent_graph_side, (long)value); + } + + public Callable DataGetter + { + get => (Callable)ClassDB.ClassGetProperty(Instance, __prop_data_getter); + set => ClassDB.ClassSetProperty(Instance, __prop_data_getter, value); + } + +} + +internal class DebugDraw2DFPSGraph : DebugDraw2DGraph +{ + public DebugDraw2DFPSGraph(GodotObject _instance) : base (_instance) {} + + public DebugDraw2DFPSGraph() : this((GodotObject)ClassDB.Instantiate("DebugDraw2DFPSGraph")) { } + + private static readonly StringName __prop_frame_time_mode = "frame_time_mode"; + + public bool FrameTimeMode + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_frame_time_mode); + set => ClassDB.ClassSetProperty(Instance, __prop_frame_time_mode, value); + } + +} + +static internal class DebugDraw3D +{ + private static GodotObject _instance; + public static GodotObject Instance + { + get + { + if (!GodotObject.IsInstanceValid(_instance)) + { + _instance = Engine.GetSingleton("DebugDraw3D"); + } + return _instance; + } + } + + public enum PointType : long + { + TypeSquare = 0, + TypeSphere = 1, + } + + private static readonly StringName __regenerate_geometry_meshes = "regenerate_geometry_meshes"; + private static readonly StringName __clear_all = "clear_all"; + private static readonly StringName __draw_sphere = "draw_sphere"; + private static readonly StringName __draw_sphere_xf = "draw_sphere_xf"; + private static readonly StringName __draw_cylinder = "draw_cylinder"; + private static readonly StringName __draw_cylinder_ab = "draw_cylinder_ab"; + private static readonly StringName __draw_box = "draw_box"; + private static readonly StringName __draw_box_ab = "draw_box_ab"; + private static readonly StringName __draw_box_xf = "draw_box_xf"; + private static readonly StringName __draw_aabb = "draw_aabb"; + private static readonly StringName __draw_aabb_ab = "draw_aabb_ab"; + private static readonly StringName __draw_line_hit = "draw_line_hit"; + private static readonly StringName __draw_line_hit_offset = "draw_line_hit_offset"; + private static readonly StringName __draw_line = "draw_line"; + private static readonly StringName __draw_lines = "draw_lines"; + private static readonly StringName __draw_ray = "draw_ray"; + private static readonly StringName __draw_line_path = "draw_line_path"; + private static readonly StringName __draw_arrowhead = "draw_arrowhead"; + private static readonly StringName __draw_arrow = "draw_arrow"; + private static readonly StringName __draw_arrow_ray = "draw_arrow_ray"; + private static readonly StringName __draw_arrow_path = "draw_arrow_path"; + private static readonly StringName __draw_point_path = "draw_point_path"; + private static readonly StringName __draw_square = "draw_square"; + private static readonly StringName __draw_plane = "draw_plane"; + private static readonly StringName __draw_points = "draw_points"; + private static readonly StringName __draw_camera_frustum = "draw_camera_frustum"; + private static readonly StringName __draw_camera_frustum_planes = "draw_camera_frustum_planes"; + private static readonly StringName __draw_position = "draw_position"; + private static readonly StringName __draw_gizmo = "draw_gizmo"; + private static readonly StringName __draw_grid = "draw_grid"; + private static readonly StringName __draw_grid_xf = "draw_grid_xf"; + private static readonly StringName __get_render_stats = "get_render_stats"; + private static readonly StringName __get_render_stats_for_world = "get_render_stats_for_world"; + private static readonly StringName __new_scoped_config = "new_scoped_config"; + private static readonly StringName __scoped_config = "scoped_config"; + + public static void RegenerateGeometryMeshes() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__regenerate_geometry_meshes); +#endif + } + } + + public static void ClearAll() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__clear_all); +#endif + } + } + + public static void DrawSphere(Vector3 position, float radius = 0.5f, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_sphere, position, radius, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawSphereXf(Transform3D transform, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_sphere_xf, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawCylinder(Transform3D transform, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_cylinder, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawCylinderAb(Vector3 a, Vector3 b, float radius = 0.5f, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_cylinder_ab, a, b, radius, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawBox(Vector3 position, Quaternion rotation, Vector3 size, Color? color = null, bool is_box_centered = false, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_box, position, rotation, size, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_box_centered, duration); +#endif + } + } + + public static void DrawBoxAb(Vector3 a, Vector3 b, Vector3? up = null, Color? color = null, bool is_ab_diagonal = true, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_box_ab, a, b, up ?? _DebugDrawUtils_.DefaultArgumentsData.arg_3, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_ab_diagonal, duration); +#endif + } + } + + public static void DrawBoxXf(Transform3D transform, Color? color = null, bool is_box_centered = true, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_box_xf, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_box_centered, duration); +#endif + } + } + + public static void DrawAabb(Aabb aabb, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_aabb, aabb, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawAabbAb(Vector3 a, Vector3 b, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_aabb_ab, a, b, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawLineHit(Vector3 start, Vector3 end, Vector3 hit, bool is_hit, float hit_size = 0.25f, Color? hit_color = null, Color? after_hit_color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_line_hit, start, end, hit, is_hit, hit_size, hit_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, after_hit_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawLineHitOffset(Vector3 start, Vector3 end, bool is_hit, float unit_offset_of_hit = 0.5f, float hit_size = 0.25f, Color? hit_color = null, Color? after_hit_color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_line_hit_offset, start, end, is_hit, unit_offset_of_hit, hit_size, hit_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, after_hit_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawLine(Vector3 a, Vector3 b, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_line, a, b, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawLines(Vector3[] lines, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_lines, lines, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawRay(Vector3 origin, Vector3 direction, float length, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_ray, origin, direction, length, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawLinePath(Vector3[] path, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_line_path, path, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawArrowhead(Transform3D transform, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_arrowhead, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawArrow(Vector3 a, Vector3 b, Color? color = null, float arrow_size = 0.5f, bool is_absolute_size = false, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_arrow, a, b, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, arrow_size, is_absolute_size, duration); +#endif + } + } + + public static void DrawArrowRay(Vector3 origin, Vector3 direction, float length, Color? color = null, float arrow_size = 0.5f, bool is_absolute_size = false, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_arrow_ray, origin, direction, length, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, arrow_size, is_absolute_size, duration); +#endif + } + } + + public static void DrawArrowPath(Vector3[] path, Color? color = null, float arrow_size = 0.75f, bool is_absolute_size = true, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_arrow_path, path, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, arrow_size, is_absolute_size, duration); +#endif + } + } + + public static void DrawPointPath(Vector3[] path, DebugDraw3D.PointType type = (DebugDraw3D.PointType)0, float size = 0.25f, Color? points_color = null, Color? lines_color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_point_path, path, (long)type, size, points_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, lines_color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawSquare(Vector3 position, float size = 0.20000000298023f, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_square, position, size, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawPlane(Plane plane, Color? color = null, Vector3? anchor_point = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_plane, plane, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, anchor_point ?? _DebugDrawUtils_.DefaultArgumentsData.arg_4, duration); +#endif + } + } + + public static void DrawPoints(Vector3[] points, DebugDraw3D.PointType type = (DebugDraw3D.PointType)0, float size = 0.20000000298023f, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_points, points, (long)type, size, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawCameraFrustum(Camera3D camera, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_camera_frustum, camera, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawCameraFrustumPlanes(Godot.Collections.Array camera_frustum, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_camera_frustum_planes, camera_frustum, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawPosition(Transform3D transform, Color? color = null, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_position, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, duration); +#endif + } + } + + public static void DrawGizmo(Transform3D transform, Color? color = null, bool is_centered = false, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_gizmo, transform, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_centered, duration); +#endif + } + } + + public static void DrawGrid(Vector3 origin, Vector3 x_size, Vector3 y_size, Vector2I subdivision, Color? color = null, bool is_centered = true, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_grid, origin, x_size, y_size, subdivision, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_centered, duration); +#endif + } + } + + public static void DrawGridXf(Transform3D transform, Vector2I subdivision, Color? color = null, bool is_centered = true, float duration = 0f) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__draw_grid_xf, transform, subdivision, color ?? _DebugDrawUtils_.DefaultArgumentsData.arg_2, is_centered, duration); +#endif + } + } + + public static DebugDraw3DStats GetRenderStats() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DStats)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__get_render_stats)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static DebugDraw3DStats GetRenderStatsForWorld(Viewport viewport) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DStats)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__get_render_stats_for_world, viewport)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static DebugDraw3DScopeConfig NewScopedConfig() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__new_scoped_config)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public static DebugDraw3DScopeConfig ScopedConfig() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__scoped_config)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + private static readonly StringName __prop_empty_color = "empty_color"; + private static readonly StringName __prop_debug_enabled = "debug_enabled"; + private static readonly StringName __prop_config = "config"; + + public static Color EmptyColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_empty_color); + set => ClassDB.ClassSetProperty(Instance, __prop_empty_color, value); + } + + public static bool DebugEnabled + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_debug_enabled); + set => ClassDB.ClassSetProperty(Instance, __prop_debug_enabled, value); + } + + public static DebugDraw3DConfig Config + { + get => (DebugDraw3DConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)ClassDB.ClassGetProperty(Instance, __prop_config)); + set => ClassDB.ClassSetProperty(Instance, __prop_config, value.Instance); + } + +} + +internal class DebugDraw3DStats : _DebugDrawInstanceWrapper_ +{ + public DebugDraw3DStats(GodotObject _instance) : base (_instance) {} + + public DebugDraw3DStats() : this((GodotObject)ClassDB.Instantiate("DebugDraw3DStats")) { } + + private static readonly StringName __prop_instances = "instances"; + private static readonly StringName __prop_lines = "lines"; + private static readonly StringName __prop_instances_physics = "instances_physics"; + private static readonly StringName __prop_lines_physics = "lines_physics"; + private static readonly StringName __prop_total_geometry = "total_geometry"; + private static readonly StringName __prop_visible_instances = "visible_instances"; + private static readonly StringName __prop_visible_lines = "visible_lines"; + private static readonly StringName __prop_total_visible = "total_visible"; + private static readonly StringName __prop_time_filling_buffers_instances_usec = "time_filling_buffers_instances_usec"; + private static readonly StringName __prop_time_filling_buffers_lines_usec = "time_filling_buffers_lines_usec"; + private static readonly StringName __prop_time_filling_buffers_instances_physics_usec = "time_filling_buffers_instances_physics_usec"; + private static readonly StringName __prop_time_filling_buffers_lines_physics_usec = "time_filling_buffers_lines_physics_usec"; + private static readonly StringName __prop_total_time_filling_buffers_usec = "total_time_filling_buffers_usec"; + private static readonly StringName __prop_time_culling_instances_usec = "time_culling_instances_usec"; + private static readonly StringName __prop_time_culling_lines_usec = "time_culling_lines_usec"; + private static readonly StringName __prop_total_time_culling_usec = "total_time_culling_usec"; + private static readonly StringName __prop_total_time_spent_usec = "total_time_spent_usec"; + private static readonly StringName __prop_created_scoped_configs = "created_scoped_configs"; + private static readonly StringName __prop_orphan_scoped_configs = "orphan_scoped_configs"; + + public int Instances + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_instances); + set => ClassDB.ClassSetProperty(Instance, __prop_instances, value); + } + + public int Lines + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_lines); + set => ClassDB.ClassSetProperty(Instance, __prop_lines, value); + } + + public int InstancesPhysics + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_instances_physics); + set => ClassDB.ClassSetProperty(Instance, __prop_instances_physics, value); + } + + public int LinesPhysics + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_lines_physics); + set => ClassDB.ClassSetProperty(Instance, __prop_lines_physics, value); + } + + public int TotalGeometry + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_total_geometry); + set => ClassDB.ClassSetProperty(Instance, __prop_total_geometry, value); + } + + public int VisibleInstances + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_visible_instances); + set => ClassDB.ClassSetProperty(Instance, __prop_visible_instances, value); + } + + public int VisibleLines + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_visible_lines); + set => ClassDB.ClassSetProperty(Instance, __prop_visible_lines, value); + } + + public int TotalVisible + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_total_visible); + set => ClassDB.ClassSetProperty(Instance, __prop_total_visible, value); + } + + public int TimeFillingBuffersInstancesUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_filling_buffers_instances_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_filling_buffers_instances_usec, value); + } + + public int TimeFillingBuffersLinesUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_filling_buffers_lines_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_filling_buffers_lines_usec, value); + } + + public int TimeFillingBuffersInstancesPhysicsUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_filling_buffers_instances_physics_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_filling_buffers_instances_physics_usec, value); + } + + public int TimeFillingBuffersLinesPhysicsUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_filling_buffers_lines_physics_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_filling_buffers_lines_physics_usec, value); + } + + public int TotalTimeFillingBuffersUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_total_time_filling_buffers_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_total_time_filling_buffers_usec, value); + } + + public int TimeCullingInstancesUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_culling_instances_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_culling_instances_usec, value); + } + + public int TimeCullingLinesUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_time_culling_lines_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_time_culling_lines_usec, value); + } + + public int TotalTimeCullingUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_total_time_culling_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_total_time_culling_usec, value); + } + + public int TotalTimeSpentUsec + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_total_time_spent_usec); + set => ClassDB.ClassSetProperty(Instance, __prop_total_time_spent_usec, value); + } + + public int CreatedScopedConfigs + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_created_scoped_configs); + set => ClassDB.ClassSetProperty(Instance, __prop_created_scoped_configs, value); + } + + public int OrphanScopedConfigs + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_orphan_scoped_configs); + set => ClassDB.ClassSetProperty(Instance, __prop_orphan_scoped_configs, value); + } + +} + +internal class DebugDraw3DConfig : _DebugDrawInstanceWrapper_ +{ + public DebugDraw3DConfig(GodotObject _instance) : base (_instance) {} + + public DebugDraw3DConfig() : this((GodotObject)ClassDB.Instantiate("DebugDraw3DConfig")) { } + + private static readonly StringName __prop_freeze_3d_render = "freeze_3d_render"; + private static readonly StringName __prop_visible_instance_bounds = "visible_instance_bounds"; + private static readonly StringName __prop_use_frustum_culling = "use_frustum_culling"; + private static readonly StringName __prop_frustum_length_scale = "frustum_length_scale"; + private static readonly StringName __prop_force_use_camera_from_scene = "force_use_camera_from_scene"; + private static readonly StringName __prop_geometry_render_layers = "geometry_render_layers"; + private static readonly StringName __prop_line_hit_color = "line_hit_color"; + private static readonly StringName __prop_line_after_hit_color = "line_after_hit_color"; + + public bool Freeze3dRender + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_freeze_3d_render); + set => ClassDB.ClassSetProperty(Instance, __prop_freeze_3d_render, value); + } + + public bool VisibleInstanceBounds + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_visible_instance_bounds); + set => ClassDB.ClassSetProperty(Instance, __prop_visible_instance_bounds, value); + } + + public bool UseFrustumCulling + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_use_frustum_culling); + set => ClassDB.ClassSetProperty(Instance, __prop_use_frustum_culling, value); + } + + public float FrustumLengthScale + { + get => (float)ClassDB.ClassGetProperty(Instance, __prop_frustum_length_scale); + set => ClassDB.ClassSetProperty(Instance, __prop_frustum_length_scale, value); + } + + public bool ForceUseCameraFromScene + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_force_use_camera_from_scene); + set => ClassDB.ClassSetProperty(Instance, __prop_force_use_camera_from_scene, value); + } + + public int GeometryRenderLayers + { + get => (int)ClassDB.ClassGetProperty(Instance, __prop_geometry_render_layers); + set => ClassDB.ClassSetProperty(Instance, __prop_geometry_render_layers, value); + } + + public Color LineHitColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_line_hit_color); + set => ClassDB.ClassSetProperty(Instance, __prop_line_hit_color, value); + } + + public Color LineAfterHitColor + { + get => (Color)ClassDB.ClassGetProperty(Instance, __prop_line_after_hit_color); + set => ClassDB.ClassSetProperty(Instance, __prop_line_after_hit_color, value); + } + +} + +internal class DebugDraw3DScopeConfig : _DebugDrawInstanceWrapper_, IDisposable +{ + public DebugDraw3DScopeConfig(GodotObject _instance) : base (_instance) {} + + public DebugDraw3DScopeConfig() : this((GodotObject)ClassDB.Instantiate("DebugDraw3DScopeConfig")) { } + + private static readonly StringName __set_thickness = "set_thickness"; + private static readonly StringName __get_thickness = "get_thickness"; + private static readonly StringName __set_center_brightness = "set_center_brightness"; + private static readonly StringName __get_center_brightness = "get_center_brightness"; + private static readonly StringName __set_hd_sphere = "set_hd_sphere"; + private static readonly StringName __is_hd_sphere = "is_hd_sphere"; + private static readonly StringName __set_plane_size = "set_plane_size"; + private static readonly StringName __get_plane_size = "get_plane_size"; + private static readonly StringName __set_viewport = "set_viewport"; + private static readonly StringName __get_viewport = "get_viewport"; + private static readonly StringName __set_no_depth_test = "set_no_depth_test"; + private static readonly StringName __is_no_depth_test = "is_no_depth_test"; + // Additional custom statics + private static readonly StringName ___manual_unregister = "_manual_unregister"; + + // Custom Disposable + public new void Dispose() + { + Instance?.Call(___manual_unregister); + } + + public DebugDraw3DScopeConfig SetThickness(float value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_thickness, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public float GetThickness() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (float)(Instance?.Call(__get_thickness)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public DebugDraw3DScopeConfig SetCenterBrightness(float value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_center_brightness, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public float GetCenterBrightness() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (float)(Instance?.Call(__get_center_brightness)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public DebugDraw3DScopeConfig SetHdSphere(bool value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_hd_sphere, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public bool IsHdSphere() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (bool)(Instance?.Call(__is_hd_sphere)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public DebugDraw3DScopeConfig SetPlaneSize(float value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_plane_size, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public float GetPlaneSize() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (float)(Instance?.Call(__get_plane_size)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public DebugDraw3DScopeConfig SetViewport(Viewport value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_viewport, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public Viewport GetViewport() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (Viewport)(Instance?.Call(__get_viewport)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public DebugDraw3DScopeConfig SetNoDepthTest(bool value) + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (DebugDraw3DScopeConfig)_DebugDrawUtils_.CreateWrapperFromObject((GodotObject)Instance?.Call(__set_no_depth_test, value)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + public bool IsNoDepthTest() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + return (bool)(Instance?.Call(__is_no_depth_test)); +#endif + } +#if !DEBUG && !FORCED_DD3D + else +#endif + { +#if !DEBUG && !FORCED_DD3D + return default; +#endif + } + } + + +} + +static internal class DebugDrawManager +{ + private static GodotObject _instance; + public static GodotObject Instance + { + get + { + if (!GodotObject.IsInstanceValid(_instance)) + { + _instance = Engine.GetSingleton("DebugDrawManager"); + } + return _instance; + } + } + + private static readonly StringName __clear_all = "clear_all"; + + public static void ClearAll() + { +#if !DEBUG && !FORCED_DD3D + if (_DebugDrawUtils_.IsCallEnabled) +#endif + { +#if (!DEBUG || FORCED_DD3D) || (DEBUG && !FORCED_DD3D) + Instance?.Call(__clear_all); +#endif + } + } + + private static readonly StringName __prop_debug_enabled = "debug_enabled"; + + public static bool DebugEnabled + { + get => (bool)ClassDB.ClassGetProperty(Instance, __prop_debug_enabled); + set => ClassDB.ClassSetProperty(Instance, __prop_debug_enabled, value); + } + +} + +internal class _DebugDrawInstanceWrapper_ : IDisposable +{ + public GodotObject Instance { get; protected set; } + + public _DebugDrawInstanceWrapper_(GodotObject _instance) + { + if (_instance == null) throw new ArgumentNullException("_instance"); + if (!ClassDB.IsParentClass(_instance.GetClass(), GetType().Name)) throw new ArgumentException("\"_instance\" has the wrong type."); + Instance = _instance; + + } + + public void Dispose() + { + Instance?.Dispose(); + Instance = null; + } + + public void ClearNativePointer() + { + Instance = null; + } +} + +internal static class _DebugDrawUtils_ +{ + const bool is_debug_enabled = +#if DEBUG + true; +#else + false; +#endif + public static readonly bool IsCallEnabled = is_debug_enabled || OS.HasFeature("forced_dd3d"); + + public static class DefaultArgumentsData + { + public static readonly Color arg_0 = new Color(0.95999997854233f, 0.95999997854233f, 0.95999997854233f, 1f); + public static readonly Variant arg_1 = default; + public static readonly Color arg_2 = new Color(0f, 0f, 0f, 0f); + public static readonly Vector3 arg_3 = new Vector3(0f, 1f, 0f); + public static readonly Vector3 arg_4 = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + } + + static System.Collections.Generic.Dictionary cached_instances = new(); + static DateTime previous_clear_time = DateTime.Now; + + public static object CreateWrapperFromObject(GodotObject _instance) + { + if (_instance == null) + { + return null; + } + + ulong id = _instance.GetInstanceId(); + if (cached_instances.ContainsKey(id)) + { + return cached_instances[id]; + } + + if ((DateTime.Now - previous_clear_time).TotalSeconds > 1) + { + var query = cached_instances.Where((i) => GodotObject.IsInstanceIdValid(i.Key)).ToArray(); + foreach (var i in query) + { + i.Value.ClearNativePointer(); + cached_instances.Remove(i.Key); + } + previous_clear_time = DateTime.Now; + } + + switch(_instance.GetClass()) + { + case "DebugDraw2DStats": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw2DStats(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw2DConfig": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw2DConfig(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw2DGraph": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw2DGraph(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw2DFPSGraph": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw2DFPSGraph(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw3DStats": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw3DStats(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw3DConfig": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw3DConfig(_instance); + cached_instances[id] = new_instance; + return new_instance; + } + case "DebugDraw3DScopeConfig": + { + _DebugDrawInstanceWrapper_ new_instance = new DebugDraw3DScopeConfig(_instance); + return new_instance; + } + } + throw new NotImplementedException(); + } +} diff --git a/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/log.txt b/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/log.txt new file mode 100644 index 000000000..4202e5911 --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/gen/csharp/log.txt @@ -0,0 +1,238 @@ +Generation of bindings started, output file: res://addons/debug_draw_3d/gen/csharp/DebugDrawGeneratedAPI.generated.cs +Log file: res://addons/debug_draw_3d/gen/csharp/log.txt +Hold Shift to print information on the Output panel when manually starting generation via the 'Project - Tools - Debug Draw' menu + Class: DebugDraw2D + Wrapper... + Constants... + Enums... + Methods... + clear_all + begin_text_group + end_text_group + set_text + clear_texts + create_graph + create_fps_graph + graph_update_data + remove_graph + clear_graphs + get_graph + get_graph_names + get_render_stats + Properties... + empty_color + debug_enabled + config + custom_canvas + Class: DebugDraw2DStats + Wrapper... + Constants... + Enums... + Methods... + Properties... + overlay_text_groups + overlay_text_lines + overlay_graphs_enabled + overlay_graphs_total + Class: DebugDraw2DConfig + Wrapper... + Constants... + Enums... + BlockPosition + Methods... + Properties... + graphs_base_offset + text_block_position + text_block_offset + text_padding + text_default_duration + text_default_size + text_foreground_color + text_background_color + text_custom_font + Class: DebugDraw2DGraph + Wrapper... + Constants... + Enums... + GraphPosition + GraphSide + TextFlags + Methods... + get_title + set_parent + Properties... + enabled + upside_down + show_title + show_text_flags + size + buffer_size + offset + corner + line_width + line_color + background_color + border_color + text_suffix + custom_font + title_size + text_size + title_color + text_color + text_precision + parent_graph + parent_graph_side + data_getter + Class: DebugDraw2DFPSGraph + Wrapper... + Constants... + Enums... + Methods... + Properties... + frame_time_mode + Class: DebugDraw3D + Wrapper... + Constants... + Enums... + PointType + Methods... + regenerate_geometry_meshes + clear_all + draw_sphere + color will be remapped to arg_2 + draw_sphere_xf + color will be remapped to arg_2 + draw_cylinder + color will be remapped to arg_2 + draw_cylinder_ab + color will be remapped to arg_2 + draw_box + color will be remapped to arg_2 + draw_box_ab + color will be remapped to arg_2 + draw_box_xf + color will be remapped to arg_2 + draw_aabb + color will be remapped to arg_2 + draw_aabb_ab + color will be remapped to arg_2 + draw_line_hit + hit_color will be remapped to arg_2 + after_hit_color will be remapped to arg_2 + draw_line_hit_offset + hit_color will be remapped to arg_2 + after_hit_color will be remapped to arg_2 + draw_line + color will be remapped to arg_2 + draw_lines + color will be remapped to arg_2 + draw_ray + color will be remapped to arg_2 + draw_line_path + color will be remapped to arg_2 + draw_arrowhead + color will be remapped to arg_2 + draw_arrow + color will be remapped to arg_2 + draw_arrow_ray + color will be remapped to arg_2 + draw_arrow_path + color will be remapped to arg_2 + draw_point_path + points_color will be remapped to arg_2 + lines_color will be remapped to arg_2 + draw_square + color will be remapped to arg_2 + draw_plane + color will be remapped to arg_2 + draw_points + color will be remapped to arg_2 + draw_camera_frustum + color will be remapped to arg_2 + draw_camera_frustum_planes + color will be remapped to arg_2 + draw_position + color will be remapped to arg_2 + draw_gizmo + color will be remapped to arg_2 + draw_grid + color will be remapped to arg_2 + draw_grid_xf + color will be remapped to arg_2 + get_render_stats + get_render_stats_for_world + new_scoped_config + scoped_config + Properties... + empty_color + debug_enabled + config + Class: DebugDraw3DStats + Wrapper... + Constants... + Enums... + Methods... + Properties... + instances + lines + instances_physics + lines_physics + total_geometry + visible_instances + visible_lines + total_visible + time_filling_buffers_instances_usec + time_filling_buffers_lines_usec + time_filling_buffers_instances_physics_usec + time_filling_buffers_lines_physics_usec + total_time_filling_buffers_usec + time_culling_instances_usec + time_culling_lines_usec + total_time_culling_usec + total_time_spent_usec + created_scoped_configs + orphan_scoped_configs + Class: DebugDraw3DConfig + Wrapper... + Constants... + Enums... + Methods... + Properties... + freeze_3d_render + visible_instance_bounds + use_frustum_culling + frustum_length_scale + force_use_camera_from_scene + geometry_render_layers + line_hit_color + line_after_hit_color + Class: DebugDraw3DScopeConfig + Wrapper... + Constants... + Enums... + Methods... + set_thickness + get_thickness + set_center_brightness + get_center_brightness + set_hd_sphere + is_hd_sphere + set_plane_size + get_plane_size + set_viewport + get_viewport + set_no_depth_test + is_no_depth_test + Properties... + Class: DebugDrawManager + Wrapper... + Constants... + Enums... + Methods... + clear_all + Properties... + debug_enabled + DebugDraw utilities: + Arguments remap... + Class factory... +The generation process is completed! diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/.gdignore b/OpenKh.Godot/addons/debug_draw_3d/libs/.gdignore new file mode 100644 index 000000000..e69de29bb diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so new file mode 100644 index 000000000..42efd8b5f Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm32.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so new file mode 100644 index 000000000..a8ccc9812 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.arm64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so new file mode 100644 index 000000000..79f7bb09e Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_32.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so new file mode 100644 index 000000000..c32348a88 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_debug.x86_64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so new file mode 100644 index 000000000..e55cb86f5 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm32.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so new file mode 100644 index 000000000..709626f6a Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.arm64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so new file mode 100644 index 000000000..7e2b8752f Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_32.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so new file mode 100644 index 000000000..411481fa5 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.android.template_release.x86_64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib new file mode 100644 index 000000000..546917bdb Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_debug.universal.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib new file mode 100644 index 000000000..4189257c3 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib new file mode 100644 index 000000000..6551b1d1c Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.ios.template_release.universal.enabled.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so new file mode 100644 index 000000000..e945e6881 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.editor.x86_64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so new file mode 100644 index 000000000..0728b2e6c Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.enabled.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so new file mode 100644 index 000000000..7af044706 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.linux.template_release.x86_64.so differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist new file mode 100644 index 000000000..91b3a638d --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/Resources/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + libdd3d.macos.editor.universal.dylib + CFBundleName + Debug Draw 3D + CFBundleDisplayName + Debug Draw 3D + CFBundleIdentifier + ru.dmitriysalnikov.dd3d + NSHumanReadableCopyright + Copyright (c) Dmitriy Salnikov. + CFBundleVersion + 1.4.4 + CFBundleShortVersionString + 1.4.4 + CFBundlePackageType + FMWK + CSResourcesFileMapped + + DTPlatformName + macosx + LSMinimumSystemVersion + 10.14 + + + \ No newline at end of file diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib new file mode 100644 index 000000000..9a6969466 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.editor.universal.framework/libdd3d.macos.editor.universal.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist new file mode 100644 index 000000000..d53370c5e --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/Resources/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + libdd3d.macos.template_release.universal.enabled.dylib + CFBundleName + Debug Draw 3D + CFBundleDisplayName + Debug Draw 3D + CFBundleIdentifier + ru.dmitriysalnikov.dd3d + NSHumanReadableCopyright + Copyright (c) Dmitriy Salnikov. + CFBundleVersion + 1.4.4 + CFBundleShortVersionString + 1.4.4 + CFBundlePackageType + FMWK + CSResourcesFileMapped + + DTPlatformName + macosx + LSMinimumSystemVersion + 10.14 + + + \ No newline at end of file diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib new file mode 100644 index 000000000..c73e88883 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.enabled.framework/libdd3d.macos.template_release.universal.enabled.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist new file mode 100644 index 000000000..a4734e129 --- /dev/null +++ b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/Resources/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + libdd3d.macos.template_release.universal.dylib + CFBundleName + Debug Draw 3D + CFBundleDisplayName + Debug Draw 3D + CFBundleIdentifier + ru.dmitriysalnikov.dd3d + NSHumanReadableCopyright + Copyright (c) Dmitriy Salnikov. + CFBundleVersion + 1.4.4 + CFBundleShortVersionString + 1.4.4 + CFBundlePackageType + FMWK + CSResourcesFileMapped + + DTPlatformName + macosx + LSMinimumSystemVersion + 10.14 + + + \ No newline at end of file diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib new file mode 100644 index 000000000..959d06ca0 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.macos.template_release.universal.framework/libdd3d.macos.template_release.universal.dylib differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm new file mode 100644 index 000000000..5aaf34daa Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.threads.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm new file mode 100644 index 000000000..80df88095 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_debug.wasm32.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm new file mode 100644 index 000000000..cf1aa9251 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.enabled.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm new file mode 100644 index 000000000..f71dfb129 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.enabled.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm new file mode 100644 index 000000000..2026b9821 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.threads.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm new file mode 100644 index 000000000..910fd0342 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.web.template_release.wasm32.wasm differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll new file mode 100644 index 000000000..d9b2e8e07 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.editor.x86_64.dll differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll new file mode 100644 index 000000000..94f8246b8 Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.dll differ diff --git a/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll new file mode 100644 index 000000000..86ce9f5ac Binary files /dev/null and b/OpenKh.Godot/addons/debug_draw_3d/libs/libdd3d.windows.template_release.x86_64.enabled.dll differ diff --git a/OpenKh.Godot/default_bus_layout.tres b/OpenKh.Godot/default_bus_layout.tres new file mode 100644 index 000000000..2d6f3b685 --- /dev/null +++ b/OpenKh.Godot/default_bus_layout.tres @@ -0,0 +1,3 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://bxokgdhr64evp"] + +[resource] diff --git a/OpenKh.Godot/icon.svg b/OpenKh.Godot/icon.svg new file mode 100644 index 000000000..9d8b7fa14 --- /dev/null +++ b/OpenKh.Godot/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/OpenKh.Godot/icon.svg.import b/OpenKh.Godot/icon.svg.import new file mode 100644 index 000000000..b3f680336 --- /dev/null +++ b/OpenKh.Godot/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cp475vvb1omec" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/OpenKh.Godot/project.godot b/OpenKh.Godot/project.godot new file mode 100644 index 000000000..51176d172 --- /dev/null +++ b/OpenKh.Godot/project.godot @@ -0,0 +1,37 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="OpenKh.Godot" +run/main_scene="res://Scenes/Root.tscn" +config/features=PackedStringArray("4.3", "C#", "Forward Plus") +run/max_fps=60 +config/icon="res://icon.svg" + +[debug_draw_3d] + +settings/3d/volumetric_defaults/thickness=0.0 + +[dotnet] + +project/assembly_name="OpenKh.Godot" + +[editor] + +import/use_multiple_threads=false + +[editor_plugins] + +enabled=PackedStringArray() + +[gui] + +theme/default_font_multichannel_signed_distance_field=true diff --git a/OpenKh.Godot/readme.md b/OpenKh.Godot/readme.md new file mode 100644 index 000000000..219ba2055 --- /dev/null +++ b/OpenKh.Godot/readme.md @@ -0,0 +1,3 @@ +# OpenKh.Godot + +Uses Godot 4.3 diff --git a/OpenKh.Kh1/Cvbl.cs b/OpenKh.Kh1/Cvbl.cs new file mode 100644 index 000000000..35f42d7d5 --- /dev/null +++ b/OpenKh.Kh1/Cvbl.cs @@ -0,0 +1,371 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using OpenKh.Common; +using Xe.BinaryMapper; + +namespace OpenKh.Kh1 +{ + public class Cvbl + { + public class CvblHeader + { + [Data] public uint Unk1 { get; set; } + [Data] public uint NumMeshes { get; set; } + [Data] public ushort NumUnknownEntries { get; set; } + [Data] public ushort HasUnknownEntries { get; set; } + [Data] public uint Unk2 { get; set; } + } + public class MeshEntry + { + [Data] public ushort Unk1 { get; set; } + [Data] public ushort JointStyle { get; set; } + [Data] public int Material { get; set; } + [Data] public int Unk2 { get; set; } + [Data] public uint MeshOffset { get; set; } + } + + public class VertexStyle8 : IVertex + { + [Data] public float NormalX { get; set; } + [Data] public float NormalY { get; set; } + [Data] public float NormalZ { get; set; } + [Data] public ushort JointSlotId { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + [Data] public float U { get; set; } + [Data] public float V { get; set; } + + [Data] public float Unk1 { get; set; } // is this the weird Y UV coordinate that mdls has? + [Data] public uint Unk2 { get; set; } + + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public float Weight { get; set; } + + + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + public Vector2 UV => new(U, V); + public ushort[] Joints + { + get => [JointSlotId]; + set + { + if (value is null || value.Length == 0) return; + JointSlotId = value.First(); + } + } + public float[] Weights => [Weight]; + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 48; + } + + public class VertexStyle9 : IVertex + { + [Data] public float NormalX { get; set; } + [Data] public float NormalY { get; set; } + [Data] public float NormalZ { get; set; } + [Data] public ushort JointCount { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + [Data] public float U { get; set; } + [Data] public float V { get; set; } + [Data(Count = 8)] public byte[] JointSlotIds { get; set; } + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public float PositionW { get; set; } // ??? maybe unk? + [Data(Count = 8)] public float[] JointWeights { get; set; } + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + public Vector2 UV => new(U, V); + public ushort[] Joints + { + get => JointSlotIds.Select(i => (ushort)i).Take(JointCount).ToArray(); + set + { + if (value is null || value.Length != JointSlotIds.Length) return; + JointSlotIds = value.Select(i => (byte)i).ToArray(); + } + } + public float[] Weights => JointWeights.Take(JointCount).ToArray(); + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 80; + } + + public class VertexStyle10 : IVertex + { + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public ushort JointSlotId { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + + public ushort[] Joints + { + get => [JointSlotId]; + set + { + if (value is null || value.Length == 0) return; + JointSlotId = value.First(); + } + } + public float[] Weights => [1]; + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 16; + } + + public interface IVertex + { + public Vector3 Position { get; set; } + public Vector3 Normal => Vector3.Zero; + public Vector2 UV => Vector2.Zero; + public ushort[] Joints { get; set; } + public float[] Weights => Array.Empty(); + public int Face => 0; + public int Size => 0; + } + + public class Submesh + { + public int Material; + public ushort JointStyle; + + public List Vertices = new(); + public List Faces = new(); + public Dictionary JointSlots = new(); + + public Dictionary> ExtraJointData = new(); + } + + public CvblHeader Header; + public List Submeshes = new(); + public List MeshEntries = new(); + + private static Dictionary MdlsJointsToDictionary(List joints) + { + if (joints is null) return null; + var dict = new Dictionary(); + for (var i = 0u; i < joints.Count; i++) + { + var j = joints[(int)i]; + + var scaleMatrix = Matrix4x4.CreateScale(new Vector3(j.ScaleX, j.ScaleY, j.ScaleZ)); + + var rotationMatrixX = Matrix4x4.CreateRotationX(j.RotateX); + var rotationMatrixY = Matrix4x4.CreateRotationY(j.RotateY); + var rotationMatrixZ = Matrix4x4.CreateRotationZ(j.RotateZ); + var rotationMatrix = rotationMatrixX * rotationMatrixY * rotationMatrixZ; + + var translationMatrix = Matrix4x4.CreateTranslation(new Vector3(j.TranslateX, j.TranslateY, j.TranslateZ)); + + dict[i] = scaleMatrix * rotationMatrix * translationMatrix; + } + return dict; + } + + public Cvbl(Stream stream, List mdlsJoints) + { + var file = stream.ReadAllBytes(); + var str = new MemoryStream(file); + var joints = MdlsJointsToDictionary(mdlsJoints); + + Header = BinaryMapping.ReadObject(str); + + str.Position = 0; + + if (Header.HasUnknownEntries is not (0 or 1)) return; + var meshEntries = new List(); + + var meshEntriesOffset = 16 + (Header.HasUnknownEntries == 1 ? Header.NumUnknownEntries * 32 : 0); + + str.Seek(meshEntriesOffset, SeekOrigin.Begin); + + for (var i = 0; i < Header.NumMeshes; i++) meshEntries.Add(BinaryMapping.ReadObject(str)); + + MeshEntries = meshEntries; + + foreach (var meshEntry in meshEntries) + { + var mesh = new Submesh + { + Material = meshEntry.Material, + JointStyle = meshEntry.JointStyle, + }; + var jointStyle = meshEntry.JointStyle; + + var jointSlots = new Dictionary(); + for (var i = 0u; i < 48; i++) + { + jointSlots[i] = (0, Matrix4x4.Identity); + } + + var extraJointData = new Dictionary>(); + + + var totalVertCount = 0u; + var run = true; + var subsectionOffset = meshEntry.MeshOffset + 16; + while (run && subsectionOffset < str.Length) + { + var subsectionDataOffset = subsectionOffset + 8; + + str.Seek(subsectionOffset, SeekOrigin.Begin); + + var subsectionType = str.ReadUInt32(); + var subsectionLength = str.ReadUInt32(); + + if (subsectionType == 1) + { + var numVerts = str.ReadUInt32(); + + var submeshDataOffset = subsectionDataOffset + 24; + + str.Seek(submeshDataOffset, SeekOrigin.Begin); + + for (var i = 0u; i < numVerts; i++) + { + IVertex vert = meshEntry.JointStyle switch + { + 8 => BinaryMapping.ReadObject(str), + 9 => BinaryMapping.ReadObject(str), + 10 => BinaryMapping.ReadObject(str), + _ => null, + }; + + if (vert is null) continue; + + var bones = vert.Joints.Select(j => (ushort)jointSlots[j].Item1).ToArray(); + vert.Joints = bones; + + if (joints != null) + vert.Position = RelativeToGlobalVertex(vert.Position, joints, vert.Joints.Select(j => (uint)j).ToArray(), vert.Weights, + vert.Joints.Select(j => jointSlots[j].Item2).ToArray()); + + + str.Seek(submeshDataOffset + ((i + 1) * vert.Size), SeekOrigin.Begin); + + switch (vert.Face) + { + case 1: + mesh.Faces.Add([totalVertCount + i - 2, totalVertCount + i - 1, totalVertCount + i]); + break; + case 2: + mesh.Faces.Add([totalVertCount + i, totalVertCount + i - 1, totalVertCount + i - 2]); + break; + } + mesh.Vertices.Add(vert); + } + totalVertCount += numVerts; + } + else if (subsectionType == 17) + { + var jointId = str.ReadUInt32(); + var jointSlotId = str.ReadUInt32(); + + jointSlots[jointSlotId] = (jointId, jointSlots[jointSlotId].Item2); + + switch (jointStyle) + { + case 8: + { + var data = new List(); + for (var i = 0; i < 28; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + + break; + } + case 9: + { + var data = new List(); + for (var i = 0; i < 28; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + + jointSlots[jointSlotId] = (jointId, new Matrix4x4( + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle() + )); + break; + } + case 10: + { + var data = new List(); + for (var i = 0; i < 16; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + break; + } + default: + Console.WriteLine($"Warning: Unknown joint style: {jointStyle} @ {subsectionDataOffset}"); + return; + } + } + else if (subsectionType == 32768) run = false; + else break; + subsectionOffset += subsectionLength; + } + + mesh.JointSlots = jointSlots.ToDictionary(i => i.Key, i => i.Value.Item1); + mesh.ExtraJointData = extraJointData; + + Submeshes.Add(mesh); + } + } + + private static Vector3 RelativeToGlobalVertex(Vector3 vertex, Dictionary joints, IReadOnlyList jointIds, IReadOnlyList weights, IReadOnlyList toLocalTransforms) + { + var vert = new Vector4(0, 0, 0, 0); + for (var i = 0; i < jointIds.Count; i++) + { + var jointId = jointIds[i]; + var weight = weights[i]; + var tlt = i < toLocalTransforms.Count ? toLocalTransforms[i] : Matrix4x4.Identity; + var npVert = new Vector4(vertex, 1) * weight; + var globalMat = joints[jointId] * tlt; + npVert = Vector4.Transform(npVert, globalMat); + vert += npVert; + } + return new Vector3(vert.X, vert.Y, vert.Z); + } + } +} diff --git a/OpenKh.Kh1/Idx1.cs b/OpenKh.Kh1/Idx1.cs index c90cc2806..69e64cd68 100644 --- a/OpenKh.Kh1/Idx1.cs +++ b/OpenKh.Kh1/Idx1.cs @@ -12,7 +12,7 @@ public record Idx1 { public const int EntryLength = 0x10; public const int MaxItemCount = 0xE00; - public const int kingdomimg = 0x4d7000 / 0x800; + public const int KingdomImg = 0x4d7000 / 0x800; [Data] public uint Hash { get; set; } [Data] public uint CompressionFlag { get; set; } @@ -41,15 +41,7 @@ public static void Write(Stream stream, ICollection entries) stream.SetLength(stream.Position); } - public static uint GetHash(string text) => - GetHash(Encoding.UTF8.GetBytes(text)); - public static uint GetHash(byte[] data) - { - var hash = 0U; - foreach (var ch in data) - hash = (2 * hash) ^ (uint)((ch << 16) % 69665); - - return hash; - } + public static uint GetHash(string text) => GetHash(Encoding.UTF8.GetBytes(text)); + public static uint GetHash(byte[] data) => data.Aggregate(0U, (current, ch) => (2 * current) ^ (uint)((ch << 16) % 69665)); } } diff --git a/OpenKh.Kh1/Idx1Name.cs b/OpenKh.Kh1/Idx1Name.cs index c5679153b..a45f3dd6f 100644 --- a/OpenKh.Kh1/Idx1Name.cs +++ b/OpenKh.Kh1/Idx1Name.cs @@ -7,8 +7,8 @@ namespace OpenKh.Kh1 { public record Idx1Name { - public static string[] Names = File.ReadAllLines(Path.Combine(AppContext.BaseDirectory, "resources/kh1idx.txt")); - private static Dictionary _nameDictionary = Names.ToDictionary(name => Idx1.GetHash(name), name => name); + public static readonly string[] Names = File.ReadAllLines(Path.Combine(AppContext.BaseDirectory, "resources/kh1idx.txt")); + private static readonly Dictionary NameDictionary = Names.ToDictionary(Idx1.GetHash, name => name); public Idx1 Entry { get; set; } public string Name { get; set; } @@ -20,7 +20,6 @@ public static IEnumerable Lookup(IEnumerable entries) => entries Name = Lookup(entry) }); - public static string Lookup(Idx1 entry) => - _nameDictionary.TryGetValue(entry.Hash, out var name) ? name : null; + public static string Lookup(Idx1 entry) => NameDictionary.GetValueOrDefault(entry.Hash); } } diff --git a/OpenKh.Kh1/Img1.cs b/OpenKh.Kh1/Img1.cs index 5ea857083..91db9bdf5 100644 --- a/OpenKh.Kh1/Img1.cs +++ b/OpenKh.Kh1/Img1.cs @@ -40,11 +40,10 @@ public bool TryFileOpen(string fileName, out Stream stream) public bool FileOpen(string fileName, Action callback) { - bool result; - if (result = Entries.TryGetValue(fileName, out var entry)) - callback(FileOpen(entry)); - - return result; + if (!Entries.TryGetValue(fileName, out var entry)) return false; + + callback(FileOpen(entry)); + return true; } public Stream FileOpen(string fileName) @@ -66,10 +65,7 @@ public Stream FileOpen(Idx1 entry) return new SubStream(_stream, (_firstBlock + entry.IsoBlock) * IsoBlockAlign, entry.Length); } - public static byte[] Decompress(Stream src) - { - return Decompress(new BinaryReader(src).ReadBytes((int)src.Length)); - } + public static byte[] Decompress(Stream src) => Decompress(new BinaryReader(src).ReadBytes((int)src.Length)); public static byte[] Decompress(byte[] srcData) { @@ -83,7 +79,7 @@ public static byte[] Decompress(byte[] srcData) (srcData[srcIndex--] << 8) | (srcData[srcIndex--] << 16); - int dstIndex = decSize - 1; + var dstIndex = decSize - 1; var dstData = new byte[decSize]; while (dstIndex >= 0 && srcIndex >= 0) { @@ -94,7 +90,7 @@ public static byte[] Decompress(byte[] srcData) if (copyIndex > 0 && srcIndex >= 0) { var copyLength = srcData[srcIndex--]; - for (int i = 0; i < copyLength + 3 && dstIndex >= 0; i++) + for (var i = 0; i < copyLength + 3 && dstIndex >= 0; i++) { if (dstIndex + copyIndex + 1 < dstData.Length) dstData[dstIndex--] = dstData[dstIndex + copyIndex + 1]; @@ -103,15 +99,11 @@ public static byte[] Decompress(byte[] srcData) } } else - { dstData[dstIndex--] = data; - } } else - { dstData[dstIndex--] = data; - } } return dstData; @@ -124,14 +116,15 @@ public static byte[] Compress(byte[] srcData) var decompressedLength = srcData.Length; var key = GetLeastUsedByte(srcData); - var buffer = new List(decompressedLength); - - buffer.Add(key); - buffer.Add((byte)(decompressedLength >> 0)); - buffer.Add((byte)(decompressedLength >> 8)); - buffer.Add((byte)(decompressedLength >> 16)); + var buffer = new List(decompressedLength) + { + key, + (byte)(decompressedLength >> 0), + (byte)(decompressedLength >> 8), + (byte)(decompressedLength >> 16), + }; - int sourceIndex = decompressedLength - 1; + var sourceIndex = decompressedLength - 1; while (sourceIndex >= 0) { @@ -183,10 +176,7 @@ public static byte[] Compress(byte[] srcData) var compressedLength = buffer.Count; var cmp = new byte[compressedLength]; - for (var i = 0; i < compressedLength; i++) - { - cmp[i] = buffer[compressedLength - i - 1]; - } + for (var i = 0; i < compressedLength; i++) cmp[i] = buffer[compressedLength - i - 1]; return cmp; } diff --git a/OpenKh.Kh1/KingdomArchive.cs b/OpenKh.Kh1/KingdomArchive.cs index add3a672f..bb8788a29 100644 --- a/OpenKh.Kh1/KingdomArchive.cs +++ b/OpenKh.Kh1/KingdomArchive.cs @@ -53,7 +53,7 @@ public static void Write(Stream stream, ICollection entries) stream.Write(entries.Count); var accumulator = (entries.Count + 2) * 4; - foreach (var entry in entries) + foreach (var entry in entries) //unused? { accumulator = Helpers.Align(accumulator, Alignment); stream.Write(accumulator); diff --git a/OpenKh.Kh1/Mdls.cs b/OpenKh.Kh1/Mdls.cs index cd287a37f..01c8187df 100644 --- a/OpenKh.Kh1/Mdls.cs +++ b/OpenKh.Kh1/Mdls.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; +using IronSoftware.Drawing; using System.IO; using Xe.BinaryMapper; @@ -25,8 +24,8 @@ public class Mdls public Mdls(string filepath) { - int imageCount = 0; - using (FileStream stream = new FileStream(filepath, FileMode.Open)) + var imageCount = 0; + using (var stream = new FileStream(filepath, FileMode.Open)) { //uint tess = testing(0x8B8B8B8A); @@ -37,7 +36,7 @@ public Mdls(string filepath) throw new Exception("Invalid file!"); } - int dataSize = stream.ReadInt32(); // Unused for now + var dataSize = stream.ReadInt32(); // Unused for now // Header Header = BinaryMapping.ReadObject(stream); @@ -46,9 +45,9 @@ public Mdls(string filepath) // Mesh headers Meshes = new List(); - for (int i = 0; i < ModelHeader.MeshCount; i++) + for (var i = 0; i < ModelHeader.MeshCount; i++) { - MdlsMesh mesh = new MdlsMesh(); + var mesh = new MdlsMesh(); mesh.Header = BinaryMapping.ReadObject(stream); Meshes.Add(mesh); } @@ -58,21 +57,21 @@ public Mdls(string filepath) // Joints Joints = new List(); stream.Position = INITIAL_ADDRESS + Header.ModelOffset + ModelHeader.JointInfoOffset; - for (int i = 0; i < ModelHeader.JointCount; i++) + for (var i = 0; i < ModelHeader.JointCount; i++) { - MdlsJoint joint = BinaryMapping.ReadObject(stream); + var joint = BinaryMapping.ReadObject(stream); Joints.Add(joint); } // Mesh data - for (int i = 0; i < ModelHeader.MeshCount; i++) + for (var i = 0; i < ModelHeader.MeshCount; i++) { - int meshDataOffset = INITIAL_ADDRESS + Header.ModelOffset + Meshes[i].Header.MeshPacketOffset; + var meshDataOffset = INITIAL_ADDRESS + Header.ModelOffset + Meshes[i].Header.MeshPacketOffset; stream.Position = meshDataOffset; - int nextOffset = INITIAL_ADDRESS + Header.UnkOffset; + var nextOffset = INITIAL_ADDRESS + Header.UnkOffset; if(i + 1 < ModelHeader.MeshCount) nextOffset = INITIAL_ADDRESS + Header.ModelOffset + Meshes[i+1].Header.MeshPacketOffset; - int packetSize = nextOffset - meshDataOffset; + var packetSize = nextOffset - meshDataOffset; Meshes[i].packet = new MdlsMeshPacket(); Meshes[i].packet.RawData = stream.ReadBytes(packetSize); @@ -83,32 +82,32 @@ public Mdls(string filepath) Images = new List(); imageCount = Header.TextureInfoSize / 0x10; stream.Position = INITIAL_ADDRESS + Header.TextureInfoOffset; - for (int i = 0; i < imageCount; i++) + for (var i = 0; i < imageCount; i++) { - MdlsImage image = new MdlsImage(); + var image = new MdlsImage(); image.Info = BinaryMapping.ReadObject(stream); Images.Add(image); } stream.Position = INITIAL_ADDRESS + Header.TextureDataOffset; - for (int i = 0; i < imageCount; i++) + for (var i = 0; i < imageCount; i++) { - int imageDataLength = Images[i].Info.Width * Images[i].Info.Height; + var imageDataLength = Images[i].Info.Width * Images[i].Info.Height; Images[i].Data = stream.ReadBytes(imageDataLength); } - for (int i = 0; i < imageCount; i++) + for (var i = 0; i < imageCount; i++) { Images[i].Clut = stream.ReadBytes(CLUT_SIZE); } } - string directory = Path.GetDirectoryName(filepath); - for (int i = 0; i < imageCount; i++) + var directory = Path.GetDirectoryName(filepath); + for (var i = 0; i < imageCount; i++) { //Images[i].printClut(); Images[i].loadBitmap(); - string outPath = Path.Combine(directory, "TEXTURE_"+i+".png"); + var outPath = Path.Combine(directory, "TEXTURE_"+i+".png"); //string outPath = Path.Combine(directory, "TEXTURE_"+i+".bmp"); //Images[i].bitmap.Save(outPath, ImageFormat.Png); //MdlsImage.CreateAndSavePng(Images[i].Data, Images[i].Clut, Images[i].Width, Images[i].Height, outPath); @@ -165,12 +164,12 @@ public uint ParentId { get { uint mask = 0x000003FF; // 0b00000000000000000000001111111111; - uint ret = HierarchyBitfield & mask; + var ret = HierarchyBitfield & mask; return ret; } set { - uint mask = 0xFFFFFC00; // 0b11111111111111111111110000000000; - uint wihtoutValue = HierarchyBitfield & mask; + var mask = 0xFFFFFC00; // 0b11111111111111111111110000000000; + var wihtoutValue = HierarchyBitfield & mask; HierarchyBitfield = wihtoutValue + value; } } @@ -240,20 +239,20 @@ public void UnpackData() TriangleStrips = new List>(); Vertices = new List(); Faces = new List(); - using (MemoryStream stream = new MemoryStream(RawData)) + using (var stream = new MemoryStream(RawData)) { - int[] weightMatrix = new int[16]; + var weightMatrix = new int[16]; while (stream.Position < RawData.Length) { - MdlsMeshSubPacketHeader header = BinaryMapping.ReadObject(stream); + var header = BinaryMapping.ReadObject(stream); while(stream.PeekInt32() != 0x00008000) { - int subPacketType = stream.PeekInt32(); + var subPacketType = stream.PeekInt32(); // Weight matrix definition if (subPacketType == 0) { - MdlsPacketMatrixPointer matrixPointer = BinaryMapping.ReadObject(stream); + var matrixPointer = BinaryMapping.ReadObject(stream); if (matrixPointer.TableIndex1 == 0) { weightMatrix[matrixPointer.TableIndex0] = matrixPointer.JointId; @@ -267,12 +266,12 @@ public void UnpackData() // Triangle strip else if (subPacketType == 1) { - MdlsPacketVertices vertices = BinaryMapping.ReadObject(stream); + var vertices = BinaryMapping.ReadObject(stream); StripHeaders.Add(vertices); - List stripVertices = new List(); - for (int i = 0; i < vertices.VertexCount; i++) + var stripVertices = new List(); + for (var i = 0; i < vertices.VertexCount; i++) { - MdlsVertex vertex = BinaryMapping.ReadObject(stream); + var vertex = BinaryMapping.ReadObject(stream); vertex.Index = Vertices.Count; vertex.JointId = weightMatrix[vertex.MatrixId]; Vertices.Add(vertex); @@ -399,7 +398,7 @@ public class MdlsImage public byte[] Clut { get; set; } public int Width { get { return Info.Width ; } } public int Height { get { return Info.Height; } } - public Bitmap bitmap { get; set; } + public AnyBitmap bitmap { get; set; } public void loadBitmap() { @@ -413,13 +412,13 @@ public void loadBitmap() throw new ArgumentException("Image data length or CLUT length is invalid"); } - bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); + bitmap = new AnyBitmap(Width, Height/*, PixelFormat.Format32bppArgb*/); - for (int y = 0; y < Height; y++) + for (var y = 0; y < Height; y++) { - for (int x = 0; x < Width; x++) + for (var x = 0; x < Width; x++) { - byte pixelValue = Data[y * Width + x]; + var pixelValue = Data[y * Width + x]; pixelValue = IndexFix(pixelValue); @@ -428,7 +427,7 @@ public void loadBitmap() Debug.Write("[" + x + "|" + y + "] ["+ pixelValue + "] "); } - Color pixelColor = GetColorFromCLUT(Clut, pixelValue); + var pixelColor = GetColorFromCLUT(Clut, pixelValue); bitmap.SetPixel(x, y, pixelColor); } @@ -447,15 +446,15 @@ public void loadBitmap2() throw new ArgumentException("Image data length or CLUT length is invalid"); } - bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); + bitmap = new AnyBitmap(Width, Height/*, PixelFormat.Format32bppArgb*/); - using (MemoryStream stream = new MemoryStream(Data)) + using (var stream = new MemoryStream(Data)) { - for (int y = 0; y < Height; y++) + for (var y = 0; y < Height; y++) { - for (int x = 0; x < Width; x += 4) + for (var x = 0; x < Width; x += 4) { - int pixelIndex = stream.ReadInt32(); + var pixelIndex = stream.ReadInt32(); if ((pixelIndex & 31) >= 8) { @@ -471,11 +470,11 @@ public void loadBitmap2() pixelIndex <<= 2; - byte[] pixelGroup = BitConverter.GetBytes(pixelIndex); - Color pixel1 = GetColorFromCLUT(Clut, pixelGroup[0]); - Color pixel2 = GetColorFromCLUT(Clut, pixelGroup[1]); - Color pixel3 = GetColorFromCLUT(Clut, pixelGroup[2]); - Color pixel4 = GetColorFromCLUT(Clut, pixelGroup[3]); + var pixelGroup = BitConverter.GetBytes(pixelIndex); + var pixel1 = GetColorFromCLUT(Clut, pixelGroup[0]); + var pixel2 = GetColorFromCLUT(Clut, pixelGroup[1]); + var pixel3 = GetColorFromCLUT(Clut, pixelGroup[2]); + var pixel4 = GetColorFromCLUT(Clut, pixelGroup[3]); bitmap.SetPixel(x, y, pixel1); bitmap.SetPixel(x + 1, y, pixel2); @@ -488,9 +487,9 @@ public void loadBitmap2() public void printClut() { - for(int i = 0; i < 256; i ++) + for(var i = 0; i < 256; i ++) { - byte[] temp = new byte[4]; + var temp = new byte[4]; temp[0] = Clut[i*4]; temp[1] = Clut[i*4 + 1]; temp[2] = Clut[i*4 + 2]; @@ -548,23 +547,23 @@ public void printClut() private static Color GetColorFromCLUT(byte[] clut, int index) { - int start = index * 4; - byte red = clut[start]; - byte green = clut[start + 1]; - byte blue = clut[start + 2]; + var start = index * 4; + var red = clut[start]; + var green = clut[start + 1]; + var blue = clut[start + 2]; //byte alpha = (byte)(clut[start + 3] / 2); //byte alpha = clut[start + 3]; //byte alpha = (byte)(255 - clut[start + 3]); - byte alpha = (byte)((clut[start + 3] * 0xFF) >> 7); + var alpha = (byte)((clut[start + 3] * 0xFF) >> 7); - Color color = Color.FromArgb(alpha, red, green, blue); + var color = Color.FromArgb(alpha, red, green, blue); return color; } private static string getColorAsHex(Color color) { - byte[] byteArray = new byte[4]; + var byteArray = new byte[4]; byteArray[0] = color.R; byteArray[1] = color.G; byteArray[2] = color.B; diff --git a/OpenKh.Kh1/OpenKh.Kh1.csproj b/OpenKh.Kh1/OpenKh.Kh1.csproj index 7bae57c00..12ffd58ff 100644 --- a/OpenKh.Kh1/OpenKh.Kh1.csproj +++ b/OpenKh.Kh1/OpenKh.Kh1.csproj @@ -6,8 +6,8 @@ - - + + @@ -22,10 +22,4 @@ - - - - - - diff --git a/OpenKh.Kh1/Wpn.cs b/OpenKh.Kh1/Wpn.cs index 31c3f2f58..318595f3f 100644 --- a/OpenKh.Kh1/Wpn.cs +++ b/OpenKh.Kh1/Wpn.cs @@ -18,71 +18,65 @@ public class Wpn public Wpn(string filepath) { - int imageCount = 0; - using (FileStream stream = new FileStream(filepath, FileMode.Open)) - { - // Header - Header = BinaryMapping.ReadObject(stream); + var imageCount = 0; + using var stream = new FileStream(filepath, FileMode.Open); + // Header + Header = BinaryMapping.ReadObject(stream); - //Reserved (16 * 7 bytes) + //Reserved (16 * 7 bytes) - // Main file - Unknown data - Read MainFile for some info - //stream.Position = Header.MainOffset; + // Main file - Unknown data - Read MainFile for some info + //stream.Position = Header.MainOffset; - // Model Environment - stream.Position = Header.MenvOffset; - ModelEnvFile = new MenvFile(); - ModelEnvFile.MenvHeader = BinaryMapping.ReadObject(stream); - long meshListPointer = stream.Position; - ModelEnvFile.ModelHeader = BinaryMapping.ReadObject(stream); + // Model Environment + stream.Position = Header.MenvOffset; + ModelEnvFile = new MenvFile(); + ModelEnvFile.MenvHeader = BinaryMapping.ReadObject(stream); + var meshListPointer = stream.Position; + ModelEnvFile.ModelHeader = BinaryMapping.ReadObject(stream); - for(int i = 0; i < ModelEnvFile.ModelHeader.MeshCount; i++) - { - MdlsMesh mesh = new MdlsMesh(); - mesh.Header = BinaryMapping.ReadObject(stream); - ModelEnvFile.Meshes.Add(mesh); - } + for(var i = 0; i < ModelEnvFile.ModelHeader.MeshCount; i++) + { + var mesh = new MdlsMesh(); + mesh.Header = BinaryMapping.ReadObject(stream); + ModelEnvFile.Meshes.Add(mesh); + } - for (int i = 0; i < ModelEnvFile.ModelHeader.MeshCount; i++) - { - MdlsMesh mesh = ModelEnvFile.Meshes[i]; - stream.Position = meshListPointer + mesh.Header.MeshPacketOffset; - - long nextOffset = Header.MenvOffset + ModelEnvFile.MenvHeader.UnkOffset; - if (i + 1 < ModelEnvFile.ModelHeader.MeshCount) - nextOffset = meshListPointer + ModelEnvFile.Meshes[i + 1].Header.MeshPacketOffset; - long packetSize = nextOffset - stream.Position; - - mesh.packet = new MdlsMeshPacket(); - mesh.packet.RawData = stream.ReadBytes((int)packetSize); - mesh.packet.UnpackData(); - } - - // Textures - Images = new List(); - imageCount = ModelEnvFile.MenvHeader.TextureInfoSize / 0x10; - stream.Position = Header.MenvOffset + ModelEnvFile.MenvHeader.TextureInfoOffset; - for (int i = 0; i < imageCount; i++) - { - MdlsImage image = new MdlsImage(); - image.Info = BinaryMapping.ReadObject(stream); - Images.Add(image); - } - stream.Position = Header.MenvOffset + ModelEnvFile.MenvHeader.TextureDataOffset; - for (int i = 0; i < imageCount; i++) - { - int imageDataLength = Images[i].Info.Width * Images[i].Info.Height; - Images[i].Data = stream.ReadBytes(imageDataLength); - } - for (int i = 0; i < imageCount; i++) - { - Images[i].Clut = stream.ReadBytes(CLUT_SIZE); - } - for (int i = 0; i < imageCount; i++) + for (var i = 0; i < ModelEnvFile.ModelHeader.MeshCount; i++) + { + var mesh = ModelEnvFile.Meshes[i]; + stream.Position = meshListPointer + mesh.Header.MeshPacketOffset; + + long nextOffset = Header.MenvOffset + ModelEnvFile.MenvHeader.UnkOffset; + if (i + 1 < ModelEnvFile.ModelHeader.MeshCount) + nextOffset = meshListPointer + ModelEnvFile.Meshes[i + 1].Header.MeshPacketOffset; + var packetSize = nextOffset - stream.Position; + + mesh.packet = new MdlsMeshPacket(); + mesh.packet.RawData = stream.ReadBytes((int)packetSize); + mesh.packet.UnpackData(); + } + + // Textures + Images = []; + imageCount = ModelEnvFile.MenvHeader.TextureInfoSize / 0x10; + stream.Position = Header.MenvOffset + ModelEnvFile.MenvHeader.TextureInfoOffset; + for (var i = 0; i < imageCount; i++) + { + var image = new MdlsImage { - Images[i].loadBitmap(); - } + Info = BinaryMapping.ReadObject(stream), + }; + Images.Add(image); } + stream.Position = Header.MenvOffset + ModelEnvFile.MenvHeader.TextureDataOffset; + for (var i = 0; i < imageCount; i++) + { + var imageDataLength = Images[i].Info.Width * Images[i].Info.Height; + Images[i].Data = stream.ReadBytes(imageDataLength); + } + for (var i = 0; i < imageCount; i++) Images[i].Clut = stream.ReadBytes(CLUT_SIZE); + for (var i = 0; i < imageCount; i++) Images[i].loadBitmap(); } public class WpnHeader @@ -117,10 +111,7 @@ public class MenvFile public MdlsModelHeader ModelHeader { get; set; } public List Meshes { get; set; } - public MenvFile() - { - Meshes = new List(); - } + public MenvFile() => Meshes = []; public class MenvModelHeader { diff --git a/OpenKh.Kh2/Bgm.cs b/OpenKh.Kh2/Bgm.cs new file mode 100644 index 000000000..2eec82a5e --- /dev/null +++ b/OpenKh.Kh2/Bgm.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Common; +using OpenKh.Kh2.SystemData; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2; + +public class Bgm +{ + [Data] public uint Identifier { get; set; } + [Data] public ushort SequenceID { get; set; } + [Data] public ushort SoundfontID { get; set; } + [Data] public byte TrackCount { get; set; } + [Data] public byte Unk0 { get; set; } + [Data] public byte Unk1 { get; set; } + [Data] public byte Unk2 { get; set; } + [Data] public sbyte Volume { get; set; } + [Data] public byte Unk3 { get; set; } + [Data] public ushort PartsPerQuarterNote { get; set; } + [Data] public uint FileSize { get; set; } + + [Data] public uint Reserved0 { get; set; } + [Data] public uint Reserved1 { get; set; } + [Data] public uint Reserved2 { get; set; } + + public List Tracks = new(); + + public class BgmTrack + { + [Data] public byte[] Raw { get; set; } + } + + public static Bgm Read(Stream stream) + { + var bgm = BinaryMapping.ReadObject(stream); + + bgm.Tracks = Enumerable.Range(0, bgm.TrackCount).Select(_ => stream.ReadBytes((int)stream.ReadUInt32())).ToList(); + + return bgm; + } +} + +public enum BgmTrackCommandType +{ + EndOfTrack = 0x00, + LoopBegin = 0x02, + LoopEnd = 0x03, + SetTempo = 0x08, + Unk0 = 0x0A, + TimeSignature = 0x0C, + Unk1 = 0x0D, + NoteOnPreviousKeyVelocity = 0x10, + NoteOn = 0x11, + NoteOnPreviousVelocity = 0x12, + NoteOnPreviousKey = 0x13, + NoteOffPreviousNote = 0x18, + Unk2 = 0x19, + NoteOff = 0x1A, + ProgramChange = 0x20, + Volume = 0x22, + Expression = 0x24, + Pan = 0x26, + Unk3 = 0x28, + Unk4 = 0x31, + Unk5 = 0x34, + Unk6 = 0x35, + SustainPedal = 0x3C, + Unk7 = 0x3E, + Unk8 = 0x40, + Unk9 = 0x47, + Unk10 = 0x48, + Unk11 = 0x50, + Unk12 = 0x58, + Unk13 = 0x5C, + Portamento = 0x5D, +} + +public static class BgmTrackCommandHelper +{ + public static int ArgumentByteReadCount(this BgmTrackCommandType command) + { + switch (command) + { + case BgmTrackCommandType.SetTempo: + case BgmTrackCommandType.Unk0: + case BgmTrackCommandType.Unk1: + case BgmTrackCommandType.NoteOnPreviousVelocity: + case BgmTrackCommandType.NoteOnPreviousKey: + case BgmTrackCommandType.NoteOff: + case BgmTrackCommandType.ProgramChange: + case BgmTrackCommandType.Volume: + case BgmTrackCommandType.Expression: + case BgmTrackCommandType.Pan: + case BgmTrackCommandType.Unk3: + case BgmTrackCommandType.Unk4: + case BgmTrackCommandType.Unk5: + case BgmTrackCommandType.Unk6: + case BgmTrackCommandType.SustainPedal: + case BgmTrackCommandType.Unk7: + case BgmTrackCommandType.Unk12: + case BgmTrackCommandType.Portamento: + return 1; + case BgmTrackCommandType.TimeSignature: + case BgmTrackCommandType.NoteOn: + case BgmTrackCommandType.Unk2: + case BgmTrackCommandType.Unk9: + return 2; + case BgmTrackCommandType.Unk8: + case BgmTrackCommandType.Unk10: + case BgmTrackCommandType.Unk11: + return 3; + default: + return 0; + } + } +} diff --git a/OpenKh.Kh2/Bop.cs b/OpenKh.Kh2/Bop.cs new file mode 100644 index 000000000..4db4ccea2 --- /dev/null +++ b/OpenKh.Kh2/Bop.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Common; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Bop + { + public class BopEntry + { + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + + [Data] public float RotationX { get; set; } + [Data] public float RotationY { get; set; } + [Data] public float RotationZ { get; set; } + + [Data] public float ScaleX { get; set; } + [Data] public float ScaleY { get; set; } + [Data] public float ScaleZ { get; set; } + + [Data] public uint BobIndex { get; set; } + [Data] public uint Group { get; set; } + [Data] public int MotionIndex { get; set; } + [Data] public uint MotionOffset { get; set; } + [Data] public uint Flag { get; set; } + + [Data] public float ModelHUpper { get; set; } + [Data] public float ModelHLower { get; set; } + [Data] public float ModelMUpper { get; set; } + [Data] public float ModelMLower { get; set; } + [Data] public float ModelLUpper { get; set; } + [Data] public float ModelLLower { get; set; } + + [Data] public float PartsHUpper { get; set; } + [Data] public float PartsHLower { get; set; } + [Data] public float PartsMUpper { get; set; } + [Data] public float PartsMLower { get; set; } + [Data] public float PartsLUpper { get; set; } + [Data] public float PartsLLower { get; set; } + } + + public List Entries = new(); + private Bop() + { + + } + public static Bop Read(Stream stream) + { + var bop = new Bop(); + + stream.ReadUInt32(); //the number 8 + var fileSize = stream.ReadUInt32(); + var count = (int)(fileSize / 0x68); + + bop.Entries = Enumerable.Range(0, count).Select(_ => BinaryMapping.ReadObject(stream)).ToList(); + + return bop; + } + } +} diff --git a/OpenKh.Kh2/Wd.cs b/OpenKh.Kh2/Wd.cs new file mode 100644 index 000000000..4ca1149dd --- /dev/null +++ b/OpenKh.Kh2/Wd.cs @@ -0,0 +1,7 @@ +namespace OpenKh.Kh2 +{ + public class Wd + { + + } +} diff --git a/OpenKh.sln b/OpenKh.sln index a24320005..b1e047764 100644 --- a/OpenKh.sln +++ b/OpenKh.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32203.90 MinimumVisualStudioVersion = 10.0.40219.1 @@ -226,6 +226,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Graphical tool tests", "Gra EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Tests.ModsManager", "OpenKh.Tests.ModsManager\OpenKh.Tests.ModsManager.csproj", "{788CDE3D-77D8-4910-8D1A-C829EFD591D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Godot", "OpenKh.Godot\OpenKh.Godot.csproj", "{8222BC82-8D60-4146-AFFC-50B6E4CA46BF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution .NET Debug|Any CPU = .NET Debug|Any CPU @@ -912,6 +914,14 @@ Global {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Release|Any CPU.Build.0 = Release|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}..NET Debug|Any CPU.Build.0 = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}..NET Release|Any CPU.ActiveCfg = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}..NET Release|Any CPU.Build.0 = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {8222BC82-8D60-4146-AFFC-50B6E4CA46BF}.Release|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/docs/kh2/file/model.md b/docs/kh2/file/model.md index 4b1bca541..d513d2643 100644 --- a/docs/kh2/file/model.md +++ b/docs/kh2/file/model.md @@ -61,7 +61,7 @@ Stored straight after the model [header](#header). | 0x0e | uint8 | Unknown | 0x0f | uint8 | Unknown -UVSC option: +[UVSC option](raw-texture.md#uvsc-uv-scroll): | Bit | Description | |-----|-------------|