diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 1aad38346..b2b35ecd3 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -1,5 +1,6 @@ from pathlib import Path import shutil, copy, bpy, re, os +from typing import NamedTuple from io import BytesIO from math import ceil, log, radians from mathutils import Matrix, Vector @@ -14,6 +15,7 @@ ui_procAnim, update_world_default_rendermode, ) +from .sm64_geolayout_utility import OverrideHash from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup from .sm64_utility import ( END_IF_FOOTER, @@ -26,7 +28,7 @@ ) from .sm64_level_parser import parse_level_binary from .sm64_rom_tweaks import ExtendBank0x04 -from typing import Tuple +from .sm64_geolayout_classes import BaseDisplayListNode from ..f3d.f3d_bleed import BleedGraphics @@ -102,12 +104,17 @@ } +class GfxOverride(NamedTuple): + gfx: GfxList + nodes: list["BaseDisplayListNode"] + + class SM64Model(FModel): def __init__(self, name, DLFormat, matWriteMethod): FModel.__init__(self, name, DLFormat, matWriteMethod) self.no_light_direction = bpy.context.scene.fast64.sm64.matstack_fix self.layer_adapted_fmats = {} - self.draw_overrides: dict[FMesh, dict[tuple, tuple[GfxList, list["DisplayListNode"]]]] = {} + self.draw_overrides: dict[FMesh, dict[OverrideHash, GfxOverride]] = {} def getDrawLayerV3(self, obj): return int(obj.draw_layer_static) @@ -124,7 +131,7 @@ def __init__(self, scrollMethod: ScrollMethod): self.functionNodeDraw = False GfxFormatter.__init__(self, scrollMethod, 8, "segmented_to_virtual") - def processGfxScrollCommand(self, commandIndex: int, command: GbiMacro, gfxListName: str) -> Tuple[str, str]: + def processGfxScrollCommand(self, commandIndex: int, command: GbiMacro, gfxListName: str) -> tuple[str, str]: tags: GfxTag = command.tags fMaterial: FMaterial = command.fMaterial diff --git a/fast64_internal/sm64/sm64_geolayout_classes.py b/fast64_internal/sm64/sm64_geolayout_classes.py index d0c5908d8..b75b51714 100644 --- a/fast64_internal/sm64/sm64_geolayout_classes.py +++ b/fast64_internal/sm64/sm64_geolayout_classes.py @@ -218,7 +218,7 @@ def getDrawLayers(self): class Geolayout: def __init__(self, name, isStartGeo): - self.nodes = [] + self.nodes: list[TransformNode] = [] self.name = toAlnum(name) self.startAddress = 0 self.isStartGeo = isStartGeo @@ -284,7 +284,7 @@ def getDrawLayers(self): class TransformNode: def __init__(self, node): self.node = node - self.children = [] + self.children: list[TransformNode] = [] self.parent = None self.skinned = False self.skinnedWithoutDL = False @@ -445,7 +445,7 @@ def __init__(self, material, specificMat, drawLayer, overrideType, texDimensions class JumpNode: - def __init__(self, storeReturn, geolayout, geoRef: str = None): + def __init__(self, storeReturn, geolayout: Geolayout, geoRef: str = None): self.geolayout = geolayout self.storeReturn = storeReturn self.hasDL = False @@ -698,10 +698,9 @@ def __init__(self, drawLayer, fieldLayout, hasDL, translate, rotate, dlRef: str self.rotate = rotate self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): if self.hasDL: @@ -798,10 +797,9 @@ def __init__(self, drawLayer, useDeform, translate, dlRef: str = None): self.hasDL = useDeform self.translate = translate self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): return [8] if self.hasDL else [] @@ -842,10 +840,9 @@ def __init__(self, drawLayer, hasDL, rotate, dlRef: str = None): self.hasDL = hasDL self.rotate = rotate self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): return [8] if self.hasDL else [] @@ -884,10 +881,9 @@ def __init__(self, drawLayer, hasDL, translate, dlRef: str = None): self.hasDL = hasDL self.translate = translate self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): return [8] if self.hasDL else [] @@ -923,10 +919,9 @@ def __init__(self, drawLayer, dlRef: str = None): self.drawLayer = drawLayer self.hasDL = True self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): return [4] @@ -979,10 +974,9 @@ def __init__(self, drawLayer, geo_scale, use_deform, dlRef: str = None): self.scaleValue = geo_scale self.hasDL = use_deform self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def get_ptr_offsets(self): return [8] if self.hasDL else [] @@ -1058,10 +1052,9 @@ def __init__(self, drawLayer, use_deform, translate, dlRef: str = None): self.hasDL = use_deform self.translate = translate self.fMesh = None - self.DLmicrocode = None + self.dlRef = dlRef # exists to get the override DL from an fMesh - self.override_hash = None def size(self): return 12 diff --git a/fast64_internal/sm64/sm64_geolayout_utility.py b/fast64_internal/sm64/sm64_geolayout_utility.py index d94be95b1..edf43ddc6 100644 --- a/fast64_internal/sm64/sm64_geolayout_utility.py +++ b/fast64_internal/sm64/sm64_geolayout_utility.py @@ -135,12 +135,17 @@ def updateBone(bone, context): addBoneToGroup(armatureObj, bone.name) +OverrideHash = tuple[any, ...] + + class BaseDisplayListNode: """Base displaylist node with common helper functions dealing with displaylists""" dl_ext = "WITH_DL" # add dl_ext to geo command if command has a displaylist override_layer = False dlRef: str | GfxList | None + override_hash: OverrideHash | None = None + DLmicrocode = None def get_dl_address(self): assert not isinstance(self.dlRef, str), "dlRef string not supported in binary" diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 558c4b682..c678dbd78 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -11,7 +11,7 @@ from .sm64_objects import InlineGeolayoutObjConfig, inlineGeoLayoutObjects from .sm64_geolayout_bone import getSwitchOptionBone from .sm64_camera import saveCameraSettingsToGeolayout -from .sm64_f3d_writer import SM64Model, SM64GfxFormatter +from .sm64_f3d_writer import SM64Model, SM64GfxFormatter, GfxOverride, OverrideHash from .sm64_texscroll import modifyTexScrollFiles, modifyTexScrollHeadersGroup from .sm64_level_parser import parse_level_binary from .sm64_rom_tweaks import ExtendBank0x04 @@ -407,17 +407,16 @@ def create_revert_node(draw_layer, node: DisplayListNode | None = None): if f_mesh.cullVertexList: create_revert_node(draw_layer, transform_node) else: - draw_overrides = f_model.draw_overrides.setdefault(f_mesh, {}) if node.override_hash: node.override_hash = (5, *node.override_hash) elif hasattr(f_mesh, "override_layer") and f_mesh.override_layer: node.override_hash = (5, node.drawLayer) else: node.override_hash = (6,) - existing_cmd_list, existing_nodes = draw_overrides.get(node.override_hash, (None, [])) - if existing_cmd_list is not None: - node.DLmicrocode = existing_cmd_list - existing_nodes.append(node) + override = f_model.draw_overrides.get(f_mesh, {}).get(node.override_hash) + if override is not None: + node.DLmicrocode = override.gfx + override.nodes.append(node) continue else: node.DLmicrocode = cmd_list = copy.copy(cmd_list) @@ -426,7 +425,7 @@ def create_revert_node(draw_layer, node: DisplayListNode | None = None): else: cmd_list.name += "_with_revert" cmd_list.commands = cmd_list.commands.copy() - draw_overrides[node.override_hash] = (cmd_list, [node]) + f_model.draw_overrides.setdefault(f_mesh, {})[node.override_hash] = GfxOverride(cmd_list, [node]) # remove SPEndDisplayList from gfx_list, material_revert has its own SPEndDisplayList cmd while SPEndDisplayList() in cmd_list.commands: cmd_list.commands.remove(SPEndDisplayList()) @@ -435,20 +434,26 @@ def create_revert_node(draw_layer, node: DisplayListNode | None = None): def add_overrides_to_fmodel(f_model: SM64Model): for f_mesh, draw_overrides in f_model.draw_overrides.items(): - nodes = [node for _, nodes in draw_overrides.items() for node in nodes] - if all(node.override_hash is not None for _, (_, nodes) in draw_overrides.items() for node in nodes): - # all nodes use an override, make the first override the main draw + # each override dict might have a none which ends up unused, actually check the node + nodes = [node for override in draw_overrides.values() for node in override.nodes] + if ( + len(nodes) > 0 + and all(node.override_hash is not None for node in nodes) + and not any(node.dlRef is f_mesh.draw for node in nodes) + ): override_hash, cmd_list, nodes = next( (override_hash, cmd_list, nodes) for override_hash, (cmd_list, nodes) in draw_overrides.items() - if override_hash is not None and any(node.override_hash == override_hash for node in nodes) + if any(node.override_hash == override_hash for node in nodes) ) for node in nodes: if node.override_hash == override_hash: node.DLmicrocode = cmd_list node.override_hash = None f_mesh.draw = cmd_list + f_mesh.name = cmd_list.name draw_overrides.pop(override_hash) + for override_hash, (cmd_list, nodes) in draw_overrides.items(): # remove no longer used overrides if all(node.override_hash is None or node.override_hash != override_hash for node in nodes): @@ -515,10 +520,10 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, children = meshGeolayout.nodes meshGeolayout.nodes = [] - for node in children: - node = copy.copy(node) - node.node = copy.copy(node.node) - meshGeolayout.nodes.append(generate_overrides(fModel, node, [], meshGeolayout, geolayoutGraph)) + for child in children: + child_copy = copy.copy(child) + child_copy.node = copy.copy(child_copy.node) + meshGeolayout.nodes.append(generate_overrides(fModel, child_copy, [], meshGeolayout, geolayoutGraph)) append_revert_to_geolayout(geolayoutGraph, fModel) add_overrides_to_fmodel(fModel) @@ -1092,16 +1097,20 @@ def generate_overrides( else: node.geolayout.nodes = [] for child in start_nodes: - child = copy.copy(child) - child.node = copy.copy(child.node) - node.geolayout.nodes.append(generate_overrides(fModel, child, switch_stack.copy(), geolayout, graph, name)) + child_copy = copy.copy(child) + child_copy.node = copy.copy(child_copy.node) + node.geolayout.nodes.append( + generate_overrides(fModel, child_copy, switch_stack.copy(), geolayout, graph, name) + ) elif node.hasDL or hasattr(node, "drawLayer"): + draw_overrides = fModel.draw_overrides.setdefault(node.fMesh, {}) for i, override_node in enumerate(switch_stack): if node.hasDL: - dl, override_hash = save_override_draw( + save_override_draw( fModel, node.DLmicrocode, name, + draw_overrides, node.override_hash, override_node.material, override_node.specificMat, @@ -1112,38 +1121,35 @@ def generate_overrides( node.drawLayer, True, ) - if dl is not None and override_hash is not None: - node.DLmicrocode = dl - node.override_hash = override_hash if override_node.drawLayer is not None and node.drawLayer != override_node.drawLayer: node.drawLayer = override_node.drawLayer if node.fMesh is not None: node.fMesh.override_layer = True if node.hasDL: - draw_overrides = fModel.draw_overrides.setdefault(node.fMesh, {}) - _, nodes = draw_overrides.setdefault(node.override_hash, (node.DLmicrocode, [])) + nodes = draw_overrides.setdefault(node.override_hash, GfxOverride(node.DLmicrocode, [])).nodes nodes.append(node) for i, child in enumerate(children): - child = copy.copy(child) - child_node = child.node = copy.copy(child.node) - if isinstance(child_node, SwitchOverrideNode): - child.parent = None + child_copy = copy.copy(child) + child_node_copy = child_copy.node = copy.copy(child_copy.node) + if isinstance(child_node_copy, SwitchOverrideNode): + child_copy.parent = None assert i != 0, "Switch override must not be the first child of its parent" - override_switch_stack = [*switch_stack, child_node] + override_switch_stack = [*switch_stack, child_node_copy] option0 = copy.copy(children[0]) + option0_copy = copy.copy(option0) new_name = toAlnum(f"{name}_opt_{i}") new_geolayout = graph.addGeolayout(transform_node, geolayout.name + new_name) graph.addGeolayoutCall(geolayout, new_geolayout) new_geolayout.nodes.append( - generate_overrides(fModel, option0, override_switch_stack.copy(), new_geolayout, graph, new_name) + generate_overrides(fModel, option0_copy, override_switch_stack.copy(), new_geolayout, graph, new_name) ) option_child = TransformNode(JumpNode(True, new_geolayout)) transform_node.children.append(option_child) option_child.parent = transform_node else: - child = generate_overrides(fModel, child, switch_stack.copy(), geolayout, graph, name) - transform_node.children.append(child) - child.parent = transform_node + generate_overrides(fModel, child_copy, switch_stack.copy(), geolayout, graph, name) + transform_node.children.append(child_copy) + child_copy.parent = transform_node return transform_node @@ -1483,16 +1489,17 @@ def processMesh( src_meshes = temp_obj.get("src_meshes", []) - if len(src_meshes): - fMeshes = {} - # find dl - draw, name = None, src_meshes[0]["dl_name"] + def find_draw_by_name(name): for fmesh in fModel.meshes.values(): for fmesh_draw in [fmesh.draw] + fmesh.draw_overrides: if fmesh_draw.name == name: - draw = fmesh_draw - break - node.dlRef = draw + return fmesh_draw + return None + + if len(src_meshes): + fMeshes = {} + name = src_meshes[0]["dl_name"] + node.dlRef = find_draw_by_name(name) node.drawLayer = src_meshes[0]["layer"] processed_inline_geo = True @@ -1520,7 +1527,6 @@ def processMesh( temp_obj["src_meshes"] = [ ({"dl_name": fMesh.draw.name, "layer": drawLayer}) for drawLayer, fMesh in fMeshes.items() ] - node.dlRef = temp_obj["src_meshes"][0]["dl_name"] else: # TODO: Display warning to the user that there is an object that doesn't have polygons print("Object", obj.original_name, "does not have any polygons.") @@ -2479,17 +2485,17 @@ def save_override_draw( f_model: SM64Model, draw: GfxList, prefix: str, - existing_hash, + draw_overrides: dict[OverrideHash, GfxOverride], + existing_hash: OverrideHash, override_mat: bpy.types.Material | None, specific_mats: tuple[bpy.types.Material] | None, override_layer: int | None, override_type: str, fMesh: FMesh, - obj: object, + node: DisplayListNode, draw_layer: int, convert_texture_data: bool, ): - draw_overrides = f_model.draw_overrides.setdefault(fMesh, {}) specific_mats = specific_mats or tuple() f_override_mat = override_tex_dimensions = None new_layer = draw_layer if override_layer is None else override_layer @@ -2505,7 +2511,7 @@ def save_override_draw( name = f"{fMesh.name}{prefix}" new_name = name override_index = -1 - while new_name in [x.name for x, _ in draw_overrides.values()]: + while new_name in [x.gfx.name for x in draw_overrides.values()]: override_index += 1 new_name = f"{name}_{override_index}" name = new_name @@ -2560,7 +2566,7 @@ def save_override_draw( new_mat.material = copy.copy(new_mat.material) # so we can change the tag new_mat.material.tag |= GfxListTag.NoExport f_model.layer_adapted_fmats[material_hash] = new_mat - new_mat |= f_model.layer_adapted_fmats.get(material_hash) + new_mat = new_mat or f_model.layer_adapted_fmats.get(material_hash) # replace the material load if necessary # if we replaced the previous load with the same override, then remove the cmd to optimize DL @@ -2642,10 +2648,10 @@ def save_override_draw( new_hash = tuple(new_hash) if save_mesh_override: - new_dl_override, nodes = draw_overrides.setdefault(new_hash, (new_dl_override, [])) - nodes.append(obj) - return new_dl_override, new_hash - return None, None + override = draw_overrides.setdefault(new_hash, GfxOverride(new_dl_override, [])) + node.DLmicrocode = override.gfx + node.override_hash = new_hash + override.nodes.append(node) def findVertIndexInBuffer(loop, buffer, loopDict):