diff --git a/__init__.py b/__init__.py index bc5a1d977..6cdbc03bd 100644 --- a/__init__.py +++ b/__init__.py @@ -36,6 +36,7 @@ mat_unregister, check_or_ask_color_management, ) +from .fast64_internal.f3d.f3d_enums import enum_ac_tri_type from .fast64_internal.f3d.f3d_writer import f3d_writer_register, f3d_writer_unregister from .fast64_internal.f3d.f3d_parser import f3d_parser_register, f3d_parser_unregister from .fast64_internal.f3d.flipbook import flipbook_register, flipbook_unregister @@ -93,6 +94,8 @@ def draw(self, context): col = self.layout.column() col.scale_y = 1.1 # extra padding prop_split(col, context.scene, "f3d_type", "Microcode") + if context.scene.f3d_type == "F3DZEX2 (Emu64)": + prop_split(col, context.scene.fast64.settings, "ac_tri_type", "Triangle Export Type") if context.scene.f3d_type in {"F3DEX3", "T3D"}: prop_split(col, context.scene, "packed_normals_algorithm", "Packed normals alg") col.prop(context.scene, "saveTextures") @@ -104,6 +107,8 @@ def draw(self, context): "While inlining, all meshes will be restored to world default values.\n You can configure these values in the world properties tab.", icon="INFO", ) + if context.scene.f3d_type == "F3DZEX2 (Emu64)": + col.box().label(text="Emu64 supports ignore texture restrictions!", icon="INFO") col.prop(context.scene, "ignoreTextureRestrictions") if context.scene.ignoreTextureRestrictions: col.box().label(text="Width/height must be < 1024. Must be png format.") @@ -206,6 +211,8 @@ class Fast64Settings_Properties(bpy.types.PropertyGroup): name="Prefer RGBA Over CI", description="When enabled, fast64 will default colored textures's format to RGBA even if they fit CI requirements, with the exception of textures that would not fit into TMEM otherwise", ) + ac_tri_type: bpy.props.EnumProperty(name="Triangle Export Type", items=enum_ac_tri_type) + dont_ask_color_management: bpy.props.BoolProperty(name="Don't ask to set color management properties") repo_settings_tab: bpy.props.BoolProperty(default=True, name="Repo Settings") @@ -332,9 +339,9 @@ def upgrade_changed_props(): OOT_ObjectProperties.upgrade_changed_props() for scene in bpy.data.scenes: settings: Fast64Settings_Properties = scene.fast64.settings - if settings.internal_game_update_ver != 1: + if settings.internal_game_update_ver < 2: set_game_defaults(scene, False) - settings.internal_game_update_ver = 1 + settings.internal_game_update_ver = 2 if scene.get("decomp_compatible", False): scene.gameEditorMode = "Homebrew" del scene["decomp_compatible"] diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index 5dcadcafe..7801474f8 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -34,6 +34,10 @@ SPLine3D, SPLineW3D, SP2Triangles, + SPNTrianglesInit_5b, + SPNTriangles_5b, + SPNTrianglesInit_7b, + SPNTriangles_7b, SPCullDisplayList, SPSegment, SPBranchLessZraw, @@ -45,13 +49,16 @@ SPSetOtherMode, DPLoadBlock, DPLoadTLUTCmd, + DPLoadTLUT_Dolphin, DPFullSync, DPSetRenderMode, DPSetTextureImage, + DPSetTextureImage_Dolphin, DPPipeSync, DPLoadSync, DPTileSync, DPSetTile, + DPSetTile_Dolphin, DPSetTileSize, DPLoadTile, FModel, @@ -65,6 +72,17 @@ get_F3D_GBI, ) +TRI_CMDS = [ + SP2Triangles, + SP1Triangle, + SPLine3D, + SPLineW3D, + SPNTrianglesInit_5b, + SPNTriangles_5b, + SPNTrianglesInit_7b, + SPNTriangles_7b, +] + def get_geo_cmds( clear_modes: set[str], set_modes: set[str], is_ex2: bool, matWriteMethod: GfxMatWriteMethod @@ -155,9 +173,14 @@ def place_in_flaglist(flag: bool, enum: str, set_list: SPSetGeometryMode, clear_ place_in_flaglist(defaults.g_tex_gen, "G_TEXTURE_GEN", setGeo, clearGeo) place_in_flaglist(defaults.g_tex_gen_linear, "G_TEXTURE_GEN_LINEAR", setGeo, clearGeo) place_in_flaglist(defaults.g_shade_smooth, "G_SHADING_SMOOTH", setGeo, clearGeo) - if bpy.context.scene.f3d_type == "F3DEX_GBI_2" or bpy.context.scene.f3d_type == "F3DEX_GBI": + if self.f3d.F3DEX_GBI: place_in_flaglist(defaults.g_clipping, "G_CLIPPING", setGeo, clearGeo) - + if self.f3d.POINT_LIT_GBI: + place_in_flaglist(defaults.g_lighting_positional, "G_LIGHTING_POSITIONAL", setGeo, clearGeo) + if self.f3d.F3DZEX2_EMU64: + place_in_flaglist(defaults.g_decal_gequal, "G_DECAL_GEQUAL", setGeo, clearGeo) + place_in_flaglist(defaults.g_decal_equal, "G_DECAL_EQUAL", setGeo, clearGeo) + place_in_flaglist(defaults.g_decal_special, "G_DECAL_SPECIAL", setGeo, clearGeo) self.default_load_geo = SPLoadGeometryMode(setGeo.flagList) self.default_set_geo = setGeo self.default_clear_geo = clearGeo @@ -310,14 +333,17 @@ def build_tmem_dict(self, cmd_list: GfxList): tmem_dict = dict() tile_dict = {i: 0 for i in range(8)} # an assumption that hopefully never needs correction for cmd in cmd_list.commands: - if type(cmd) == DPSetTextureImage: + if type(cmd) in (DPSetTextureImage, DPSetTextureImage_Dolphin): im_buffer = cmd continue + elif type(cmd) == DPSetTile_Dolphin: + tmem_dict[cmd.name + 15] = im_buffer + elif type(cmd) == DPLoadTLUT_Dolphin: + tmem_dict[cmd.tlut_name] = cmd # loadtlut_dolphin loads on its own if type(cmd) == DPSetTile: tile_dict[cmd.tile] = cmd.tmem if type(cmd) in (DPLoadTLUTCmd, DPLoadTile, DPLoadBlock): tmem_dict[tile_dict[cmd.tile]] = im_buffer - continue return tmem_dict def bleed_textures(self, cur_fmat: FMaterial, last_mat: FMaterial, bleed_state: int): @@ -344,7 +370,7 @@ def bleed_textures(self, cur_fmat: FMaterial, last_mat: FMaterial, bleed_state: continue if rm_load and type(cmd) == DPSetTile: commands_bled.commands[j] = None - if rm_load and type(cmd) in (DPLoadTLUTCmd, DPLoadTile, DPLoadBlock): + if rm_load and type(cmd) in (DPLoadTLUTCmd, DPLoadTLUT_Dolphin, DPLoadTile, DPLoadBlock): commands_bled.commands[j] = None rm_load = None continue @@ -633,20 +659,20 @@ def bleed_individual_cmd( SPViewport, SPDisplayList, SPBranchList, - SP1Triangle, - SPLine3D, - SPLineW3D, - SP2Triangles, SPCullDisplayList, SPSegment, SPBranchLessZraw, SPModifyVertex, SPEndDisplayList, DPSetTextureImage, + DPSetTextureImage_Dolphin, + DPSetTile_Dolphin, DPLoadBlock, DPLoadTile, DPLoadTLUTCmd, + DPLoadTLUT_Dolphin, DPFullSync, + *TRI_CMDS, ]: return False diff --git a/fast64_internal/f3d/f3d_enums.py b/fast64_internal/f3d/f3d_enums.py index 46e2ac678..b256ab4cc 100644 --- a/fast64_internal/f3d/f3d_enums.py +++ b/fast64_internal/f3d/f3d_enums.py @@ -185,6 +185,11 @@ ("G_TF_BILERP", "Bilinear", "Standard N64 filtering with 3 point sample"), ] +enumTextAdjust = [ + ("G_TA_N64", "N64", "Center origin"), + ("G_TA_DOLPHIN", "Dolphin", "Top left"), +] + enumTextLUT = [ ("G_TT_NONE", "None", "None"), ("G_TT_RGBA16", "RGBA16", "RGBA16"), @@ -390,7 +395,14 @@ "Variant of F3DEX2 family using vertex rejection instead of clipping", 5, ), + ( + "F3DEX2_PL", + "F3DEX2 (Point Lit)", + "Variant of F3DEX2 family with support for point lighting used in a few games including MM", + 11, + ), ("F3DEX3", "F3DEX3", "Custom microcode by Sauraen", 6), + ("F3DZEX2 (Emu64)", "F3DZEX2 (Emu64)", "Microcode used in Animal Crossing (GC), extended version of F3DZEX", 12), ("", "Homebrew", "", 8), ("RDPQ", "RDPQ", "Base libdragon microcode", 9), ("T3D", "Tiny3D", "Custom libdragon microcode by HailToDodongo", 10), @@ -455,3 +467,13 @@ ("Segment", "Segment", "Call a segmented DL to set the tint, can change at runtime"), ("Light", "From Light", "Automatically load tint color from selectable light slot. Tint level stored in DL"), ] + +enum_ac_tri_type = [ + ( + "Auto", + "Auto", + "Pick between 5 bit and 7 bit commands depending on triangle count, 7 bit can access more of the vertex buffer, 5 bit has smaller commands", + ), + ("5b", "5 Bit", "Always use 5 bit commands"), + ("7b", "7 Bit", "Always use 7 bit commands"), +] diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index a240c99c5..5c374e4a7 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -68,8 +68,10 @@ class GfxMatWriteMethod(enum.Enum): "F3DLX.Rej": (64, 32), "F3DLP.Rej": (80, 32), "F3DEX2/LX2": (32, 32), + "F3DEX2_PL": (32, 32), "F3DEX2.Rej/LX2.Rej": (64, 64), "F3DEX3": (56, 56), + "F3DZEX2 (Emu64)": (2**7, 32), "T3D": (70, 70), } @@ -133,18 +135,25 @@ class GfxMatWriteMethod(enum.Enum): } +EMU64_SWIZZLE_SIZES = {"G_IM_SIZ_4b": (8, 8), "G_IM_SIZ_8b": (8, 4), "G_IM_SIZ_16b": (4, 4), "G_IM_SIZ_32b": (2, 2)} + + def isUcodeF3DEX1(F3D_VER: str) -> bool: return F3D_VER in {"F3DLP.Rej", "F3DLX.Rej", "F3DEX/LX"} def isUcodeF3DEX2(F3D_VER: str) -> bool: - return F3D_VER in {"F3DEX2.Rej/LX2.Rej", "F3DEX2/LX2"} + return F3D_VER in {"F3DEX2.Rej/LX2.Rej", "F3DEX2/LX2", "F3DEX2_PL", "F3DZEX2 (Emu64)"} def isUcodeF3DEX3(F3D_VER: str) -> bool: return F3D_VER == "F3DEX3" +def is_ucode_point_lit(F3D_VER: str) -> bool: + return F3D_VER in {"F3DEX3", "F3DEX2_PL", "F3DZEX2 (Emu64)"} + + def is_ucode_t3d(UCODE_VER: str) -> bool: return UCODE_VER == "T3D" @@ -163,6 +172,8 @@ def __init__(self, F3D_VER): F3DEX_GBI_3 = self.F3DEX_GBI_3 = isUcodeF3DEX3(F3D_VER) F3DLP_GBI = self.F3DLP_GBI = self.F3DEX_GBI self.F3D_OLD_GBI = not (F3DEX_GBI or F3DEX_GBI_2 or F3DEX_GBI_3) + F3DZEX2_EMU64 = self.F3DZEX2_EMU64 = F3D_VER == "F3DZEX2 (Emu64)" + POINT_LIT_GBI = self.POINT_LIT_GBI = is_ucode_point_lit(F3D_VER) self.F3D_GBI = is_ucode_f3d(F3D_VER) # F3DEX2 is F3DEX1 and F3DEX3 is F3DEX2, but F3DEX3 is not F3DEX1 @@ -211,6 +222,13 @@ def __init__(self, F3D_VER): self.G_TRIFAN = 0x09 self.G_LIGHTTORDP = 0x0A else: + if F3DZEX2_EMU64: + self.G_TRIN = 0x09 + self.G_TRIN_INDEPEND = 0x0A + self.G_SETTEXEDGEALPHA = 0xCE + self.G_SETCOMBINE_NOTEV = 0xCF + self.G_SETCOMBINE_TEV = 0xD0 + self.G_SETTILE_DOLPHIN = 0xD2 self.G_SPECIAL_1 = 0xD5 self.G_SPECIAL_2 = 0xD4 self.G_SPECIAL_3 = 0xD3 @@ -360,6 +378,31 @@ def __init__(self, F3D_VER): self.G_LOD = 0x00100000 # NOT IMPLEMENTED if F3DEX_GBI or F3DLP_GBI: self.G_CLIPPING = 0x00800000 + if F3DZEX2_EMU64: + self.G_DECAL_LEQUAL = 0x00000000 + self.G_DECAL_GEQUAL = 0x00000010 + self.G_DECAL_EQUAL = 0x00000020 + self.G_DECAL_ALWAYS = self.G_DECAL_GEQUAL | self.G_DECAL_EQUAL + self.G_DECAL_SPECIAL = 0x00000040 + self.G_DECAL_ALL = self.G_DECAL_ALWAYS | self.G_DECAL_SPECIAL + self.G_TLUT_DOLPHIN = 2 + self.GX_WRAP_MODE_VARS = { + "0": 0, + "GX_CLAMP": 0, + "GX_REPEAT": 1, + "GX_MIRROR": 2, + } + self.G_DOLPHIN_TLUT_DEFAULT_MODE = 15 + self.G_VTX_MODE_5bit = 0x00 + self.G_VTX_MODE_7bit = 0x01 + self.G_SPECIAL_NONE = 0 + self.G_SPECIAL_UNKNOWN = 1 + self.G_SPECIAL_TA_MODE = 2 + self.TA_ADJUST_MODE_VARS = { + "0": 0, + "G_TA_N64": 0, + "G_TA_DOLPHIN": 1, + } else: self.G_CLIPPING = 0x00000000 @@ -372,7 +415,6 @@ def __init__(self, F3D_VER): self.G_LIGHTING_SPECULAR = 0x00002000 self.G_FRESNEL_COLOR = 0x00004000 self.G_FRESNEL_ALPHA = 0x00008000 - self.G_LIGHTING_POSITIONAL = 0x00400000 # Ignored, always on self.allGeomModeFlags = { "G_ZBUFFER", @@ -387,7 +429,6 @@ def __init__(self, F3D_VER): "G_TEXTURE_GEN_LINEAR", "G_LOD", "G_SHADING_SMOOTH", - "G_LIGHTING_POSITIONAL", "G_CLIPPING", } if F3DEX_GBI_3: @@ -401,6 +442,18 @@ def __init__(self, F3D_VER): "G_FRESNEL_COLOR", "G_FRESNEL_ALPHA", } + elif F3DZEX2_EMU64: + self.allGeomModeFlags |= { + "G_DECAL_LEQUAL", + "G_DECAL_GEQUAL", + "G_DECAL_EQUAL", + "G_DECAL_ALWAYS", + "G_DECAL_SPECIAL", + "G_DECAL_ALL", + } + if POINT_LIT_GBI: + self.G_LIGHTING_POSITIONAL = 0x00400000 + self.allGeomModeFlags.add("G_LIGHTING_POSITIONAL") self.G_FOG_H = self.G_FOG / 0x10000 self.G_LIGHTING_H = self.G_LIGHTING / 0x10000 @@ -1804,6 +1857,10 @@ def _SHIFTL(value, amount, mask): return (int(value) & ((1 << mask) - 1)) << amount +def _SHIFTR(value, amount, mask): + return (int(value) >> amount) & ((1 << mask) - 1) + + MTX_SIZE = 64 VTX_SIZE = 16 GFX_SIZE = 8 @@ -2625,9 +2682,9 @@ def to_c_textures(self, texCSeparate, savePNG, texDir, texArrayBitSize): data = CData() for _, fImage in self.textures.items(): if savePNG: - data.append(fImage.to_c_tex_separate(texDir, texArrayBitSize)) + data.append(fImage.to_c_tex_separate(texDir, texArrayBitSize, self.f3d)) else: - data.append(fImage.to_c(texArrayBitSize)) + data.append(fImage.to_c(texArrayBitSize, self.f3d)) return data def to_c_materials(self, gfxFormatter): @@ -3280,7 +3337,7 @@ class FImage: width: int height: int filename: str - data: bytearray = field(init=False, compare=False, default_factory=bytearray) + data: np.ndarray = field(init=False, compare=False, default=np.ndarray([])) startAddress: int = field(init=False, compare=False, default=0) isLargeTexture: bool = field(init=False, compare=False, default=False) converted: bool = field(init=False, compare=False, default=False) @@ -3290,61 +3347,39 @@ def aligner_name(self): return f"{self.name}_aligner" def size(self): - return len(self.data) + return self.data.size * self.data.itemsize def to_binary(self): - return self.data + return self.data.astype(self.data.dtype.newbyteorder(">")).tobytes() - def to_c(self, texArrayBitSize): - return self.to_c_helper(self.to_c_data(texArrayBitSize), texArrayBitSize) + def to_c(self, texArrayBitSize, f3d): + return self.to_c_helper(self.to_c_data(texArrayBitSize), texArrayBitSize, f3d) - def to_c_tex_separate(self, texPath, texArrayBitSize): - return self.to_c_helper('#include "' + texPath + self.filename + '"', texArrayBitSize) + def to_c_tex_separate(self, texPath, texArrayBitSize, f3d): + return self.to_c_helper('#include "' + texPath + self.filename + '"', texArrayBitSize, f3d) - def to_c_helper(self, texData, bitsPerValue): + def to_c_helper(self, texData, bitsPerValue, f3d): code = CData() code.header = f"extern u{str(bitsPerValue)} {self.name}[];\n" - # This is to force 8 byte alignment - if bitsPerValue != 64: + if bitsPerValue != 64 and not f3d.F3DZEX2_EMU64: # This is to force 8 byte alignment code.source = f"Gfx {self.aligner_name}[] = {{gsSPEndDisplayList()}};\n" - code.source += f"u{str(bitsPerValue)} {self.name}[] = {{\n\t" + code.source += f"u{str(bitsPerValue)} {self.name}[] " + if f3d.F3DZEX2_EMU64: # Emu64 requires 32 BYTE alignments + code.source += "__attribute__((aligned(32))) " + code.source += "= {\n\t" code.source += texData code.source += "\n};\n\n" return code - def to_c_data(self, bitsPerValue): + def to_c_data(self, bits_per_val: int): if not self.converted: raise PluginError( "Error: Trying to write texture data to C, but haven't actually converted the image file to bytes yet." ) - - bytesPerValue = int(bitsPerValue / 8) - numValues = int(len(self.data) / bytesPerValue) - remainderCount = len(self.data) - numValues * bytesPerValue - digits = 2 + 2 * bytesPerValue - - code = "".join( - [ - format( - int.from_bytes(self.data[i * bytesPerValue : (i + 1) * bytesPerValue], "big"), - "#0" + str(digits) + "x", - ) - + ", " - + ("\n\t" if i % 8 == 7 else "") - for i in range(numValues) - ] - ) - - if remainderCount > 0: - start = numValues * bytesPerValue - end = (numValues + 1) * bytesPerValue - code += format( - int.from_bytes(self.data[start:end], "big") << (8 * (bytesPerValue - remainderCount)), - "#0" + str(digits) + "x", - ) - - return code + data = self.data.astype(dtype=self.data.dtype.newbyteorder(">")) + hex_str = data.tobytes().hex(":", bits_per_val // 8).split(":") + return "".join([f"0x{s}, " + ("\n\t" if i % 8 == 7 else "") for i, s in enumerate(hex_str)]) def set_addr(self, startAddress): startAddress = get64bitAlignedAddr(startAddress) @@ -3611,6 +3646,18 @@ def _gsSP1Triangle_w1f(v0, v1, v2, flag, f3d): return _SHIFTL((flag), 24, 8) | _SHIFTL((v0) * 10, 16, 8) | _SHIFTL((v1) * 10, 8, 8) | _SHIFTL((v2) * 10, 0, 8) +def gsSPNTriangles(n: int, f3d): + return _SHIFTL(f3d.G_TRIN_INDEPEND, 24, 8) | _SHIFTL(n - 1, 17, 7) + + +def gsSPNTriangleData1(v0: int, v1: int, v2: int, f3d): # 5 bits per vertex id (32) + return _SHIFTL(v2, 10, 5) | _SHIFTL(v1, 5, 5) | _SHIFTL(v0, 0, 5) + + +def gsSPNTriangleData2(v0: int, v1: int, v2: int, f3d): # 7 bits per vertex id (128) + return _SHIFTL(v2, 14, 7) | _SHIFTL(v1, 7, 7) | _SHIFTL(v0, 0, 7) + + def _gsSPLine3D_w1(v0, v1, wd): return _SHIFTL((v0) * 2, 16, 8) | _SHIFTL((v1) * 2, 8, 8) | _SHIFTL((wd), 0, 8) @@ -3721,6 +3768,125 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") +@dataclass(unsafe_hash=True) +class SPNTrianglesInit_5b(GbiMacro): + n: int + v0: int + v1: int + v2: int + v3: int + v4: int + v5: int + v6: int + v7: int + v8: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + _SHIFTL(f3d.G_TRIN_INDEPEND, 24, 8) + | _SHIFTL(self.n - 1, 17, 7) + | _SHIFTL(gsSPNTriangleData1(self.v6, self.v7, self.v8, f3d), 2, 15) + | _SHIFTL(_SHIFTR(gsSPNTriangleData1(self.v3, self.v4, self.v5, f3d), 13, 2), 0, 2), + _SHIFTL(gsSPNTriangleData1(self.v3, self.v4, self.v5, f3d), 19, 13) + | _SHIFTL(gsSPNTriangleData1(self.v0, self.v1, self.v2, f3d), 4, 15) + | _SHIFTL(f3d.G_VTX_MODE_5bit, 0, 1), + ) + else: + raise PluginError("SPNTrianglesInit_5b only available in F3DZEX2 (Emu64).") + + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + +@dataclass(unsafe_hash=True) +class SPNTriangles_5b(GbiMacro): + v0: int + v1: int + v2: int + v3: int + v4: int + v5: int + v6: int + v7: int + v8: int + v9: int + v10: int + v11: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + _SHIFTL(gsSPNTriangleData1(self.v9, self.v10, self.v11, f3d), 17, 15) + | _SHIFTL(gsSPNTriangleData1(self.v6, self.v7, self.v8, f3d), 2, 15) + | _SHIFTL(_SHIFTR(gsSPNTriangleData1(self.v3, self.v4, self.v5, f3d), 13, 2), 0, 2), + _SHIFTL(gsSPNTriangleData1(self.v3, self.v4, self.v5, f3d), 19, 13) + | _SHIFTL(gsSPNTriangleData1(self.v0, self.v1, self.v2, f3d), 4, 15) + | _SHIFTL(f3d.G_VTX_MODE_5bit, 0, 1), + ) + else: + raise PluginError("SPNTriangles_5b only available in F3DZEX2 (Emu64).") + + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + +# The 7b tri commands aren't split in the gbi? +@dataclass(unsafe_hash=True) +class SPNTrianglesInit_7b(GbiMacro): + n: int + v0: int + v1: int + v2: int + v3: int + v4: int + v5: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + _SHIFTL(gsSPNTriangles(self.n, f3d), 0, 32), + ( + _SHIFTL(gsSPNTriangleData2(self.v3, self.v4, self.v5, f3d), 22, 21) + | _SHIFTL(gsSPNTriangleData2(self.v0, self.v1, self.v2, f3d), 1, 21) + | _SHIFTL(f3d.G_VTX_MODE_7bit, 0, 1) + ), + ) + else: + raise PluginError("SPNTrianglesInit_7b only available in F3DZEX2 (Emu64).") + + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + +@dataclass(unsafe_hash=True) +class SPNTriangles_7b(GbiMacro): + v0: int + v1: int + v2: int + v3: int + v4: int + v5: int + v6: int + v7: int + v8: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + ( + _SHIFTL(gsSPNTriangleData2(self.v6, self.v7, self.v8, f3d), 11, 21) + | _SHIFTR(gsSPNTriangleData2(self.v3, self.v4, self.v5, f3d), 21 - 10, 10) # Upper 10 bits + ), + ( + _SHIFTL(gsSPNTriangleData2(self.v3, self.v4, self.v5, f3d), 32 - 11, 11) # Lower 11 bits + | _SHIFTR(gsSPNTriangleData2(self.v0, self.v1, self.v2, f3d), 1, 21) + | _SHIFTL(f3d.G_VTX_MODE_7bit, 0, 1) + ), + ) + else: + raise PluginError("SPNTriangles_7b only available in F3DZEX2 (Emu64).") + + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + # F3DEX3 TODO: Encoding of _g*SP5Triangles commands (SPTriangleStrip, SPTriangleFan) # and support for these in export including tri reordering @@ -4511,6 +4677,22 @@ def to_binary(self, f3d, segments): @dataclass(unsafe_hash=True) +class DPSetTextureAdjustMode(GbiMacro): + mode: str + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + _SHIFTL(f3d.G_SPECIAL_1, 24, 8) + | _SHIFTL(f3d.G_SPECIAL_TA_MODE, 16, 8) + | _SHIFTL(f3d.TA_ADJUST_MODE_VARS[self.mode], 0, 16), + 0, + ) + else: + raise PluginError("DPSetTextureAdjustMode only available in F3DZEX2 (Emu64).") + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + class DPSetTextureConvert(SPSetOtherModeHSub): def to_binary(self, f3d, segments): if self.mode == "G_TC_CONV": @@ -4659,6 +4841,34 @@ def to_binary(self, f3d, segments): return gsSetImage(f3d.G_SETTIMG, fmt, siz, self.width, imagePtr) +@dataclass(unsafe_hash=True) +class DPSetTextureImage_Dolphin(GbiMacro): + fmt: str + siz: str + height: int + width: int + image: FImage + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + fmt = f3d.G_IM_FMT_VARS[self.fmt] + siz = f3d.G_IM_SIZ_VARS[self.siz] + image_ptr = int.from_bytes(encodeSegmentedAddr(self.image.startAddress, segments), "big") + words = ( + _SHIFTL(f3d.G_SETTIMG, 24, 8) + | _SHIFTL(fmt, 21, 3) + | _SHIFTL(siz, 19, 2) + | _SHIFTL(1, 18, 1) + | _SHIFTL((self.height / 4) - 1, 10, 8) + | _SHIFTL((self.width - 1), 0, 10), + image_ptr, + ) + else: + raise PluginError("DPSetTextureImage_Dolphin only available in F3DZEX2 (Emu64).") + + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + def gsDPSetCombine(muxs0, muxs1, f3d): words = _SHIFTL(f3d.G_SETCOMBINE, 24, 8) | _SHIFTL(muxs0, 0, 24), muxs1 return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") @@ -4845,6 +5055,18 @@ def to_binary(self, f3d, segments): return SPLightToRDP(self.light, self.alpha, word0).to_binary(f3d, segments) +@dataclass(unsafe_hash=True) +class DPSetTexEdgeAlpha(GbiMacro): + alpha: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = (_SHIFTL(f3d.G_SETTEXEDGEALPHA, 24, 8), _SHIFTL(self.alpha, 0, 8)) + else: + raise PluginError("DPSetTexEdgeAlpha only available in F3DZEX2 (Emu64).") + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + @dataclass(unsafe_hash=True) class DPSetOtherMode(GbiMacro): mode0: set[str] @@ -4882,6 +5104,30 @@ def is_LOADTILE(self, f3d): return self.tile == f3d.G_TX_LOADTILE +@dataclass(unsafe_hash=True) +class DPSetTileSize_Dolphin(GbiMacro): + tile: int + s: int + t: int + width: int + height: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + (_SHIFTL(f3d.G_SETTILESIZE, 24, 8) | _SHIFTL(self.s, 10, 14) | _SHIFTL(self.width - 1, 0, 10)), + ( + _SHIFTL(1, 31, 1) + | _SHIFTL(self.tile, 24, 3) + | _SHIFTL(self.t, 10, 14) + | _SHIFTL(self.height - 1, 0, 10) + ), + ) + else: + raise PluginError("DPSetTileSize_Dolphin only available in F3DZEX2 (Emu64).") + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + @dataclass(unsafe_hash=True) class DPLoadTile(GbiMacro): tile: int @@ -4935,6 +5181,37 @@ def is_LOADTILE(self, f3d): return self.tile == f3d.G_TX_LOADTILE +@dataclass(unsafe_hash=True) +class DPSetTile_Dolphin(GbiMacro): + d_fmt: str # Always G_DOLPHIN_TLUT_DEFAULT_MODE (15)? + tile: int + name: int + wrap_s: int + wrap_t: int + shift_s: int + shift_t: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + assert self.d_fmt == f3d.G_DOLPHIN_TLUT_DEFAULT_MODE + words = ( + ( + _SHIFTL(f3d.G_SETTILE_DOLPHIN, 24, 8) + | _SHIFTL(f3d.G_DOLPHIN_TLUT_DEFAULT_MODE, 20, 4) + | _SHIFTL(self.tile, 16, 3) + | _SHIFTL(self.name, 12, 4) + | _SHIFTL(f3d.GX_WRAP_MODE_VARS[self.wrap_s], 10, 2) + | _SHIFTL(f3d.GX_WRAP_MODE_VARS[self.wrap_t], 8, 2) + | _SHIFTL(self.shift_s, 4, 4) + | _SHIFTL(self.shift_t, 0, 4) + ), + 0, + ) + else: + raise PluginError("DPSetTile_Dolphin only available in F3DZEX2 (Emu64).") + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + @dataclass(unsafe_hash=True) class DPLoadBlock(GbiMacro): tile: int @@ -5257,6 +5534,34 @@ def size(self, f3d): return GFX_SIZE * 7 +@dataclass(unsafe_hash=True) +class DPLoadTextureBlock_4b_Dolphin(GbiMacro): + timg: FImage + fmt: str + width: int + height: int + pal: int + ws: int + wt: int + ss: int + st: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + return DPSetTextureImage_Dolphin(self.fmt, f3d.G_IM_SIZ_4b, self.height, self.width, self.timg).to_binary( + f3d, segments + ) + DPSetTile_Dolphin( + "G_DOLPHIN_TLUT_DEFAULT_MODE", 0, self.pal, self.ws, self.wt, self.ss, self.st + ).to_binary( + f3d, segments + ) + else: + raise PluginError("DPLoadTextureBlock_4b_Dolphin only available in F3DZEX2 (Emu64).") + + def size(self, f3d): + return GFX_SIZE * 2 + + # gsDPLoadTextureBlock_4bS # gsDPLoadMultiBlock_4b # gsDPLoadMultiBlock_4bS @@ -5412,6 +5717,25 @@ def size(self, f3d): return GFX_SIZE * 7 +@dataclass(unsafe_hash=True) +class DPLoadTextureTile_4b_Dolphin(GbiMacro): + img: FImage + fmt: str + width: int + height: int + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + return DPSetTextureImage_Dolphin(self.fmt, f3d.G_IM_SIZ_4b, self.height, self.width, self.img).to_binary( + f3d, segments + ) + DPSetTile_Dolphin("G_DOLPHIN_TLUT_DEFAULT_MODE", 0, 0, 0, 0, 0, 0).to_binary(f3d, segments) + else: + raise PluginError("DPLoadTextureTile_4b_Dolphin only available in F3DZEX2 (Emu64).") + + def size(self, f3d): + return GFX_SIZE * 2 + + # gsDPLoadMultiTile_4b @@ -5477,6 +5801,28 @@ def size(self, f3d): return GFX_SIZE * 6 +@dataclass(unsafe_hash=True) +class DPLoadTLUT_Dolphin(GbiMacro): + tlut_name: int + count: int + unk: int # Always 1? Possibly dropped support for ia16 + addr: FImage + + def to_binary(self, f3d, segments): + if f3d.F3DZEX2_EMU64: + words = ( + _SHIFTL(f3d.G_LOADTLUT, 24, 8) + | _SHIFTL(f3d.G_TLUT_DOLPHIN, 22, 2) + | _SHIFTL(self.tlut_name, 16, 4) + | _SHIFTL(self.unk, 14, 2) + | _SHIFTL(self.count, 0, 14), + self.addr, + ) + else: + raise PluginError("DPLoadTLUT_Dolphin only available in F3DZEX2 (Emu64).") + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + # gsDPSetScissor # gsDPSetScissorFrac diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 890176c34..1fc27ed77 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -38,6 +38,7 @@ isUcodeF3DEX3, is_ucode_f3d, is_ucode_t3d, + is_ucode_point_lit, default_draw_layers, ) from .f3d_material_presets import * @@ -188,6 +189,13 @@ def menu_items_enum(_self, context): "fog": "g_fog", "texGen": "g_tex_gen", } +POINT_LIT_GEO_MODES = {"positionalLighting": "g_lighting_positional"} + +F3DZEX2_EMU64_GEO_MODES = { + "decalGEqual": "g_decal_gequal", + "decalEqual": "g_decal_equal", + "decalSpecial": "g_decal_special", +} def geo_modes_in_ucode(UCODE_VER: str): @@ -198,6 +206,10 @@ def geo_modes_in_ucode(UCODE_VER: str): geo_modes.update(F3DLX_GEO_MODES) if isUcodeF3DEX3(UCODE_VER): geo_modes.update(F3DEX3_GEO_MODES) + if is_ucode_point_lit(UCODE_VER): + geo_modes.update(POINT_LIT_GEO_MODES) + if UCODE_VER == "F3DZEX2 (Emu64)": + geo_modes.update(F3DZEX2_EMU64_GEO_MODES) if is_ucode_t3d(UCODE_VER): geo_modes.update(T3D_GEO_MODES) return geo_modes @@ -557,6 +569,8 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], c.enabled = enable or not disable_dependent return c + f3d = get_F3D_GBI() + point_lit_unused = f3d.F3DEX_GBI_3 and settings.has_prop_in_ucode("g_lighting_positional") ccWarnings = shadeInCC = False blendWarnings = shadeInBlender = zInBlender = False if isinstance(dataHolder, F3DMaterialProperty): @@ -574,6 +588,8 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], c = indentGroup(inputGroup, "g_lighting", False) if ccWarnings and not shadeInCC and is_on("g_lighting") and not is_on("g_tex_gen"): multilineLabel(c, "Shade not used in CC, can disable\nlighting.", icon="INFO") + if not point_lit_unused: # Draw this flag in Not Useful for f3dex3 + draw_mode(c, "g_lighting_positional") draw_mode(c, "g_packed_normals", "g_lighting_specular", "g_ambocclusion", "g_fresnel_color") if should_draw("g_tex_gen_linear"): d = indentGroup(c, "g_tex_gen", False) @@ -635,6 +651,41 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], else: inputGroup.column().label(text=f"Shade alpha = {shadeAlphaLabel}") + if should_draw("g_decal_equal", "g_decal_gequal", "g_decal_special"): + if blendWarnings: + info_col, c = inputGroup, indentGroup(inputGroup, "Decals:", True) + else: + info_col = c = indentGroup(inputGroup, "In decals (ZMODE_DEC):", True) + c.enabled = not blendWarnings or settings.zmode == "ZMODE_DEC" + if blendWarnings and settings.zmode != "ZMODE_DEC": + c.label(text="Non-decal rendermode, these will be ignored.", icon="INFO") + draw_mode(c, "g_decal_equal", "g_decal_gequal", "g_decal_special") + if not blendWarnings or settings.zmode == "ZMODE_DEC": + # These geo modes are not as clear as others, so we give the user a bit more info + if not settings.g_decal_equal and not settings.g_decal_gequal: + compare_mode = "GX_LEQUAL" + elif settings.g_decal_gequal and not settings.g_decal_equal: + compare_mode = "GX_GEQUAL" + elif settings.g_decal_equal and not settings.g_decal_gequal: + compare_mode = "GX_EQUAL" + else: + compare_mode = "GX_ALWAYS" + info_col.label(text=f"Compare mode (GC) = {compare_mode}", icon="FILTER") + if not settings.g_decal_equal: + info_col.label( + text="Depth offset " + ("towards" if settings.g_decal_gequal else "away from") + " the camera", + icon="IMAGE_ZDEPTH", + ) + if settings.g_decal_special: + blend_mode = "Blend mode (GC):\n" + if settings.g_decal_gequal: + blend_mode += " GX_BM_NONE, GX_BL_ONE,\n GX_BL_ZERO, GX_LO_NOOP" + else: + blend_mode += ( + " GX_BM_BLEND, GX_BL_DSTALPHA,\n GX_BL_INVDSTALPHA, GX_LO_NOOP" + ) + multilineLabel(info_col.box(), blend_mode, icon="SHADERFX") + if should_draw("g_attroffset_st_enable", "g_attroffset_z_enable"): indentGroup(inputGroup, "Attribute offsets:", True) draw_mode(inputGroup, "g_attroffset_st_enable", "g_attroffset_z_enable") @@ -663,9 +714,11 @@ def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], elif ccWarnings and is_on("g_shade") and not shadeInCC and not shadeInBlender: c.label(text="Shade is not being used, can disable.", icon="INFO") - if should_draw("g_lod", "g_clipping"): + if should_draw("g_lod", "g_clipping") or point_lit_unused: c = indentGroup(inputGroup, "Not useful:", True) draw_mode(c, "g_lod", "g_clipping") + if point_lit_unused: + draw_mode(c, "g_lighting_positional") def ui_upper_mode(settings, dataHolder, layout: UILayout, useDropdown): @@ -742,13 +795,31 @@ def ui_other(settings, dataHolder, layout, useDropdown): prop_split(clipRatioGroup, settings, "clip_ratio", "Clip Ratio") if isinstance(dataHolder, Material) or isinstance(dataHolder, F3DMaterialProperty): - blend_color_group = layout.row() + blend_color_group = inputGroup.row() prop_input_name = blend_color_group.column() prop_input = blend_color_group.column() prop_input_name.prop(dataHolder, "set_blend", text="Blend Color") prop_input.prop(dataHolder, "blend_color", text="") prop_input.enabled = dataHolder.set_blend + if bpy.context.scene.f3d_type == "F3DZEX2 (Emu64)": + inputGroup.separator() + adjust_row = inputGroup.row() + adjust_row.prop(dataHolder, "set_bilerp_text_adjust", text="Bilerp Adjust Mode") + prop_value = adjust_row.column() + prop_value.enabled = dataHolder.set_bilerp_text_adjust + prop_value.prop(dataHolder, "bilerp_text_adjust", text="") + if dataHolder.set_bilerp_text_adjust and settings.g_mdsft_text_filt != "G_TF_BILERP": + inputGroup.label(text="Texture filter is not bilerp.", icon="INFO") + + alpha_row = inputGroup.row() + alpha_row.prop(dataHolder, "set_tex_edge_alpha", text="Tex Edge Alpha") + prop_value = alpha_row.column() + prop_value.enabled = dataHolder.set_tex_edge_alpha + prop_value.prop(dataHolder, "tex_edge_alpha", text="") + if dataHolder.set_tex_edge_alpha and not settings.is_emu64_texedge: + inputGroup.label(text="Render mode is not recognised as tex edge.", icon="INFO") + def tmemUsageUI(layout, textureProp): tex = textureProp.tex @@ -1857,6 +1928,8 @@ def set_output_node_groups(material: Material): output_node.inputs["Cycle_A_2"].default_value = 0.5 if output_method == "CLIP": output_node.inputs["Alpha Threshold"].default_value = 0.125 + if bpy.context.scene.f3d_type == "F3DZEX2 (Emu64)" and f3dMat.rdp_settings.is_emu64_texedge: + output_node.inputs["Alpha Threshold"].default_value = f3dMat.tex_edge_alpha material.node_tree.links.new(nodes["Cycle_1"].outputs["Color"], output_node.inputs["Cycle_C_1"]) material.node_tree.links.new(nodes["Cycle_1"].outputs["Alpha"], output_node.inputs["Cycle_A_1"]) material.node_tree.links.new(nodes["FogBlender"].outputs["Color"], output_node.inputs["Cycle_C_2"]) @@ -2976,6 +3049,14 @@ class TextureProperty(PropertyGroup): min=1, default=16, ) + use_pal_index: bpy.props.BoolProperty( + name="Palette Index Reference", description="F3DZEX2 (Emu64): Reference an already loaded palette" + ) + pal_index: bpy.props.StringProperty( + name="Palette Index", + default="0x03", + description="F3DZEX2 (Emu64): The palette's index. Defaults to the grass pallete index (3)", + ) menu: bpy.props.BoolProperty() tex_set: bpy.props.BoolProperty( @@ -3024,6 +3105,8 @@ def key(self): self.tex_reference_size if texSet and useRef else None, self.pal_reference if texSet and useRef and isCI else None, self.pal_reference_size if texSet and useRef and isCI else None, + self.use_pal_index if texSet and useRef and isCI else None, + self.pal_index if self.use_pal_index and texSet and useRef and isCI else None, ) @@ -3061,6 +3144,7 @@ def ui_image( showCheckBox: bool, hide_lowhigh=False, ): + is_fdzex_ac = bpy.context.scene.f3d_type == "F3DZEX2 (Emu64)" inputGroup = layout.box().column() inputGroup.prop( @@ -3082,8 +3166,13 @@ def ui_image( prop_split(prop_input, textureProp, "tex_reference", "Texture Reference") prop_split(prop_input, textureProp, "tex_reference_size", "Texture Size") if textureProp.tex_format[:2] == "CI": + if is_fdzex_ac: + row = prop_input.row() + row.prop(textureProp, "use_pal_index") + if textureProp.use_pal_index: + row.prop(textureProp, "pal_index", text="") flipbook = getattr(material.flipbookGroup, "flipbook" + texIndex) - if flipbook is None or not flipbook.enable: + if (not is_fdzex_ac or not textureProp.use_pal_index) and (flipbook is None or not flipbook.enable): prop_split(prop_input, textureProp, "pal_reference", "Palette Reference") prop_split(prop_input, textureProp, "pal_reference_size", "Palette Size") @@ -3124,24 +3213,18 @@ def ui_image( prop_split(prop_input, textureProp, "tex_format", name="Format") if textureProp.tex_format[:2] == "CI": prop_split(prop_input, textureProp, "ci_format", name="CI Format") - + if is_fdzex_ac and textureProp.ci_format == "IA16": + multilineLabel(prop_input, text="IA16 not supported in F3DZEX2 (Emu64).", icon="ERROR") if not isLarge: + s, t = textureProp.S, textureProp.T if width > 0 and height > 0: texelsPerWord = 64 // texBitSizeInt[textureProp.tex_format] if width % texelsPerWord != 0: msg = prop_input.box().column() msg.label(text=f"Suggest {textureProp.tex_format} tex be multiple ", icon="INFO") msg.label(text=f"of {texelsPerWord} pixels wide for fast loading.") - warnClampS = ( - not isPowerOf2(width) - and not textureProp.S.clamp - and (not textureProp.autoprop or textureProp.S.mask != 0) - ) - warnClampT = ( - not isPowerOf2(height) - and not textureProp.T.clamp - and (not textureProp.autoprop or textureProp.T.mask != 0) - ) + warnClampS = not isPowerOf2(width) and not s.clamp and (not textureProp.autoprop or s.mask != 0) + warnClampT = not isPowerOf2(height) and not t.clamp and (not textureProp.autoprop or t.mask != 0) if warnClampS or warnClampT: msg = prop_input.box().column() msg.label(text=f"Clamping required for non-power-of-2 image", icon="ERROR") @@ -3149,32 +3232,43 @@ def ui_image( texFieldSettings = prop_input.column() clampSettings = texFieldSettings.row() - clampSettings.prop(textureProp.S, "clamp", text="Clamp S") - clampSettings.prop(textureProp.T, "clamp", text="Clamp T") + clampSettings.prop(s, "clamp", text="Clamp S") + clampSettings.prop(t, "clamp", text="Clamp T") mirrorSettings = texFieldSettings.row() - mirrorSettings.prop(textureProp.S, "mirror", text="Mirror S") - mirrorSettings.prop(textureProp.T, "mirror", text="Mirror T") + mirrorSettings.prop(s, "mirror", text="Mirror S") + mirrorSettings.prop(t, "mirror", text="Mirror T") + + if is_fdzex_ac and ((s.clamp and s.mirror) or (t.clamp and t.mirror)): + texFieldSettings.box().label( + text="Clamp + mirror not supported in F3DZEX2 (Emu64).", + icon="ERROR", + ) prop_input.prop(textureProp, "autoprop", text="Auto Set Other Properties") if not textureProp.autoprop: mask = prop_input.row() - mask.prop(textureProp.S, "mask", text="Mask S") - mask.prop(textureProp.T, "mask", text="Mask T") + mask.prop(s, "mask", text="Mask S") + mask.prop(t, "mask", text="Mask T") + if is_fdzex_ac and (log2iRoundUp(width) != s.mask or log2iRoundUp(height) != t.mask): + prop_input.box().label( + text="Mask is not emulated in emu64, non default values are not supported", + icon="ERROR", + ) shift = prop_input.row() - shift.prop(textureProp.S, "shift", text="Shift S") - shift.prop(textureProp.T, "shift", text="Shift T") + shift.prop(s, "shift", text="Shift S") + shift.prop(t, "shift", text="Shift T") if hide_lowhigh: return low = prop_input.row() - low.prop(textureProp.S, "low", text="S Low") - low.prop(textureProp.T, "low", text="T Low") + low.prop(s, "low", text="S Low") + low.prop(t, "low", text="T Low") high = prop_input.row() - high.prop(textureProp.S, "high", text="S High") - high.prop(textureProp.T, "high", text="T High") + high.prop(s, "high", text="S High") + high.prop(t, "high", text="T High") class CombinerProperty(PropertyGroup): @@ -3366,6 +3460,27 @@ class RDPSettings(PropertyGroup): update=update_node_values_with_preset, description="F3DEX3: Enables offsets to vertex ST values, usually for UV scrolling", ) + # AC Decal Modes + g_decal_equal: bpy.props.BoolProperty( + name="Equal", + default=False, + update=update_node_values_with_preset, + description="F3DZEX2 (Emu64): Disables any offset and uses the equal compare mode", + ) + g_decal_gequal: bpy.props.BoolProperty( + name="Greater or Equal", + default=False, + update=update_node_values_with_preset, + description="F3DZEX2 (Emu64): When enabled, the greater or equal compare mode is used, " + "if Equal is disabled a positive offset is also used (closer to the camera).\n" + "When disabled, the default negative offset and less or equal compare mode are used", + ) + g_decal_special: bpy.props.BoolProperty( + name="Special", + default=False, + update=update_node_values_with_preset, + description="F3DZEX2 (Emu64): Apropriately sets decal blend modes", + ) # v1/2 difference g_cull_front: bpy.props.BoolProperty( name="Cull Front", @@ -3452,6 +3567,12 @@ class RDPSettings(PropertyGroup): update=update_node_values_with_preset, description="F3DEX1/LX only, exact function unknown", ) + g_lighting_positional: bpy.props.BoolProperty( + name="Positional Lighting", + default=False, + update=update_node_values_with_preset, + description="F3DEX2 (Point Lit): Enables calculating shade color using positional lights along with directional, ignored in F3DEX3", + ) # upper half mode # v2 only @@ -3709,6 +3830,26 @@ def is_geo_mode_on(self, prop: str) -> bool: def does_blender_use_input(self, setting: str) -> bool: return any(input == setting for input in self.blend_inputs) + @property + def is_emu64_texedge(self): + return ( + self.aa_en + and self.cvg_x_alpha + and self.alpha_cvg_sel + and self.cvg_dst == "CVG_DST_CLAMP" + and self.zmode == "ZMODE_OPA" + ) + + @property + def is_emu64_texedge(self): + return ( + self.aa_en + and self.cvg_x_alpha + and self.alpha_cvg_sel + and self.cvg_dst == "CVG_DST_CLAMP" + and self.zmode == "ZMODE_OPA" + ) + def attributes_to_dict(self, info: dict): data = {} for key, attr, default in info: @@ -4078,6 +4219,8 @@ class AddPresetF3D(AddPresetBase, Operator): "f3d_mat.tex0.tex_reference_size", "f3d_mat.tex0.pal_reference", "f3d_mat.tex0.pal_reference_size", + "f3d_mat.tex0.use_pal_index", + "f3d_mat.tex0.pal_index", "f3d_mat.tex0.S", "f3d_mat.tex0.T", "f3d_mat.tex0.menu", @@ -4095,6 +4238,8 @@ class AddPresetF3D(AddPresetBase, Operator): "f3d_mat.tex1.tex_reference_size", "f3d_mat.tex1.pal_reference", "f3d_mat.tex1.pal_reference_size", + "f3d_mat.tex1.use_pal_index", + "f3d_mat.tex1.pal_index", "f3d_mat.tex1.S", "f3d_mat.tex1.T", "f3d_mat.tex1.menu", @@ -4405,6 +4550,14 @@ class F3DMaterialProperty(PropertyGroup): default=False, update=update_node_values_with_preset, ) + set_tex_edge_alpha: bpy.props.BoolProperty( + default=False, + update=update_node_values_with_preset, + ) + set_bilerp_text_adjust: bpy.props.BoolProperty( + default=False, + update=update_node_values_with_preset, + ) set_key: bpy.props.BoolProperty( default=True, update=update_node_values_with_preset, @@ -4431,6 +4584,22 @@ class F3DMaterialProperty(PropertyGroup): max=1, default=(0, 0, 0, 1), ) + tex_edge_alpha: bpy.props.FloatProperty( + name="Tex Edge Alpha", + min=0, + max=1, + step=100.0 / 255.0, + default=144.0 / 255.0, + update=update_node_values_with_preset, + description="F3DZEX2 (Emu64): Alpha threshold for tex edge (cutout) materials, displays only alpha values greater or equal", + ) + bilerp_text_adjust: bpy.props.EnumProperty( + name="Bilerp Adjust Mode", + items=enumTextAdjust, + default="G_TA_N64", + update=update_node_values_without_preset, + description="F3DZEX2 (Emu64): Changes bilerp filter origin", + ) prim_color: bpy.props.FloatVectorProperty( name="Primitive Color", subtype="COLOR", @@ -4705,6 +4874,8 @@ def key(self) -> F3DMaterialHash: ), self.use_default_lighting, self.set_blend, + self.set_tex_edge_alpha, + self.set_bilerp_text_adjust, self.set_prim, self.set_env, self.set_key, @@ -4713,6 +4884,8 @@ def key(self) -> F3DMaterialHash: self.set_lights, self.set_fog, tuple([round(value, 4) for value in self.blend_color]) if self.set_blend else None, + round(self.tex_edge_alpha, 4) if self.set_tex_edge_alpha else None, + self.bilerp_text_adjust if self.set_bilerp_text_adjust else None, tuple([round(value, 4) for value in self.prim_color]) if self.set_prim else None, round(self.prim_lod_frac, 4) if self.set_prim else None, round(self.prim_lod_min, 4) if self.set_prim else None, diff --git a/fast64_internal/f3d/f3d_material_presets.py b/fast64_internal/f3d/f3d_material_presets.py index 89f0502eb..5d13be2f1 100644 --- a/fast64_internal/f3d/f3d_material_presets.py +++ b/fast64_internal/f3d/f3d_material_presets.py @@ -88,6 +88,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -201,6 +202,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -314,6 +316,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -427,6 +430,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -540,6 +544,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -653,6 +658,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -766,6 +772,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -879,6 +886,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -992,6 +1000,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1105,6 +1114,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1218,6 +1228,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1331,6 +1342,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1444,6 +1456,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1557,6 +1570,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1670,6 +1684,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1783,6 +1798,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -1895,6 +1911,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2025,6 +2042,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2137,6 +2155,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2249,6 +2268,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2361,6 +2381,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2473,6 +2494,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2585,6 +2607,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2697,6 +2720,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2809,6 +2833,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -2921,6 +2946,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3033,6 +3059,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3145,6 +3172,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3257,6 +3285,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3370,6 +3399,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3484,6 +3514,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3605,6 +3636,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_DISABLE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3776,6 +3808,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_DISABLE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -3927,6 +3960,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_DISABLE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4078,6 +4112,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_DISABLE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4229,6 +4264,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_DISABLE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4380,6 +4416,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4512,6 +4549,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4644,6 +4682,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4776,6 +4815,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -4908,6 +4948,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5040,6 +5081,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5172,6 +5214,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5304,6 +5347,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5436,6 +5480,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5568,6 +5613,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5700,6 +5746,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5832,6 +5879,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -5964,6 +6012,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -6096,6 +6145,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -6228,6 +6278,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' @@ -6360,6 +6411,7 @@ f3d_mat.rdp_settings.g_lod = False f3d_mat.rdp_settings.g_shade_smooth = True f3d_mat.rdp_settings.g_clipping = False +f3d_mat.rdp_settings.g_lighting_positional = True f3d_mat.rdp_settings.g_mdsft_alpha_dither = 'G_AD_NOISE' f3d_mat.rdp_settings.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' f3d_mat.rdp_settings.g_mdsft_combkey = 'G_CK_NONE' diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index 6b19b2ed0..499eac642 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -26,6 +26,7 @@ F3DMaterialProperty, update_node_values_of_material, F3DMaterialHash, + texBitSizeInt, ) if TYPE_CHECKING: @@ -539,6 +540,10 @@ def initContext(self): self.numLights: int = 0 self.lightData: dict[Light, bpy.types.Object] = {} # Light : blender light object + self.ac_pal_dict: dict[int, int] = {} # F3DZEX2 (Emu64) + self.set_img = DPSetTextureImage_Dolphin("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 0, 0, None) + self.tri_init_count = 0 + """ Restarts context, but keeps cached materials/textures. Warning: calls initContext, make sure to save/restore preserved fields @@ -570,6 +575,7 @@ def clearMaterial(self): mat.set_lights = False mat.set_env = False mat.set_blend = False + mat.set_tex_edge_alpha = False mat.set_key = False mat.set_k0_5 = False @@ -607,6 +613,10 @@ def clearMaterial(self): self.lights.a = Ambient([0, 0, 0]) self.numLights = 0 + self.ac_pal_dict: dict[int, int] = {} # F3DZEX2 (Emu64) + self.set_img = DPSetTextureImage_Dolphin("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 0, 0, None) + self.tri_init_count = 0 + mat.presetName = "Custom" def mat(self) -> F3DMaterialProperty: @@ -717,19 +727,11 @@ def addTriangle(self, indices, dlData): if self.materialChanged: region = None - tileSettings = self.tileSettings[0] - tileSizeSettings = self.tileSizes[0] - if tileSettings.tmem in self.tmemDict: - textureName = self.tmemDict[tileSettings.tmem] - self.loadTexture(dlData, textureName, region, tileSettings, False) - self.applyTileToMaterial(0, tileSettings, tileSizeSettings, dlData) - - tileSettings = self.tileSettings[1] - tileSizeSettings = self.tileSizes[1] - if tileSettings.tmem in self.tmemDict: - textureName = self.tmemDict[tileSettings.tmem] - self.loadTexture(dlData, textureName, region, tileSettings, False) - self.applyTileToMaterial(1, tileSettings, tileSizeSettings, dlData) + for tileSettings, tileSizeSettings in zip(self.tileSettings[:2], self.tileSizes[:2]): + if tileSettings.tmem in self.tmemDict: + textureName = self.tmemDict[tileSettings.tmem] + self.loadTexture(dlData, textureName, region, tileSettings, False) + self.applyTileToMaterial(tileSettings.tile, tileSettings, tileSizeSettings, dlData) self.applyLights() @@ -797,8 +799,11 @@ def applyTLUTToIndex(self, index): if texProp.tex_format[:2] == "CI": # Only handles TLUT at 256 - tlutName = self.tmemDict[256] - if 256 in self.tmemDict and tlutName is not None: + if self.f3d.F3DZEX2_EMU64: + tlutName = self.ac_pal_dict.get(self.getTileSettings(index).palette, None) + else: + tlutName = self.tmemDict.get(256, None) + if tlutName is not None: tlut = self.textureData[tlutName] # print(f"TLUT: {tlutName}, {isinstance(tlut, F3DTextureReference)}") if isinstance(tlut, F3DTextureReference) or texProp.use_tex_reference: @@ -915,6 +920,16 @@ def setGeoFlags(self, command: "ParsedMacro", value: bool): rdp_settings.g_fresnel_color = value if bitFlags & self.f3d.G_FRESNEL_ALPHA: rdp_settings.g_fresnel_alpha = value + elif self.f3d.F3DZEX2_EMU64: + if bitFlags & self.f3d.G_DECAL_GEQUAL: + rdp_settings.g_decal_gequal = value + if bitFlags & self.f3d.G_DECAL_EQUAL: + rdp_settings.g_decal_equal = value + if bitFlags & self.f3d.G_DECAL_SPECIAL: + rdp_settings.g_decal_special = value + if self.f3d.POINT_LIT_GBI: + if bitFlags & self.f3d.G_LIGHTING_POSITIONAL: + rdp_settings.g_lighting_positional = value if bitFlags & self.f3d.G_FOG: rdp_settings.g_fog = value if bitFlags & self.f3d.G_LIGHTING: @@ -959,6 +974,18 @@ def loadGeoFlags(self, command: "ParsedMacro"): rdp_settings.g_lighting_specular = False rdp_settings.g_fresnel_color = False rdp_settings.g_fresnel_alpha = False + if self.f3d.F3DZEX2_EMU64: + rdp_settings.g_decal_gequal = bitFlags & self.f3d.G_DECAL_GEQUAL != 0 + rdp_settings.g_decal_equal = bitFlags & self.f3d.G_DECAL_EQUAL != 0 + rdp_settings.g_decal_special = bitFlags & self.f3d.G_DECAL_SPECIAL != 0 + else: + rdp_settings.g_decal_gequal = False + rdp_settings.g_decal_equal = False + rdp_settings.g_decal_special = False + if self.f3d.POINT_LIT_GBI: + rdp_settings.g_lighting_positional = bitFlags & self.f3d.G_LIGHTING_POSITIONAL != 0 + else: + rdp_settings.g_lighting_positional = False rdp_settings.g_fog = bitFlags & self.f3d.G_FOG != 0 rdp_settings.g_lighting = bitFlags & self.f3d.G_LIGHTING != 0 rdp_settings.g_tex_gen = bitFlags & self.f3d.G_TEXTURE_GEN != 0 @@ -1558,6 +1585,63 @@ def applyTLUT(self, image, tlut): if invalidIndicesDetected: print("Invalid LUT Indices detected.") + def load_dolphin_tlut(self, params): # gsDPLoadTLUT_Dolphin + tlut_name = math_eval(params[0], self.f3d) + texture_name = params[3] + self.ac_pal_dict[tlut_name] = texture_name + self.materialChanged = True + + def set_texture_image_dolphin(self, params): # DPSetTextureImage_Dolphin + self.set_img.fmt = getTileFormat(params[0], self.f3d) + self.set_img.siz = getTileSize(params[1], self.f3d) + self.set_img.height = math_eval(params[2], self.f3d) + self.set_img.width = math_eval(params[3], self.f3d) + self.set_img.image = self.currentTextureName = params[4] + self.materialChanged = True + + def set_tile_size_dolphin(self, params): # DPSetTileSize_Dolphin + tile_size = self.getTileSizeSettings(params[0]) + + dimensions = [0, 0, 0, 0] + for i in range(1, 5): + dimensions[i - 1] = math_eval(params[i], self.f3d) + + tile_size.uls = dimensions[0] + tile_size.ult = dimensions[1] + tile_size.lrs = dimensions[2] + tile_size.lrt = dimensions[3] + + def load_dolphin_4b_texture_block(self, params): # gsDPLoadTextureBlock_4b_Dolphin + self.set_texture_image_dolphin(params) + self.set_tile_size_dolphin(params) + + def set_tile_dolphin(self, params, dlData): # DPSetTile_Dolphin + tile = self.getTileIndex(params[1]) + tile_settings: DPSetTile = self.tileSettings[tile] + self.tmemDict[tile] = self.set_img.image + tile_settings.fmt = self.set_img.fmt + tile_settings.siz = self.set_img.siz + tile_settings.palette = math_eval(params[2], self.f3d) + + actual_fmt = tile_settings.fmt[8:].replace("_", "") + tile_settings.siz[8:-1].replace("_", "") + texelsPerWord = 64 // texBitSizeInt[actual_fmt] + assert self.set_img.width % texelsPerWord == 0 + tile_settings.line = self.set_img.width // texelsPerWord + + if tile_settings.palette in self.ac_pal_dict: + lut_tile_settings: DPSetTile = copy.copy(tile_settings) + lut_tile_settings.fmt = "G_IM_FMT_RGBA" + lut_tile_settings.siz = "G_IM_SIZ_16b" + tlut = self.loadTexture( + dlData, self.ac_pal_dict[tile_settings.palette], [0, 0, 16, 16], lut_tile_settings, True + ) + + tile_settings.masks = log2iRoundUp(self.set_img.width) + tile_settings.maskt = log2iRoundUp(self.set_img.height) + tile_size: DPSetTileSize = self.tileSizes[tile] + tile_size.uls = 0 + tile_size.ult = 0 + def getVertexDataStart(self, vertexDataParam: str, f3d: F3D): matchResult = re.search(r"\&?([A-Za-z0-9\_]*)\s*(\[([^\]]*)\])?\s*(\+(.*))?", vertexDataParam) if matchResult is None: @@ -1595,6 +1679,20 @@ def processCommands(self, dlData: str, dlName: str, dlCommands: "list[ParsedMacr self.addTriangle(command.params[0:3], dlData) elif command.name == "gsSP2Triangles": self.addTriangle(command.params[0:3] + command.params[4:7], dlData) + elif command.name == "gsSPNTrianglesInit_5b": + self.tri_init_count = math_eval(command.params[0], self.f3d) + self.addTriangle(command.params[1 : max(10, self.tri_init_count)], dlData) + self.tri_init_count -= 3 + elif command.name == "gsSPNTrianglesInit_7b": + self.tri_init_count = math_eval(command.params[0], self.f3d) + self.addTriangle(command.params[1 : max(7, self.tri_init_count)], dlData) + self.tri_init_count -= 2 + elif command.name == "gsSPNTriangles_5b": + self.addTriangle(command.params[0 : max(12, self.tri_init_count * 3)], dlData) + self.tri_init_count -= 4 + elif command.name == "gsSPNTriangles_7b": + self.addTriangle(command.params[0 : max(9, self.tri_init_count * 3)], dlData) + self.tri_init_count -= 3 elif command.name == "gsSPDisplayList" or command.name.startswith("gsSPBranch"): newDLName = self.processDLName(command.params[0]) if newDLName is not None: @@ -1737,6 +1835,12 @@ def processCommands(self, dlData: str, dlName: str, dlCommands: "list[ParsedMacr elif command.name == "gsDPSetBlendColor": mat.blend_color = self.gammaInverseParam(command.params) mat.set_blend = True + elif command.name == "gsDPSetTexEdgeAlpha": # F3DEX (AC) + mat.tex_edge_alpha = math_eval(command.params[0], self.f3d) / 255 + mat.set_tex_edge_alpha = True + elif command.name == "gsDPSetTextureAdjustMode": + mat.bilerp_text_adjust = command.params[0] + mat.set_bilerp_text_adjust = True elif command.name == "gsDPSetFogColor": mat.fog_color = self.gammaInverseParam(command.params) mat.set_fog = True @@ -1779,6 +1883,16 @@ def processCommands(self, dlData: str, dlName: str, dlCommands: "list[ParsedMacr self.loadTile(command.params) elif command.name == "gsDPLoadTLUTCmd": self.loadTLUT(command.params, dlData) + elif command.name == "gsDPSetTile_Dolphin": + self.set_tile_dolphin(command.params, dlData) + elif command.name == "gsDPLoadTLUT_Dolphin": + self.load_dolphin_tlut(command.params) + elif command.name == "gsDPSetTextureImage_Dolphin": + self.set_texture_image_dolphin(command.params) + elif command.name == "gsDPSetTileSize_Dolphin": + self.set_tile_size_dolphin(command.params) + elif command.name == "gsDPLoadTextureBlock_4b_Dolphin": + self.load_dolphin_4b_texture_block(command.params) # This all ignores S/T high/low values # This is pretty bad/confusing @@ -2329,7 +2443,7 @@ def importMeshC( transformMatrix = mathutils.Matrix.Scale(1 / scale, 4) - parseF3D(data, name, transformMatrix, name, name, drawLayer, f3dContext, True) + parseF3D(removeComments(data), name, transformMatrix, name, name, drawLayer, f3dContext, True) f3dContext.createMesh(obj, removeDoubles, importNormals, callClearMaterial) applyRotation([obj], math.radians(-90), "X") diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index c0709589f..7b2e6782c 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -1,5 +1,6 @@ from typing import Union, Optional from dataclasses import dataclass, field +import numpy as np # included by blender import bpy from math import ceil, floor @@ -206,7 +207,10 @@ def maybeSaveSingleLargeTextureSetup( sm = 2 if is4bit else 4 nocm = ("G_TX_WRAP", "G_TX_NOMIRROR") if curImgSet != i: - gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + if fModel.f3d.F3DZEX2_EMU64: + gfxOut.commands.append(DPSetTextureImage_Dolphin(fmt, siz, texDimensions[1], wid, fImage)) + else: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) def loadOneOrTwoS(tmemBase, tidxBase, TL, TH): if line != curTileLines[tidxBase]: @@ -505,13 +509,16 @@ def moreSetupFromModel( fMaterial, material, self.indexInMat ) if self.isTexRef: - if self.flipbook is not None: + if fModel.f3d.F3DZEX2_EMU64 and self.texProp.use_pal_index: + self.palLen = 16 if self.texFormat == "CI4" else 256 + self.palIndex = int(self.texProp.pal_index, 0) + elif self.flipbook is not None: self.palLen = len(self.pal) else: self.palLen = self.texProp.pal_reference_size else: assert self.flipbook is None - self.pal = getColorsUsedInImage(self.texProp.tex, self.palFormat) + self.pal = getColorsUsedInImage(self.texProp.tex, self.palFormat, fModel.f3d.F3DZEX2_EMU64) self.palLen = len(self.pal) if self.palLen > (16 if self.texFormat == "CI4" else 256): raise PluginError( @@ -578,7 +585,9 @@ def writeAll( loadGfx = fMaterial.texture_DL f3d = fModel.f3d if self.loadPal: - savePaletteLoad(loadGfx, fPalette, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d) + savePaletteLoad( + loadGfx, fPalette, self.palIndex, self.palFormat, self.palAddr, self.palLen, 5 - self.indexInMat, f3d + ) if self.doTexLoad: saveTextureLoadOnly(fImage, loadGfx, self.texProp, None, 7 - self.indexInMat, self.texAddr, f3d) if self.doTexTile: @@ -602,9 +611,11 @@ def writeAll( assert ( self.pal is not None ), "self.pal is None, either moreSetupFromModel or materialless_setup must be called beforehand" - writeCITextureData(self.texProp.tex, fImage, self.pal, self.palFormat, self.texFormat) + writeCITextureData( + self.texProp.tex, fImage, self.pal, self.palFormat, self.texFormat, f3d.F3DZEX2_EMU64 + ) else: - writeNonCITextureData(self.texProp.tex, fImage, self.texFormat) + writeNonCITextureData(self.texProp.tex, fImage, self.texFormat, f3d.F3DZEX2_EMU64) class MultitexManager: @@ -655,7 +666,25 @@ def writeAll( # Determine how to arrange / load palette entries into upper half of tmem if self.isCI: assert self.ti0.useTex or self.ti1.useTex - if not self.ti1.useTex: + if fModel.f3d.F3DZEX2_EMU64: + non_rgba = False + if self.ti0.useTex: + if self.ti0.pal is not None: + self.ti0.palIndex = 15 # The default pallete index in AC is 15 + self.ti0.palLen = len(self.ti0.pal) + self.ti0.loadPal = True + non_rgba = self.ti0.palFormat != "RGBA16" + if self.ti1.useTex: + if self.ti1.pal is not None: + self.ti1.palIndex = 15 - 1 + self.ti1.palLen = len(self.ti1.pal) + self.ti1.loadPal = True + non_rgba = self.ti1.palFormat != "RGBA16" + if non_rgba: + raise PluginError( + f"In material {material.name}: Only RGBA16 palette format supported in F3DZEX2 (Emu64)" + ) + elif not self.ti1.useTex: self.ti0.loadPal = True elif not self.ti0.useTex: self.ti1.loadPal = True @@ -975,9 +1004,11 @@ def saveTextureLoadOnly( # LoadTile will pad rows to 64 bit word alignment, while # LoadBlock assumes this is already done. - useLoadBlock = canUseLoadBlock(fImage, texProp.tex_format, f3d) + needs_load = not f3d.F3DZEX2_EMU64 + useLoadBlock = canUseLoadBlock(fImage, texProp.tex_format, f3d) and needs_load line = 0 if useLoadBlock else getTileLine(fImage, SL, SH, siz, f3d) wid = 1 if useLoadBlock else fImage.width + height = 1 if useLoadBlock else fImage.height if siz == "G_IM_SIZ_4b": if useLoadBlock: @@ -985,7 +1016,7 @@ def saveTextureLoadOnly( dxt = f3d.CALC_DXT_4b(fImage.width) siz = "G_IM_SIZ_16b" loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) - else: + elif needs_load: sl2 = int(SL * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) sh2 = int(SH * (2 ** (f3d.G_TEXTURE_IMAGE_FRAC - 1))) siz = "G_IM_SIZ_8b" @@ -1000,14 +1031,18 @@ def saveTextureLoadOnly( dxt = f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + "_BYTES"]) siz += "_LOAD_BLOCK" loadCommand = DPLoadBlock(loadtile, 0, 0, dxs, dxt) - else: + elif needs_load: loadCommand = DPLoadTile(loadtile, sl, tl, sh, th) if not omitSetTextureImage: - gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) - if not omitSetTile: - gfxOut.commands.append(DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) - gfxOut.commands.append(loadCommand) + if f3d.F3DZEX2_EMU64: + gfxOut.commands.append(DPSetTextureImage_Dolphin(fmt, siz, height, wid, fImage)) + else: + gfxOut.commands.append(DPSetTextureImage(fmt, siz, wid, fImage)) + if needs_load: + if not omitSetTile: + gfxOut.commands.append(DPSetTile(fmt, siz, line, tmem, loadtile, 0, nocm, 0, 0, nocm, 0, 0)) + gfxOut.commands.append(loadCommand) def saveTextureTile( @@ -1051,8 +1086,18 @@ def saveTextureTile( SL, _, SH, _, sl, tl, sh, th = getTileSizeSettings(texProp, tileSettings, f3d) line = getTileLine(fImage, SL, SH, siz, f3d) - tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) - tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) + if f3d.F3DZEX2_EMU64: + if (clamp_S and mirror_S) or (clamp_T and mirror_T): + raise PluginError("Clamp + mirror not supported in F3DZEX2 (Emu64)") + if tileSettings is None and (log2iRoundUp(fImage.width) != masks or log2iRoundUp(fImage.height) != maskt): + raise PluginError("Mask is not emulated in emu64, non default values are not supported") + wrap_s = "GX_CLAMP" if clamp_S else "GX_MIRROR" if mirror_S else "GX_REPEAT" + wrap_t = "GX_CLAMP" if clamp_T else "GX_MIRROR" if mirror_T else "GX_REPEAT" + tileCommand = DPSetTile_Dolphin("G_DOLPHIN_TLUT_DEFAULT_MODE", rendertile, pal, wrap_s, wrap_t, shifts, shiftt) + tileSizeCommand = DPSetTileSize_Dolphin(rendertile, sl, tl, (sh - sl) // 4 + 1, (th - tl) // 4 + 1) + else: + tileCommand = DPSetTile(fmt, siz, line, tmem, rendertile, pal, cmt, maskt, shiftt, cms, masks, shifts) + tileSizeCommand = DPSetTileSize(rendertile, sl, tl, sh, th) scrollInfo = getattr(fMaterial.scrollData, f"tile_scroll_tex{rendertile}") if scrollInfo.s or scrollInfo.t: @@ -1073,6 +1118,7 @@ def saveTextureTile( def savePaletteLoad( gfxOut: GfxList, fPalette: FImage, + palIndex: int, palFormat: str, palAddr: int, palLen: int, @@ -1082,6 +1128,10 @@ def savePaletteLoad( assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 palFmt = texFormatOf[palFormat] nocm = ("G_TX_WRAP", "G_TX_NOMIRROR") + if f3d.F3DZEX2_EMU64: + assert palFormat == "RGBA16" + gfxOut.commands.append(DPLoadTLUT_Dolphin(palIndex, palLen - 1, 1, fPalette)) + return gfxOut.commands.extend( [ DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), @@ -1094,12 +1144,45 @@ def savePaletteLoad( # Functions for converting and writing texture and palette data -def extractConvertCIPixel(image, pixels, i, j, palFormat): +def get_rgba16_colors(pixels): # 5 bit RGB, 1 bit alpha + rgb5b = np.round(pixels[:, :3] * (2**5 - 1)).astype(np.uint16) + return (rgb5b[:, 0] << 11) | (rgb5b[:, 1] << 6) | (rgb5b[:, 2] << 1) | (pixels[:, 3] > 0.5) + + +def get_rgb5a3_colors(pixels): + """Each pixel can either be 5 bit RGB (opaque) or 4 bit RGB 3 bit alpha. + The upper 16th bit defines if a pixel is fully opaque, + therefor ignoring the other 3 bits that would otherwise be used for alpha.""" + opaque_mask = pixels[:, 3] == 255 + rgb5 = np.round(pixels[:, :3] * (2**5 - 1)).astype(np.uint16) + rgb4 = np.round(pixels[:, :3] * (2**4 - 1)).astype(np.uint16) + opaque_pixels = (1 << 15) | ((rgb5[:, 0] >> 3) << 10) | ((rgb5[:, 1] >> 3) << 5) | (rgb5[:, 2] >> 3) + translucent_pixels = ( + np.round(pixels[:, 3] * (2**3 - 1)).astype(np.uint16) + | ((rgb4[:, 0] >> 4) << 8) + | ((rgb4[:, 1] >> 4) << 4) + | (rgb4[:, 2] >> 4) + ) + return np.where(opaque_mask, opaque_pixels, translucent_pixels) + + +def get_ia_colors(pixels, i_bits=8, a_bits=8): + size = max(8, i_bits + a_bits) + i_max, a_max = 2**i_bits - 1, 2**a_bits - 1 + assert hasattr(np, f"uint{size}"), f"Invalid size {size}" + typ = getattr(np, f"uint{size}") + result = (color_to_luminance_np(pixels) * i_max).round().astype(typ) + if a_bits > 0: + return result << a_bits | (pixels[:, 3] * a_max).round().astype(typ) + return result + + +def extractConvertCIPixel(image, pixels, i, j, palFormat, use_argb): color = [1, 1, 1, 1] for field in range(image.channels): color[field] = pixels[(j * image.size[0] + i) * image.channels + field] if palFormat == "RGBA16": - pixelColor = getRGBA16Tuple(color) + pixelColor = get_rgb5a3_color(color) if use_argb else getRGBA16Tuple(color) elif palFormat == "IA16": pixelColor = getIA16Tuple(color) else: @@ -1107,130 +1190,121 @@ def extractConvertCIPixel(image, pixels, i, j, palFormat): return pixelColor -def getColorsUsedInImage(image, palFormat): - palette = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - if pixelColor not in palette: - palette.append(pixelColor) - return palette - - -def mergePalettes(pal0, pal1): - palette = [c for c in pal0] - for c in pal1: - if c not in palette: - palette.append(c) - return palette - +def image_to_ci_texture(image, palFormat, use_argb): + pixels = get_pixels_from_image(image) + if palFormat == "RGBA16": + if use_argb: + return get_rgb5a3_colors(pixels) + else: + return get_rgba16_colors(pixels) + elif palFormat == "IA16": + return get_ia_colors(pixels) + raise PluginError(f"Internal error, palette format is {palFormat}") -def getColorIndicesOfTexture(image, palette, palFormat): - texture = [] - # N64 is -Y, Blender is +Y - pixels = image.pixels[:] - for j in reversed(range(image.size[1])): - for i in range(image.size[0]): - pixelColor = extractConvertCIPixel(image, pixels, i, j, palFormat) - if pixelColor not in palette: - raise PluginError(f"Bug: {image.name} palette len {len(palette)} missing CI") - texture.append(palette.index(pixelColor)) - return texture +def getColorsUsedInImage(image, palFormat, use_argb): + return np.sort(np.unique(image_to_ci_texture(image, palFormat, use_argb))) -def compactNibbleArray(texture, width, height): - nibbleData = bytearray(0) - dataSize = int(width * height / 2) - nibbleData = [((texture[i * 2] & 0xF) << 4) | (texture[i * 2 + 1] & 0xF) for i in range(dataSize)] +def mergePalettes(pal0, pal1): + return np.sort(np.unique(np.concatenate((pal0, pal1)))) - if (width * height) % 2 == 1: - nibbleData.append((texture[-1] & 0xF) << 4) - return bytearray(nibbleData) +def getColorIndicesOfTexture(image, sorted_palette, palFormat, use_argb): + return np.searchsorted(sorted_palette, image_to_ci_texture(image, palFormat, use_argb)).astype(np.uint8) def writePaletteData(fPalette: FImage, palette: list[int]): if fPalette.converted: return - for color in palette: - fPalette.data.extend(color.to_bytes(2, "big")) + fPalette.data = palette fPalette.converted = True +def emu64_swizzle_pixels(input_list: np.ndarray[Any, (Any, 4)], width: int, height: int, fmt: str): + block_w, block_h = EMU64_SWIZZLE_SIZES[texBitSizeF3D[fmt]] + block_x_count = width // block_w + block_y_count = height // block_h + output_buffer = np.empty((height * width, 4), input_list.dtype) + i = 0 + + for y_block in reversed(range(block_y_count)): + for x_block in range(block_x_count): + for y_pixel in reversed(range(block_h)): + for x_pixel in range(block_w): + pixel_index = (width * block_h * y_block) + y_pixel * width + x_block * block_w + x_pixel + output_buffer[i] = input_list[pixel_index] + i += 1 + + return output_buffer + + +def get_pixels_from_image(image: bpy.types.Image) -> np.ndarray[np.float32, (Any, 4)]: + channel_count = image.channels + width, height = image.size + + bpy_pixels = np.array(image.pixels, dtype=np.float32).reshape((height, width, channel_count)).clip(0.0, 1.0) + result = np.ones((image.size[1], image.size[0], 4), dtype=np.float32) # default to white opaque + result[:, :, :channel_count] = bpy_pixels # copy, if channel count is 3 all alpha channels will stay 1 + result = np.flip(result, 0) # N64 is -Y, Blender is +Y + result = result.reshape((height * width, 4)) # flatten pixels + + return result + + +def compact_nibble_np(pixels: np.ndarray[np.uint8, Any]): + if len(pixels) % 2 != 0: # uneven pixel count. this is uncommon, don't bother with a more opt approach + pixels = np.append(pixels, pixels[-1]) + return (pixels[::2] << 4) | pixels[1::2] + + def writeCITextureData( - image: bpy.types.Image, - fImage: FImage, - palette: list[int], - palFmt: str, - texFmt: str, + image: bpy.types.Image, fImage: FImage, palette: list[int], palFmt: str, texFmt: str, emu64: bool ): if fImage.converted: return - texture = getColorIndicesOfTexture(image, palette, palFmt) + pixels = getColorIndicesOfTexture(image, palette, palFmt, emu64) + if emu64: + pixels = emu64_swizzle_pixels(np.array(pixels, dtype=np.uint8), image.size[0], image.size[1], texFmt) if texFmt == "CI4": - fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) + fImage.data = np.array(compact_nibble_np(np.array(pixels, dtype=np.uint8)), dtype=np.uint8) else: - fImage.data = bytearray(texture) + fImage.data = pixels + fImage.converted = True -def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): +def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str, emu64: bool): if fImage.converted: return fmt = texFormatOf[texFmt] bitSize = texBitSizeF3D[texFmt] - pixels = image.pixels[:] + pixels = get_pixels_from_image(image) + if emu64: + pixels = emu64_swizzle_pixels(pixels, image.size[0], image.size[1], texFmt) if fmt == "G_IM_FMT_RGBA": - if bitSize == "G_IM_SIZ_16b": - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ - ( - ( - ((int(round(pixels[(j * image.size[0] + i) * image.channels + 0] * 0x1F)) & 0x1F) << 3) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x1F) - >> 2 - ) - ), - ( - ((int(round(pixels[(j * image.size[0] + i) * image.channels + 1] * 0x1F)) & 0x03) << 6) - | ( - (int(round(pixels[(j * image.size[0] + i) * image.channels + 2] * 0x1F)) & 0x1F) - << 1 - ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - ), - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) - elif bitSize == "G_IM_SIZ_32b": - fImage.data = bytearray( - [ - int(round(pixels[(j * image.size[0] + i) * image.channels + field] * 0xFF)) & 0xFF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - for field in range(image.channels) - ] - ) + if bitSize == "G_IM_SIZ_16b": # 5 bit RGB, 1 bit alpha + fImage.data = get_rgba16_colors(pixels) + elif bitSize == "G_IM_SIZ_32b": # 8 bit RGBA + fImage.data = (pixels * 0xFF).round().astype(np.uint8) else: raise PluginError("Invalid combo: " + fmt + ", " + bitSize) elif fmt == "G_IM_FMT_YUV": - raise PluginError("YUV not yet implemented.") - if bitSize == "G_IM_SIZ_16b": - pass + if bitSize == "G_IM_SIZ_16b": # 4 bit Y, 2 bit UV + # https://gist.github.com/Quasimondo/c3590226c924a06b276d606f4f189639 + m = np.array([[0.29900, -0.16874, 0.50000], [0.58700, -0.33126, -0.41869], [0.11400, 0.50000, -0.08131]]) + + pixels: np.ndarray[Any, 3] = np.dot(pixels[:, :3], m) + pixels[:, 1:] += 0.5 + fImage.data = ( + (pixels[:, 0] * 0xFF).round().astype(np.uint16) << 8 + | (pixels[:, 1] * 0xF).round().astype(np.uint16) << 4 + | (pixels[:, 2] * 0xF).round().astype(np.uint16) + ) else: raise PluginError("Invalid combo: " + fmt + ", " + bitSize) @@ -1238,136 +1312,22 @@ def writeNonCITextureData(image: bpy.types.Image, fImage: FImage, texFmt: str): raise PluginError("Internal error, writeNonCITextureData called for CI image.") elif fmt == "G_IM_FMT_IA": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ - ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0x7 - ) - ) - & 0x7 - ) - << 1 - ) - | (1 if pixels[(j * image.size[0] + i) * image.channels + 3] > 0.5 else 0) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ - ( - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xF - ) - ) - & 0xF - ) - << 4 - ) - | (int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xF)) & 0xF) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_16b": - fImage.data = bytearray( - [ - byteVal - for doubleByte in [ - ( - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) - * image.channels : (j * image.size[0] + i) - * image.channels - + 3 - ] - ) - * 0xFF - ) - ) - & 0xFF, - int(round(pixels[(j * image.size[0] + i) * image.channels + 3] * 0xFF)) & 0xFF, - ) - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - for byteVal in doubleByte - ] - ) + if bitSize == "G_IM_SIZ_4b": # 3 bit intensity, 1 bit alpha + fImage.data = compact_nibble_np(get_ia_colors(pixels, i_bits=3, a_bits=1)) + elif bitSize == "G_IM_SIZ_8b": # 4 bit intensity, 4 bit alpha + fImage.data = get_ia_colors(pixels, i_bits=4, a_bits=4) + elif bitSize == "G_IM_SIZ_16b": # 8 bit intensity, 8 bit alpha + fImage.data = get_ia_colors(pixels) else: raise PluginError("Invalid combo: " + fmt + ", " + bitSize) elif fmt == "G_IM_FMT_I": - if bitSize == "G_IM_SIZ_4b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels - + 3 - ] - ) - * 0xF - ) - ) - & 0xF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) - elif bitSize == "G_IM_SIZ_8b": - fImage.data = bytearray( - [ - int( - round( - colorToLuminance( - pixels[ - (j * image.size[0] + i) * image.channels : (j * image.size[0] + i) * image.channels - + 3 - ] - ) - * 0xFF - ) - ) - & 0xFF - for j in reversed(range(image.size[1])) - for i in range(image.size[0]) - ] - ) + if bitSize == "G_IM_SIZ_4b": # 4 bit intensity + fImage.data = compact_nibble_np(get_ia_colors(pixels, i_bits=4, a_bits=0)) + elif bitSize == "G_IM_SIZ_8b": # 8 bit intensity + fImage.data = get_ia_colors(pixels, i_bits=8, a_bits=0) else: raise PluginError("Invalid combo: " + fmt + ", " + bitSize) else: raise PluginError("Invalid image format " + fmt) - # We stored 4bit values in byte arrays, now to convert - if bitSize == "G_IM_SIZ_4b": - fImage.data = compactNibbleArray(fImage.data, image.size[0], image.size[1]) - fImage.converted = True diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index 53af72694..5ea27f208 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -937,7 +937,7 @@ def getSortedBuffer(self) -> dict[int, list[BufferVertex]]: return limbVerts - def processGeometry(self): + def processGeometry(self, ac_use_7b=False): # Sort verts by limb index, then load current limb verts bufferStart = self.bufferStart bufferEnd = self.bufferStart @@ -997,9 +997,7 @@ def processGeometry(self): bufferStart = bufferEnd # Load triangles - triCmds = createTriangleCommands( - self.vertexBufferTriangles, self.vertBuffer, not self.triConverterInfo.f3d.F3D_OLD_GBI - ) + triCmds = createTriangleCommands(self.vertexBufferTriangles, self.vertBuffer, ac_use_7b, self.triConverterInfo) if not self.triConverterInfo.f3d.F3DEX_GBI_3 or not self.material.f3d_mat.use_cel_shading: self.triList.commands.extend(triCmds) else: @@ -1126,6 +1124,16 @@ def writeCelLevels(self, celTriList: Optional[GfxList] = None, triCmds: Optional # Disable alpha compare culling for future DLs self.triList.commands.append(SPAlphaCompareCull("G_ALPHA_COMPARE_CULL_DISABLE", 0)) + def get_best_load_size_and_tri_type(self, vertices_up_next: int): + if self.triConverterInfo.f3d.F3DZEX2_EMU64: + setting = bpy.context.scene.fast64.settings.ac_tri_type + if (setting == "Auto" and vertices_up_next > 2**5) or setting == "7b": + return True, 2**7 + else: + return False, 2**5 + else: + return False, self.triConverterInfo.f3d.vert_load_size + def addFace(self, face, stOffset): triIndices = [] addedVerts = [] # verts added to existing vertexBuffer @@ -1149,10 +1157,11 @@ def addFace(self, face, stOffset): if bufferVert not in self.vertBuffer[: self.bufferStart]: allVerts.append(bufferVert) + ac_use_7b, vert_load_size = self.get_best_load_size_and_tri_type(len(self.vertBuffer) + len(addedVerts)) # We care only about load size, since loading is what takes up time. # Even if vert_buffer is larger, its still another load to fill it. - if len(self.vertBuffer) + len(addedVerts) > self.triConverterInfo.f3d.vert_load_size: - self.processGeometry() + if len(self.vertBuffer) + len(addedVerts) > vert_load_size: + self.processGeometry(ac_use_7b) self.vertBuffer = self.vertBuffer[: self.bufferStart] + allVerts self.vertexBufferTriangles = [triIndices] else: @@ -1161,7 +1170,8 @@ def addFace(self, face, stOffset): def finish(self, terminateDL): if len(self.vertexBufferTriangles) > 0: - self.processGeometry() + ac_use_7b, _vert_load_size = self.get_best_load_size_and_tri_type(len(self.vertBuffer)) + self.processGeometry(ac_use_7b) # if self.originalGroupIndex != self.currentGroupIndex: # self.triList.commands.append(SPMatrix(getMatrixAddrFromGroup(self.originalGroupIndex), "G_MTX_LOAD")) @@ -1228,22 +1238,46 @@ def getLoopColor(loop: bpy.types.MeshLoop, mesh: bpy.types.Mesh) -> Vector: return mathutils.Vector((normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedA)) -def createTriangleCommands(triangles, vertexBuffer, useSP2Triangle): +def createTriangleCommands(triangles, vertexBuffer, ac_use_7b, triConverterInfo): triangles = copy.deepcopy(triangles) commands = [] - def getIndices(tri): - return [vertexBuffer.index(v) for v in tri] + def getIndices(*tris): + return [vertexBuffer.index(v) for tri in tris for v in tri] + + if triConverterInfo.f3d.F3DZEX2_EMU64: + + def get_n_tris_indices(n: int, tri_index=0, triangles=triangles): # Includes dummy data to fill out the command + if len(triangles) - tri_index >= n: + return getIndices(*triangles[tri_index : tri_index + n]), tri_index + n + indices = getIndices(*triangles[tri_index:]) + while len(indices) < n * 3: + indices.extend((0, 0, 0)) + return indices, tri_index + n - t = 0 - while t < len(triangles): - firstTriIndices = getIndices(triangles[t]) - t += 1 - if useSP2Triangle and t < len(triangles): - commands.append(SP2Triangles(*firstTriIndices, 0, *getIndices(triangles[t]), 0)) - t += 1 + tris, tri_index = get_n_tris_indices(2 if ac_use_7b else 3) + if ac_use_7b: + commands.append(SPNTrianglesInit_7b(len(triangles), *tris)) + else: + commands.append(SPNTrianglesInit_5b(len(triangles), *tris)) + + n = 3 if ac_use_7b else 4 + while tri_index < len(triangles): + tris, tri_index = get_n_tris_indices(n, tri_index) + commands.append(SPNTriangles_7b(*tris) if ac_use_7b else SPNTriangles_5b(*tris)) + + return commands + + tri_index = 0 + while tri_index < len(triangles): + if not triConverterInfo.f3d.F3D_OLD_GBI and tri_index + 1 < len(triangles): + commands.append( + SP2Triangles(*getIndices(triangles[tri_index]), 0, *getIndices(triangles[tri_index + 1]), 0) + ) + tri_index += 2 else: - commands.append(SP1Triangle(*firstTriIndices, 0)) + commands.append(SP1Triangle(*getIndices(triangles[tri_index]), 0)) + tri_index += 1 return commands @@ -1593,7 +1627,8 @@ def saveGeoModeCommon(saveFunc: Callable, settings: RDPSettings, defaults: RDPSe saveFunc(settings.g_shade, defaults.g_shade, "G_SHADE", *args) saveFunc(settings.g_cull_front, defaults.g_cull_front, "G_CULL_FRONT", *args) saveFunc(settings.g_cull_back, defaults.g_cull_back, "G_CULL_BACK", *args) - if bpy.context.scene.f3d_type == "F3DEX3": + f3d = get_F3D_GBI() + if f3d.F3DEX_GBI_3: saveFunc(settings.g_ambocclusion, defaults.g_ambocclusion, "G_AMBOCCLUSION", *args) saveFunc(settings.g_attroffset_z_enable, defaults.g_attroffset_z_enable, "G_ATTROFFSET_Z_ENABLE", *args) saveFunc(settings.g_attroffset_st_enable, defaults.g_attroffset_st_enable, "G_ATTROFFSET_ST_ENABLE", *args) @@ -1602,14 +1637,20 @@ def saveGeoModeCommon(saveFunc: Callable, settings: RDPSettings, defaults: RDPSe saveFunc(settings.g_lighting_specular, defaults.g_lighting_specular, "G_LIGHTING_SPECULAR", *args) saveFunc(settings.g_fresnel_color, defaults.g_fresnel_color, "G_FRESNEL_COLOR", *args) saveFunc(settings.g_fresnel_alpha, defaults.g_fresnel_alpha, "G_FRESNEL_ALPHA", *args) + elif f3d.F3DZEX2_EMU64: + saveFunc(settings.g_decal_gequal, defaults.g_decal_gequal, "G_DECAL_GEQUAL", *args) + saveFunc(settings.g_decal_equal, defaults.g_decal_equal, "G_DECAL_EQUAL", *args) + saveFunc(settings.g_decal_special, defaults.g_decal_special, "G_DECAL_SPECIAL", *args) saveFunc(settings.g_fog, defaults.g_fog, "G_FOG", *args) saveFunc(settings.g_lighting, defaults.g_lighting, "G_LIGHTING", *args) saveFunc(settings.g_tex_gen, defaults.g_tex_gen, "G_TEXTURE_GEN", *args) saveFunc(settings.g_tex_gen_linear, defaults.g_tex_gen_linear, "G_TEXTURE_GEN_LINEAR", *args) saveFunc(settings.g_lod, defaults.g_lod, "G_LOD", *args) saveFunc(settings.g_shade_smooth, defaults.g_shade_smooth, "G_SHADING_SMOOTH", *args) - if isUcodeF3DEX1(bpy.context.scene.f3d_type): + if f3d.F3DLP_GBI: saveFunc(settings.g_clipping, defaults.g_clipping, "G_CLIPPING", *args) + if f3d.POINT_LIT_GBI: + saveFunc(settings.g_lighting_positional, defaults.g_lighting_positional, "G_LIGHTING_POSITIONAL", *args) def saveGeoModeDefinition(fMaterial, settings, defaults, matWriteMethod, is_ex2: bool): @@ -1786,6 +1827,11 @@ def saveOtherDefinition(fMaterial, material, defaults): int(material.blend_color[3] * 255), ) ) + if bpy.context.scene.f3d_type == "F3DZEX2 (Emu64)": + if material.set_tex_edge_alpha: + fMaterial.mat_only_DL.commands.append(DPSetTexEdgeAlpha(int(material.tex_edge_alpha * 255))) + if material.set_bilerp_text_adjust: + fMaterial.mat_only_DL.commands.append(DPSetTextureAdjustMode(material.bilerp_text_adjust)) enumMatWriteMethod = [ diff --git a/fast64_internal/sm64/sm64_constants.py b/fast64_internal/sm64/sm64_constants.py index 68b0c4bb9..cfcf7fdd5 100644 --- a/fast64_internal/sm64/sm64_constants.py +++ b/fast64_internal/sm64/sm64_constants.py @@ -3938,6 +3938,7 @@ def get_member_as_dict(name: str, member: DictOrVal[T]): "cullBack": True, "lighting": True, "shadeSmooth": True, + "positionalLighting": True, }, "otherModeH": { "textureFilter": "G_TF_BILERP", diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 4f644a41b..d82ab72c6 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -2,6 +2,7 @@ import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator, inspect from math import pi, ceil, degrees, radians, copysign from mathutils import * +import numpy as np from .utility_anim import * from typing import Callable, Iterable, Any, Optional, Tuple, TypeVar, Union from bpy.types import UILayout, Scene, World @@ -590,6 +591,14 @@ def getRGBA16Tuple(color): ) +def get_rgb5a3_color(color): + r, g, b, a = round(color[0] * 255), round(color[1] * 255), round(color[2] * 255), round(color[3] * 255) + if a == 255: # Opaque, upper bit is 1 + return (1 << 15) | ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3) + else: # rgb 4 bits, a 3 bits + return ((a >> 5) << 12) | ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4) + + RGB_TO_LUM_COEF = mathutils.Vector([0.2126729, 0.7151522, 0.0721750]) @@ -599,6 +608,10 @@ def colorToLuminance(color: mathutils.Color | list[float] | Vector): return RGB_TO_LUM_COEF.dot(color[:3]) +def color_to_luminance_np(pixels: np.ndarray[Any, 4]) -> np.ndarray[Any, 1]: + return np.dot(pixels[:, :3], RGB_TO_LUM_COEF) + + def getIA16Tuple(color): intensity = colorToLuminance(color[0:3]) alpha = color[3] diff --git a/fast64_internal/z64/constants.py b/fast64_internal/z64/constants.py index 6ed83eb2e..55bc3c514 100644 --- a/fast64_internal/z64/constants.py +++ b/fast64_internal/z64/constants.py @@ -511,6 +511,7 @@ "cullBack": True, "lighting": True, "shadeSmooth": True, + "positionalLighting": True, }, "otherModeH": { "alphaDither": "G_AD_NOISE",