diff --git a/include/d/actor/d_a_mant.h b/include/d/actor/d_a_mant.h index 7514bc6fca..6d5b173d1a 100644 --- a/include/d/actor/d_a_mant.h +++ b/include/d/actor/d_a_mant.h @@ -88,9 +88,14 @@ public: /* 0x396A */ u8 field_0x396A[0x399E - 0x396A]; /* 0x399E */ s16 field_0x399e; /* 0x39A0 */ u8 field_0x39A0[0x39A4 - 0x39A0]; - +#if TARGET_PC + /* 0x39A4 */ cM_rnd_c mMantRng; +#endif }; - +#if TARGET_PC +STATIC_ASSERT(sizeof(mant_class) == 0x39ac); +#else STATIC_ASSERT(sizeof(mant_class) == 0x39a4); +#endif #endif /* D_A_MANT_H */ diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index 47b7831c0e..956bd6cc72 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -11,6 +11,7 @@ #include "d/d_com_inf_game.h" #if TARGET_PC +#include #include "dusk/dvd_asset.hpp" #include "dusk/frame_interpolation.h" @@ -40,6 +41,8 @@ static f32* l_texCoord_get() { alignas(32) static f32 buf[338]; static bool _ //#define l_pos (l_pos_get()) #define l_normal (l_normal_get()) #define l_texCoord (l_texCoord_get()) + +static bool l_Egnd_mantTEX_hasReplacement = false; #else #include "assets/l_Egnd_mantTEX.h" @@ -223,6 +226,7 @@ void daMant_packet_c::draw() { GXInitTexObjCI( &undersideTexObj, l_Egnd_mantTEX_U, 0x80, 0x80, GX_TF_C8, GX_CLAMP, GX_CLAMP, 0, 0); GXInitTexObjLOD(&undersideTexObj, GX_LINEAR, GX_LINEAR, 0.0, 0.0, 0.0, 0, 0, GX_ANISO_1); + l_Egnd_mantTEX_hasReplacement = aurora::texture::has_replacement(&mainTexObj, &tlutObj); textureObjsInitialized = true; } #else @@ -636,7 +640,11 @@ static int daMant_Execute(mant_class* i_this) { iVar8 = 0; if (i_this->field_0x3967 != 0) { +#if TARGET_PC + mant_cut_type = l_Egnd_mantTEX_hasReplacement ? 1 : i_this->field_0x3967; +#else mant_cut_type = i_this->field_0x3967; +#endif if (i_this->field_0x3968 < 15) { i_this->field_0x3968++; @@ -648,9 +656,18 @@ static int daMant_Execute(mant_class* i_this) { iVar8 = 20; } - unaff_r29 = cM_rndF(65536.0f); - var_f31 = cM_rndFX(32.0f); - var_f30 = cM_rndFX(32.0f); +#if TARGET_PC + if (l_Egnd_mantTEX_hasReplacement) { + unaff_r29 = i_this->mMantRng.getF(65536.0f); + var_f31 = i_this->mMantRng.getFX(32.0f); + var_f30 = i_this->mMantRng.getFX(32.0f); + } else +#endif + { + unaff_r29 = cM_rndF(65536.0f); + var_f31 = cM_rndFX(32.0f); + var_f30 = cM_rndFX(32.0f); + } } i_this->field_0x3967 = 0; @@ -760,6 +777,8 @@ static int daMant_Create(fopAc_ac_c* i_this) { if(textureObjsInitialized) { GXInitTlutObjData(&tlutObj, l_Egnd_mantPAL); // make sure the cached textures are updated } + + m_this->mMantRng.init(66, 16983, 855); #endif lbl_277_bss_0 = 0; diff --git a/tools/mant_tear_exporter.py b/tools/mant_tear_exporter.py new file mode 100644 index 0000000000..b24918203a --- /dev/null +++ b/tools/mant_tear_exporter.py @@ -0,0 +1,189 @@ +""" +Loads d_a_mant.rel from CWD, applies tears using seed (66, 16983, 855) and cut type 1, writes PNGs to tear_textures/ +Requires pillow and xxhash. +""" + +import math +import struct +import xxhash +from pathlib import Path +from PIL import Image + +def yaz0_decompress(data): + expand_size = struct.unpack_from(">I", data, 4)[0] + out = bytearray(expand_size) + src_pos = 0x10 + dst_pos = 0 + chunk_bits_left = 0 + chunk_bits = 0 + + while dst_pos < expand_size: + if chunk_bits_left == 0: + chunk_bits = data[src_pos] + src_pos += 1 + chunk_bits_left = 8 + + if chunk_bits & 0x80: + out[dst_pos] = data[src_pos] + src_pos += 1 + dst_pos += 1 + else: + b0 = data[src_pos] + b1 = data[src_pos + 1] + src_pos += 2 + dist = ((b0 & 0x0F) << 8) | b1 + count = b0 >> 4 + if count == 0: + count = data[src_pos] + 0x12 + src_pos += 1 + else: + count += 2 + + copy_pos = dst_pos - dist - 1 + for _ in range(count): + out[dst_pos] = out[copy_pos] + dst_pos += 1 + copy_pos += 1 + + chunk_bits <<= 1 + chunk_bits_left -= 1 + + return bytes(out) + +SINCOS_TABLE = tuple( + ( + math.sin((i * math.tau) / (1 << 13)), + math.cos((i * math.tau) / (1 << 13)), + ) + for i in range(1 << 13) +) + +def rnd(rng): + rng[0] = (rng[0] * 171) % 30269 + rng[1] = (rng[1] * 172) % 30307 + rng[2] = (rng[2] * 170) % 30323 + value = rng[0] / 30269.0 + rng[1] / 30307.0 + rng[2] / 30323.0 + return abs(value % 1.0) + +def rnd_f(rng, max_value): + return rnd(rng) * max_value + +def rnd_fx(rng, max_value): + return max_value * (rnd(rng) - 0.5) * 2.0 + +def linear_index_to_swizzled(linear_index): + within_tile_x = linear_index & 0x7 + tile_row_offset = (linear_index & 0x78) * 4 + tile_column_offset = (linear_index >> 4) & 0x18 + macro_row_offset = linear_index & 0x3E00 + + return within_tile_x + tile_row_offset + tile_column_offset + macro_row_offset + +SWIZZLED_TO_XY = [(0, 0)] * 0x4000 +for linear_index in range(0x4000): + x = linear_index & 0x7F + y = linear_index >> 7 + swizzled_index = linear_index_to_swizzled(linear_index) + SWIZZLED_TO_XY[swizzled_index] = (x, y) + +NEIGHBOR_OFFSETS = (0, 1, 0x80, 0x81, 2, 0x82, 0x102, 0x101, 0x100) + +def write_c8_texture(c8_data, stage, output_dir, palette, palette_data): + min_index = min(c8_data) + max_index = max(c8_data) + tlut_offset = 2 * min_index + tlut_size = 2 * (max_index + 1 - min_index) + tlut_end = tlut_offset + tlut_size + path = output_dir / ( + f"[{stage:02d}] tex1_{0x80}x{0x80}_" + f"{xxhash.xxh64(c8_data, seed=0).intdigest():016x}_" + f"{xxhash.xxh64(palette_data[tlut_offset:tlut_end], seed=0).intdigest():016x}_" + f"{0x9}.png" + ) + + rgba = Image.new("RGBA", (0x80, 0x80)) + pixels = rgba.load() + for swizzled_index, palette_index in enumerate(c8_data): + x, y = SWIZZLED_TO_XY[swizzled_index] + pixels[x, y] = palette[palette_index] + rgba.save(path) + return path + +def write_stage(tex, tex_u, pal, stage, output_dir, palette): + return [ + write_c8_texture(bytes(tex), stage, output_dir, palette, pal), + write_c8_texture(bytes(tex_u), stage, output_dir, palette, pal), + ] + +def export_mant_tears(): + rel = yaz0_decompress(Path("d_a_mant.rel").read_bytes()) + tex = bytearray(rel[0x1C00 : 0x1C00 + 0x4000]) + tex_u = bytearray([6] * 0x4000) + pal = bytes(rel[0x9C00 : 0x9C00 + 0x60]) + rng = [int(66), int(16983), int(855)] + + Path("tear_textures").mkdir(parents=True, exist_ok=True) + written = [] + + rgba_palette = [] + for offset in range(0, len(pal), 2): + color16 = struct.unpack_from(">H", pal, offset)[0] + if color16 & 0x8000: + r = (color16 >> 7) & 0xF8 + r |= r >> 5 + g = (color16 >> 2) & 0xF8 + g |= g >> 5 + b = (color16 << 3) & 0xF8 + b |= b >> 5 + a = 255 + else: + r = (color16 >> 4) & 0xF0 + r |= r >> 4 + g = color16 & 0xF0 + g |= g >> 4 + b = (color16 << 4) & 0xF0 + b |= b >> 4 + a = (color16 >> 7) & 0xE0 + a |= (a >> 3) | (a >> 6) + rgba_palette.append((r, g, b, a)) + while len(rgba_palette) < 256: + rgba_palette.append((0, 0, 0, 0)) + + written.extend(write_stage(tex, tex_u, pal, 0, Path("tear_textures"), rgba_palette)) + + cut_step = 0 + for _ in range(15): + cut_step += 1 + + angle = int(rnd_f(rng, 65536.0)) & 0xFFFF + if angle >= 0x8000: + angle -= 0x10000 + + x = rnd_fx(rng, 32.0) + y = rnd_fx(rng, 32.0) + sincos_index = (angle & 0xFFFF) >> (16 - 13) + sin_v, cos_v = SINCOS_TABLE[sincos_index] + + for i, texel_count in enumerate( + tuple( + 1 if i <= 3 or i >= 26 else (9 if 12 <= i <= 18 else 4) + for i in range(30) + ) + ): + x += sin_v + y -= cos_v + + packed = int(x + 64.0) | (int(y + 64.0) << 7) + for j in range(texel_count): + u_var1 = packed + NEIGHBOR_OFFSETS[j] + if 0 <= u_var1 < 0x4000: + i_var5 = linear_index_to_swizzled(u_var1) + tex[i_var5] = 0 + tex_u[i_var5] = 0 + + written.extend(write_stage(tex, tex_u, pal, cut_step, Path("tear_textures"), rgba_palette)) + + return written + +if __name__ == "__main__": + export_mant_tears()