diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/api/Sm64dsModelImporter.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/api/Sm64dsModelImporter.cs index c56a9b297..d6fe5e1b4 100644 --- a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/api/Sm64dsModelImporter.cs +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/api/Sm64dsModelImporter.cs @@ -1,7 +1,11 @@ -using fin.animation.keyframes; +using System.Drawing; + +using fin.animation.keyframes; using fin.compression; using fin.data.dictionaries; +using fin.data.lazy; using fin.data.queues; +using fin.image; using fin.io; using fin.math.transform; using fin.model; @@ -78,6 +82,33 @@ var firstChild } } + // Set up materials + var finMaterialManager = model.MaterialManager; + var lazyTextureDictionary + = new LazyDictionary<(Texture texture, Palette? palette), ITexture>( + textureAndPalette => { + var (sm64Texture, sm64Palette) = textureAndPalette; + + var finTexture + = finMaterialManager.CreateTexture( + ImageReader.ReadImage(sm64Texture, sm64Palette)); + finTexture.Name = sm64Texture.Name; + + return finTexture; + }); + + foreach (var sm64Material in bmd.Materials) { + var textureId = sm64Material.TextureId; + var paletteId = sm64Material.TexturePaletteId; + + if (textureId != -1) { + var sm64Texture = bmd.Textures[textureId]; + var sm64Palette = paletteId != -1 ? bmd.Palettes[paletteId] : null; + + var finTexture = lazyTextureDictionary[(sm64Texture, sm64Palette)]; + } + } + // Set up mesh { } diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Bmd.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Bmd.cs index 24e0cefcb..63405de67 100644 --- a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Bmd.cs +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Bmd.cs @@ -41,6 +41,10 @@ public partial class Bmd : IBinaryConvertible { [RSequenceLengthSource(nameof(TextureCount))] public Texture[] Textures { get; set; } + [RAtPosition(nameof(TexturePalettesOffset))] + [RSequenceLengthSource(nameof(TexturePaletteCount))] + public Palette[] Palettes { get; set; } + [RAtPosition(nameof(MaterialsOffset))] [RSequenceLengthSource(nameof(MaterialCount))] public Material[] Materials { get; set; } diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/ImageReader.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/ImageReader.cs new file mode 100644 index 000000000..9fcf33879 --- /dev/null +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/ImageReader.cs @@ -0,0 +1,213 @@ +using System.Drawing; + +using fin.image; +using fin.image.formats; +using fin.util.asserts; +using fin.util.color; + +using schema.binary; + +using SixLabors.ImageSharp.PixelFormats; + +namespace sm64ds.schema.bmd; + +/// +/// Shamelessly stolen from: +/// https://github.com/Arisotura/SM64DSe/blob/master/SM64DSFormats/BMD.NitroTexture.cs +/// +public class ImageReader { + public static IImage ReadImage(Texture texture, Palette? palette) { + switch (texture.TextureType) { + case TextureType.A3_I5: break; + case TextureType.PALETTE_4: + return ReadPalette4_(texture, palette.AssertNonnull()); + case TextureType.PALETTE_16: + return ReadPalette16_(texture, palette.AssertNonnull()); + case TextureType.PALETTE_256: + return ReadPalette256_(texture, palette.AssertNonnull()); + case TextureType.TEX_4X4: break; + case TextureType.A5_I3: break; + case TextureType.DIRECT: break; + default: throw new ArgumentOutOfRangeException(); + } + + return FinImage.Create1x1FromColor(Color.Red); + } + + private static IImage ReadPalette4_(Texture texture, Palette palette) { + var paletteColors = GetPaletteColors_(texture, palette); + + var image = new Rgba32Image(texture.Width, texture.Height); + var fastLock = image.Lock(); + var dst = fastLock.Pixels; + var dstI = 0; + + using var textureBr = new SchemaBinaryReader(texture.Data); + while (!textureBr.Eof) { + var texel = textureBr.ReadByte(); + + for (var i = 0; i < 4; ++i) { + var subTexel = (texel >> (2 * i)) & 0x3; + dst[dstI++] = paletteColors[subTexel]; + } + } + + return image; + } + + private static IImage ReadPalette16_(Texture texture, Palette palette) { + var paletteColors = GetPaletteColors_(texture, palette); + + var image = new Rgba32Image(texture.Width, texture.Height); + var fastLock = image.Lock(); + var dst = fastLock.Pixels; + var dstI = 0; + + using var textureBr = new SchemaBinaryReader(texture.Data); + while (!textureBr.Eof) { + var texel = textureBr.ReadByte(); + + for (var i = 0; i < 2; ++i) { + var subTexel = (texel >> (4 * i)) & 0xF; + dst[dstI++] = paletteColors[subTexel]; + } + } + + return image; + } + + private static IImage ReadPalette256_(Texture texture, Palette palette) { + var paletteColors = GetPaletteColors_(texture, palette); + + var image = new Rgba32Image(texture.Width, texture.Height); + var fastLock = image.Lock(); + var dst = fastLock.Pixels; + var dstI = 0; + + using var textureBr = new SchemaBinaryReader(texture.Data); + while (!textureBr.Eof) { + var texel = textureBr.ReadByte(); + dst[dstI++] = paletteColors[texel]; + } + + return image; + } + + /*private static IImage ReadTex4x4_(Texture texture, Palette palette) { + var paletteColors = GetPaletteColors_(texture, palette); + + int yOut = 0, xOut = 0; + + var width = texture.Width; + var image = new Rgba32Image(width, texture.Height); + var fastLock = image.Lock(); + var dst = fastLock.Pixels; + + using var textureBr = new SchemaBinaryReader(texture.Data); + + while (!textureBr.Eof) { + uint blox = Helper.BytesToUInt32(tex, _in); + ushort palidx_data = texture. + = Helper.BytesToUShort16(tex, m_TextureDataLength + (_in >> 1)); + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + byte texel = (byte) (blox & 0x3); + blox >>= 2; + + int pal_offset = (int) ((palidx_data & 0x3FFF) << 2); + ushort color_mode = (ushort) (palidx_data >> 14); + uint color = 0xFFFFFFFF; + + switch (texel) { + case 0: color = Helper.BytesToUShort16(pal, pal_offset, 0); break; + case 1: + color = Helper.BytesToUShort16(pal, pal_offset + 2, 0); break; + case 2: { + switch (color_mode) { + case 0: + case 2: + color = Helper.BytesToUShort16(pal, pal_offset + 4, 0); break; + case 1: { + ushort c0 = Helper.BytesToUShort16(pal, pal_offset, 0); + ushort c1 = Helper.BytesToUShort16(pal, pal_offset + 2, 0); + color = Helper.BlendColorsBGR15(c0, 1, c1, 1); + } + break; + case 3: { + ushort c0 = Helper.BytesToUShort16(pal, pal_offset, 0); + ushort c1 = Helper.BytesToUShort16(pal, pal_offset + 2, 0); + color = Helper.BlendColorsBGR15(c0, 5, c1, 3); + } + break; + } + } + break; + case 3: { + switch (color_mode) { + case 0: + case 1: color = 0xFFFFFFFF; break; + case 2: + color = Helper.BytesToUShort16(pal, pal_offset + 6, 0); break; + case 3: { + ushort c0 = Helper.BytesToUShort16(pal, pal_offset, 0); + ushort c1 = Helper.BytesToUShort16(pal, pal_offset + 2, 0); + color = Helper.BlendColorsBGR15(c0, 3, c1, 5); + } + break; + } + } + break; + } + + int _out = (int) (((y * width) + x) * 4); + int yoff = (int) (y * width * 4); + int xoff = (int) (x * 4); + + var dstI = _out + yoff + xoff; + + if (color == 0xFFFFFFFF) { + dst[dstI] = default; + } else { + dst[dstI] = ; + byte red = (byte) ((color & 0x001F) << 3); + byte green = (byte) ((color & 0x03E0) >> 2); + byte blue = (byte) ((color & 0x7C00) >> 7); + + m_ARGB[_out + yoff + xoff] = blue; + m_ARGB[_out + yoff + xoff + 1] = green; + m_ARGB[_out + yoff + xoff + 2] = red; + m_ARGB[_out + yoff + xoff + 3] = 0xFF; + } + } + } + + xOut += 4; + if (xOut >= width) { + xOut = 0; + yOut += 4; + } + } + + return image; + }*/ + + // TODO: Optimize this to use stackalloc instead + public static Rgba32[] GetPaletteColors_(Texture texture, Palette palette) { + using var paletteBr = new SchemaBinaryReader(palette.Data); + + var paletteColors = new Rgba32[palette.Data.Length >> 1]; + for (var i = 0; i < paletteColors.Length; ++i) { + ColorUtil.SplitRgb5A1(paletteBr.ReadUInt16(), + out var b, + out var g, + out var r, + out _); + var a = (byte) (texture.UseTransparentColor0 && i == 0 ? 0 : 0xFF); + + paletteColors[i] = new Rgba32(r, g, b, a); + } + + return paletteColors; + } +} \ No newline at end of file diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Material.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Material.cs index 7d8c14f68..bfdc24c90 100644 --- a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Material.cs +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Material.cs @@ -15,8 +15,8 @@ public partial class Material : IBinaryConvertible { [RAtPosition(nameof(nameOffset_))] public string Name { get; set; } - public uint TextureId { get; set; } - public uint TexturePaletteId { get; set; } + public int TextureId { get; set; } + public int TexturePaletteId { get; set; } public FixedPointVector2 TextureScale { get; set; } public uint TextureRotation { get; set; } diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Palette.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Palette.cs new file mode 100644 index 000000000..2d0f55823 --- /dev/null +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Palette.cs @@ -0,0 +1,29 @@ +using fin.schema; + +using schema.binary; +using schema.binary.attributes; + +namespace sm64ds.schema.bmd; + +/// +/// Shamelessly stolen from: +/// https://kuribo64.net/get.php?id=KBNyhM0kmNiuUBb3 +/// +[BinarySchema] +public partial class Palette : IBinaryConvertible { + private uint nameOffset_; + + [NullTerminatedString] + [RAtPosition(nameof(nameOffset_))] + public string Name { get; set; } + + private uint dataOffset_; + private uint dataLength_; + + [RAtPosition(nameof(dataOffset_))] + [RSequenceLengthSource(nameof(dataLength_))] + public byte[] Data { get; set; } + + [Unknown] + public uint Unknown { get; set; } +} \ No newline at end of file diff --git a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Texture.cs b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Texture.cs index 22ddb01fb..bf1e1f3fa 100644 --- a/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Texture.cs +++ b/FinModelUtility/Games/SuperMario64Ds/SuperMario64Ds/src/schema/bmd/Texture.cs @@ -1,8 +1,20 @@ -using schema.binary; +using fin.math; + +using schema.binary; using schema.binary.attributes; namespace sm64ds.schema.bmd; +public enum TextureType { + A3_I5 = 1, + PALETTE_4 = 2, + PALETTE_16 = 3, + PALETTE_256 = 4, + TEX_4X4 = 5, + A5_I3 = 6, + DIRECT = 7 +} + /// /// Shamelessly stolen from: /// https://kuribo64.net/get.php?id=KBNyhM0kmNiuUBb3 @@ -26,4 +38,11 @@ public partial class Texture : IBinaryConvertible { public ushort Height { get; set; } public uint Parameters { get; set; } + + [Skip] + public TextureType TextureType + => (TextureType) this.Parameters.ExtractFromRight(26, 3); + + [Skip] + public bool UseTransparentColor0 => this.Parameters.GetBit(29); } \ No newline at end of file