diff --git a/.github/workflows/black-lint.yml b/.github/workflows/black-lint.yml new file mode 100644 index 000000000..317336173 --- /dev/null +++ b/.github/workflows/black-lint.yml @@ -0,0 +1,13 @@ +# https://black.readthedocs.io/en/stable/integrations/github_actions.html + +name: Black Lint + +on: [push, pull_request] + +jobs: + black-lint: + permissions: {} # Remove all permissions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable diff --git a/README.md b/README.md index 81c30522c..5e21ef434 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Fast64 -This requires Blender 3.2+. +This requires Blender 3.2+. Blender 4.0+ is recommended. Forked from [kurethedead/fast64 on BitBucket](https://bitbucket.org/kurethedead/fast64/src). @@ -22,7 +22,7 @@ Thanks to InTheBeef for LowPolySkinnedMario. ### Discord Server We have a Discord server for support as well as development [here](https://discord.gg/ny7PDcN2x8). -### Table of Contents +### Links to Docs / Guides for Each Game 1. [ Super Mario 64 ](/fast64_internal/sm64/README.md) 2. [ Ocarina Of Time ](/fast64_internal/oot/README.md) @@ -48,6 +48,21 @@ There may occur cases where code is formatted differently based on the code use ### Converting To F3D v5 Materials A new optimized shader graph was introduced to decrease processing times for material creation and exporting. If you have a project that still uses old materials, you may want to convert them to v5. To convert an old project, click the "Recreate F3D Materials As V5" operator near the top of the Fast64 tab in 3D view. This may take a while depending on the number of materials in the project. Then go to the outliner, change the display mode to "Orphan Data" (broken heart icon), then click "Purge" in the top right corner. Purge multiple times until all of the old node groups are gone. +### F3DEX3 Features + +Fast64 supports exporting data for [F3DEX3](https://github.com/HackerN64/F3DEX3), a modded microcode which brings many new features and higher performance. **Preview of these new features in the 3D view is not currently supported**, but they will be exported correctly. To modify the vertex colors of an object with a material which is using packed normals (shading/lighting and vertex colors together), temporarily switch to a vertex colored preset or uncheck `Lighting` in the material geometry mode settings. Once the vertex colors are painted how you want them, re-enable `Lighting` and `Packed Normals`. + +Selecting F3DEX3 as your microcode unlocks a large number of additional presets based on F3DEX3 features. For more information on all these features, see the F3DEX3 readme, GBI, and [these videos](https://www.youtube.com/playlist?list=PLU2OUGtyQi6QswDQOXWIMaYFUcgQ9Psvm). The preset names get very long and are abbreviated as follows: +- `Shaded`: Computes lighting, which normally affects shade color. +- `Vcol`: Vertex colors are enabled in addition to lighting; normally these are multiplied together to become shade color. +- `Ao`: Ambient occlusion. +- `Cel`: Cel shading. If followed by a number, this is the number of cel levels. +- `Ltcol`: Cel shading tints are loaded from light colors. +- `Blend` vs. `Mul` for cel shading: Whether to apply the tint in the blender with linear interpolation, or by multiplication in the CC. The latter sometimes looks better, but does not support vertex colors. +- `Lerp` vs. `Mult` for multitexture (water): Whether the two textures are combined by linear interpolation or multiplication. + +For cel shading, it is recommended to start with one of the cel shading presets, then modify the settings under the `Use Cel Shading` panel. Hover over each UI control for additional information about how that setting works. + ### Updater Fast64 features an updater ([CGCookie/blender-addon-updater](https://github.com/CGCookie/blender-addon-updater)). diff --git a/__init__.py b/__init__.py index 0bcd23fb1..a4ef80e71 100644 --- a/__init__.py +++ b/__init__.py @@ -3,7 +3,7 @@ from . import addon_updater_ops from .fast64_internal.operators import AddWaterBox from .fast64_internal.panels import SM64_Panel -from .fast64_internal.utility import PluginError, raisePluginError, attemptModifierApply, prop_split +from .fast64_internal.utility import PluginError, raisePluginError, attemptModifierApply, prop_split, multilineLabel from .fast64_internal.sm64 import SM64_Properties, sm64_register, sm64_unregister from .fast64_internal.sm64.sm64_geolayout_bone import SM64_BoneProperties @@ -40,7 +40,7 @@ # info about add on bl_info = { "name": "Fast64", - "version": (2, 1, 0), + "version": (2, 2, 0), "author": "kurethedead", "location": "3DView", "description": "Plugin for exporting F3D display lists and other game data related to Nintendo 64 games.", @@ -161,11 +161,16 @@ def draw(self, context): col = self.layout.column() col.scale_y = 1.1 # extra padding prop_split(col, context.scene, "f3d_type", "F3D Microcode") - col.prop(context.scene, "isHWv1") col.prop(context.scene, "saveTextures") col.prop(context.scene, "f3d_simple", text="Simple Material UI") col.prop(context.scene, "generateF3DNodeGraph", text="Generate F3D Node Graph For Materials") col.prop(context.scene, "exportInlineF3D", text="Bleed and Inline Material Exports") + if context.scene.exportInlineF3D: + multilineLabel( + col.box(), + "While inlining, all meshes will be restored to world default values.\n You can configure these values in the world properties tab.", + icon="INFO", + ) col.prop(context.scene, "decomp_compatible", invert_checkbox=True, text="Homebrew Compatibility") col.prop(context.scene, "ignoreTextureRestrictions") if context.scene.ignoreTextureRestrictions: @@ -182,7 +187,7 @@ class Fast64_GlobalObjectPanel(bpy.types.Panel): @classmethod def poll(cls, context): - return context.object is not None and context.object.data is None + return context.object is not None and context.object.type == "EMPTY" def draw(self, context): box = self.layout @@ -344,7 +349,7 @@ def execute(self, context: "bpy.types.Context"): bpy.ops.object.mode_set(mode="OBJECT") upgradeF3DVersionAll( - [obj for obj in bpy.data.objects if isinstance(obj.data, bpy.types.Mesh)], + [obj for obj in bpy.data.objects if obj.type == "MESH"], list(bpy.data.armatures), MatUpdateConvert.version, ) @@ -427,7 +432,6 @@ def gameEditorUpdate(self, context): # register operators and panels here # append menu layout drawing function to an existing window def register(): - if bpy.app.version < (3, 2, 0): msg = "\n".join( ( @@ -472,8 +476,11 @@ def register(): bpy.types.Scene.saveTextures = bpy.props.BoolProperty(name="Save Textures As PNGs (Breaks CI Textures)") bpy.types.Scene.generateF3DNodeGraph = bpy.props.BoolProperty(name="Generate F3D Node Graph", default=True) bpy.types.Scene.exportHiddenGeometry = bpy.props.BoolProperty(name="Export Hidden Geometry", default=True) - bpy.types.Scene.exportInlineF3D = bpy.props.BoolProperty(name="Bleed and Inline Material Exports", \ - description = "Inlines and bleeds materials in a single mesh. GeoLayout + Armature exports bleed over entire model", default=False) + bpy.types.Scene.exportInlineF3D = bpy.props.BoolProperty( + name="Bleed and Inline Material Exports", + description="Inlines and bleeds materials in a single mesh. GeoLayout + Armature exports bleed over entire model", + default=False, + ) bpy.types.Scene.blenderF3DScale = bpy.props.FloatProperty( name="F3D Blender Scale", default=100, update=on_update_render_settings ) diff --git a/fast64_internal/f3d/f3d_bleed.py b/fast64_internal/f3d/f3d_bleed.py index a1667ff9b..b1dbf2057 100644 --- a/fast64_internal/f3d/f3d_bleed.py +++ b/fast64_internal/f3d/f3d_bleed.py @@ -1,9 +1,13 @@ from __future__ import annotations -from dataclasses import dataclass, field + import copy +import bpy + +from dataclasses import dataclass, field from .f3d_gbi import ( GfxTag, + GfxListTag, SPMatrix, SPVertex, SPViewport, @@ -18,7 +22,9 @@ SPBranchLessZraw, SPModifyVertex, SPEndDisplayList, - SPGeometryMode, + SPLoadGeometryMode, + SPSetGeometryMode, + SPClearGeometryMode, SPSetOtherMode, DPLoadBlock, DPLoadTLUTCmd, @@ -35,6 +41,7 @@ FModel, FMesh, FMaterial, + FAreaData, F3D, GfxList, FTriGroup, @@ -43,56 +50,140 @@ class BleedGraphics: - # bleed_state "enums" bleed_start = 1 bleed_in_progress = 2 + # tells bleed logic to check against itself instead of last list + bleed_self_conflict = 3 def __init__(self): self.bled_gfx_lists = dict() + # build world default cmds to compare against, f3d types needed for reset cmd building + self.is_f3d_old = bpy.context.scene.f3d_type == "F3D" + self.is_f3dex2 = "F3DEX2" in bpy.context.scene.f3d_type + self.build_default_geo() + self.build_default_othermodes() + + def build_default_geo(self): + defaults = bpy.context.scene.world.rdp_defaults + + setGeo = SPSetGeometryMode([]) + clearGeo = SPClearGeometryMode([]) + + def place_in_flaglist(flag: bool, enum: str, set_list: SPSetGeometryMode, clear_list: SPClearGeometryMode): + if flag: + set_list.flagList.append(enum) + else: + clear_list.flagList.append(enum) + + place_in_flaglist(defaults.g_zbuffer, "G_ZBUFFER", setGeo, clearGeo) + place_in_flaglist(defaults.g_shade, "G_SHADE", setGeo, clearGeo) + place_in_flaglist(defaults.g_cull_front, "G_CULL_FRONT", setGeo, clearGeo) + place_in_flaglist(defaults.g_cull_back, "G_CULL_BACK", setGeo, clearGeo) + place_in_flaglist(defaults.g_fog, "G_FOG", setGeo, clearGeo) + place_in_flaglist(defaults.g_lighting, "G_LIGHTING", setGeo, clearGeo) + 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": + place_in_flaglist(defaults.g_clipping, "G_CLIPPING", setGeo, clearGeo) + + self.default_load_geo = SPLoadGeometryMode(setGeo.flagList) + self.default_set_geo = setGeo + self.default_clear_geo = clearGeo + + def build_default_othermodes(self): + defaults = bpy.context.scene.world.rdp_defaults + + othermode_H = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - self.is_f3d_old, []) + # if the render mode is set, it will be consider non-default a priori + othermode_L = SPSetOtherMode("G_SETOTHERMODE_L", 0, 3 - self.is_f3d_old, []) + + othermode_L.flagList.append(defaults.g_mdsft_alpha_compare) + othermode_L.flagList.append(defaults.g_mdsft_zsrcsel) + + othermode_H.flagList.append(defaults.g_mdsft_rgb_dither) + othermode_H.flagList.append(defaults.g_mdsft_alpha_dither) + othermode_H.flagList.append(defaults.g_mdsft_combkey) + othermode_H.flagList.append(defaults.g_mdsft_textconv) + othermode_H.flagList.append(defaults.g_mdsft_text_filt) + othermode_H.flagList.append("G_TT_NONE") + othermode_H.flagList.append(defaults.g_mdsft_textlod) + othermode_H.flagList.append(defaults.g_mdsft_textdetail) + othermode_H.flagList.append(defaults.g_mdsft_textpersp) + othermode_H.flagList.append(defaults.g_mdsft_cycletype) + othermode_H.flagList.append(defaults.g_mdsft_pipeline) + + self.default_othermode_L = othermode_L + self.default_othermode_H = othermode_H def bleed_fModel(self, fModel: FModel, fMeshes: dict[FMesh]): - # walk fModel, no order to drawing is observed, so lastMat is not kept track of + # walk fModel, no order to drawing is observed, so last_mat is not kept track of for drawLayer, fMesh in fMeshes.items(): - self.bleed_fmesh(fModel.f3d, fMesh, None, fMesh.draw, fModel.getRenderMode(drawLayer)) + self.bleed_fmesh(fMesh, None, fMesh.draw, fModel.getAllMaterials().items(), fModel.getRenderMode(drawLayer)) self.clear_gfx_lists(fModel) # clear the gfx lists so they don't export def clear_gfx_lists(self, fModel: FModel): - for (fMaterial, texDimensions) in fModel.materials.values(): - fMaterial.material = None - fMaterial.revert = None + for fMaterial, texDimensions in fModel.materials.values(): + fMaterial.material.tag |= GfxListTag.NoExport + if fMaterial.revert: + fMaterial.revert.tag |= GfxListTag.NoExport for fMesh in fModel.meshes.values(): for tri_list in fMesh.triangleGroups: - tri_list.triList = None + tri_list.triList.tag |= GfxListTag.NoExport - def bleed_fmesh(self, f3d: F3D, fMesh: FMesh, lastMat: FMaterial, cmd_list: GfxList, default_render_mode=None): + def bleed_fmesh( + self, + fMesh: FMesh, + last_mat: FMaterial, + cmd_list: GfxList, + fmodel_materials, + default_render_mode: list[str] = None, + ): if bled_mat := self.bled_gfx_lists.get(cmd_list, None): return bled_mat - self.on_bleed_start(f3d, lastMat, cmd_list) + bleed_state = self.bleed_start - for triGroup in fMesh.triangleGroups: + cur_fmat = None + reset_cmd_dict = dict() + bleed_gfx_lists = BleedGfxLists() + fmesh_static_cmds, fmesh_jump_cmds = self.on_bleed_start(cmd_list) + for jump_list_cmd in fmesh_jump_cmds: # bleed mat and tex - bleed_gfx_lists = BleedGfxLists() - if triGroup.fMaterial: - bleed_gfx_lists.bled_mats = self.bleed_mat(triGroup.fMaterial, lastMat, cmd_list, bleed_state) - if not (triGroup.fMaterial.isTexLarge[0] or triGroup.fMaterial.isTexLarge[1]): - bleed_gfx_lists.bled_tex = self.bleed_textures(triGroup.fMaterial, lastMat, cmd_list, bleed_state) + if jump_list_cmd.displayList.tag & GfxListTag.Material: + # update last_mat + if cur_fmat: + last_mat = cur_fmat + _, cur_fmat = find_material_from_jump_cmd(fmodel_materials, jump_list_cmd) + if not cur_fmat: + # make better error msg + print("could not find material used in fmesh draw") + continue + bleed_gfx_lists.bled_mats = self.bleed_mat(cur_fmat, last_mat, bleed_state) + if not (cur_fmat.isTexLarge[0] or cur_fmat.isTexLarge[1]): + bleed_gfx_lists.bled_tex = self.bleed_textures(cur_fmat, last_mat, bleed_state) else: - bleed_gfx_lists.bled_tex = triGroup.fMaterial.texture_DL.commands - lastMat = triGroup.fMaterial + bleed_gfx_lists.bled_tex = cur_fmat.texture_DL.commands # bleed tri group (for large textures) and to remove other unnecessary cmds - self.bleed_tri_group(f3d, triGroup, bleed_gfx_lists, cmd_list, bleed_state) - self.inline_triGroup(f3d, triGroup, bleed_gfx_lists, cmd_list) - self.on_tri_group_bleed_end(f3d, triGroup, lastMat, bleed_gfx_lists) + if jump_list_cmd.displayList.tag & GfxListTag.Geometry: + tri_list = jump_list_cmd.displayList + self.bleed_tri_group(tri_list, cur_fmat, bleed_state) + self.inline_triGroup(tri_list, bleed_gfx_lists, cmd_list, reset_cmd_dict) + self.on_tri_group_bleed_end(tri_list, cur_fmat, bleed_gfx_lists) + # reset bleed gfx lists after inlining + bleed_gfx_lists = BleedGfxLists() + # set bleed state for cmd reverts bleed_state = self.bleed_in_progress - self.on_bleed_end(f3d, lastMat, bleed_gfx_lists, cmd_list, default_render_mode) - return lastMat + + last_mat = cur_fmat + self.on_bleed_end(last_mat, cmd_list, fmesh_static_cmds, reset_cmd_dict, default_render_mode) + return last_mat def build_tmem_dict(self, cmd_list: GfxList): im_buffer = None tmem_dict = dict() - tile_dict = {i:0 for i in range(8)} # an assumption that hopefully never needs correction + 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: im_buffer = cmd @@ -103,15 +194,15 @@ def build_tmem_dict(self, cmd_list: GfxList): tmem_dict[tile_dict[cmd.tile]] = im_buffer continue return tmem_dict - - def bleed_textures(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxList, bleed_state: int): - if lastMat: + + def bleed_textures(self, cur_fmat: FMaterial, last_mat: FMaterial, bleed_state: int): + if last_mat: # bleed cmds if matching tile has duplicate cmds # deep copy breaks on Image objects so I will only copy the levels needed - commands_bled = copy.copy(curMat.texture_DL) - commands_bled.commands = copy.copy(curMat.texture_DL.commands) # copy the commands also + commands_bled = copy.copy(cur_fmat.texture_DL) + commands_bled.commands = copy.copy(cur_fmat.texture_DL.commands) # copy the commands also # eliminate set tex images, but only if there is an overlap of the same image at the same tmem location - last_im_loads = self.build_tmem_dict(lastMat.texture_DL) + last_im_loads = self.build_tmem_dict(last_mat.texture_DL) new_im_loads = self.build_tmem_dict(commands_bled) removable_images = [] for tmem, image in new_im_loads.items(): @@ -120,7 +211,7 @@ def bleed_textures(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxLis # now go through list and cull out loads for the specific cmds # this will be the set tex image, and the loading cmds rm_load = False - for j, cmd in enumerate(curMat.texture_DL.commands): + for j, cmd in enumerate(cur_fmat.texture_DL.commands): # remove set tex explicitly if cmd in removable_images: commands_bled.commands[j] = None @@ -133,49 +224,46 @@ def bleed_textures(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxLis rm_load = None continue # now eval as normal conditionals - for j, cmd in enumerate(curMat.texture_DL.commands): + for j, cmd in enumerate(cur_fmat.texture_DL.commands): if not cmd: - continue # some cmds are None from previous step + continue # some cmds are None from previous step if self.bleed_individual_cmd(commands_bled, cmd, bleed_state): - if cmd in lastMat.texture_DL.commands: + if cmd in last_mat.texture_DL.commands: commands_bled.commands[j] = None # remove Nones from list while None in commands_bled.commands: commands_bled.commands.remove(None) bled_tex = commands_bled else: - bled_tex = curMat.texture_DL + bled_tex = cur_fmat.texture_DL return bled_tex.commands - def bleed_mat(self, curMat: FMaterial, lastMat: FMaterial, cmd_list: GfxList, bleed_state: int): - if lastMat: - gfx = curMat.mat_only_DL + def bleed_mat(self, cur_fmat: FMaterial, last_mat: FMaterial, bleed_state: int): + if last_mat: + gfx = cur_fmat.mat_only_DL # deep copy breaks on Image objects so I will only copy the levels needed commands_bled = copy.copy(gfx) commands_bled.commands = copy.copy(gfx.commands) # copy the commands also - LastList = lastMat.mat_only_DL.commands + last_cmd_list = last_mat.mat_only_DL.commands for j, cmd in enumerate(gfx.commands): - if self.bleed_individual_cmd(commands_bled, cmd, bleed_state): - if cmd in LastList: - commands_bled.commands[j] = None + if self.bleed_individual_cmd(commands_bled, cmd, bleed_state, last_cmd_list): + commands_bled.commands[j] = None # remove Nones from list while None in commands_bled.commands: commands_bled.commands.remove(None) else: - commands_bled = curMat.mat_only_DL + commands_bled = self.bleed_cmd_list(cur_fmat.mat_only_DL, bleed_state) # remove SPEndDisplayList while SPEndDisplayList() in commands_bled.commands: commands_bled.commands.remove(SPEndDisplayList()) return commands_bled.commands - def bleed_tri_group( - self, f3d: F3D, triGroup: FTriGroup, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList, bleed_state: int - ): + def bleed_tri_group(self, tri_list: GfxList, cur_fmat: fMaterial, bleed_state: int): # remove SPEndDisplayList from triGroup - while SPEndDisplayList() in triGroup.triList.commands: - triGroup.triList.commands.remove(SPEndDisplayList()) - if (triGroup.fMaterial.isTexLarge[0] or triGroup.fMaterial.isTexLarge[1]): - triGroup.triList = self.bleed_cmd_list(triGroup.triList, bleed_state) + while SPEndDisplayList() in tri_list.commands: + tri_list.commands.remove(SPEndDisplayList()) + if not cur_fmat or (cur_fmat.isTexLarge[0] or cur_fmat.isTexLarge[1]): + tri_list = self.bleed_cmd_list(tri_list, bleed_state) # this is a little less versatile than comparing by last used material def bleed_cmd_list(self, target_cmd_list: GfxList, bleed_state: int): @@ -183,11 +271,13 @@ def bleed_cmd_list(self, target_cmd_list: GfxList, bleed_state: int): commands_bled = copy.copy(target_cmd_list) # copy the commands commands_bled.commands = copy.copy(target_cmd_list.commands) # copy the commands for j, cmd in enumerate(target_cmd_list.commands): - if not self.bleed_individual_cmd(commands_bled, cmd, bleed_state): + # some cmds you can bleed vs world defaults, others only if they repeat within this gfx list + bleed_cmd_status = self.bleed_individual_cmd(commands_bled, cmd, bleed_state) + if not bleed_cmd_status: continue last_use = usage_dict.get((type(cmd), getattr(cmd, "tile", None)), None) usage_dict[(type(cmd), getattr(cmd, "tile", None))] = cmd - if last_use == cmd: + if last_use == cmd or bleed_cmd_status != self.bleed_self_conflict: commands_bled.commands[j] = None # remove Nones from list while None in commands_bled.commands: @@ -195,46 +285,118 @@ def bleed_cmd_list(self, target_cmd_list: GfxList, bleed_state: int): return commands_bled # Put triGroup bleed gfx in the FMesh.draw object - def inline_triGroup(self, f3d: F3D, triGroup: FTriGroup, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList): + def inline_triGroup( + self, tri_list: GfxList, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList, reset_cmd_dict: dict[GbiMacro] + ): # add material cmd_list.commands.extend(bleed_gfx_lists.bled_mats) # add textures cmd_list.commands.extend(bleed_gfx_lists.bled_tex) # add in triangles - cmd_list.commands.extend(triGroup.triList.commands) + cmd_list.commands.extend(tri_list.commands) # skinned meshes don't draw tris sometimes, use this opportunity to save a sync - tri_cmds = [c for c in triGroup.triList.commands if type(c) == SP1Triangle or type(c) == SP2Triangles] + tri_cmds = [c for c in tri_list.commands if type(c) == SP1Triangle or type(c) == SP2Triangles] if tri_cmds: - bleed_gfx_lists.reset_cmds.add(DPPipeSync) + reset_cmd_dict[DPPipeSync] = DPPipeSync() + [bleed_gfx_lists.add_reset_cmd(cmd, reset_cmd_dict) for cmd in bleed_gfx_lists.bled_mats] - def on_bleed_start(self, f3d: F3D, lastMat: FMaterial, cmd_list: GfxList): + # pre processes cmd_list and removes cmds deemed useless. subclass and override if this causes a game specific issue + def on_bleed_start(self, cmd_list: GfxList): # remove SPDisplayList and SPEndDisplayList from FMesh.draw - iter_cmds = copy.copy(cmd_list.commands) - spDLCmds = (c for c in iter_cmds if type(c) == SPDisplayList) - spEndCmds = (c for c in iter_cmds if type(c) == SPEndDisplayList) - for spDL in spDLCmds: - cmd_list.commands.remove(spDL) - for spEnd in spEndCmds: - cmd_list.commands.remove(spEnd) - - def on_tri_group_bleed_end(self, f3d: F3D, triGroup: FTriGroup, lastMat: FMaterial, bleed_gfx_lists: BleedGfxLists): + # place static cmds after SPDisplay lists aside and append them to the end after inline + sp_dl_start = False + non_jump_dl_cmds = [] + jump_dl_cmds = [] + for j, cmd in enumerate(cmd_list.commands): + if type(cmd) == SPEndDisplayList: + cmd_list.commands[j] = None + # get rid of geo mode cmds those will all be reset via bleed + elif type(cmd) == SPClearGeometryMode and sp_dl_start: + cmd_list.commands[j] = None + elif type(cmd) == SPSetGeometryMode and sp_dl_start: + cmd_list.commands[j] = None + # bleed will handle all syncs after inlining starts, but won't destroy syncs at gfxList start + elif type(cmd) == DPPipeSync and sp_dl_start: + cmd_list.commands[j] = None + elif type(cmd) == SPDisplayList: + cmd_list.commands[j] = None + sp_dl_start = True + jump_dl_cmds.append(cmd) + continue + elif sp_dl_start and cmd is not None: + cmd_list.commands[j] = None + non_jump_dl_cmds.append(cmd) + # remove Nones from list + while None in cmd_list.commands: + cmd_list.commands.remove(None) + return non_jump_dl_cmds, jump_dl_cmds + + def on_tri_group_bleed_end(self, triGroup: FTriGroup, last_mat: FMaterial, bleed_gfx_lists: BleedGfxLists): return def on_bleed_end( - self, f3d: F3D, lastMat: FMaterial, bleed_gfx_lists: BleedGfxLists, cmd_list: GfxList, default_render_mode=None + self, + last_mat: FMaterial, + cmd_list: GfxList, + fmesh_static_cmds: list[GbiMacro], + reset_cmd_dict: dict[GbiMacro], + default_render_mode: list[str] = None, ): - [bleed_gfx_lists.add_reset_cmd(cmd) for cmd in cmd_list.commands] # revert certain cmds for extra safety - reset_cmds = [reset_cmd for cmd in bleed_gfx_lists.reset_cmds if (reset_cmd := bleed_gfx_lists.create_reset_cmd(cmd, default_render_mode))] - # if pipe sync in rest list, make sure it is the first cmd + reset_cmds = self.create_reset_cmds(reset_cmd_dict, default_render_mode) + # if pipe sync in reset list, make sure it is the first cmd if DPPipeSync in reset_cmds: reset_cmds.remove(DPPipeSync) reset_cmds.insert(0, DPPipeSync) cmd_list.commands.extend(reset_cmds) + cmd_list.commands.extend(fmesh_static_cmds) # this is troublesome cmd_list.commands.append(SPEndDisplayList()) - self.bled_gfx_lists[cmd_list] = lastMat + self.bled_gfx_lists[cmd_list] = last_mat + + def create_reset_cmds(self, reset_cmd_dict: dict[GbiMacro], default_render_mode: list[str]): + reset_cmds = [] + for cmd_type, cmd_use in reset_cmd_dict.items(): + if cmd_type == DPPipeSync: + reset_cmds.append(DPPipeSync()) + + # generally either loadgeo, or a combo of set/clear is used based on microcode selected + # if you are in f3d, any selection different from the default will add a set/clear + if cmd_type == SPLoadGeometryMode and cmd_use != self.default_load_geo: + reset_cmds.append(self.default_load_geo) + + elif cmd_type == SPSetGeometryMode and cmd_use != self.default_set_geo: + reset_cmds.append(self.default_set_geo) + + elif cmd_type == SPClearGeometryMode and cmd_use != self.default_clear_geo: + reset_cmds.append(self.default_clear_geo) - def bleed_individual_cmd(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): + elif cmd_type == DPSetTextureLUT: + if cmd_use.mode != "G_TT_NONE": + reset_cmds.append(cmd_type("G_TT_NONE")) + + elif cmd_type == "G_SETOTHERMODE_H": + if cmd_use != self.default_othermode_H: + reset_cmds.append(self.default_othermode_H) + + # render mode takes up most bits of the lower half, so seeing high bit usage is enough to determine render mode was used + elif cmd_type == DPSetRenderMode or (cmd_type == "G_SETOTHERMODE_L" and cmd_use.length >= 31): + if default_render_mode: + reset_cmds.append( + SPSetOtherMode( + "G_SETOTHERMODE_L", + 0, + 32 - self.is_f3d_old, + [*self.default_othermode_L.flagList, *default_render_mode], + ) + ) + + elif cmd_type == "G_SETOTHERMODE_L": + if cmd_use != self.default_othermode_L: + reset_cmds.append(self.default_othermode_L) + return reset_cmds + + def bleed_individual_cmd(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + # never bleed these cmds if type(cmd) in [ SPMatrix, SPVertex, @@ -258,50 +420,81 @@ def bleed_individual_cmd(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: in ]: return False + # if no last list then calling func will own behavior of bleeding + if not last_cmd_list: + return self.bleed_self_conflict + + # apply specific logic to these cmds, see functions below, otherwise default behavior is to bleed if cmd is in the last list bleed_func = getattr(self, (f"bleed_{type(cmd).__name__}"), None) if bleed_func: - return bleed_func(cmd_list, cmd, bleed_state) + return bleed_func(cmd_list, cmd, bleed_state, last_cmd_list) + else: + return cmd in last_cmd_list - return True + # bleed these cmds only if it is the second call and cmd was in the last use list, or if they match world defaults and it is the first call + def bleed_SPLoadGeometryMode( + self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None + ): + if bleed_state != self.bleed_start: + return cmd in last_cmd_list + else: + return cmd == self.default_load_geo - def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): + def bleed_SPSetGeometryMode( + self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None + ): if bleed_state != self.bleed_start: - return True + return cmd in last_cmd_list + else: + return cmd == self.default_set_geo - def bleed_DPSetTileSize(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - return cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1 + def bleed_SPClearGeometryMode( + self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None + ): + if bleed_state != self.bleed_start: + return cmd in last_cmd_list + else: + return cmd == self.default_clear_geo - def bleed_DPSetTile(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - # should only be removed if there is no other set tile in this list - # that is on the same tile, and has different settings - for parse_cmd in cmd_list.commands: - if type(parse_cmd) is DPSetTile and parse_cmd is not cmd: - if cmd != self: - return False - return True - - def bleed_DPTileSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - # will be bled if there are two of these syncs, at most only one tilesync - # is ever required after rendering triangles, and before subsequent cmds rdp attr changes - for parse_cmd in cmd_list.commands: - if type(parse_cmd) is DPTileSync and parse_cmd is not cmd: - return True - return False + def bleed_SPSetOtherMode(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + if bleed_state != self.bleed_start: + return cmd in last_cmd_list + else: + if cmd.cmd == "G_SETOTHERMODE_H": + return cmd == self.default_othermode_H + else: + return cmd == self.default_othermode_L - def bleed_DPPipeSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - # will be bled if there are two of these syncs, at most only one pipesync - # is ever required after rendering triangles, and before subsequent cmds rdp attr changes - for parse_cmd in cmd_list.commands: - if type(parse_cmd) is DPPipeSync and parse_cmd is not cmd: - return True - return False + # bleed if there are no tags to scroll and cmd was in last list + def bleed_DPSetTileSize(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + return cmd.tags != GfxTag.TileScroll0 and cmd.tags != GfxTag.TileScroll1 and cmd in last_cmd_list + + # At most, only one sync is needed after drawing tris. The f3d writer should + # already have placed the appropriate sync type required. If a second sync is + # detected between drawing cmds, then remove that sync. Remove the latest sync + # not the first seen sync. + def bleed_DPTileSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) + + def bleed_DPPipeSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) - def bleed_DPLoadSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): - # will be bled if there are two of these syncs, at most only one pipesync - # is ever required after rendering triangles, and before subsequent cmds rdp attr changes + def bleed_DPLoadSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, last_cmd_list: GfxList = None): + return self.bleed_between_tris(cmd_list, cmd, bleed_state, [DPLoadSync, DPPipeSync, DPTileSync]) + + def bleed_between_tris(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int, conflict_cmds: list[GbiMacro]): + tri_buffered = False for parse_cmd in cmd_list.commands: - if type(parse_cmd) is DPLoadSync and parse_cmd is not cmd: - return True + if parse_cmd is cmd: + return tri_buffered + if type(parse_cmd) in [SP2Triangles, SP1Triangle, SPLine3D, SPLineW3D]: + tri_buffered = False + continue + if type(parse_cmd) in conflict_cmds: + if not tri_buffered: + tri_buffered = True + else: + return True return False @@ -310,20 +503,7 @@ def bleed_DPLoadSync(self, cmd_list: GfxList, cmd: GbiMacro, bleed_state: int): class BleedGfxLists: bled_mats: GfxList = field(default_factory=list) bled_tex: GfxList = field(default_factory=list) - reset_cmds: set[GbiMacro] = field(default_factory=set) # set of cmds to reset - - def __post_init__(self): - # this cmds always reset - self.reset_cmds.add(DPSetCycleType) - def create_reset_cmd(self, cmd, default_render_mode): - if cmd == DPSetRenderMode: - if not default_render_mode: - return - else: - return cmd(default_render_mode, None) - return cmd(*self.reset_command_dict.get(cmd, [])) - @property def reset_command_dict(self): return { @@ -333,9 +513,34 @@ def reset_command_dict(self): DPSetRenderMode: (None, None), } - def add_reset_cmd(self, cmd: GbiMacro): - if type(cmd) in self.reset_command_dict.keys(): - self.reset_cmds.add(type(cmd)) - if type(cmd) is SPSetOtherMode: - if any(["G_RM" in flag for flag in cmd.flagList]): - self.reset_cmds.add(DPSetRenderMode) + def add_reset_cmd(self, cmd: GbiMacro, reset_cmd_dict: dict[GbiMacro]): + reset_cmd_list = ( + SPLoadGeometryMode, + SPSetGeometryMode, + SPClearGeometryMode, + DPSetTextureLUT, + DPSetRenderMode, + ) + # separate other mode H and othermode L + if type(cmd) == SPSetOtherMode: + reset_cmd_dict[cmd.cmd] = cmd + + if type(cmd) in reset_cmd_list: + reset_cmd_dict[type(cmd)] = cmd + + +# helper function used for sm64 +def find_material_from_jump_cmd( + material_list: tuple[tuple[bpy.types.Material, str], tuple[FMaterial, tuple[int, int]]], + dl_jump: SPDisplayList, +): + if dl_jump.displayList.tag & GfxListTag.Geometry: + return None, None + for mat in material_list: + fmaterial = mat[1][0] + bpy_material = mat[0][0] + if dl_jump.displayList.tag == GfxListTag.MaterialRevert and fmaterial.revert == dl_jump.displayList: + return bpy_material, fmaterial + elif fmaterial.material == dl_jump.displayList: + return bpy_material, fmaterial + return None, None diff --git a/fast64_internal/f3d/f3d_constants.py b/fast64_internal/f3d/f3d_constants.py deleted file mode 100644 index f7bb049ec..000000000 --- a/fast64_internal/f3d/f3d_constants.py +++ /dev/null @@ -1,129 +0,0 @@ -import mathutils - -# Equivalent of euler(-90, -90, 0) -blenderToSM64Rotation = mathutils.Matrix( - [[0, 1, 0], - [0, 0, 1], - [1, 0, 0]] -) - -blenderToSM64RotationLevel = mathutils.Matrix( - [[1, 0, 0], - [0, 0, 1], - [0,-1, 0]] -) - -colorFormats = { - 'RGBA' : 0, - 'YUV' : 1, - 'CI' : 2, - 'IA' : 3, - 'I' : 4 -} - -pixelBitSizes = { - '4bit' : 0, - '8bit' : 1, - '16bit' : 2, - '32bit' : 3 -} - -# Color combinations -# In SM64, environment alpha controls Mario's alpha. - -# 2 cycle with specular lighting stored in primitive -S_PHONG_TEX = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'ENVIRONMENT', '0', - 'PRIMITIVE', 'COMBINED', 'TEXEL0', 'COMBINED', - '0', '0', '0', 'COMBINED'] - -# MULTIPLY TWO TEXTURES -S_MULTIPLY = [ - 'TEXEL0', '0', 'TEXEL1', '0', - '0', '0', '0', 'ENVIRONMENT', - 'TEXEL0', '0', 'TEXEL1', '0', - '0', '0', '0', 'ENVIRONMENT' -] - -S_PRIM_TRANSPARENT_SHADE = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'PRIMITIVE', '0', - 'TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'PRIMITIVE', '0'] - -# REGULAR SHADED TEXTURE -S_SHADED_TEX = ['TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'ENVIRONMENT', - 'TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'ENVIRONMENT'] - -# VERTEX COLOR -S_VERTEX_COLORED_TEX = ['TEXEL0', '0', 'SHADE', '0', - 'ENVIRONMENT', '0', 'SHADE', '0', - 'TEXEL0', '0', 'SHADE', '0', - 'ENVIRONMENT', '0', 'SHADE', '0'] - -S_VERTEX_COLORED_TEX_NO_VERT_ALPHA = ['TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'TEXEL0', - 'TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'TEXEL0',] - -S_VERTEX_COLORED_TEX_TRANSPARENT = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'SHADE', '0'] - -S_SHADED_TEX_CUTOUT = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'ENVIRONMENT', '0', - 'TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'ENVIRONMENT', '0'] - -# UNLIT TEXTURE, USED FOR METAL MARIO -S_UNLIT_TEX = ['0', '0', '0', 'TEXEL0', - '0', '0', '0', 'ENVIRONMENT', - '0', '0', '0', 'TEXEL0', - '0', '0', '0', 'ENVIRONMENT'] - -S_UNLIT_TEX_CUTOUT = ['0', '0', '0', 'TEXEL0', - 'TEXEL0', '0', 'ENVIRONMENT', '0', - '0', '0', '0', 'TEXEL0', - 'TEXEL0', '0', 'ENVIRONMENT', '0'] - -# SM64 CUSTOM IMPORTER CC -S_SHADED_TEX_NOALPHA = ['TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'SHADE', - 'TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'SHADE'] - -# SM64 BODY CC -S_SHADED_SOLID = ['0', '0', '0', 'SHADE', - '0', '0', '0', 'ENVIRONMENT', - '0', '0', '0', 'SHADE', - '0', '0', '0', 'ENVIRONMENT'] - -# SM64 BODY TEXTURES (FACE, SIDEBURNS, ETC.) -S_UNLIT_DECAL_ON_SHADED_SOLID = ['TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', - '0', '0', '0', 'ENVIRONMENT', - 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', - '0', '0', '0', 'ENVIRONMENT'] - -# SHADED RANDOM NOISE -S_SHADED_NOISE = ['NOISE', '0', 'SHADE', '0', - '0', '0', '0', 'ENVIRONMENT', - 'NOISE', '0', 'SHADE', '0', - '0', '0', '0', 'ENVIRONMENT'] - -# FOG SHADED TEXTURES -S_FOG_SHADED_TEX = ['TEXEL0', '0', 'SHADE', '0', - '0', '0', '0', 'ENVIRONMENT', - '0', '0', '0', 'COMBINED', - '0', '0', '0', 'COMBINED'] - -S_FOG_SHADED_TEX_CUTOUT = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'ENVIRONMENT', '0', - '0', '0', '0', 'COMBINED', - '0', '0', '0', 'COMBINED'] - -S_FOG_PRIM_TRANSPARENT_SHADE = ['TEXEL0', '0', 'SHADE', '0', - 'TEXEL0', '0', 'PRIMITIVE', '0', - '0', '0', '0', 'COMBINED', - '0', '0', '0', 'COMBINED'] \ No newline at end of file diff --git a/fast64_internal/f3d/f3d_enums.py b/fast64_internal/f3d/f3d_enums.py index d546b49e2..452408c52 100644 --- a/fast64_internal/f3d/f3d_enums.py +++ b/fast64_internal/f3d/f3d_enums.py @@ -171,7 +171,11 @@ enumTextConv = [ ("G_TC_CONV", "Convert", "Convert YUV to RGB"), - ("G_TC_FILTCONV", "Filter And Convert", "Applies chosen filter on cycle 1 and converts YUB to RGB in the second cycle"), + ( + "G_TC_FILTCONV", + "Filter And Convert", + "Applies chosen filter on cycle 1 and converts YUB to RGB in the second cycle", + ), ("G_TC_FILT", "Filter", "Applies chosen filter on textures with no color conversion"), ] @@ -189,11 +193,19 @@ enumTextLOD = [ ("G_TL_TILE", "Tile", "Shows selected color combiner tiles"), - ("G_TL_LOD", "LoD", "Enables LoD calculations, LoD tile is base tile + clamp(log2(texel/pixel)), remainder of log2(texel/pixel) ratio gets stored to LoD Fraction in the color combiner"), + ( + "G_TL_LOD", + "LoD", + "Enables LoD calculations, LoD tile is base tile + clamp(log2(texel/pixel)), remainder of log2(texel/pixel) ratio gets stored to LoD Fraction in the color combiner", + ), ] enumTextDetail = [ - ("G_TD_CLAMP", "Clamp", "Shows base tile for texel0 and texel 1 when magnifying (>1 texel/pixel), else shows LoD tiles"), + ( + "G_TD_CLAMP", + "Clamp", + "Shows base tile for texel0 and texel 1 when magnifying (>1 texel/pixel), else shows LoD tiles", + ), ("G_TD_SHARPEN", "Sharpen", "Sharpens pixel colors when magnifying (<1 texel/pixel), always shows LoD tiles"), ("G_TD_DETAIL", "Detail", "Shows base tile when magnifying (<1 texel/pixel), else shows LoD tiles + 1"), ] @@ -213,8 +225,16 @@ enumColorDither = [("G_CD_DISABLE", "Disable", "Disable"), ("G_CD_ENABLE", "Enable", "Enable")] enumPipelineMode = [ - ("G_PM_1PRIMITIVE", "1 Primitive", "Adds in pipe sync after every tri draw. Adds significant amounts of lag. Only use in vanilla SM64 hacking projects"), - ("G_PM_NPRIMITIVE", "N Primitive", "No additional syncs are added after tri draws. Default option for every game but vanilla SM64"), + ( + "G_PM_1PRIMITIVE", + "1 Primitive", + "Adds in pipe sync after every tri draw. Adds significant amounts of lag. Only use in vanilla SM64 hacking projects", + ), + ( + "G_PM_NPRIMITIVE", + "N Primitive", + "No additional syncs are added after tri draws. Default option for every game but vanilla SM64", + ), ] enumAlphaCompare = [ @@ -248,7 +268,7 @@ "Input (CC/Blender)", "First cycle: Color Combiner RGB, Second cycle: Blender numerator from first cycle", ), - ("G_BL_CLR_MEM", "Framebuffer Color", "Framebuffer Color (Memory)"), + ("G_BL_CLR_MEM", "Framebuffer Color", "Framebuffer (Memory) Color"), ("G_BL_CLR_BL", "Blend Color", "Blend Color Register"), ("G_BL_CLR_FOG", "Fog Color", "Fog Color Register"), ] @@ -256,13 +276,13 @@ enumBlendAlpha = [ ("G_BL_A_IN", "Color Combiner Alpha", "Color Combiner Alpha"), ("G_BL_A_FOG", "Fog Alpha", "Fog Color Register Alpha"), - ("G_BL_A_SHADE", "Shade Alpha", "Stepped Shade Alpha"), + ("G_BL_A_SHADE", "Shade Alpha", "Stepped Shade Alpha from RSP, often fog"), ("G_BL_0", "0", "0"), ] enumBlendMix = [ ("G_BL_1MA", "1 - A", "1 - A, where A is selected above"), - ("G_BL_A_MEM", "Framebuffer Alpha", "Framebuffer (Memory) Alpha"), + ("G_BL_A_MEM", "Framebuffer Alpha", "Framebuffer (Memory) Alpha (Coverage)"), ("G_BL_1", "1", "1"), ("G_BL_0", "0", "0"), ] @@ -357,15 +377,58 @@ } enumF3D = [ - ("F3D", "F3D", "F3D"), - ("F3DEX/LX", "F3DEX/LX", "F3DEX/LX"), + ("F3D", "F3D", "Original microcode used in SM64"), + ("F3DEX/LX", "F3DEX/LX", "F3DEX version 1"), ("F3DLX.Rej", "F3DLX.Rej", "F3DLX.Rej"), ("F3DLP.Rej", "F3DLP.Rej", "F3DLP.Rej"), - ("F3DEX2/LX2", "F3DEX2/LX2", "F3DEX2/LX2"), - ("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej"), + ("F3DEX2/LX2", "F3DEX2/LX2/ZEX", "Family of microcodes used in later N64 games including OoT and MM"), + ("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "Variant of F3DEX2 family using vertex rejection instead of clipping"), + ("F3DEX3", "F3DEX3", "Custom microcode by Sauraen"), ] enumLargeEdges = [ ("Clamp", "Clamp", "Clamp outside image bounds"), ("Wrap", "Wrap", "Wrap outside image bounds (more expensive)"), ] + +enumCelThreshMode = [ + ( + "Lighter", + "Lighter", + "This cel level is drawn when the lighting level per-pixel is LIGHTER than (>=) the threshold", + ), + ("Darker", "Darker", "This cel level is drawn when the lighting level per-pixel is DARKER than (<) the threshold"), +] + +enumCelTintPipeline = [ + ( + "CC", + "CC (tint in Prim Color)", + "Cel shading puts tint color in Prim Color and tint level in Prim Alpha. Set up CC color to LERP between source color and tint color based on tint level, or multiply source color by tint color. Source may be Tex 0 or Env Color", + ), + ( + "Blender", + "Blender (tint in Fog Color)", + "Cel shading puts tint color in Fog Color and tint level in Fog Alpha. Set up blender to LERP between CC output and tint color based on tint level. Then set CC to Tex 0 * shade color (vertex colors)", + ), +] + +enumCelCutoutSource = [ + ( + "TEXEL0", + "Texture 0", + "Cel shading material has binary alpha cutout from Texture 0 alpha. Does not work with I4 or I8 formats", + ), + ( + "TEXEL1", + "Texture 1", + "Cel shading material has binary alpha cutout from Texture 1 alpha. Does not work with I4 or I8 formats", + ), + ("ENVIRONMENT", "None / Env Alpha", "Make sure your material writes env color, and set env alpha to opaque (255)"), +] + +enumCelTintType = [ + ("Fixed", "Fixed", "Fixed tint color and level stored directly in DL"), + ("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"), +] diff --git a/fast64_internal/f3d/f3d_gbi.py b/fast64_internal/f3d/f3d_gbi.py index 55301f7d7..04a476bf0 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -23,11 +23,16 @@ class DLFormat(enum.Enum): Dynamic = 2 -class GfxListTag(enum.Enum): +class GfxListTag(enum.IntFlag): Geometry = 1 Material = 2 - MaterialRevert = 3 - Draw = 3 + MaterialRevert = 4 + Draw = 4 + NoExport = 16 + + @property + def Export(self): + return not self & GfxListTag.NoExport class GfxTag(enum.Flag): @@ -53,16 +58,8 @@ class GfxMatWriteMethod(enum.Enum): ("PROCEDURAL", "Procedural", "Procedural"), ] -lightIndex = { - "LIGHT_1": 1, - "LIGHT_2": 2, - "LIGHT_3": 3, - "LIGHT_4": 4, - "LIGHT_5": 5, - "LIGHT_6": 6, - "LIGHT_7": 7, - "LIGHT_8": 8, -} +# 1-8 for F3DEX2 etc., 1-10 for F3DEX3 +lightIndex = {f"LIGHT_{n}": n for n in range(1, 11)} # tuple of max buffer size, max load count. vertexBufferSize = { @@ -72,6 +69,7 @@ class GfxMatWriteMethod(enum.Enum): "F3DLP.Rej": (80, 32), "F3DEX2/LX2": (32, 32), "F3DEX2.Rej/LX2.Rej": (64, 64), + "F3DEX3": (56, 56), } drawLayerRenderMode = { @@ -123,39 +121,41 @@ class GfxMatWriteMethod(enum.Enum): } +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"} + + +def isUcodeF3DEX3(F3D_VER: str) -> bool: + return F3D_VER == "F3DEX3" + + class F3D: """NOTE: do not initialize this class manually! use get_F3D_GBI so that the single instance is cached from the microcode type.""" - def __init__(self, F3D_VER, _HW_VERSION_1): - if F3D_VER == "F3DEX2.Rej/LX2.Rej" or F3D_VER == "F3DEX2/LX2": - self.F3DEX_GBI = False - self.F3DEX_GBI_2 = True - self.F3DLP_GBI = False - elif F3D_VER == "F3DLP.Rej" or F3D_VER == "F3DLX.Rej" or F3D_VER == "F3DEX/LX": - self.F3DEX_GBI = True - self.F3DEX_GBI_2 = False - self.F3DLP_GBI = True - elif F3D_VER == "F3D": - self.F3DEX_GBI = False - self.F3DEX_GBI_2 = False - self.F3DLP_GBI = False - else: - raise PluginError("Invalid F3D version " + F3D_VER + ".") + def __init__(self, F3D_VER): + self.F3D_VER = F3D_VER + F3DEX_GBI = self.F3DEX_GBI = isUcodeF3DEX1(F3D_VER) + F3DEX_GBI_2 = self.F3DEX_GBI_2 = isUcodeF3DEX2(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) + + # F3DEX2 is F3DEX1 and F3DEX3 is F3DEX2, but F3DEX3 is not F3DEX1 + if F3DEX_GBI_2: + F3DEX_GBI = self.F3DEX_GBI = True + elif F3DEX_GBI_3: + F3DEX_GBI_2 = self.F3DEX_GBI_2 = True self.vert_buffer_size = vertexBufferSize[F3D_VER][0] self.vert_load_size = vertexBufferSize[F3D_VER][1] - - F3DEX_GBI = self.F3DEX_GBI - F3DEX_GBI_2 = self.F3DEX_GBI_2 - F3DLP_GBI = self.F3DLP_GBI - self._HW_VERSION_1 = _HW_VERSION_1 - self.F3D_VER = F3D_VER - # self._LANGUAGE_ASSEMBLY = _LANGUAGE_ASSEMBLY + self.G_MAX_LIGHTS = 9 if F3DEX_GBI_3 else 7 + self.G_INPUT_BUFFER_CMDS = 21 if F3DEX_GBI_2: - self.F3DEX_GBI = True - F3DEX_GBI = True - self.G_NOOP = 0x00 self.G_RDPHALF_2 = 0xF1 self.G_SETOTHERMODE_H = 0xE3 @@ -172,9 +172,6 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_POPMTX = 0xD8 self.G_TEXTURE = 0xD7 self.G_DMA_IO = 0xD6 - self.G_SPECIAL_1 = 0xD5 - self.G_SPECIAL_2 = 0xD4 - self.G_SPECIAL_3 = 0xD3 self.G_VTX = 0x01 self.G_MODIFYVTX = 0x02 @@ -183,7 +180,16 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_TRI1 = 0x05 self.G_TRI2 = 0x06 self.G_QUAD = 0x07 - self.G_LINE3D = 0x08 + + if F3DEX_GBI_3: + self.G_TRISTRIP = 0x08 + self.G_TRIFAN = 0x09 + self.G_LIGHTTORDP = 0x0A + else: + self.G_SPECIAL_1 = 0xD5 + self.G_SPECIAL_2 = 0xD4 + self.G_SPECIAL_3 = 0xD3 + self.G_LINE3D = 0x08 else: # DMA commands @@ -274,13 +280,9 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_RDP_TRI_TXTR_MASK = 0x02 self.G_RDP_TRI_ZBUFF_MASK = 0x01 - self.BOWTIE_VAL = 0 - # gets added to RDP command, in order to test for addres fixup self.G_RDP_ADDR_FIXUP = 3 # |RDP cmds| <= this, do addr fixup - # if _LANGUAGE_ASSEMBLY: self.G_RDP_TEXRECT_CHECK = (-1 * self.G_TEXRECTFLIP) & 0xFF - # endif self.G_DMACMDSIZ = 128 self.G_IMMCMDSIZ = 64 @@ -336,7 +338,45 @@ def __init__(self, F3D_VER, _HW_VERSION_1): else: self.G_CLIPPING = 0x00000000 - # if _LANGUAGE_ASSEMBLY: + if F3DEX_GBI_3: + self.G_AMBOCCLUSION = 0x00000040 + self.G_ATTROFFSET_Z_ENABLE = 0x00000080 + self.G_ATTROFFSET_ST_ENABLE = 0x00000100 + self.G_PACKED_NORMALS = 0x00000800 + self.G_LIGHTTOALPHA = 0x00001000 + 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", + "G_TEXTURE_ENABLE", + "G_SHADE", + "G_CULL_FRONT", + "G_CULL_BACK", + "G_CULL_BOTH", + "G_FOG", + "G_LIGHTING", + "G_TEXTURE_GEN", + "G_TEXTURE_GEN_LINEAR", + "G_LOD", + "G_SHADING_SMOOTH", + "G_LIGHTING_POSITIONAL", + "G_CLIPPING", + } + if F3DEX_GBI_3: + self.allGeomModeFlags |= { + "G_AMBOCCLUSION", + "G_ATTROFFSET_Z_ENABLE", + "G_ATTROFFSET_ST_ENABLE", + "G_PACKED_NORMALS", + "G_LIGHTTOALPHA", + "G_LIGHTING_SPECULAR", + "G_FRESNEL_COLOR", + "G_FRESNEL_ALPHA", + } + self.G_FOG_H = self.G_FOG / 0x10000 self.G_LIGHTING_H = self.G_LIGHTING / 0x10000 self.G_TEXTURE_GEN_H = self.G_TEXTURE_GEN / 0x10000 @@ -344,10 +384,8 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_LOD_H = self.G_LOD / 0x10000 # NOT IMPLEMENTED if F3DEX_GBI or F3DLP_GBI: self.G_CLIPPING_H = self.G_CLIPPING / 0x10000 - # endif # Need these defined for Sprite Microcode - # if _LANGUAGE_ASSEMBLY: self.G_TX_LOADTILE = 7 self.G_TX_RENDERTILE = 0 @@ -357,7 +395,6 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_TX_CLAMP = 0x2 self.G_TX_NOMASK = 0 self.G_TX_NOLOD = 0 - # endif self.G_TX_VARS = { "G_TX_NOMIRROR": 0, @@ -692,13 +729,8 @@ def __init__(self, F3D_VER, _HW_VERSION_1): self.G_CD_MAGICSQ = 0 << G_MDSFT_RGBDITHER self.G_CD_BAYER = 1 << G_MDSFT_RGBDITHER self.G_CD_NOISE = 2 << G_MDSFT_RGBDITHER - - if not _HW_VERSION_1: - self.G_CD_DISABLE = 3 << G_MDSFT_RGBDITHER - self.G_CD_ENABLE = self.G_CD_NOISE # HW 1.0 compatibility mode - else: - self.G_CD_ENABLE = 1 << G_MDSFT_COLORDITHER - self.G_CD_DISABLE = 0 << G_MDSFT_COLORDITHER + self.G_CD_DISABLE = 3 << G_MDSFT_RGBDITHER + self.G_CD_ENABLE = self.G_CD_NOISE # G_SETOTHERMODE_H gSetAlphaDither self.G_AD_PATTERN = 0 << G_MDSFT_ALPHADITHER @@ -1396,6 +1428,15 @@ def RM_OPA_CI(clk): self.G_DL_PUSH = 0x00 self.G_DL_NOPUSH = 0x01 + if F3DEX_GBI_3: + self.G_NORMALS_MODE_FAST = 0x00 + self.G_NORMALS_MODE_AUTO = 0x01 + self.G_NORMALS_MODE_MANUAL = 0x02 + + self.G_ALPHA_COMPARE_CULL_DISABLE = 0 + self.G_ALPHA_COMPARE_CULL_BELOW = 1 + self.G_ALPHA_COMPARE_CULL_ABOVE = -1 + # Some structs here self.G_MAXZ = 0x03FF # 10 bits of integer screen-Z precision @@ -1416,18 +1457,19 @@ def RM_OPA_CI(clk): self.G_MV_PMTX = 6 self.G_MV_VIEWPORT = 8 self.G_MV_LIGHT = 10 - self.G_MV_POINT = 12 - self.G_MV_MATRIX = 14 # NOTE: this is in moveword table - self.G_MVO_LOOKATX = 0 * 24 - self.G_MVO_LOOKATY = 1 * 24 - self.G_MVO_L0 = 2 * 24 - self.G_MVO_L1 = 3 * 24 - self.G_MVO_L2 = 4 * 24 - self.G_MVO_L3 = 5 * 24 - self.G_MVO_L4 = 6 * 24 - self.G_MVO_L5 = 7 * 24 - self.G_MVO_L6 = 8 * 24 - self.G_MVO_L7 = 9 * 24 + if not F3DEX_GBI_3: + self.G_MV_POINT = 12 + self.G_MV_MATRIX = 14 # NOTE: this is in moveword table + self.G_MVO_LOOKATX = 0 * 24 + self.G_MVO_LOOKATY = 1 * 24 + self.G_MVO_L0 = 2 * 24 + self.G_MVO_L1 = 3 * 24 + self.G_MVO_L2 = 4 * 24 + self.G_MVO_L3 = 5 * 24 + self.G_MVO_L4 = 6 * 24 + self.G_MVO_L5 = 7 * 24 + self.G_MVO_L6 = 8 * 24 + self.G_MVO_L7 = 9 * 24 else: self.G_MV_VIEWPORT = 0x80 self.G_MV_LOOKATY = 0x82 @@ -1454,17 +1496,25 @@ def RM_OPA_CI(clk): an immediate word will be stored. """ - self.G_MW_MATRIX = 0x00 # NOTE: also used by movemem + if F3DEX_GBI_3: + self.G_MW_FX = 0x00 + else: + self.G_MW_MATRIX = 0x00 # NOTE: also used by movemem self.G_MW_NUMLIGHT = 0x02 - self.G_MW_CLIP = 0x04 + if not F3DEX_GBI_3: + self.G_MW_CLIP = 0x04 self.G_MW_SEGMENT = 0x06 self.G_MW_FOG = 0x08 self.G_MW_LIGHTCOL = 0x0A - if F3DEX_GBI_2: - self.G_MW_FORCEMTX = 0x0C - else: - self.G_MW_POINTS = 0x0C - self.G_MW_PERSPNORM = 0x0E + if not F3DEX_GBI_3: + if F3DEX_GBI_2: + self.G_MW_FORCEMTX = 0x0C + else: + self.G_MW_POINTS = 0x0C + self.G_MW_PERSPNORM = 0x0E + + if F3DEX_GBI_3: + self.G_MW_HALFWORD_FLAG = 0x8000 # These are offsets from the address in the dmem table @@ -1493,7 +1543,26 @@ def RM_OPA_CI(clk): self.G_MWO_aLIGHT_1 = 0x00 self.G_MWO_bLIGHT_1 = 0x04 - if F3DEX_GBI_2: + if F3DEX_GBI_3: + self.G_MWO_aLIGHT_2 = 0x10 + self.G_MWO_bLIGHT_2 = 0x14 + self.G_MWO_aLIGHT_3 = 0x20 + self.G_MWO_bLIGHT_3 = 0x24 + self.G_MWO_aLIGHT_4 = 0x30 + self.G_MWO_bLIGHT_4 = 0x34 + self.G_MWO_aLIGHT_5 = 0x40 + self.G_MWO_bLIGHT_5 = 0x44 + self.G_MWO_aLIGHT_6 = 0x50 + self.G_MWO_bLIGHT_6 = 0x54 + self.G_MWO_aLIGHT_7 = 0x60 + self.G_MWO_bLIGHT_7 = 0x64 + self.G_MWO_aLIGHT_8 = 0x70 + self.G_MWO_bLIGHT_8 = 0x74 + self.G_MWO_aLIGHT_9 = 0x80 + self.G_MWO_bLIGHT_9 = 0x84 + self.G_MWO_aLIGHT_10 = 0x90 + self.G_MWO_bLIGHT_10 = 0x94 + elif F3DEX_GBI_2: self.G_MWO_aLIGHT_2 = 0x18 self.G_MWO_bLIGHT_2 = 0x1C self.G_MWO_aLIGHT_3 = 0x30 @@ -1524,27 +1593,42 @@ def RM_OPA_CI(clk): self.G_MWO_aLIGHT_8 = 0xE0 self.G_MWO_bLIGHT_8 = 0xE4 - self.G_MWO_MATRIX_XX_XY_I = 0x00 - self.G_MWO_MATRIX_XZ_XW_I = 0x04 - self.G_MWO_MATRIX_YX_YY_I = 0x08 - self.G_MWO_MATRIX_YZ_YW_I = 0x0C - self.G_MWO_MATRIX_ZX_ZY_I = 0x10 - self.G_MWO_MATRIX_ZZ_ZW_I = 0x14 - self.G_MWO_MATRIX_WX_WY_I = 0x18 - self.G_MWO_MATRIX_WZ_WW_I = 0x1C - self.G_MWO_MATRIX_XX_XY_F = 0x20 - self.G_MWO_MATRIX_XZ_XW_F = 0x24 - self.G_MWO_MATRIX_YX_YY_F = 0x28 - self.G_MWO_MATRIX_YZ_YW_F = 0x2C - self.G_MWO_MATRIX_ZX_ZY_F = 0x30 - self.G_MWO_MATRIX_ZZ_ZW_F = 0x34 - self.G_MWO_MATRIX_WX_WY_F = 0x38 - self.G_MWO_MATRIX_WZ_WW_F = 0x3C + if not F3DEX_GBI_3: + self.G_MWO_MATRIX_XX_XY_I = 0x00 + self.G_MWO_MATRIX_XZ_XW_I = 0x04 + self.G_MWO_MATRIX_YX_YY_I = 0x08 + self.G_MWO_MATRIX_YZ_YW_I = 0x0C + self.G_MWO_MATRIX_ZX_ZY_I = 0x10 + self.G_MWO_MATRIX_ZZ_ZW_I = 0x14 + self.G_MWO_MATRIX_WX_WY_I = 0x18 + self.G_MWO_MATRIX_WZ_WW_I = 0x1C + self.G_MWO_MATRIX_XX_XY_F = 0x20 + self.G_MWO_MATRIX_XZ_XW_F = 0x24 + self.G_MWO_MATRIX_YX_YY_F = 0x28 + self.G_MWO_MATRIX_YZ_YW_F = 0x2C + self.G_MWO_MATRIX_ZX_ZY_F = 0x30 + self.G_MWO_MATRIX_ZZ_ZW_F = 0x34 + self.G_MWO_MATRIX_WX_WY_F = 0x38 + self.G_MWO_MATRIX_WZ_WW_F = 0x3C + self.G_MWO_POINT_RGBA = 0x10 self.G_MWO_POINT_ST = 0x14 self.G_MWO_POINT_XYSCREEN = 0x18 self.G_MWO_POINT_ZSCREEN = 0x1C + if F3DEX_GBI_3: + self.G_MWO_AO_AMBIENT = 0x00 + self.G_MWO_AO_DIRECTIONAL = 0x02 + self.G_MWO_AO_POINT = 0x04 + self.G_MWO_PERSPNORM = 0x06 + self.G_MWO_FRESNEL_SCALE = 0x0C + self.G_MWO_FRESNEL_OFFSET = 0x0E + self.G_MWO_ATTR_OFFSET_S = 0x10 + self.G_MWO_ATTR_OFFSET_T = 0x12 + self.G_MWO_ATTR_OFFSET_Z = 0x14 + self.G_MWO_ALPHA_COMPARE_CULL = 0x16 + self.G_MWO_NORMALS_MODE = 0x18 + # Texturing macros # These are also defined defined above for Sprite Microcode @@ -1579,39 +1663,31 @@ def RM_OPA_CI(clk): tile manipulation yourself. RJM. """ - if _HW_VERSION_1: - self.G_TX_LDBLK_MAX_TXL = 4095 - else: - self.G_TX_LDBLK_MAX_TXL = 2047 - - # Clipping Macros - self.FR_NEG_FRUSTRATIO_1 = 0x00000001 - self.FR_POS_FRUSTRATIO_1 = 0x0000FFFF - self.FR_NEG_FRUSTRATIO_2 = 0x00000002 - self.FR_POS_FRUSTRATIO_2 = 0x0000FFFE - self.FR_NEG_FRUSTRATIO_3 = 0x00000003 - self.FR_POS_FRUSTRATIO_3 = 0x0000FFFD - self.FR_NEG_FRUSTRATIO_4 = 0x00000004 - self.FR_POS_FRUSTRATIO_4 = 0x0000FFFC - self.FR_NEG_FRUSTRATIO_5 = 0x00000005 - self.FR_POS_FRUSTRATIO_5 = 0x0000FFFB - self.FR_NEG_FRUSTRATIO_6 = 0x00000006 - self.FR_POS_FRUSTRATIO_6 = 0x0000FFFA + self.G_TX_LDBLK_MAX_TXL = 2047 + + if not F3DEX_GBI_3: + # Clipping Macros + self.FR_NEG_FRUSTRATIO_1 = 0x00000001 + self.FR_POS_FRUSTRATIO_1 = 0x0000FFFF + self.FR_NEG_FRUSTRATIO_2 = 0x00000002 + self.FR_POS_FRUSTRATIO_2 = 0x0000FFFE + self.FR_NEG_FRUSTRATIO_3 = 0x00000003 + self.FR_POS_FRUSTRATIO_3 = 0x0000FFFD + self.FR_NEG_FRUSTRATIO_4 = 0x00000004 + self.FR_POS_FRUSTRATIO_4 = 0x0000FFFC + self.FR_NEG_FRUSTRATIO_5 = 0x00000005 + self.FR_POS_FRUSTRATIO_5 = 0x0000FFFB + self.FR_NEG_FRUSTRATIO_6 = 0x00000006 + self.FR_POS_FRUSTRATIO_6 = 0x0000FFFA self.G_BZ_PERSP = 0 self.G_BZ_ORTHO = 1 # Lighting Macros - self.numLights = { - "NUMLIGHTS_0": 1, - "NUMLIGHTS_1": 1, - "NUMLIGHTS_2": 2, - "NUMLIGHTS_3": 3, - "NUMLIGHTS_4": 4, - "NUMLIGHTS_5": 5, - "NUMLIGHTS_6": 6, - "NUMLIGHTS_7": 7, - } + if F3DEX_GBI_3: + self.numLights = {f"NUMLIGHTS_{n}": n for n in range(10)} + else: + self.numLights = {f"NUMLIGHTS_{n}": (1 if n == 0 else n) for n in range(8)} def GBL_c1(self, m1a, m1b, m2a, m2b): return (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18 @@ -1648,65 +1724,47 @@ def CALC_DXT_4b(self, width): return int(((1 << self.G_TX_DXT_FRAC) + self.TXL2WORDS_4b(width) - 1) / self.TXL2WORDS_4b(width)) def NUML(self, n): + if self.F3DEX_GBI_3: + return n * 0x10 nVal = self.numLights[n] return ((nVal) * 24) if self.F3DEX_GBI_2 else (((nVal) + 1) * 32 + 0x80000000) def getLightMWO_a(self, n): - if n == "G_MWO_aLIGHT_1": - return self.G_MWO_aLIGHT_1 - elif n == "G_MWO_aLIGHT_2": - return self.G_MWO_aLIGHT_2 - elif n == "G_MWO_aLIGHT_3": - return self.G_MWO_aLIGHT_3 - elif n == "G_MWO_aLIGHT_4": - return self.G_MWO_aLIGHT_4 - elif n == "G_MWO_aLIGHT_5": - return self.G_MWO_aLIGHT_5 - elif n == "G_MWO_aLIGHT_6": - return self.G_MWO_aLIGHT_6 - elif n == "G_MWO_aLIGHT_7": - return self.G_MWO_aLIGHT_7 - elif n == "G_MWO_aLIGHT_8": - return self.G_MWO_aLIGHT_8 + if n.startswith("G_MWO_aLIGHT_") and hasattr(self, n): + return getattr(self, n) else: raise PluginError("Invalid G_MWO_a value for lights: " + n) def getLightMWO_b(self, n): - if n == "G_MWO_bLIGHT_1": - return self.G_MWO_bLIGHT_1 - elif n == "G_MWO_bLIGHT_2": - return self.G_MWO_bLIGHT_2 - elif n == "G_MWO_bLIGHT_3": - return self.G_MWO_bLIGHT_3 - elif n == "G_MWO_bLIGHT_4": - return self.G_MWO_bLIGHT_4 - elif n == "G_MWO_bLIGHT_5": - return self.G_MWO_bLIGHT_5 - elif n == "G_MWO_bLIGHT_6": - return self.G_MWO_bLIGHT_6 - elif n == "G_MWO_bLIGHT_7": - return self.G_MWO_bLIGHT_7 - elif n == "G_MWO_bLIGHT_8": - return self.G_MWO_bLIGHT_8 + if n.startswith("G_MWO_bLIGHT_") and hasattr(self, n): + return getattr(self, n) else: raise PluginError("Invalid G_MWO_b value for lights: " + n) + def _DLHINTVALUE(self, count: int) -> int: + remainderCommands = count % self.G_INPUT_BUFFER_CMDS + if not self.F3DEX_GBI_3 or count == 0 or remainderCommands == 0: + return 0 + return (self.G_INPUT_BUFFER_CMDS - remainderCommands) << 3 -g_F3D = {"GBI": None, "f3d_type": None, "isHWv1": None} +g_F3D = { + "GBI": None, + "f3d_type": None, +} -def get_cached_F3D_GBI(f3d_type: str, isHWv1: bool) -> F3D: + +def get_cached_F3D_GBI(f3d_type: str) -> F3D: """Get constructed/cached F3D class""" - if g_F3D["GBI"] is None or f3d_type != g_F3D["f3d_type"] or isHWv1 != g_F3D["isHWv1"]: + if g_F3D["GBI"] is None or f3d_type != g_F3D["f3d_type"]: g_F3D["f3d_type"] = f3d_type - g_F3D["isHWv1"] = isHWv1 - g_F3D["GBI"] = F3D(f3d_type, isHWv1) + g_F3D["GBI"] = F3D(f3d_type) return g_F3D["GBI"] def get_F3D_GBI() -> F3D: """Gets cached F3D class and automatically supplies params""" - return get_cached_F3D_GBI(bpy.context.scene.f3d_type, bpy.context.scene.isHWv1) + return get_cached_F3D_GBI(bpy.context.scene.f3d_type) def _SHIFTL(value, amount, mask): @@ -1716,10 +1774,10 @@ def _SHIFTL(value, amount, mask): MTX_SIZE = 64 VTX_SIZE = 16 GFX_SIZE = 8 -VP_SIZE = 8 -LIGHT_SIZE = 16 # 12, but padded to 64bit alignment +VP_SIZE = 16 # it's 16 bytes but vanilla GBI has only one s64 for alignment, not two +LIGHT_SIZE = 16 AMBIENT_SIZE = 8 -HILITE_SIZE = 16 +HILITE_SIZE = 16 # 8 in F3DEX3, but this variable is not used in fast64 class ExportCData: @@ -1995,10 +2053,11 @@ def drawToC(self, f3d: F3D, gfxList: "GfxList") -> CData: class Vtx: - def __init__(self, position, uv, colorOrNormal): + def __init__(self, position, uv, colorOrNormal, packedNormal=0): self.position = position self.uv = uv self.colorOrNormal = colorOrNormal + self.packedNormal = packedNormal def to_binary(self): signX = 1 if self.uv[0] >= 0 else -1 @@ -2008,7 +2067,7 @@ def to_binary(self): self.position[0].to_bytes(2, "big", signed=True) + self.position[1].to_bytes(2, "big", signed=True) + self.position[2].to_bytes(2, "big", signed=True) - + bytearray([0x00, 0x00]) + + self.packedNormal.to_bytes(2, "big", signed=True) + uv[0].to_bytes(2, "big", signed=True) + uv[1].to_bytes(2, "big", signed=True) + bytearray(self.colorOrNormal) @@ -2018,7 +2077,8 @@ def to_c(self): def spc(x): return "{" + ", ".join([str(a) for a in x]) + "}" - return "{{ " + ", ".join([spc(self.position), "0", spc(self.uv), spc(self.colorOrNormal)]) + " }}" + flag = "0" if self.packedNormal == 0 else f"{self.packedNormal:#06x}" + return "{{ " + ", ".join([spc(self.position), flag, spc(self.uv), spc(self.colorOrNormal)]) + " }}" class VtxList: @@ -2235,8 +2295,6 @@ def __eq__(self, __o: object) -> bool: class FModel: def __init__( self, - f3dType: F3D, - isHWv1: bool, name: str, DLFormat: "DLFormat", matWriteMethod: GfxMatWriteMethod, @@ -2253,7 +2311,7 @@ def __init__( # GfxList self.materialRevert: Union[GfxList, None] = None # F3D library - self.f3d: F3D = F3D(f3dType, isHWv1) + self.f3d: F3D = get_F3D_GBI() # array of FModel self.subModels: list[FModel] = [] self.parentModel: Union[FModel, None] = None @@ -2619,7 +2677,7 @@ def to_c_gfx_scroll(self, gfxFormatter: GfxFormatter) -> CScrollData: data = CScrollData() for fMaterial, _ in self.materials.values(): fMaterial: FMaterial - if fMaterial.material: + if fMaterial.material.tag.Export: data.append(gfxFormatter.gfxScrollToC(fMaterial.material, self.f3d)) for fMesh in self.meshes.values(): fMesh: FMesh @@ -2659,9 +2717,9 @@ def freePalettes(self): class FTexRect(FModel): - def __init__(self, f3dType, isHWv1, name, matWriteMethod): + def __init__(self, name, matWriteMethod): self.draw = GfxList(name, GfxListTag.Draw, DLFormat.Dynamic) - FModel.__init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod) + FModel.__init__(self, name, DLFormat, matWriteMethod) def to_c(self, savePNG, texDir, gfxFormatter): staticData = CData() @@ -2861,6 +2919,13 @@ def __init__(self, name, index, fMaterial): self.fMaterial = fMaterial self.vertexList = VtxList(name + "_vtx_" + str(index)) self.triList = GfxList(name + "_tri_" + str(index), GfxListTag.Geometry, DLFormat.Static) + self.celTriLists = [] + self.celTriListBaseName = f"{name}_tri_{index}_cel" + + def add_cel_tri_list(self): + ret = GfxList(f"{self.celTriListBaseName}{len(self.celTriLists)}", GfxListTag.Geometry, DLFormat.Static) + self.celTriLists.append(ret) + return ret def get_ptr_addresses(self, f3d): return self.triList.get_ptr_addresses(f3d) @@ -2871,13 +2936,17 @@ def set_addr(self, startAddress, f3d): return startAddress, addrRange[1] def save_binary(self, romfile, f3d, segments): + for celTriList in self.celTriLists: + celTriList.save_binary(romfile, f3d, segments) self.triList.save_binary(romfile, f3d, segments) self.vertexList.save_binary(romfile) def to_c(self, f3d, gfxFormatter): data = CData() data.append(self.vertexList.to_c()) - if self.triList: + for celTriList in self.celTriLists: + data.append(celTriList.to_c(f3d)) + if self.triList.tag.Export: data.append(self.triList.to_c(f3d)) return data @@ -2977,7 +3046,7 @@ def sets_rendermode(self): def get_ptr_addresses(self, f3d): addresses = self.material.get_ptr_addresses(f3d) - if self.revert is not None: + if self.revert is not None and self.revert.tag.Export: addresses.extend(self.revert.get_ptr_addresses(f3d)) return addresses @@ -2995,9 +3064,9 @@ def save_binary(self, romfile, f3d, segments): def to_c(self, f3d): data = CData() - if self.material: + if self.material.tag.Export: data.append(self.material.to_c(f3d)) - if self.revert is not None: + if self.revert is not None and self.revert.tag.Export: data.append(self.revert.to_c(f3d)) return data @@ -3069,8 +3138,9 @@ def to_c(self): class Lights: - def __init__(self, name): + def __init__(self, name, f3d): self.name = name + self.f3d = f3d self.startAddress = 0 self.a = None self.l = [] @@ -3086,21 +3156,36 @@ def save_binary(self, romfile): romfile.write(self.to_binary()) def size(self): - return max(len(self.l), 1) * LIGHT_SIZE + AMBIENT_SIZE + if self.f3d.F3DEX_GBI_3: + count = len(self.l) + else: + count = max(len(self.l), 1) + return count * LIGHT_SIZE + AMBIENT_SIZE def getLightPointer(self, i): - return self.startAddress + AMBIENT_SIZE + i * LIGHT_SIZE + if self.f3d.F3DEX_GBI_3: + return self.startAddress + i * LIGHT_SIZE + else: + return self.startAddress + AMBIENT_SIZE + i * LIGHT_SIZE def getAmbientPointer(self): - return self.startAddress + if self.f3d.F3DEX_GBI_3: + return self.startAddress + len(self.l) * LIGHT_SIZE + else: + return self.startAddress def to_binary(self): - data = self.a.to_binary() - if len(self.l) == 0: + ambientData = self.a.to_binary() + data = bytes() + if len(self.l) == 0 and not self.f3d.F3DEX_GBI_3: data += Light([0, 0, 0], [0, 0, 0]).to_binary() else: for i in range(len(self.l)): data += self.l[i].to_binary() + if self.f3d.F3DEX_GBI_3: + data = data + ambientData + else: + data = ambientData + data return data def to_c(self): @@ -3115,8 +3200,10 @@ def to_c(self): class LookAt: - def __init__(self, name): + # F3DEX3 TODO: update this + def __init__(self, name, f3d): self.name = name + self.f3d = f3d self.startAddress = 0 self.l = [] # 2 lights @@ -3141,7 +3228,7 @@ def spc(x): + spc(self.l[1].color) + "}, 0, " + "{" - + spc(self.l[0].normal) + + spc(self.l[1].normal) + "}, 0}}" + "}}\n" ) @@ -3256,6 +3343,7 @@ def gsSPNoOp(f3d): class GbiMacro: _segptrs = False _ptr_amp = False + _hex = 0 # If nonzero, write int values as hex with specified digits tags = GfxTag(0) """ @@ -3288,6 +3376,9 @@ def getattr_virtual(self, field, static): return field.name if hasattr(field, "__iter__") and type(field) is not str: return " | ".join(field) if len(field) else "0" + if self._hex > 0 and isinstance(field, int): + temp = field if field >= 0 else (1 << (self._hex * 4)) + field + return f"{temp:#0{self._hex + 2}x}" # + 2 for the 0x part return str(field) def to_c(self, static=True): @@ -3369,6 +3460,9 @@ def to_binary(self, f3d, segments): return gsDma1p(f3d.G_MOVEMEM, vpPtr, VP_SIZE, f3d.G_MV_VIEWPORT) +# F3DEX3 TODO: Encoding of hints (and generation of the hint values) + + @dataclass(unsafe_hash=True) class SPDisplayList(GbiMacro): displayList: GfxList @@ -3400,6 +3494,13 @@ def to_binary(self, f3d, segments): return gsDma1p(f3d.G_DL, dlPtr, 0, f3d.G_DL_NOPUSH) +@dataclass(unsafe_hash=True) +class SPEndDisplayList(GbiMacro): + def to_binary(self, f3d, segments): + words = _SHIFTL(f3d.G_ENDDL, 24, 8), 0 + return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") + + # SPSprite2DBase @@ -3431,12 +3532,20 @@ def gsImmp21(c, p0, p1, dat): def gsMoveWd(index, offset, data, f3d): + if f3d.F3DEX_GBI_3: + offset &= 0xFFF if f3d.F3DEX_GBI_2: return gsDma1p(f3d.G_MOVEWORD, data, offset, index) else: return gsImmp21(f3d.G_MOVEWORD, offset, index, data) +def gsMoveHalfwd(index, offset, data, f3d): + if not f3d.F3DEX_GBI_3: + raise PluginError("gsMoveHalfwd requires F3DEX3 microcode") + return gsDma1p(f3d.G_MOVEWORD, data, (offset & 0xFFF) | f3d.G_MW_HALFWORD_FLAG, index) + + # SPSprite2DScaleFlip # SPSprite2DDraw @@ -3519,7 +3628,9 @@ class SPLine3D(GbiMacro): flag: int def to_binary(self, f3d, segments): - if f3d.F3DEX_GBI_2: + if f3d.F3DEX_GBI_3: + raise PluginError("SPLine3D is removed in F3DEX3") + elif f3d.F3DEX_GBI_2: words = _SHIFTL(f3d.G_LINE3D, 24, 8) | _gsSPLine3D_w1f(self.v0, self.v1, 0, self.flag, f3d), 0 else: words = _SHIFTL(f3d.G_LINE3D, 24, 8), _gsSPLine3D_w1f(self.v0, self.v1, 0, self.flag, f3d) @@ -3534,7 +3645,9 @@ class SPLineW3D(GbiMacro): flag: int def to_binary(self, f3d, segments): - if f3d.F3DEX_GBI_2: + if f3d.F3DEX_GBI_3: + raise PluginError("SPLineW3D is removed in F3DEX3") + elif f3d.F3DEX_GBI_2: words = _SHIFTL(f3d.G_LINE3D, 24, 8) | _gsSPLine3D_w1f(self.v0, self.v1, self.wd, self.flag, f3d), 0 else: words = _SHIFTL(f3d.G_LINE3D, 24, 8), _gsSPLine3D_w1f(self.v0, self.v1, self.wd, self.flag, f3d) @@ -3566,6 +3679,10 @@ def to_binary(self, f3d, segments): 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 + + @dataclass(unsafe_hash=True) class SPCullDisplayList(GbiMacro): vstart: int @@ -3597,6 +3714,9 @@ class SPClipRatio(GbiMacro): ratio: int def to_binary(self, f3d, segments): + if f3d.F3DEX_GBI_3: + return gsSPNoOp(f3d) + # These values are supposed to be flipped. shortRatioPos = int.from_bytes((-self.ratio).to_bytes(2, "big", signed=True), "big", signed=False) shortRatioNeg = int.from_bytes(self.ratio.to_bytes(2, "big", signed=True), "big", signed=False) @@ -3616,6 +3736,177 @@ def size(self, f3d): # SPForceMatrix +@dataclass(unsafe_hash=True) +class SPAmbOcclusionAmb(GbiMacro): + amb: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusionAmb requires F3DEX3 microcode") + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_AO_AMBIENT, self.amb, f3d) + + +@dataclass(unsafe_hash=True) +class SPAmbOcclusionDir(GbiMacro): + dir: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusionDir requires F3DEX3 microcode") + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_AO_DIRECTIONAL, self.dir, f3d) + + +@dataclass(unsafe_hash=True) +class SPAmbOcclusionPoint(GbiMacro): + point: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusionPoint requires F3DEX3 microcode") + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_AO_POINT, self.point, f3d) + + +@dataclass(unsafe_hash=True) +class SPAmbOcclusionAmbDir(GbiMacro): + amb: int + dir: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusionAmbDir requires F3DEX3 microcode") + return gsMoveWd(f3d.G_MW_FX, f3d.G_MWO_AO_AMBIENT, (_SHIFTL(self.amb, 16, 16) | _SHIFTL(self.dir, 0, 16)), f3d) + + +@dataclass(unsafe_hash=True) +class SPAmbOcclusionDirPoint(GbiMacro): + dir: int + point: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusionDirPoint requires F3DEX3 microcode") + return gsMoveWd( + f3d.G_MW_FX, f3d.G_MWO_AO_DIRECTIONAL, (_SHIFTL(self.dir, 16, 16) | _SHIFTL(self.point, 0, 16)), f3d + ) + + +@dataclass(unsafe_hash=True) +class SPAmbOcclusion(GbiMacro): + amb: int + dir: int + point: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbOcclusion requires F3DEX3 microcode") + return SPAmbOcclusionAmbDir(self.amb, self.dir).to_binary(f3d, segments) + SPAmbOcclusionPoint( + self.point + ).to_binary(f3d, segments) + + +@dataclass(unsafe_hash=True) +class SPFresnelScale(GbiMacro): + scale: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPFresnelScale requires F3DEX3 microcode") + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_FRESNEL_SCALE, self.scale, f3d) + + +@dataclass(unsafe_hash=True) +class SPFresnelOffset(GbiMacro): + offset: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPFresnelOffset requires F3DEX3 microcode") + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_FRESNEL_OFFSET, self.offset, f3d) + + +@dataclass(unsafe_hash=True) +class SPFresnel(GbiMacro): + scale: int + offset: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPFresnel requires F3DEX3 microcode") + return gsMoveWd( + f3d.G_MW_FX, f3d.G_MWO_FRESNEL_SCALE, (_SHIFTL(self.scale, 16, 16) | _SHIFTL(self.offset, 0, 16)), f3d + ) + + +@dataclass(unsafe_hash=True) +class SPAttrOffsetST(GbiMacro): + s: int + t: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAttrOffsetST requires F3DEX3 microcode") + return gsMoveWd(f3d.G_MW_FX, f3d.G_MWO_ATTR_OFFSET_S, (_SHIFTL(self.s, 16, 16) | _SHIFTL(self.t, 0, 16)), f3d) + + +@dataclass(unsafe_hash=True) +class SPAttrOffsetZ(GbiMacro): + z: int + _hex = 4 + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAttrOffsetZ requires F3DEX3 microcode") + return gsMoveWd(f3d.G_MW_FX, f3d.G_MWO_ATTR_OFFSET_Z, (_SHIFTL(self.z, 16, 16)), f3d) + + +@dataclass(unsafe_hash=True) +class SPAlphaCompareCull(GbiMacro): + mode: str + thresh: int + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAlphaCompareCull requires F3DEX3 microcode") + if self.mode == "G_ALPHA_COMPARE_CULL_DISABLE": + modeVal = f3d.G_ALPHA_COMPARE_CULL_DISABLE + elif self.mode == "G_ALPHA_COMPARE_CULL_BELOW": + modeVal = f3d.G_ALPHA_COMPARE_CULL_BELOW + elif self.mode == "G_ALPHA_COMPARE_CULL_ABOVE": + modeVal = f3d.G_ALPHA_COMPARE_CULL_ABOVE + return gsMoveHalfwd( + f3d.G_MW_FX, f3d.G_MWO_ALPHA_COMPARE_CULL, (_SHIFTL(modeVal, 8, 8) | _SHIFTL(self.thresh, 0, 8)), f3d + ) + + +@dataclass(unsafe_hash=True) +class SPNormalsMode(GbiMacro): + mode: str + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPNormalsMode requires F3DEX3 microcode") + if self.mode == "G_NORMALS_MODE_FAST": + modeVal = f3d.G_NORMALS_MODE_FAST + elif self.mode == "G_NORMALS_MODE_AUTO": + modeVal = f3d.G_NORMALS_MODE_AUTO + elif self.mode == "G_NORMALS_MODE_MANUAL": + modeVal = f3d.G_NORMALS_MODE_MANUAL + return gsMoveHalfwd(f3d.G_MW_FX, f3d.G_MWO_NORMALS_MODE, modeVal & 0xFF, f3d) + + +# SPMITMatrix (F3DEX3) + + @dataclass(unsafe_hash=True) class SPModifyVertex(GbiMacro): vtx: int @@ -3687,16 +3978,32 @@ class SPLight(GbiMacro): light: int # start address of light n: str _segptrs = True # call segmented_to_virtual in to_c method + _size = LIGHT_SIZE def to_binary(self, f3d, segments): lightPtr = int.from_bytes(encodeSegmentedAddr(self.light, segments), "big") + idx = lightIndex[self.n] if f3d.F3DEX_GBI_2: - data = gsDma2p(f3d.G_MOVEMEM, lightPtr, LIGHT_SIZE, f3d.G_MV_LIGHT, lightIndex[self.n] * 24 + 24) + if f3d.F3DEX_GBI_3: + offset = (idx - 1) * 0x10 + 0x10 + else: + offset = idx * 24 + 24 + data = gsDma2p(f3d.G_MOVEMEM, lightPtr, self._size, f3d.G_MV_LIGHT, offset) else: - data = gsDma1p(f3d.G_MOVEMEM, lightPtr, LIGHT_SIZE, (lightIndex[self.n] - 1) * 2 + f3d.G_MV_L0) + data = gsDma1p(f3d.G_MOVEMEM, lightPtr, self._size, (idx - 1) * 2 + f3d.G_MV_L0) return data +@dataclass(unsafe_hash=True) +class SPAmbient(SPLight): + _size = AMBIENT_SIZE + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPAmbient requires F3DEX3 microcode") + return super().to_binary(f3d, segments) + + @dataclass(unsafe_hash=True) class SPLightColor(GbiMacro): # n is macro name (string) @@ -3704,7 +4011,7 @@ class SPLightColor(GbiMacro): col: int def to_binary(self, f3d, segments): - return gsMoveWd(f3d.G_MW_LIGHTCOL, f3d.getLightMWO_a(self.n), self.col, f3d), +gsMoveWd( + return gsMoveWd(f3d.G_MW_LIGHTCOL, f3d.getLightMWO_a(self.n), self.col, f3d) + gsMoveWd( f3d.G_MW_LIGHTCOL, f3d.getLightMWO_b(self.n), self.col, f3d ) @@ -3718,6 +4025,8 @@ class SPSetLights(GbiMacro): lights: Lights def get_ptr_offsets(self, f3d): + if f3d.F3DEX_GBI_3: + return [12] offsets = [] if len(self.lights.l) == 0: offsets = [12, 20] @@ -3729,8 +4038,13 @@ def get_ptr_offsets(self, f3d): return offsets def to_binary(self, f3d, segments): - data = SPNumLights("NUMLIGHTS_" + str(len(self.lights.l))).to_binary(f3d, segments) - if len(self.lights.l) == 0: + n = len(self.lights.l) + data = SPNumLights(f"NUMLIGHTS_{n}").to_binary(f3d, segments) + if f3d.F3DEX_GBI_3: + data += gsDma2p( + f3d.G_MOVEMEM, self.lights.startAddress, len(self.lights.l) * 0x10 + 8, f3d.G_MV_LIGHT, 0x10 + ) + elif len(self.lights.l) == 0: # The light does not exist in python, but is added in # when converted to binary, making this address valid. data += SPLight(self.lights.getLightPointer(0), "LIGHT_1").to_binary(f3d, segments) @@ -3738,26 +4052,26 @@ def to_binary(self, f3d, segments): else: for i in range(len(self.lights.l)): data += SPLight(self.lights.getLightPointer(i), "LIGHT_" + str(i + 1)).to_binary(f3d, segments) - data += SPLight(self.lights.getAmbientPointer(), "LIGHT_" + str(len(self.lights.l) + 1)).to_binary( - f3d, segments - ) + data += SPLight(self.lights.getAmbientPointer(), "LIGHT_" + str(n + 1)).to_binary(f3d, segments) return data def to_c(self, static=True): - header = ( - "gsSPSetLights" + str(len(self.lights.l)) + "(" - if static - else "gSPSetLights" + str(len(self.lights.l)) + "(glistp++, " - ) + n = len(self.lights.l) + header = f"gsSPSetLights{n}(" if static else f"gSPSetLights{n}(glistp++, " if not static and bpy.context.scene.decomp_compatible: - header += "(*(Lights" + str(len(self.lights.l)) + "*) segmented_to_virtual(&" + self.lights.name + "))" + header += f"(*(Lights{n}*) segmented_to_virtual(&{self.lights.name}))" else: header += self.lights.name return header + ")" def size(self, f3d): - return GFX_SIZE * (2 + max(len(self.lights.l), 1)) + if f3d.F3DEX_GBI_3: + return GFX_SIZE * 2 + else: + return GFX_SIZE * (2 + max(len(self.lights.l), 1)) + +# F3DEX3 TODO: SPCameraWorld # Reflection/Hiliting Macros @@ -3782,8 +4096,11 @@ class SPLookAt(GbiMacro): _ptr_amp = True # add an ampersand to names def to_binary(self, f3d, segments): - light0Ptr = int.from_bytes(encodeSegmentedAddr(self.la.startAddress, segments), "big") - return gsSPLookAtX(light0Ptr, f3d) + gsSPLookAtY(light0Ptr + 16, f3d) + lookAtPtr = int.from_bytes(encodeSegmentedAddr(self.la.startAddress, segments), "big") + if f3d.F3DEX_GBI_3: + return gsDma2p(f3d.G_MOVEMEM, lookAtPtr, 8, f3d.G_MV_LIGHT, 8) + else: + return gsSPLookAtX(lookAtPtr, f3d) + gsSPLookAtY(lookAtPtr + 16, f3d) @dataclass(unsafe_hash=True) @@ -3864,7 +4181,6 @@ def to_binary(self, f3d, segments): if f3d.F3DEX_GBI_2: words = ( _SHIFTL(f3d.G_TEXTURE, 24, 8) - | _SHIFTL(f3d.BOWTIE_VAL, 16, 8) | _SHIFTL((self.level), 11, 3) | _SHIFTL((self.tile), 8, 3) | _SHIFTL((self.on), 1, 7) @@ -3872,7 +4188,6 @@ def to_binary(self, f3d, segments): else: words = ( _SHIFTL(f3d.G_TEXTURE, 24, 8) - | _SHIFTL(f3d.BOWTIE_VAL, 16, 8) | _SHIFTL((self.level), 11, 3) | _SHIFTL((self.tile), 8, 3) | _SHIFTL((self.on), 0, 8) @@ -3889,20 +4204,16 @@ class SPPerspNormalize(GbiMacro): s: int def to_binary(self, f3d, segments): - return gsMoveWd(f3d.G_MW_PERSPNORM, 0, (self.s), f3d) + if f3d.F3DEX_GBI_3: + return gsMoveHalfwd(f3d.G_MW_FX, G_MWO_PERSPNORM, (self.s), f3d) + else: + return gsMoveWd(f3d.G_MW_PERSPNORM, 0, (self.s), f3d) # SPPopMatrixN # SPPopMatrix -@dataclass(unsafe_hash=True) -class SPEndDisplayList(GbiMacro): - def to_binary(self, f3d, segments): - words = _SHIFTL(f3d.G_ENDDL, 24, 8), 0 - return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") - - def gsSPGeometryMode_F3DEX_GBI_2(c, s, f3d): words = (_SHIFTL(f3d.G_GEOMETRYMODE, 24, 8) | _SHIFTL(~c, 0, 24)), s return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") @@ -3916,32 +4227,8 @@ def gsSPGeometryMode_Non_F3DEX_GBI_2(word, f3d): def geoFlagListToWord(flagList, f3d): word = 0 for name in flagList: - if name == "G_ZBUFFER": - word += f3d.G_ZBUFFER - elif name == "G_SHADE": - word += f3d.G_SHADE - elif name == "G_TEXTURE_ENABLE": - word += f3d.G_TEXTURE_ENABLE - elif name == "G_SHADING_SMOOTH": - word += f3d.G_SHADING_SMOOTH - elif name == "G_CULL_FRONT": - word += f3d.G_CULL_FRONT - elif name == "G_CULL_BACK": - word += f3d.G_CULL_BACK - elif name == "G_CULL_BOTH": - word += f3d.G_CULL_BOTH - elif name == "G_FOG": - word += f3d.G_FOG - elif name == "G_LIGHTING": - word += f3d.G_LIGHTING - elif name == "G_TEXTURE_GEN": - word += f3d.G_TEXTURE_GEN - elif name == "G_TEXTURE_GEN_LINEAR": - word += f3d.G_TEXTURE_GEN_LINEAR - elif name == "G_LOD": - word += f3d.G_LOD - elif name == "G_CLIPPING": - word += f3d.G_CLIPPING + if name in f3d.allGeomModeFlags: + word += getattr(f3d, name) else: raise PluginError("Invalid geometry mode flag " + name) @@ -4162,24 +4449,17 @@ class DPSetColorDither(GbiMacro): mode: str def to_binary(self, f3d, segments): - if not f3d._HW_VERSION_1: - if self.mode == "G_CD_MAGICSQ": - modeVal = f3d.G_CD_MAGICSQ - elif self.mode == "G_CD_BAYER": - modeVal = f3d.G_CD_BAYER - elif self.mode == "G_CD_NOISE": - modeVal = f3d.G_CD_NOISE - elif self.mode == "G_CD_DISABLE": - modeVal = f3d.G_CD_DISABLE - elif self.mode == "G_CD_ENABLE": - modeVal = f3d.G_CD_ENABLE - return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_RGBDITHER, 2, modeVal, f3d) - else: - if self.mode == "G_CD_ENABLE": - modeVal = f3d.G_CD_ENABLE - elif self.mode == "G_CD_DISABLE": - modeVal = f3d.G_CD_DISABLE - return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_COLORDITHER, 1, modeVal, f3d) + if self.mode == "G_CD_MAGICSQ": + modeVal = f3d.G_CD_MAGICSQ + elif self.mode == "G_CD_BAYER": + modeVal = f3d.G_CD_BAYER + elif self.mode == "G_CD_NOISE": + modeVal = f3d.G_CD_NOISE + elif self.mode == "G_CD_DISABLE": + modeVal = f3d.G_CD_DISABLE + elif self.mode == "G_CD_ENABLE": + modeVal = f3d.G_CD_ENABLE + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_RGBDITHER, 2, modeVal, f3d) @dataclass(unsafe_hash=True) @@ -4188,18 +4468,15 @@ class DPSetAlphaDither(GbiMacro): mode: str def to_binary(self, f3d, segments): - if not f3d._HW_VERSION_1: - if self.mode == "G_AD_PATTERN": - modeVal = f3d.G_AD_PATTERN - elif self.mode == "G_AD_NOTPATTERN": - modeVal = f3d.G_AD_NOTPATTERN - elif self.mode == "G_AD_NOISE": - modeVal = f3d.G_AD_NOISE - elif self.mode == "G_AD_DISABLE": - modeVal = f3d.G_AD_DISABLE - return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_ALPHADITHER, 2, modeVal, f3d) - else: - raise PluginError("SetAlphaDither not available in HW v1.") + if self.mode == "G_AD_PATTERN": + modeVal = f3d.G_AD_PATTERN + elif self.mode == "G_AD_NOTPATTERN": + modeVal = f3d.G_AD_NOTPATTERN + elif self.mode == "G_AD_NOISE": + modeVal = f3d.G_AD_NOISE + elif self.mode == "G_AD_DISABLE": + modeVal = f3d.G_AD_DISABLE + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, f3d.G_MDSFT_ALPHADITHER, 2, modeVal, f3d) @dataclass(unsafe_hash=True) @@ -4487,6 +4764,45 @@ def to_binary(self, f3d, segments): return words[0].to_bytes(4, "big") + words[1].to_bytes(4, "big") +@dataclass(unsafe_hash=True) +class SPLightToRDP(GbiMacro): + light: int + alpha: int + word0: int # word0 of the command to write, which is word1 of this command + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPLightToRDP requires F3DEX3 microcode") + word = _SHIFTL(f3d.G_LIGHTTORDP, 24, 8) | _SHIFTL(self.light * 0x10, 8, 8) | _SHIFTL(self.alpha, 0, 8) + return word.to_bytes(4, "big") + self.word0.to_bytes(4, "big") + + +@dataclass(unsafe_hash=True) +class SPLightToPrimColor(GbiMacro): + light: int + alpha: int + m: int + l: int + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPLightToPrimColor requires F3DEX3 microcode") + word0 = _SHIFTL(f3d.G_SETPRIMCOLOR, 24, 8) | _SHIFTL(self.m, 8, 8) | _SHIFTL(self.l, 0, 8) + return SPLightToRDP(self.light, self.alpha, word0).to_binary(f3d, segments) + + +@dataclass(unsafe_hash=True) +class SPLightToFogColor(GbiMacro): + light: int + alpha: int + + def to_binary(self, f3d, segments): + if not f3d.F3DEX_GBI_3: + raise PluginError("SPLightToFogColor requires F3DEX3 microcode") + word0 = _SHIFTL(f3d.G_SETFOGCOLOR, 24, 8) + return SPLightToRDP(self.light, self.alpha, word0).to_binary(f3d, segments) + + @dataclass(unsafe_hash=True) class DPSetOtherMode(GbiMacro): mode0: list @@ -5059,39 +5375,19 @@ class DPLoadTLUT_pal16(GbiMacro): _ptr_amp = True # adds & to name of image def to_binary(self, f3d, segments): - if not f3d._HW_VERSION_1: - return ( - DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) - + DPTileSync().to_binary(f3d, segments) - + DPSetTile( - "0", "0", 0, (256 + (((self.pal) & 0xF) * 16)), f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0 - ).to_binary(f3d, segments) - + DPLoadSync().to_binary(f3d, segments) - + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, 15).to_binary(f3d, segments) - + DPPipeSync().to_binary(f3d, segments) - ) - else: - return _DPLoadTextureBlock( - self.dram, - (256 + (((self.pal) & 0xF) * 16)), - f3d.G_IM_FMT_VARS["G_IM_FMT_RGBA"], - f3d.G_IM_SIZ_VARS["G_IM_SIZ_16b"], - 4 * 16, - 1, - self.pal, - 0, - 0, - 0, - 0, - 0, - 0, + return ( + DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) + + DPTileSync().to_binary(f3d, segments) + + DPSetTile( + "0", "0", 0, (256 + (((self.pal) & 0xF) * 16)), f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0 ).to_binary(f3d, segments) + + DPLoadSync().to_binary(f3d, segments) + + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, 15).to_binary(f3d, segments) + + DPPipeSync().to_binary(f3d, segments) + ) def size(self, f3d): - if not f3d._HW_VERSION_1: - return GFX_SIZE * 6 - else: - return GFX_SIZE * 7 + return GFX_SIZE * 6 @dataclass(unsafe_hash=True) @@ -5100,37 +5396,17 @@ class DPLoadTLUT_pal256(GbiMacro): _ptr_amp = True # adds & to name of image def to_binary(self, f3d, segments): - if not f3d._HW_VERSION_1: - return ( - DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) - + DPTileSync().to_binary(f3d, segments) - + DPSetTile("0", "0", 0, 256, f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0).to_binary(f3d, segments) - + DPLoadSync().to_binary(f3d, segments) - + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, 255).to_binary(f3d, segments) - + DPPipeSync().to_binary(f3d, segments) - ) - else: - return _DPLoadTextureBlock( - self.dram, - 256, - f3d.G_IM_FMT_VARS["G_IM_FMT_RGBA"], - f3d.G_IM_SIZ_VARS["G_IM_SIZ_16b"], - 4 * 256, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ).to_binary(f3d, segments) + return ( + DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) + + DPTileSync().to_binary(f3d, segments) + + DPSetTile("0", "0", 0, 256, f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0).to_binary(f3d, segments) + + DPLoadSync().to_binary(f3d, segments) + + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, 255).to_binary(f3d, segments) + + DPPipeSync().to_binary(f3d, segments) + ) def size(self, f3d): - if not f3d._HW_VERSION_1: - return GFX_SIZE * 6 - else: - return GFX_SIZE * 7 + return GFX_SIZE * 6 @dataclass(unsafe_hash=True) @@ -5141,37 +5417,17 @@ class DPLoadTLUT(GbiMacro): _ptr_amp = True # adds & to name of image def to_binary(self, f3d, segments): - if not f3d._HW_VERSION_1: - return ( - DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) - + DPTileSync().to_binary(f3d, segments) - + DPSetTile("0", "0", 0, self.tmemaddr, f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0).to_binary(f3d, segments) - + DPLoadSync().to_binary(f3d, segments) - + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, self.count - 1).to_binary(f3d, segments) - + DPPipeSync().to_binary(f3d, segments) - ) - else: - return _DPLoadTextureBlock( - self.dram, - self.tmemaddr, - f3d.G_IM_FMT_VARS["G_IM_FMT_RGBA"], - f3d.G_IM_SIZ_VARS["G_IM_SIZ_16b"], - 4, - self.count, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - ).to_binary(f3d, segments) + return ( + DPSetTextureImage("G_IM_FMT_RGBA", "G_IM_SIZ_16b", 1, self.dram).to_binary(f3d, segments) + + DPTileSync().to_binary(f3d, segments) + + DPSetTile("0", "0", 0, self.tmemaddr, f3d.G_TX_LOADTILE, 0, 0, 0, 0, 0, 0, 0).to_binary(f3d, segments) + + DPLoadSync().to_binary(f3d, segments) + + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, self.count - 1).to_binary(f3d, segments) + + DPPipeSync().to_binary(f3d, segments) + ) def size(self, f3d): - if not f3d._HW_VERSION_1: - return GFX_SIZE * 6 - else: - return GFX_SIZE * 7 + return GFX_SIZE * 6 # gsDPSetScissor diff --git a/fast64_internal/f3d/f3d_generate_presets.py b/fast64_internal/f3d/f3d_generate_presets.py index f5d381e3e..98c16ef1e 100644 --- a/fast64_internal/f3d/f3d_generate_presets.py +++ b/fast64_internal/f3d/f3d_generate_presets.py @@ -1,29 +1,29 @@ import os -basePath = 'presets/f3d' -data = '' +basePath = "presets/f3d" +data = "" presetList = "material_presets = {\n" for subdir in os.listdir(basePath): - subPath = os.path.join(basePath, subdir) - if subdir != '__pycache__' and subdir != 'user' and os.path.isdir(subPath): - presetList += '\t"' + subdir + '" : {\n' - for filename in os.listdir(subPath): - presetPath = os.path.join(subPath, filename) - if os.path.isfile(presetPath): - print(presetPath) - presetFile = open(presetPath, 'r') - presetData = presetFile.read() - presetFile.close() + subPath = os.path.join(basePath, subdir) + if subdir != "__pycache__" and subdir != "user" and os.path.isdir(subPath): + presetList += '\t"' + subdir + '" : {\n' + for filename in os.listdir(subPath): + presetPath = os.path.join(subPath, filename) + if os.path.isfile(presetPath): + print(presetPath) + presetFile = open(presetPath, "r") + presetData = presetFile.read() + presetFile.close() - data += filename[:-3] + " = '''\n" + presetData + "'''\n\n" - presetList += '\t\t"' + filename[:-3] + '" : ' + filename[:-3] + ',\n' - presetList += '\t},\n' + data += filename[:-3] + " = '''\n" + presetData + "'''\n\n" + presetList += '\t\t"' + filename[:-3] + '" : ' + filename[:-3] + ",\n" + presetList += "\t},\n" -presetList += '}\n' +presetList += "}\n" data += presetList -outFile = open('f3d_material_presets.py', 'w') +outFile = open("f3d_material_presets.py", "w") outFile.write(data) -outFile.close() \ No newline at end of file +outFile.close() diff --git a/fast64_internal/f3d/f3d_material.py b/fast64_internal/f3d/f3d_material.py index 267e17ad2..da1794e50 100644 --- a/fast64_internal/f3d/f3d_material.py +++ b/fast64_internal/f3d/f3d_material.py @@ -1,18 +1,42 @@ import logging import bpy, math, os -from bpy.types import Operator, Menu +from bpy.types import ( + Attribute, + Context, + Image, + Light, + Material, + Menu, + Mesh, + NodeGroupOutput, + NodeInputs, + NodeLink, + NodeSocket, + NodeTree, + Object, + Operator, + Panel, + Property, + PropertyGroup, + Scene, + ShaderNodeGroup, + TextureNodeImage, + UILayout, + VIEW3D_HT_header, + World, +) from bl_operators.presets import AddPresetBase from bpy.utils import register_class, unregister_class from mathutils import Color from .f3d_enums import * -from .f3d_gbi import get_F3D_GBI, GBL_c1, GBL_c2, enumTexScroll +from .f3d_gbi import get_F3D_GBI, GBL_c1, GBL_c2, enumTexScroll, isUcodeF3DEX1 from .f3d_material_presets import * from ..utility import * from ..render_settings import Fast64RenderSettings_Properties, update_scene_props_from_render_settings from .f3d_material_helpers import F3DMaterial_UpdateLock from bpy.app.handlers import persistent -from typing import Generator, Optional, Tuple, Any +from typing import Generator, Optional, Tuple, Any, Dict, Union F3DMaterialHash = Any # giant tuple @@ -153,6 +177,36 @@ def update_draw_layer(self, context): set_output_node_groups(material) +def rendermodePresetToBits(rdp_settings: "RDPSettings") -> Tuple[int, int]: + f3d = get_F3D_GBI() + r1 = getattr(f3d, rdp_settings.rendermode_preset_cycle_1, f3d.G_RM_AA_ZB_OPA_SURF) + r2 = getattr(f3d, rdp_settings.rendermode_preset_cycle_2, f3d.G_RM_AA_ZB_OPA_SURF) + if rdp_settings.g_mdsft_cycletype == "G_CYC_1CYCLE": + r2 = 0 + return r1, r2 + + +def all_blender_uses(rdp_settings: "RDPSettings") -> Dict[str, bool]: + """ + Returns a dictionary of the external features which the blender may or may + not use, or None if set_rendermode is disabled so we don't know. + """ + if not rdp_settings.set_rendermode: + return None + is_one_cycle = rdp_settings.g_mdsft_cycletype == "G_CYC_1CYCLE" + if rdp_settings.rendermode_advanced_enabled: + useZ = rdp_settings.z_cmp or rdp_settings.z_upd + useShade = rdp_settings.blend_a1 == "G_BL_A_SHADE" + if not is_one_cycle: + useShade = useShade or rdp_settings.blend_a2 == "G_BL_A_SHADE" + else: + r1, r2 = rendermodePresetToBits(rdp_settings) + f3d = get_F3D_GBI() + useZ = bool((r1 | r2) & (f3d.Z_CMP | f3d.Z_UPD)) + useShade = ((r1 >> 26) & 3) == f3d.G_BL_A_SHADE or ((r2 >> 24) & 3) == f3d.G_BL_A_SHADE + return {"Shade Alpha": useShade, "Z Buffer": useZ} + + def get_blend_method(material): f3dMat = material.f3d_mat drawLayer = material.f3d_mat.draw_layer @@ -204,13 +258,26 @@ def get_blend_method(material): return blend_method -def update_blend_method(material: bpy.types.Material, context): +def update_blend_method(material: Material, context): material.blend_method = get_blend_method(material) if material.blend_method == "CLIP": material.alpha_threshold = 0.125 -class DrawLayerProperty(bpy.types.PropertyGroup): +def getZMode(material: Material): + f3dMat = material.f3d_mat + settings = f3dMat.rdp_settings + if not settings.set_rendermode: + return "ZMODE_OPA" + if settings.rendermode_advanced_enabled: + return settings.zmode + r1, r2 = rendermodePresetToBits(settings) + f3d = get_F3D_GBI() + zmode = ((r1 | r2) & f3d.ZMODE_DEC) // f3d.ZMODE_INTER + return enumZMode[zmode][0] + + +class DrawLayerProperty(PropertyGroup): sm64: bpy.props.EnumProperty(items=sm64EnumDrawLayers, default="1", update=update_draw_layer) oot: bpy.props.EnumProperty(items=ootEnumDrawLayers, default="Opaque", update=update_draw_layer) @@ -230,24 +297,9 @@ def getTmemMax(texFormat): # Necessary for UV half pixel offset (see 13.7.5.3) def isTexturePointSampled(material): f3dMat = material.f3d_mat - return f3dMat.rdp_settings.g_mdsft_text_filt == "G_TF_POINT" -def isLightingDisabled(material): - f3dMat = material.f3d_mat - return not f3dMat.rdp_settings.g_lighting - - -# Necessary as G_SHADE_SMOOTH actually does nothing -def checkIfFlatShaded(material): - if material.mat_ver > 3: - f3dMat = material.f3d_mat - else: - f3dMat = material - return not f3dMat.rdp_settings.g_shade_smooth - - def F3DOrganizeLights(self, context): # Flag to prevent infinite recursion on update callback with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: @@ -278,92 +330,41 @@ def F3DOrganizeLights(self, context): self.f3d_light7 = lightList[6] if len(lightList) > 6 else None -def combiner_uses(material, checkList, is2Cycle): - display = False - for value in checkList: - if value[:5] == "TEXEL": - value1 = value - value2 = value.replace("0", "1") if "0" in value else value.replace("1", "0") - else: - value1 = value - value2 = value - - display |= material.combiner1.A == value1 - if is2Cycle: - display |= material.combiner2.A == value2 - - display |= material.combiner1.B == value1 - if is2Cycle: - display |= material.combiner2.B == value2 - - display |= material.combiner1.C == value1 - if is2Cycle: - display |= material.combiner2.C == value2 - - display |= material.combiner1.D == value1 - if is2Cycle: - display |= material.combiner2.D == value2 - - display |= material.combiner1.A_alpha == value1 - if is2Cycle: - display |= material.combiner2.A_alpha == value2 - - display |= material.combiner1.B_alpha == value1 - if is2Cycle: - display |= material.combiner2.B_alpha == value2 - - display |= material.combiner1.C_alpha == value1 - if is2Cycle: - display |= material.combiner2.C_alpha == value2 - - display |= material.combiner1.D_alpha == value1 - if is2Cycle: - display |= material.combiner2.D_alpha == value2 - - return display - - -def combiner_uses_alpha(material, checkList, is2Cycle): - display = False - for value in checkList: - if value[:5] == "TEXEL": - value1 = value - value2 = value.replace("0", "1") if "0" in value else value.replace("1", "0") - else: - value1 = value - value2 = value - - display |= material.combiner1.A_alpha == value1 - if is2Cycle: - display |= material.combiner2.A_alpha == value2 - - display |= material.combiner1.B_alpha == value1 - if is2Cycle: - display |= material.combiner2.B_alpha == value2 - - display |= material.combiner1.C_alpha == value1 - if is2Cycle: - display |= material.combiner2.C_alpha == value2 - - display |= material.combiner1.D_alpha == value1 - if is2Cycle: - display |= material.combiner2.D_alpha == value2 - - return display - - -CombinerUses = dict[str, bool] +def combiner_uses( + f3dMat: "F3DMaterialProperty", + checkList, + checkCycle1=True, + checkCycle2=True, + checkColor=True, + checkAlpha=True, + swapTexelsCycle2=True, +): + is2Cycle = f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" + for i in range(1, 3): + if i == 1 and not checkCycle1 or i == 2 and (not checkCycle2 or not is2Cycle): + continue + combiner = getattr(f3dMat, f"combiner{i}") + for isAlpha in [False, True]: + if not isAlpha and not checkColor or isAlpha and not checkAlpha: + continue + for letter in ["A", "B", "C", "D"]: + value = getattr(combiner, letter + ("_alpha" if isAlpha else "")) + if i == 2 and swapTexelsCycle2 and value.startswith("TEXEL"): + value = "TEXEL" + chr(ord(value[5]) ^ 1) # Swap 0 and 1 + if value in checkList: + return True + return False def combiner_uses_tex0(f3d_mat: "F3DMaterialProperty"): - return combiner_uses(f3d_mat, ["TEXEL0", "TEXEL0_ALPHA"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE") + return combiner_uses(f3d_mat, ["TEXEL0", "TEXEL0_ALPHA"]) def combiner_uses_tex1(f3d_mat: "F3DMaterialProperty"): - return combiner_uses(f3d_mat, ["TEXEL1", "TEXEL1_ALPHA"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE") + return combiner_uses(f3d_mat, ["TEXEL1", "TEXEL1_ALPHA"]) -def all_combiner_uses(f3d_mat: "F3DMaterialProperty") -> CombinerUses: +def all_combiner_uses(f3d_mat: "F3DMaterialProperty") -> dict[str, bool]: use_tex0 = combiner_uses_tex0(f3d_mat) use_tex1 = combiner_uses_tex1(f3d_mat) @@ -374,22 +375,14 @@ def all_combiner_uses(f3d_mat: "F3DMaterialProperty") -> CombinerUses: "Primitive": combiner_uses( f3d_mat, ["PRIMITIVE", "PRIMITIVE_ALPHA", "PRIM_LOD_FRAC"], - f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE", - ), - "Environment": combiner_uses( - f3d_mat, ["ENVIRONMENT", "ENV_ALPHA"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" - ), - "Shade": combiner_uses( - f3d_mat, ["SHADE", "SHADE_ALPHA"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" ), - "Shade Alpha": combiner_uses_alpha( - f3d_mat, ["SHADE"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" - ), - "Key": combiner_uses(f3d_mat, ["CENTER", "SCALE"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE"), - "LOD Fraction": combiner_uses( - f3d_mat, ["LOD_FRACTION"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" - ), - "Convert": combiner_uses(f3d_mat, ["K4", "K5"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE"), + "Environment": combiner_uses(f3d_mat, ["ENVIRONMENT", "ENV_ALPHA"]), + "Shade": combiner_uses(f3d_mat, ["SHADE"], checkAlpha=False), + "Shade Alpha": combiner_uses(f3d_mat, ["SHADE"], checkColor=False) + or combiner_uses(f3d_mat, ["SHADE_ALPHA"], checkAlpha=False), + "Key": combiner_uses(f3d_mat, ["CENTER", "SCALE"]), + "LOD Fraction": combiner_uses(f3d_mat, ["LOD_FRACTION"]), + "Convert": combiner_uses(f3d_mat, ["K4", "K5"]), } return useDict @@ -404,21 +397,133 @@ def ui_geo_mode(settings, dataHolder, layout, useDropdown): icon="TRIA_DOWN" if dataHolder.menu_geo else "TRIA_RIGHT", ) if not useDropdown or dataHolder.menu_geo: - inputGroup.prop(settings, "g_zbuffer", text="Z Buffer") - inputGroup.prop(settings, "g_shade", text="Shading") - inputGroup.prop(settings, "g_cull_front", text="Cull Front") - inputGroup.prop(settings, "g_cull_back", text="Cull Back") - inputGroup.prop(settings, "g_fog", text="Fog") - inputGroup.prop(settings, "g_lighting", text="Lighting") - inputGroup.prop(settings, "g_tex_gen", text="Texture UV Generate") - inputGroup.prop(settings, "g_tex_gen_linear", text="Texture UV Generate Linear") - inputGroup.prop(settings, "g_shade_smooth", text="Smooth Shading") - if bpy.context.scene.f3d_type == "F3DEX_GBI_2" or bpy.context.scene.f3d_type == "F3DEX_GBI": - inputGroup.prop(settings, "g_clipping", text="Clipping") - - -def ui_upper_mode(settings, dataHolder, layout: bpy.types.UILayout, useDropdown): - inputGroup: bpy.types.UILayout = layout.column() + + def indentGroup(parent: UILayout, textOrProp: Union[str, "F3DMaterialProperty"], isText: bool) -> UILayout: + c = parent.column(align=True) + if isText: + c.label(text=textOrProp) + else: + c.prop(settings, textOrProp) + if not getattr(settings, textOrProp): + return None + c = c.split(factor=0.1) + c.label(text="") + c = c.column(align=True) + return c + + isF3DEX3 = bpy.context.scene.f3d_type == "F3DEX3" + lightFxPrereq = isF3DEX3 and settings.g_lighting + if isinstance(dataHolder, F3DMaterialProperty): + ccWarnings = blendWarnings = True + ccUse = all_combiner_uses(dataHolder) + shadeInCC = ccUse["Shade"] or ccUse["Shade Alpha"] + blendUse = all_blender_uses(settings) + if blendUse is None: + blendWarnings = shadeInBlender = zInBlender = False + else: + shadeInBlender = blendUse["Shade Alpha"] + zInBlender = blendUse["Z Buffer"] + else: + ccWarnings = shadeInCC = False + blendWarnings = shadeInBlender = zInBlender = False + + inputGroup.prop(settings, "g_shade_smooth") + + c = indentGroup(inputGroup, "g_lighting", False) + if c is not None: + if ccWarnings and not shadeInCC and not settings.g_tex_gen: + c.label(text="Shade not used in CC, can disable lighting.", icon="INFO") + if isF3DEX3: + c.prop(settings, "g_packed_normals") + c.prop(settings, "g_lighting_specular") + c.prop(settings, "g_ambocclusion") + d = indentGroup(c, "g_tex_gen", False) + if d is not None: + d.prop(settings, "g_tex_gen_linear") + + if lightFxPrereq and settings.g_fresnel_color: + shadeColorLabel = "Fresnel" + elif not settings.g_lighting or (lightFxPrereq and settings.g_lighttoalpha): + shadeColorLabel = "Vertex color" + elif lightFxPrereq and settings.g_packed_normals and not settings.g_lighttoalpha: + shadeColorLabel = "Lighting * vertex color" + else: + shadeColorLabel = "Lighting" + if lightFxPrereq: + c = indentGroup(inputGroup, f"Shade color = {shadeColorLabel}:", True) + c.prop(settings, "g_fresnel_color") + else: + inputGroup.column().label(text=f"Shade color = {shadeColorLabel}") + + shadowMapInShadeAlpha = False + if settings.g_fog: + shadeAlphaLabel = "Fog" + elif lightFxPrereq and settings.g_fresnel_alpha: + shadeAlphaLabel = "Fresnel" + elif lightFxPrereq and settings.g_lighttoalpha: + shadeAlphaLabel = "Light intensity" + elif lightFxPrereq and settings.g_ambocclusion: + shadeAlphaLabel = "Shadow map / AO in vtx alpha" + shadowMapInShadeAlpha = True + else: + shadeAlphaLabel = "Vtx alpha" + c = indentGroup(inputGroup, f"Shade alpha = {shadeAlphaLabel}:", True) + if lightFxPrereq: + c.prop(settings, "g_lighttoalpha") + c.prop(settings, "g_fresnel_alpha") + c.prop(settings, "g_fog") + if lightFxPrereq and settings.g_fog and settings.g_fresnel_alpha: + c.label(text="Fog overrides Fresnel Alpha.", icon="ERROR") + if lightFxPrereq and settings.g_fog and settings.g_lighttoalpha: + c.label(text="Fog overrides Light-to-Alpha.", icon="ERROR") + if lightFxPrereq and settings.g_fresnel_alpha and settings.g_lighttoalpha: + c.label(text="Fresnel Alpha overrides Light-to-Alpha.", icon="ERROR") + if shadowMapInShadeAlpha and ccUse["Shade Alpha"]: + c.label(text="Shadow map = shade alpha used in CC, probably wrong.", icon="INFO") + if settings.g_fog and ccUse["Shade Alpha"]: + c.label(text="Fog = shade alpha used in CC, probably wrong.", icon="INFO") + if blendWarnings and shadeInBlender and not settings.g_fog: + c.label(text="Rendermode uses shade alpha, probably fog.", icon="INFO") + elif blendWarnings and not shadeInBlender and settings.g_fog: + c.label(text="Fog not used in rendermode / blender, can disable.", icon="INFO") + + if isF3DEX3: + c = indentGroup(inputGroup, "Attribute offsets:", True) + c.prop(settings, "g_attroffset_st_enable") + c.prop(settings, "g_attroffset_z_enable") + + c = indentGroup(inputGroup, "Face culling:", True) + c.prop(settings, "g_cull_front") + c.prop(settings, "g_cull_back") + if settings.g_cull_front and settings.g_cull_back: + c.label(text="Nothing will be drawn.", icon="ERROR") + + c = indentGroup(inputGroup, "Disable if not using:", True) + c.prop(settings, "g_zbuffer") + if blendWarnings and not settings.g_zbuffer and zInBlender: + c.label(text="Rendermode / blender using Z, must enable.", icon="ERROR") + elif blendWarnings and settings.g_zbuffer and not zInBlender: + c.label(text="Z is not being used, can disable.", icon="INFO") + c.prop(settings, "g_shade") + if ccWarnings and not settings.g_shade and (shadeInCC or shadeInBlender): + if shadeInCC and shadeInBlender: + where = "CC and blender" + elif shadeInCC: + where = "CC" + else: + where = "rendermode / blender" + c.label(text=f"Shade in use in {where}, must enable.", icon="ERROR") + elif ccWarnings and settings.g_shade and not shadeInCC and not shadeInBlender: + c.label(text="Shade is not being used, can disable.", icon="INFO") + + c = indentGroup(inputGroup, "Not useful:", True) + c.prop(settings, "g_lod") + if isUcodeF3DEX1(bpy.context.scene.f3d_type): + c.prop(settings, "g_clipping") + + +def ui_upper_mode(settings, dataHolder, layout: UILayout, useDropdown): + inputGroup: UILayout = layout.column() if useDropdown: inputGroup.prop( dataHolder, @@ -427,11 +532,8 @@ def ui_upper_mode(settings, dataHolder, layout: bpy.types.UILayout, useDropdown) icon="TRIA_DOWN" if dataHolder.menu_upper else "TRIA_RIGHT", ) if not useDropdown or dataHolder.menu_upper: - if not bpy.context.scene.isHWv1: - prop_split(inputGroup, settings, "g_mdsft_alpha_dither", "Alpha Dither") - prop_split(inputGroup, settings, "g_mdsft_rgb_dither", "RGB Dither") - else: - prop_split(inputGroup, settings, "g_mdsft_color_dither", "Color Dither") + prop_split(inputGroup, settings, "g_mdsft_alpha_dither", "Alpha Dither") + prop_split(inputGroup, settings, "g_mdsft_rgb_dither", "RGB Dither") prop_split(inputGroup, settings, "g_mdsft_combkey", "Chroma Key") prop_split(inputGroup, settings, "g_mdsft_textconv", "Texture Convert") prop_split(inputGroup, settings, "g_mdsft_text_filt", "Texture Filter") @@ -452,8 +554,8 @@ def ui_upper_mode(settings, dataHolder, layout: bpy.types.UILayout, useDropdown) prop_split(inputGroup, settings, "g_mdsft_pipeline", "Pipeline Span Buffer Coherency") -def ui_lower_mode(settings, dataHolder, layout: bpy.types.UILayout, useDropdown): - inputGroup: bpy.types.UILayout = layout.column() +def ui_lower_mode(settings, dataHolder, layout: UILayout, useDropdown): + inputGroup: UILayout = layout.column() if useDropdown: inputGroup.prop( dataHolder, @@ -463,8 +565,6 @@ def ui_lower_mode(settings, dataHolder, layout: bpy.types.UILayout, useDropdown) ) if not useDropdown or dataHolder.menu_lower: prop_split(inputGroup, settings, "g_mdsft_alpha_compare", "Alpha Compare") - if settings.g_mdsft_alpha_compare == "G_AC_THRESHOLD" and settings.g_mdsft_cycletype == "G_CYC_2CYCLE": - inputGroup.label(text="Compares blend alpha to *first cycle* combined (CC) alpha.") prop_split(inputGroup, settings, "g_mdsft_zsrcsel", "Z Source Selection") if settings.g_mdsft_zsrcsel == "G_ZS_PRIM": prim_box = inputGroup.box() @@ -484,7 +584,7 @@ def ui_other(settings, dataHolder, layout, useDropdown): clipRatioGroup = inputGroup.column() prop_split(clipRatioGroup, settings, "clip_ratio", "Clip Ratio") - if isinstance(dataHolder, bpy.types.Material) or isinstance(dataHolder, F3DMaterialProperty): + if isinstance(dataHolder, Material) or isinstance(dataHolder, F3DMaterialProperty): blend_color_group = layout.row() prop_input_name = blend_color_group.column() prop_input = blend_color_group.column() @@ -509,7 +609,7 @@ def tmemUsageUI(layout, textureProp): # shading = 1 # lighting = 1 # cycle type = 1 cycle -class F3DPanel(bpy.types.Panel): +class F3DPanel(Panel): bl_label = "F3D Material" bl_idname = "MATERIAL_PT_F3D_Inspector" bl_space_type = "PROPERTIES" @@ -589,19 +689,20 @@ def ui_chroma(self, material, layout, name, setName, setProp, showCheckBox): inputGroup = layout.row() prop_input_name = inputGroup.column() prop_input = inputGroup.column() + f3d_mat = material.f3d_mat if showCheckBox: - prop_input_name.prop(material, setName, text="Chroma Key") + prop_input_name.prop(f3d_mat, setName, text="Chroma Key") else: prop_input_name.label(text="Chroma Key") - prop_input.prop(material.f3d_mat, "key_center", text="Center") - prop_input.prop(material, "key_scale", text="Scale") - prop_input.prop(material, "key_width", text="Width") - if material.key_width[0] > 1 or material.key_width[1] > 1 or material.key_width[2] > 1: + prop_input.prop(f3d_mat, "key_center", text="Center") + prop_input.prop(f3d_mat, "key_scale", text="Scale") + prop_input.prop(f3d_mat, "key_width", text="Width") + if f3d_mat.key_width[0] > 1 or f3d_mat.key_width[1] > 1 or f3d_mat.key_width[2] > 1: layout.box().label(text="NOTE: Keying is disabled for channels with width > 1.") prop_input.enabled = setProp return inputGroup - def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: bpy.types.UILayout, name, showCheckBox): + def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: UILayout, name, showCheckBox): inputGroup = layout.row() prop_input_left = inputGroup.column() prop_input = inputGroup.column() @@ -611,7 +712,7 @@ def ui_lights(self, f3d_mat: "F3DMaterialProperty", layout: bpy.types.UILayout, prop_input_left.label(text=name) prop_input_left.enabled = f3d_mat.rdp_settings.g_lighting and f3d_mat.rdp_settings.g_shade - lightSettings: bpy.types.UILayout = prop_input.column() + lightSettings: UILayout = prop_input.column() if f3d_mat.rdp_settings.g_lighting: prop_input_left.separator(factor=0.25) light_controls = prop_input_left.box() @@ -713,6 +814,26 @@ def ui_lower_render_mode(self, material, layout, useDropdown): prop_split(renderGroup, material.rdp_settings, "alpha_cvg_sel", "Use Coverage For Alpha") prop_split(renderGroup, material.rdp_settings, "force_bl", "Force Blending") + if material.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE": + if ( + material.rdp_settings.blend_b1 == "G_BL_A_MEM" + or material.rdp_settings.blend_p1 == "G_BL_CLR_MEM" + or material.rdp_settings.blend_m1 == "G_BL_CLR_MEM" + ): + multilineLabel( + renderGroup.box(), + "RDP silicon bug: Framebuffer color / alpha in blender\n" + + "cycle 1 is broken, actually value from PREVIOUS pixel.", + "ORPHAN_DATA", + ) + if material.rdp_settings.blend_a2 == "G_BL_A_SHADE": + multilineLabel( + renderGroup.box(), + "RDP silicon bug: Shade alpha in blender cycle 2\n" + + "is broken, actually shade alpha from NEXT pixel.", + "ORPHAN_DATA", + ) + # cycle dependent - (P * A + M - B) / (A + B) combinerBox = renderGroup.box() combinerBox.label(text="Blender (Color = (P * A + M * B) / (A + B)") @@ -738,11 +859,7 @@ def ui_lower_render_mode(self, material, layout, useDropdown): renderGroup.enabled = material.rdp_settings.set_rendermode def ui_uvCheck(self, layout, context): - if ( - hasattr(context, "object") - and context.object is not None - and isinstance(context.object.data, bpy.types.Mesh) - ): + if hasattr(context, "object") and context.object is not None and isinstance(context.object.data, Mesh): uv_layers = context.object.data.uv_layers if uv_layers.active is None or uv_layers.active.name != "UVMap": uvErrorBox = layout.box() @@ -755,9 +872,45 @@ def ui_draw_layer(self, material, layout, context): elif context.scene.gameEditorMode == "OOT": prop_split(layout, material.f3d_mat.draw_layer, "oot", "Draw Layer") - def ui_fog(self, f3dMat, inputCol, showCheckBox): + def ui_misc(self, f3dMat: "F3DMaterialProperty", inputCol: UILayout, showCheckBox: bool) -> None: + if f3dMat.rdp_settings.g_ambocclusion: + if showCheckBox or f3dMat.set_ao: + inputGroup = inputCol.column() + if showCheckBox: + inputGroup.prop(f3dMat, "set_ao", text="Set Ambient Occlusion") + if f3dMat.set_ao: + prop_split(inputGroup.row(), f3dMat, "ao_ambient", "AO Ambient") + prop_split(inputGroup.row(), f3dMat, "ao_directional", "AO Directional") + prop_split(inputGroup.row(), f3dMat, "ao_point", "AO Point") + + if f3dMat.rdp_settings.g_fresnel_color or f3dMat.rdp_settings.g_fresnel_alpha: + if showCheckBox or f3dMat.set_fresnel: + inputGroup = inputCol.column() + if showCheckBox: + inputGroup.prop(f3dMat, "set_fresnel", text="Set Fresnel") + if f3dMat.set_fresnel: + prop_split(inputGroup.row(), f3dMat, "fresnel_lo", "Fresnel Lo") + prop_split(inputGroup.row(), f3dMat, "fresnel_hi", "Fresnel Hi") + + if f3dMat.rdp_settings.g_attroffset_st_enable: + if showCheckBox or f3dMat.set_attroffs_st: + inputGroup = inputCol.column() + if showCheckBox: + inputGroup.prop(f3dMat, "set_attroffs_st", text="Set ST Attr Offset") + if f3dMat.set_attroffs_st: + prop_split(inputGroup.row(), f3dMat, "attroffs_st", "ST Attr Offset") + + if f3dMat.rdp_settings.g_attroffset_z_enable: + if showCheckBox or f3dMat.set_attroffs_z: + inputGroup = inputCol.column() + if showCheckBox: + inputGroup.prop(f3dMat, "set_attroffs_z", text="Set Z Attr Offset") + if f3dMat.set_attroffs_z: + prop_split(inputGroup.row(), f3dMat, "attroffs_z", "Z Attr Offset") + if f3dMat.rdp_settings.g_fog: - inputGroup = inputCol.column() + if showCheckBox or f3dMat.set_fog: + inputGroup = inputCol.column() if showCheckBox: inputGroup.prop(f3dMat, "set_fog", text="Set Fog") if f3dMat.set_fog: @@ -765,20 +918,129 @@ def ui_fog(self, f3dMat, inputCol, showCheckBox): if f3dMat.use_global_fog: inputGroup.label(text="Only applies to levels (area fog settings).", icon="INFO") else: - fogColorGroup = inputGroup.row().split(factor=0.5) - fogColorGroup.label(text="Fog Color") - fogColorGroup.prop(f3dMat, "fog_color", text="") - fogPositionGroup = inputGroup.row().split(factor=0.5) - fogPositionGroup.label(text="Fog Range") - fogPositionGroup.prop(f3dMat, "fog_position", text="") - - def drawVertexColorNotice(self, layout): - noticeBox = layout.box().column() - noticeBox.label(text="There must be two vertex color layers.", icon="LINENUMBERS_ON") - noticeBox.label(text='They should be called "Col" and "Alpha".') + prop_split(inputGroup.row(), f3dMat, "fog_color", "Fog Color") + prop_split(inputGroup.row(), f3dMat, "fog_position", "Fog Range") + + def ui_cel_shading(self, material: Material, layout: UILayout): + inputGroup = layout.box().column() + r = inputGroup.row(align=True) + r.prop( + material.f3d_mat, + "expand_cel_shading_ui", + text="", + icon="TRIA_DOWN" if material.f3d_mat.expand_cel_shading_ui else "TRIA_RIGHT", + icon_only=True, + emboss=False, + ) + r.prop(material.f3d_mat, "use_cel_shading") + if not material.f3d_mat.expand_cel_shading_ui: + return + if not material.f3d_mat.use_cel_shading: + inputGroup = inputGroup.column() + inputGroup.enabled = False + cel = material.f3d_mat.cel_shading + prop_split(inputGroup.row(), cel, "tintPipeline", "Tint pipeline:") + prop_split(inputGroup.row(), cel, "cutoutSource", "Cutout:") + + if getZMode(material) != "ZMODE_OPA": + inputGroup.label(text="zmode in blender / rendermode must be opaque.", icon="ERROR") + + if cel.cutoutSource == "ENVIRONMENT": + if not material.f3d_mat.set_env or material.f3d_mat.env_color[3] != 1.0: + inputGroup.label(text="Enable env color, and set env alpha to 255.", icon="ERROR") + else: + tex = material.f3d_mat.tex0 if cel.cutoutSource == "TEXEL0" else material.f3d_mat.tex1 + if tex.tex is None or not tex.tex_set: + inputGroup.label(text=f"Texture {cel.cutoutSource[5]} is not set up correctly.", icon="ERROR") - def drawShadeAlphaNotice(self, layout): - layout.box().column().label(text='There must be a vertex color layer called "Alpha".', icon="IMAGE_ALPHA") + if ( + len(cel.levels) >= 3 + and cel.levels[0].threshMode == cel.levels[1].threshMode + and not all([cel.levels[0].threshMode == lvl.threshMode for lvl in cel.levels[1:]]) + ): + multilineLabel( + inputGroup.box(), + "If using both lighter and darker cel\n" + "levels, one of each must be at the beginning", + "ERROR", + ) + + r = inputGroup.row(align=True) + r.label(text="Cel levels:") + op = r.operator(CelLevelAdd.bl_idname, text="", icon="ADD") + op.materialName = material.name + if len(cel.levels) > 0: + op = r.operator(CelLevelRemove.bl_idname, text="", icon="REMOVE") + op.materialName = material.name + + showSegHelp = False + for level in cel.levels: + box = inputGroup.box().column() + r = box.row().split(factor=0.2) + r.label(text="Draw when") + r = r.split(factor=0.3) + r.prop(level, "threshMode", text="") + r = r.split(factor=0.2) + r.label(text="than") + r.prop(level, "threshold") + r = box.row().split(factor=0.08) + r.label(text="Tint:") + r = r.split(factor=0.27) + r.prop(level, "tintType", text="") + r = r.split(factor=0.45) + if level.tintType == "Fixed": + r.prop(level, "tintFixedLevel") + r = r.split(factor=0.3) + r.label(text="Color:") + r.prop(level, "tintFixedColor", text="") + elif level.tintType == "Segment": + r.prop(level, "tintSegmentNum") + r.prop(level, "tintSegmentOffset") + showSegHelp = True + elif level.tintType == "Light": + r.prop(level, "tintFixedLevel") + r.prop(level, "tintLightSlot") + else: + raise PluginError("Invalid tintType") + if showSegHelp: + tintName, tintNameCap = ("prim", "Prim") if cel.tintPipeline == "CC" else ("fog", "Fog") + multilineLabel( + inputGroup, + "Segments: In your code, set up DL in segment(s) used with\n" + + f"gsDPSet{tintNameCap}Color then gsSPEndDisplayList at appropriate offset\n" + + f"with {tintName} color = tint color and {tintName} alpha = tint level.", + "INFO", + ) + + def checkDrawLayersWarnings(self, f3dMat: "F3DMaterialProperty", useDict: Dict[str, bool], layout: UILayout): + settings = f3dMat.rdp_settings + isF3DEX3 = bpy.context.scene.f3d_type == "F3DEX3" + lightFxPrereq = isF3DEX3 and settings.g_lighting + + blendUse = all_blender_uses(settings) + anyUseShadeAlpha = useDict["Shade Alpha"] or (blendUse is not None and blendUse["Shade Alpha"]) + + g_lighting = settings.g_lighting + g_fog = settings.g_fog + g_packed_normals = lightFxPrereq and settings.g_packed_normals + g_ambocclusion = lightFxPrereq and settings.g_ambocclusion + g_lighttoalpha = lightFxPrereq and settings.g_lighttoalpha + g_fresnel_color = lightFxPrereq and settings.g_fresnel_color + g_fresnel_alpha = lightFxPrereq and settings.g_fresnel_alpha + + usesVertexColor = useDict["Shade"] and (not g_lighting or (g_packed_normals and not g_fresnel_color)) + usesVertexAlpha = anyUseShadeAlpha and (g_ambocclusion or not (g_fog or g_lighttoalpha or g_fresnel_alpha)) + if not usesVertexColor and not usesVertexAlpha: + return + noticeBox = layout.box().column() + if not usesVertexColor: + noticeBox.label(text='Mesh must have Color Attribute (vtx color) layer called "Alpha".', icon="IMAGE_ALPHA") + elif not usesVertexAlpha: + noticeBox.label( + text='Mesh must have Color Attribute (vtx color) layer called "Col".', icon="IMAGE_RGB_ALPHA" + ) + else: + noticeBox.label(text="Mesh must have two Color Attribute (vtx color) layers.", icon="IMAGE_RGB_ALPHA") + noticeBox.label(text='They must be called "Col" and "Alpha".', icon="IMAGE_ALPHA") def checkDrawMixedCIWarning(self, layout, useDict, f3dMat): useTex0 = useDict["Texture 0"] and f3dMat.tex0.tex_set @@ -798,10 +1060,7 @@ def draw_simple(self, f3dMat, material, layout, context): inputCol = layout.column() useDict = all_combiner_uses(f3dMat) - if not f3dMat.rdp_settings.g_lighting: - self.drawVertexColorNotice(layout) - elif useDict["Shade Alpha"]: - self.drawShadeAlphaNotice(layout) + self.checkDrawLayersWarnings(f3dMat, useDict, layout) useMultitexture = useDict["Texture 0"] and useDict["Texture 1"] and f3dMat.tex0.tex_set and f3dMat.tex1.tex_set @@ -836,10 +1095,9 @@ def draw_simple(self, f3dMat, material, layout, context): if useDict["Convert"] and f3dMat.set_k0_5: self.ui_convert(f3dMat, inputCol, False) - if f3dMat.set_fog: - self.ui_fog(f3dMat, inputCol, False) + self.ui_misc(f3dMat, inputCol, False) - def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): + def draw_full(self, f3dMat, material, layout: UILayout, context): layout.row().prop(material, "menu_tab", expand=True) menuTab = material.menu_tab useDict = all_combiner_uses(f3dMat) @@ -847,58 +1105,47 @@ def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): if menuTab == "Combiner": self.ui_draw_layer(material, layout, context) - if not f3dMat.rdp_settings.g_lighting: - self.drawVertexColorNotice(layout) - elif useDict["Shade Alpha"]: - self.drawShadeAlphaNotice(layout) + self.checkDrawLayersWarnings(f3dMat, useDict, layout) + + def drawCCProps(ui: UILayout, combiner: "CombinerProperty", isAlpha: bool, enabled: bool = True) -> None: + ui = ui.column() + ui.enabled = enabled + for letter in ["A", "B", "C", "D"]: + r = ui.row().split(factor=0.25 if isAlpha else 0.1) + r.label(text=f"{letter}{' Alpha' if isAlpha else ''}:") + r.prop(combiner, f"{letter}{'_alpha' if isAlpha else ''}", text="") + + isTwoCycle = f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" combinerBox = layout.box() combinerBox.prop(f3dMat, "set_combiner", text="Color Combiner (Color = (A - B) * C + D)") - combinerCol = combinerBox.row() + combinerCol = combinerBox.row().split(factor=0.45) combinerCol.enabled = f3dMat.set_combiner - rowColor = combinerCol.column() - rowAlpha = combinerCol.column() - - rowColor.prop(f3dMat.combiner1, "A") - rowColor.prop(f3dMat.combiner1, "B") - rowColor.prop(f3dMat.combiner1, "C") - rowColor.prop(f3dMat.combiner1, "D") - rowAlpha.prop(f3dMat.combiner1, "A_alpha") - rowAlpha.prop(f3dMat.combiner1, "B_alpha") - rowAlpha.prop(f3dMat.combiner1, "C_alpha") - rowAlpha.prop(f3dMat.combiner1, "D_alpha") - if ( - f3dMat.rdp_settings.g_mdsft_alpha_compare == "G_AC_THRESHOLD" - and f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" - ): - combinerBox.label(text="First cycle alpha out used for compare threshold.") + drawCCProps(combinerCol, f3dMat.combiner1, False) + drawCCProps(combinerCol, f3dMat.combiner1, True, not f3dMat.use_cel_shading) + if f3dMat.use_cel_shading: + r = combinerBox.column().label( + text=f"CC alpha{' cycle 1' if isTwoCycle else ''} is occupied by cel shading." + ) - if f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE": + if isTwoCycle: combinerBox2 = layout.box() combinerBox2.label(text="Color Combiner Cycle 2") combinerBox2.enabled = f3dMat.set_combiner - combinerCol2 = combinerBox2.row() - rowColor2 = combinerCol2.column() - rowAlpha2 = combinerCol2.column() - - rowColor2.prop(f3dMat.combiner2, "A") - rowColor2.prop(f3dMat.combiner2, "B") - rowColor2.prop(f3dMat.combiner2, "C") - rowColor2.prop(f3dMat.combiner2, "D") - rowAlpha2.prop(f3dMat.combiner2, "A_alpha") - rowAlpha2.prop(f3dMat.combiner2, "B_alpha") - rowAlpha2.prop(f3dMat.combiner2, "C_alpha") - rowAlpha2.prop(f3dMat.combiner2, "D_alpha") - - if useDict["Texture 0"]: - cc_list = ["A", "B", "C", "D", "A_alpha", "B_alpha", "C_alpha", "D_alpha"] - if len([c for c in cc_list if getattr(f3dMat.combiner2, c) == "TEXEL1"]): - combinerBox2.label( - text="Warning: Using 'Texture 1' in Cycle 2 can cause display issues!", - icon="LIBRARY_DATA_BROKEN", - ) + combinerCol2 = combinerBox2.row().split(factor=0.45) + drawCCProps(combinerCol2, f3dMat.combiner2, False) + drawCCProps(combinerCol2, f3dMat.combiner2, True) + + if combiner_uses(f3dMat, ["TEXEL0", "TEXEL0_ALPHA"], checkCycle1=False, swapTexelsCycle2=False): + combinerBox2.label(text="'Texture 0' in Cycle 2 is actually Texture 1.", icon="INFO") + if combiner_uses(f3dMat, ["TEXEL1", "TEXEL1_ALPHA"], checkCycle1=False, swapTexelsCycle2=False): + multilineLabel( + combinerBox2, + "RDP silicon bug: 'Texture 1' in Cycle 2 is actually\n" + + "Texture 0 for the NEXT pixel, causes visual issues.", + "ORPHAN_DATA", + ) - combinerBox2.label(text="Note: In second cycle, texture 0 and texture 1 are flipped.") if menuTab == "Sources": self.ui_uvCheck(layout, context) @@ -936,7 +1183,7 @@ def draw_full(self, f3dMat, material, layout: bpy.types.UILayout, context): if useDict["Convert"]: self.ui_convert(f3dMat, inputCol, True) - self.ui_fog(f3dMat, inputCol, True) + self.ui_misc(f3dMat, inputCol, True) if menuTab == "Geo": ui_geo_mode(f3dMat.rdp_settings, f3dMat, layout, False) @@ -961,6 +1208,7 @@ def draw(self, context): return f3dMat = material.f3d_mat + settings = f3dMat.rdp_settings layout.prop(context.scene, "f3d_simple", text="Show Simplified UI") layout = layout.box() titleCol = layout.column() @@ -971,8 +1219,16 @@ def draw(self, context): split.label(text="Preset") row = split.row(align=True) row.menu(MATERIAL_MT_f3d_presets.__name__, text=f3dMat.presetName) - row.operator(AddPresetF3D.bl_idname, text="", icon="ZOOM_IN") - row.operator(AddPresetF3D.bl_idname, text="", icon="ZOOM_OUT").remove_active = True + row.operator(AddPresetF3D.bl_idname, text="", icon="ADD") + row.operator(AddPresetF3D.bl_idname, text="", icon="REMOVE").remove_active = True + + if settings.g_mdsft_alpha_compare == "G_AC_THRESHOLD" and settings.g_mdsft_cycletype == "G_CYC_2CYCLE": + multilineLabel( + layout.box(), + "RDP silicon bug: Alpha compare in 2-cycle mode is broken.\n" + + "Compares to FIRST cycle CC alpha output from NEXT pixel.", + "ORPHAN_DATA", + ) if context.scene.f3d_simple and f3dMat.presetName != "Custom": self.draw_simple(f3dMat, material, layout, context) @@ -980,6 +1236,13 @@ def draw(self, context): presetCol.prop(context.scene, "f3dUserPresetsOnly") self.draw_full(f3dMat, material, layout, context) + if context.scene.f3d_type == "F3DEX3": + self.ui_cel_shading(material, layout) + else: + r = layout.row() + r.enabled = False + r.label(text="Use Cel Shading (requires F3DEX3)", icon="TRIA_RIGHT") + def ui_tileScroll(tex, name, layout): row = layout.row() @@ -1093,6 +1356,22 @@ def update_light_properties(self, context): update_light_colors(material, context) +def update_cel_cutout_source(self, context): + with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: + if not material: + return + if not material.f3d_mat.use_cel_shading: + return + + f3dMat = material.f3d_mat + cel = f3dMat.cel_shading + firstDarker = len(cel.levels) >= 1 and cel.levels[0].threshMode == "Darker" + + f3dMat.combiner1.A_alpha, f3dMat.combiner1.B_alpha = ("1", "SHADE") if firstDarker else ("SHADE", "0") + f3dMat.combiner1.C_alpha = cel.cutoutSource + f3dMat.combiner1.D_alpha = "0" + + def getSocketFromCombinerToNodeDictColor(nodes, combinerInput): nodeName, socketIndex = combinerToNodeDictColor[combinerInput] return nodes[nodeName].outputs[socketIndex] if nodeName is not None else None @@ -1143,14 +1422,14 @@ def getSocketFromCombinerToNodeDictAlpha(nodes, combinerInput): } -def remove_first_link_if_exists(material: bpy.types.Material, links: tuple[bpy.types.NodeLink]): +def remove_first_link_if_exists(material: Material, links: tuple[NodeLink]): if len(links) > 0: link = links[0] material.node_tree.links.remove(link) def link_if_none_exist( - material: bpy.types.Material, fromOutput: bpy.types.NodeSocket, toInput: bpy.types.NodeSocket + material: Material, fromOutput: NodeSocket, toInput: NodeSocket ): # TODO: (V5) add output/input type annotations if len(fromOutput.links) == 0: material.node_tree.links.new(fromOutput, toInput) @@ -1207,7 +1486,7 @@ def update_node_combiner(material, combinerInputs, cycleIndex): material.node_tree.links.new(cycle_node.inputs[i], input_value) -def check_fog_settings(material: bpy.types.Material): +def check_fog_settings(material: Material): f3dMat: "F3DMaterialProperty" = material.f3d_mat fog_enabled: bool = f3dMat.rdp_settings.g_fog fog_rendermode_enabled: bool = fog_enabled @@ -1236,7 +1515,7 @@ def check_fog_settings(material: bpy.types.Material): return fog_enabled, fog_rendermode_enabled -def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): +def update_fog_nodes(material: Material, context: Context): nodes = material.node_tree.nodes f3dMat: "F3DMaterialProperty" = material.f3d_mat @@ -1244,7 +1523,7 @@ def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): nodes["Shade Color"].inputs["Fog"].default_value = int(fog_enabled) - fogBlender: bpy.types.ShaderNodeGroup = nodes["FogBlender"] + fogBlender: ShaderNodeGroup = nodes["FogBlender"] if fog_rendermode_enabled and fog_enabled: fogBlender.node_tree = bpy.data.node_groups["FogBlender_On"] else: @@ -1269,7 +1548,7 @@ def update_fog_nodes(material: bpy.types.Material, context: bpy.types.Context): nodes["CalcFog"].inputs["FogFar"].default_value = f3dMat.fog_position[1] -def update_noise_nodes(material: bpy.types.Material): +def update_noise_nodes(material: Material): f3dMat: "F3DMaterialProperty" = material.f3d_mat uses_noise = f3dMat.combiner1.A == "NOISE" or f3dMat.combiner2.A == "NOISE" noise_group = bpy.data.node_groups["F3DNoise_Animated" if uses_noise else "F3DNoise_NonAnimated"] @@ -1279,9 +1558,7 @@ def update_noise_nodes(material: bpy.types.Material): nodes["F3DNoiseFactor"].node_tree = noise_group -def update_combiner_connections( - material: bpy.types.Material, context: bpy.types.Context, combiner: (int | None) = None -): +def update_combiner_connections(material: Material, context: Context, combiner: (int | None) = None): f3dMat: "F3DMaterialProperty" = material.f3d_mat update_noise_nodes(material) @@ -1314,7 +1591,7 @@ def update_combiner_connections( update_node_combiner(material, combinerInputs2, 2) -def set_output_node_groups(material: bpy.types.Material): +def set_output_node_groups(material: Material): nodes = material.node_tree.nodes f3dMat: "F3DMaterialProperty" = material.f3d_mat is_one_cycle = f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_1CYCLE" @@ -1390,7 +1667,7 @@ def update_color_node(combiner_inputs, color: Color, prefix: str): # prim_color | Prim # env_color | Env def get_color_input_update_callback(attr_name="", prefix=""): - def input_update_callback(self: bpy.types.Material, context: bpy.types.Context): + def input_update_callback(self: Material, context: Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return @@ -1402,7 +1679,7 @@ def input_update_callback(self: bpy.types.Material, context: bpy.types.Context): return input_update_callback -def update_node_values_of_material(material: bpy.types.Material, context): +def update_node_values_of_material(material: Material, context): nodes = material.node_tree.nodes update_blend_method(material, context) @@ -1454,9 +1731,9 @@ def update_node_values_of_material(material: bpy.types.Material, context): update_fog_nodes(material, context) -def set_texture_settings_node(material: bpy.types.Material): +def set_texture_settings_node(material: Material): nodes = material.node_tree.nodes - textureSettings: bpy.types.ShaderNodeGroup = nodes["TextureSettings"] + textureSettings: ShaderNodeGroup = nodes["TextureSettings"] desired_group = bpy.data.node_groups["TextureSettings_Lite"] if (material.f3d_mat.tex0.tex and not material.f3d_mat.tex0.autoprop) or ( @@ -1479,7 +1756,7 @@ def setAutoProp(fieldProperty, pixelLength): def set_texture_size(self, tex_size, tex_index): nodes = self.node_tree.nodes - uv_basis: bpy.types.ShaderNodeGroup = nodes["UV Basis"] + uv_basis: ShaderNodeGroup = nodes["UV Basis"] inputs = uv_basis.inputs inputs[f"{tex_index} S TexSize"].default_value = tex_size[0] @@ -1490,11 +1767,9 @@ def trunc_10_2(val: float): return int(val * 4) / 4 -def update_tex_values_field( - self: bpy.types.Material, texProperty: "TextureProperty", tex_size: list[int], tex_index: int -): +def update_tex_values_field(self: Material, texProperty: "TextureProperty", tex_size: list[int], tex_index: int): nodes = self.node_tree.nodes - textureSettings: bpy.types.ShaderNodeGroup = nodes["TextureSettings"] + textureSettings: ShaderNodeGroup = nodes["TextureSettings"] inputs = textureSettings.inputs set_texture_size(self, tex_size, tex_index) @@ -1530,19 +1805,19 @@ def update_tex_values_field( inputs[str_index + " T Shift"].default_value = texProperty.T.shift -def iter_tex_nodes(node_tree: bpy.types.NodeTree, texIndex: int) -> Generator[bpy.types.TextureNodeImage, None, None]: +def iter_tex_nodes(node_tree: NodeTree, texIndex: int) -> Generator[TextureNodeImage, None, None]: for i in range(1, 5): nodeName = f"Tex{texIndex}_{i}" if node_tree.nodes.get(nodeName): yield node_tree.nodes[nodeName] -def toggle_texture_node_muting(material: bpy.types.Material, texIndex: int, isUsed: bool): +def toggle_texture_node_muting(material: Material, texIndex: int, isUsed: bool): node_tree = material.node_tree f3dMat: "F3DMaterialProperty" = material.f3d_mat # Enforce typing from generator - texNode: None | bpy.types.TextureNodeImage = None + texNode: None | TextureNodeImage = None node_3point_key = "3 Point Lerp" if texIndex == 0 else "3 Point Lerp.001" node_3point = node_tree.nodes.get(node_3point_key) @@ -1566,7 +1841,7 @@ def toggle_texture_node_muting(material: bpy.types.Material, texIndex: int, isUs def set_texture_nodes_settings( - material: bpy.types.Material, texProperty: "TextureProperty", texIndex: int, isUsed: bool + material: Material, texProperty: "TextureProperty", texIndex: int, isUsed: bool ) -> list[int] | None: node_tree = material.node_tree f3dMat: "F3DMaterialProperty" = material.f3d_mat @@ -1580,7 +1855,7 @@ def set_texture_nodes_settings( return texSize # Enforce typing from generator - texNode: None | bpy.types.TextureNodeImage = None + texNode: None | TextureNodeImage = None for texNode in iter_tex_nodes(node_tree, texIndex): if texNode.image is not texProperty.tex: texNode.image = texProperty.tex @@ -1597,7 +1872,7 @@ def set_texture_nodes_settings( return texSize -def update_tex_values_index(self: bpy.types.Material, *, texProperty: "TextureProperty", texIndex: int, isUsed: bool): +def update_tex_values_index(self: Material, *, texProperty: "TextureProperty", texIndex: int, isUsed: bool): nodes = self.node_tree.nodes tex_size = set_texture_nodes_settings(self, texProperty, texIndex, isUsed) @@ -1670,11 +1945,11 @@ def get_tex_gen_size(tex_size: list[int | float]): return (tex_size[0] - 1) / 1024, (tex_size[1] - 1) / 1024 -def update_tex_values_manual(material: bpy.types.Material, context, prop_path=None): +def update_tex_values_manual(material: Material, context, prop_path=None): f3dMat: "F3DMaterialProperty" = material.f3d_mat nodes = material.node_tree.nodes texture_settings = nodes["TextureSettings"] - texture_inputs: bpy.types.NodeInputs = texture_settings.inputs + texture_inputs: NodeInputs = texture_settings.inputs useDict = all_combiner_uses(f3dMat) tex0_used = useDict["Texture 0"] and f3dMat.tex0.tex is not None @@ -1707,7 +1982,7 @@ def update_tex_values_manual(material: bpy.types.Material, context, prop_path=No texture_inputs["1 S TexSize"].default_value = f3dMat.tex1.tex.size[0] texture_inputs["1 T TexSize"].default_value = f3dMat.tex1.tex.size[0] - uv_basis: bpy.types.ShaderNodeGroup = nodes["UV Basis"] + uv_basis: ShaderNodeGroup = nodes["UV Basis"] if f3dMat.uv_basis == "TEXEL0": uv_basis.node_tree = bpy.data.node_groups["UV Basis 0"] else: @@ -1787,21 +2062,18 @@ def update_preset_manual(material, context): def update_preset_manual_v4(material, preset): - override = bpy.context.copy() - override["material"] = material if preset == "Shaded Solid": preset = "sm64_shaded_solid" if preset == "Shaded Texture": preset = "sm64_shaded_texture" if preset.lower() != "custom": material.f3d_update_flag = True - bpy.ops.script.execute_preset( - override, filepath=findF3DPresetPath(preset), menu_idname="MATERIAL_MT_f3d_presets" - ) + with bpy.context.temp_override(material=material): + bpy.ops.script.execute_preset(filepath=findF3DPresetPath(preset), menu_idname="MATERIAL_MT_f3d_presets") material.f3d_update_flag = False -def has_f3d_nodes(material: bpy.types.Material): +def has_f3d_nodes(material: Material): return "Material Output F3D" in material.node_tree.nodes @@ -1845,8 +2117,13 @@ def createOrUpdateSceneProperties(): if upgrade_group and group: # Need to upgrade; remove old outputs - for out in group.outputs: - group.outputs.remove(out) + if bpy.app.version >= (4, 0, 0): + for item in group.interface.items_tree: + if item.item_type == "SOCKET" and item.in_out == "OUTPUT": + group.interface.remove(item) + else: + for out in group.outputs: + group.outputs.remove(out) new_group = group else: logger.info("Creating Scene Properties") @@ -1858,27 +2135,62 @@ def createOrUpdateSceneProperties(): new_group["version"] = SCENE_PROPERTIES_VERSION # Create outputs - _nodeFogEnable: bpy.types.NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogEnable") - _nodeFogColor: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketColor", "FogColor") - _nodeF3D_NearClip: bpy.types.NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_NearClip") - _nodeF3D_FarClip: bpy.types.NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_FarClip") - _nodeBlender_Game_Scale: bpy.types.NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "Blender_Game_Scale") - _nodeFogNear: bpy.types.NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogNear") - _nodeFogFar: bpy.types.NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogFar") - _nodeShadeColor: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketColor", "ShadeColor") - _nodeAmbientColor: bpy.types.NodeSocketColor = new_group.outputs.new("NodeSocketColor", "AmbientColor") - _nodeLightDirection: bpy.types.NodeSocketVectorDirection = new_group.outputs.new( - "NodeSocketVectorDirection", "LightDirection" - ) + if bpy.app.version >= (4, 0, 0): + tree_interface = new_group.interface + + _nodeFogEnable: NodeSocketFloat = tree_interface.new_socket( + "FogEnable", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeFogColor: NodeSocketColor = tree_interface.new_socket( + "FogColor", socket_type="NodeSocketColor", in_out="OUTPUT" + ) + _nodeF3D_NearClip: NodeSocketFloat = tree_interface.new_socket( + "F3D_NearClip", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeF3D_FarClip: NodeSocketFloat = tree_interface.new_socket( + "F3D_FarClip", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeBlender_Game_Scale: NodeSocketFloat = tree_interface.new_socket( + "Blender_Game_Scale", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeFogNear: NodeSocketFloat = tree_interface.new_socket( + "FogNear", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeFogFar: NodeSocketFloat = tree_interface.new_socket( + "FogFar", socket_type="NodeSocketFloat", in_out="OUTPUT" + ) + _nodeShadeColor: NodeSocketColor = tree_interface.new_socket( + "ShadeColor", socket_type="NodeSocketColor", in_out="OUTPUT" + ) + _nodeAmbientColor: NodeSocketColor = tree_interface.new_socket( + "AmbientColor", socket_type="NodeSocketColor", in_out="OUTPUT" + ) + _nodeLightDirection: NodeSocketVector = tree_interface.new_socket( + "LightDirection", socket_type="NodeSocketVector", in_out="OUTPUT" + ) + + else: + _nodeFogEnable: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogEnable") + _nodeFogColor: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "FogColor") + _nodeF3D_NearClip: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_NearClip") + _nodeF3D_FarClip: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "F3D_FarClip") + _nodeBlender_Game_Scale: NodeSocketFloat = new_group.outputs.new("NodeSocketFloat", "Blender_Game_Scale") + _nodeFogNear: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogNear") + _nodeFogFar: NodeSocketInt = new_group.outputs.new("NodeSocketInt", "FogFar") + _nodeShadeColor: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "ShadeColor") + _nodeAmbientColor: NodeSocketColor = new_group.outputs.new("NodeSocketColor", "AmbientColor") + _nodeLightDirection: NodeSocketVectorDirection = new_group.outputs.new( + "NodeSocketVectorDirection", "LightDirection" + ) # Set outputs from render settings - sceneOutputs: bpy.types.NodeGroupOutput = new_group.nodes["Group Output"] + sceneOutputs: NodeGroupOutput = new_group.nodes["Group Output"] renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings update_scene_props_from_render_settings(bpy.context, sceneOutputs, renderSettings) -def createScenePropertiesForMaterial(material: bpy.types.Material): +def createScenePropertiesForMaterial(material: Material): node_tree = material.node_tree # Either create or update SceneProperties if needed @@ -1933,16 +2245,16 @@ def link_f3d_material_library(): bpy.ops.object.mode_set(mode=get_mode_set_from_context_mode(prevMode)) -def shouldConvOrCreateColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): +def shouldConvOrCreateColorAttribute(mesh: Mesh, attr_name="Col"): has_attr, conv_attr = False, False if attr_name in mesh.attributes: - attribute: bpy.types.Attribute = mesh.attributes[attr_name] + attribute: Attribute = mesh.attributes[attr_name] has_attr = True conv_attr = attribute.data_type != "FLOAT_COLOR" or attribute.domain != "CORNER" return has_attr, conv_attr -def convertColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): +def convertColorAttribute(mesh: Mesh, attr_name="Col"): prev_index = mesh.attributes.active_index attr_index = mesh.attributes.find(attr_name) if attr_index < 0: @@ -1953,8 +2265,8 @@ def convertColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): mesh.attributes.active_index = prev_index -def addColorAttributesToModel(obj: bpy.types.Object): - if not isinstance(obj.data, bpy.types.Mesh): +def addColorAttributesToModel(obj: Object): + if obj.type != "MESH": return prevMode = bpy.context.mode @@ -1963,7 +2275,7 @@ def addColorAttributesToModel(obj: bpy.types.Object): selectSingleObject(obj) - mesh: bpy.types.Mesh = obj.data + mesh: Mesh = obj.data conv_col, has_col = shouldConvOrCreateColorAttribute(mesh, attr_name="Col") if conv_col: @@ -1981,7 +2293,7 @@ def addColorAttributesToModel(obj: bpy.types.Object): bpy.ops.object.mode_set(mode=get_mode_set_from_context_mode(prevMode)) -def createF3DMat(obj: bpy.types.Object | None, preset="Shaded Solid", index=None): +def createF3DMat(obj: Object | None, preset="Shaded Solid", index=None): # link all node_groups + material from addon's data .blend link_f3d_material_library() @@ -2025,7 +2337,7 @@ def reloadDefaultF3DPresets(): update_preset_manual_v4(material, presetNameToFilename[material.f3d_mat.presetName]) -class CreateFast3DMaterial(bpy.types.Operator): +class CreateFast3DMaterial(Operator): bl_idname = "object.create_f3d_mat" bl_label = "Create Fast3D Material" bl_options = {"REGISTER", "UNDO", "PRESET"} @@ -2041,7 +2353,7 @@ def execute(self, context): return {"FINISHED"} -class ReloadDefaultF3DPresets(bpy.types.Operator): +class ReloadDefaultF3DPresets(Operator): bl_idname = "object.reload_f3d_presets" bl_label = "Reload Default Fast3D Presets" bl_options = {"REGISTER", "UNDO", "PRESET"} @@ -2052,18 +2364,18 @@ def execute(self, context): return {"FINISHED"} -def get_tex_prop_from_path(material: bpy.types.Material, path: str) -> Tuple["TextureProperty", int]: +def get_tex_prop_from_path(material: Material, path: str) -> Tuple["TextureProperty", int]: if "tex0" in path: return material.f3d_mat.tex0, 0 return material.f3d_mat.tex1, 1 -def already_updating_material(material: bpy.types.Material | None): +def already_updating_material(material: Material | None): """Check if material is updating already""" return getattr(material, "f3d_update_flag", False) -def update_tex_field_prop(self: bpy.types.Property, context: bpy.types.Context): +def update_tex_field_prop(self: Property, context: Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return @@ -2077,7 +2389,7 @@ def update_tex_field_prop(self: bpy.types.Property, context: bpy.types.Context): set_texture_settings_node(material) -def toggle_auto_prop(self, context: bpy.types.Context): +def toggle_auto_prop(self, context: Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return @@ -2094,7 +2406,7 @@ def toggle_auto_prop(self, context: bpy.types.Context): set_texture_settings_node(material) -class TextureFieldProperty(bpy.types.PropertyGroup): +class TextureFieldProperty(PropertyGroup): clamp: bpy.props.BoolProperty( name="Clamp", update=update_tex_field_prop, @@ -2133,7 +2445,7 @@ def key(self): return (self.clamp, self.mirror, round(self.low * 4), round(self.high * 4), self.mask, self.shift) -class SetTileSizeScrollProperty(bpy.types.PropertyGroup): +class SetTileSizeScrollProperty(PropertyGroup): s: bpy.props.IntProperty(min=-4095, max=4095, default=0) t: bpy.props.IntProperty(min=-4095, max=4095, default=0) interval: bpy.props.IntProperty(min=1, soft_max=1000, default=1) @@ -2142,9 +2454,9 @@ def key(self): return (self.s, self.t, self.interval) -class TextureProperty(bpy.types.PropertyGroup): +class TextureProperty(PropertyGroup): tex: bpy.props.PointerProperty( - type=bpy.types.Image, + type=Image, name="Texture", update=update_tex_values_and_formats, ) @@ -2239,7 +2551,7 @@ def on_tex_autoprop(texProperty, context): setAutoProp(texProperty.T, tex_size[1]) -def update_combiner_connections_and_preset(self, context: bpy.types.Context): +def update_combiner_connections_and_preset(self, context: Context): with F3DMaterial_UpdateLock(get_material_from_context(context)) as material: if not material: return @@ -2258,8 +2570,8 @@ def update_combiner_connections_and_preset(self, context: bpy.types.Context): def ui_image( canUseLargeTextures: bool, - layout: bpy.types.UILayout, - material: bpy.types.Material, + layout: UILayout, + material: Material, textureProp: TextureProperty, name: str, showCheckBox: bool, @@ -2379,7 +2691,7 @@ def ui_image( high.prop(textureProp.T, "high", text="T High") -class CombinerProperty(bpy.types.PropertyGroup): +class CombinerProperty(PropertyGroup): A: bpy.props.EnumProperty( name="A", description="A", @@ -2457,7 +2769,7 @@ def key(self): ) -class ProceduralAnimProperty(bpy.types.PropertyGroup): +class ProceduralAnimProperty(PropertyGroup): speed: bpy.props.FloatProperty(name="Speed", default=1) amplitude: bpy.props.FloatProperty(name="Amplitude", default=1) frequency: bpy.props.FloatProperty(name="Frequency", default=1) @@ -2481,7 +2793,7 @@ def key(self): ) -class ProcAnimVectorProperty(bpy.types.PropertyGroup): +class ProcAnimVectorProperty(PropertyGroup): x: bpy.props.PointerProperty(type=ProceduralAnimProperty) y: bpy.props.PointerProperty(type=ProceduralAnimProperty) z: bpy.props.PointerProperty(type=ProceduralAnimProperty) @@ -2500,7 +2812,7 @@ def key(self): ) -class PrimDepthSettings(bpy.types.PropertyGroup): +class PrimDepthSettings(PropertyGroup): z: bpy.props.IntProperty( name="Prim Depth: Z", default=0, @@ -2531,64 +2843,122 @@ def key(self): return (self.z, self.dz) -class RDPSettings(bpy.types.PropertyGroup): +class RDPSettings(PropertyGroup): g_zbuffer: bpy.props.BoolProperty( name="Z Buffer", default=True, update=update_node_values_with_preset, - description="Enables calculation of Z value for primitives. Disable if not reading or writing Z-Buffer in the blender" + description="Enables calculation of Z value for primitives. Disable if not reading or writing Z-Buffer in the blender", ) g_shade: bpy.props.BoolProperty( name="Shading", default=True, update=update_node_values_with_preset, - description="Computes shade coordinates for primitives. Disable if not using lighting, vertex colors or fog" + description="Computes shade coordinates for primitives. Disable if not using lighting, vertex colors or fog", + ) + g_ambocclusion: bpy.props.BoolProperty( + name="Ambient Occlusion", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Scales each type light intensity differently with vertex alpha. Bake scene shadows / AO into vertex alpha, not vertex color", + ) + g_attroffset_z_enable: bpy.props.BoolProperty( + name="Z Offset (for decal fix)", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Enables offset to vertex Z. To fix decals, set the Z mode to opaque and enable this", + ) + g_attroffset_st_enable: bpy.props.BoolProperty( + name="ST Offset (for UV scroll)", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Enables offsets to vertex ST values, usually for UV scrolling", ) # v1/2 difference g_cull_front: bpy.props.BoolProperty( name="Cull Front", + default=False, update=update_node_values_with_preset, - description="Disables drawing of front faces" + description="Disables drawing of front faces", ) # v1/2 difference g_cull_back: bpy.props.BoolProperty( name="Cull Back", default=True, update=update_node_values_with_preset, - description="Disables drawing of back faces" + description="Disables drawing of back faces", + ) + g_packed_normals: bpy.props.BoolProperty( + name="Packed Normals (Vtx Colors + Lighting)", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Packs vertex normals in unused 16 bits of each vertex, enabling simultaneous vertex colors and lighting", + ) + g_lighttoalpha: bpy.props.BoolProperty( + name="Light to Alpha (for cel shading)", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Moves light intensity to shade alpha, used for cel shading and other effects", + ) + g_lighting_specular: bpy.props.BoolProperty( + name="Specular Lighting", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Microcode lighting computes specular instead of diffuse component. If using, must set size field of every light in code", + ) + g_fresnel_color: bpy.props.BoolProperty( + name="Fresnel to Color", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Shade color derived from how much each vertex normal faces the camera. For bump mapping", + ) + g_fresnel_alpha: bpy.props.BoolProperty( + name="Fresnel to Alpha", + default=False, + update=update_node_values_with_preset, + description="F3DEX3: Shade alpha derived from how much each vertex normal faces the camera. For water, glass, ghosts, etc., or toon outlines", ) g_fog: bpy.props.BoolProperty( name="Fog", + default=False, update=update_node_values_with_preset, - description="Turns on/off fog calculation. Fog variable gets stored into shade alpha" + description="Turns on/off fog calculation. Fog variable gets stored into shade alpha", ) g_lighting: bpy.props.BoolProperty( name="Lighting", default=True, update=update_node_values_with_preset, - description="Enables calculation shade color using lights. Turn off for vertex colors as shade color" + description="Enables calculating shade color using lights. Turn off for vertex colors as shade color", ) g_tex_gen: bpy.props.BoolProperty( name="Texture UV Generate", + default=False, update=update_node_values_with_preset, - description="Generates texture coordinates for reflection mapping based on vertex normals and lookat direction. On a skybox texture, maps the sky to the center of the texture and the ground to a circle inscribed in the border. Requires lighting enabled to use" + description="Generates texture coordinates for reflection mapping based on vertex normals and lookat direction. On a skybox texture, maps the sky to the center of the texture and the ground to a circle inscribed in the border. Requires lighting enabled to use", ) g_tex_gen_linear: bpy.props.BoolProperty( name="Texture UV Generate Linear", + default=False, update=update_node_values_with_preset, - description="Modifies the texgen mapping; enable with texgen. Use a normal panorama image for the texture, with the sky at the top and the ground at the bottom. Requires lighting enabled to use" + description="Modifies the texgen mapping; enable with texgen. Use a normal panorama image for the texture, with the sky at the top and the ground at the bottom. Requires lighting enabled to use", + ) + g_lod: bpy.props.BoolProperty( + name="LoD (does nothing)", + default=False, + update=update_node_values_with_preset, + description="Not implemented in any known microcodes. No effect whether enabled or disabled", ) - # v1/2 difference g_shade_smooth: bpy.props.BoolProperty( name="Smooth Shading", default=True, update=update_node_values_with_preset, - description="Shades primitive smoothly using interpolation between shade values for each vertex (Gouraud shading)" + description="Shades primitive smoothly using interpolation between shade values for each vertex (Gouraud shading)", ) - # f3dlx2 only g_clipping: bpy.props.BoolProperty( name="Clipping", + default=False, update=update_node_values_with_preset, + description="F3DEX1/LX only, exact function unknown", ) # upper half mode @@ -2598,7 +2968,7 @@ class RDPSettings(bpy.types.PropertyGroup): items=enumAlphaDither, default="G_AD_NOISE", update=update_node_values_with_preset, - description="Applies your choice dithering type to output framebuffer alpha. Dithering is used to convert high precision source colors into lower precision framebuffer values" + description="Applies your choice dithering type to output framebuffer alpha. Dithering is used to convert high precision source colors into lower precision framebuffer values", ) # v2 only g_mdsft_rgb_dither: bpy.props.EnumProperty( @@ -2606,41 +2976,41 @@ class RDPSettings(bpy.types.PropertyGroup): items=enumRGBDither, default="G_CD_MAGICSQ", update=update_node_values_with_preset, - description="Applies your choice dithering type to output framebuffer color. Dithering is used to convert high precision source colors into lower precision framebuffer values" + description="Applies your choice dithering type to output framebuffer color. Dithering is used to convert high precision source colors into lower precision framebuffer values", ) g_mdsft_combkey: bpy.props.EnumProperty( name="Chroma Key", items=enumCombKey, default="G_CK_NONE", update=update_node_values_with_preset, - description="Turns on/off the chroma key. Chroma key requires a special setup to work properly" + description="Turns on/off the chroma key. Chroma key requires a special setup to work properly", ) g_mdsft_textconv: bpy.props.EnumProperty( name="Texture Convert", items=enumTextConv, default="G_TC_FILT", update=update_node_values_with_preset, - description="Sets the function of the texture convert unit, to do texture filtering, YUV to RGB conversion, or both" + description="Sets the function of the texture convert unit, to do texture filtering, YUV to RGB conversion, or both", ) g_mdsft_text_filt: bpy.props.EnumProperty( name="Texture Filter", items=enumTextFilt, default="G_TF_BILERP", update=update_node_values_without_preset, - description="Applies your choice of filtering to texels" + description="Applies your choice of filtering to texels", ) g_mdsft_textlut: bpy.props.EnumProperty( name="Texture LUT", items=enumTextLUT, default="G_TT_NONE", - description="Changes texture look up table (LUT) behavior. This property is auto set if you choose a CI texture" + description="Changes texture look up table (LUT) behavior. This property is auto set if you choose a CI texture", ) g_mdsft_textlod: bpy.props.EnumProperty( name="Texture LOD", items=enumTextLOD, default="G_TL_TILE", update=update_node_values_with_preset, - description="Turns on/off the use of LoD on textures. LoD textures change the used tile based on the texel/pixel ratio" + description="Turns on/off the use of LoD on textures. LoD textures change the used tile based on the texel/pixel ratio", ) num_textures_mipmapped: bpy.props.IntProperty( name="Number of Mipmaps", @@ -2654,21 +3024,21 @@ class RDPSettings(bpy.types.PropertyGroup): items=enumTextDetail, default="G_TD_CLAMP", update=update_node_values_with_preset, - description="Changes type of LoD usage. Affects how tiles are selected based on texel magnification. Only works when G_TL_LOD is selected" + description="Changes type of LoD usage. Affects how tiles are selected based on texel magnification. Only works when G_TL_LOD is selected", ) g_mdsft_textpersp: bpy.props.EnumProperty( name="Texture Perspective Correction", items=enumTextPersp, default="G_TP_PERSP", update=update_node_values_with_preset, - description="Turns on/off texture perspective correction" + description="Turns on/off texture perspective correction", ) g_mdsft_cycletype: bpy.props.EnumProperty( name="Cycle Type", items=enumCycleType, default="G_CYC_1CYCLE", update=update_node_values_with_preset, - description="Changes RDP pipeline configuration. For normal textured triangles use one or two cycle mode" + description="Changes RDP pipeline configuration. For normal textured triangles use one or two cycle mode", ) # v1 only g_mdsft_color_dither: bpy.props.EnumProperty( @@ -2676,14 +3046,14 @@ class RDPSettings(bpy.types.PropertyGroup): items=enumColorDither, default="G_CD_ENABLE", update=update_node_values_with_preset, - description="Applies your choice dithering type to output frambuffer" + description="Applies your choice dithering type to output frambuffer", ) g_mdsft_pipeline: bpy.props.EnumProperty( name="Pipeline Span Buffer Coherency", items=enumPipelineMode, default="G_PM_1PRIMITIVE", update=update_node_values_with_preset, - description="Changes primitive rasterization timing by adding syncs after tri draws. Vanilla SM64 has synchronization issues which could cause a crash if not using 1 prim. For any modern SM64 hacking project or other game N-prim should always be used" + description="Changes primitive rasterization timing by adding syncs after tri draws. Vanilla SM64 has synchronization issues which could cause a crash if not using 1 prim. For any modern SM64 hacking project or other game N-prim should always be used", ) # lower half mode @@ -2692,14 +3062,14 @@ class RDPSettings(bpy.types.PropertyGroup): items=enumAlphaCompare, default="G_AC_NONE", update=update_node_values_with_preset, - description="Uses alpha comparisons to decide if a pixel should be written. Applies before blending" + description="Uses alpha comparisons to decide if a pixel should be written. Applies before blending", ) g_mdsft_zsrcsel: bpy.props.EnumProperty( name="Z Source Selection", items=enumDepthSource, default="G_ZS_PIXEL", update=update_node_values_with_preset, - description="Changes screen-space Z value source used for Z-Buffer calculations" + description="Changes screen-space Z value source used for Z-Buffer calculations", ) prim_depth: bpy.props.PointerProperty( @@ -2738,47 +3108,45 @@ class RDPSettings(bpy.types.PropertyGroup): ) aa_en: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Enables anti-aliasing to rasterized primitive edges. Uses coverage to determine edges" + description="Enables anti-aliasing to rasterized primitive edges. Uses coverage to determine edges", ) z_cmp: bpy.props.BoolProperty( - update=update_node_values_with_preset, - description="Checks pixel Z value against Z-Buffer to test writing" + update=update_node_values_with_preset, description="Checks pixel Z value against Z-Buffer to test writing" ) z_upd: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Updates the Z-Buffer with the most recently written pixel Z value" + description="Updates the Z-Buffer with the most recently written pixel Z value", ) im_rd: bpy.props.BoolProperty( - update=update_node_values_with_preset, - description="Enables reading from framebuffer for blending calculations" + update=update_node_values_with_preset, description="Enables reading from framebuffer for blending calculations" ) clr_on_cvg: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Only draw on coverage (amount primitive covers target pixel) overflow" + description="Only draw on coverage (amount primitive covers target pixel) overflow", ) cvg_dst: bpy.props.EnumProperty( name="Coverage Destination", items=enumCoverage, update=update_node_values_with_preset, - description="Changes how coverage (amount primitive covers target pixel) gets retrieved/stored" + description="Changes how coverage (amount primitive covers target pixel) gets retrieved/stored", ) zmode: bpy.props.EnumProperty( name="Z Mode", items=enumZMode, update=update_node_values_with_preset, - description="Changes Z calculation for different types of primitives" + description="Changes Z calculation for different types of primitives", ) cvg_x_alpha: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Multiply coverage (amount primitive covers target pixel) with alpha and store result as coverage" + description="Multiply coverage (amount primitive covers target pixel) with alpha and store result as coverage", ) alpha_cvg_sel: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Use coverage (amount primitive covers target pixel) as alpha instead of color combiner alpha" + description="Use coverage (amount primitive covers target pixel) as alpha instead of color combiner alpha", ) force_bl: bpy.props.BoolProperty( update=update_node_values_with_preset, - description="Always uses blending on. Default blending is conditionally only applied during partial coverage. Forcing blending will disable division step of the blender, so B input must be 1-A or there may be rendering issues. Always use this option when Z Buffering is off" + description="Always uses blending on. Default blending is conditionally only applied during partial coverage. Forcing blending will disable division step of the blender, so B input must be 1-A or there may be rendering issues. Always use this option when Z Buffering is off", ) # cycle dependent - (P * A + M - B) / (A + B) @@ -2832,10 +3200,16 @@ def key(self): self.g_shade, self.g_cull_front, self.g_cull_back, + self.g_attroffset_st_enable, + self.g_attroffset_z_enable, + self.g_packed_normals, + self.g_lighttoalpha, + self.g_ambocclusion, self.g_fog, self.g_lighting, self.g_tex_gen, self.g_tex_gen_linear, + self.g_lod, self.g_shade_smooth, self.g_clipping, self.g_mdsft_alpha_dither, @@ -2877,7 +3251,7 @@ def key(self): ) -class DefaultRDPSettingsPanel(bpy.types.Panel): +class DefaultRDPSettingsPanel(Panel): bl_label = "RDP Default Settings" bl_idname = "WORLD_PT_RDP_Default_Inspector" bl_space_type = "PROPERTIES" @@ -2900,6 +3274,99 @@ def draw(self, context): ui_other(world.rdp_defaults, world, layout, True) +class CelLevelProperty(PropertyGroup): + threshMode: bpy.props.EnumProperty( + items=enumCelThreshMode, name="Draw when", default="Lighter", update=update_cel_cutout_source + ) + threshold: bpy.props.IntProperty( + name="Threshold", + description="Light level at which the boundary between cel levels occurs. One level is >= this value, the other is < it", + min=2, + max=255, + default=128, + ) + tintType: bpy.props.EnumProperty(items=enumCelTintType, name="Tint type", default="Fixed") + tintFixedLevel: bpy.props.IntProperty( + name="Level", + description="0: original color <=> 255: fully tint color", + min=0, + max=255, + default=50, + ) + tintFixedColor: bpy.props.FloatVectorProperty( + name="Tint color", + size=3, + min=0.0, + max=1.0, + subtype="COLOR", + ) + tintSegmentNum: bpy.props.IntProperty( + name="Segment", + description="Segment number to store tint DL in", + min=8, + max=0xD, + default=8, + ) + tintSegmentOffset: bpy.props.IntProperty( + name="Offset (instr)", + description="Number of instructions (8 bytes) within this DL to jump to", + min=0, + max=1000, + default=0, + ) + tintLightSlot: bpy.props.IntProperty( + name="Light (/end)", + description="Which light to load RGB color from, counting from the end. 0 = ambient, 1 = last directional / point light, 2 = second-to-last, etc.", + min=0, + max=9, + default=1, + ) + + +class CelShadingProperty(PropertyGroup): + tintPipeline: bpy.props.EnumProperty(items=enumCelTintPipeline, name="Tint pipeline", default="CC") + cutoutSource: bpy.props.EnumProperty( + items=enumCelCutoutSource, + name="Cutout", + default="ENVIRONMENT", + update=update_cel_cutout_source, + ) + levels: bpy.props.CollectionProperty(type=CelLevelProperty, name="Cel levels") + + +def celGetMaterialLevels(materialName): + material = bpy.data.materials.get(materialName) + if material is None: + raise PluginError(f"Could not find material {materialName}") + return material.f3d_mat.cel_shading.levels + + +class CelLevelAdd(bpy.types.Operator): + bl_idname = "material.f3d_cel_level_add" + bl_label = "Add Cel Level" + bl_options = {"REGISTER", "UNDO"} + + materialName: bpy.props.StringProperty() + + def execute(self, context): + levels = celGetMaterialLevels(self.materialName) + levels.add() + return {"FINISHED"} + + +class CelLevelRemove(bpy.types.Operator): + bl_idname = "material.f3d_cel_level_remove" + bl_label = "Remove Last Level" + bl_options = {"REGISTER", "UNDO"} + + materialName: bpy.props.StringProperty() + + def execute(self, context): + levels = celGetMaterialLevels(self.materialName) + levels.remove(len(levels) - 1) + return {"FINISHED"} + + def getOptimalFormat(tex, curFormat, isMultitexture): texFormat = "RGBA16" if isMultitexture: @@ -2975,8 +3442,11 @@ def draw(self, _context): props_default = getattr(self, "preset_operator_defaults", None) add_operator = getattr(self, "preset_add_operator", None) presetDir = getCurrentPresetDir() - paths = bpy.utils.preset_paths(presetDir) if not bpy.context.scene.f3dUserPresetsOnly else [] - paths += bpy.utils.preset_paths("f3d/user") + paths = bpy.utils.preset_paths("f3d/user") + if not bpy.context.scene.f3dUserPresetsOnly: + paths += bpy.utils.preset_paths(presetDir) + if bpy.context.scene.f3d_type == "F3DEX3": + paths += bpy.utils.preset_paths(f"{presetDir}_f3dex3") self.path_menu( paths, self.preset_operator, @@ -3123,7 +3593,7 @@ def execute(self, context): def rna_recursive_attr_expand(value, rna_path_step, level): if rna_path_step in self.ignore_props: return - if isinstance(value, bpy.types.PropertyGroup): + if isinstance(value, PropertyGroup): for sub_value_attr in value.bl_rna.properties.keys(): if sub_value_attr == "rna_type": continue @@ -3293,7 +3763,7 @@ def convertToNewMat(material, oldMat): old_light = oldMat.get(f"f3d_light{str(i)}") # can be a broken property with V1 materials (IDPropertyGroup), thankfully this isnt typical to see when upgrading but # this method is safer - if type(old_light) is bpy.types.Light: + if type(old_light) is Light: setattr(material.f3d_mat, f"f3d_light{str(i)}", old_light) # Fog Properties @@ -3312,7 +3782,7 @@ def convertToNewMat(material, oldMat): recursiveCopyOldPropertyGroup(oldMat["rdp_settings"], material.f3d_mat.rdp_settings) -class F3DMaterialProperty(bpy.types.PropertyGroup): +class F3DMaterialProperty(PropertyGroup): presetName: bpy.props.StringProperty( name="Preset Name", default="Custom", @@ -3520,13 +3990,80 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): default=(0.5, 0.5, 0.5, 1), update=update_light_properties, ) - f3d_light1: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light2: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light3: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light4: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light5: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light6: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) - f3d_light7: bpy.props.PointerProperty(type=bpy.types.Light, update=F3DOrganizeLights) + f3d_light1: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light2: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light3: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light4: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light5: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light6: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + f3d_light7: bpy.props.PointerProperty(type=Light, update=F3DOrganizeLights) + + # Ambient Occlusion + ao_ambient: bpy.props.FloatProperty( + name="AO Ambient", + min=0.0, + max=1.0, + default=1.0, + description="How much ambient occlusion (vertex alpha) affects ambient light intensity", + update=update_node_values_without_preset, + ) + ao_directional: bpy.props.FloatProperty( + name="AO Directional", + min=0.0, + max=1.0, + default=0.625, + description="How much ambient occlusion (vertex alpha) affects directional light intensity", + update=update_node_values_without_preset, + ) + ao_point: bpy.props.FloatProperty( + name="AO Point", + min=0.0, + max=1.0, + default=0.0, + description="How much ambient occlusion (vertex alpha) affects point light intensity", + update=update_node_values_without_preset, + ) + set_ao: bpy.props.BoolProperty(update=update_node_values_without_preset) + + # Fresnel + fresnel_lo: bpy.props.FloatProperty( + name="Fresnel lo", + min=-1000.0, + max=1000.0, + default=0.7, + description="Dot product value which gives shade alpha = 0. The dot product ranges from 1 when the normal points directly at the camera, to 0 when it points sideways", + update=update_node_values_without_preset, + ) + fresnel_hi: bpy.props.FloatProperty( + name="Fresnel hi", + min=-1000.0, + max=1000.0, + default=0.4, + description="Dot product value which gives shade alpha = FF. The dot product ranges from 1 when the normal points directly at the camera, to 0 when it points sideways", + update=update_node_values_without_preset, + ) + set_fresnel: bpy.props.BoolProperty(update=update_node_values_without_preset) + + # Attribute Offsets + attroffs_st: bpy.props.FloatVectorProperty( + name="ST Attr Offset", + size=2, + min=-1024.0, + max=1024.0, + default=(0.0, 0.0), + description="Offset applied to ST (UV) coordinates, after texture scale. Units are texels. Usually for UV scrolling", + update=update_node_values_without_preset, + ) + attroffs_z: bpy.props.IntProperty( + name="Z Attr Offset", + min=-0x8000, + max=0x7FFF, + default=-2, + description="Offset applied to Z coordinate. To fix decals, set Z mode to opaque and set Z attr offset to something like -2", + update=update_node_values_without_preset, + ) + set_attroffs_st: bpy.props.BoolProperty(update=update_node_values_without_preset) + set_attroffs_z: bpy.props.BoolProperty(update=update_node_values_without_preset) # Fog Properties fog_color: bpy.props.FloatVectorProperty( @@ -3562,6 +4099,10 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): use_large_textures: bpy.props.BoolProperty(name="Large Texture Mode") large_edges: bpy.props.EnumProperty(items=enumLargeEdges, default="Clamp") + expand_cel_shading_ui: bpy.props.BoolProperty(name="Expand Cel Shading UI") + use_cel_shading: bpy.props.BoolProperty(name="Use Cel Shading", update=update_cel_cutout_source) + cel_shading: bpy.props.PointerProperty(type=CelShadingProperty) + def key(self) -> F3DMaterialHash: useDefaultLighting = self.set_lights and self.use_default_lighting return ( @@ -3575,6 +4116,25 @@ def key(self) -> F3DMaterialHash: self.rdp_settings.key(), self.draw_layer.key(), self.use_large_textures, + self.use_cel_shading, + self.cel_shading.tintPipeline if self.use_cel_shading else None, + tuple( + [ + ( + c.threshMode, + c.threshold, + c.tintType, + c.tintFixedLevel, + c.tintFixedColor, + c.tintSegmentNum, + c.tintSegmentOffset, + c.tintLightSlot, + ) + for c in self.cel_shading.levels + ] + ) + if self.use_cel_shading + else None, self.use_default_lighting, self.set_blend, self.set_prim, @@ -3600,6 +4160,12 @@ def key(self) -> F3DMaterialHash: round(self.k5, 4) if self.set_k0_5 else None, self.combiner1.key() if self.set_combiner else None, self.combiner2.key() if self.set_combiner else None, + tuple([round(value, 4) for value in (self.ao_ambient, self.ao_directional, self.ao_point)]) + if self.set_ao + else None, + tuple([round(value, 4) for value in (self.fresnel_lo, self.fresnel_hi)]) if self.set_fresnel else None, + tuple([round(value, 4) for value in self.attroffs_st]) if self.set_attroffs_st else None, + self.attroffs_z if self.set_attroffs_z else None, tuple([round(value, 4) for value in self.fog_color]) if self.set_fog else None, tuple([round(value, 4) for value in self.fog_position]) if self.set_fog else None, tuple([round(value, 4) for value in self.default_light_color]) if useDefaultLighting else None, @@ -3617,7 +4183,7 @@ def key(self) -> F3DMaterialHash: ) -class UnlinkF3DImage0(bpy.types.Operator): +class UnlinkF3DImage0(Operator): bl_idname = "image.tex0_unlink" bl_label = "Unlink F3D Image" bl_options = {"REGISTER", "UNDO", "PRESET"} @@ -3629,7 +4195,7 @@ def execute(self, context): return {"FINISHED"} # must return a set -class UnlinkF3DImage1(bpy.types.Operator): +class UnlinkF3DImage1(Operator): bl_idname = "image.tex1_unlink" bl_label = "Unlink F3D Image" bl_options = {"REGISTER", "UNDO", "PRESET"} @@ -3641,7 +4207,7 @@ def execute(self, context): return {"FINISHED"} # must return a set -class UpdateF3DNodes(bpy.types.Operator): +class UpdateF3DNodes(Operator): bl_idname = "material.update_f3d_nodes" bl_label = "Update F3D Nodes" bl_options = {"REGISTER", "UNDO", "PRESET"} @@ -3668,7 +4234,7 @@ def execute(self, context): return {"FINISHED"} # must return a set -class F3DRenderSettingsPanel(bpy.types.Panel): +class F3DRenderSettingsPanel(Panel): bl_label = "F3D Render Settings" bl_idname = "OBJECT_PT_F3D_RENDER_SETTINGS_PANEL" bl_space_type = "VIEW_3D" @@ -3750,7 +4316,7 @@ def draw(self, context): def draw_f3d_render_settings(self, context): - layout: bpy.types.UILayout = self.layout + layout: UILayout = self.layout layout.popover(F3DRenderSettingsPanel.bl_idname) @@ -3771,6 +4337,10 @@ def draw_f3d_render_settings(self, context): PrimDepthSettings, RDPSettings, DefaultRDPSettingsPanel, + CelLevelProperty, + CelShadingProperty, + CelLevelAdd, + CelLevelRemove, F3DMaterialProperty, ReloadDefaultF3DPresets, UpdateF3DNodes, @@ -3815,61 +4385,65 @@ def mat_register(): savePresets() - bpy.types.Scene.f3d_type = bpy.props.EnumProperty( + Scene.f3d_type = bpy.props.EnumProperty( name="F3D Microcode", items=enumF3D, default="F3D", ) - bpy.types.Scene.isHWv1 = bpy.props.BoolProperty(name="Is Hardware v1?") # RDP Defaults - bpy.types.World.rdp_defaults = bpy.props.PointerProperty(type=RDPSettings) - bpy.types.World.menu_geo = bpy.props.BoolProperty() - bpy.types.World.menu_upper = bpy.props.BoolProperty() - bpy.types.World.menu_lower = bpy.props.BoolProperty() - bpy.types.World.menu_other = bpy.props.BoolProperty() - bpy.types.World.menu_layers = bpy.props.BoolProperty() - - bpy.types.Material.is_f3d = bpy.props.BoolProperty() - bpy.types.Material.mat_ver = bpy.props.IntProperty(default=1) - bpy.types.Material.f3d_update_flag = bpy.props.BoolProperty() - bpy.types.Material.f3d_mat = bpy.props.PointerProperty(type=F3DMaterialProperty) - bpy.types.Material.menu_tab = bpy.props.EnumProperty(items=enumF3DMenu) - - bpy.types.Scene.f3dUserPresetsOnly = bpy.props.BoolProperty(name="User Presets Only") - bpy.types.Scene.f3d_simple = bpy.props.BoolProperty(name="Display Simple", default=True) - - bpy.types.Object.use_f3d_culling = bpy.props.BoolProperty( + World.rdp_defaults = bpy.props.PointerProperty(type=RDPSettings) + World.menu_geo = bpy.props.BoolProperty() + World.menu_upper = bpy.props.BoolProperty() + World.menu_lower = bpy.props.BoolProperty() + World.menu_other = bpy.props.BoolProperty() + World.menu_layers = bpy.props.BoolProperty() + + Material.is_f3d = bpy.props.BoolProperty() + Material.mat_ver = bpy.props.IntProperty(default=1) + Material.f3d_update_flag = bpy.props.BoolProperty() + Material.f3d_mat = bpy.props.PointerProperty(type=F3DMaterialProperty) + Material.menu_tab = bpy.props.EnumProperty(items=enumF3DMenu) + + Scene.f3dUserPresetsOnly = bpy.props.BoolProperty(name="User Presets Only") + Scene.f3d_simple = bpy.props.BoolProperty(name="Display Simple", default=True) + + Object.use_f3d_culling = bpy.props.BoolProperty( name="Enable Culling (Applies to F3DEX and up)", default=True, ) - bpy.types.Object.ignore_render = bpy.props.BoolProperty(name="Ignore Render") - bpy.types.Object.ignore_collision = bpy.props.BoolProperty(name="Ignore Collision") - bpy.types.Object.f3d_lod_z = bpy.props.IntProperty( + Object.ignore_render = bpy.props.BoolProperty(name="Ignore Render") + Object.ignore_collision = bpy.props.BoolProperty(name="Ignore Collision") + Object.bleed_independently = bpy.props.BoolProperty( + name="Bleed Independently", + description="While bleeding, this object will not inherit properties from previously drawn meshes in the drawing graph", + ) + Object.f3d_lod_z = bpy.props.IntProperty( name="F3D LOD Z", min=1, default=10, ) - bpy.types.Object.f3d_lod_always_render_farthest = bpy.props.BoolProperty(name="Always Render Farthest LOD") + Object.f3d_lod_always_render_farthest = bpy.props.BoolProperty(name="Always Render Farthest LOD") - bpy.types.VIEW3D_HT_header.append(draw_f3d_render_settings) + VIEW3D_HT_header.append(draw_f3d_render_settings) def mat_unregister(): - bpy.types.VIEW3D_HT_header.remove(draw_f3d_render_settings) - - del bpy.types.Material.menu_tab - del bpy.types.Material.f3d_mat - del bpy.types.Material.is_f3d - del bpy.types.Material.mat_ver - del bpy.types.Material.f3d_update_flag - del bpy.types.Scene.f3d_simple - del bpy.types.Object.ignore_render - del bpy.types.Object.ignore_collision - del bpy.types.Object.use_f3d_culling - del bpy.types.Scene.f3dUserPresetsOnly - del bpy.types.Object.f3d_lod_z - del bpy.types.Object.f3d_lod_always_render_farthest + VIEW3D_HT_header.remove(draw_f3d_render_settings) + + del Material.menu_tab + del Material.f3d_mat + del Material.is_f3d + del Material.mat_ver + del Material.f3d_update_flag + del Scene.f3d_simple + del Object.ignore_render + del Object.ignore_collision + del Object.bleed_independently + del Object.use_f3d_culling + del Scene.f3dUserPresetsOnly + del Object.f3d_lod_z + del Object.f3d_lod_always_render_farthest for cls in reversed(mat_classes): unregister_class(cls) diff --git a/fast64_internal/f3d/f3d_material_presets.py b/fast64_internal/f3d/f3d_material_presets.py index aa3e269b7..bcdfa0722 100644 --- a/fast64_internal/f3d/f3d_material_presets.py +++ b/fast64_internal/f3d/f3d_material_presets.py @@ -57,6 +57,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -64,12 +68,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = True f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -175,6 +188,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -182,12 +199,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = True f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -293,6 +319,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -300,12 +330,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -411,6 +450,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -418,12 +461,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -529,6 +581,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -536,12 +592,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -647,6 +712,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -654,12 +723,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -765,6 +843,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -772,12 +854,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -883,6 +974,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -890,12 +985,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1001,6 +1105,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1008,12 +1116,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1119,6 +1236,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1126,12 +1247,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1237,6 +1367,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1244,12 +1378,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1355,6 +1498,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1362,12 +1509,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1473,6 +1629,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1480,12 +1640,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = False f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1591,6 +1760,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1598,12 +1771,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = False f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1709,6 +1891,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1716,12 +1902,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = False f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1827,6 +2022,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1834,12 +2033,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -1944,6 +2152,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -1951,12 +2163,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = True f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2061,6 +2282,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = True @@ -2068,12 +2293,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2178,6 +2412,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = True @@ -2185,12 +2423,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2295,6 +2542,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = True @@ -2302,12 +2553,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = True f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2412,6 +2672,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -2419,12 +2683,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2529,6 +2802,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -2536,12 +2813,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2646,6 +2932,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -2653,12 +2943,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2763,6 +3062,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -2770,12 +3073,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2880,6 +3192,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -2887,12 +3203,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -2997,6 +3322,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3004,12 +3333,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3114,6 +3452,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3121,12 +3463,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3231,6 +3582,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3238,12 +3593,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = False f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3348,6 +3712,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3355,12 +3723,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = False f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3466,6 +3843,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3473,12 +3854,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3585,6 +3975,10 @@ f3d_mat.f3d_light5 = None f3d_mat.f3d_light6 = None f3d_mat.f3d_light7 = None +f3d_mat.set_ao = False +f3d_mat.set_fresnel = False +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) f3d_mat.fog_position = (985, 1000) f3d_mat.set_fog = False @@ -3592,12 +3986,21 @@ f3d_mat.rdp_settings.name = '' f3d_mat.rdp_settings.g_zbuffer = True f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False f3d_mat.rdp_settings.g_cull_front = False f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False f3d_mat.rdp_settings.g_fog = False f3d_mat.rdp_settings.g_lighting = True f3d_mat.rdp_settings.g_tex_gen = False f3d_mat.rdp_settings.g_tex_gen_linear = False +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_mdsft_alpha_dither = 'G_AD_NOISE' @@ -3644,6 +4047,3271 @@ f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update """ +oot_cel_4_blend_tex_vcol_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'OoT Cel Shaded 4 Blend Tex VCol AO' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'SHADE' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'TEXEL0' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = '0' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = '0' +f3d_mat.combiner2.D = 'SHADE' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = '1' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = False +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = True +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 0.5) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.25979697704315186, 0.09201788902282715, 0.022466713562607765, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = True +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_1CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_THRESHOLD' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_AA_ZB_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = True +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_FOG' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_FOG' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.use_cel_shading = True +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'Blender' +f3d_mat.cel_shading.cutoutSource = 'TEXEL0' +f3d_mat.cel_shading.levels.clear() +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 60 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 140 +item_sub_1.tintFixedColor = (0.012216208502650261, 0.012216208502650261, 0.012216208502650261) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 240 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 150 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 48 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 150 +item_sub_1.tintFixedColor = (0.0, 0.0, 0.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_cel_blend_tex_vcol_ltcol = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'OoT Cel Shaded Blend Tex VCol LtCol' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'SHADE' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'TEXEL0' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = '0' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = '0' +f3d_mat.combiner2.D = 'SHADE' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = '1' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = False +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = True +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 0.5) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.25979697704315186, 0.09201788902282715, 0.022466713562607765, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = True +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_1CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_THRESHOLD' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_AA_ZB_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = True +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_FOG' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_FOG' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.use_cel_shading = True +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'Blender' +f3d_mat.cel_shading.cutoutSource = 'TEXEL0' +f3d_mat.cel_shading.levels.clear() +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Light' +item_sub_1.tintFixedLevel = 60 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Light' +item_sub_1.tintFixedLevel = 140 +item_sub_1.tintFixedColor = (0.012216208502650261, 0.012216208502650261, 0.012216208502650261) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 0 +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_cel_blend_tex_vcol_ltcol_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'OoT Cel Shaded Blend Tex VCol LtCol AO' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'SHADE' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'TEXEL0' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = '0' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = '0' +f3d_mat.combiner2.D = 'SHADE' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = '1' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = False +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 0.5) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.25979697704315186, 0.09201788902282715, 0.022466713562607765, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = True +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_1CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_THRESHOLD' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_AA_ZB_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = True +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_FOG' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_FOG' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.use_cel_shading = True +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'Blender' +f3d_mat.cel_shading.cutoutSource = 'TEXEL0' +f3d_mat.cel_shading.levels.clear() +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Light' +item_sub_1.tintFixedLevel = 60 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Light' +item_sub_1.tintFixedLevel = 140 +item_sub_1.tintFixedColor = (0.012216208502650261, 0.012216208502650261, 0.012216208502650261) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 0 +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_cel_blend_vcol = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'OoT Cel Shaded Blend VCol' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = 'SHADE' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'ENVIRONMENT' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = '0' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = '0' +f3d_mat.combiner2.D = 'SHADE' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = '1' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = False +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = True +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 0.5) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.25979697704315186, 0.09201788902282715, 0.022466713562607765, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = True +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_1CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_THRESHOLD' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_AA_ZB_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = True +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_FOG' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_FOG' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.use_cel_shading = True +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'Blender' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 60 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 140 +item_sub_1.tintFixedColor = (0.012216208502650261, 0.012216208502650261, 0.012216208502650261) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_cel_mul_tex = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'OoT Cel Shaded Mul Tex' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'PRIMITIVE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'SHADE' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'TEXEL0' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = '0' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = '0' +f3d_mat.combiner2.D = 'SHADE' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = '1' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = False +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = True +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 0.5) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.25979697704315186, 0.09201788902282715, 0.022466713562607765, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = True +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_1CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_THRESHOLD' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_AA_ZB_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = True +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_0' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.use_cel_shading = True +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'TEXEL0' +f3d_mat.cel_shading.levels.clear() +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Lighter' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 50 +item_sub_1.tintFixedColor = (1.0, 1.0, 1.0) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +item_sub_1 = f3d_mat.cel_shading.levels.add() +item_sub_1.name = '' +item_sub_1.threshMode = 'Darker' +item_sub_1.threshold = 128 +item_sub_1.tintType = 'Fixed' +item_sub_1.tintFixedLevel = 50 +item_sub_1.tintFixedColor = (0.04445325583219528, 0.04445325583219528, 0.04445325583219528) +item_sub_1.tintSegmentNum = 8 +item_sub_1.tintSegmentOffset = 0 +item_sub_1.tintLightSlot = 1 +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_ghost_fresnel = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Ghost Fresnel' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = 'SHADE' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.20000000298023224 +f3d_mat.set_fresnel = True +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = True +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_PASS' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_ghost_texture_fresnel = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Ghost Texture Fresnel' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = 'SHADE' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.20000000298023224 +f3d_mat.set_fresnel = True +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = True +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_PASS' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_glass_specular_fresnel = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Glass Specular Fresnel' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = 'SHADE' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.20000000298023224 +f3d_mat.set_fresnel = True +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = True +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = True +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_PASS' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_solid_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Solid Ao' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = 'COMBINED' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_solid_ao_transparent = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Solid Ao Transparent' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_specular_vcol_texture = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Specular Vcol Texture' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = 'COMBINED' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = False +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = True +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_multitexture_lerp_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Multitexture Lerp Ao' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL1' +f3d_mat.combiner1.B = 'TEXEL0' +f3d_mat.combiner1.C = 'ENV_ALPHA' +f3d_mat.combiner1.D = 'TEXEL0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'SHADE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = 'COMBINED' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_multitexture_lerp_ao_transparent = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Multitexture Lerp Ao Transparent' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL1' +f3d_mat.combiner1.B = 'TEXEL0' +f3d_mat.combiner1.C = 'ENV_ALPHA' +f3d_mat.combiner1.D = 'TEXEL0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'SHADE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.env_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_multitexture_lerp_transparent_vertex_alpha = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Multitexture Lerp Transparent Vertex Alpha' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL1' +f3d_mat.combiner1.B = 'TEXEL0' +f3d_mat.combiner1.C = 'ENV_ALPHA' +f3d_mat.combiner1.D = 'TEXEL0' +f3d_mat.combiner1.A_alpha = 'TEXEL1' +f3d_mat.combiner1.B_alpha = 'TEXEL0' +f3d_mat.combiner1.C_alpha = 'ENVIRONMENT' +f3d_mat.combiner1.D_alpha = 'TEXEL0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'SHADE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'SHADE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_solid_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Solid Ao' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = 'COMBINED' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_solid_ao_transparent = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Solid Ao Transparent' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = '0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = '0' +f3d_mat.combiner1.D = 'SHADE' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_texture_ao = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Texture Ao' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = '1' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = '0' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = '0' +f3d_mat.combiner2.D_alpha = 'COMBINED' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = False +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = True +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_OPA_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '1' +f3d_mat.draw_layer.oot = 'Opaque' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_texture_ao_transparent = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Texture Ao Transparent' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'SHADE' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = '0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = '0' +f3d_mat.combiner1.D_alpha = 'TEXEL0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'PRIMITIVE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = False +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.env_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = True +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = True +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_FOG_SHADE_A' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_shaded_vcol_texture_transparent_vertex_alpha = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Shaded Vcol Texture Transparent Vertex Alpha' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'ENVIRONMENT' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'TEXEL0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'ENVIRONMENT' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'COMBINED' +f3d_mat.combiner2.B = '0' +f3d_mat.combiner2.C = 'SHADE' +f3d_mat.combiner2.D = '0' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'SHADE' +f3d_mat.combiner2.D_alpha = '0' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.4000000059604645 +f3d_mat.set_fresnel = False +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = True +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = False +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = False +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = False +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_OPA_SURF' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = False +f3d_mat.rdp_settings.z_cmp = False +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = False +f3d_mat.rdp_settings.clr_on_cvg = False +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_CLAMP' +f3d_mat.rdp_settings.zmode = 'ZMODE_OPA' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = False +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_water_lerp_specular_fresnel = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Water Lerp Specular Fresnel' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL1' +f3d_mat.combiner1.B = 'TEXEL0' +f3d_mat.combiner1.C = 'ENV_ALPHA' +f3d_mat.combiner1.D = 'TEXEL0' +f3d_mat.combiner1.A_alpha = 'TEXEL1' +f3d_mat.combiner1.B_alpha = 'TEXEL0' +f3d_mat.combiner1.C_alpha = 'ENVIRONMENT' +f3d_mat.combiner1.D_alpha = 'TEXEL0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'SHADE' +f3d_mat.combiner2.B = 'PRIMITIVE' +f3d_mat.combiner2.C = 'COMBINED' +f3d_mat.combiner2.D = 'PRIMITIVE' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = 'SHADE' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (0.10837142914533615, 0.24593788385391235, 0.5403782725334167, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 0.5) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.20000000298023224 +f3d_mat.set_fresnel = True +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = True +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = True +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_NOOP' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = True +f3d_mat.rdp_settings.clr_on_cvg = True +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_WRAP' +f3d_mat.rdp_settings.zmode = 'ZMODE_INTER' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_MEM' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + +oot_water_mult_specular_fresnel = """ +import bpy +f3d_mat = bpy.context.material.f3d_mat + +bpy.context.material.f3d_update_flag = True +f3d_mat.presetName = 'Oot Water Mult Specular Fresnel' +f3d_mat.combiner1.name = '' +f3d_mat.combiner1.A = 'TEXEL0' +f3d_mat.combiner1.B = '0' +f3d_mat.combiner1.C = 'TEXEL1' +f3d_mat.combiner1.D = '0' +f3d_mat.combiner1.A_alpha = 'TEXEL0' +f3d_mat.combiner1.B_alpha = '0' +f3d_mat.combiner1.C_alpha = 'TEXEL1' +f3d_mat.combiner1.D_alpha = '0' +f3d_mat.combiner2.name = '' +f3d_mat.combiner2.A = 'SHADE' +f3d_mat.combiner2.B = 'PRIMITIVE' +f3d_mat.combiner2.C = 'COMBINED' +f3d_mat.combiner2.D = 'PRIMITIVE' +f3d_mat.combiner2.A_alpha = 'COMBINED' +f3d_mat.combiner2.B_alpha = '0' +f3d_mat.combiner2.C_alpha = 'PRIMITIVE' +f3d_mat.combiner2.D_alpha = 'SHADE' +f3d_mat.tex0.name = '' +f3d_mat.tex0.tex_set = True +f3d_mat.tex1.name = '' +f3d_mat.tex1.tex_set = True +f3d_mat.set_prim = True +f3d_mat.set_lights = False +f3d_mat.set_env = True +f3d_mat.set_blend = False +f3d_mat.set_key = True +f3d_mat.set_k0_5 = True +f3d_mat.set_combiner = True +f3d_mat.use_default_lighting = True +f3d_mat.blend_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.prim_color = (0.10837142914533615, 0.24593788385391235, 0.5403782725334167, 1.0) +f3d_mat.env_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.key_center = (0.5, 0.5, 0.5, 1.0) +f3d_mat.key_scale = (0.0, 0.0, 0.0) +f3d_mat.key_width = (0.0, 0.0, 0.0) +f3d_mat.k0 = 0.686274528503418 +f3d_mat.k1 = -0.16862745583057404 +f3d_mat.k2 = -0.3490196168422699 +f3d_mat.k3 = 0.8705882430076599 +f3d_mat.k4 = 0.4470588266849518 +f3d_mat.k5 = 0.16470588743686676 +f3d_mat.prim_lod_frac = 0.0 +f3d_mat.prim_lod_min = 0.0 +f3d_mat.default_light_color = (1.0, 1.0, 1.0, 1.0) +f3d_mat.set_ambient_from_light = True +f3d_mat.ambient_light_color = (0.2140410989522934, 0.2140410989522934, 0.2140410989522934, 1.0) +f3d_mat.f3d_light1 = None +f3d_mat.f3d_light2 = None +f3d_mat.f3d_light3 = None +f3d_mat.f3d_light4 = None +f3d_mat.f3d_light5 = None +f3d_mat.f3d_light6 = None +f3d_mat.f3d_light7 = None +f3d_mat.ao_ambient = 1.0 +f3d_mat.ao_directional = 0.625 +f3d_mat.ao_point = 0.0 +f3d_mat.set_ao = False +f3d_mat.fresnel_lo = 0.699999988079071 +f3d_mat.fresnel_hi = 0.20000000298023224 +f3d_mat.set_fresnel = True +f3d_mat.attroffs_st = (0.0, 0.0) +f3d_mat.attroffs_z = -2 +f3d_mat.set_attroffs_st = False +f3d_mat.set_attroffs_z = False +f3d_mat.fog_color = (0.0, 0.0, 0.0, 1.0) +f3d_mat.fog_position = (985, 1000) +f3d_mat.set_fog = False +f3d_mat.use_global_fog = True +f3d_mat.rdp_settings.name = '' +f3d_mat.rdp_settings.g_zbuffer = True +f3d_mat.rdp_settings.g_shade = True +f3d_mat.rdp_settings.g_ambocclusion = False +f3d_mat.rdp_settings.g_attroffset_z_enable = False +f3d_mat.rdp_settings.g_attroffset_st_enable = False +f3d_mat.rdp_settings.g_cull_front = False +f3d_mat.rdp_settings.g_cull_back = False +f3d_mat.rdp_settings.g_packed_normals = False +f3d_mat.rdp_settings.g_lighttoalpha = False +f3d_mat.rdp_settings.g_lighting_specular = True +f3d_mat.rdp_settings.g_fresnel_color = False +f3d_mat.rdp_settings.g_fresnel_alpha = True +f3d_mat.rdp_settings.g_fog = False +f3d_mat.rdp_settings.g_lighting = True +f3d_mat.rdp_settings.g_tex_gen = False +f3d_mat.rdp_settings.g_tex_gen_linear = False +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_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' +f3d_mat.rdp_settings.g_mdsft_textconv = 'G_TC_FILT' +f3d_mat.rdp_settings.g_mdsft_text_filt = 'G_TF_BILERP' +f3d_mat.rdp_settings.g_mdsft_textlut = 'G_TT_NONE' +f3d_mat.rdp_settings.g_mdsft_textlod = 'G_TL_TILE' +f3d_mat.rdp_settings.num_textures_mipmapped = 2 +f3d_mat.rdp_settings.g_mdsft_textdetail = 'G_TD_CLAMP' +f3d_mat.rdp_settings.g_mdsft_textpersp = 'G_TP_PERSP' +f3d_mat.rdp_settings.g_mdsft_cycletype = 'G_CYC_2CYCLE' +f3d_mat.rdp_settings.g_mdsft_color_dither = 'G_CD_ENABLE' +f3d_mat.rdp_settings.g_mdsft_pipeline = 'G_PM_NPRIMITIVE' +f3d_mat.rdp_settings.g_mdsft_alpha_compare = 'G_AC_NONE' +f3d_mat.rdp_settings.g_mdsft_zsrcsel = 'G_ZS_PIXEL' +f3d_mat.rdp_settings.prim_depth.name = '' +f3d_mat.rdp_settings.prim_depth.z = 0 +f3d_mat.rdp_settings.prim_depth.dz = 0 +f3d_mat.rdp_settings.clip_ratio = 1 +f3d_mat.rdp_settings.set_rendermode = True +f3d_mat.rdp_settings.rendermode_advanced_enabled = True +f3d_mat.rdp_settings.rendermode_preset_cycle_1 = 'G_RM_NOOP' +f3d_mat.rdp_settings.rendermode_preset_cycle_2 = 'G_RM_AA_ZB_XLU_SURF2' +f3d_mat.rdp_settings.aa_en = True +f3d_mat.rdp_settings.z_cmp = True +f3d_mat.rdp_settings.z_upd = False +f3d_mat.rdp_settings.im_rd = True +f3d_mat.rdp_settings.clr_on_cvg = True +f3d_mat.rdp_settings.cvg_dst = 'CVG_DST_WRAP' +f3d_mat.rdp_settings.zmode = 'ZMODE_INTER' +f3d_mat.rdp_settings.cvg_x_alpha = False +f3d_mat.rdp_settings.alpha_cvg_sel = False +f3d_mat.rdp_settings.force_bl = True +f3d_mat.rdp_settings.blend_p1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_p2 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m1 = 'G_BL_CLR_IN' +f3d_mat.rdp_settings.blend_m2 = 'G_BL_CLR_MEM' +f3d_mat.rdp_settings.blend_a1 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_a2 = 'G_BL_A_IN' +f3d_mat.rdp_settings.blend_b1 = 'G_BL_1MA' +f3d_mat.rdp_settings.blend_b2 = 'G_BL_1MA' +f3d_mat.draw_layer.name = '' +f3d_mat.draw_layer.sm64 = '5' +f3d_mat.draw_layer.oot = 'Transparent' +f3d_mat.large_edges = 'Clamp' +f3d_mat.expand_cel_shading_ui = False +f3d_mat.use_cel_shading = False +f3d_mat.cel_shading.name = '' +f3d_mat.cel_shading.tintPipeline = 'CC' +f3d_mat.cel_shading.cutoutSource = 'ENVIRONMENT' +f3d_mat.cel_shading.levels.clear() +bpy.context.material.f3d_update_flag = False +f3d_mat.use_default_lighting = f3d_mat.use_default_lighting # Force nodes update +""" + material_presets = { "oot": { "oot_shaded_environment_mapped": oot_shaded_environment_mapped, @@ -3664,6 +7332,29 @@ "oot_vertex_colored_texture_cutout": oot_vertex_colored_texture_cutout, "oot_vertex_colored_texture_transparent": oot_vertex_colored_texture_transparent, }, + "oot_f3dex3": { + "oot_cel_4_blend_tex_vcol_ao": oot_cel_4_blend_tex_vcol_ao, + "oot_cel_blend_tex_vcol_ltcol": oot_cel_blend_tex_vcol_ltcol, + "oot_cel_blend_tex_vcol_ltcol_ao": oot_cel_blend_tex_vcol_ltcol_ao, + "oot_cel_blend_vcol": oot_cel_blend_vcol, + "oot_cel_mul_tex": oot_cel_mul_tex, + "oot_ghost_fresnel": oot_ghost_fresnel, + "oot_ghost_texture_fresnel": oot_ghost_texture_fresnel, + "oot_glass_specular_fresnel": oot_glass_specular_fresnel, + "oot_shaded_solid_ao": oot_shaded_solid_ao, + "oot_shaded_solid_ao_transparent": oot_shaded_solid_ao_transparent, + "oot_shaded_specular_vcol_texture": oot_shaded_specular_vcol_texture, + "oot_shaded_vcol_multitexture_lerp_ao": oot_shaded_vcol_multitexture_lerp_ao, + "oot_shaded_vcol_multitexture_lerp_ao_transparent": oot_shaded_vcol_multitexture_lerp_ao_transparent, + "oot_shaded_vcol_multitexture_lerp_transparent_vertex_alpha": oot_shaded_vcol_multitexture_lerp_transparent_vertex_alpha, + "oot_shaded_vcol_solid_ao": oot_shaded_vcol_solid_ao, + "oot_shaded_vcol_solid_ao_transparent": oot_shaded_vcol_solid_ao_transparent, + "oot_shaded_vcol_texture_ao": oot_shaded_vcol_texture_ao, + "oot_shaded_vcol_texture_ao_transparent": oot_shaded_vcol_texture_ao_transparent, + "oot_shaded_vcol_texture_transparent_vertex_alpha": oot_shaded_vcol_texture_transparent_vertex_alpha, + "oot_water_lerp_specular_fresnel": oot_water_lerp_specular_fresnel, + "oot_water_mult_specular_fresnel": oot_water_mult_specular_fresnel, + }, "sm64": { "sm64_decal": sm64_decal, "sm64_environment_map": sm64_environment_map, diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index 8212a6aca..45b25fb23 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -1,5 +1,6 @@ from typing import Union import bmesh, bpy, mathutils, re, math, traceback +from mathutils import Vector from bpy.utils import register_class, unregister_class from .f3d_gbi import * from .f3d_material import ( @@ -98,7 +99,7 @@ def cmdToPositiveInt(cmd): def parseF3DBinary(romfile, startAddress, scene, bMesh, obj, transformMatrix, groupName, segmentData, vertexBuffer): - f3d = F3D("F3D", False) + f3d = F3D("F3D") currentAddress = startAddress romfile.seek(currentAddress) command = romfile.read(8) @@ -257,7 +258,7 @@ def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command, segme data = romfile.read(dataLength) for i in range(numVerts): - vert = mathutils.Vector(readVectorFromShorts(data, i * 16)) + vert = Vector(readVectorFromShorts(data, i * 16)) vert = transformMatrix @ vert transformedVert = bytearray(6) writeVectorToShorts(transformedVert, 0, vert) @@ -276,9 +277,9 @@ def interpretDrawTriangle(command, vertexBuffer, faceSeq, vertSeq, uv_layer, def index1 = int(command[6] / 0x0A) index2 = int(command[7] / 0x0A) - vert0 = mathutils.Vector(getPosition(vertexBuffer, index0)) - vert1 = mathutils.Vector(getPosition(vertexBuffer, index1)) - vert2 = mathutils.Vector(getPosition(vertexBuffer, index2)) + vert0 = Vector(getPosition(vertexBuffer, index0)) + vert1 = Vector(getPosition(vertexBuffer, index1)) + vert2 = Vector(getPosition(vertexBuffer, index2)) verts[0] = vertSeq.new(vert0) verts[1] = vertSeq.new(vert1) @@ -292,7 +293,7 @@ def interpretDrawTriangle(command, vertexBuffer, faceSeq, vertSeq, uv_layer, def loopIndex = 0 for loop in tri.loops: - loop[uv_layer].uv = mathutils.Vector(getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A))) + loop[uv_layer].uv = Vector(getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A))) loopIndex += 1 return verts @@ -506,19 +507,19 @@ def initContext(self): # data for Mesh.from_pydata, list of BufferVertex tuples # use BufferVertex to also form uvs / normals / colors self.verts: list[F3DVert] = [] - self.limbGroups: dict[str : list[int]] = {} # dict of groupName : vertex indices + self.limbGroups: dict[str, list[int]] = {} # dict of groupName : vertex indices - self.lights: Lights = Lights("lights_context") + self.lights: Lights = Lights("lights_context", self.f3d) # Here these are ints, but when parsing the values will be normalized. self.lights.l = [ - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), ] self.lights.a = Ambient([0, 0, 0]) self.numLights: int = 0 @@ -576,15 +577,15 @@ def clearMaterial(self): self.tileSizes = [DPSetTileSize(i, 0, 0, 32, 32) for i in range(8)] - self.lights = Lights("lights_context") + self.lights = Lights("lights_context", self.f3d) self.lights.l = [ - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), - Light([0, 0, 0], [0x28, 0x28, 0x28]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), + Light([0, 0, 0], [0x49, 0x49, 0x49]), ] self.lights.a = Ambient([0, 0, 0]) self.numLights = 0 @@ -600,14 +601,14 @@ def vertexFormatPatterns(self, data): # decomp format "\{\s*\{\s*" + "\{([^,\}]*),([^,\}]*),([^,\}]*)\}\s*," - + "[^,\}]*,\s*" + + "([^,\}]*),\s*" + "\{([^,\}]*),([^,\}]*)\}\s*,\s*" + "\{([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\}\s*" + "\}\s*\}", # nusys format "\{\s*" + "([^,\}]*),([^,\}]*),([^,\}]*)," - + "[^,\}]*," + + "([^,\}]*)," + "([^,\}]*),([^,\}]*)," + "([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\s*" + "\}", @@ -641,7 +642,7 @@ def getTransformedVertex(self, index: int): mat = self.mat() f3dVert = bufferVert.f3dVert - position = transform @ mathutils.Vector(f3dVert.position) + position = transform @ Vector(f3dVert.position) if mat.tex0.tex is not None: texDimensions = mat.tex0.tex.size elif mat.tex0.use_tex_reference: @@ -656,18 +657,19 @@ def getTransformedVertex(self, index: int): uv = [convertF3DUV(f3dVert.uv[i], texDimensions[i]) for i in range(2)] uv[1] = 1 - uv[1] - color = [ - value / 256 - if value > 0 - else int.from_bytes(round(value).to_bytes(1, "big", signed=True), "big", signed=False) / 256 - for value in f3dVert.getColorOrNormal() - ] - - normal = bytesToNormal(f3dVert.getColorOrNormal()[:3]) + [0] - normal = (transform.inverted().transposed() @ mathutils.Vector(normal)).normalized()[:3] + has_rgb, has_normal, has_packed_normals = getRgbNormalSettings(self.mat()) + rgb = Vector([v / 0xFF for v in f3dVert.rgb]) if has_rgb else Vector([1.0, 1.0, 1.0]) + alpha = f3dVert.alpha / 0xFF + normal = Vector([0.0, 0.0, 0.0]) # Zero normal makes normals_split_custom_set use auto + if has_normal: + if has_packed_normals: + normal = f3dVert.normal + else: + normal = Vector([v - 0x100 if v >= 0x80 else v for v in f3dVert.rgb]).normalized() + normal = (transform.inverted().transposed() @ normal).normalized() # NOTE: The groupIndex here does NOT correspond to a vertex group, but to the name of the limb (c variable) - return BufferVertex(F3DVert(position, uv, color, normal), bufferVert.groupIndex, bufferVert.materialIndex) + return BufferVertex(F3DVert(position, uv, rgb, normal, alpha), bufferVert.groupIndex, bufferVert.materialIndex) def addVertices(self, num, start, vertexDataName, vertexDataOffset): vertexData = self.vertexData[vertexDataName] @@ -879,6 +881,23 @@ def setGeoFlags(self, command, value): mat.rdp_settings.g_cull_front = value if bitFlags & self.f3d.G_CULL_BACK: mat.rdp_settings.g_cull_back = value + if self.f3d.F3DEX_GBI_3: + if bitFlags & self.f3d.G_AMBOCCLUSION: + mat.rdp_settings.g_ambocclusion = value + if bitFlags & self.f3d.G_ATTROFFSET_Z_ENABLE: + mat.rdp_settings.g_attroffset_z_enable = value + if bitFlags & self.f3d.G_ATTROFFSET_ST_ENABLE: + mat.rdp_settings.g_attroffset_st_enable = value + if bitFlags & self.f3d.G_PACKED_NORMALS: + mat.rdp_settings.g_packed_normals = value + if bitFlags & self.f3d.G_LIGHTTOALPHA: + mat.rdp_settings.g_lighttoalpha = value + if bitFlags & self.f3d.G_LIGHTING_SPECULAR: + mat.rdp_settings.g_lighting_specular = value + if bitFlags & self.f3d.G_FRESNEL_COLOR: + mat.rdp_settings.g_fresnel_color = value + if bitFlags & self.f3d.G_FRESNEL_ALPHA: + mat.rdp_settings.g_fresnel_alpha = value if bitFlags & self.f3d.G_FOG: mat.rdp_settings.g_fog = value if bitFlags & self.f3d.G_LIGHTING: @@ -887,6 +906,8 @@ def setGeoFlags(self, command, value): mat.rdp_settings.g_tex_gen = value if bitFlags & self.f3d.G_TEXTURE_GEN_LINEAR: mat.rdp_settings.g_tex_gen_linear = value + if bitFlags & self.f3d.G_LOD: + mat.rdp_settings.g_lod = value if bitFlags & self.f3d.G_SHADING_SMOOTH: mat.rdp_settings.g_shade_smooth = value if bitFlags & self.f3d.G_CLIPPING: @@ -901,10 +922,29 @@ def loadGeoFlags(self, command): mat.rdp_settings.g_shade = bitFlags & self.f3d.G_SHADE != 0 mat.rdp_settings.g_cull_front = bitFlags & self.f3d.G_CULL_FRONT != 0 mat.rdp_settings.g_cull_back = bitFlags & self.f3d.G_CULL_BACK != 0 + if self.f3d.F3DEX_GBI_3: + mat.rdp_settings.g_ambocclusion = bitFlags & self.f3d.G_AMBOCCLUSION != 0 + mat.rdp_settings.g_attroffset_z_enable = bitFlags & self.f3d.G_ATTROFFSET_Z_ENABLE != 0 + mat.rdp_settings.g_attroffset_st_enable = bitFlags & self.f3d.G_ATTROFFSET_ST_ENABLE != 0 + mat.rdp_settings.g_packed_normals = bitFlags & self.f3d.G_PACKED_NORMALS != 0 + mat.rdp_settings.g_lighttoalpha = bitFlags & self.f3d.G_LIGHTTOALPHA != 0 + mat.rdp_settings.g_lighting_specular = bitFlags & self.f3d.G_LIGHTING_SPECULAR != 0 + mat.rdp_settings.g_fresnel_color = bitFlags & self.f3d.G_FRESNEL_COLOR != 0 + mat.rdp_settings.g_fresnel_alpha = bitFlags & self.f3d.G_FRESNEL_ALPHA != 0 + else: + mat.rdp_settings.g_ambocclusion = False + mat.rdp_settings.g_attroffset_z_enable = False + mat.rdp_settings.g_attroffset_st_enable = False + mat.rdp_settings.g_packed_normals = False + mat.rdp_settings.g_lighttoalpha = False + mat.rdp_settings.g_lighting_specular = False + mat.rdp_settings.g_fresnel_color = False + mat.rdp_settings.g_fresnel_alpha = False mat.rdp_settings.g_fog = bitFlags & self.f3d.G_FOG != 0 mat.rdp_settings.g_lighting = bitFlags & self.f3d.G_LIGHTING != 0 mat.rdp_settings.g_tex_gen = bitFlags & self.f3d.G_TEXTURE_GEN != 0 mat.rdp_settings.g_tex_gen_linear = bitFlags & self.f3d.G_TEXTURE_GEN_LINEAR != 0 + mat.rdp_settings.g_lod = bitFlags & self.f3d.G_LOD != 0 mat.rdp_settings.g_shade_smooth = bitFlags & self.f3d.G_SHADING_SMOOTH != 0 mat.rdp_settings.g_clipping = bitFlags & self.f3d.G_CLIPPING != 0 @@ -1147,9 +1187,9 @@ def getLightObj(self, light: Light): lightObj.rotation_euler = ( mathutils.Euler((0, 0, math.pi)).to_quaternion() - @ ( - mathutils.Euler((math.pi / 2, 0, 0)).to_quaternion() @ mathutils.Vector(light.normal) - ).rotation_difference(mathutils.Vector((0, 0, 1))) + @ (mathutils.Euler((math.pi / 2, 0, 0)).to_quaternion() @ Vector(light.normal)).rotation_difference( + Vector((0, 0, 1)) + ) ).to_euler() # lightObj.rotation_euler[0] *= 1 bLight.color = light.color @@ -1173,7 +1213,7 @@ def setLightColor(self, data, command): self.mat().set_lights = True lightIndex = self.getLightIndex(command.params[0]) colorData = math_eval(command.params[1], self.f3d) - color = mathutils.Vector( + color = Vector( [((colorData >> 24) & 0xFF) / 0xFF, ((colorData >> 16) & 0xFF) / 0xFF, ((colorData >> 8) & 0xFF) / 0xFF] ) @@ -1231,21 +1271,21 @@ def setLights(self, data, command): def createLights(self, data, lightsName): numLights, lightValues = parseLightsData(data, lightsName, self) - ambientColor = mathutils.Vector(gammaInverse([value / 255 for value in lightValues[0:3]])) + ambientColor = Vector(gammaInverse([value / 255 for value in lightValues[0:3]])) lightList = [] for i in range(numLights): - color = mathutils.Vector(gammaInverse([value / 255 for value in lightValues[3 + 6 * i : 3 + 6 * i + 3]])) - direction = mathutils.Vector(bytesToNormal(lightValues[3 + 6 * i + 3 : 3 + 6 * i + 6])) + color = Vector(gammaInverse([value / 255 for value in lightValues[3 + 6 * i : 3 + 6 * i + 3]])) + direction = Vector(bytesToNormal(lightValues[3 + 6 * i + 3 : 3 + 6 * i + 6])) lightList.append(Light(color, direction)) while len(lightList) < 7: - lightList.append(Light(mathutils.Vector([0, 0, 0]), mathutils.Vector([0x28, 0x28, 0x28]))) + lightList.append(Light(Vector([0, 0, 0]), Vector([0x49, 0x49, 0x49]))) # normally a and l are Ambient and Light objects, # but here they will be a color and blender light object array. - lights = Lights(lightsName) + lights = Lights(lightsName, self.f3d) lights.a = Ambient(ambientColor) lights.l = lightList @@ -1547,6 +1587,45 @@ def processCommands(self, dlData, dlName, dlCommands): self.setLightColor(dlData, command) elif command.name[:13] == "gsSPSetLights": self.setLights(dlData, command) + elif command.name == "gsSPAmbOcclusionAmb": + mat.ao_ambient = float_from_u16_str(command.params[0]) + mat.set_ao = True + elif command.name == "gsSPAmbOcclusionDir": + mat.ao_directional = float_from_u16_str(command.params[0]) + mat.set_ao = True + elif command.name == "gsSPAmbOcclusionPoint": + mat.ao_point = float_from_u16_str(command.params[0]) + mat.set_ao = True + elif command.name == "gsSPAmbOcclusionAmbDir": + mat.ao_ambient = float_from_u16_str(command.params[0]) + mat.ao_directional = float_from_u16_str(command.params[1]) + mat.set_ao = True + elif command.name == "gsSPAmbOcclusionDirPoint": + mat.ao_directional = float_from_u16_str(command.params[0]) + mat.ao_point = float_from_u16_str(command.params[1]) + mat.set_ao = True + elif command.name == "gsSPAmbOcclusion": + mat.ao_ambient = float_from_u16_str(command.params[0]) + mat.ao_directional = float_from_u16_str(command.params[1]) + mat.ao_point = float_from_u16_str(command.params[2]) + mat.set_ao = True + elif command.name == "gsSPFresnel": + scale = int_from_s16_str(command.params[0]) + offset = int_from_s16_str(command.params[1]) + dotMax = ((0x7F - offset) << 15) // scale + dotMin = ((0x00 - offset) << 15) // scale + mat.fresnel_hi = dotMax / float(0x7FFF) + mat.fresnel_lo = dotMin / float(0x7FFF) + mat.set_fresnel = True + elif command.name == "gsSPAttrOffsetST": + mat.attroffs_st = [ + int_from_s16_str(command.params[0]) / 32, + int_from_s16_str(command.params[1]) / 32, + ] + mat.set_attroffs_st = True + elif command.name == "gsSPAttrOffsetZ": + mat.attroffs_z = int_from_s16_str(command.params[0]) + mat.set_attroffs_z = True elif command.name == "gsSPFogFactor": pass elif command.name == "gsSPFogPosition": @@ -1776,12 +1855,11 @@ def createMesh(self, obj, removeDoubles, importNormals, callDeleteMaterialContex color_layer = mesh.vertex_colors.new(name="Col").data for i in range(len(mesh.loops)): - # if self.materialContext.f3d_mat.rdp_settings.g_lighting: - color_layer[i].color = self.verts[i].color + color_layer[i].color = self.verts[i].rgb.to_4d() alpha_layer = mesh.vertex_colors.new(name="Alpha").data for i in range(len(mesh.loops)): - alpha_layer[i].color = [self.verts[i].color[3]] * 3 + [1] + alpha_layer[i].color = [self.verts[i].alpha] * 3 + [1] if bpy.context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") @@ -1803,7 +1881,7 @@ def createMesh(self, obj, removeDoubles, importNormals, callDeleteMaterialContex i = 0 for key, lightObj in self.lightData.items(): - lightObj.location = bpy.context.scene.cursor.location + mathutils.Vector((i, 0, 0)) + lightObj.location = bpy.context.scene.cursor.location + Vector((i, 0, 0)) i += 1 self.clearGeometry() @@ -1903,29 +1981,21 @@ def parseVertexData(dlData: str, vertexDataName: str, f3dContext: F3DContext): patterns = f3dContext.vertexFormatPatterns(data) vertexData = [] for pattern in patterns: - # Note that color is None here, as we are just parsing vertex data. - # The same values should be used for color and normal, but getColorOrNormal() will be used later - # which returns whichever value is not None between the two. - - # When loaded into the vertex buffer and transformed, the actual normal/color will be calculated. - vertexData = [ - F3DVert( - mathutils.Vector( - [math_eval(match.group(1), f3d), math_eval(match.group(2), f3d), math_eval(match.group(3), f3d)] - ), - mathutils.Vector([math_eval(match.group(4), f3d), math_eval(match.group(5), f3d)]), - None, - mathutils.Vector( - [ - math_eval(match.group(6), f3d), - math_eval(match.group(7), f3d), - math_eval(match.group(8), f3d), - math_eval(match.group(9), f3d), - ] - ), + # For this step, store rgb/normal as rgb and packed normal as normal. + for match in re.finditer(pattern, data, re.DOTALL): + values = [math_eval(g, f3d) for g in match.groups()] + if len(values) == 9: + # A format without the flag / packed normal + values = values[0:3] + [0] + values[3:9] + vertexData.append( + F3DVert( + Vector(values[0:3]), + Vector(values[4:6]), + Vector(values[6:9]), + unpackNormal(values[3]), + values[9], + ) ) - for match in re.finditer(pattern, data, re.DOTALL) - ] if len(vertexData) > 0: break f3dContext.vertexData[vertexDataName] = vertexData @@ -2233,8 +2303,6 @@ def execute(self, context): removeDoubles = context.scene.DLRemoveDoubles importNormals = context.scene.DLImportNormals drawLayer = context.scene.DLImportDrawLayer - f3dType = context.scene.f3d_type - isHWv1 = context.scene.isHWv1 data = getImportData([importPath]) @@ -2245,7 +2313,7 @@ def execute(self, context): removeDoubles, importNormals, drawLayer, - F3DContext(F3D(f3dType, isHWv1), basePath, createF3DMat(None)), + F3DContext(get_F3D_GBI(), basePath, createF3DMat(None)), ) self.report({"INFO"}, "Success!") diff --git a/fast64_internal/f3d/f3d_render_engine.py b/fast64_internal/f3d/f3d_render_engine.py index f8a5910a2..51442a71f 100644 --- a/fast64_internal/f3d/f3d_render_engine.py +++ b/fast64_internal/f3d/f3d_render_engine.py @@ -1,9 +1,9 @@ import bpy, math, time -from mathutils import * -from bgl import * +from mathutils import * +from bgl import * from bpy.utils import register_class, unregister_class -vertexShader = ''' +vertexShader = """ #version 330 core layout (location = 0) in vec3 pos; layout (location = 1) in vec2 uv; @@ -18,9 +18,9 @@ gl_Position = transform * vec4(pos, 1.0); // see how we directly give a vec3 to vec4's constructor vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color } -''' +""" -fragmentShader = ''' +fragmentShader = """ #version 330 core out vec4 color; @@ -30,342 +30,352 @@ { color = vertexColor; } -''' +""" + class F3DRenderEngine(bpy.types.RenderEngine): + bl_idname = "f3d_renderer" + bl_label = "Fast3D" + use_preview = True + + # Init is called whenever a new render engine instance is created. Multiple + # instances may exist at the same time, for example for a viewport and final + # render. + def __init__(self): + self.draw_data = F3DDrawData() + self.first_time = False + + # When the render engine instance is destroy, this is called. Clean up any + # render engine data here, for example stopping running render threads. + def __del__(self): + pass + + # This is the method called by Blender for both final renders (F12) and + # small preview for materials, world and lights. + def render(self, depsgraph): + scene = depsgraph.scene + scale = scene.render.resolution_percentage / 100.0 + self.size_x = int(scene.render.resolution_x * scale) + self.size_y = int(scene.render.resolution_y * scale) + + # Fill the render result with a flat color. The framebuffer is + # defined as a list of pixels, each pixel itself being a list of + # R,G,B,A values. + if self.is_preview: + color = [0.1, 0.2, 0.1, 1.0] + else: + color = [0.2, 0.1, 0.1, 1.0] + + pixel_count = self.size_x * self.size_y + rect = [color] * pixel_count + + # Here we write the pixel values to the RenderResult + result = self.begin_result(0, 0, self.size_x, self.size_y) + layer = result.layers[0].passes["Combined"] + layer.rect = rect + self.end_result(result) + + # For viewport renders, this method gets called once at the start and + # whenever the scene or 3D viewport changes. This method is where data + # should be read from Blender in the same thread. Typically a render + # thread will be started to do the work while keeping Blender responsive. + + # Not called when viewport camera transform changes. + def view_update(self, context, depsgraph): + region = context.region + view3d = context.space_data + scene = depsgraph.scene + + # Get viewport dimensions + dimensions = region.width, region.height + + print("Start View Update: " + str(glGetError())) + if not self.first_time: + # First time initialization + self.first_time = True + for datablock in depsgraph.ids: + print(datablock) + if isinstance(datablock, bpy.types.Image): + self.draw_data.textures[datablock.name] = F3DRendererTexture(datablock) + print("Create Texture: " + str(glGetError())) + elif isinstance(datablock, bpy.types.Material): + self.draw_data.materials[datablock.name] = F3DRendererMaterial(datablock) + print("Create Material: " + str(glGetError())) + elif isinstance(datablock, bpy.types.Mesh): + pass + elif isinstance(datablock, bpy.types.Object) and datablock.type == "MESH": + self.draw_data.objects[datablock.name] = F3DRendererObject(datablock, self.draw_data) + print("Create Object: " + str(glGetError())) + else: + # Test which datablocks changed + for update in depsgraph.updates: + print("Datablock updated: ", update.id.name) + # if isinstance(update.id, bpy.types.Scene): + # for self.draw_data + + # Test if any material was added, removed or changed. + if depsgraph.id_type_updated("MATERIAL"): + print("Materials updated") + + # Loop over all object instances in the scene. + if self.first_time or depsgraph.id_type_updated("OBJECT"): + print("Updated object.") + for instance in depsgraph.object_instances: + pass + + context.region_data.perspective_matrix + print("End View Update: " + str(glGetError())) + + # For viewport renders, this method is called whenever Blender redraws + # the 3D viewport. The renderer is expected to quickly draw the render + # with OpenGL, and not perform other expensive work. + # Blender will draw overlays for selection and editing on top of the + # rendered image automatically. + def view_draw(self, context, depsgraph): + region = context.region + scene = depsgraph.scene + + # Get viewport dimensions + dimensions = region.width, region.height + + print("Start View Draw: " + str(glGetError())) + glEnable(GL_BLEND) + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) + self.draw_data.draw() + glDisable(GL_BLEND) - bl_idname = "f3d_renderer" - bl_label = "Fast3D" - use_preview = True - - # Init is called whenever a new render engine instance is created. Multiple - # instances may exist at the same time, for example for a viewport and final - # render. - def __init__(self): - self.draw_data = F3DDrawData() - self.first_time = False - - # When the render engine instance is destroy, this is called. Clean up any - # render engine data here, for example stopping running render threads. - def __del__(self): - pass - - # This is the method called by Blender for both final renders (F12) and - # small preview for materials, world and lights. - def render(self, depsgraph): - scene = depsgraph.scene - scale = scene.render.resolution_percentage / 100.0 - self.size_x = int(scene.render.resolution_x * scale) - self.size_y = int(scene.render.resolution_y * scale) - - # Fill the render result with a flat color. The framebuffer is - # defined as a list of pixels, each pixel itself being a list of - # R,G,B,A values. - if self.is_preview: - color = [0.1, 0.2, 0.1, 1.0] - else: - color = [0.2, 0.1, 0.1, 1.0] - - pixel_count = self.size_x * self.size_y - rect = [color] * pixel_count - - # Here we write the pixel values to the RenderResult - result = self.begin_result(0, 0, self.size_x, self.size_y) - layer = result.layers[0].passes["Combined"] - layer.rect = rect - self.end_result(result) - - # For viewport renders, this method gets called once at the start and - # whenever the scene or 3D viewport changes. This method is where data - # should be read from Blender in the same thread. Typically a render - # thread will be started to do the work while keeping Blender responsive. - - # Not called when viewport camera transform changes. - def view_update(self, context, depsgraph): - region = context.region - view3d = context.space_data - scene = depsgraph.scene - - # Get viewport dimensions - dimensions = region.width, region.height - - print("Start View Update: " + str(glGetError())) - if not self.first_time: - # First time initialization - self.first_time = True - for datablock in depsgraph.ids: - print(datablock) - if isinstance(datablock, bpy.types.Image): - self.draw_data.textures[datablock.name] = F3DRendererTexture(datablock) - print("Create Texture: " + str(glGetError())) - elif isinstance(datablock, bpy.types.Material): - self.draw_data.materials[datablock.name] = F3DRendererMaterial(datablock) - print("Create Material: " + str(glGetError())) - elif isinstance(datablock, bpy.types.Mesh): - pass - elif isinstance(datablock, bpy.types.Object) and isinstance(datablock.data, bpy.types.Mesh): - self.draw_data.objects[datablock.name] = F3DRendererObject(datablock, self.draw_data) - print("Create Object: " + str(glGetError())) - else: - # Test which datablocks changed - for update in depsgraph.updates: - print("Datablock updated: ", update.id.name) - #if isinstance(update.id, bpy.types.Scene): - # for self.draw_data - - # Test if any material was added, removed or changed. - if depsgraph.id_type_updated('MATERIAL'): - print("Materials updated") - - # Loop over all object instances in the scene. - if self.first_time or depsgraph.id_type_updated('OBJECT'): - print("Updated object.") - for instance in depsgraph.object_instances: - pass - - context.region_data.perspective_matrix - print("End View Update: " + str(glGetError())) - - # For viewport renders, this method is called whenever Blender redraws - # the 3D viewport. The renderer is expected to quickly draw the render - # with OpenGL, and not perform other expensive work. - # Blender will draw overlays for selection and editing on top of the - # rendered image automatically. - def view_draw(self, context, depsgraph): - region = context.region - scene = depsgraph.scene - - # Get viewport dimensions - dimensions = region.width, region.height - - print("Start View Draw: " + str(glGetError())) - glEnable(GL_BLEND) - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) - self.draw_data.draw() - glDisable(GL_BLEND) class F3DRendererTexture: - def __init__(self, image): - self.image = image - width, height = image.size - - self.texture_buffer = Buffer(GL_INT, 1) - image.gl_load() - self.texture_buffer[0] = image.bindcode - - #glGenTextures(1, self.texture) - glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_2D, self.texture_buffer[0]) - #glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, pixels) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glBindTexture(GL_TEXTURE_2D, 0) - - def __del__(self): - glBindTexture(GL_TEXTURE_2D, 0) - glDeleteTextures(1, self.texture_buffer) - - def bind(self): - glBindTexture(GL_TEXTURE_2D, self.texture_buffer[0]) + def __init__(self, image): + self.image = image + width, height = image.size + + self.texture_buffer = Buffer(GL_INT, 1) + image.gl_load() + self.texture_buffer[0] = image.bindcode + + # glGenTextures(1, self.texture) + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, self.texture_buffer[0]) + # glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, pixels) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glBindTexture(GL_TEXTURE_2D, 0) + + def __del__(self): + glBindTexture(GL_TEXTURE_2D, 0) + glDeleteTextures(1, self.texture_buffer) + + def bind(self): + glBindTexture(GL_TEXTURE_2D, self.texture_buffer[0]) + class F3DRendererMaterial: - def __init__(self, material): - self.material = material - - def __del__(self): - pass + def __init__(self, material): + self.material = material + + def __del__(self): + pass - def update(self, viewport, projection, time): - pass + def update(self, viewport, projection, time): + pass + + def apply(self): + pass - def apply(self): - pass class F3DRendererObject: - def __init__(self, obj, render_data): - mesh = obj.data - self.submeshes = [] - facesByMat = {} - mesh.calc_loop_triangles() - for face in mesh.loop_triangles: - if face.material_index not in facesByMat: - facesByMat[face.material_index] = [] - facesByMat[face.material_index].append(face) - - for material_index, faces in facesByMat.items(): - material = mesh.materials[material_index] - # Material should always be added in view_update - #f3d_material = render_data.materials[material] - f3d_material = None - - self.submeshes.append(F3DRendererSubmesh(f3d_material, obj, faces, render_data)) - - def draw(self): - print("Draw Object: " + str(glGetError())) - for submesh in self.submeshes: - submesh.draw() + def __init__(self, obj, render_data): + mesh = obj.data + self.submeshes = [] + facesByMat = {} + mesh.calc_loop_triangles() + for face in mesh.loop_triangles: + if face.material_index not in facesByMat: + facesByMat[face.material_index] = [] + facesByMat[face.material_index].append(face) + + for material_index, faces in facesByMat.items(): + material = mesh.materials[material_index] + # Material should always be added in view_update + # f3d_material = render_data.materials[material] + f3d_material = None + + self.submeshes.append(F3DRendererSubmesh(f3d_material, obj, faces, render_data)) + + def draw(self): + print("Draw Object: " + str(glGetError())) + for submesh in self.submeshes: + submesh.draw() + class F3DRendererSubmesh: - def __init__(self, f3d_material, obj, triangles, render_data): - print("Begin submesh: " + str(glGetError())) - mesh = obj.data - loopIndices = [] - for triangle in triangles: - for loopIndex in triangle.loops: - loopIndices.append(loopIndex) - - self.material = f3d_material - self.obj = obj - - self.vertex_array = Buffer(GL_INT, 1) - glGenVertexArrays(1, self.vertex_array) - glBindVertexArray(self.vertex_array[0]) - - print("Gen/Bind VAO: " + str(glGetError())) - - self.vertex_buffer = Buffer(GL_INT, 3) - self.size = len(loopIndices) - glGenBuffers(3, self.vertex_buffer) - - position = [] - for loopIndex in loopIndices: - position.extend(mesh.vertices[mesh.loops[loopIndex].vertex_index].co[0:3]) - self.position_buffer = Buffer(GL_FLOAT, len(position), position) - - uv = [] - if 'UVMap' in mesh.uv_layers: - uv = [0, 0] * len(loopIndices) - else: - for loopUV in mesh.uv_layers['UVMap'].data: - uv.extend(loopUV.uv[0:2]) - self.uv_buffer = Buffer(GL_FLOAT, len(uv), uv) - - colorOrNormal = [] - if True: # TODO: Choose normal or vertex color - for loopIndex in loopIndices: - colorOrNormal.extend(mesh.loops[loopIndex].normal[0:3]) - else: - if 'Col' in mesh.vertex_colors: - color_data = mesh.vertex_colors['Col'].data - else: - color_data = [0,0,0] * len(loopIndices) - - if 'Alpha' in mesh.vertex_colors: - alpha_data = mesh.vertex_colors['Alpha'].data - else: - alpha_data = [0,0,0] * len(loopIndices) - - for loopIndex in loopIndices: - # TODO: Fix Alpha - colorOrNormal.extend(color_data[loopIndex][0:3] + [alpha_data[loopIndex][0]]) - self.colorOrNormal_buffer = Buffer(GL_FLOAT, len(colorOrNormal), colorOrNormal) - - position_location = glGetAttribLocation(render_data.shaderProgram, "pos") - uv_location = glGetAttribLocation(render_data.shaderProgram, "uv") - colorOrNormal_location = glGetAttribLocation(render_data.shaderProgram, "colorOrNormal") - print("pos: " + str(position_location) + ', uv: ' + str(uv_location) +\ - ", colorOrNormal: " + str(colorOrNormal_location)) - print("Get Attribute Locations: " + str(glGetError())) - - # Floats and Ints are 4 bytes? - glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[0]) - print("Bind vertex buffer position: " + str(glGetError())) - glBufferData(GL_ARRAY_BUFFER, len(position) * 4, self.position_buffer, GL_STATIC_DRAW) - print("Buffer position data: " + str(glGetError())) - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None) - glEnableVertexAttribArray(0) - print("Set Attribute Pointer: " + str(glGetError())) - - glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[1]) - glBufferData(GL_ARRAY_BUFFER, len(uv) * 4, self.uv_buffer, GL_STATIC_DRAW) - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, None) - glEnableVertexAttribArray(0) - - glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[2]) - glBufferData(GL_ARRAY_BUFFER, len(colorOrNormal) * 4, self.colorOrNormal_buffer, GL_STATIC_DRAW) - glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, None) - glEnableVertexAttribArray(2) - - glBindBuffer(GL_ARRAY_BUFFER, 0) - glBindVertexArray(0) - - self.render_data = render_data - - print("End submesh: " + str(glGetError())) - - - def draw(self): - print("Draw Start Submesh: " + str(glGetError())) - #glActiveTexture(GL_TEXTURE0) - #glBindTexture(GL_TEXTURE_2D, self.texture[0]) - - # Ignore material for now - #self.material.apply() - - # Handle modifiers? armatures? - transformLocation = glGetUniformLocation(self.render_data.shaderProgram, "transform") - glUniformMatrix4fv(transformLocation, 1, GL_FALSE, self.obj.matrix_world) - glBindVertexArray(self.vertex_array[0]) - glDrawArrays(GL_TRIANGLES, 0, self.size) - glBindVertexArray(0) - glBindTexture(GL_TEXTURE_2D, 0) - print("Draw End Submesh: " + str(glGetError())) - - def __del__(self): - glDeleteBuffers(3, self.vertex_buffer) - - glDeleteBuffers(1, self.position_buffer) - glDeleteBuffers(1, self.uv_buffer) - glDeleteBuffers(1, self.colorOrNormal_buffer) - - glDeleteVertexArrays(1, self.vertex_array) - glBindTexture(GL_TEXTURE_2D, 0) - #glDeleteTextures(1, self.texture) + def __init__(self, f3d_material, obj, triangles, render_data): + print("Begin submesh: " + str(glGetError())) + mesh = obj.data + loopIndices = [] + for triangle in triangles: + for loopIndex in triangle.loops: + loopIndices.append(loopIndex) + + self.material = f3d_material + self.obj = obj + + self.vertex_array = Buffer(GL_INT, 1) + glGenVertexArrays(1, self.vertex_array) + glBindVertexArray(self.vertex_array[0]) + + print("Gen/Bind VAO: " + str(glGetError())) + + self.vertex_buffer = Buffer(GL_INT, 3) + self.size = len(loopIndices) + glGenBuffers(3, self.vertex_buffer) + + position = [] + for loopIndex in loopIndices: + position.extend(mesh.vertices[mesh.loops[loopIndex].vertex_index].co[0:3]) + self.position_buffer = Buffer(GL_FLOAT, len(position), position) + + uv = [] + if "UVMap" in mesh.uv_layers: + uv = [0, 0] * len(loopIndices) + else: + for loopUV in mesh.uv_layers["UVMap"].data: + uv.extend(loopUV.uv[0:2]) + self.uv_buffer = Buffer(GL_FLOAT, len(uv), uv) + + colorOrNormal = [] + if True: # TODO: Choose normal or vertex color + for loopIndex in loopIndices: + colorOrNormal.extend(mesh.loops[loopIndex].normal[0:3]) + else: + if "Col" in mesh.vertex_colors: + color_data = mesh.vertex_colors["Col"].data + else: + color_data = [0, 0, 0] * len(loopIndices) + + if "Alpha" in mesh.vertex_colors: + alpha_data = mesh.vertex_colors["Alpha"].data + else: + alpha_data = [0, 0, 0] * len(loopIndices) + + for loopIndex in loopIndices: + # TODO: Fix Alpha + colorOrNormal.extend(color_data[loopIndex][0:3] + [alpha_data[loopIndex][0]]) + self.colorOrNormal_buffer = Buffer(GL_FLOAT, len(colorOrNormal), colorOrNormal) + + position_location = glGetAttribLocation(render_data.shaderProgram, "pos") + uv_location = glGetAttribLocation(render_data.shaderProgram, "uv") + colorOrNormal_location = glGetAttribLocation(render_data.shaderProgram, "colorOrNormal") + print( + "pos: " + + str(position_location) + + ", uv: " + + str(uv_location) + + ", colorOrNormal: " + + str(colorOrNormal_location) + ) + print("Get Attribute Locations: " + str(glGetError())) + + # Floats and Ints are 4 bytes? + glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[0]) + print("Bind vertex buffer position: " + str(glGetError())) + glBufferData(GL_ARRAY_BUFFER, len(position) * 4, self.position_buffer, GL_STATIC_DRAW) + print("Buffer position data: " + str(glGetError())) + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None) + glEnableVertexAttribArray(0) + print("Set Attribute Pointer: " + str(glGetError())) + + glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[1]) + glBufferData(GL_ARRAY_BUFFER, len(uv) * 4, self.uv_buffer, GL_STATIC_DRAW) + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, None) + glEnableVertexAttribArray(0) + + glBindBuffer(GL_ARRAY_BUFFER, self.vertex_buffer[2]) + glBufferData(GL_ARRAY_BUFFER, len(colorOrNormal) * 4, self.colorOrNormal_buffer, GL_STATIC_DRAW) + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, None) + glEnableVertexAttribArray(2) + + glBindBuffer(GL_ARRAY_BUFFER, 0) + glBindVertexArray(0) + + self.render_data = render_data + + print("End submesh: " + str(glGetError())) + + def draw(self): + print("Draw Start Submesh: " + str(glGetError())) + # glActiveTexture(GL_TEXTURE0) + # glBindTexture(GL_TEXTURE_2D, self.texture[0]) + + # Ignore material for now + # self.material.apply() + + # Handle modifiers? armatures? + transformLocation = glGetUniformLocation(self.render_data.shaderProgram, "transform") + glUniformMatrix4fv(transformLocation, 1, GL_FALSE, self.obj.matrix_world) + glBindVertexArray(self.vertex_array[0]) + glDrawArrays(GL_TRIANGLES, 0, self.size) + glBindVertexArray(0) + glBindTexture(GL_TEXTURE_2D, 0) + print("Draw End Submesh: " + str(glGetError())) + + def __del__(self): + glDeleteBuffers(3, self.vertex_buffer) + + glDeleteBuffers(1, self.position_buffer) + glDeleteBuffers(1, self.uv_buffer) + glDeleteBuffers(1, self.colorOrNormal_buffer) + + glDeleteVertexArrays(1, self.vertex_array) + glBindTexture(GL_TEXTURE_2D, 0) + # glDeleteTextures(1, self.texture) + class F3DDrawData: - def __init__(self): - self.textures = {} - self.materials = {} - self.objects = {} - print("Start: " + str(glGetError())) - - # Create shader program - vertexHandle = glCreateShader(GL_VERTEX_SHADER) - fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER) - glShaderSource(vertexHandle, vertexShader) - glShaderSource(vertexHandle, fragmentShader) - glCompileShader(vertexHandle) - glCompileShader(fragmentHandle) - - print("Shader Created: " + str(glGetError())) - - self.shaderProgram = glCreateProgram() - glAttachShader(self.shaderProgram, vertexHandle) - glAttachShader(self.shaderProgram, fragmentHandle) - glLinkProgram(self.shaderProgram) - glDeleteShader(vertexHandle) - glDeleteShader(fragmentHandle) - - print("Program Created: " + str(glGetError())) - - #messageSize = Buffer(GL_INT, 1) - #message = Buffer(GL_BYTE, 1000) - #glGetShaderInfoLog(self.shaderProgram, 1000, messageSize, message) - print("End: " + str(glGetError())) - - def __del__(self): - for idName, f3dObject in self.objects.items(): - del f3dObject - - def draw(self): - print("Start Draw Data: " + str(glGetError())) - glClearColor(0.2, 0.3, 0.3, 1.0) - glClear(GL_COLOR_BUFFER_BIT) - - print("Before use program: " + str(glGetError())) - glUseProgram(self.shaderProgram) - for idName, f3dObject in self.objects.items(): - f3dObject.draw() + def __init__(self): + self.textures = {} + self.materials = {} + self.objects = {} + print("Start: " + str(glGetError())) + + # Create shader program + vertexHandle = glCreateShader(GL_VERTEX_SHADER) + fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER) + glShaderSource(vertexHandle, vertexShader) + glShaderSource(vertexHandle, fragmentShader) + glCompileShader(vertexHandle) + glCompileShader(fragmentHandle) + + print("Shader Created: " + str(glGetError())) + + self.shaderProgram = glCreateProgram() + glAttachShader(self.shaderProgram, vertexHandle) + glAttachShader(self.shaderProgram, fragmentHandle) + glLinkProgram(self.shaderProgram) + glDeleteShader(vertexHandle) + glDeleteShader(fragmentHandle) + + print("Program Created: " + str(glGetError())) + + # messageSize = Buffer(GL_INT, 1) + # message = Buffer(GL_BYTE, 1000) + # glGetShaderInfoLog(self.shaderProgram, 1000, messageSize, message) + print("End: " + str(glGetError())) + + def __del__(self): + for idName, f3dObject in self.objects.items(): + del f3dObject + + def draw(self): + print("Start Draw Data: " + str(glGetError())) + glClearColor(0.2, 0.3, 0.3, 1.0) + glClear(GL_COLOR_BUFFER_BIT) + + print("Before use program: " + str(glGetError())) + glUseProgram(self.shaderProgram) + for idName, f3dObject in self.objects.items(): + f3dObject.draw() # RenderEngines also need to tell UI Panels that they are compatible with. @@ -373,40 +383,43 @@ def draw(self): # exclude any panels that are replaced by custom panels registered by the # render engine, or that are not supported. def get_panels(): - exclude_panels = { - 'VIEWLAYER_PT_filter', - 'VIEWLAYER_PT_layer_passes', - } + exclude_panels = { + "VIEWLAYER_PT_filter", + "VIEWLAYER_PT_layer_passes", + } + + panels = [] + for panel in bpy.types.Panel.__subclasses__(): + if hasattr(panel, "COMPAT_ENGINES") and "BLENDER_RENDER" in panel.COMPAT_ENGINES: + if panel.__name__ not in exclude_panels: + panels.append(panel) - panels = [] - for panel in bpy.types.Panel.__subclasses__(): - if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: - if panel.__name__ not in exclude_panels: - panels.append(panel) + return panels - return panels render_engine_classes = [ - #F3DRenderEngine, + # F3DRenderEngine, ] -def render_engine_register() : - for cls in render_engine_classes: - register_class(cls) - - for panel in get_panels(): - panel.COMPAT_ENGINES.add('CUSTOM') - #from bl_ui import (properties_render) - #properties_render.RENDER_PT_render.COMPAT_ENGINES.add(CustomRenderEngine.bl_idname) +def render_engine_register(): + for cls in render_engine_classes: + register_class(cls) + + for panel in get_panels(): + panel.COMPAT_ENGINES.add("CUSTOM") + + # from bl_ui import (properties_render) + # properties_render.RENDER_PT_render.COMPAT_ENGINES.add(CustomRenderEngine.bl_idname) + -def render_engine_unregister() : - for cls in render_engine_classes: - unregister_class(cls) +def render_engine_unregister(): + for cls in render_engine_classes: + unregister_class(cls) - for panel in get_panels(): - if 'CUSTOM' in panel.COMPAT_ENGINES: - panel.COMPAT_ENGINES.remove('CUSTOM') + for panel in get_panels(): + if "CUSTOM" in panel.COMPAT_ENGINES: + panel.COMPAT_ENGINES.remove("CUSTOM") - #from bl_ui import (properties_render) - #properties_render.RENDER_PT_render.COMPAT_ENGINES.remove(CustomRenderEngine.bl_idname) \ No newline at end of file + # from bl_ui import (properties_render) + # properties_render.RENDER_PT_render.COMPAT_ENGINES.remove(CustomRenderEngine.bl_idname) diff --git a/fast64_internal/f3d/f3d_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index 9c93c6415..6a8ee8af9 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -4,7 +4,6 @@ from math import ceil, floor from .f3d_enums import * -from .f3d_constants import * from .f3d_material import ( all_combiner_uses, getTmemWordUsage, @@ -1061,35 +1060,13 @@ def savePaletteLoad( assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0 palFmt = texFormatOf[palFormat] nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"] - - if not f3d._HW_VERSION_1: - gfxOut.commands.extend( - [ - DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), - DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), - DPLoadTLUTCmd(loadtile, palLen - 1), - ] - ) - else: - gfxOut.commands.extend( - [ - _DPLoadTextureBlock( - fPalette, - 256 + palAddr, - palFmt, - "G_IM_SIZ_16b", - 4 * palLen, - 1, - 0, - nocm, - nocm, - 0, - 0, - 0, - 0, - ) - ] - ) + gfxOut.commands.extend( + [ + DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette), + DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0), + DPLoadTLUTCmd(loadtile, palLen - 1), + ] + ) # Functions for converting and writing texture and palette data diff --git a/fast64_internal/f3d/f3d_writer.py b/fast64_internal/f3d/f3d_writer.py index b44baadb2..a226dd287 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1,17 +1,18 @@ -from typing import Union +from typing import Union, Optional, Callable, Any, List +from dataclasses import dataclass import functools import bpy, mathutils, os, re, copy, math +from mathutils import Vector from math import ceil from bpy.utils import register_class, unregister_class from .f3d_enums import * -from .f3d_constants import * from .f3d_material import ( all_combiner_uses, getMaterialScrollDimensions, + getZMode, isTexturePointSampled, - isLightingDisabled, - checkIfFlatShaded, + RDPSettings, ) from .f3d_texture_writer import MultitexManager, TileLoad, maybeSaveSingleLargeTextureSetup from .f3d_gbi import * @@ -117,9 +118,7 @@ def getInfoDict(obj): edgeDict[edgeKey].append(face) for loopIndex in face.loops: - convertInfo = LoopConvertInfo( - uv_data, obj, isLightingDisabled(obj.material_slots[face.material_index].material) - ) + convertInfo = LoopConvertInfo(uv_data, obj, obj.material_slots[face.material_index].material) f3dVertDict[loopIndex] = getF3DVert(mesh.loops[loopIndex], face, convertInfo, mesh) for face in mesh.loop_triangles: for edgeKey in face.edge_keys: @@ -325,8 +324,7 @@ def saveMeshWithLargeTexturesByFaces( texDimensions, material, currentGroupIndex, - triGroup.triList, - triGroup.vertexList, + triGroup, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), ) @@ -428,18 +426,18 @@ def addCullCommand(obj, fMesh, transformMatrix, matWriteMethod): fMesh.add_cull_vtx() # if the object has a specifically set culling bounds, use that instead for vertexPos in obj.get("culling_bounds", obj.bound_box): - # Most other fields of convertVertexData are unnecessary for bounding box verts fMesh.cullVertexList.vertices.append( - convertVertexData( - obj.data, - mathutils.Vector(vertexPos), + F3DVert( + Vector(vertexPos), [0, 0], + Vector([0, 0, 0]), None, - mathutils.Vector([0, 0, 0, 0]), + 0.0, + ).toVtx( + obj.data, [32, 32], transformMatrix, - False, - False, + True, ) ) @@ -471,8 +469,9 @@ def exportF3DCommon(obj, fModel, transformMatrix, includeChildren, name, DLForma try: infoDict = getInfoDict(tempObj) triConverterInfo = TriangleConverterInfo(tempObj, None, fModel.f3d, transformMatrix, infoDict) + revert_materials = fModel.matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert fMeshes = saveStaticModel( - triConverterInfo, fModel, tempObj, transformMatrix, name, convertTextureData, True, None + triConverterInfo, fModel, tempObj, transformMatrix, name, convertTextureData, revert_materials, None ) cleanupCombineObj(tempObj, meshList) obj.select_set(True) @@ -515,7 +514,14 @@ def revertMatAndEndDraw(gfxList, otherCommands): DPPipeSync(), SPSetGeometryMode(["G_LIGHTING"]), SPClearGeometryMode(["G_TEXTURE_GEN"]), - DPSetCombineMode(*S_SHADED_SOLID), + DPSetCombineMode( + *( + ["0", "0", "0", "SHADE"] + + ["0", "0", "0", "ENVIRONMENT"] + + ["0", "0", "0", "SHADE"] + + ["0", "0", "0", "ENVIRONMENT"] + ) + ), SPTexture(0xFFFF, 0xFFFF, 0, 0, 0), ] + otherCommands @@ -651,8 +657,7 @@ def saveMeshByFaces( texDimensions, material, currentGroupIndex, - triGroup.triList, - triGroup.vertexList, + triGroup, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), ) @@ -665,28 +670,11 @@ def saveMeshByFaces( return currentGroupIndex -def get8bitRoundedNormal(loop: bpy.types.MeshLoop, mesh): - alpha_layer = getColorLayer(mesh, "Alpha") - - if alpha_layer is not None: - normalizedAColor = alpha_layer[loop.index].color - if is3_2_or_above(): - normalizedAColor = gammaCorrect(normalizedAColor) - normalizedA = colorToLuminance(normalizedAColor[0:3]) - else: - normalizedA = 1 - - # Don't round, as this may move UV toward UV bounds. - return mathutils.Vector( - (int(loop.normal[0] * 128) / 128, int(loop.normal[1] * 128) / 128, int(loop.normal[2] * 128) / 128, normalizedA) - ) - - +@dataclass class LoopConvertInfo: - def __init__(self, uv_data: bpy.types.bpy_prop_collection | list[bpy.types.MeshUVLoop], obj, exportVertexColors): - self.uv_data: bpy.types.bpy_prop_collection | list[bpy.types.MeshUVLoop] = uv_data - self.obj = obj - self.exportVertexColors = exportVertexColors + uv_data: bpy.types.bpy_prop_collection | list[bpy.types.MeshUVLoop] + obj: bpy.types.Object + material: bpy.types.Material def getNewIndices(existingIndices, bufferStart): @@ -701,21 +689,21 @@ def getNewIndices(existingIndices, bufferStart): return newIndices -# Color and normal are separate, since for parsing, the normal must be transformed into -# bone/object space while the color should just be a regular conversion. class F3DVert: def __init__( self, - position: mathutils.Vector, - uv: mathutils.Vector, - color: mathutils.Vector | None, # 4 components - normal: mathutils.Vector | None, # 4 components + position: Vector, + uv: Vector, + rgb: Optional[Vector], + normal: Optional[Vector], + alpha: float, ): - self.position: mathutils.Vector = position - self.uv: mathutils.Vector = uv - self.stOffset: tuple(int, int) | None = None - self.color: mathutils.Vector | None = color - self.normal: mathutils.Vector | None = normal + self.position: Vector = position + self.uv: Vector = uv + self.stOffset: Optional[tuple(int, int)] = None + self.rgb: Optional[Vector] = rgb + self.normal: Optional[Vector] = normal + self.alpha: float = alpha def __eq__(self, other): if not isinstance(other, F3DVert): @@ -724,17 +712,50 @@ def __eq__(self, other): self.position == other.position and self.uv == other.uv and self.stOffset == other.stOffset - and self.color == other.color + and self.rgb == other.rgb and self.normal == other.normal + and self.alpha == other.alpha + ) + + def toVtx(self, mesh, texDimensions, transformMatrix, isPointSampled: bool, tex_scale=(1, 1)) -> Vtx: + # Position (8 bytes) + position = [int(round(floatValue)) for floatValue in (transformMatrix @ self.position)] + + # UV (4 bytes) + # For F3D, Bilinear samples the point from the center of the pixel. + # However, Point samples from the corner. + # Thus we add 0.5 to the UV only if bilinear filtering. + # see section 13.7.5.3 in programming manual. + pixelOffset = ( + (0, 0) + if (isPointSampled or tex_scale[0] == 0 or tex_scale[1] == 0) + else (0.5 / tex_scale[0], 0.5 / tex_scale[1]) ) + pixelOffset = self.stOffset if self.stOffset is not None else pixelOffset - def getColorOrNormal(self): - if self.color is None and self.normal is None: - raise PluginError("An F3D vert has neither a color or a normal.") - elif self.color is not None: - return self.color + uv = [ + convertFloatToFixed16(self.uv[0] * texDimensions[0] - pixelOffset[0]), + convertFloatToFixed16(self.uv[1] * texDimensions[1] - pixelOffset[1]), + ] + + packedNormal = 0 + if self.normal is not None: + # normal transformed correctly. + normal = (transformMatrix.inverted().transposed() @ self.normal).normalized() + if self.rgb is not None: + packedNormal = packNormal(normal) + + if self.rgb is not None: + colorOrNormal = [scaleToU8(c).to_bytes(1, "big")[0] for c in self.rgb] else: - return self.normal + colorOrNormal = [ + int(round(normal[0] * 127)).to_bytes(1, "big", signed=True)[0], + int(round(normal[1] * 127)).to_bytes(1, "big", signed=True)[0], + int(round(normal[2] * 127)).to_bytes(1, "big", signed=True)[0], + ] + colorOrNormal.append(scaleToU8(self.alpha).to_bytes(1, "big")[0]) + + return Vtx(position, uv, colorOrNormal, packedNormal) # groupIndex is either a vertex group (writing), or name of c variable identifying a transform group, like a limb (parsing) @@ -796,8 +817,7 @@ def __init__( texDimensions: tuple[int, int], material: bpy.types.Material, currentGroupIndex, - triList, - vtxList, + triGroup: FTriGroup, existingVertexData: list[BufferVertex], existingVertexMaterialRegions, ): @@ -813,15 +833,15 @@ def __init__( self.bufferStart = len(self.vertBuffer) self.vertexBufferTriangles = [] # [(index0, index1, index2)] - self.triList = triList - self.vtxList = vtxList + self.triGroup = triGroup + self.triList = triGroup.triList + self.vtxList = triGroup.vertexList - exportVertexColors = isLightingDisabled(material) + self.material = material uv_data = triConverterInfo.obj.data.uv_layers["UVMap"].data - self.convertInfo = LoopConvertInfo(uv_data, triConverterInfo.obj, exportVertexColors) + self.convertInfo = LoopConvertInfo(uv_data, triConverterInfo.obj, material) self.texDimensions = texDimensions self.isPointSampled = isTexturePointSampled(material) - self.exportVertexColors = exportVertexColors self.tex_scale = material.f3d_mat.tex_scale def vertInBuffer(self, bufferVert, material_index): @@ -862,16 +882,11 @@ def processGeometry(self): # Save vertices for bufferVert in self.vertBuffer[bufferStart:bufferEnd]: self.vtxList.vertices.append( - convertVertexData( + bufferVert.f3dVert.toVtx( self.triConverterInfo.mesh, - bufferVert.f3dVert.position, - bufferVert.f3dVert.uv, - bufferVert.f3dVert.stOffset, - bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, - self.exportVertexColors, tex_scale=self.tex_scale, ) ) @@ -897,16 +912,11 @@ def processGeometry(self): # Save vertices for bufferVert in self.vertBuffer[bufferStart:bufferEnd]: self.vtxList.vertices.append( - convertVertexData( + bufferVert.f3dVert.toVtx( self.triConverterInfo.mesh, - bufferVert.f3dVert.position, - bufferVert.f3dVert.uv, - bufferVert.f3dVert.stOffset, - bufferVert.f3dVert.getColorOrNormal(), self.texDimensions, self.triConverterInfo.getTransformMatrix(bufferVert.groupIndex), self.isPointSampled, - self.exportVertexColors, tex_scale=self.tex_scale, ) ) @@ -914,9 +924,135 @@ def processGeometry(self): bufferStart = bufferEnd # Load triangles - self.triList.commands.extend( - createTriangleCommands(self.vertexBufferTriangles, self.vertBuffer, self.triConverterInfo.f3d.F3DEX_GBI) + triCmds = createTriangleCommands( + self.vertexBufferTriangles, self.vertBuffer, not self.triConverterInfo.f3d.F3D_OLD_GBI ) + if not self.material.f3d_mat.use_cel_shading: + self.triList.commands.extend(triCmds) + else: + if len(triCmds) <= 2: + self.writeCelLevels(triCmds=triCmds) + else: + celTriList = self.triGroup.add_cel_tri_list() + celTriList.commands.extend(triCmds) + celTriList.commands.append(SPEndDisplayList()) + self.writeCelLevels(celTriList=celTriList) + + def writeCelLevels(self, celTriList: Optional[GfxList] = None, triCmds: Optional[List[GbiMacro]] = None) -> None: + assert (celTriList == None) != (triCmds == None) + f3dMat = self.material.f3d_mat + cel = f3dMat.cel_shading + f3d = get_F3D_GBI() + + # Don't want to have to change back and forth arbitrarily between decal and + # opaque mode. So if you're using both lighter and darker, need to do those + # first before switching to decal. + if getZMode(self.material) != "ZMODE_OPA": + raise PluginError( + f"Material {self.material.name} with cel shading: zmode in blender / rendermode must be opaque.", + icon="ERROR", + ) + wroteLighter = wroteDarker = usesDecal = False + if len(cel.levels) == 0: + raise PluginError(f"Material {self.material.name} with cel shading has no cel levels") + for level in cel.levels: + if level.threshMode == "Darker": + if wroteDarker: + usesDecal = True + elif usesDecal: + raise PluginError( + f"Material {self.material.name}: must use Lighter and Darker cel levels before duplicating either of them" + ) + wroteDarker = True + else: + if wroteLighter: + usesDecal = True + elif usesDecal: + raise PluginError( + f"Material {self.material.name}: must use Lighter and Darker cel levels before duplicating either of them" + ) + wroteLighter = True + + # Because this might not be the first tri list in the object with this + # material, we have to set things even if they were set up already in + # the material. + wroteLighter = wroteDarker = wroteOpaque = wroteDecal = False + lastDarker = None + for level in cel.levels: + darker = level.threshMode == "Darker" + self.triList.commands.append(DPPipeSync()) + if usesDecal: + if not wroteOpaque: + wroteOpaque = True + self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, ["ZMODE_OPA"])) + if not wroteDecal and (darker and wroteDarker or not darker and wroteLighter): + wroteDecal = True + self.triList.commands.append(SPSetOtherMode("G_SETOTHERMODE_L", 10, 2, ["ZMODE_DEC"])) + if darker: + wroteDarker = True + else: + wroteLighter = True + + if lastDarker != darker: + lastDarker = darker + # Set up CC. + ccSettings = [] + for prop in ["A", "B", "C", "D"]: + ccSettings.append(getattr(f3dMat.combiner1, prop)) + ccSettings.extend(["1", "SHADE"] if darker else ["SHADE", "0"]) + ccSettings.extend([cel.cutoutSource, "0"]) + if f3dMat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE": + for prop in ["A", "B", "C", "D", "A_alpha", "B_alpha", "C_alpha", "D_alpha"]: + ccSettings.append(getattr(f3dMat.combiner2, prop)) + else: + ccSettings *= 2 + self.triList.commands.append(DPSetCombineMode(*ccSettings)) + + # Set up tint color and level + if level.tintType == "Fixed": + color = exportColor(level.tintFixedColor) + if cel.tintPipeline == "CC": + self.triList.commands.append( + DPSetPrimColor(0, 0, color[0], color[1], color[2], level.tintFixedLevel) + ) + else: + self.triList.commands.append(DPSetFogColor(color[0], color[1], color[2], level.tintFixedLevel)) + elif level.tintType == "Segment": + self.triList.commands.append( + SPDisplayList( + GfxList( + f"{level.tintSegmentNum:#04x}{level.tintSegmentOffset * 8:06x}", + GfxListTag.Material, + DLFormat.Static, + ) + ) + ) + elif level.tintType == "Light": + if cel.tintPipeline == "CC": + self.triList.commands.append(SPLightToPrimColor(level.tintLightSlot, level.tintFixedLevel, 0, 0)) + else: + self.triList.commands.append(SPLightToFogColor(level.tintLightSlot, level.tintFixedLevel)) + else: + raise PluginError("Unknown tint type") + + # Set up threshold + self.triList.commands.append( + DPSetBlendColor(255, 255, 255, 0x100 - level.threshold if darker else level.threshold) + ) + self.triList.commands.append( + SPAlphaCompareCull( + "G_ALPHA_COMPARE_CULL_ABOVE" if darker else "G_ALPHA_COMPARE_CULL_BELOW", level.threshold + ) + ) + + # Draw tris, inline or by call + if triCmds is not None: + self.triList.commands.extend(triCmds) + else: + self.triList.commands.append(SPDisplayList(celTriList)) + + # Disable alpha compare culling for future DLs + self.triList.commands.append(SPAlphaCompareCull("G_ALPHA_COMPARE_CULL_DISABLE", 0)) def addFace(self, face, stOffset): triIndices = [] @@ -962,187 +1098,43 @@ def finish(self, terminateDL): def getF3DVert(loop: bpy.types.MeshLoop, face, convertInfo: LoopConvertInfo, mesh: bpy.types.Mesh): - position: mathutils.Vector = mesh.vertices[loop.vertex_index].co.copy().freeze() + position: Vector = mesh.vertices[loop.vertex_index].co.copy().freeze() # N64 is -Y, Blender is +Y - uv: mathutils.Vector = convertInfo.uv_data[loop.index].uv.copy() + uv: Vector = convertInfo.uv_data[loop.index].uv.copy() uv[:] = [field if not math.isnan(field) else 0 for field in uv] uv[1] = 1 - uv[1] uv = uv.freeze() - color, normal = getLoopColorOrNormal( - loop, face, convertInfo.obj.data, convertInfo.obj, convertInfo.exportVertexColors - ) - - return F3DVert(position, uv, color, normal) - - -def getLoopNormal(loop: bpy.types.MeshLoop, face, mesh, isFlatShaded): - # This is a workaround for flat shading not working well. - # Since we support custom blender normals we can now ignore this. - # if isFlatShaded: - # normal = -face.normal #??? - # else: - # normal = -loop.normal #??? - # return get8bitRoundedNormal(normal).freeze() - return get8bitRoundedNormal(loop, mesh).freeze() - - -""" -def getLoopNormalCreased(bLoop, obj): - edges = obj.data.edges - centerVert = bLoop.vert - - availableFaces = [] - visitedFaces = [bLoop.face] - connectedFaces = getConnectedFaces(bLoop.face, bLoop.vert) - if len(connectedFaces) == 0: - return bLoop.calc_normal() - - for face in connectedFaces: - availableFaces.append(FaceWeight(face, bLoop.face, 1)) - - curNormal = bLoop.calc_normal() * bLoop.calc_angle() - while len(availableFaces) > 0: - nextFaceWeight = getHighestFaceWeight(availableFaces) - curNormal += getWeightedNormalAndMoveToNextFace( - nextFaceWeight, visitedFaces, availableFaces, centerVert, edges) - - return curNormal.normalized() - -def getConnectedFaces(bFace, bVert): - connectedFaces = [] - for face in bVert.link_faces: - if face == bFace: - continue - for edge in face.edges: - if bFace in edge.link_faces: - connectedFaces.append(face) - return connectedFaces - -# returns false if not enough faces to check for creasing -def getNextFace(faceWeight, bVert, visitedFaces, availableFaces): - connectedFaces = getConnectedFaces(faceWeight.face, bVert) - visitedFaces.append(faceWeight.face) - - newFaceFound = False - nextPrevFace = faceWeight.face - for face in connectedFaces: - if face in visitedFaces: - continue - elif not newFaceFound: - newFaceFound = True - faceWeight.prevFace = faceWeight.face - faceWeight.face = face - else: - availableFaces.append(FaceWeight(face, nextPrevFace, - faceWeight.weight)) - - if not newFaceFound: - availableFaces.remove(faceWeight) - - removedFaceWeights = [] - for otherFaceWeight in availableFaces: - if otherFaceWeight.face in visitedFaces: - removedFaceWeights.append(otherFaceWeight) - for removedFaceWeight in removedFaceWeights: - availableFaces.remove(removedFaceWeight) - -def getLoopFromFaceVert(bFace, bVert): - for loop in bFace.loops: - if loop.vert == bVert: - return loop - return None - -def getEdgeBetweenFaces(faceWeight): - face1 = faceWeight.face - face2 = faceWeight.prevFace - for edge1 in face1.edges: - for edge2 in face2.edges: - if edge1 == edge2: - return edge1 - return None - -class FaceWeight: - def __init__(self, face, prevFace, weight): - self.face = face - self.prevFace = prevFace - self.weight = weight - -def getWeightedNormalAndMoveToNextFace(selectFaceWeight, visitedFaces, - availableFaces, centerVert, edges): - selectLoop = getLoopFromFaceVert(selectFaceWeight.face, centerVert) - edgeIndex = getEdgeBetweenFaces(selectFaceWeight).index - - # Ignore triangulated faces - if edgeIndex < len(edges): - selectFaceWeight.weight *= 1 - edges[edgeIndex].crease - - getNextFace(selectFaceWeight, centerVert, visitedFaces, availableFaces) - return selectLoop.calc_normal() * selectLoop.calc_angle() * \ - selectFaceWeight.weight - -def getHighestFaceWeight(faceWeights): - highestFaceWeight = faceWeights[0] - for faceWeight in faceWeights[1:]: - if faceWeight.weight > highestFaceWeight.weight: - highestFaceWeight = faceWeight - return highestFaceWeight -""" - - -def convertVertexData( - mesh, - loopPos, - loopUV, - stOffset, - loopColorOrNormal, - texDimensions, - transformMatrix, - isPointSampled, - exportVertexColors, - tex_scale=(1, 1), -): - # Position (8 bytes) - position = [int(round(floatValue)) for floatValue in (transformMatrix @ loopPos)] - - # UV (4 bytes) - # For F3D, Bilinear samples the point from the center of the pixel. - # However, Point samples from the corner. - # Thus we add 0.5 to the UV only if bilinear filtering. - # see section 13.7.5.3 in programming manual. - pixelOffset = ( - (0, 0) - if (isPointSampled or tex_scale[0] == 0 or tex_scale[1] == 0) - else (0.5 / tex_scale[0], 0.5 / tex_scale[1]) - ) - pixelOffset = stOffset if stOffset is not None else pixelOffset - - uv = [ - convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset[0]), - convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset[1]), - ] - # Color/Normal (4 bytes) - if exportVertexColors: - colorOrNormal = [scaleToU8(c).to_bytes(1, "big")[0] for c in loopColorOrNormal] - else: - # normal transformed correctly. - normal = (transformMatrix.inverted().transposed() @ loopColorOrNormal).normalized() - colorOrNormal = [ - int(round(normal[0] * 127)).to_bytes(1, "big", signed=True)[0], - int(round(normal[1] * 127)).to_bytes(1, "big", signed=True)[0], - int(round(normal[2] * 127)).to_bytes(1, "big", signed=True)[0], - scaleToU8(loopColorOrNormal[3]).to_bytes(1, "big")[0], - ] - - return Vtx(position, uv, colorOrNormal) + has_rgb, has_normal, _ = getRgbNormalSettings(convertInfo.material.f3d_mat) + mesh = convertInfo.obj.data + color = getLoopColor(loop, mesh) + rgb = color[:3] if has_rgb else None + normal = getLoopNormal(loop) if has_normal else None + alpha = color[3] + + return F3DVert(position, uv, rgb, normal, alpha) + + +def getLoopNormal(loop: bpy.types.MeshLoop) -> Vector: + # Have to quantize to something because F3DVerts will be compared, and we + # don't want floating-point inaccuracy causing "same" vertices not to be + # merged. But, it hasn't been transformed yet, so quantizing to s8 here will + # lose some accuracy. + return Vector( + ( + round(loop.normal[0] * 2**16) / 2**16, + round(loop.normal[1] * 2**16) / 2**16, + round(loop.normal[2] * 2**16) / 2**16, + ) + ).freeze() @functools.lru_cache(0) def is3_2_or_above(): - return bpy.app.version[0] >= 3 and bpy.app.version[1] >= 2 + return bpy.app.version >= (3, 2, 0) -def getLoopColor(loop: bpy.types.MeshLoop, mesh, mat_ver): +def getLoopColor(loop: bpy.types.MeshLoop, mesh: bpy.types.Mesh) -> Vector: color_layer = getColorLayer(mesh, layer="Col") alpha_layer = getColorLayer(mesh, layer="Alpha") @@ -1164,57 +1156,22 @@ def getLoopColor(loop: bpy.types.MeshLoop, mesh, mat_ver): return mathutils.Vector((normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedA)) -def getLoopColorOrNormal( - loop: bpy.types.MeshLoop, face, mesh: bpy.types.Mesh, obj: bpy.types.Object, exportVertexColors: bool -) -> tuple[mathutils.Vector, None] | tuple[None, mathutils.Vector]: - material = obj.material_slots[face.material_index].material - isFlatShaded = checkIfFlatShaded(material) - if exportVertexColors: - return getLoopColor(loop, mesh, material.mat_ver), None - else: - return None, getLoopNormal(loop, face, mesh, isFlatShaded) - - def createTriangleCommands(triangles, vertexBuffer, useSP2Triangle): triangles = copy.deepcopy(triangles) commands = [] - if useSP2Triangle: - while len(triangles) > 0: - if len(triangles) >= 2: - commands.append( - SP2Triangles( - vertexBuffer.index(triangles[0][0]), - vertexBuffer.index(triangles[0][1]), - vertexBuffer.index(triangles[0][2]), - 0, - vertexBuffer.index(triangles[1][0]), - vertexBuffer.index(triangles[1][1]), - vertexBuffer.index(triangles[1][2]), - 0, - ) - ) - triangles = triangles[2:] - else: - commands.append( - SP1Triangle( - vertexBuffer.index(triangles[0][0]), - vertexBuffer.index(triangles[0][1]), - vertexBuffer.index(triangles[0][2]), - 0, - ) - ) - triangles = [] - else: - while len(triangles) > 0: - commands.append( - SP1Triangle( - vertexBuffer.index(triangles[0][0]), - vertexBuffer.index(triangles[0][1]), - vertexBuffer.index(triangles[0][2]), - 0, - ) - ) - triangles = triangles[1:] + + def getIndices(tri): + return [vertexBuffer.index(v) for v in tri] + + 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 + else: + commands.append(SP1Triangle(*firstTriIndices, 0)) return commands @@ -1339,6 +1296,33 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): ) ) + if f3dMat.set_ao: + fMaterial.mat_only_DL.commands.append( + SPAmbOcclusion( + min(round(f3dMat.ao_ambient * 2**16), 0xFFFF), + min(round(f3dMat.ao_directional * 2**16), 0xFFFF), + min(round(f3dMat.ao_point * 2**16), 0xFFFF), + ) + ) + + if f3dMat.set_fresnel: + dotMin = round(f3dMat.fresnel_lo * 0x7FFF) + dotMax = round(f3dMat.fresnel_hi * 0x7FFF) + scale = max(min(0x3F8000 // (dotMax - dotMin), 0x7FFF), -0x8000) + offset = max(min(-(0x7F * dotMin) // (dotMax - dotMin), 0x7FFF), -0x8000) + fMaterial.mat_only_DL.commands.append(SPFresnel(scale, offset)) + + if f3dMat.set_attroffs_st: + fMaterial.mat_only_DL.commands.append( + SPAttrOffsetST( + to_s16(f3dMat.attroffs_st[0] * 32), + to_s16(f3dMat.attroffs_st[1] * 32), + ) + ) + + if f3dMat.set_attroffs_z: + fMaterial.mat_only_DL.commands.append(SPAttrOffsetZ(f3dMat.attroffs_z)) + if f3dMat.set_fog: if f3dMat.use_global_fog and fModel.global_data.getCurrentAreaData() is not None: fogData = fModel.global_data.getCurrentAreaData().fog_data @@ -1375,7 +1359,6 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): f3dMat.rdp_settings, multitexManager.getTT(), defaults, - fModel.f3d._HW_VERSION_1, fModel.matWriteMethod, fModel.f3d, ) @@ -1414,7 +1397,7 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): fMaterial.mat_only_DL.commands.extend([SPSetLights(fLights)]) # TODO: handle synching: NO NEED? if useDict["Key"] and f3dMat.set_key: - if material.mat_ver == 4: + if material.mat_ver >= 4: center = f3dMat.key_center else: center = nodes["Chroma Key Center"].outputs[0].default_value @@ -1484,11 +1467,11 @@ def saveLightsDefinition(fModel, fMaterial, material, lightsName): if lights is not None: return lights - lights = Lights(toAlnum(lightsName)) + lights = Lights(toAlnum(lightsName), fModel.f3d) if material.use_default_lighting: lights.a = Ambient(exportColor(material.ambient_light_color)) - lights.l.append(Light(exportColor(material.default_light_color), [0x28, 0x28, 0x28])) + lights.l.append(Light(exportColor(material.default_light_color), [0x49, 0x49, 0x49])) else: lights.a = Ambient(exportColor(material.ambient_light_color)) @@ -1531,21 +1514,41 @@ def saveBitGeoF3DEX2(value, defaultValue, flagName, geo, matWriteMethod): geo.clearFlagList.append(flagName) -def saveGeoModeDefinitionF3DEX2(fMaterial, settings, defaults, matWriteMethod): - geo = SPGeometryMode([], []) +def saveBitGeo(value, defaultValue, flagName, setGeo, clearGeo, matWriteMethod): + if value != defaultValue or matWriteMethod == GfxMatWriteMethod.WriteAll: + if value: + setGeo.flagList.append(flagName) + else: + clearGeo.flagList.append(flagName) + - saveBitGeoF3DEX2(settings.g_zbuffer, defaults.g_zbuffer, "G_ZBUFFER", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_shade, defaults.g_shade, "G_SHADE", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_cull_front, defaults.g_cull_front, "G_CULL_FRONT", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_cull_back, defaults.g_cull_back, "G_CULL_BACK", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_fog, defaults.g_fog, "G_FOG", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_lighting, defaults.g_lighting, "G_LIGHTING", geo, matWriteMethod) +def saveGeoModeCommon(saveFunc: Callable, settings: RDPSettings, defaults: RDPSettings, args: Any): + saveFunc(settings.g_zbuffer, defaults.g_zbuffer, "G_ZBUFFER", *args) + 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": + 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) + saveFunc(settings.g_packed_normals, defaults.g_packed_normals, "G_PACKED_NORMALS", *args) + saveFunc(settings.g_lighttoalpha, defaults.g_lighttoalpha, "G_LIGHTTOALPHA", *args) + 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) + 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): + saveFunc(settings.g_clipping, defaults.g_clipping, "G_CLIPPING", *args) - # make sure normals are saved correctly. - saveBitGeoF3DEX2(settings.g_tex_gen, defaults.g_tex_gen, "G_TEXTURE_GEN", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_tex_gen_linear, defaults.g_tex_gen_linear, "G_TEXTURE_GEN_LINEAR", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_shade_smooth, defaults.g_shade_smooth, "G_SHADING_SMOOTH", geo, matWriteMethod) - saveBitGeoF3DEX2(settings.g_clipping, defaults.g_clipping, "G_CLIPPING", geo, matWriteMethod) + +def saveGeoModeDefinitionF3DEX2(fMaterial, settings, defaults, matWriteMethod): + geo = SPGeometryMode([], []) + saveGeoModeCommon(saveBitGeoF3DEX2, settings, defaults, (geo, matWriteMethod)) if len(geo.clearFlagList) != 0 or len(geo.setFlagList) != 0: if len(geo.clearFlagList) == 0: @@ -1560,33 +1563,11 @@ def saveGeoModeDefinitionF3DEX2(fMaterial, settings, defaults, matWriteMethod): fMaterial.revert.commands.append(SPGeometryMode(geo.setFlagList, geo.clearFlagList)) -def saveBitGeo(value, defaultValue, flagName, setGeo, clearGeo, matWriteMethod): - if value != defaultValue or matWriteMethod == GfxMatWriteMethod.WriteAll: - if value: - setGeo.flagList.append(flagName) - else: - clearGeo.flagList.append(flagName) - - def saveGeoModeDefinition(fMaterial, settings, defaults, matWriteMethod): setGeo = SPSetGeometryMode([]) clearGeo = SPClearGeometryMode([]) - saveBitGeo(settings.g_zbuffer, defaults.g_zbuffer, "G_ZBUFFER", setGeo, clearGeo, matWriteMethod) - saveBitGeo(settings.g_shade, defaults.g_shade, "G_SHADE", setGeo, clearGeo, matWriteMethod) - saveBitGeo(settings.g_cull_front, defaults.g_cull_front, "G_CULL_FRONT", setGeo, clearGeo, matWriteMethod) - saveBitGeo(settings.g_cull_back, defaults.g_cull_back, "G_CULL_BACK", setGeo, clearGeo, matWriteMethod) - saveBitGeo(settings.g_fog, defaults.g_fog, "G_FOG", setGeo, clearGeo, matWriteMethod) - saveBitGeo(settings.g_lighting, defaults.g_lighting, "G_LIGHTING", setGeo, clearGeo, matWriteMethod) - - # make sure normals are saved correctly. - saveBitGeo(settings.g_tex_gen, defaults.g_tex_gen, "G_TEXTURE_GEN", setGeo, clearGeo, matWriteMethod) - saveBitGeo( - settings.g_tex_gen_linear, defaults.g_tex_gen_linear, "G_TEXTURE_GEN_LINEAR", setGeo, clearGeo, matWriteMethod - ) - saveBitGeo(settings.g_shade_smooth, defaults.g_shade_smooth, "G_SHADING_SMOOTH", setGeo, clearGeo, matWriteMethod) - if bpy.context.scene.f3d_type == "F3DEX_GBI_2" or bpy.context.scene.f3d_type == "F3DEX_GBI": - saveBitGeo(settings.g_clipping, defaults.g_clipping, "G_CLIPPING", setGeo, clearGeo, matWriteMethod) + saveGeoModeCommon(saveBitGeo, settings, defaults, (setGeo, clearGeo, matWriteMethod)) if len(setGeo.flagList) > 0: fMaterial.mat_only_DL.commands.append(setGeo) @@ -1604,22 +1585,20 @@ def saveModeSetting(fMaterial, value, defaultValue, cmdClass): fMaterial.revert.commands.append(cmdClass(defaultValue)) -def saveOtherModeHDefinition(fMaterial, settings, tlut, defaults, isHWv1, matWriteMethod, f3d): +def saveOtherModeHDefinition(fMaterial, settings, tlut, defaults, matWriteMethod, f3d): if matWriteMethod == GfxMatWriteMethod.WriteAll: - saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, isHWv1, f3d) + saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, f3d) elif matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: - saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults, isHWv1) + saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults) else: raise PluginError("Unhandled material write method: " + str(matWriteMethod)) -def saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, isHWv1, f3d): - is_f3d_old = all((not f3d.F3DEX_GBI, not f3d.F3DEX_GBI_2, not f3d.F3DLP_GBI)) - cmd = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - is_f3d_old, []) +def saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, f3d): + cmd = SPSetOtherMode("G_SETOTHERMODE_H", 4, 20 - f3d.F3D_OLD_GBI, []) cmd.flagList.append(settings.g_mdsft_alpha_dither) - if not isHWv1: - cmd.flagList.append(settings.g_mdsft_rgb_dither) - cmd.flagList.append(settings.g_mdsft_combkey) + cmd.flagList.append(settings.g_mdsft_rgb_dither) + cmd.flagList.append(settings.g_mdsft_combkey) cmd.flagList.append(settings.g_mdsft_textconv) cmd.flagList.append(settings.g_mdsft_text_filt) cmd.flagList.append(tlut) @@ -1627,96 +1606,55 @@ def saveOtherModeHDefinitionAll(fMaterial, settings, tlut, defaults, isHWv1, f3d cmd.flagList.append(settings.g_mdsft_textdetail) cmd.flagList.append(settings.g_mdsft_textpersp) cmd.flagList.append(settings.g_mdsft_cycletype) - if isHWv1: - cmd.flagList.append(settings.g_mdsft_color_dither) cmd.flagList.append(settings.g_mdsft_pipeline) fMaterial.mat_only_DL.commands.append(cmd) -def saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults, isHWv1): +def saveOtherModeHDefinitionIndividual(fMaterial, settings, tlut, defaults): saveModeSetting(fMaterial, settings.g_mdsft_alpha_dither, defaults.g_mdsft_alpha_dither, DPSetAlphaDither) - - if not isHWv1: - saveModeSetting(fMaterial, settings.g_mdsft_rgb_dither, defaults.g_mdsft_rgb_dither, DPSetColorDither) - - saveModeSetting(fMaterial, settings.g_mdsft_combkey, defaults.g_mdsft_combkey, DPSetCombineKey) - + saveModeSetting(fMaterial, settings.g_mdsft_rgb_dither, defaults.g_mdsft_rgb_dither, DPSetColorDither) + saveModeSetting(fMaterial, settings.g_mdsft_combkey, defaults.g_mdsft_combkey, DPSetCombineKey) saveModeSetting(fMaterial, settings.g_mdsft_textconv, defaults.g_mdsft_textconv, DPSetTextureConvert) - saveModeSetting(fMaterial, settings.g_mdsft_text_filt, defaults.g_mdsft_text_filt, DPSetTextureFilter) - saveModeSetting(fMaterial, tlut, "G_TT_NONE", DPSetTextureLUT) - saveModeSetting(fMaterial, settings.g_mdsft_textlod, defaults.g_mdsft_textlod, DPSetTextureLOD) - saveModeSetting(fMaterial, settings.g_mdsft_textdetail, defaults.g_mdsft_textdetail, DPSetTextureDetail) - saveModeSetting(fMaterial, settings.g_mdsft_textpersp, defaults.g_mdsft_textpersp, DPSetTexturePersp) - saveModeSetting(fMaterial, settings.g_mdsft_cycletype, defaults.g_mdsft_cycletype, DPSetCycleType) - - if isHWv1: - saveModeSetting(fMaterial, settings.g_mdsft_color_dither, defaults.g_mdsft_color_dither, DPSetColorDither) - saveModeSetting(fMaterial, settings.g_mdsft_pipeline, defaults.g_mdsft_pipeline, DPPipelineMode) def saveOtherModeLDefinition(fMaterial, settings, defaults, defaultRenderMode, matWriteMethod, f3d): if matWriteMethod == GfxMatWriteMethod.WriteAll: - saveOtherModeLDefinitionAll(fMaterial, settings, defaults, defaultRenderMode, f3d) + saveOtherModeLDefinitionAll(fMaterial, settings, defaults, f3d) elif matWriteMethod == GfxMatWriteMethod.WriteDifferingAndRevert: saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRenderMode) else: raise PluginError("Unhandled material write method: " + str(matWriteMethod)) -def saveOtherModeLDefinitionAll(fMaterial: FMaterial, settings, defaults, defaultRenderMode, f3d): - is_f3d_old = all((not f3d.F3DEX_GBI, not f3d.F3DEX_GBI_2, not f3d.F3DLP_GBI)) - if not settings.set_rendermode: - cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, 3 - is_f3d_old, []) - cmd.flagList.append(settings.g_mdsft_alpha_compare) - cmd.flagList.append(settings.g_mdsft_zsrcsel) +def saveOtherModeLDefinitionAll(fMaterial: FMaterial, settings, defaults, f3d): + baseLength = 3 if not settings.set_rendermode else 32 + cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, baseLength - f3d.F3D_OLD_GBI, []) + cmd.flagList.append(settings.g_mdsft_alpha_compare) + cmd.flagList.append(settings.g_mdsft_zsrcsel) - else: - cmd = SPSetOtherMode("G_SETOTHERMODE_L", 0, 32 - is_f3d_old, []) - cmd.flagList.append(settings.g_mdsft_alpha_compare) - cmd.flagList.append(settings.g_mdsft_zsrcsel) - - if settings.set_rendermode: - flagList, blendList = getRenderModeFlagList(settings, fMaterial) - cmd.flagList.extend(flagList) - if blendList is not None: - cmd.flagList.extend( - [ - "GBL_c1(" - + blendList[0] - + ", " - + blendList[1] - + ", " - + blendList[2] - + ", " - + blendList[3] - + ")", - "GBL_c2(" - + blendList[4] - + ", " - + blendList[5] - + ", " - + blendList[6] - + ", " - + blendList[7] - + ")", - ] - ) - else: - cmd.flagList.extend(defaultRenderMode) + if settings.set_rendermode: + flagList, blendList = getRenderModeFlagList(settings, fMaterial) + cmd.flagList.extend(flagList) + if blendList is not None: + cmd.flagList.extend( + [ + "GBL_c1(" + blendList[0] + ", " + blendList[1] + ", " + blendList[2] + ", " + blendList[3] + ")", + "GBL_c2(" + blendList[4] + ", " + blendList[5] + ", " + blendList[6] + ", " + blendList[7] + ")", + ] + ) fMaterial.mat_only_DL.commands.append(cmd) if settings.g_mdsft_zsrcsel == "G_ZS_PRIM": fMaterial.mat_only_DL.commands.append(DPSetPrimDepth(z=settings.prim_depth.z, dz=settings.prim_depth.dz)) - fMaterial.revert.commands.append(DPSetPrimDepth()) def saveOtherModeLDefinitionIndividual(fMaterial, settings, defaults, defaultRenderMode): @@ -1835,11 +1773,9 @@ def getWriteMethodFromEnum(enumVal): return matWriteMethodEnumDict[enumVal] -def exportF3DtoC( - dirPath, obj, DLFormat, transformMatrix, f3dType, isHWv1, texDir, savePNG, texSeparate, name, matWriteMethod -): +def exportF3DtoC(dirPath, obj, DLFormat, transformMatrix, texDir, savePNG, texSeparate, name, matWriteMethod): inline = bpy.context.scene.exportInlineF3D - fModel = FModel(f3dType, isHWv1, name, DLFormat, matWriteMethod if not inline else GfxMatWriteMethod.WriteAll) + fModel = FModel(name, DLFormat, matWriteMethod if not inline else GfxMatWriteMethod.WriteAll) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, True, name, DLFormat, not savePNG) if inline: @@ -1919,7 +1855,7 @@ def execute(self, context): if len(allObjs) == 0: raise PluginError("No objects selected.") obj = context.selected_objects[0] - if not isinstance(obj.data, bpy.types.Mesh): + if obj.type != "MESH": raise PluginError("Object is not a mesh.") scaleValue = bpy.context.scene.blenderF3DScale @@ -1934,8 +1870,6 @@ def execute(self, context): exportPath = bpy.path.abspath(context.scene.DLExportPath) dlFormat = DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic - f3dType = context.scene.f3d_type - isHWv1 = context.scene.isHWv1 texDir = bpy.context.scene.DLTexDir savePNG = bpy.context.scene.saveTextures separateTexDef = bpy.context.scene.DLSeparateTextureDef @@ -1947,8 +1881,6 @@ def execute(self, context): obj, dlFormat, finalTransform, - f3dType, - isHWv1, texDir, savePNG, separateTexDef, diff --git a/fast64_internal/f3d/flipbook.py b/fast64_internal/f3d/flipbook.py index f2d7fa3cb..86e9f3bdb 100644 --- a/fast64_internal/f3d/flipbook.py +++ b/fast64_internal/f3d/flipbook.py @@ -234,7 +234,7 @@ def ootFlipbookRequirementMessage(layout: bpy.types.UILayout): def ootFlipbookAnimUpdate(self, armatureObj: bpy.types.Object, segment: str, index: int): for child in armatureObj.children: - if not isinstance(child.data, bpy.types.Mesh): + if child.type != "MESH": continue for material in child.data.materials: for i in range(2): @@ -252,12 +252,13 @@ def ootFlipbookAnimUpdate(self, armatureObj: bpy.types.Object, segment: str, ind # END GAME SPECIFIC CALLBACKS + # we use a handler since update functions are not called when a property is animated. @persistent def flipbookAnimHandler(dummy): if bpy.context.scene.gameEditorMode == "OOT": for obj in bpy.data.objects: - if isinstance(obj.data, bpy.types.Armature): + if obj.type == "ARMATURE": # we only want to update texture on keyframed armatures. # this somewhat mitigates the issue of two skeletons using the same flipbook material. if obj.animation_data is None or obj.animation_data.action is None: diff --git a/fast64_internal/f3d_material_converter.py b/fast64_internal/f3d_material_converter.py index 9504e9f7e..c8dead3b8 100644 --- a/fast64_internal/f3d_material_converter.py +++ b/fast64_internal/f3d_material_converter.py @@ -105,7 +105,7 @@ def set_best_draw_layer_for_materials(): objects = bpy.data.objects obj: bpy.types.Object = None for obj in objects: - if not isinstance(obj.data, bpy.types.Mesh) or len(obj.material_slots) < 1: + if obj.type != "MESH" or len(obj.material_slots) < 1: continue p: bpy.types.MeshPolygon = None @@ -118,7 +118,7 @@ def set_best_draw_layer_for_materials(): mat.f3d_mat.draw_layer.sm64 = obj.draw_layer_static if len(obj.vertex_groups) == 0: - continue # object doesn't have vertex groups + continue # object doesn't have vertex groups # get vertex group in the polygon group = get_group_from_polygon(obj, p) @@ -131,7 +131,7 @@ def set_best_draw_layer_for_materials(): finished_mats.add(mat.name) for obj in objects: - if not isinstance(obj.data, bpy.types.Mesh): + if obj.type != "MESH": continue for mat_slot in obj.material_slots: mat: bpy.types.Material = mat_slot.material @@ -264,7 +264,7 @@ def execute(self, context): if context.scene.bsdf_conv_all: convertAllBSDFtoF3D( - [obj for obj in bpy.data.objects if isinstance(obj.data, bpy.types.Mesh)], + [obj for obj in bpy.data.objects if obj.type == "MESH"], context.scene.rename_uv_maps, ) else: @@ -300,7 +300,7 @@ def execute(self, context): if context.scene.update_conv_all: upgradeF3DVersionAll( - [obj for obj in bpy.data.objects if isinstance(obj.data, bpy.types.Mesh)], + [obj for obj in bpy.data.objects if obj.type == "MESH"], bpy.data.armatures, self.version, ) diff --git a/fast64_internal/mm/data/xml/ActorList.xml b/fast64_internal/mm/data/xml/ActorList.xml new file mode 100644 index 000000000..11e286839 --- /dev/null +++ b/fast64_internal/mm/data/xml/ActorList.xml @@ -0,0 +1,928 @@ + + + + + + + + + + + + Large Orange Flame + Large Orange Flame + Large Blue Flame + Large Green Flame + Small Orange Flame + Large Orange Flame + Large Green Flame + Large Blue Flame + Large Magenta Flame + Large Pale Orange Flame + Large Pale Yellow Flame + Large Pale Green Flame + Large Pale Pink Flame + Large Pale Purple Flame + Large Pale Indigo Flame + Large Pale Blue Flame + + + + + + + + + + Golden + Golden - Appears - Clear Flag + Boss Key Chest + Golden - Falls - Switch Flag + Golden - Invisible + Wooden + Wooden - Invisible + Wooden - Clear Flag + Wooden - Falls - Switch Flag + Crash + Crash + Golden - Appears - Switch Flag + + + + + + + + + + + + + + + Regular + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + Invisible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/fast64_internal/oot/README.md b/fast64_internal/oot/README.md index 69ba2e02d..99d2fc4b3 100644 --- a/fast64_internal/oot/README.md +++ b/fast64_internal/oot/README.md @@ -1,17 +1,30 @@ # Ocarina Of Time +## Table of Contents +1. [Getting Started](#getting-started) +2. [Scene Overview](#scene-overview) +3. [Actors](#actors) +4. [Exits](#exits) +5. [Draw Config and Dynamic Material Properties](#scene-draw-configuration-and-dynamic-material-properties) +6. [Collision](#collision) +7. [Skeletons and Animations](#skeletons-and-animations) +8. [Flipbook Textures](#flipbook-textures) +9. [Custom Link Process](#custom-link-process) +10. [Custom Skeleton Mesh Process](#custom-skeleton-mesh-process) +11. [Cutscenes](#cutscenes) + ### Getting Started -1. In the 3D view properties sidebar, go to the ``Fast64`` tab, then ``Fast64 Global Settings`` and set ``Game`` to ``OOT``. -2. Set ``F3D Microcode`` to ``F3DEX2/LX2``. -3. Switch to the ``OOT`` tab. In ``OOT File Settings``, set your decomp path to the path of your [OoT Decomp](https://github.com/zeldaret/oot/) repository on disk. -4. In ``OOT Tools``, click "Add Scene" to create a basic scene. +1. In the 3D view properties sidebar (default hotkey to show this is `n` in the viewport), go to the ``Fast64`` tab, then ``Fast64 Global Settings`` and set ``Game`` to ``OOT``. +2. Switch to the ``OOT`` tab. In ``OOT File Settings``, set your decomp path to the path of your [HackerOoT (recommended)](https://github.com/HackerN64/HackerOoT) or [OoT Decomp](https://github.com/zeldaret/oot/) repository on disk. Check `Enable HackerOoT Features` if using HackerOoT. +3. In ``OOT Tools``, click `Add Scene` to create a basic scene. +4. Press `a` so that everything is selected, then click `Clear Transform`. 5. In ``OOT Scene Exporter`` you can choose the scene to replace or add. Some scenes have some hardcoded things that will cause them to break, so choose something like ``Market Entrance (Child Day) (Entra)``. - To add a custom scene choose ``Custom`` in the scene search box, then choose in which folder you want to export the scene and which name you want it to be (note that Fast64 will force the scene name to be lower-case). +- Enable ``Export as Single File`` if you want to have your scene in the same format as the other ones in decomp. 6. Make sure you selected the right scene in ``Scene Object`` then click "Export Scene" to export it. When you click ``Add Scene`` this is set automatically. -7. Compile and run the game. This was tested for commit ef56b01. +7. Compile and run the game. 8. (Optional) In the ``View`` tab you may want to increase the ``Clip End`` value. -9. Note: You can enable ``Export as Single File`` if you want to have your scene in the same format as the other ones in decomp. -10. Note: You can read [this code](https://github.com/Dragorn421/oot/tree/mod_base_for_mods) to take a glance at what you can do for quality of life for testing. +9. Note: You can read [this code](https://github.com/Dragorn421/oot/tree/mod_base_for_mods) to take a glance at what you can do for quality of life for testing. ### Scene Overview In Blender, the "empty" object type is used to define different types of OOT data, including scenes and rooms. @@ -32,9 +45,9 @@ Read the "Getting Started" section for information on scene exporting. To add an actor you need to create a new empty object in Blender, the shape doesn't matter. When the empty object is created you can set the ``Actor`` object type in the ``Object Properties`` panel. -To add actors to a scene, create a new Empty and parent it to a Room, otherwise they will not be exported in the room C code. Then in the Object Properties panel select ``Actor`` as the Object Type. Use the ``Select Actor ID`` button to choose an actor, and then set the Actor Parameter value as desired (see the list of Actor Parameters below). +To add actors to a scene, create a new Empty and parent it to a Room, otherwise they will not be exported in the room C code. Then in the Object Properties panel select ``Actor`` as the Object Type. Use the ``Select Actor ID`` button to choose an actor, and then set the Actor Parameter value as desired (see the list of Actor Parameters below). -Finally, every actors you are using needs their assets. In OoT they're called "Objects", if an actor is missing an object the code will not spawn the actor. To do this select the Room that your actor is parented to, select the "Objects" tab in its Object Properties window, and click "Add Item". +Finally, every actors you are using needs their assets. In OoT they're called "Objects", if an actor is missing an object the code will not spawn the actor. To do this select the Room that your actor is parented to, select the "Objects" tab in its Object Properties window, and click "Add Item". Then "Search Object ID" to find the actor object you need. For example, if adding a Deku Baba actor (EN_DEKUBABA) you need to add the "Dekubaba" object to the Room's object dependencies. Note that the object list must not contain more than 15 items. @@ -82,7 +95,7 @@ To import an animation, select the armature the animation belongs to then click To export an animation, select an armature and click "Export", which will export the active animation of the armature. ### Flipbook Textures -Many actors in OOT will animate textures through code using a flipbook method, like with Link's eyes/mouth. A flipbook material will use a texture reference pointing to an address formatted as 0x0?000000. You can find the flipbook texture frames in the material properties tab underneath the dynamic material section. +Many actors in OOT will animate textures through code using a flipbook method, like with Link's eyes/mouth. A flipbook material will use a texture reference pointing to an address formatted as 0x0?000000. You can find the flipbook texture frames in the material properties tab underneath the dynamic material section. ![](/images/oot_flipbook.png) On import, Fast64 will try to read the provided actors code for flipbook textures. On export, Fast64 will try to modify texture arrays used for flipbook textures. @@ -107,7 +120,7 @@ For Link, the eyes/mouth materials use flipbook textures. For Link animations yo 11. Common Issues: - Corrupted mesh: Make sure the root, upper control, and lower control bones are the only bones set to non-deform. - Incorrect waist DL: Go to src/code/z_player_lib.c and modify sPlayerWaistDLs to include your own waist DL. - + Note on Link's bone-weighting requirements in depth: Heavy modifications of Links model can cause his matrices array to shift from what many display lists in the game expect. Changing the amount of display lists Link's skeleton has can cause some references to matrices in segment 0xD to break, and those display lists must be updated to reflect your changes. @@ -124,24 +137,19 @@ Heavy modifications of Links model can cause his matrices array to shift from wh 6. In the actor header file, (in src/overlays/actors/\/), set the joint/morph table sizes to be (number of bones + 1) 7. In the actor source file, this value should also be used for the limbCount argument in SkelAnime_InitFlex(). -### Creating a Cutscene -**Creating the cutscene itself:** +### Cutscenes +For more informations about cutscenes [click here](cutscene_docs.md) -To create custom cutscenes you need to get [zcamedit, made by Sauraen](https://github.com/sauraen/zcamedit). +**Creating the cutscene itself:** -1. Start with using the ``Add Cutscene`` button from the OOT Panel. Name it ``Cutscene.YOUR_CS_NAME``, ``YOUR_CS_NAME`` being the name of your choice, it can be something like: ``Cutscene.fireTempleIntroCS``. Note that this object can't be parented to any object. -2. Select the cutscene empty object, then in the ``Object Properties`` panel click on ``Init Cutscene Empty``, then ``Create camera shot``. This will initialise the cutscene and add a basic camera shot with 4 bones. -3. Select the scene where you want to add the cutscene, and in the object properties go in the ``Cutscene`` tab then enable ``Write Cutscene``. In ``Data`` select ``Object`` and in ``Cutscene Object`` select the cutscene empty object you just created. -4. Now you need to create the camera shot. The ``Create camera shot`` button from the cutscene object's properties panel will add a basic shot that you can edit. To have a better idea of the position/angle of one point of the shot, you can change the display of the armature in the ``Object Data Properties`` panel (select the shot object first), choose ``Octahedral`` in ``Viewport Display`` then ``Display As``. -5. As mentioned in the zcamedit repository, the first and last bone of the shot won't be actual camera points, it defines the start and the end of a cutscene, this means with the basic shot you'll have only 2 actual camera points. Either duplicate (``SHIFT+D``) or add a new bone to add more camera points. -6. You can edit the position and rotation of each bone in the ``Edit Mode`` after selecting the shot object. The "tail" (less large point) of a bone is the direction, and "head" (larger point) is the origin. -7. When you're done with your cutscene, start with exporting your scene (you don't need to export it everytime). When this is done, select the cutscene object you want to export and use ``Export Cutscene`` from ``OOT Cutscene Exporter`` in the OOT panel. In the ``File`` field, you can choose the scene of your choice (note that it can export into actors too). +1. Start with using the ``Add Cutscene`` button from the OOT Panel. Name it ``Cutscene.YOUR_CS_NAME``, ``YOUR_CS_NAME`` being the name of your choice, it can be something like: ``Cutscene.fireTempleIntroCS``. Note that this object can't be parented to any object, also this will automatically create a new camera and it will also set the Blender scene's active camera to this one. +2. Select the scene where you want to add the cutscene, and in the object properties go in the ``Cutscene`` tab then enable ``Write Cutscene``. In ``Data`` select ``Object`` and in ``Cutscene Object`` select the cutscene empty object you just created. +3. Now you can create the camera shot. The ``Create Camera Shot`` button from the cutscene object's properties panel will add a basic shot with 4 bones that you can edit. To have a better idea of the position/angle of one point of the shot, you can change the display of the armature in the ``Object Data Properties`` panel (select the shot object first), choose ``Octahedral`` in ``Viewport Display`` then ``Display As``. +4. The first and last bone of the shot won't be actual camera points, it defines the start and the end of a camera shot, this means a basic shot will only have 2 actual camera points. Either duplicate (``SHIFT+D``) or add a new bone to add more camera points. Note that these points will be exported in the order you can see in the view layer. +5. You can edit the position and rotation of each bone in the ``Edit Mode`` after selecting the shot object. The "tail" (less large point) of a bone is the direction (the "look-at", or AT), and "head" (larger point) is the origin (the "eye"). +7. When you're done with your cutscene, exporting the scene will also export the camera shot and actor cue data. If you don't want to re-export the scene everytime, select the cutscene object you want to export and use ``Export Cutscene`` from ``OOT Cutscene Exporter`` in the OOT panel. In the ``File`` field, you can choose the scene of your choice (note that it can export into actors too). You can toggle the usage of decomp's names and macros with the ``Use Decomp for Export`` checkbox, you can also choose to insert the motion data in an existing cutscene (it will create a new array if it can't find it, it's based on the name of the object you selected). This is done by toggling the ``Export Motion Data Only`` checkbox. 8. Compile the game. -To get more informations about the game's cutscene system/camera system, read [zcamedit's readme](https://github.com/sauraen/zcamedit#setting-up-a-cutscene) - -Note: a "cutscene terminator" is a cutscene command that makes a scene transition. For example, this is used in the intro cutscene or in the credits. -
Armature Data Properties Panel @@ -166,8 +174,23 @@ To be able to actually watch your cutscene you need to have a way to trigger it, - ``gHyruleFieldIntroCs`` is the name of the array with the cutscene commands, as defined in ``assets/scenes/overworld/spot00_scene.c``, ``CutsceneData gHyruleFieldIntroCs[]`` 4. Compile the game again and use the entrance you chose for ``sEntranceCutsceneTable`` and your cutscene should play. +Alternatively, you can use the map select to watch your cutscene, though note that this won't make it watchable during normal gameplay: + +1. Open ``src/overlays/gamestates/ovl_select/z_select.c`` +2. Either edit or add an entry inside ``SceneSelectEntry sScenes[]``, for instance: ``{ "My Scene", MapSelect_LoadGame, ENTR_MYSCENE_0 },`` (note that the entrance used is the first of the block you need to have for the scene) +3. Compile the game, you may or may not need to run ``make clean`` first if you edited the entrance table +4. Get on the map select then scroll until you see your new entry (in the previous example is will be called "My Scene") then press R to change the header, on the vanilla map select the first cutscene header will be called ``ļ¾ƒļ¾žļ¾“00``, on HackerOoT it will be ``Cutscene 0`` then press A to start the cutscene. + Note that you can have the actual address of your cutscene if you use ``sym_info.py`` from decomp. Example with ``gHyruleFieldIntroCs``: - Command: ``./sym_info.py gHyruleFieldIntroCs`` - Result: ``Symbol gHyruleFieldIntroCs (RAM: 0x02013AA0, ROM: 0x27E9AA0, build/assets/scenes/overworld/spot00/spot00_scene.o)`` If you have a softlock in-game then you probably did something wrong when creating the cutscene. Make sure you set up the bones properly. The softlock means the game is playing a cutscene but it's probably reading wrong data. Make sure the cutscene is exported, if it's not export it again. + +If the camera preview in Blender isn't following where you have the bones or if the cutscene sort of works in-game but the positions are all wrong: + +1. Make sure your scene empty object, room empty object, and cutscene empty object are all at the Blender origin. You can usually do this with a combination of Object > Clear > Origin and Alt+G. Maybe Object > Apply > All Transforms if that doesn't work. If your room empty object is 1 meter below your scene empty object, as fast64 does by default, that offset will be applied to everything in game and then the zcamedit stuff will not be at the correct relative position. + +2. If you moved / rotated / etc. one of the camera shots / armatures in object mode, this transformation will be ignored. You can fix this by selecting the shot / armature in object mode and clicking Object > Apply > All Transforms. That will convert the transform to actual changed positions for each bone. + +If the game crashes check the transitions if you use the transition command (check both the ones from the entrance table and your cutscene script), also it will crash if you try to use the map select without having a 5th entrance (or more depending on the number of cutscenes you have) in the group for your scene. diff --git a/fast64_internal/oot/__init__.py b/fast64_internal/oot/__init__.py index d9f19d7f0..22a324464 100644 --- a/fast64_internal/oot/__init__.py +++ b/fast64_internal/oot/__init__.py @@ -37,6 +37,12 @@ from .cutscene.operators import cutscene_ops_register, cutscene_ops_unregister from .cutscene.properties import cutscene_props_register, cutscene_props_unregister from .cutscene.panels import cutscene_panels_register, cutscene_panels_unregister +from .cutscene.preview import cutscene_preview_register, cutscene_preview_unregister + +from .cutscene.motion.operators import csMotion_ops_register, csMotion_ops_unregister +from .cutscene.motion.properties import csMotion_props_register, csMotion_props_unregister +from .cutscene.motion.panels import csMotion_panels_register, csMotion_panels_unregister +from .cutscene.motion.preview import csMotion_preview_register, csMotion_preview_unregister from .skeleton.operators import skeleton_ops_register, skeleton_ops_unregister from .skeleton.properties import skeleton_props_register, skeleton_props_unregister @@ -76,18 +82,19 @@ class OOT_Properties(bpy.types.PropertyGroup): def oot_panel_register(): oot_operator_panel_register() + cutscene_panels_register() + scene_panels_register() f3d_panels_register() collision_panels_register() oot_obj_panel_register() - scene_panels_register() spline_panels_register() anim_panels_register() skeleton_panels_register() - cutscene_panels_register() def oot_panel_unregister(): oot_operator_panel_unregister() + cutscene_panels_unregister() collision_panels_unregister() oot_obj_panel_unregister() scene_panels_unregister() @@ -95,7 +102,6 @@ def oot_panel_unregister(): f3d_panels_unregister() anim_panels_unregister() skeleton_panels_unregister() - cutscene_panels_unregister() def oot_register(registerPanels): @@ -121,6 +127,12 @@ def oot_register(registerPanels): file_register() anim_props_register() + csMotion_ops_register() + csMotion_props_register() + csMotion_panels_register() + csMotion_preview_register() + cutscene_preview_register() + for cls in oot_classes: register_class(cls) @@ -154,5 +166,11 @@ def oot_unregister(unregisterPanels): file_unregister() anim_props_unregister() + cutscene_preview_unregister() + csMotion_preview_unregister() + csMotion_panels_unregister() + csMotion_props_unregister() + csMotion_ops_unregister() + if unregisterPanels: oot_panel_unregister() diff --git a/fast64_internal/oot/actor/properties.py b/fast64_internal/oot/actor/properties.py index 65a6508b1..50f0a7054 100644 --- a/fast64_internal/oot/actor/properties.py +++ b/fast64_internal/oot/actor/properties.py @@ -2,7 +2,8 @@ from bpy.utils import register_class, unregister_class from bpy.props import EnumProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, PointerProperty from ...utility import prop_split, label_split -from ..oot_constants import ootData, ootEnumSceneSetupPreset, ootEnumCamTransition +from ..oot_constants import ootData, ootEnumCamTransition +from ..oot_upgrade import upgradeActors from ..scene.properties import OOTAlternateSceneHeaderProperty from ..room.properties import OOTAlternateRoomHeaderProperty from .operators import OOT_SearchActorIDEnumOperator @@ -15,6 +16,12 @@ drawEnumWithCustom, ) +ootEnumSceneSetupPreset = [ + ("Custom", "Custom", "Custom"), + ("All Scene Setups", "All Scene Setups", "All Scene Setups"), + ("All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups"), +] + class OOTActorHeaderItemProperty(PropertyGroup): headerIndex: IntProperty(name="Scene Setup", min=4, default=4) @@ -110,6 +117,11 @@ class OOTActorProperty(PropertyGroup): rotOverrideZ: StringProperty(name="Rot Z", default="0") headerSettings: PointerProperty(type=OOTActorHeaderProperty) + @staticmethod + def upgrade_object(obj: Object): + print(f"Processing '{obj.name}'...") + upgradeActors(obj) + def draw_props(self, layout: UILayout, altRoomProp: OOTAlternateRoomHeaderProperty, objName: str): # prop_split(layout, actorProp, 'actorID', 'Actor') actorIDBox = layout.column() @@ -145,15 +157,19 @@ def draw_props(self, layout: UILayout, altRoomProp: OOTAlternateRoomHeaderProper class OOTTransitionActorProperty(PropertyGroup): - roomIndex: IntProperty(min=0) + fromRoom: PointerProperty(type=Object, poll=lambda self, object: self.isRoomEmptyObject(object)) + toRoom: PointerProperty(type=Object, poll=lambda self, object: self.isRoomEmptyObject(object)) cameraTransitionFront: EnumProperty(items=ootEnumCamTransition, default="0x00") cameraTransitionFrontCustom: StringProperty(default="0x00") cameraTransitionBack: EnumProperty(items=ootEnumCamTransition, default="0x00") cameraTransitionBackCustom: StringProperty(default="0x00") - dontTransition: BoolProperty(default=False) + isRoomTransition: BoolProperty(name="Is Room Transition", default=True) actor: PointerProperty(type=OOTActorProperty) + def isRoomEmptyObject(self, obj: Object): + return obj.type == "EMPTY" and obj.ootEmptyType == "Room" + def draw_props( self, layout: UILayout, altSceneProp: OOTAlternateSceneHeaderProperty, roomObj: Object, objName: str ): @@ -175,10 +191,12 @@ def draw_props( if roomObj is None: actorIDBox.label(text="This must be part of a Room empty's hierarchy.", icon="OUTLINER") else: - actorIDBox.prop(self, "dontTransition") - if not self.dontTransition: - label_split(actorIDBox, "Room To Transition From", str(roomObj.ootRoomHeader.roomIndex)) - prop_split(actorIDBox, self, "roomIndex", "Room To Transition To") + actorIDBox.prop(self, "isRoomTransition") + if self.isRoomTransition: + prop_split(actorIDBox, self, "fromRoom", "Room To Transition From") + prop_split(actorIDBox, self, "toRoom", "Room To Transition To") + if self.fromRoom == self.toRoom: + actorIDBox.label(text="Warning: You selected the same room!", icon="ERROR") actorIDBox.label(text='Y+ side of door faces toward the "from" room.', icon="ORIENTATION_NORMAL") drawEnumWithCustom(actorIDBox, self, "cameraTransitionFront", "Camera Transition Front", "") drawEnumWithCustom(actorIDBox, self, "cameraTransitionBack", "Camera Transition Back", "") @@ -188,23 +206,29 @@ def draw_props( class OOTEntranceProperty(PropertyGroup): - # This is also used in entrance list, and roomIndex is obtained from the room this empty is parented to. + # This is also used in entrance list. spawnIndex: IntProperty(min=0) customActor: BoolProperty(name="Use Custom Actor") actor: PointerProperty(type=OOTActorProperty) + tiedRoom: PointerProperty( + type=Object, + poll=lambda self, object: self.isRoomEmptyObject(object), + description="Used to set the room index", + ) + + def isRoomEmptyObject(self, obj: Object): + return obj.type == "EMPTY" and obj.ootEmptyType == "Room" + def draw_props(self, layout: UILayout, obj: Object, altSceneProp: OOTAlternateSceneHeaderProperty, objName: str): box = layout.column() # box.box().label(text = "Properties") roomObj = getRoomObj(obj) - if roomObj is not None: - split = box.split(factor=0.5) - split.label(text="Room Index") - split.label(text=str(roomObj.ootRoomHeader.roomIndex)) - else: + if roomObj is None: box.label(text="This must be part of a Room empty's hierarchy.", icon="OUTLINER") entranceProp = obj.ootEntranceProperty + prop_split(box, entranceProp, "tiedRoom", "Room") prop_split(box, entranceProp, "spawnIndex", "Spawn Index") prop_split(box, entranceProp.actor, "actorParam", "Actor Param") box.prop(entranceProp, "customActor") diff --git a/fast64_internal/oot/collision/operators.py b/fast64_internal/oot/collision/operators.py index cb8553c70..a640b9a3f 100644 --- a/fast64_internal/oot/collision/operators.py +++ b/fast64_internal/oot/collision/operators.py @@ -88,7 +88,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("No object selected.") obj = context.active_object - if type(obj.data) is not Mesh: + if obj.type != "MESH": raise PluginError("No mesh object selected.") finalTransform = Matrix.Scale(getOOTScale(obj.ootActorScale), 4) diff --git a/fast64_internal/oot/cutscene/classes.py b/fast64_internal/oot/cutscene/classes.py new file mode 100644 index 000000000..bdfc5ebb3 --- /dev/null +++ b/fast64_internal/oot/cutscene/classes.py @@ -0,0 +1,597 @@ +import bpy + +from dataclasses import dataclass, field +from bpy.types import Object +from typing import Optional +from ..oot_constants import ootData +from .motion.utility import getBlenderPosition, getBlenderRotation, getRotation, getInteger + + +# NOTE: ``paramNumber`` is the expected number of parameters inside the parsed commands, +# this account for the unused parameters. Every classes are based on the commands arguments from ``z64cutscene_commands.h`` + + +@dataclass +class CutsceneCmdBase: + """This class contains common Cutscene data""" + + params: list[str] + + startFrame: Optional[int] = None + endFrame: Optional[int] = None + + def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): + enum = ootData.enumData.enumByKey[enumKey] + item = enum.itemById.get(self.params[index]) + if item is None: + setting = getInteger(self.params[index]) + if isSeqLegacy: + setting -= 1 + item = enum.itemByIndex.get(setting) + return item.key if item is not None else self.params[index] + + +@dataclass +class CutsceneCmdCamPoint(CutsceneCmdBase): + """This class contains a single Camera Point command data""" + + continueFlag: Optional[str] = None + camRoll: Optional[int] = None + frame: Optional[int] = None + viewAngle: Optional[float] = None + pos: list[int] = field(default_factory=list) + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.continueFlag = self.params[0] + self.camRoll = getInteger(self.params[1]) + self.frame = getInteger(self.params[2]) + self.viewAngle = float(self.params[3][:-1]) + self.pos = [getInteger(self.params[4]), getInteger(self.params[5]), getInteger(self.params[6])] + + +@dataclass +class CutsceneCmdActorCue(CutsceneCmdBase): + """This class contains a single Actor Cue command data""" + + actionID: Optional[int] = None + rot: list[str] = field(default_factory=list) + startPos: list[int] = field(default_factory=list) + endPos: list[int] = field(default_factory=list) + paramNumber: int = 15 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.actionID = getInteger(self.params[0]) + self.rot = [getRotation(self.params[3]), getRotation(self.params[4]), getRotation(self.params[5])] + self.startPos = [getInteger(self.params[6]), getInteger(self.params[7]), getInteger(self.params[8])] + self.endPos = [getInteger(self.params[9]), getInteger(self.params[10]), getInteger(self.params[11])] + + +@dataclass +class CutsceneCmdActorCueList(CutsceneCmdBase): + """This class contains the Actor Cue List command data""" + + isPlayer: bool = False + commandType: Optional[str] = None + entryTotal: Optional[int] = None + entries: list[CutsceneCmdActorCue] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "actorCueList" + + def __post_init__(self): + if self.params is not None: + if self.isPlayer: + self.commandType = "Player" + self.entryTotal = getInteger(self.params[0]) + else: + self.commandType = self.params[0] + if self.commandType.startswith("0x"): + # make it a 4 digit hex + self.commandType = self.commandType.removeprefix("0x") + self.commandType = "0x" + "0" * (4 - len(self.commandType)) + self.commandType + else: + self.commandType = ootData.enumData.enumByKey["csCmd"].itemById[self.commandType].key + self.entryTotal = getInteger(self.params[1].strip()) + + +@dataclass +class CutsceneCmdCamEyeSpline(CutsceneCmdBase): + """This class contains the Camera Eye Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamATSpline(CutsceneCmdBase): + """This class contains the Camera AT (look-at) Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamEyeSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera Eye Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera AT Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamEye(CutsceneCmdBase): + """This class contains a single Camera Eye point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamAT(CutsceneCmdBase): + """This class contains a single Camera AT point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdMisc(CutsceneCmdBase): + """This class contains a single misc command entry""" + + type: Optional[str] = None # see ``CutsceneMiscType`` in decomp + paramNumber: int = 14 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csMiscType", 0) + + +@dataclass +class CutsceneCmdMiscList(CutsceneCmdBase): + """This class contains Misc command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdMisc] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "miscList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdTransition(CutsceneCmdBase): + """This class contains Transition command data""" + + type: Optional[str] = None + paramNumber: int = 3 + listName: str = "transitionList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csTransitionType", 0) + + +@dataclass +class CutsceneCmdText(CutsceneCmdBase): + """This class contains Text command data""" + + textId: Optional[int] = None + type: Optional[str] = None + altTextId1: Optional[int] = None + altTextId2: Optional[int] = None + paramNumber: int = 6 + id: str = "Text" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.textId = getInteger(self.params[0]) + self.type = self.getEnumValue("csTextType", 3) + self.altTextId1 = (getInteger(self.params[4]),) + self.altTextId2 = (getInteger(self.params[5]),) + + +@dataclass +class CutsceneCmdTextNone(CutsceneCmdBase): + """This class contains Text None command data""" + + paramNumber: int = 2 + id: str = "None" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdTextOcarinaAction(CutsceneCmdBase): + """This class contains Text Ocarina Action command data""" + + ocarinaActionId: Optional[str] = None + messageId: Optional[int] = None + paramNumber: int = 4 + id: str = "OcarinaAction" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.ocarinaActionId = self.getEnumValue("ocarinaSongActionId", 0) + self.messageId = getInteger(self.params[3]) + + +@dataclass +class CutsceneCmdTextList(CutsceneCmdBase): + """This class contains Text List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdText | CutsceneCmdTextNone | CutsceneCmdTextOcarinaAction] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "textList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdLightSetting(CutsceneCmdBase): + """This class contains Light Setting command data""" + + isLegacy: Optional[bool] = None + lightSetting: Optional[int] = None + paramNumber: int = 11 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.lightSetting = getInteger(self.params[0]) + if self.isLegacy: + self.lightSetting -= 1 + + +@dataclass +class CutsceneCmdLightSettingList(CutsceneCmdBase): + """This class contains Light Setting List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdLightSetting] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "lightSettingsList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdTime(CutsceneCmdBase): + """This class contains Time Ocarina Action command data""" + + hour: Optional[int] = None + minute: Optional[int] = None + paramNumber: int = 5 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.hour = getInteger(self.params[3]) + self.minute = getInteger(self.params[4]) + + +@dataclass +class CutsceneCmdTimeList(CutsceneCmdBase): + """This class contains Time List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdTime] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "timeList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdStartStopSeq(CutsceneCmdBase): + """This class contains Start/Stop Seq command data""" + + isLegacy: Optional[bool] = None + seqId: Optional[str] = None + paramNumber: int = 11 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqId = self.getEnumValue("seqId", 0, self.isLegacy) + + +@dataclass +class CutsceneCmdStartStopSeqList(CutsceneCmdBase): + """This class contains Start/Stop Seq List command data""" + + entryTotal: Optional[int] = None + type: Optional[str] = None + entries: list[CutsceneCmdStartStopSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "seqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdFadeSeq(CutsceneCmdBase): + """This class contains Fade Seq command data""" + + seqPlayer: Optional[str] = None + paramNumber: int = 11 + enumKey: str = "csFadeOutSeqPlayer" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqPlayer = self.getEnumValue("csFadeOutSeqPlayer", 0) + + +@dataclass +class CutsceneCmdFadeSeqList(CutsceneCmdBase): + """This class contains Fade Seq List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdFadeSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "fadeSeqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdRumbleController(CutsceneCmdBase): + """This class contains Rumble Controller command data""" + + sourceStrength: Optional[int] = None + duration: Optional[int] = None + decreaseRate: Optional[int] = None + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.sourceStrength = getInteger(self.params[3]) + self.duration = getInteger(self.params[4]) + self.decreaseRate = getInteger(self.params[5]) + + +@dataclass +class CutsceneCmdRumbleControllerList(CutsceneCmdBase): + """This class contains Rumble Controller List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdRumbleController] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "rumbleList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdDestination(CutsceneCmdBase): + """This class contains Destination command data""" + + id: Optional[str] = None + paramNumber: int = 3 + listName: str = "destination" + + def __post_init__(self): + if self.params is not None: + self.id = self.getEnumValue("csDestination", 0) + self.startFrame = getInteger(self.params[1]) + + +@dataclass +class Cutscene: + """This class contains a Cutscene's data, including every commands' data""" + + name: str + totalEntries: int + frameCount: int + paramNumber: int = 2 + + destination: CutsceneCmdDestination = None + actorCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + playerCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(default_factory=list) + camATSplineList: list[CutsceneCmdCamATSpline] = field(default_factory=list) + camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(default_factory=list) + camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(default_factory=list) + camEyeList: list[CutsceneCmdCamEye] = field(default_factory=list) + camATList: list[CutsceneCmdCamAT] = field(default_factory=list) + textList: list[CutsceneCmdTextList] = field(default_factory=list) + miscList: list[CutsceneCmdMiscList] = field(default_factory=list) + rumbleList: list[CutsceneCmdRumbleControllerList] = field(default_factory=list) + transitionList: list[CutsceneCmdTransition] = field(default_factory=list) + lightSettingsList: list[CutsceneCmdLightSettingList] = field(default_factory=list) + timeList: list[CutsceneCmdTimeList] = field(default_factory=list) + seqList: list[CutsceneCmdStartStopSeqList] = field(default_factory=list) + fadeSeqList: list[CutsceneCmdFadeSeqList] = field(default_factory=list) + + +class CutsceneObjectFactory: + """This class contains functions to create new Blender objects""" + + def getNewObject(self, name: str, data, selectObject: bool, parentObj: Object) -> Object: + newObj = bpy.data.objects.new(name=name, object_data=data) + bpy.context.view_layer.active_layer_collection.collection.objects.link(newObj) + if selectObject: + newObj.select_set(True) + bpy.context.view_layer.objects.active = newObj + newObj.parent = parentObj + newObj.location = [0.0, 0.0, 0.0] + newObj.rotation_euler = [0.0, 0.0, 0.0] + newObj.scale = [1.0, 1.0, 1.0] + return newObj + + def getNewEmptyObject(self, name: str, selectObject: bool, parentObj: Object): + return self.getNewObject(name, None, selectObject, parentObj) + + def getNewArmatureObject(self, name: str, selectObject: bool, parentObj: Object): + newArmatureData = bpy.data.armatures.new(name) + newArmatureData.display_type = "STICK" + newArmatureData.show_names = True + newArmatureObject = self.getNewObject(name, newArmatureData, selectObject, parentObj) + return newArmatureObject + + def getNewCutsceneObject(self, name: str, frameCount: int, parentObj: Object): + newCSObj = self.getNewEmptyObject(name, True, parentObj) + newCSObj.ootEmptyType = "Cutscene" + newCSObj.ootCutsceneProperty.csEndFrame = frameCount + return newCSObj + + def getNewActorCueListObject(self, name: str, commandType: str, parentObj: Object): + newActorCueListObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueListObj.ootEmptyType = f"CS {'Player' if 'Player' in name else 'Actor'} Cue List" + cmdEnum = ootData.enumData.enumByKey["csCmd"] + + if commandType == "Player": + commandType = "player_cue" + + index = cmdEnum.itemByKey[commandType].index if commandType in cmdEnum.itemByKey else int(commandType, base=16) + item = cmdEnum.itemByIndex.get(index) + + if item is not None: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = item.key + else: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = "Custom" + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandTypeCustom = commandType + + return newActorCueListObj + + def getNewActorCueObject( + self, + name: str, + startFrame: int, + actionID: int | str, + location: list[int], + rot: list[str], + parentObj: Object, + ): + isDummy = "(D)" in name + isPlayer = not isDummy and not "Actor" in name + + newActorCueObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueObj.location = getBlenderPosition(location, bpy.context.scene.ootBlenderScale) + newActorCueObj.empty_display_type = "ARROWS" + newActorCueObj.rotation_mode = "XZY" + newActorCueObj.rotation_euler = getBlenderRotation(rot) + emptyType = "Dummy" if isDummy else "Player" if isPlayer else "Actor" + newActorCueObj.ootEmptyType = f"CS {emptyType} Cue" + newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame + + item = None + if isPlayer: + playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] + if isinstance(actionID, int): + item = playerEnum.itemByIndex.get(actionID) + else: + item = playerEnum.itemByKey.get(actionID) + + if item is not None: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = item.key + elif not isDummy: + if isPlayer: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = "Custom" + + if isinstance(actionID, int): + cueActionID = f"0x{actionID:04X}" + else: + cueActionID = actionID + + newActorCueObj.ootCSMotionProperty.actorCueProp.cueActionID = cueActionID + + return newActorCueObj + + def getNewCameraObject( + self, name: str, displaySize: float, clipStart: float, clipEnd: float, alpha: float, parentObj: Object + ): + newCamera = bpy.data.cameras.new(name) + newCameraObj = self.getNewObject(name, newCamera, False, parentObj) + newCameraObj.data.display_size = displaySize + newCameraObj.data.clip_start = clipStart + newCameraObj.data.clip_end = clipEnd + newCameraObj.data.passepartout_alpha = alpha + return newCameraObj + + def getNewActorCuePreviewObject(self, name: str, selectObject, parentObj: Object): + newPreviewObj = self.getNewEmptyObject(name, selectObject, parentObj) + newPreviewObj.ootEmptyType = f"CS {'Actor' if 'Actor' in name else 'Player'} Cue Preview" + return newPreviewObj diff --git a/fast64_internal/oot/cutscene/constants.py b/fast64_internal/oot/cutscene/constants.py index 81a9c2412..b3525246a 100644 --- a/fast64_internal/oot/cutscene/constants.py +++ b/fast64_internal/oot/cutscene/constants.py @@ -1,83 +1,252 @@ -ootEnumCSTextboxTypeEntryC = { - "Text": "CS_TEXT_DISPLAY_TEXTBOX", - "None": "CS_TEXT_NONE", - "LearnSong": "CS_TEXT_LEARN_SONG", -} +from ..oot_constants import ootData +from .classes import ( + CutsceneCmdActorCueList, + CutsceneCmdActorCue, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, + CutsceneCmdCamPoint, + CutsceneCmdMisc, + CutsceneCmdMiscList, + CutsceneCmdTransition, + CutsceneCmdText, + CutsceneCmdTextNone, + CutsceneCmdTextOcarinaAction, + CutsceneCmdTextList, + CutsceneCmdLightSetting, + CutsceneCmdLightSettingList, + CutsceneCmdTime, + CutsceneCmdTimeList, + CutsceneCmdStartStopSeq, + CutsceneCmdStartStopSeqList, + CutsceneCmdFadeSeq, + CutsceneCmdFadeSeqList, + CutsceneCmdRumbleController, + CutsceneCmdRumbleControllerList, + CutsceneCmdDestination, +) -ootEnumCSListTypeListC = { - "Textbox": "CS_TEXT_LIST", - "FX": "CS_SCENE_TRANS_FX", - "Lighting": "CS_LIGHTING_LIST", - "Time": "CS_TIME_LIST", - "PlayBGM": "CS_PLAY_BGM_LIST", - "StopBGM": "CS_STOP_BGM_LIST", - "FadeBGM": "CS_FADE_BGM_LIST", - "Misc": "CS_MISC_LIST", - "0x09": "CS_CMD_09_LIST", - "Unk": "CS_UNK_DATA_LIST", -} -ootEnumCSListTypeEntryC = { - "Textbox": None, # special case - "FX": None, # no list entries - "Lighting": "CS_LIGHTING", - "Time": "CS_TIME", - "PlayBGM": "CS_PLAY_BGM", - "StopBGM": "CS_STOP_BGM", - "FadeBGM": "CS_FADE_BGM", - "Misc": "CS_MISC", - "0x09": "CS_CMD_09", - "Unk": "CS_UNK_DATA", +ootEnumCSListTypeListC = { + "TextList": "CS_TEXT_LIST", + "Transition": "CS_TRANSITION", + "LightSettingsList": "CS_LIGHT_SETTING_LIST", + "TimeList": "CS_TIME_LIST", + "StartSeqList": "CS_START_SEQ_LIST", + "StopSeqList": "CS_STOP_SEQ_LIST", + "FadeOutSeqList": "CS_FADE_OUT_SEQ_LIST", + "MiscList": "CS_MISC_LIST", + "RumbleList": "CS_RUMBLE_CONTROLLER_LIST", } ootEnumCSWriteType = [ - ("Custom", "Custom", "Provide the name of a cutscene header variable"), - ("Embedded", "Embedded", "Cutscene data is within scene header (deprecated)"), - ("Object", "Object", "Reference to Blender object representing cutscene"), + ("Custom", "Custom", "Provide the name of a cutscene header variable", "", 0), + ("Object", "Object", "Reference to Blender object representing cutscene", "", 2), ] +# order here sets order on the UI ootEnumCSListType = [ - ("Textbox", "Textbox", "Textbox"), - ("FX", "Scene Trans FX", "Scene Trans FX"), - ("Lighting", "Lighting", "Lighting"), - ("Time", "Time", "Time"), - ("PlayBGM", "Play BGM", "Play BGM"), - ("StopBGM", "Stop BGM", "Stop BGM"), - ("FadeBGM", "Fade BGM", "Fade BGM"), - ("Misc", "Misc", "Misc"), - ("0x09", "Cmd 09", "Cmd 09"), - ("Unk", "Unknown Data", "Unknown Data"), + ("TextList", "Text List", "Textbox", "ALIGN_BOTTOM", 0), + ("MiscList", "Misc List", "Misc", "OPTIONS", 7), + ("RumbleList", "Rumble List", "Rumble Controller", "OUTLINER_OB_FORCE_FIELD", 8), + ("Transition", "Transition", "Transition", "COLORSET_10_VEC", 1), + ("LightSettingsList", "Light Settings List", "Lighting", "LIGHT_SUN", 2), + ("TimeList", "Time List", "Time", "TIME", 3), + ("StartSeqList", "Start Seq List", "Play BGM", "PLAY", 4), + ("StopSeqList", "Stop Seq List", "Stop BGM", "SNAP_FACE", 5), + ("FadeOutSeqList", "Fade-Out Seq List", "Fade BGM", "IPO_EASE_IN_OUT", 6), ] -ootEnumCSListTypeIcons = [ - "ALIGN_BOTTOM", - "COLORSET_10_VEC", - "LIGHT_SUN", - "TIME", - "PLAY", - "SNAP_FACE", - "IPO_EASE_IN_OUT", - "OPTIONS", - "EVENT_F9", - "QUESTION", -] +csListTypeToIcon = { + "TextList": "ALIGN_BOTTOM", + "Transition": "COLORSET_10_VEC", + "LightSettingsList": "LIGHT_SUN", + "TimeList": "TIME", + "StartSeqList": "PLAY", + "StopSeqList": "SNAP_FACE", + "FadeOutSeqList": "IPO_EASE_IN_OUT", + "MiscList": "OPTIONS", + "RumbleList": "OUTLINER_OB_FORCE_FIELD", +} -ootEnumCSTextboxType = [("Text", "Text", "Text"), ("None", "None", "None"), ("LearnSong", "Learn Song", "Learn Song")] +ootEnumCSTextboxType = [ + ("Text", "Text", "Text"), + ("None", "None", "None"), + ("OcarinaAction", "Ocarina Action", "Learn Song"), +] ootEnumCSTextboxTypeIcons = ["FILE_TEXT", "HIDE_ON", "FILE_SOUND"] -ootEnumCSTransitionType = [ - ("1", "To White +", "Also plays whiteout sound for certain scenes/entrances"), - ("2", "To Blue", "To Blue"), - ("3", "From Red", "From Red"), - ("4", "From Green", "From Green"), - ("5", "From White", "From White"), - ("6", "From Blue", "From Blue"), - ("7", "To Red", "To Red"), - ("8", "To Green", "To Green"), - ("9", "Set Unk", "gSaveContext.unk_1410 = 1, works with scene xn 11/17"), - ("10", "From Black", "From Black"), - ("11", "To Black", "To Black"), - ("12", "To Dim Unk", "Fade gSaveContext.unk_1410 255>100, works with scene xn 11/17"), - ("13", "From Dim", "Alpha 100>255"), +ootCSSubPropToName = { + "startFrame": "Start Frame", + "endFrame": "End Frame", + # TextBox + "textID": "Text ID", + "ocarinaAction": "Ocarina Action", + "csTextType": "Text Type", + "topOptionTextID": "Text ID for Top Option", + "bottomOptionTextID": "Text ID for Bottom Option", + "ocarinaMessageId": "Ocarina Message ID", + # Lighting + "lightSettingsIndex": "Light Settings Index", + # Time + "hour": "Hour", + "minute": "Minute", + # Seq + "csSeqID": "Seq ID", + "csSeqPlayer": "Seq Player Type", + # Misc + "csMiscType": "Misc Type", + # Rumble + "rumbleSourceStrength": "Source Strength", + "rumbleDuration": "Duration", + "rumbleDecreaseRate": "Decrease Rate", + # Lists + "TextList": "Text List", + "TimeList": "Time List", + "FadeOutSeqList": "Fade-Out Seq List", + "Transition": "Transition", + "StartSeqList": "Start Seq List", + "MiscList": "Misc List", + "LightSettingsList": "Light Settings List", + "StopSeqList": "Stop Seq List", + "RumbleList": "Rumble List", +} + +ootEnumCSMotionCamMode = [ + ("splineEyeOrAT", "Eye/AT Spline", "Eye/AT Spline"), + ("splineEyeOrATRelPlayer", "Spline Rel. Player", "Relative to Player's location/yaw"), + ("eyeOrAT", "Eye/AT Point", "Single Eye/AT point (not recommended)"), +] + +ootEnumCSActorCueListCommandType = [ + item for item in ootData.enumData.ootEnumCsCmd if "actor_cue" in item[0] or "player_cue" in item[0] ] +ootEnumCSActorCueListCommandType.sort() +ootEnumCSActorCueListCommandType.insert(0, ("Custom", "Custom", "Custom")) + +ootCSLegacyToNewCmdNames = { + "CS_CAM_POS_LIST": "CS_CAM_EYE_SPLINE", + "CS_CAM_FOCUS_POINT_LIST": "CS_CAM_AT_SPLINE", + "CS_CAM_POS_PLAYER_LIST": "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", + "CS_CAM_FOCUS_POINT_PLAYER_LIST": "CS_CAM_AT_SPLINE_REL_TO_PLAYER", + "CS_NPC_ACTION_LIST": "CS_ACTOR_CUE_LIST", + "CS_PLAYER_ACTION_LIST": "CS_PLAYER_CUE_LIST", + "CS_CMD_07": "CS_CAM_EYE", + "CS_CMD_08": "CS_CAM_AT", + "CS_CAM_POS": "CS_CAM_POINT", + "CS_CAM_FOCUS_POINT": "CS_CAM_POINT", + "CS_CAM_POS_PLAYER": "CS_CAM_POINT", + "CS_CAM_FOCUS_POINT_PLAYER": "CS_CAM_POINT", + "CS_NPC_ACTION": "CS_ACTOR_CUE", + "CS_PLAYER_ACTION": "CS_PLAYER_CUE", + "CS_CMD_09_LIST": "CS_RUMBLE_CONTROLLER_LIST", + "CS_CMD_09": "CS_RUMBLE_CONTROLLER", + "CS_TEXT_DISPLAY_TEXTBOX": "CS_TEXT", + "CS_TEXT_LEARN_SONG": "CS_TEXT_OCARINA_ACTION", + "CS_SCENE_TRANS_FX": "CS_TRANSITION", + "CS_FADE_BGM_LIST": "CS_FADE_OUT_SEQ_LIST", + "CS_FADE_BGM": "CS_FADE_OUT_SEQ", + "CS_TERMINATOR": "CS_DESTINATION", + "CS_LIGHTING_LIST": "CS_LIGHT_SETTING_LIST", + "CS_LIGHTING": "L_CS_LIGHT_SETTING", + "CS_PLAY_BGM_LIST": "CS_START_SEQ_LIST", + "CS_PLAY_BGM": "L_CS_START_SEQ", + "CS_STOP_BGM_LIST": "CS_STOP_SEQ_LIST", + "CS_STOP_BGM": "L_CS_STOP_SEQ", +} + +ootCSListCommands = [ + "CS_ACTOR_CUE_LIST", + "CS_PLAYER_CUE_LIST", + "CS_CAM_EYE_SPLINE", + "CS_CAM_AT_SPLINE", + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", + "CS_CAM_EYE", + "CS_CAM_AT", + "CS_MISC_LIST", + "CS_LIGHT_SETTING_LIST", + "CS_RUMBLE_CONTROLLER_LIST", + "CS_TEXT_LIST", + "CS_START_SEQ_LIST", + "CS_STOP_SEQ_LIST", + "CS_FADE_OUT_SEQ_LIST", + "CS_TIME_LIST", + "CS_UNK_DATA_LIST", + "CS_PLAY_BGM_LIST", + "CS_STOP_BGM_LIST", + "CS_LIGHTING_LIST", +] + +ootCSListEntryCommands = [ + "CS_ACTOR_CUE", + "CS_PLAYER_CUE", + "CS_CAM_POINT", + "CS_MISC", + "CS_LIGHT_SETTING", + "CS_RUMBLE_CONTROLLER", + "CS_TEXT", + "CS_TEXT_NONE", + "CS_TEXT_OCARINA_ACTION", + "CS_START_SEQ", + "CS_STOP_SEQ", + "CS_FADE_OUT_SEQ", + "CS_TIME", + "CS_UNK_DATA", + "CS_PLAY_BGM", + "CS_STOP_BGM", + "CS_LIGHTING", + # some old commands need to remove 1 to the first argument to stay accurate + "L_CS_LIGHT_SETTING", + "L_CS_START_SEQ", + "L_CS_STOP_SEQ", +] + +ootCSSingleCommands = [ + "CS_BEGIN_CUTSCENE", + "CS_END", + "CS_TRANSITION", + "CS_DESTINATION", +] + +ootCSListAndSingleCommands = ootCSSingleCommands + ootCSListCommands +ootCSListAndSingleCommands.remove("CS_BEGIN_CUTSCENE") +ootCutsceneCommandsC = ootCSSingleCommands + ootCSListCommands + ootCSListEntryCommands + +cmdToClass = { + "CS_CAM_POINT": CutsceneCmdCamPoint, + "CS_MISC": CutsceneCmdMisc, + "CS_LIGHT_SETTING": CutsceneCmdLightSetting, + "CS_TIME": CutsceneCmdTime, + "CS_FADE_OUT_SEQ": CutsceneCmdFadeSeq, + "CS_RUMBLE_CONTROLLER": CutsceneCmdRumbleController, + "CS_TEXT": CutsceneCmdText, + "CS_TEXT_NONE": CutsceneCmdTextNone, + "CS_TEXT_OCARINA_ACTION": CutsceneCmdTextOcarinaAction, + "CS_START_SEQ": CutsceneCmdStartStopSeq, + "CS_STOP_SEQ": CutsceneCmdStartStopSeq, + "CS_ACTOR_CUE": CutsceneCmdActorCue, + "CS_PLAYER_CUE": CutsceneCmdActorCue, + "CS_CAM_EYE_SPLINE": CutsceneCmdCamEyeSpline, + "CS_CAM_AT_SPLINE": CutsceneCmdCamATSpline, + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER": CutsceneCmdCamEyeSplineRelToPlayer, + "CS_CAM_AT_SPLINE_REL_TO_PLAYER": CutsceneCmdCamATSplineRelToPlayer, + "CS_CAM_EYE": CutsceneCmdCamEye, + "CS_CAM_AT": CutsceneCmdCamAT, + "CS_MISC_LIST": CutsceneCmdMiscList, + "CS_TRANSITION": CutsceneCmdTransition, + "CS_TEXT_LIST": CutsceneCmdTextList, + "CS_LIGHT_SETTING_LIST": CutsceneCmdLightSettingList, + "CS_TIME_LIST": CutsceneCmdTimeList, + "CS_FADE_OUT_SEQ_LIST": CutsceneCmdFadeSeqList, + "CS_RUMBLE_CONTROLLER_LIST": CutsceneCmdRumbleControllerList, + "CS_START_SEQ_LIST": CutsceneCmdStartStopSeqList, + "CS_STOP_SEQ_LIST": CutsceneCmdStartStopSeqList, + "CS_ACTOR_CUE_LIST": CutsceneCmdActorCueList, + "CS_PLAYER_CUE_LIST": CutsceneCmdActorCueList, + "CS_DESTINATION": CutsceneCmdDestination, +} diff --git a/fast64_internal/oot/cutscene/exporter/__init__.py b/fast64_internal/oot/cutscene/exporter/__init__.py index 39486d43c..6a6aed48e 100644 --- a/fast64_internal/oot/cutscene/exporter/__init__.py +++ b/fast64_internal/oot/cutscene/exporter/__init__.py @@ -1,13 +1 @@ -from .functions import convertCutsceneObject, readCutsceneData - -from .classes import ( - OOTCSList, - OOTCSTextbox, - OOTCSLighting, - OOTCSTime, - OOTCSBGM, - OOTCSMisc, - OOTCS0x09, - OOTCSUnk, - OOTCutscene, -) +from .functions import getNewCutsceneExport diff --git a/fast64_internal/oot/cutscene/exporter/classes.py b/fast64_internal/oot/cutscene/exporter/classes.py index d5e637713..c038de11b 100644 --- a/fast64_internal/oot/cutscene/exporter/classes.py +++ b/fast64_internal/oot/cutscene/exporter/classes.py @@ -1,83 +1,458 @@ -class OOTCSTextbox: - def __init__(self): - self.textboxType = None - self.messageId = "0x0000" - self.ocarinaSongAction = "0x0000" - self.startFrame = 0 - self.endFrame = 1 - self.type = "0x0000" - self.topOptionBranch = "0x0000" - self.bottomOptionBranch = "0x0000" - self.ocarinaMessageId = "0x0000" - - -class OOTCSLighting: - def __init__(self): - self.index = 1 - self.startFrame = 0 - - -class OOTCSTime: - def __init__(self): - self.startFrame = 0 - self.hour = 23 - self.minute = 59 - - -class OOTCSBGM: - def __init__(self): - self.value = "0x0000" - self.startFrame = 0 - self.endFrame = 1 - - -class OOTCSMisc: - def __init__(self): - self.operation = 1 - self.startFrame = 0 - self.endFrame = 1 - - -class OOTCS0x09: - def __init__(self): - self.startFrame = 0 - self.unk2 = "0x00" - self.unk3 = "0x00" - self.unk4 = "0x00" - - -class OOTCSUnk: - def __unk__(self): - self.unk1 = "0x00000000" - self.unk2 = "0x00000000" - self.unk3 = "0x00000000" - self.unk4 = "0x00000000" - self.unk5 = "0x00000000" - self.unk6 = "0x00000000" - self.unk7 = "0x00000000" - self.unk8 = "0x00000000" - self.unk9 = "0x00000000" - self.unk10 = "0x00000000" - self.unk11 = "0x00000000" - self.unk12 = "0x00000000" - - -class OOTCSList: - def __init__(self): - self.listType = None - self.entries = [] - self.unkType = "0x0001" - self.fxType = "1" - self.fxStartFrame = 0 - self.fxEndFrame = 0 - - -class OOTCutscene: - def __init__(self): - self.name = "" - self.csEndFrame = 100 - self.csWriteTerminator = False - self.csTermIdx = 0 - self.csTermStart = 99 - self.csTermEnd = 100 - self.csLists = [] +import math +import bpy + +from dataclasses import dataclass +from typing import TYPE_CHECKING +from bpy.types import Object +from ....utility import PluginError, indent +from ...oot_constants import ootData +from ..constants import ootEnumCSListTypeListC + +if TYPE_CHECKING: + from ..properties import OOTCutsceneProperty, OOTCSTextProperty + +from ..classes import ( + CutsceneCmdTransition, + CutsceneCmdRumbleController, + CutsceneCmdMisc, + CutsceneCmdTime, + CutsceneCmdLightSetting, + CutsceneCmdText, + CutsceneCmdTextNone, + CutsceneCmdTextOcarinaAction, + CutsceneCmdActorCueList, + CutsceneCmdActorCue, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, + CutsceneCmdCamPoint, +) + + +class CutsceneCmdToC: + """This class contains functions to create the cutscene commands""" + + def getEnumValue(self, enumKey: str, owner, propName: str): + item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) + return item.id if item is not None else getattr(owner, f"{propName}Custom") + + def getGenericListCmd(self, cmdName: str, entryTotal: int): + return indent * 2 + f"{cmdName}({entryTotal}),\n" + + def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): + return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" + + def getTransitionCmd(self, transition: CutsceneCmdTransition): + return indent * 2 + f"CS_TRANSITION({transition.type}, {transition.startFrame}, {transition.endFrame}),\n" + + def getRumbleControllerCmd(self, rumble: CutsceneCmdRumbleController): + return indent * 3 + ( + f"CS_RUMBLE_CONTROLLER(" + + f"0, {rumble.startFrame}, 0, " + + f"{rumble.sourceStrength}, {rumble.duration}, {rumble.decreaseRate}, 0, 0),\n" + ) + + def getMiscCmd(self, misc: CutsceneCmdMisc): + return indent * 3 + (f"CS_MISC(" + f"{misc.type}, {misc.startFrame}, {misc.endFrame}" + ", 0" * 11 + "),\n") + + def getTimeCmd(self, time: CutsceneCmdTime): + return indent * 3 + (f"CS_TIME(" + f"0, {time.startFrame}, 0, {time.hour}, {time.minute}" + "),\n") + + def getLightSettingCmd(self, lightSetting: CutsceneCmdLightSetting): + return indent * 3 + ( + f"CS_LIGHT_SETTING(" + f"{lightSetting.lightSetting}, {lightSetting.startFrame}" + ", 0" * 9 + "),\n" + ) + + def getTextCmd(self, text: CutsceneCmdText): + return indent * 3 + ( + f"CS_TEXT(" + + f"{text.textId}, {text.startFrame}, {text.endFrame}, {text.type}, {text.altTextId1}, {text.altTextId2}" + + "),\n" + ) + + def getTextNoneCmd(self, textNone: CutsceneCmdTextNone): + return indent * 3 + f"CS_TEXT_NONE({textNone.startFrame}, {textNone.endFrame}),\n" + + def getTextOcarinaActionCmd(self, ocarinaAction: CutsceneCmdTextOcarinaAction): + return indent * 3 + ( + f"CS_TEXT_OCARINA_ACTION(" + + f"{ocarinaAction.ocarinaActionId}, {ocarinaAction.startFrame}, " + + f"{ocarinaAction.endFrame}, {ocarinaAction.messageId}" + + "),\n" + ) + + def getDestinationCmd(self, csProp: "OOTCutsceneProperty"): + dest = self.getEnumValue("csDestination", csProp, "csDestination") + return indent * 2 + f"CS_DESTINATION({dest}, {csProp.csDestinationStartFrame}, 0),\n" + + def getActorCueListCmd(self, actorCueList: CutsceneCmdActorCueList, isPlayerActor: bool): + return indent * 2 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" + + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" + + f"{actorCueList.entryTotal}),\n" + ) + + def getActorCueCmd(self, actorCue: CutsceneCmdActorCue, isPlayerActor: bool): + return indent * 3 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE(" + + f"{actorCue.actionID}, {actorCue.startFrame}, {actorCue.endFrame}, " + + "".join(f"{rot}, " for rot in actorCue.rot) + + "".join(f"{pos}, " for pos in actorCue.startPos) + + "".join(f"{pos}, " for pos in actorCue.endPos) + + "0.0f, 0.0f, 0.0f),\n" + ) + + def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): + return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" + + def getCamEyeSplineCmd(self, camEyeSpline: CutsceneCmdCamEyeSpline): + return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) + + def getCamATSplineCmd(self, camATSpline: CutsceneCmdCamATSpline): + return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) + + def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: CutsceneCmdCamEyeSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame + ) + + def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: CutsceneCmdCamATSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame + ) + + def getCamEyeCmd(self, camEye: CutsceneCmdCamEye): + return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) + + def getCamATCmd(self, camAT: CutsceneCmdCamAT): + return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) + + def getCamPointCmd(self, camPoint: CutsceneCmdCamPoint): + return indent * 3 + ( + f"CS_CAM_POINT(" + + f"{camPoint.continueFlag}, {camPoint.camRoll}, {camPoint.frame}, {camPoint.viewAngle}f, " + + "".join(f"{pos}, " for pos in camPoint.pos) + + "0),\n" + ) + + +@dataclass +class CutsceneExport(CutsceneCmdToC): + """This class contains functions to create the new cutscene data""" + + csObjects: dict[str, list[Object]] + useDecomp: bool + motionOnly: bool + entryTotal: int = 0 + frameCount: int = 0 + motionFrameCount: int = 0 + camEndFrame: int = 0 + + def getOoTRotation(self, obj: Object): + """Returns the converted Blender rotation""" + + def conv(r): + r /= 2.0 * math.pi + r -= math.floor(r) + r = round(r * 0x10000) + + if r >= 0x8000: + r += 0xFFFF0000 + + assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) + + return hex(r & 0xFFFF) + + rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] + return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] + + def getOoTPosition(self, pos): + """Returns the converted Blender position""" + + scale = bpy.context.scene.ootBlenderScale + + x = round(pos[0] * scale) + y = round(pos[2] * scale) + z = round(-pos[1] * scale) + + if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): + raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") + + return [x, y, z] + + def getActorCueListData(self, isPlayer: bool): + """Returns the Actor Cue List commands from the corresponding objects""" + + playerOrActor = f"{'Player' if isPlayer else 'Actor'}" + actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] + actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) + actorCueData = "" + + self.entryTotal += len(actorCueListObjects) + for obj in actorCueListObjects: + entryTotal = len(obj.children) + + if entryTotal == 0: + raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") + + if obj.children[-1].ootEmptyType != "CS Dummy Cue": + # we need an extra point that won't be exported to get the real last cue's + # end frame and end position + raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") + + commandType = obj.ootCSMotionProperty.actorCueListProp.commandType + + if commandType == "Custom": + commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom + elif self.useDecomp: + commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id + + # ignoring dummy cue + actorCueList = CutsceneCmdActorCueList(None, entryTotal=entryTotal - 1, commandType=commandType) + actorCueData += self.getActorCueListCmd(actorCueList, isPlayer) + + for i, childObj in enumerate(obj.children, 1): + startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame + if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": + endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame + actionID = None + + if isPlayer: + cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID + if cueID != "Custom": + actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id + + if actionID is None: + actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID + + actorCue = CutsceneCmdActorCue( + None, + startFrame, + endFrame, + actionID, + self.getOoTRotation(childObj), + self.getOoTPosition(childObj.location), + self.getOoTPosition(obj.children[i].location), + ) + actorCueData += self.getActorCueCmd(actorCue, isPlayer) + + return actorCueData + + def getCameraShotPointData(self, bones, useAT: bool): + """Returns the Camera Point data from the bone data""" + + shotPoints: list[CutsceneCmdCamPoint] = [] + + if len(bones) < 4: + raise RuntimeError("Camera Armature needs at least 4 bones!") + + for bone in bones: + if bone.parent is not None: + raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") + + shotPoints.append( + CutsceneCmdCamPoint( + None, + None, + None, + ("CS_CAM_CONTINUE" if self.useDecomp else "0"), + bone.ootCamShotPointProp.shotPointRoll if useAT else 0, + bone.ootCamShotPointProp.shotPointFrame, + bone.ootCamShotPointProp.shotPointViewAngle, + self.getOoTPosition(bone.head if not useAT else bone.tail), + ) + ) + + # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting + shotPoints.append( + CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useDecomp else "-1", 0, 0, 0.0, [0, 0, 0]) + ) + return shotPoints + + def getCamCmdFunc(self, camMode: str, useAT: bool): + """Returns the camera get function depending on the camera mode""" + + camCmdFuncMap = { + "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, + "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd + if useAT + else self.getCamEyeSplineRelToPlayerCmd, + "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, + } + + return camCmdFuncMap[camMode] + + def getCamClass(self, camMode: str, useAT: bool): + """Returns the camera dataclass depending on the camera mode""" + + camCmdClassMap = { + "splineEyeOrAT": CutsceneCmdCamATSpline if useAT else CutsceneCmdCamEyeSpline, + "splineEyeOrATRelPlayer": CutsceneCmdCamATSplineRelToPlayer + if useAT + else CutsceneCmdCamEyeSplineRelToPlayer, + "eyeOrAT": CutsceneCmdCamAT if useAT else CutsceneCmdCamEye, + } + + return camCmdClassMap[camMode] + + def getCamListData(self, shotObj: Object, useAT: bool): + """Returns the Camera Shot data from the corresponding Armatures""" + + camPointList = self.getCameraShotPointData(shotObj.data.bones, useAT) + startFrame = shotObj.data.ootCamShotProp.shotStartFrame + + # "fake" end frame + endFrame = ( + startFrame + max(2, sum(point.frame for point in camPointList)) + (camPointList[-2].frame if useAT else 1) + ) + + if not useAT: + for pointData in camPointList: + pointData.frame = 0 + self.camEndFrame = endFrame + + camData = self.getCamClass(shotObj.data.ootCamShotProp.shotCamMode, useAT)(None, startFrame, endFrame) + return self.getCamCmdFunc(shotObj.data.ootCamShotProp.shotCamMode, useAT)(camData) + "".join( + self.getCamPointCmd(pointData) for pointData in camPointList + ) + + def getCameraShotData(self): + """Returns every Camera Shot commands""" + + shotObjects = self.csObjects["camShot"] + cameraShotData = "" + + if len(shotObjects) > 0: + motionFrameCount = -1 + for shotObj in shotObjects: + cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) + motionFrameCount = max(motionFrameCount, self.camEndFrame + 1) + self.motionFrameCount += motionFrameCount + self.entryTotal += len(shotObjects) * 2 + + return cameraShotData + + def getTextListData(self, textEntry: "OOTCSTextProperty"): + match textEntry.textboxType: + case "Text": + return self.getTextCmd( + CutsceneCmdText( + None, + textEntry.startFrame, + textEntry.endFrame, + textEntry.textID, + self.getEnumValue("csTextType", textEntry, "csTextType"), + textEntry.topOptionTextID, + textEntry.bottomOptionTextID, + ) + ) + case "None": + return self.getTextNoneCmd(CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame)) + case "OcarinaAction": + return self.getTextOcarinaActionCmd( + CutsceneCmdTextOcarinaAction( + None, + textEntry.startFrame, + textEntry.endFrame, + self.getEnumValue("ocarinaSongActionId", textEntry, "ocarinaAction"), + textEntry.ocarinaMessageId, + ) + ) + + def getCutsceneData(self): + csProp: "OOTCutsceneProperty" = self.csObjects["Cutscene"][0].ootCutsceneProperty + self.frameCount = csProp.csEndFrame + data = "" + + if csProp.csUseDestination: + data += self.getDestinationCmd(csProp) + self.entryTotal += 1 + + for entry in csProp.csLists: + subData = "" + listCmd = "" + entryTotal = 0 + match entry.listType: + case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": + entryTotal = len(entry.seqList) + for elem in entry.seqList: + enumKey = "csFadeOutSeqPlayer" if entry.listType == "FadeOutSeqList" else "seqId" + propName = "csSeqPlayer" if entry.listType == "FadeOutSeqList" else "csSeqID" + subData += self.getGenericSeqCmd( + ootEnumCSListTypeListC[entry.listType].removesuffix("_LIST"), + self.getEnumValue(enumKey, elem, propName), + elem.startFrame, + elem.endFrame, + ) + case "Transition": + subData += self.getTransitionCmd( + CutsceneCmdTransition( + None, + entry.transitionStartFrame, + entry.transitionEndFrame, + self.getEnumValue("csTransitionType", entry, "transitionType"), + ) + ) + case _: + curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) + entryTotal = len(curList) + for elem in curList: + match entry.listType: + case "TextList": + subData += self.getTextListData(elem) + case "LightSettingsList": + subData += self.getLightSettingCmd( + CutsceneCmdLightSetting( + None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex + ) + ) + case "TimeList": + subData += self.getTimeCmd( + CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) + ) + case "MiscList": + subData += self.getMiscCmd( + CutsceneCmdMisc( + None, + elem.startFrame, + elem.endFrame, + self.getEnumValue("csMiscType", elem, "csMiscType"), + ) + ) + case "RumbleList": + subData += self.getRumbleControllerCmd( + CutsceneCmdRumbleController( + None, + elem.startFrame, + elem.endFrame, + elem.rumbleSourceStrength, + elem.rumbleDuration, + elem.rumbleDecreaseRate, + ) + ) + case _: + raise PluginError("ERROR: Unknown Cutscene List Type!") + if entry.listType != "Transition": + listCmd = self.getGenericListCmd(ootEnumCSListTypeListC[entry.listType], entryTotal) + self.entryTotal += 1 + data += listCmd + subData + + return data + + def getExportData(self): + """Returns the cutscene data""" + + csData = "" + if not self.motionOnly: + csData = self.getCutsceneData() + csData += self.getActorCueListData(False) + self.getActorCueListData(True) + self.getCameraShotData() + + if self.motionFrameCount > self.frameCount: + self.frameCount += self.motionFrameCount - self.frameCount + + return ( + (indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n") + csData + (indent + "CS_END(),\n") + ) diff --git a/fast64_internal/oot/cutscene/exporter/functions.py b/fast64_internal/oot/cutscene/exporter/functions.py index 9ac6204f5..4512f1512 100644 --- a/fast64_internal/oot/cutscene/exporter/functions.py +++ b/fast64_internal/oot/cutscene/exporter/functions.py @@ -1,104 +1,49 @@ -from ...oot_utility import getCutsceneName, getCustomProperty +import bpy -from .classes import ( - OOTCSList, - OOTCSTextbox, - OOTCSLighting, - OOTCSTime, - OOTCSBGM, - OOTCSMisc, - OOTCS0x09, - OOTCSUnk, - OOTCutscene, -) +from bpy.types import Object +from ....utility import PluginError +from .classes import CutsceneExport -def readCutsceneData(csParentOut, csParentIn): - for listIn in csParentIn.csLists: - listOut = OOTCSList() - listOut.listType = listIn.listType - listOut.unkType, listOut.fxType, listOut.fxStartFrame, listOut.fxEndFrame = ( - listIn.unkType, - listIn.fxType, - listIn.fxStartFrame, - listIn.fxEndFrame, - ) - listData = [] - if listOut.listType == "Textbox": - for entryIn in listIn.textbox: - entryOut = OOTCSTextbox() - entryOut.textboxType = entryIn.textboxType - entryOut.messageId = entryIn.messageId - entryOut.ocarinaSongAction = entryIn.ocarinaSongAction - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - entryOut.type = entryIn.type - entryOut.topOptionBranch = entryIn.topOptionBranch - entryOut.bottomOptionBranch = entryIn.bottomOptionBranch - entryOut.ocarinaMessageId = entryIn.ocarinaMessageId - listOut.entries.append(entryOut) - elif listOut.listType == "Lighting": - for entryIn in listIn.lighting: - entryOut = OOTCSLighting() - entryOut.index = entryIn.index - entryOut.startFrame = entryIn.startFrame - listOut.entries.append(entryOut) - elif listOut.listType == "Time": - for entryIn in listIn.time: - entryOut = OOTCSTime() - entryOut.startFrame = entryIn.startFrame - entryOut.hour = entryIn.hour - entryOut.minute = entryIn.minute - listOut.entries.append(entryOut) - elif listOut.listType in {"PlayBGM", "StopBGM", "FadeBGM"}: - for entryIn in listIn.bgm: - entryOut = OOTCSBGM() - entryOut.value = entryIn.value - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - listOut.entries.append(entryOut) - elif listOut.listType == "Misc": - for entryIn in listIn.misc: - entryOut = OOTCSMisc() - entryOut.operation = entryIn.operation - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - listOut.entries.append(entryOut) - elif listOut.listType == "0x09": - for entryIn in listIn.nine: - entryOut = OOTCS0x09() - entryOut.startFrame = entryIn.startFrame - entryOut.unk2 = entryIn.unk2 - entryOut.unk3 = entryIn.unk3 - entryOut.unk4 = entryIn.unk4 - listOut.entries.append(entryOut) - elif listOut.listType == "Unk": - for entryIn in listIn.unk: - entryOut = OOTCSUnk() - entryOut.unk1 = entryIn.unk1 - entryOut.unk2 = entryIn.unk2 - entryOut.unk3 = entryIn.unk3 - entryOut.unk4 = entryIn.unk4 - entryOut.unk5 = entryIn.unk5 - entryOut.unk6 = entryIn.unk6 - entryOut.unk7 = entryIn.unk7 - entryOut.unk8 = entryIn.unk8 - entryOut.unk9 = entryIn.unk9 - entryOut.unk10 = entryIn.unk10 - entryOut.unk11 = entryIn.unk11 - entryOut.unk12 = entryIn.unk12 - listOut.entries.append(entryOut) - csParentOut.csLists.append(listOut) +def getCutsceneObjects(csName: str): + """Returns the object list containing every object from the cutscene to export""" + csObjects: dict[str, list[Object]] = { + "Cutscene": [], + "CS Actor Cue List": [], + "CS Player Cue List": [], + "camShot": [], + } -def convertCutsceneObject(obj): - cs = OOTCutscene() - cs.name = getCutsceneName(obj) - csprop = obj.ootCutsceneProperty - cs.csEndFrame = getCustomProperty(csprop, "csEndFrame") - cs.csWriteTerminator = getCustomProperty(csprop, "csWriteTerminator") - cs.csTermIdx = getCustomProperty(csprop, "csTermIdx") - cs.csTermStart = getCustomProperty(csprop, "csTermStart") - cs.csTermEnd = getCustomProperty(csprop, "csTermEnd") - readCutsceneData(cs, csprop) - return cs + if csName is None: + raise PluginError("ERROR: The cutscene name is None!") + + for obj in bpy.data.objects: + isEmptyObj = obj.type == "EMPTY" + + # look for the cutscene object based on the cutscene name + parentCheck = obj.parent is not None and obj.parent.name == f"Cutscene.{csName}" + csObjCheck = isEmptyObj and obj.ootEmptyType == "Cutscene" and obj.name == f"Cutscene.{csName}" + if parentCheck or csObjCheck: + # add the relevant objects based on the empty type or if it's an armature + if isEmptyObj and obj.ootEmptyType in csObjects.keys(): + csObjects[obj.ootEmptyType].append(obj) + + if obj.type == "ARMATURE" and obj.parent.ootEmptyType == "Cutscene": + csObjects["camShot"].append(obj) + + if len(csObjects["Cutscene"]) != 1: + raise PluginError(f"ERROR: Expected 1 Cutscene Object, found {len(csObjects['Cutscene'])} ({csName}).") + + return csObjects + + +def getNewCutsceneExport(csName: str, motionOnly: bool): + """Returns the initialised cutscene exporter""" + + # this allows us to change the exporter's variables to get what we need + return CutsceneExport( + getCutsceneObjects(csName), + bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.useDecompFeatures, + motionOnly, + ) diff --git a/fast64_internal/oot/cutscene/importer/__init__.py b/fast64_internal/oot/cutscene/importer/__init__.py new file mode 100644 index 000000000..2bca31fb6 --- /dev/null +++ b/fast64_internal/oot/cutscene/importer/__init__.py @@ -0,0 +1 @@ +from .functions import importCutsceneData diff --git a/fast64_internal/oot/cutscene/importer/classes.py b/fast64_internal/oot/cutscene/importer/classes.py new file mode 100644 index 000000000..066494895 --- /dev/null +++ b/fast64_internal/oot/cutscene/importer/classes.py @@ -0,0 +1,528 @@ +import bpy + +from dataclasses import dataclass +from typing import TYPE_CHECKING +from bpy.types import Object, Armature +from ....utility import PluginError +from ..motion.utility import setupCutscene, getBlenderPosition, getInteger + +if TYPE_CHECKING: + from ..properties import OOTCSListProperty, OOTCutsceneProperty + +from ..constants import ( + ootCSLegacyToNewCmdNames, + ootCSListCommands, + ootCutsceneCommandsC, + ootCSListEntryCommands, + ootCSSingleCommands, + ootCSListAndSingleCommands, + cmdToClass, +) + +from ..classes import ( + CutsceneCmdActorCueList, + CutsceneCmdCamPoint, + Cutscene, + CutsceneObjectFactory, +) + + +@dataclass +class ParsedCutscene: + """Local class used to order the parsed cutscene properly""" + + csName: str + csData: list[str] # contains every command lists or standalone ones like ``CS_TRANSITION()`` + + +@dataclass +class PropertyData: + listType: str + subPropsData: dict[str, str] + useEndFrame: bool + + +@dataclass +class CutsceneImport(CutsceneObjectFactory): + """This class contains functions to create the new cutscene Blender data""" + + filePath: str # used when importing from the panel + fileData: str # used when importing the cutscenes when importing a scene + + def getCmdParams(self, data: str, cmdName: str, paramNumber: int): + """Returns the list of every parameter of the given command""" + + parenthesis = "(" if not cmdName.endswith("(") else "" + params = data.strip().removeprefix(f"{cmdName}{parenthesis}").replace(" ", "").removesuffix(")").split(",") + validTimeCmd = cmdName == "CS_TIME" and len(params) == 6 and paramNumber == 5 + if len(params) != paramNumber and not validTimeCmd: + raise PluginError( + f"ERROR: The number of expected parameters for `{cmdName}` " + + "and the number of found ones is not the same!" + ) + return params + + def getNewCutscene(self, csData: str, name: str): + params = self.getCmdParams(csData, "CS_BEGIN_CUTSCENE", Cutscene.paramNumber) + return Cutscene(name, getInteger(params[0]), getInteger(params[1])) + + def getParsedCutscenes(self): + """Returns the parsed commands read from every cutscene we can find""" + + fileData = "" + + if self.fileData is not None: + fileData = self.fileData + elif self.filePath is not None: + with open(self.filePath, "r") as inputFile: + fileData = inputFile.read() + else: + raise PluginError("ERROR: File data can't be found!") + + # replace old names + oldNames = list(ootCSLegacyToNewCmdNames.keys()) + fileData = fileData.replace("CS_CMD_CONTINUE", "CS_CAM_CONTINUE") + fileData = fileData.replace("CS_CMD_STOP", "CS_CAM_STOP") + for oldName in oldNames: + fileData = fileData.replace(f"{oldName}(", f"{ootCSLegacyToNewCmdNames[oldName]}(") + + # parse cutscenes + fileLines = fileData.split("\n") + csData = [] + cutsceneList: list[list[str]] = [] + foundCutscene = False + for line in fileLines: + if not line.startswith("//") and not line.startswith("/*"): + if "CutsceneData " in line: + foundCutscene = True + + if foundCutscene: + sLine = line.strip() + if not sLine.endswith("),") and sLine.endswith(","): + line += fileLines[fileLines.index(line) + 1].strip() + + if len(csData) == 0 or "CS_" in line: + csData.append(line) + + if "};" in line: + foundCutscene = False + cutsceneList.append(csData) + csData = [] + + if len(cutsceneList) == 0: + print("INFO: Found no cutscenes in this file!") + return None + + # parse the commands from every cutscene we found + parsedCutscenes: list[ParsedCutscene] = [] + for cutscene in cutsceneList: + cmdListFound = False + curCmdPrefix = None + parsedCS = [] + parsedData = "" + csName = None + + for line in cutscene: + curCmd = line.strip().split("(")[0] + index = cutscene.index(line) + 1 + nextCmd = cutscene[index].strip().split("(")[0] if index < len(cutscene) else None + line = line.strip() + if "CutsceneData" in line: + csName = line.split(" ")[1][:-2] + + # NOTE: ``CS_UNK_DATA()`` are commands that are completely useless, so we're ignoring those + if csName is not None and not "CS_UNK_DATA" in curCmd: + if curCmd in ootCutsceneCommandsC: + line = line.removesuffix(",") + "\n" + + if curCmd in ootCSSingleCommands and curCmd != "CS_END": + parsedData += line + + if not cmdListFound and curCmd in ootCSListCommands: + cmdListFound = True + parsedData = "" + + # camera and lighting have "non-standard" list names + if curCmd.startswith("CS_CAM"): + curCmdPrefix = "CS_CAM" + elif curCmd.startswith("CS_LIGHT") or curCmd.startswith("L_CS_LIGHT"): + curCmdPrefix = "CS_LIGHT" + else: + curCmdPrefix = curCmd[:-5] + + if curCmdPrefix is not None: + if curCmdPrefix in curCmd: + parsedData += line + elif not cmdListFound and curCmd in ootCSListEntryCommands: + print(f"{csName}, command:\n{line}") + raise PluginError(f"ERROR: Found a list entry outside a list inside ``{csName}``!") + + if cmdListFound and nextCmd == "CS_END" or nextCmd in ootCSListAndSingleCommands: + cmdListFound = False + parsedCS.append(parsedData) + parsedData = "" + elif not "CutsceneData" in curCmd and not "};" in curCmd: + print(f"WARNING: Unknown command found: ``{curCmd}``") + cmdListFound = False + parsedCutscenes.append(ParsedCutscene(csName, parsedCS)) + + return parsedCutscenes + + def getCutsceneList(self): + """Returns the list of cutscenes with the data processed""" + + parsedCutscenes = self.getParsedCutscenes() + + if parsedCutscenes is None: + # if it's none then there's no cutscene in the file + return None + + cutsceneList: list[Cutscene] = [] + + # for each cutscene from the list returned by getParsedCutscenes(), + # create classes containing the cutscene's informations + # that will be used later when creating Blender objects to complete the import + for parsedCS in parsedCutscenes: + cutscene = None + for data in parsedCS.csData: + cmdData = data.removesuffix("\n").split("\n") + cmdListData = cmdData.pop(0) + cmdListName = cmdListData.strip().split("(")[0] + + # create a new cutscene data + if cmdListName == "CS_BEGIN_CUTSCENE": + cutscene = self.getNewCutscene(data, parsedCS.csName) + + # if we have a cutscene, create and add the commands data in it + elif cutscene is not None and data.startswith(f"{cmdListName}("): + isPlayer = cmdListData.startswith("CS_PLAYER_CUE_LIST(") + isStartSeq = cmdListData.startswith("CS_START_SEQ_LIST(") + isStopSeq = cmdListData.startswith("CS_STOP_SEQ_LIST(") + + cmd = cmdToClass.get(cmdListName) + if cmd is not None: + cmdList = getattr(cutscene, "playerCueList" if isPlayer else cmd.listName) + + paramNumber = cmd.paramNumber - 1 if isPlayer else cmd.paramNumber + params = self.getCmdParams(cmdListData, cmdListName, paramNumber) + if isStartSeq or isStopSeq: + commandData = cmd(params, type="start" if isStartSeq else "stop") + elif cmdListData.startswith("CS_ACTOR_CUE_LIST(") or isPlayer: + commandData = cmd(params, isPlayer=isPlayer) + else: + commandData = cmd(params) + + if cmdListName != "CS_TRANSITION" and cmdListName != "CS_DESTINATION": + foundEndCmd = False + for d in cmdData: + cmdEntryName = d.strip().split("(")[0] + isLegacy = d.startswith("L_") + if isLegacy: + cmdEntryName = cmdEntryName.removeprefix("L_") + d = d.removeprefix("L_") + + if "CAM" in cmdListName: + flag = d.removeprefix("CS_CAM_POINT(").split(",")[0] + if foundEndCmd: + raise PluginError("ERROR: More camera commands after last one!") + foundEndCmd = "CS_CAM_STOP" in flag or "-1" in flag + + entryCmd = cmdToClass[cmdEntryName] + params = self.getCmdParams(d, cmdEntryName, entryCmd.paramNumber) + + if "CS_LIGHT_SETTING(" in d or isStartSeq or isStopSeq: + listEntry = entryCmd(params, isLegacy=isLegacy) + else: + listEntry = entryCmd(params) + commandData.entries.append(listEntry) + if cmdListName == "CS_DESTINATION": + cutscene.destination = commandData + else: + cmdList.append(commandData) + else: + print(f"WARNING: `{cmdListName}` is not implemented yet!") + + # after processing the commands we can add the cutscene to the cutscene list + if cutscene is not None: + cutsceneList.append(cutscene) + return cutsceneList + + def setActorCueData(self, csObj: Object, actorCueList: list[CutsceneCmdActorCueList], cueName: str, csNbr: int): + """Creates the objects from the Actor Cue List data""" + + cueObjList = [] + cueEndFrames = [] + for i, entry in enumerate(actorCueList, 1): + if len(entry.entries) == 0: + raise PluginError("ERROR: Actor Cue List does not have any Actor Cue!") + + lastFrame = lastPos = None + actorCueListObj = self.getNewActorCueListObject( + f"CS_{csNbr:02}.{cueName} Cue List {i:02}", entry.commandType, csObj + ) + + for j, actorCue in enumerate(entry.entries, 1): + if lastFrame is not None and lastFrame != actorCue.startFrame: + raise PluginError("ERROR: Actor Cues are not temporally continuous!") + + if lastPos is not None and lastPos != actorCue.startPos: + raise PluginError("ERROR: Actor Cues are not spatially continuous!") + + cueObjList.append( + self.getNewActorCueObject( + f"CS_{csNbr:02}.{cueName} Cue {i}.{j:02}", + actorCue.startFrame, + actorCue.actionID, + actorCue.startPos, + actorCue.rot, + actorCueListObj, + ) + ) + lastFrame = actorCue.endFrame + lastPos = actorCue.endPos + cueEndFrames.append(lastFrame) + + # we need a dummy actor cue to get the end position of the last real one + if lastFrame is not None: + cueObjList.append( + self.getNewActorCueObject( + f"CS_{csNbr:02}.{cueName} Cue {i}.999 (D)", + lastFrame, + "DUMMY", + lastPos, + actorCue.rot, + actorCueListObj, + ) + ) + cueEndFrames.append(lastFrame + 1) + + # updating the end frames + if len(cueEndFrames) != len(cueObjList): + raise PluginError("ERROR: Lists lengths do not match!") + + for obj, endFrame in zip(cueObjList, cueEndFrames): + # reading this value will trigger the "get" function + getEndFrame = obj.ootCSMotionProperty.actorCueProp.cueEndFrame + + if endFrame != getEndFrame and obj.ootEmptyType != "CS Dummy Cue": + print(f"WARNING: `{obj.name}`'s end frame do not match the one from the script!") + + def validateCameraData(self, cutscene: Cutscene): + """Safety checks to make sure the camera data is correct""" + + camLists: list[tuple[str, list, list]] = [ + ("Eye and AT Spline", cutscene.camEyeSplineList, cutscene.camATSplineList), + ("Eye and AT Spline Rel to Player", cutscene.camEyeSplineRelPlayerList, cutscene.camATSplineRelPlayerList), + ("Eye and AT", cutscene.camEyeList, cutscene.camATList), + ] + + for camType, eyeList, atList in camLists: + for eyeListEntry, atListEntry in zip(eyeList, atList): + eyeTotal = len(eyeListEntry.entries) + atTotal = len(atListEntry.entries) + + # Eye -> bone's head, AT -> bone's tail, that's why both lists requires the same length + if eyeTotal != atTotal: + raise PluginError(f"ERROR: Found {eyeTotal} Eye lists but {atTotal} AT lists in {camType}!") + + if eyeTotal < 4: + raise PluginError(f"ERROR: Only {eyeTotal} cam point in this command!") + + if eyeTotal > 4: + # NOTE: There is a bug in the game where when incrementing to the next set of key points, + # the key point which checked for whether it's the last point or not is the last point + # of the next set, not the last point of the old set. This means we need to remove + # the extra point at the end that will only tell the game that this camera shot stops. + del eyeListEntry.entries[-1] + del atListEntry.entries[-1] + + def setBoneData( + self, cameraShotObj: Object, boneData: list[tuple[CutsceneCmdCamPoint, CutsceneCmdCamPoint]], csNbr: int + ): + """Creates the bones from the Camera Point data""" + + scale = bpy.context.scene.ootBlenderScale + for i, (eyePoint, atPoint) in enumerate(boneData, 1): + # we need the edit mode to be able to change the bone's location + bpy.ops.object.mode_set(mode="EDIT") + armatureData: Armature = cameraShotObj.data + boneName = f"CS_{csNbr:02}.Camera Point {i:02}" + newEditBone = armatureData.edit_bones.new(boneName) + newEditBone.head = getBlenderPosition(eyePoint.pos, scale) + newEditBone.tail = getBlenderPosition(atPoint.pos, scale) + bpy.ops.object.mode_set(mode="OBJECT") + newBone = armatureData.bones[boneName] + + if eyePoint.frame != 0: + print("WARNING: Frames must be 0!") + + # using the "AT" (look-at) data since this is what determines where the camera is looking + # the "Eye" only sets the location of the camera + newBone.ootCamShotPointProp.shotPointFrame = atPoint.frame + newBone.ootCamShotPointProp.shotPointViewAngle = atPoint.viewAngle + newBone.ootCamShotPointProp.shotPointRoll = atPoint.camRoll + + def setCameraShotData( + self, csObj: Object, eyePoints: list, atPoints: list, camMode: str, startIndex: int, csNbr: int + ): + """Creates the armatures from the Camera Shot data""" + + endIndex = 0 + + # this is required to be able to change the object mode + if bpy.context.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + for i, (camEyeSpline, camATSpline) in enumerate(zip(eyePoints, atPoints), startIndex): + cameraShotObj = self.getNewArmatureObject(f"CS_{csNbr:02}.Camera Shot {i:02}", True, csObj) + + if camEyeSpline.endFrame < camEyeSpline.startFrame + 2 or camATSpline.endFrame < camATSpline.startFrame + 2: + print("WARNING: Non-standard end frame") + + cameraShotObj.data.ootCamShotProp.shotStartFrame = camEyeSpline.startFrame + cameraShotObj.data.ootCamShotProp.shotCamMode = camMode + boneData = [(eyePoint, atPoint) for eyePoint, atPoint in zip(camEyeSpline.entries, camATSpline.entries)] + self.setBoneData(cameraShotObj, boneData, csNbr) + endIndex = i + + return endIndex + 1 + + def setPropOrCustom(self, prop, propName: str, value): + try: + setattr(prop, propName, value) + except TypeError: + setattr(prop, propName, "Custom") + setattr(prop, f"{propName}Custom", value) + + def setSubPropertyData(self, subPropsData: dict[str, str], newSubElem, entry): + customNames = [ + "csMiscType", + "csTextType", + "ocarinaAction", + "transitionType", + "csSeqID", + "csSeqPlayer", + "transition", + ] + + for key, value in subPropsData.items(): + if value is not None: + if key in customNames: + valueToSet = getattr(entry, value) + self.setPropOrCustom(newSubElem, key, valueToSet) + else: + setattr(newSubElem, key, getattr(entry, value)) + + def setPropertyData(self, csProp: "OOTCutsceneProperty", cutscene: Cutscene, propDataList: list[PropertyData]): + for data in propDataList: + listName = f"{data.listType[0].lower() + data.listType[1:]}List" + dataList = getattr(cutscene, (listName if data.listType != "FadeOutSeq" else "fadeSeqList")) + for list in dataList: + newElem: "OOTCSListProperty" = csProp.csLists.add() + + if data.listType == "Seq": + type = "StartSeqList" if list.type == "start" else "StopSeqList" + else: + type = f"{data.listType}List" if data.listType != "Transition" else data.listType + newElem.listType = type + + if data.listType == "Transition": + newElem.transitionStartFrame = list.startFrame + newElem.transitionEndFrame = list.endFrame + self.setSubPropertyData(data.subPropsData, newElem, list) + else: + list.entries.sort(key=lambda elem: elem.startFrame) + for entry in list.entries: + newSubElem = getattr(newElem, "seqList" if "fadeOut" in listName else listName).add() + newSubElem.startFrame = entry.startFrame + + if data.useEndFrame: + newSubElem.endFrame = entry.endFrame + + if data.listType == "Text": + self.setPropOrCustom(newSubElem, "textboxType", entry.id) + match entry.id: + case "Text": + newSubElem.textID = f"0x{entry.textId:04X}" + self.setPropOrCustom(newSubElem, "csTextType", entry.type) + case "None": + pass + case "OcarinaAction": + newSubElem.ocarinaMessageId = f"0x{entry.messageId:04X}" + self.setPropOrCustom(newSubElem, "ocarinaAction", entry.ocarinaActionId) + case _: + raise PluginError("ERROR: Unknown text type!") + self.setSubPropertyData(data.subPropsData, newSubElem, entry) + + def setCutsceneData(self, csNumber): + """Creates the cutscene empty objects from the file data""" + + cutsceneList = self.getCutsceneList() + + if cutsceneList is None: + # if it's none then there's no cutscene in the file + return csNumber + + for i, cutscene in enumerate(cutsceneList, csNumber): + print(f'Found Cutscene "{cutscene.name}"! Importing...') + self.validateCameraData(cutscene) + csName = f"Cutscene.{cutscene.name}" + csObj = self.getNewCutsceneObject(csName, cutscene.frameCount, None) + csProp = csObj.ootCutsceneProperty + csNumber = i + + self.setActorCueData(csObj, cutscene.actorCueList, "Actor", i) + self.setActorCueData(csObj, cutscene.playerCueList, "Player", i) + + if len(cutscene.camEyeSplineList) > 0: + lastIndex = self.setCameraShotData( + csObj, cutscene.camEyeSplineList, cutscene.camATSplineList, "splineEyeOrAT", 1, i + ) + + if len(cutscene.camEyeSplineRelPlayerList) > 0: + lastIndex = self.setCameraShotData( + csObj, + cutscene.camEyeSplineRelPlayerList, + cutscene.camATSplineRelPlayerList, + "splineEyeOrATRelPlayer", + lastIndex, + i, + ) + + if len(cutscene.camEyeList) > 0: + lastIndex = self.setCameraShotData( + csObj, cutscene.camEyeList, cutscene.camATList, "eyeOrAT", lastIndex, i + ) + + if cutscene.destination is not None: + csProp.csUseDestination = True + csProp.csDestinationStartFrame = cutscene.destination.startFrame + self.setPropOrCustom(csProp, "csDestination", cutscene.destination.id) + + propDataList = [ + PropertyData("Text", {"textboxType": "id"}, True), + PropertyData("Misc", {"csMiscType": "type"}, True), + PropertyData("Transition", {"transitionType": "type"}, True), + PropertyData("LightSettings", {"lightSettingsIndex": "lightSetting"}, False), + PropertyData("Time", {"hour": "hour", "minute": "minute"}, False), + PropertyData("Seq", {"csSeqID": "seqId"}, False), + PropertyData("FadeOutSeq", {"csSeqPlayer": "seqPlayer"}, True), + PropertyData( + "Rumble", + { + "rumbleSourceStrength": "sourceStrength", + "rumbleDuration": "duration", + "rumbleDecreaseRate": "decreaseRate", + }, + False, + ), + ] + self.setPropertyData(csProp, cutscene, propDataList) + + # Init camera + preview objects and setup the scene + setupCutscene(csObj) + bpy.ops.object.select_all(action="DESELECT") + print("Success!") + + # ``csNumber`` makes sure there's no duplicates + return csNumber + 1 diff --git a/fast64_internal/oot/cutscene/importer/functions.py b/fast64_internal/oot/cutscene/importer/functions.py new file mode 100644 index 000000000..402176e5d --- /dev/null +++ b/fast64_internal/oot/cutscene/importer/functions.py @@ -0,0 +1,10 @@ +import bpy + +from .classes import CutsceneImport + + +def importCutsceneData(filePath: str, sceneData: str): + """Initialises and imports the cutscene data from either a file or the scene data""" + # NOTE: ``sceneData`` is the data read when importing a scene + csMotionImport = CutsceneImport(filePath, sceneData) + return csMotionImport.setCutsceneData(bpy.context.scene.ootCSNumber) diff --git a/fast64_internal/oot/cutscene/motion/operators.py b/fast64_internal/oot/cutscene/motion/operators.py new file mode 100644 index 000000000..ad9205a5f --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/operators.py @@ -0,0 +1,370 @@ +import bpy + +from bpy.types import Object, Operator, Context, Armature +from bpy.utils import register_class, unregister_class +from bpy.props import StringProperty, EnumProperty, BoolProperty +from ....utility import PluginError +from ...oot_constants import ootData +from ..classes import CutsceneObjectFactory +from ..constants import ootEnumCSActorCueListCommandType +from ..preview import initFirstFrame, setupCompositorNodes +from .utility import ( + setupActorCuePreview, + metersToBlend, + getCSMotionValidateObj, + getNameInformations, + createNewBone, + createNewCameraShot, + getCutsceneEndFrame, + getCutsceneCamera, +) + + +def getActorCueList(operator: Operator, context: Context) -> Object | None: + cueListObj = activeObj = context.view_layer.objects.active + + if activeObj.ootEmptyType == "CS Actor Cue" and activeObj.parent.ootEmptyType == "CS Actor Cue List": + cueListObj = activeObj.parent + + if not cueListObj.ootEmptyType == "CS Actor Cue List" and cueListObj.parent.ootEmptyType == "Cutscene": + operator.report({"WARNING"}, "Select an action list or action point.") + return None + + return cueListObj + + +def createNewActorCueList(csObj: Object, isPlayer: bool): + """Creates a new Actor or Player Cue List and adds one basic cue and the dummy one""" + objFactory = CutsceneObjectFactory() + playerOrActor = "Player" if isPlayer else "Actor" + newActorCueListObj = objFactory.getNewActorCueListObject(f"New {playerOrActor} Cue List", "actor_cue_0_0", None) + index, csPrefix = getNameInformations(csObj, f"{playerOrActor} Cue List", None) + newActorCueListObj.name = f"{csPrefix}.{playerOrActor} Cue List {index:02}" + + # add a basic actor cue and the dummy one + for i in range(2): + nameSuffix = f"{i + 1:02}" if i == 0 else "999 (D)" + newActorCueObj = objFactory.getNewActorCueObject( + f"{csPrefix}.{playerOrActor} Cue {index:02}.{nameSuffix}", + i, + "cueid_none" if isPlayer else "0x0000", + [0, 0, 0], + ["0x0", "0x0", "0x0"], + newActorCueListObj, + ) + + # finally, parenting the object to the cutscene + newActorCueListObj.parent = csObj + + +class CutsceneCmdPlayPreview(Operator): + """Camera Preview Playback""" + + bl_idname = "object.play_preview" + bl_label = "Preview Playback" + + def execute(self, context): + try: + csObj = getCSMotionValidateObj(None, None, None) + + if csObj is not None: + # get and set the camera + previewSettings = context.scene.ootPreviewSettingsProperty + cameraObj = getCutsceneCamera(csObj) + cameraObj.data.passepartout_alpha = 1.0 if previewSettings.useOpaqueCamBg else 0.95 + + # from https://blender.stackexchange.com/a/259103 + space = None + for area in context.screen.areas: + if (area != context.area) and (area.type == "VIEW_3D"): + for space in area.spaces: + if space.type == "VIEW_3D": + break + if space is not None: + space.region_3d.view_perspective = "CAMERA" + + # setup frame data and play the animation + endFrame = getCutsceneEndFrame(csObj) + context.scene.frame_end = endFrame if endFrame > -1 else context.scene.frame_end + context.scene.frame_set(context.scene.frame_start) + previewSettings.ootCSPreviewCSObj = csObj + previewSettings.ootCSPreviewNodesReady = False + setupCompositorNodes() + initFirstFrame(csObj, previewSettings.ootCSPreviewNodesReady, cameraObj) + bpy.ops.screen.animation_cancel() + bpy.ops.screen.animation_play() + return {"FINISHED"} + except: + return {"CANCELLED"} + + +class CutsceneCmdAddBone(Operator): + """Add a bone to an armature""" + + bl_idname = "object.add_bone" + bl_label = "Add Bone" + + def execute(self, context): + try: + cameraShotObj = getCSMotionValidateObj(None, None, "Camera Shot") + + if cameraShotObj is not None: + armatureData: Armature = cameraShotObj.data + cameraShotObj.select_set(True) + lastMode = "EDIT" if "EDIT" in context.mode else context.mode + + # create the new bone with standard informations + blendOne = metersToBlend(context, float(1)) + boneName = f"CS_??.Camera Point ??" + createNewBone(cameraShotObj, boneName, [blendOne, blendOne, 0.0], [blendOne, 0.0, 0.0]) + + # update the name of the new bone and move it to the position of the previous last one + bpy.ops.object.mode_set(mode="EDIT") + if len(armatureData.edit_bones) > 0 and "CS_" in armatureData.edit_bones[-2].name: + secondToLastBone = armatureData.edit_bones[-2] + newBone = armatureData.edit_bones[-1] + splitName = secondToLastBone.name.split(" ") + try: + newBone.name = f"{splitName[0]} Point {int(splitName[2]) + 1:02}" + except ValueError: + newBone.name = f"Camera Point 500" + newBone.head = secondToLastBone.head + newBone.tail = secondToLastBone.tail + bpy.ops.object.mode_set(mode="OBJECT") # going back to object mode to update the bones properly + + if armatureData.bones[-1].name == boneName: + raise PluginError("ERROR: Something went wrong...") + + bpy.ops.object.mode_set(mode=lastMode) + self.report({"INFO"}, "Success!") + return {"FINISHED"} + else: + raise PluginError("You must select an armature object parented to a cutscene empty object!") + except: + return {"CANCELLED"} + + +class CutsceneCmdAddActorCue(Operator): + """Add an entry to a player or actor cue list""" + + bl_idname = "object.add_actor_cue_point" + bl_label = "Add Actor Cue" + + isPlayer: BoolProperty(default=False) + + def execute(self, context): + actorCueListObj = getCSMotionValidateObj(None, None, None) + + try: + if actorCueListObj is not None: + if actorCueListObj.parent is not None and actorCueListObj.parent.ootEmptyType != "Cutscene": + actorCueListObj = actorCueListObj.parent + + # start by creating the new object with basic values + objFactory = CutsceneObjectFactory() + newActorCueObj = objFactory.getNewActorCueObject( + f"New {'Player' if self.isPlayer else 'Actor'} Cue", + 0, + "cueid_none" if self.isPlayer else "0x0000", + [0, 0, 0], + ["0x0", "0x0", "0x0"], + None, + ) + + # if there's other actor cues, take the information from the last non-dummy one + if len(actorCueListObj.children) > 0: + dummyCue = None + for i, obj in enumerate(actorCueListObj.children): + if obj.ootEmptyType == "CS Dummy Cue": + dummyCue = obj + + if dummyCue is not None: + lastCueObj = actorCueListObj.children[i - 1] + nameSplit = lastCueObj.name.split(".") + try: + index = int(nameSplit[-1]) + 1 + except ValueError: + index = i + 500 # huge number to make sure it's going at the end of the list + startFrame = dummyCue.ootCSMotionProperty.actorCueProp.cueStartFrame + newActorCueObj.name = f"{nameSplit[0]}.{nameSplit[1]}.{index:02}" + newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame + dummyCue.ootCSMotionProperty.actorCueProp.cueStartFrame += 1 + newActorCueObj.location = lastCueObj.location + newActorCueObj.rotation_euler = lastCueObj.rotation_euler + else: + # else create the name from the actor cue list object + nameSplit = actorCueListObj.name.split(".") + try: + index = int(nameSplit[1].split(" ")[3]) + except ValueError: + index = 500 + newActorCueObj.name = f"{nameSplit[0]}.{nameSplit[1][:-8]} {index}.01" + + # add the dummy cue since the list is empty + nameSplit = newActorCueObj.name.split(".") + newDummyCueObj = objFactory.getNewActorCueObject( + f"{nameSplit[0]}.{nameSplit[1]}.999 (D)", + 1, + "0x0000", + [0, 0, 0], + ["0x0", "0x0", "0x0"], + actorCueListObj, + ) + + # update the end frame of the real cue + getEndFrame = newActorCueObj.ootCSMotionProperty.actorCueProp.cueEndFrame + newActorCueObj.parent = actorCueListObj + else: + raise PluginError("ERROR: Select the Actor or Player Cue List!") + + self.report({"INFO"}, "Success!") + return {"FINISHED"} + except Exception as e: + # print(e) + raise PluginError("ERROR: Something went wrong.") + return {"CANCELLED"} + + +class CutsceneCmdCreateActorCuePreview(Operator): + """Create a preview empty object for a player or an actor cue list""" + + bl_idname = "object.create_actor_cue_preview" + bl_label = "Create Preview Object" + + def execute(self, context): + cueList = getCSMotionValidateObj(None, None, None) + + if cueList is not None and cueList.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]: + isCueMoving = setupActorCuePreview( + cueList.parent, "Actor" if "Actor" in cueList.ootEmptyType else "Player", True, cueList + ) + self.report({"INFO"}, "Success!" if isCueMoving else "Actor Cue don't move, ignored preview.") + return {"FINISHED"} + else: + return {"CANCELLED"} + + +class CutsceneCmdCreateCameraShot(Operator): + """Create and initialize a camera shot armature""" + + bl_idname = "object.create_camera_shot" + bl_label = "Create Camera Shot" + + def execute(self, context): + csObj = getCSMotionValidateObj(None, None, None) + + if csObj is not None: + createNewCameraShot(csObj) + self.report({"INFO"}, "Success!") + return {"FINISHED"} + else: + return {"CANCELLED"} + + +class CutsceneCmdCreatePlayerCueList(Operator): + """Create a cutscene player cue list""" + + bl_idname = "object.create_player_cue_list" + bl_label = "Create Player Cue List" + + def execute(self, context): + csObj = getCSMotionValidateObj(None, None, None) + + try: + if csObj is not None: + createNewActorCueList(csObj, True) + else: + raise PluginError("ERROR: You must select the cutscene object!") + + self.report({"INFO"}, "Success!") + return {"FINISHED"} + except: + return {"CANCELLED"} + + +class CutsceneCmdCreateActorCueList(Operator): + """Create a cutscene actor cue list""" + + bl_idname = "object.create_actor_cue_list" + bl_label = "Create Actor Cue list" + + def execute(self, context): + csObj = getCSMotionValidateObj(None, None, None) + + try: + if csObj is not None: + createNewActorCueList(csObj, False) + else: + raise PluginError("ERROR: You must select the cutscene object!") + + self.report({"INFO"}, "Success!") + return {"FINISHED"} + except: + return {"CANCELLED"} + + +class OOT_SearchActorCueCmdTypeEnumOperator(Operator): + bl_idname = "object.oot_search_actorcue_cmdtype_enum_operator" + bl_label = "Select Command Type" + bl_property = "commandType" + bl_options = {"REGISTER", "UNDO"} + + commandType: EnumProperty(items=ootEnumCSActorCueListCommandType, default="actor_cue_0_0") + objName: StringProperty() + + def execute(self, context): + obj = bpy.data.objects[self.objName] + obj.ootCSMotionProperty.actorCueListProp.commandType = self.commandType + + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.commandType) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + +class OOT_SearchPlayerCueIdEnumOperator(Operator): + bl_idname = "object.oot_search_playercueid_enum_operator" + bl_label = "Select Player Cue ID" + bl_property = "playerCueID" + bl_options = {"REGISTER", "UNDO"} + + playerCueID: EnumProperty(items=ootData.enumData.ootEnumCsPlayerCueId, default="cueid_none") + objName: StringProperty() + + def execute(self, context): + obj = bpy.data.objects[self.objName] + obj.ootCSMotionProperty.actorCueProp.playerCueID = self.playerCueID + + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.playerCueID) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + +classes = ( + CutsceneCmdPlayPreview, + CutsceneCmdAddBone, + CutsceneCmdAddActorCue, + CutsceneCmdCreateActorCuePreview, + CutsceneCmdCreateCameraShot, + CutsceneCmdCreatePlayerCueList, + CutsceneCmdCreateActorCueList, + OOT_SearchActorCueCmdTypeEnumOperator, + OOT_SearchPlayerCueIdEnumOperator, +) + + +def csMotion_ops_register(): + for cls in classes: + register_class(cls) + + +def csMotion_ops_unregister(): + for cls in reversed(classes): + unregister_class(cls) diff --git a/fast64_internal/oot/cutscene/motion/panels.py b/fast64_internal/oot/cutscene/motion/panels.py new file mode 100644 index 000000000..a2018fed4 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/panels.py @@ -0,0 +1,50 @@ +from bpy.utils import register_class, unregister_class +from ....panels import OOT_Panel +from .properties import CutsceneCmdCameraShotProperty, CutsceneCmdCameraShotPointProperty + + +class OOT_CSMotionCameraShotPanel(OOT_Panel): + bl_label = "Cutscene Motion Camera Shot Controls" + bl_idname = "OOT_PT_camera_shot_panel" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_options = {"HIDE_HEADER"} + + def draw(self, context): + obj = context.view_layer.objects.active + layout = self.layout + + if obj.type == "ARMATURE": + camShotProp: CutsceneCmdCameraShotProperty = obj.data.ootCamShotProp + camShotPointProp: CutsceneCmdCameraShotPointProperty = None + activeBone = editBone = None + + box = layout.box() + camShotProp.draw_props(box, self.bl_label) + + if obj.mode == "POSE": + box.label(text="Warning: You can't be in 'Pose' mode to edit camera bones!") + elif obj.mode == "OBJECT": + activeBone = obj.data.bones.active + if activeBone is not None: + camShotPointProp = activeBone.ootCamShotPointProp + camShotPointProp.draw_props(box) + elif obj.mode == "EDIT": + editBone = obj.data.edit_bones.active + if editBone is not None: + camShotPointProp = editBone.ootCamShotPointProp + camShotPointProp.draw_props(box) + + +classes = (OOT_CSMotionCameraShotPanel,) + + +def csMotion_panels_register(): + for cls in classes: + register_class(cls) + + +def csMotion_panels_unregister(): + for cls in reversed(classes): + unregister_class(cls) diff --git a/fast64_internal/oot/cutscene/motion/preview.py b/fast64_internal/oot/cutscene/motion/preview.py new file mode 100644 index 000000000..95d5818a8 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/preview.py @@ -0,0 +1,220 @@ +import bpy +import math + +from mathutils import Vector, Quaternion +from bpy.app.handlers import persistent +from bpy.types import Object, Scene +from ....utility import PluginError +from .utility import ( + BoneData, + getCameraShotBoneData, + getCSMotionValidateObj, +) + + +# Eye -> Position, AT -> look-at, where the camera is looking + + +def getUndefinedCamPosEye(): + return (Vector((0.0, 0.0, 0.0)), Quaternion(), 45.0) + + +def getUndefinedCamPosAT(): + return (Vector((0.0, 0.0, 0.0)), Vector((0.0, 0.0, -1.0)), 0.0, 45.0) + + +def getSplineCoeffs(t: float): + # Reverse engineered from func_800BB0A0 in Debug ROM + + t = min(t, 1.0) # no check for t < 0 + oneminust = 1.0 - t + tsq = t * t + oneminustcube6 = (oneminust * oneminust * oneminust) / 6.0 + tcube = tsq * t + spline2 = ((tcube * 0.5) - tsq) + 0.6666667 + spline3 = ((tsq + t - tcube) * 0.5) + 0.16666667 + tcube6 = tcube / 6.0 + + return oneminustcube6, spline2, spline3, tcube6 + + +def getZ64SplineInterpolate(bones: list[BoneData], frame: int): + # Reverse engineered from func_800BB2B4 in Debug ROM + + p = 0 # keyframe + t = 0.0 # ratioBetweenPoints + + # Simulate cutscene for all frames up to present + for _ in range(frame): + if p + 2 >= len(bones) - 1: + # Camera position is uninitialized + return getUndefinedCamPosAT() + + framesPoint1 = bones[p + 1].frame + denomPoint1 = 1.0 / framesPoint1 if framesPoint1 != 0 else 0.0 + framesPoint2 = bones[p + 2].frame + denomPoint2 = 1.0 / framesPoint2 if framesPoint2 != 0 else 0.0 + dt = max(t * (denomPoint2 - denomPoint1) + denomPoint1, 0.0) + + # Different from in game; we remove the extra dummy point at import + # and add it at export. + if t + dt >= 1.0: + if p + 3 == len(bones) - 1: + break + + t -= 1.0 + p += 1 + + t += dt + + # Spline interpolate for current situation + if p + 3 > len(bones) - 1: + if frame > 0: + print("Internal error in spline algorithm") + + return getUndefinedCamPosAT() + + s1, s2, s3, s4 = getSplineCoeffs(t) + eye = s1 * bones[p].head + s2 * bones[p + 1].head + s3 * bones[p + 2].head + s4 * bones[p + 3].head + at = s1 * bones[p].tail + s2 * bones[p + 1].tail + s3 * bones[p + 2].tail + s4 * bones[p + 3].tail + roll = s1 * bones[p].roll + s2 * bones[p + 1].roll + s3 * bones[p + 2].roll + s4 * bones[p + 3].roll + viewAngle = ( + s1 * bones[p].viewAngle + + s2 * bones[p + 1].viewAngle + + s3 * bones[p + 2].viewAngle + + s4 * bones[p + 3].viewAngle + ) + + return (eye, at, roll, viewAngle) + + +def getCmdCamState(shotObj: Object, frame: int): + frame -= shotObj.data.ootCamShotProp.shotStartFrame + 1 + + if frame < 0: + print(f"Warning, camera command evaluated for frame {frame}") + return getUndefinedCamPosEye() + + bones = getCameraShotBoneData(shotObj, False) + + if bones is None: + return getUndefinedCamPosEye() + + eye, at, roll, viewAngle = getZ64SplineInterpolate(bones, frame) + # TODO handle cam_mode (relativeToLink) + lookvec = at - eye + + if lookvec.length < 1e-6: + return getUndefinedCamPosEye() + + lookvec.normalize() + ux = Vector((1.0, 0.0, 0.0)) + uy = Vector((0.0, 1.0, 0.0)) + uz = Vector((0.0, 0.0, 1.0)) + qroll = Quaternion(uz, roll * math.pi / 128.0) + qpitch = Quaternion(-ux, math.pi + math.acos(lookvec.dot(uz))) + qyaw = Quaternion(-uz, math.atan2(lookvec.dot(ux), lookvec.dot(uy))) + + return (eye, qyaw @ qpitch @ qroll, viewAngle) + + +def getCutsceneCamState(csObj: Object, frame: int): + """Returns (pos, rot_quat, viewAngle)""" + + shotObjects: list[Object] = [] + for childObj in csObj.children: + obj = getCSMotionValidateObj(csObj, childObj, "Camera Shot") + if obj is not None: + shotObjects.append(obj) + shotObjects.sort(key=lambda obj: obj.name) + shotObj = None + + if len(shotObjects) > 0: + startFrame = -1 + + for obj in shotObjects: + if obj.data.ootCamShotProp.shotStartFrame < frame and obj.data.ootCamShotProp.shotStartFrame > startFrame: + shotObj = obj + startFrame = obj.data.ootCamShotProp.shotStartFrame + + if shotObj is None or len(shotObjects) == 0: + return getUndefinedCamPosEye() + + return getCmdCamState(shotObj, frame) + + +def getActorCueState(cueListObj: Object, frame: int): + pos = Vector((0.0, 0.0, 0.0)) + rot = Vector((0.0, 0.0, 0.0)) + + cueList: list[Object] = [] + for cueObj in cueListObj.children: + obj = getCSMotionValidateObj(None, cueObj, None) + if obj is not None: + cueList.append(obj) + cueList.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) + + if len(cueList) >= 2: + for i in range(len(cueList) - 1): + startFrame = cueList[i].ootCSMotionProperty.actorCueProp.cueStartFrame + endFrame = cueList[i + 1].ootCSMotionProperty.actorCueProp.cueStartFrame + + if endFrame > startFrame and frame > startFrame: + if frame <= endFrame: + pos = cueList[i].location * (endFrame - frame) + cueList[i + 1].location * (frame - startFrame) + pos /= endFrame - startFrame + rot = cueList[i].rotation_euler + return pos, rot + elif i == len(cueList) - 2: + # If went off the end, use last position + pos = cueList[i + 1].location + rot = cueList[i].rotation_euler + return pos, rot + + +@persistent +def previewFrameHandler(scene: Scene): + for obj in bpy.data.objects: + parentObj = obj.parent + if ( + parentObj is not None + and parentObj.type == "EMPTY" + and parentObj.name.startswith("Cutscene.") + and parentObj.ootEmptyType == "Cutscene" + ): + if obj.type == "CAMERA": + pos, rot_quat, viewAngle = getCutsceneCamState(parentObj, scene.frame_current) + + if scene.ootPreviewSettingsProperty.useWidescreen: + viewAngle *= 4 / 3 + + if pos is not None: + obj.location = pos + obj.rotation_mode = "QUATERNION" + obj.rotation_quaternion = rot_quat + obj.data.angle = math.pi * viewAngle / 180.0 + elif obj.ootEmptyType in ["CS Actor Cue Preview", "CS Player Cue Preview"]: + cueListToPreview = None + if "Actor" in obj.ootEmptyType: + cueListToPreview = obj.ootCSMotionProperty.actorCueListProp.actorCueListToPreview + elif "Player" in obj.ootEmptyType: + cueListToPreview = obj.ootCSMotionProperty.actorCueListProp.playerCueListToPreview + else: + raise PluginError("Unknown Empty Type!") + + if cueListToPreview is not None: + pos, rot = getActorCueState(cueListToPreview, scene.frame_current) + + if pos is not None: + obj.location = pos + obj.rotation_mode = "XYZ" + obj.rotation_euler = rot + + +def csMotion_preview_register(): + bpy.app.handlers.frame_change_pre.append(previewFrameHandler) + + +def csMotion_preview_unregister(): + if previewFrameHandler in bpy.app.handlers.frame_change_pre: + bpy.app.handlers.frame_change_pre.remove(previewFrameHandler) diff --git a/fast64_internal/oot/cutscene/motion/properties.py b/fast64_internal/oot/cutscene/motion/properties.py new file mode 100644 index 000000000..d18a48282 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/properties.py @@ -0,0 +1,270 @@ +import bpy + +from bpy.types import PropertyGroup, Object, UILayout, Armature, Bone, Scene, EditBone +from bpy.props import IntProperty, StringProperty, PointerProperty, EnumProperty, FloatProperty +from bpy.utils import register_class, unregister_class +from ...oot_upgrade import upgradeCutsceneMotion +from ...oot_utility import getEnumName +from ...oot_constants import ootData +from ..constants import ootEnumCSMotionCamMode, ootEnumCSActorCueListCommandType + +from .operators import ( + CutsceneCmdAddActorCue, + CutsceneCmdCreateActorCuePreview, + OOT_SearchActorCueCmdTypeEnumOperator, + CutsceneCmdAddBone, + OOT_SearchPlayerCueIdEnumOperator, +) + + +def getNextCuesStartFrame(self): + curCueObj = bpy.context.view_layer.objects.active + parentObj = curCueObj.parent + + if parentObj is not None and parentObj.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]: + if curCueObj.ootEmptyType in ["CS Actor Cue", "CS Player Cue"]: + actorCueObjList = parentObj.children + for i, obj in enumerate(actorCueObjList): + if obj == curCueObj: + return actorCueObjList[i + 1].ootCSMotionProperty.actorCueProp.cueStartFrame + return -1 + + +class CutsceneCmdActorCueListProperty(PropertyGroup): + commandType: EnumProperty( + items=ootEnumCSActorCueListCommandType, name="CS Actor Cue Command Type", default="actor_cue_0_0" + ) + commandTypeCustom: StringProperty(name="CS Actor Cue Command Type Custom") + actorCueListToPreview: PointerProperty( + type=Object, name="", poll=lambda self, object: self.isActorCueListObj(object, "CS Actor Cue List") + ) + playerCueListToPreview: PointerProperty( + type=Object, name="", poll=lambda self, object: self.isActorCueListObj(object, "CS Player Cue List") + ) + + def isActorCueListObj(self, obj: Object, emptyType: str): + return obj.type == "EMPTY" and obj.ootEmptyType == emptyType + + def draw_props(self, layout: UILayout, isPreview: bool, labelPrefix: str, objName: str): + box = layout.box() + box.box().label(text=f"{labelPrefix} Cue List") + isPlayer = labelPrefix == "Player" + + # Player Cue has only one command type + if not isPlayer and not isPreview: + searchBox = box.row() + searchOp = searchBox.operator( + OOT_SearchActorCueCmdTypeEnumOperator.bl_idname, icon="VIEWZOOM", text="Command Type:" + ) + searchOp.objName = objName + searchBox.label(text=getEnumName(ootEnumCSActorCueListCommandType, self.commandType)) + + if self.commandType == "Custom": + split = box.split(factor=0.5) + split.label(text="Command Type Custom:") + split.prop(self, "commandTypeCustom", text="") + + if not isPreview: + split = box.split(factor=0.5) + addCueOp = split.operator(CutsceneCmdAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp.isPlayer = isPlayer + split.operator(CutsceneCmdCreateActorCuePreview.bl_idname) + else: + split = box.split(factor=0.5) + split.label(text=f"{labelPrefix} Cue List to Preview") + split.prop(self, f"{labelPrefix.lower()}CueListToPreview") + + +class CutsceneCmdActorCueProperty(PropertyGroup): + cueStartFrame: IntProperty(name="Start Frame", description="Start frame of the Actor Cue", default=0, min=0) + + cueEndFrame: IntProperty( + name="End Frame", + description="End Frame of the Actor Cue, corresponds to the Start Frame of the next Actor Cue", + default=0, + min=-1, + get=lambda self: getNextCuesStartFrame(self), + ) + + playerCueID: EnumProperty(items=ootData.enumData.ootEnumCsPlayerCueId, default="cueid_none") + cueActionID: StringProperty( + name="Action ID", default="0x0001", description="Actor action. Meaning is unique for each different actor." + ) + + def draw_props(self, layout: UILayout, labelPrefix: str, isDummy: bool, objName: str): + box = layout.box() + dummyExtra = "(Sets previous Actor Cue's end frame/pos.)" if isDummy else "" + boxBox = box.box() + boxBox.label(text=f"{labelPrefix if not isDummy else 'Dummy'} Cue") + if len(dummyExtra) > 0: + boxBox.label(text=dummyExtra) + + if not isDummy and self.cueStartFrame > self.cueEndFrame: + box.label( + text="Warning: Start Frame > End Frame!", + icon="ERROR", + ) + + split = box.split(factor=0.5) + split.prop(self, "cueStartFrame") + if not isDummy: + label = f"{labelPrefix} Cue (Action) ID:" + isPlayer = labelPrefix == "Player" + split.prop(self, "cueEndFrame") + + if isPlayer: + split = box.split(factor=0.5) + searchOp = split.operator(OOT_SearchPlayerCueIdEnumOperator.bl_idname, icon="VIEWZOOM", text=label) + searchOp.objName = objName + split.label(text=getEnumName(ootData.enumData.ootEnumCsPlayerCueId, self.playerCueID)) + + if not isPlayer or self.playerCueID == "Custom": + split = box.split(factor=0.5) + split.label(text=label) + split.prop(self, "cueActionID", text="") + + addCueOp = box.operator(CutsceneCmdAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp.isPlayer = isPlayer + + +class CutsceneCmdCameraShotProperty(PropertyGroup): + shotStartFrame: IntProperty(name="Start Frame", description="Shot start frame", default=0, min=0) + + # only used as a hint for users + shotEndFrame: IntProperty( + name="End Frame", + description="Shot end frame", + default=0, + min=-1, + get=lambda self: self.getEndFrame(), + ) + + shotCamMode: EnumProperty( + items=ootEnumCSMotionCamMode, + name="Mode", + description="Camera command mode", + default="splineEyeOrAT", + ) + + def getEndFrame(self, camShotObj: Object = None): + if camShotObj is None: + camShotObj = bpy.context.view_layer.objects.active + + if camShotObj.type == "ARMATURE": + boneFrameList: list[int] = [bone.ootCamShotPointProp.shotPointFrame for bone in camShotObj.data.bones] + # "fake" eye end frame + return self.shotStartFrame + max(2, sum(frame for frame in boneFrameList)) + 1 + return -1 + + def draw_props(self, layout: UILayout, label: str): + box = layout.box() + box.label(text=label) + split = box.split(factor=0.5) + split.prop(self, "shotStartFrame") + split.prop(self, "shotEndFrame") + box.row().prop(self, "shotCamMode", expand=True) + box.operator(CutsceneCmdAddBone.bl_idname) + + +class CutsceneCmdCameraShotPointProperty(PropertyGroup): + shotPointFrame: IntProperty( + name="Frame", + description="Key point frames value", + default=30, + min=0, + get=lambda self: self.getValue("frame"), + set=lambda self, value: self.setValue(value, "frame"), + ) + + shotPointViewAngle: FloatProperty( + name="FoV", + description="Field of view (degrees)", + default=60.0, + min=0.01, + max=179.99, + get=lambda self: self.getValue("viewAngle"), + set=lambda self, value: self.setValue(value, "viewAngle"), + ) + + shotPointRoll: IntProperty( + name="Roll", + description="Camera roll (degrees), positive turns image clockwise", + default=0, + min=-128, + max=127, + get=lambda self: self.getValue("roll"), + set=lambda self, value: self.setValue(value, "roll"), + ) + + # internal usage only + frame: IntProperty(default=30, min=0) + viewAngle: FloatProperty(default=60.0, min=0.01, max=179.99) + roll: IntProperty(min=-128, max=127, default=0) + + def getBoneFromEditBone(self, shotObject: Object, editBone: EditBone) -> Bone | None: + for bone in shotObject.data.bones: + if bone.name == editBone.name: + return bone + else: + print("Could not find corresponding bone") + return None + + def getValue(self, propName: str): + return getattr(self, propName) + + def setValue(self, value, propName: str): + setattr(self, propName, value) + + activeObj = bpy.context.view_layer.objects.active + if activeObj.mode == "EDIT": + bone = self.getBoneFromEditBone(activeObj, activeObj.data.edit_bones.active) + if bone is not None: + setattr(bone.ootCamShotPointProp, propName, value) + activeObj.data.bones.active = bone + + def draw_props(self, layout: UILayout): + box = layout.box() + box.label(text="Bone / Key point:") + row = box.row() + for propName in ["shotPointFrame", "shotPointViewAngle", "shotPointRoll"]: + row.prop(self, propName) + + +class OOTCutsceneMotionProperty(PropertyGroup): + actorCueListProp: PointerProperty(type=CutsceneCmdActorCueListProperty) + actorCueProp: PointerProperty(type=CutsceneCmdActorCueProperty) + + @staticmethod + def upgrade_object(csObj: Object): + print(f"Processing '{csObj.name}'...") + upgradeCutsceneMotion(csObj) + print("Done!") + + +classes = ( + CutsceneCmdActorCueListProperty, + CutsceneCmdActorCueProperty, + CutsceneCmdCameraShotProperty, + CutsceneCmdCameraShotPointProperty, + OOTCutsceneMotionProperty, +) + + +def csMotion_props_register(): + for cls in classes: + register_class(cls) + + Object.ootCSMotionProperty = PointerProperty(type=OOTCutsceneMotionProperty) + Armature.ootCamShotProp = PointerProperty(type=CutsceneCmdCameraShotProperty) + Bone.ootCamShotPointProp = PointerProperty(type=CutsceneCmdCameraShotPointProperty) + EditBone.ootCamShotPointProp = PointerProperty(type=CutsceneCmdCameraShotPointProperty) + + +def csMotion_props_unregister(): + del EditBone.ootCamShotPointProp + del Bone.ootCamShotPointProp + del Armature.ootCamShotProp + del Object.ootCSMotionProperty + + for cls in classes: + unregister_class(cls) diff --git a/fast64_internal/oot/cutscene/motion/utility.py b/fast64_internal/oot/cutscene/motion/utility.py new file mode 100644 index 000000000..fa8b7c06d --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/utility.py @@ -0,0 +1,297 @@ +import bpy + +from struct import unpack +from bpy.types import Object, Bone, Context, EditBone, Armature +from mathutils import Vector +from ....utility import yUpToZUp +from ...oot_utility import ootParseRotation + + +class BoneData: + def __init__(self, shotObj: Object, bone: Bone): + editBone = self.getEditBoneFromBone(shotObj, bone) if shotObj.mode == "EDIT" else None + self.name = bone.name + self.head = editBone.head if editBone is not None else bone.head + self.tail = editBone.tail if editBone is not None else bone.tail + + self.frame = ( + editBone["shotPointFrame"] + if editBone is not None and "shotPointFrame" in editBone + else bone.ootCamShotPointProp.shotPointFrame + ) + + self.viewAngle = ( + editBone["shotPointViewAngle"] + if editBone is not None and "shotPointViewAngle" in editBone + else bone.ootCamShotPointProp.shotPointViewAngle + ) + + self.roll = ( + editBone["shotPointRoll"] + if editBone is not None and "shotPointRoll" in editBone + else bone.ootCamShotPointProp.shotPointRoll + ) + + def getEditBoneFromBone(self, shotObj: Object, bone: Bone) -> EditBone | Bone: + for editBone in shotObj.data.edit_bones: + if editBone.name == bone.name: + return editBone + else: + print("Could not find corresponding bone") + return bone + + +def createNewBone(cameraShotObj: Object, name: str, headPos: list[float], tailPos: list[float]): + if bpy.context.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.mode_set(mode="EDIT") + armatureData: Armature = cameraShotObj.data + newEditBone = armatureData.edit_bones.new(name) + newEditBone.head = headPos + newEditBone.tail = tailPos + bpy.ops.object.mode_set(mode="OBJECT") + newBone = armatureData.bones[name] + newBone.ootCamShotPointProp.shotPointFrame = 30 + newBone.ootCamShotPointProp.shotPointViewAngle = 60.0 + newBone.ootCamShotPointProp.shotPointRoll = 0 + + +def createNewCameraShot(csObj: Object): + from ..classes import CutsceneObjectFactory # circular import fix + + index, csPrefix = getNameInformations(csObj, "Camera Shot", None) + + # create a basic armature + name = f"{csPrefix}.Camera Shot {index:02}" + newCameraShotObj = CutsceneObjectFactory().getNewArmatureObject(name, True, csObj) + + # add 4 bones since it's the minimum required + for i in range(1, 5): + posX = metersToBlend(bpy.context, float(i)) + createNewBone( + newCameraShotObj, + f"{csPrefix}.Camera Point {i:02}", + [posX, 0.0, 0.0], + [posX, metersToBlend(bpy.context, 1.0), 0.0], + ) + + +def getBlenderPosition(pos: list[int], scale: int): + """Returns the converted OoT position""" + + # OoT: +X right, +Y up, -Z forward + # Blender: +X right, +Z up, +Y forward + return [float(pos[0]) / scale, -float(pos[2]) / scale, float(pos[1]) / scale] + + +def getInteger(number: str): + """Returns an int number (handles properly negative hex numbers)""" + + if number.startswith("0x"): + number = number.removeprefix("0x") + + # ``"0" * (8 - len(number)`` adds the missing zeroes (if necessary) to have a 8 digit hex number + return unpack("!i", bytes.fromhex("0" * (8 - len(number)) + number))[0] + else: + return int(number) + + +def getRotation(data: str): + """Returns the rotation converted to hexadecimal""" + + if "DEG_TO_BINANG" in data or not "0x" in data: + angle = float(data.split("(")[1].removesuffix(")") if "DEG_TO_BINANG" in data else data) + binang = int(angle * (0x8000 / 180.0)) # from ``DEG_TO_BINANG()`` in decomp + + # if the angle value is higher than 0xFFFF it means we're at 360 degrees + return f"0x{0xFFFF if binang > 0xFFFF else binang:04X}" + else: + return data + + +def getBlenderRotation(rotation: list[str]): + """Returns the converted OoT rotation""" + + rot = [int(getRotation(r), base=16) for r in rotation] + return yUpToZUp @ Vector(ootParseRotation(rot)) + + +def getCSMotionValidateObj(csObj: Object, obj: Object, target: str): + """Checks if the given object belong to CS Motion and return it if true""" + + if csObj is not None and obj.parent != csObj: + return None + + if obj is None: + obj = bpy.context.view_layer.objects.active + + if target is None or target != "Camera Shot": + nonListTypes = ["CS Player Cue", "CS Actor Cue", "CS Dummy Cue", "Cutscene"] + listTypes = ["CS Player Cue List", "CS Actor Cue List"] + + if obj.type == "EMPTY" and obj.ootEmptyType in nonListTypes or obj.ootEmptyType in listTypes: + return obj + elif target is not None and target == "Camera Shot": + # get the camera shot + parentObj = obj.parent + if obj.type == "ARMATURE" and parentObj is not None and parentObj.type == "EMPTY": + return obj + + return None + + +def getNameInformations(csObj: Object, target: str, index: int): + idx = csPrefix = None + csNbr = 0 + + # get the last target objects names + if csObj.children is not None: + for i, obj in enumerate(csObj.children): + if obj.type == "EMPTY" and "Cue List" in obj.name or "Camera Shot" in obj.name or target in obj.name: + csPrefix = obj.name.split(".")[0] + if target in obj.name: + try: + idx = int(obj.name.split(" ")[-1]) + 1 + except ValueError: + pass # idx will stay None + + # saving the cutscene number if the target objects can't be found + if csPrefix is None: + for obj in bpy.data.objects: + if obj.type == "EMPTY" and obj.ootEmptyType == "Cutscene": + csNbr += 1 + + if obj.name == csObj.name: + break + + csPrefix = f"CS_{csNbr:02}" + + if idx is None: + idx = 1 + + if index is not None: + idx = index + + return idx, csPrefix + + +def metersToBlend(context: Context, value: float): + return value * 56.0 / context.scene.ootBlenderScale + + +def setupActorCuePreview(csObj: Object, actorOrPlayer: str, selectObject: bool, cueList: Object): + from ..classes import CutsceneObjectFactory # circular import fix + + # check if the cue actually moves, if not it's not necessary to create a preview object + isCueMoving = False + for i in range(len(cueList.children) - 1): + actorCue = cueList.children[i] + nextCue = cueList.children[i + 1] + curPos = [round(pos) for pos in actorCue.location] + nextPos = [round(pos) for pos in nextCue.location] + if curPos != nextPos: + isCueMoving = True + break + + if isCueMoving: + try: + index, csPrefix = getNameInformations(csObj, "Preview", int(cueList.name.split(" ")[-1])) + name = f"{csPrefix}.{actorOrPlayer} Cue Preview {index:02}" + except ValueError: + name = f"{cueList.name} - Preview" + + for obj in csObj.children: + if obj.name == name: + previewObj = obj + break + else: + previewObj = CutsceneObjectFactory().getNewActorCuePreviewObject(name, selectObject, csObj) + + actorHeight = 1.5 + if actorOrPlayer == "Player": + actorHeight = 1.7 if bpy.context.scene.ootPreviewSettingsProperty.previewPlayerAge == "link_adult" else 1.3 + + previewObj.empty_display_type = "SINGLE_ARROW" + previewObj.empty_display_size = metersToBlend(bpy.context, actorHeight) + setattr(previewObj.ootCSMotionProperty.actorCueListProp, f"{actorOrPlayer.lower()}CueListToPreview", cueList) + + return isCueMoving + + +def getCameraShotBoneData(shotObj: Object, runChecks: bool): + boneDataList: list[BoneData] = [] + for bone in shotObj.data.bones: + if bone.parent is not None: + print("Camera armature bones are not allowed to have parent bones") + return None + boneDataList.append(BoneData(shotObj, bone)) + boneDataList.sort(key=lambda b: b.name) + + if runChecks: + if boneDataList is None: + raise RuntimeError("Error in bone properties") + + if len(boneDataList) < 4: + raise RuntimeError(f"Only {len(boneDataList)} bones in `{shotObj.name}`") + + return boneDataList + + +def getCutsceneEndFrame(csObj: Object): + shotObjects: list[Object] = [] + for childObj in csObj.children: + obj = getCSMotionValidateObj(csObj, childObj, "Camera Shot") + if obj is not None: + shotObjects.append(obj) + shotObjects.sort(key=lambda obj: obj.name) + + csEndFrame = -1 + for shotObj in shotObjects: + # Seems to be the algorithm which was used in the canon tool: the at list + # counts the extra point (same frames as the last real point), and the pos + # list doesn't count the extra point but adds 1. Of course, neither of these + # values is actually the number of frames the camera motion lasts for. + boneDataList = getCameraShotBoneData(shotObj, True) + endFrame = shotObj.data.ootCamShotProp.shotStartFrame + max(2, sum(b.frame for b in boneDataList)) + 2 + csEndFrame = max(csEndFrame, endFrame) + return csEndFrame + + +def setupCutscene(csObj: Object): + from ..classes import CutsceneObjectFactory # circular import fix + + objFactory = CutsceneObjectFactory() + context = bpy.context + bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewCSObj = csObj + camObj = objFactory.getNewCameraObject( + f"{csObj.name}.Camera", + metersToBlend(context, 0.25), + metersToBlend(context, 1e-3), + metersToBlend(context, 200.0), + 0.95, + csObj, + ) + print("Created New Camera!") + + # Preview setup, used when importing cutscenes + for obj in csObj.children: + if obj.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]: + setupActorCuePreview(csObj, "Actor" if "Actor" in obj.ootEmptyType else "Player", False, obj) + + # Other setup + context.scene.frame_start = 0 + context.scene.frame_end = max(getCutsceneEndFrame(csObj), context.scene.frame_end) + context.scene.render.fps = 20 + context.scene.render.resolution_x = 320 + context.scene.render.resolution_y = 240 + context.scene.frame_set(context.scene.frame_start) + context.scene.camera = camObj + + +def getCutsceneCamera(csObj: Object) -> Object | None: + cameraObj = None + for childObj in csObj.children: + if childObj.type == "CAMERA": + cameraObj = childObj + break + return cameraObj diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index 530bd3251..bf02ff845 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -1,14 +1,19 @@ import os +import re +import bpy + from bpy.path import abspath from bpy.ops import object from bpy.props import StringProperty, EnumProperty, IntProperty -from bpy.types import Scene, Operator, Context, UILayout +from bpy.types import Scene, Operator, Context from bpy.utils import register_class, unregister_class from ...utility import CData, PluginError, writeCData, raisePluginError -from ..oot_utility import getCollection -from ..scene.exporter.to_c import ootCutsceneDataToC -from .exporter import convertCutsceneObject -from .constants import ootEnumCSTextboxType, ootEnumCSListType, ootEnumCSListTypeIcons +from ..oot_utility import getCollection, getCutsceneName +from ..oot_constants import ootData +from ..scene.exporter.to_c import getCutsceneC +from .constants import ootEnumCSTextboxType, ootEnumCSListType +from .importer import importCutsceneData +from .exporter import getNewCutsceneExport def checkGetFilePaths(context: Context): @@ -38,28 +43,70 @@ def ootCutsceneIncludes(headerfilename): return ret -def drawCSListAddOp(layout: UILayout, objName: str, collectionType): - def addButton(row): - nonlocal l - op = row.operator(OOTCSListAdd.bl_idname, text=ootEnumCSListType[l][1], icon=ootEnumCSListTypeIcons[l]) - op.collectionType = collectionType - op.listType = ootEnumCSListType[l][0] - op.objName = objName - l += 1 - - box = layout.column(align=True) - l = 0 - row = box.row(align=True) - row.label(text="Add:") - addButton(row) - for _ in range(3): - row = box.row(align=True) - for _ in range(3): - addButton(row) - box.label(text="Install zcamedit for camera/actor motion.") - - -class OOTCSTextboxAdd(Operator): +def insertCutsceneData(filePath: str, csName: str): + """Inserts the motion data in the cutscene and returns the new data""" + fileLines = [] + includes = ootCutsceneIncludes("").source.split("\n") + + # if the file is not found then it's likely a new file that needs to be created + try: + with open(filePath, "r") as inputFile: + fileLines = inputFile.readlines() + fileLines = fileLines[len(includes) - 1 :] + except FileNotFoundError: + fileLines = [] + + foundCutscene = False + motionExporter = getNewCutsceneExport(csName) + beginIndex = 0 + + for i, line in enumerate(fileLines): + # skip commented lines + if not line.startswith("//") and not line.startswith("/*"): + if f"CutsceneData {csName}" in line: + foundCutscene = True + + if foundCutscene: + if "CS_BEGIN_CUTSCENE" in line: + # save the index of the line that contains the entry total and the framecount for later use + beginIndex = i + + # looking at next line to see if we reached the end of the cs script + index = i + 1 + if index < len(fileLines) and "CS_END" in fileLines[index]: + # exporting first to get the new framecount and the total of entries values + fileLines.insert(index, motionExporter.getExportData()) + + # update framecount and entry total values + beginLine = fileLines[beginIndex] + reMatch = re.search(r"\b\(([0-9a-fA-F, ]*)\b", beginLine) + if reMatch is not None: + params = reMatch[1].split(", ") + entryTotal = int(params[0], base=0) + frameCount = int(params[1], base=0) + entries = re.sub( + r"\b\(([0-9a-fA-F]*)\b", f"({entryTotal + motionExporter.entryTotal}", beginLine + ) + frames = re.sub(r"\b([0-9a-fA-F]*)\)", f"{frameCount + motionExporter.frameCount})", beginLine) + fileLines[beginIndex] = f"{entries.split(', ')[0]}, {frames.split(', ')[1]}" + else: + raise PluginError("ERROR: Can't find `CS_BEGIN_CUTSCENE()` parameters!") + break + + fileData = CData() + + if not foundCutscene: + print(f"WARNING: Can't find Cutscene ``{csName}``, inserting data at the end of the file.") + motionExporter.addBeginEndCmds = True + csArrayName = f"CutsceneData {csName}[]" + fileLines.append("\n" + csArrayName + " = {\n" + motionExporter.getExportData() + "};\n") + fileData.header = f"{csArrayName};\n" + + fileData.source = "".join(line for line in fileLines) + return fileData + + +class OOTCSTextAdd(Operator): bl_idname = "object.oot_cstextbox_add" bl_label = "Add CS Textbox" bl_options = {"REGISTER", "UNDO"} @@ -92,6 +139,26 @@ def execute(self, context): return {"FINISHED"} +class OOT_ImportCutscene(Operator): + bl_idname = "object.oot_import_cutscenes" + bl_label = "Import All Cutscenes" + bl_options = {"REGISTER", "UNDO", "PRESET"} + + def execute(self, context): + try: + if context.mode != "OBJECT": + object.mode_set(mode="OBJECT") + + path = abspath(context.scene.ootCutsceneImportPath) + context.scene.ootCSNumber = importCutsceneData(path, None) + + self.report({"INFO"}, "Successfully imported cutscenes") + return {"FINISHED"} + except Exception as e: + raisePluginError(self, e) + return {"CANCELLED"} + + class OOT_ExportCutscene(Operator): bl_idname = "object.oot_export_cutscene" bl_label = "Export Cutscene" @@ -104,7 +171,7 @@ def execute(self, context): activeObj = context.view_layer.objects.active - if activeObj is None or activeObj.data is not None or activeObj.ootEmptyType != "Cutscene": + if activeObj is None or activeObj.type != "EMPTY" or activeObj.ootEmptyType != "Cutscene": raise PluginError("You must select a cutscene object") if activeObj.parent is not None: @@ -112,8 +179,11 @@ def execute(self, context): cpath, hpath, headerfilename = checkGetFilePaths(context) csdata = ootCutsceneIncludes(headerfilename) - converted = convertCutsceneObject(activeObj) - csdata.append(ootCutsceneDataToC(converted, converted.name)) + + if context.scene.exportMotionOnly: + csdata.append(insertCutsceneData(cpath, activeObj.name.removeprefix("Cutscene."))) + else: + csdata.append(getCutsceneC(getCutsceneName(activeObj))) writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported cutscene") @@ -132,19 +202,26 @@ def execute(self, context): try: if context.mode != "OBJECT": object.mode_set(mode="OBJECT") + cpath, hpath, headerfilename = checkGetFilePaths(context) csdata = ootCutsceneIncludes(headerfilename) count = 0 + for obj in context.view_layer.objects: - if obj.data is not None or obj.ootEmptyType != "Cutscene": - continue - if obj.parent is not None: - raise PluginError("Cutscene object must not be parented to anything") - converted = convertCutsceneObject(obj) - csdata.append(ootCutsceneDataToC(converted, converted.name)) - count += 1 + if obj.type == "EMPTY" and obj.ootEmptyType == "Cutscene": + if obj.parent is not None: + print(f"Parent: {obj.parent.name}, Object: {obj.name}") + raise PluginError("Cutscene object must not be parented to anything") + + if context.scene.exportMotionOnly: + raise PluginError("ERROR: Not implemented yet.") + else: + csdata.append(getCutsceneC(getCutsceneName(obj))) + count += 1 + if count == 0: raise PluginError("Could not find any cutscenes to export") + writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported " + str(count) + " cutscenes") return {"FINISHED"} @@ -153,11 +230,61 @@ def execute(self, context): return {"CANCELLED"} +class OOT_SearchCSDestinationEnumOperator(Operator): + bl_idname = "object.oot_search_cs_dest_enum_operator" + bl_label = "Choose Destination" + bl_property = "csDestination" + bl_options = {"REGISTER", "UNDO"} + + csDestination: EnumProperty(items=ootData.enumData.ootEnumCsDestination, default="cutscene_map_ganon_horse") + objName: StringProperty() + + def execute(self, context): + obj = bpy.data.objects[self.objName] + obj.ootCutsceneProperty.csDestination = self.csDestination + + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.csDestination) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + +class OOT_SearchCSSeqOperator(Operator): + bl_idname = "object.oot_search_cs_seq_enum_operator" + bl_label = "Search Music Sequence" + bl_property = "seqId" + bl_options = {"REGISTER", "UNDO"} + + seqId: EnumProperty(items=ootData.enumData.ootEnumSeqId, default="general_sfx") + itemIndex: IntProperty() + listType: StringProperty() + + def execute(self, context): + csProp = context.view_layer.objects.active.ootCutsceneProperty + for elem in csProp.csLists: + if elem.listType == self.listType: + elem.seqList[self.itemIndex].csSeqID = self.seqId + break + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.seqId) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + oot_cutscene_classes = ( - OOTCSTextboxAdd, + OOTCSTextAdd, OOTCSListAdd, + OOT_ImportCutscene, OOT_ExportCutscene, OOT_ExportAllCutscenes, + OOT_SearchCSDestinationEnumOperator, + OOT_SearchCSSeqOperator, ) @@ -166,10 +293,14 @@ def cutscene_ops_register(): register_class(cls) Scene.ootCutsceneExportPath = StringProperty(name="File", subtype="FILE_PATH") + Scene.ootCutsceneImportPath = StringProperty(name="File", subtype="FILE_PATH") + Scene.ootCSNumber = IntProperty(default=1, min=0) def cutscene_ops_unregister(): for cls in reversed(oot_cutscene_classes): unregister_class(cls) + del Scene.ootCSNumber + del Scene.ootCutsceneImportPath del Scene.ootCutsceneExportPath diff --git a/fast64_internal/oot/cutscene/panels.py b/fast64_internal/oot/cutscene/panels.py index f7a8323e7..76685144a 100644 --- a/fast64_internal/oot/cutscene/panels.py +++ b/fast64_internal/oot/cutscene/panels.py @@ -1,7 +1,17 @@ from bpy.utils import register_class, unregister_class +from bpy.types import Scene +from bpy.props import BoolProperty from ...utility import prop_split from ...panels import OOT_Panel -from .operators import OOT_ExportCutscene, OOT_ExportAllCutscenes +from .operators import OOT_ExportCutscene, OOT_ExportAllCutscenes, OOT_ImportCutscene + + +class OoT_PreviewSettingsPanel(OOT_Panel): + bl_idname = "OOT_PT_preview_settings" + bl_label = "OOT CS Preview Settings" + + def draw(self, context): + context.scene.ootPreviewSettingsProperty.draw_props(self.layout) class OOT_CutscenePanel(OOT_Panel): @@ -12,20 +22,69 @@ class OOT_CutscenePanel(OOT_Panel): bl_category = "OOT" def draw(self, context): - col = self.layout.column() - col.operator(OOT_ExportCutscene.bl_idname) + layout = self.layout + + exportBox = layout.box() + exportBox.label(text="Cutscene Exporter") + + col = exportBox.column() + if not context.scene.fast64.oot.hackerFeaturesEnabled: + col.prop(context.scene, "useDecompFeatures") + col.prop(context.scene, "exportMotionOnly") + + prop_split(exportBox, context.scene, "ootCutsceneExportPath", "Export To") + + activeObj = context.view_layer.objects.active + label = None + col = exportBox.column() + colcol = col.column() + if activeObj is None or activeObj.type != "EMPTY" or activeObj.ootEmptyType != "Cutscene": + label = "Select a cutscene object" + + if activeObj is not None and activeObj.parent is not None: + label = "Cutscene object must not be parented to anything" + + if label is not None: + col.label(text=label) + colcol.enabled = False + + colcol.operator(OOT_ExportCutscene.bl_idname) col.operator(OOT_ExportAllCutscenes.bl_idname) - prop_split(col, context.scene, "ootCutsceneExportPath", "File") + importBox = layout.box() + importBox.label(text="Cutscene Importer") + prop_split(importBox, context.scene, "ootCutsceneImportPath", "Import From") + + col = importBox.column() + col.operator(OOT_ImportCutscene.bl_idname) -oot_cutscene_panel_classes = (OOT_CutscenePanel,) + +oot_cutscene_panel_classes = ( + OoT_PreviewSettingsPanel, + OOT_CutscenePanel, +) def cutscene_panels_register(): + Scene.useDecompFeatures = BoolProperty( + name="Use Decomp for Export", description="Use names and macros from decomp when exporting", default=True + ) + + Scene.exportMotionOnly = BoolProperty( + name="Export Motion Data Only", + description=( + "Export everything or only the camera and actor motion data.\n" + + "This will insert the data into the cutscene." + ), + ) + for cls in oot_cutscene_panel_classes: register_class(cls) def cutscene_panels_unregister(): + del Scene.exportMotionOnly + del Scene.useDecompFeatures + for cls in oot_cutscene_panel_classes: unregister_class(cls) diff --git a/fast64_internal/oot/cutscene/preview.py b/fast64_internal/oot/cutscene/preview.py new file mode 100644 index 000000000..9d3a28830 --- /dev/null +++ b/fast64_internal/oot/cutscene/preview.py @@ -0,0 +1,292 @@ +import bpy + +from math import isclose +from bpy.types import Scene, Object, Node +from bpy.app.handlers import persistent +from ...utility import gammaInverse, hexOrDecInt +from .motion.utility import getCutsceneCamera + + +def getLerp(max: float, min: float, val: float): + # from ``Environment_LerpWeight()`` in decomp + diff = max - min + ret = None + + if diff != 0.0: + ret = 1.0 - (max - val) / diff + + if not ret >= 1.0: + return ret + + return 1.0 + + +def getColor(value: float) -> float: + """Returns the value converted in the linear color space""" + return gammaInverse([value / 0xFF, 0.0, 0.0])[0] + + +def getNode(node: Node, type: str, name: str, location: tuple[float, float]): + """Returns a compositor node""" + if node is None: + node = bpy.context.scene.node_tree.nodes.new(type) + node.select = False + node.name = node.label = name + node.location = location + return node + + +def setupCompositorNodes(): + """Creates or re-setups compositor nodes""" + if bpy.app.version < (3, 6, 0): + # Blender 3.6+ is required in order to use Compositor Nodes + return + + if not bpy.context.scene.use_nodes: + bpy.context.scene.use_nodes = True + + # sets the compositor render mode to "Camera" + space = None + for area in bpy.context.screen.areas: + if (area != bpy.context.area) and (area.type == "VIEW_3D"): + for space in area.spaces: + if space.type == "VIEW_3D": + break + if space is not None and space.shading.use_compositor != "CAMERA": + space.shading.use_compositor = "CAMERA" + + # if everything's fine and nodes are already ready to use stop there + if bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewNodesReady: + return + + # get the existing nodes + nodeTree = bpy.context.scene.node_tree + nodeRenderLayer = nodeComposite = nodeRGBTrans = nodeAlphaOver = nodeRGBMisc = nodeMixRGBMisc = None + for node in nodeTree.nodes.values(): + if node.type == "R_LAYERS": + nodeRenderLayer = node + if node.type == "COMPOSITE": + nodeComposite = node + if node.label == "CSTrans_RGB": + nodeRGBTrans = node + if node.label == "CSMisc_RGB": + nodeRGBMisc = node + if node.label == "CSPreview_AlphaOver": + nodeAlphaOver = node + if node.label == "CSMisc_MixRGB": + nodeMixRGBMisc = node + + # create or set the data of each nodes + nodeRenderLayer = getNode(nodeRenderLayer, "CompositorNodeRLayers", "CSPreview_RenderLayer", (-500, 0)) + nodeRGBMisc = getNode(nodeRGBMisc, "CompositorNodeRGB", "CSMisc_RGB", (-200, -200)) + nodeRGBTrans = getNode(nodeRGBTrans, "CompositorNodeRGB", "CSTrans_RGB", (0, -200)) + nodeMixRGBMisc = getNode(nodeMixRGBMisc, "CompositorNodeMixRGB", "CSMisc_MixRGB", (0, 0)) + nodeAlphaOver = getNode(nodeAlphaOver, "CompositorNodeAlphaOver", "CSPreview_AlphaOver", (200, 0)) + nodeComposite = getNode(nodeComposite, "CompositorNodeComposite", "CSPreview_Composite", (400, 0)) + + # link the nodes together + nodeTree.links.new(nodeMixRGBMisc.inputs[1], nodeRenderLayer.outputs[0]) + nodeTree.links.new(nodeMixRGBMisc.inputs[2], nodeRGBMisc.outputs[0]) + nodeTree.links.new(nodeAlphaOver.inputs[1], nodeMixRGBMisc.outputs[0]) + nodeTree.links.new(nodeAlphaOver.inputs[2], nodeRGBTrans.outputs[0]) + nodeTree.links.new(nodeComposite.inputs[0], nodeAlphaOver.outputs[0]) + + # misc settings + nodeMixRGBMisc.use_alpha = True + nodeMixRGBMisc.blend_type = "COLOR" + bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewNodesReady = True + + +def initFirstFrame(csObj: Object, useNodeFeatures: bool, defaultCam: Object): + # set default values for frame 0 + if useNodeFeatures: + color = [0.0, 0.0, 0.0, 0.0] + bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color + bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color + csObj.ootCutsceneProperty.preview.trigger = False + csObj.ootCutsceneProperty.preview.isFixedCamSet = False + if defaultCam is not None: + bpy.context.scene.camera = defaultCam + + +def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, cameraObjects: Object): + """Execute the actions of each command to create the preview for the current frame""" + # this function was partially adapted from ``z_demo.c`` + + if curFrame == 0: + initFirstFrame(csObj, useNodeFeatures, cameraObjects[1]) + + if useNodeFeatures: + previewProp = csObj.ootCutsceneProperty.preview + for transitionCmd in csObj.ootCutsceneProperty.preview.transitionList: + startFrame = transitionCmd.startFrame + endFrame = transitionCmd.endFrame + frameCur = curFrame + isTriggerInstance = transitionCmd.type == "trigger_instance" + linear160 = getColor(160.0) + + if transitionCmd.type == "Unknown": + print("ERROR: Unknown command!") + + if curFrame == 0: + # makes transitions appear a frame earlier if frame 0 + frameCur += 1 + + if isTriggerInstance and not previewProp.trigger: + color = [linear160, linear160, linear160, 1.0] + bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color + + if frameCur >= startFrame and frameCur <= endFrame: + color = [0.0, 0.0, 0.0, 0.0] + lerp = getLerp(endFrame, startFrame, frameCur) + linear255 = getColor(255.0) + linear155 = getColor(155.0) + + if isTriggerInstance: + previewProp.trigger = True + + if transitionCmd.type.endswith("in"): + alpha = linear255 * lerp + else: + alpha = (1.0 - lerp) * linear255 + + if "half" in transitionCmd.type: + if "_in_" in transitionCmd.type: + alpha = linear255 - ((1.0 - lerp) * linear155) + else: + alpha = linear255 - (linear155 * lerp) + + if "gray_" in transitionCmd.type or previewProp.trigger: + color[0] = color[1] = color[2] = linear160 * alpha + elif "red_" in transitionCmd.type: + color[0] = linear255 * alpha + elif "green_" in transitionCmd.type: + color[1] = linear255 * alpha + elif "blue_" in transitionCmd.type: + color[2] = linear255 * alpha + + color[3] = alpha + bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color + + for miscCmd in csObj.ootCutsceneProperty.preview.miscList: + startFrame = miscCmd.startFrame + endFrame = miscCmd.endFrame + + if miscCmd.type == "Unknown": + print("ERROR: Unknown command!") + + if curFrame == startFrame: + if miscCmd.type == "set_locked_viewpoint" and not None in cameraObjects: + bpy.context.scene.camera = cameraObjects[int(csObj.ootCutsceneProperty.preview.isFixedCamSet)] + csObj.ootCutsceneProperty.preview.isFixedCamSet ^= True + + elif miscCmd.type == "stop_cutscene": + # stop the playback and set the frame to 0 + bpy.ops.screen.animation_cancel() + bpy.context.scene.frame_set(bpy.context.scene.frame_start) + + if curFrame >= startFrame and (curFrame < endFrame or endFrame == startFrame): + if useNodeFeatures: + color = [0.0, 0.0, 0.0, 0.0] + lerp = getLerp(endFrame - 1, startFrame, curFrame) + + if miscCmd.type in ["vismono_sepia", "vismono_black_and_white"]: + if miscCmd.type == "vismono_sepia": + col = [255.0, 180.0, 100.0] + else: + col = [255.0, 255.0, 254.0] + + for i in range(3): + color[i] = getColor(col[i]) + + color[3] = getColor(255.0) * lerp + bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color + + elif miscCmd.type == "red_pulsating_lights": + color = bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value + color[0] = getColor(255.0) + color[1] = color[2] = 0.0 + step = 0.05 + if curFrame & 8: + if color[3] < 0.20: + color[3] += step + else: + if color[3] > 0.05: + color[3] -= step + bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color + + +@persistent +def cutscenePreviewFrameHandler(scene: Scene): + """Preview frame handler, executes each frame when the cutscene is played""" + previewSettings = scene.ootPreviewSettingsProperty + csObj: Object = previewSettings.ootCSPreviewCSObj + + if csObj is None or not csObj.type == "EMPTY" and not csObj.ootEmptyType == "Cutscene": + return + + # populate ``cameraObjects`` with the cutscene camera and the first found prerend fixed camera + cameraObjects = [None, getCutsceneCamera(csObj)] + + foundObj = None + for obj in bpy.data.objects: + if obj.type == "CAMERA" and obj.parent is not None and obj.parent.ootEmptyType in ["Scene", "Room"]: + camPosProp = obj.ootCameraPositionProperty + camTypes = ["CAM_SET_PREREND0", "CAM_SET_PREREND_FIXED"] + if camPosProp.camSType != "Custom" and camPosProp.camSType in camTypes: + foundObj = obj + break + elif camPosProp.camSType == "Custom": + if camPosProp.camSTypeCustom.startswith("0x"): + if hexOrDecInt(camPosProp.camSTypeCustom) == 25: + foundObj = obj + break + elif camPosProp.camSTypeCustom in camTypes: + foundObj = obj + break + + if foundObj is not None: + cameraObjects[0] = foundObj + + # setup nodes + previewSettings.ootCSPreviewNodesReady = False + setupCompositorNodes() + previewProp = csObj.ootCutsceneProperty.preview + + # set preview properties + previewProp.miscList.clear() + previewProp.transitionList.clear() + for item in csObj.ootCutsceneProperty.csLists: + if item.listType == "Transition": + newProp = previewProp.transitionList.add() + newProp.startFrame = item.transitionStartFrame + newProp.endFrame = item.transitionEndFrame + newProp.type = item.transitionType + elif item.listType == "MiscList": + for miscEntry in item.miscList: + newProp = previewProp.miscList.add() + newProp.startFrame = miscEntry.startFrame + newProp.endFrame = miscEntry.endFrame + newProp.type = miscEntry.csMiscType + + # execute the main preview logic + curFrame = bpy.context.scene.frame_current + if isclose(curFrame, previewProp.prevFrame, abs_tol=1) and isclose(curFrame, previewProp.nextFrame, abs_tol=1): + processCurrentFrame(csObj, curFrame, previewSettings.ootCSPreviewNodesReady, cameraObjects) + else: + # Simulate cutscene for all frames up to present + for i in range(bpy.context.scene.frame_current): + processCurrentFrame(csObj, i, previewSettings.ootCSPreviewNodesReady, cameraObjects) + + # since we reached the end of the function, the current frame becomes the previous one + previewProp.nextFrame = curFrame + 2 if curFrame > previewProp.prevFrame else curFrame - 2 + previewProp.prevFrame = curFrame + + +def cutscene_preview_register(): + bpy.app.handlers.frame_change_pre.append(cutscenePreviewFrameHandler) + + +def cutscene_preview_unregister(): + if cutscenePreviewFrameHandler in bpy.app.handlers.frame_change_pre: + bpy.app.handlers.frame_change_pre.remove(cutscenePreviewFrameHandler) diff --git a/fast64_internal/oot/cutscene/properties.py b/fast64_internal/oot/cutscene/properties.py index ae6d25ac7..f2f6c3c58 100644 --- a/fast64_internal/oot/cutscene/properties.py +++ b/fast64_internal/oot/cutscene/properties.py @@ -1,25 +1,39 @@ -from bpy.types import PropertyGroup, Object, UILayout +from bpy.types import PropertyGroup, Object, UILayout, Scene, Context from bpy.props import StringProperty, EnumProperty, IntProperty, BoolProperty, CollectionProperty, PointerProperty from bpy.utils import register_class, unregister_class from ...utility import PluginError, prop_split -from ..oot_utility import OOTCollectionAdd, drawCollectionOps -from .operators import OOTCSTextboxAdd, drawCSListAddOp -from .constants import ootEnumCSTextboxType, ootEnumCSListType, ootEnumCSTransitionType, ootEnumCSTextboxTypeIcons +from ..oot_utility import OOTCollectionAdd, drawCollectionOps, getEnumName +from ..oot_constants import ootData +from ..oot_upgrade import upgradeCutsceneSubProps, upgradeCSListProps, upgradeCutsceneProperty +from .operators import OOTCSTextAdd, OOT_SearchCSDestinationEnumOperator, OOTCSListAdd, OOT_SearchCSSeqOperator +from .motion.preview import previewFrameHandler +from .motion.utility import getCutsceneCamera + +from .motion.operators import ( + CutsceneCmdPlayPreview, + CutsceneCmdCreateCameraShot, + CutsceneCmdCreatePlayerCueList, + CutsceneCmdCreateActorCueList, +) + +from .constants import ( + ootEnumCSTextboxType, + ootEnumCSListType, + ootEnumCSTextboxTypeIcons, + ootCSSubPropToName, + csListTypeToIcon, +) -# Perhaps this should have been called something like OOTCSParentPropertyType, -# but now it needs to keep the same name to not break existing scenes which use -# the cutscene system. -class OOTCSProperty: - propName = None +class OOTCutsceneCommon: attrName = None subprops = ["startFrame", "endFrame"] expandTab: BoolProperty(default=True) startFrame: IntProperty(name="", default=0, min=0) - endFrame: IntProperty(name="", default=1, min=0) + endFrame: IntProperty(name="", default=0, min=0) def getName(self): - return self.propName + pass def filterProp(self, name, listProp): return True @@ -27,229 +41,401 @@ def filterProp(self, name, listProp): def filterName(self, name, listProp): return name - def draw(self, layout, listProp, listIndex, cmdIndex, objName, collectionType): - layout.prop( + def draw_props( + self, + layout: UILayout, + listProp: "OOTCSListProperty", + listIndex: int, + cmdIndex: int, + objName: str, + collectionType: str, + tabName: str, + ): + # Draws list elements + box = layout.box().column() + + box.prop( self, "expandTab", - text=self.getName() + " " + str(cmdIndex), + text=f"{tabName if tabName != 'Text' else self.getName()} No. {cmdIndex}", icon="TRIA_DOWN" if self.expandTab else "TRIA_RIGHT", ) if not self.expandTab: return - box = layout.box().column() + drawCollectionOps(box, cmdIndex, collectionType + "." + self.attrName, listIndex, objName) + for p in self.subprops: if self.filterProp(p, listProp): - prop_split(box, self, p, self.filterName(p, listProp)) + name = self.filterName(p, listProp) + displayName = ootCSSubPropToName[name] + + if name == "csSeqPlayer": + # change the property name to draw the other enum for fade seq command + p = name + + prop_split(box, self, p, displayName) + + if name == "csSeqID": + seqOp = box.operator(OOT_SearchCSSeqOperator.bl_idname) + seqOp.itemIndex = cmdIndex + seqOp.listType = listProp.listType + customValues = [ + "csMiscType", + "csTextType", + "ocarinaAction", + "csSeqID", + "csSeqPlayer", + ] + value = getattr(self, p) + if name in customValues and value == "Custom": + prop_split(box, self, f"{name}Custom", f"{displayName} Custom") -class OOTCSTextboxProperty(OOTCSProperty, PropertyGroup): - propName = "Textbox" - attrName = "textbox" + if name == "csTextType" and value != "choice": + break + + +class OOTCSTextProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "textList" subprops = [ - "messageId", - "ocarinaSongAction", + "textID", + "ocarinaAction", "startFrame", "endFrame", - "type", - "topOptionBranch", - "bottomOptionBranch", + "csTextType", + "topOptionTextID", + "bottomOptionTextID", "ocarinaMessageId", ] textboxType: EnumProperty(items=ootEnumCSTextboxType) - messageId: StringProperty(name="", default="0x0000") - ocarinaSongAction: StringProperty(name="", default="0x0000") - type: StringProperty(name="", default="0x0000") - topOptionBranch: StringProperty(name="", default="0x0000") - bottomOptionBranch: StringProperty(name="", default="0x0000") + + # subprops + textID: StringProperty(name="", default="0x0000") + ocarinaAction: EnumProperty( + name="Ocarina Action", items=ootData.enumData.ootEnumOcarinaSongActionId, default="teach_minuet" + ) + ocarinaActionCustom: StringProperty(default="OCARINA_ACTION_CUSTOM") + topOptionTextID: StringProperty(name="", default="0x0000") + bottomOptionTextID: StringProperty(name="", default="0x0000") ocarinaMessageId: StringProperty(name="", default="0x0000") + csTextType: EnumProperty(name="Text Type", items=ootData.enumData.ootEnumCsTextType, default="normal") + csTextTypeCustom: StringProperty(default="CS_TEXT_CUSTOM") def getName(self): - return self.textboxType + return getEnumName(ootEnumCSTextboxType, self.textboxType) def filterProp(self, name, listProp): if self.textboxType == "Text": - return name not in ["ocarinaSongAction", "ocarinaMessageId"] + return name not in ["ocarinaAction", "ocarinaMessageId"] elif self.textboxType == "None": return name in ["startFrame", "endFrame"] - elif self.textboxType == "LearnSong": - return name in ["ocarinaSongAction", "startFrame", "endFrame", "ocarinaMessageId"] + elif self.textboxType == "OcarinaAction": + return name in ["ocarinaAction", "startFrame", "endFrame", "ocarinaMessageId"] else: - raise PluginError("Invalid property name for OOTCSTextboxProperty") + raise PluginError("Invalid property name for OOTCSTextProperty") -class OOTCSLightingProperty(OOTCSProperty, PropertyGroup): - propName = "Lighting" - attrName = "lighting" - subprops = ["index", "startFrame"] - index: IntProperty(name="", default=1, min=1) +class OOTCSLightSettingsProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "lightSettingsList" + subprops = ["lightSettingsIndex", "startFrame"] + lightSettingsIndex: IntProperty(name="", default=0, min=0) -class OOTCSTimeProperty(OOTCSProperty, PropertyGroup): - propName = "Time" - attrName = "time" +class OOTCSTimeProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "timeList" subprops = ["startFrame", "hour", "minute"] hour: IntProperty(name="", default=23, min=0, max=23) minute: IntProperty(name="", default=59, min=0, max=59) -class OOTCSBGMProperty(OOTCSProperty, PropertyGroup): - propName = "BGM" - attrName = "bgm" - subprops = ["value", "startFrame", "endFrame"] - value: StringProperty(name="", default="0x0000") +class OOTCSSeqProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "seqList" + subprops = ["csSeqID", "startFrame", "endFrame"] + csSeqID: EnumProperty(name="Seq ID", items=ootData.enumData.ootEnumSeqId, default="general_sfx") + csSeqIDCustom: StringProperty(default="NA_BGM_CUSTOM") + csSeqPlayer: EnumProperty( + name="Seq Player", items=ootData.enumData.ootEnumCsFadeOutSeqPlayer, default="fade_out_fanfare" + ) + csSeqPlayerCustom: StringProperty(default="CS_FADE_OUT_CUSTOM") def filterProp(self, name, listProp): - return name != "endFrame" or listProp.listType == "FadeBGM" + return name != "endFrame" or listProp.listType == "FadeOutSeqList" def filterName(self, name, listProp): - if name == "value": - return "Fade Type" if listProp.listType == "FadeBGM" else "Sequence" + if name == "csSeqID" and listProp.listType == "FadeOutSeqList": + return "csSeqPlayer" return name -class OOTCSMiscProperty(OOTCSProperty, PropertyGroup): - propName = "Misc" - attrName = "misc" - subprops = ["operation", "startFrame", "endFrame"] - operation: IntProperty(name="", default=1, min=1, max=35) - - -class OOTCS0x09Property(OOTCSProperty, PropertyGroup): - propName = "0x09" - attrName = "nine" - subprops = ["startFrame", "unk2", "unk3", "unk4"] - unk2: StringProperty(name="", default="0x00") - unk3: StringProperty(name="", default="0x00") - unk4: StringProperty(name="", default="0x00") - - -class OOTCSUnkProperty(OOTCSProperty, PropertyGroup): - propName = "Unk" - attrName = "unk" - subprops = ["unk1", "unk2", "unk3", "unk4", "unk5", "unk6", "unk7", "unk8", "unk9", "unk10", "unk11", "unk12"] - unk1: StringProperty(name="", default="0x00000000") - unk2: StringProperty(name="", default="0x00000000") - unk3: StringProperty(name="", default="0x00000000") - unk4: StringProperty(name="", default="0x00000000") - unk5: StringProperty(name="", default="0x00000000") - unk6: StringProperty(name="", default="0x00000000") - unk7: StringProperty(name="", default="0x00000000") - unk8: StringProperty(name="", default="0x00000000") - unk9: StringProperty(name="", default="0x00000000") - unk10: StringProperty(name="", default="0x00000000") - unk11: StringProperty(name="", default="0x00000000") - unk12: StringProperty(name="", default="0x00000000") +class OOTCSMiscProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "miscList" + subprops = ["csMiscType", "startFrame", "endFrame"] + csMiscType: EnumProperty(name="Type", items=ootData.enumData.ootEnumCsMiscType, default="rain") + csMiscTypeCustom: StringProperty(default="CS_MISC_CUSTOM") + + +class OOTCSRumbleProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "rumbleList" + subprops = ["startFrame", "rumbleSourceStrength", "rumbleDuration", "rumbleDecreaseRate"] + + # those variables are unsigned chars in decomp + # see https://github.com/zeldaret/oot/blob/542012efa68d110d6b631f9d149f6e5f4e68cc8e/src/code/z_rumble.c#L58-L77 + rumbleSourceStrength: IntProperty(name="", default=0, min=0, max=255) + rumbleDuration: IntProperty(name="", default=0, min=0, max=255) + rumbleDecreaseRate: IntProperty(name="", default=0, min=0, max=255) class OOTCSListProperty(PropertyGroup): expandTab: BoolProperty(default=True) listType: EnumProperty(items=ootEnumCSListType) - textbox: CollectionProperty(type=OOTCSTextboxProperty) - lighting: CollectionProperty(type=OOTCSLightingProperty) - time: CollectionProperty(type=OOTCSTimeProperty) - bgm: CollectionProperty(type=OOTCSBGMProperty) - misc: CollectionProperty(type=OOTCSMiscProperty) - nine: CollectionProperty(type=OOTCS0x09Property) - unk: CollectionProperty(type=OOTCSUnkProperty) - - unkType: StringProperty(name="", default="0x0001") - fxType: EnumProperty(items=ootEnumCSTransitionType) - fxStartFrame: IntProperty(name="", default=0, min=0) - fxEndFrame: IntProperty(name="", default=1, min=0) + textList: CollectionProperty(type=OOTCSTextProperty) + lightSettingsList: CollectionProperty(type=OOTCSLightSettingsProperty) + timeList: CollectionProperty(type=OOTCSTimeProperty) + seqList: CollectionProperty(type=OOTCSSeqProperty) + miscList: CollectionProperty(type=OOTCSMiscProperty) + rumbleList: CollectionProperty(type=OOTCSRumbleProperty) + + transitionType: EnumProperty(items=ootData.enumData.ootEnumCsTransitionType, default="gray_fill_in") + transitionTypeCustom: StringProperty(default="CS_TRANS_CUSTOM") + transitionStartFrame: IntProperty(name="", default=0, min=0) + transitionEndFrame: IntProperty(name="", default=1, min=0) def draw_props(self, layout: UILayout, listIndex: int, objName: str, collectionType: str): - layout.prop( + box = layout.box().column() + enumName = getEnumName(ootEnumCSListType, self.listType) + + # Draw current command tab + box.prop( self, "expandTab", - text=self.listType + " List" if self.listType != "FX" else "Scene Trans FX", + text=enumName, icon="TRIA_DOWN" if self.expandTab else "TRIA_RIGHT", ) + if not self.expandTab: return - box = layout.box().column() + drawCollectionOps(box, listIndex, collectionType, None, objName, False) - if self.listType == "Textbox": - attrName = "textbox" - elif self.listType == "FX": - prop_split(box, self, "fxType", "Transition") - prop_split(box, self, "fxStartFrame", "Start Frame") - prop_split(box, self, "fxEndFrame", "End Frame") + # Draw current command content + if self.listType == "TextList": + attrName = "textList" + elif self.listType == "Transition": + prop_split(box, self, "transitionType", "Transition Type") + if self.transitionType == "Custom": + prop_split(box, self, "transitionTypeCustom", "Transition Type Custom") + + prop_split(box, self, "transitionStartFrame", "Start Frame") + prop_split(box, self, "transitionEndFrame", "End Frame") return - elif self.listType == "Lighting": - attrName = "lighting" - elif self.listType == "Time": - attrName = "time" - elif self.listType in ["PlayBGM", "StopBGM", "FadeBGM"]: - attrName = "bgm" - elif self.listType == "Misc": - attrName = "misc" - elif self.listType == "0x09": - attrName = "nine" - elif self.listType == "Unk": - prop_split(box, self, "unkType", "Unk List Type") - attrName = "unk" + elif self.listType == "LightSettingsList": + attrName = "lightSettingsList" + elif self.listType == "TimeList": + attrName = "timeList" + elif self.listType in ["StartSeqList", "StopSeqList", "FadeOutSeqList"]: + attrName = "seqList" + elif self.listType == "MiscList": + attrName = "miscList" + elif self.listType == "RumbleList": + attrName = "rumbleList" else: raise PluginError("Internal error: invalid listType " + self.listType) - dat = getattr(self, attrName) - for i, p in enumerate(dat): - p.draw(box, self, listIndex, i, objName, collectionType) - if len(dat) == 0: - box.label(text="No items in " + self.listType + " List.") - if self.listType == "Textbox": - row = box.row(align=True) + data = getattr(self, attrName) + + if self.listType == "TextList": + subBox = box.box() + subBox.label(text="TextBox Commands") + row = subBox.row(align=True) + for l in range(3): addOp = row.operator( - OOTCSTextboxAdd.bl_idname, + OOTCSTextAdd.bl_idname, text="Add " + ootEnumCSTextboxType[l][1], icon=ootEnumCSTextboxTypeIcons[l], ) - addOp.collectionType = collectionType + ".textbox" + + addOp.collectionType = collectionType + ".textList" addOp.textboxType = ootEnumCSTextboxType[l][0] addOp.listIndex = listIndex addOp.objName = objName else: - addOp = box.operator(OOTCollectionAdd.bl_idname, text="Add item to " + self.listType + " List") - addOp.option = len(dat) + addOp = box.operator( + OOTCollectionAdd.bl_idname, text="Add item to " + getEnumName(ootEnumCSListType, self.listType) + ) + addOp.option = len(data) addOp.collectionType = collectionType + "." + attrName addOp.subIndex = listIndex addOp.objName = objName + for i, p in enumerate(data): + # ``p`` type: + # OOTCSTextProperty | OOTCSLightSettingsProperty | OOTCSTimeProperty | + # OOTCSSeqProperty | OOTCSMiscProperty | OOTCSRumbleProperty + p.draw_props(box, self, listIndex, i, objName, collectionType, enumName.removesuffix(" List")) + + if len(data) == 0: + box.label(text="No items in " + getEnumName(ootEnumCSListType, self.listType)) + + +class OOTCutsceneCommandBase: + startFrame: IntProperty(min=0) + endFrame: IntProperty(min=0) + + +class OOTCutsceneTransitionProperty(OOTCutsceneCommandBase, PropertyGroup): + type: StringProperty(default="Unknown") + + +class OOTCutsceneMiscProperty(OOTCutsceneCommandBase, PropertyGroup): + type: StringProperty(default="Unknown") + + +class OOTCutscenePreviewProperty(PropertyGroup): + transitionList: CollectionProperty(type=OOTCutsceneTransitionProperty) + miscList: CollectionProperty(type=OOTCutsceneMiscProperty) + + trigger: BoolProperty(default=False) # for ``CS_TRANS_TRIGGER_INSTANCE`` + isFixedCamSet: BoolProperty(default=False) + prevFrame: IntProperty(default=-1) + nextFrame: IntProperty(default=1) + + +class OOTCutscenePreviewSettingsProperty(PropertyGroup): + useWidescreen: BoolProperty( + name="Use Widescreen Camera", default=False, update=lambda self, context: self.updateWidescreen(context) + ) + + useOpaqueCamBg: BoolProperty( + name="Use Opaque Camera Background", + description="Can be used to simulate the letterbox with widescreen mode enabled", + default=False, + update=lambda self, context: self.updateCamBackground(context), + ) + + previewPlayerAge: EnumProperty( + items=[("link_adult", "Adult", "Adult Link (170 cm)", 0), ("link_child", "Child", "Child Link (130 cm)", 1)], + name="Player Age for Preview", + description="For setting Link's height for preview", + default="link_adult", + ) + + # internal only + ootCSPreviewNodesReady: BoolProperty(default=False) + ootCSPreviewCSObj: PointerProperty(type=Object) + + def updateWidescreen(self, context: Context): + if self.useWidescreen: + context.scene.render.resolution_x = 426 + else: + context.scene.render.resolution_x = 320 + context.scene.render.resolution_y = 240 + + # force a refresh of the current frame + previewFrameHandler(context.scene) + + def updateCamBackground(self, context: Context): + camObj = getCutsceneCamera(context.view_layer.objects.active) + if camObj is not None: + if self.useOpaqueCamBg: + camObj.data.passepartout_alpha = 1.0 + else: + camObj.data.passepartout_alpha = 0.95 + + def draw_props(self, layout: UILayout): + previewBox = layout.box() + previewBox.box().label(text="Preview Settings") + prop_split(previewBox, self, "previewPlayerAge", "Player Age for Preview") + previewBox.prop(self, "useWidescreen") + previewBox.prop(self, "useOpaqueCamBg") + class OOTCutsceneProperty(PropertyGroup): csEndFrame: IntProperty(name="End Frame", min=0, default=100) - csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)") - csTermIdx: IntProperty(name="Index", min=0) - csTermStart: IntProperty(name="Start Frm", min=0, default=99) - csTermEnd: IntProperty(name="End Frm", min=0, default=100) + csUseDestination: BoolProperty(name="Cutscene Destination (Scene Change)") + csDestination: EnumProperty( + name="Destination", items=ootData.enumData.ootEnumCsDestination, default="cutscene_map_ganon_horse" + ) + csDestinationCustom: StringProperty(default="CS_DEST_CUSTOM") + csDestinationStartFrame: IntProperty(name="Start Frame", min=0, default=99) csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists") + menuTab: EnumProperty(items=ootEnumCSListType) - def draw_props(self, layout: UILayout, obj: Object): - layout.prop(self, "csEndFrame") - layout.prop(self, "csWriteTerminator") - if self.csWriteTerminator: - r = layout.row() - r.prop(self, "csTermIdx") - r.prop(self, "csTermStart") - r.prop(self, "csTermEnd") - for i, p in enumerate(self.csLists): - p.draw_props(layout, i, obj.name, "Cutscene") + preview: PointerProperty(type=OOTCutscenePreviewProperty) - drawCSListAddOp(layout, obj.name, "Cutscene") + @staticmethod + def upgrade_object(obj): + print(f"Processing '{obj.name}'...") + + # using the new names since the old ones will be deleted before this is used + csListsNames = ["textList", "lightSettingsList", "timeList", "seqList", "miscList", "rumbleList"] + + csProp: "OOTCutsceneProperty" = obj.ootCutsceneProperty + upgradeCutsceneProperty(csProp) + + for csListProp in csProp.csLists: + upgradeCSListProps(csListProp) + + for listName in csListsNames: + for csListSubProp in getattr(csListProp, listName): + upgradeCutsceneSubProps(csListSubProp) + + def draw_props(self, layout: UILayout, obj: Object): + split = layout.split(factor=0.5) + split.operator(CutsceneCmdCreateCameraShot.bl_idname, icon="VIEW_CAMERA") + split.operator(CutsceneCmdPlayPreview.bl_idname, icon="RESTRICT_VIEW_OFF") + + split = layout.split(factor=0.5) + split.operator(CutsceneCmdCreatePlayerCueList.bl_idname) + split.operator(CutsceneCmdCreateActorCueList.bl_idname) + + split = layout.split(factor=0.5) + split.label(text="Cutscene End Frame") + split.prop(self, "csEndFrame") + + commandsBox = layout.box() + commandsBox.box().label(text="Cutscene Commands") + + b = commandsBox.box() + b.prop(self, "csUseDestination") + if self.csUseDestination: + b.prop(self, "csDestinationStartFrame") + + searchBox = b.box() + boxRow = searchBox.row() + searchOp = boxRow.operator(OOT_SearchCSDestinationEnumOperator.bl_idname, icon="VIEWZOOM", text="") + searchOp.objName = obj.name + boxRow.label(text=getEnumName(ootData.enumData.ootEnumCsDestination, self.csDestination)) + if self.csDestination == "Custom": + prop_split(searchBox.column(), self, "csDestinationCustom", "Cutscene Destination Custom") + + commandsBox.column_flow(columns=3, align=True).prop(self, "menuTab", expand=True) + label = f"Add New {ootCSSubPropToName[self.menuTab]}" + op = commandsBox.operator(OOTCSListAdd.bl_idname, text=label, icon=csListTypeToIcon[self.menuTab]) + op.collectionType = "Cutscene" + op.listType = self.menuTab + op.objName = obj.name + + for i, csListProp in enumerate(self.csLists): + # ``csListProp`` type: OOTCSListProperty + if csListProp.listType == self.menuTab: + csListProp.draw_props(commandsBox, i, obj.name, "Cutscene") classes = ( - OOTCSTextboxProperty, - OOTCSLightingProperty, + OOTCSTextProperty, + OOTCSLightSettingsProperty, OOTCSTimeProperty, - OOTCSBGMProperty, + OOTCSSeqProperty, OOTCSMiscProperty, - OOTCS0x09Property, - OOTCSUnkProperty, + OOTCSRumbleProperty, OOTCSListProperty, + OOTCutsceneTransitionProperty, + OOTCutsceneMiscProperty, + OOTCutscenePreviewProperty, + OOTCutscenePreviewSettingsProperty, OOTCutsceneProperty, ) @@ -259,9 +445,11 @@ def cutscene_props_register(): register_class(cls) Object.ootCutsceneProperty = PointerProperty(type=OOTCutsceneProperty) + Scene.ootPreviewSettingsProperty = PointerProperty(type=OOTCutscenePreviewSettingsProperty) def cutscene_props_unregister(): + del Scene.ootPreviewSettingsProperty del Object.ootCutsceneProperty for cls in reversed(classes): diff --git a/fast64_internal/oot/cutscene_docs.md b/fast64_internal/oot/cutscene_docs.md new file mode 100644 index 000000000..e22acb573 --- /dev/null +++ b/fast64_internal/oot/cutscene_docs.md @@ -0,0 +1,106 @@ +# Cutscene Documentation + +### Dictionnary +- Cutscene "Command/Commands": Cutscene "Commands" are macros that will tell the game what to do in your cutscene. It can be anything from stopping the cutscene at a certain frame, doing scene transitions or even manipulating Actors during the cutscene. The array containing the commands is called a cutscene "script". You can find more informations about each commands below. +- Camera "AT/Eye": AT means "look-at", it defines where the camera is looking, "Eye" is the position of the camera. For a person, "Eye" would be where they are and "AT" the object they are currently looking at. On Blender, the AT is a bone's tail and the Eye is a bone's head. +- "Spline": it's the movement the camera will follow. +- Actor "Cue": This is a system that triggers an Actor's code to make some action. Every Actor that has an implementation for this listens to the channel (defined by the "Actor Cue List" command) and will execute the action depending on the cue's action ID. This system is far from perfect, for instance two actors using the same channel will execute their actions at the same time. +- "Seq": Sequence, it refers to the audio (most of the time it's the background music) + +### Commands +More detailed informations and commands' parameters can be found [here](https://github.com/zeldaret/oot/blob/master/include/z64cutscene_commands.h) + +- ``CS_BEGIN_CUTSCENE``: defines the beginning of a cutscene script +- ``CS_END``: defines the end of a cutscene script +- ``CS_CAM_POINT``: defines a single camera point, it can be used with any of the "eye" or "at" camera commands +- ``CS_CAM_EYE``: defines a single eye point, this feature is not used in the final game and lacks polish +- ``CS_CAM_EYE_SPLINE``: declares a list of "eye" camera points that forms a spline +- ``CS_CAM_AT_SPLINE``: declares a list of "at" camera points that forms a spline +- ``CS_CAM_EYE_SPLINE_REL_TO_PLAYER`` and ``CS_CAM_AT_SPLINE_REL_TO_PLAYER``: same as the 2 above except these are relative to the player's position and yaw. +- ``CS_MISC_LIST``: declares a list of various miscellaneous commands, they're all self-explanatory, you can find the list [here](https://github.com/zeldaret/oot/blob/master/include/z64cutscene.h#L167-L204) +- ``CS_MISC``: defines a single misc command +- ``CS_LIGHT_SETTING_LIST``: declares a list of light settings commands +- ``CS_LIGHT_SETTING``: changes the light settings to the specified index, the lighting is defined in the scene +- ``CS_RUMBLE_CONTROLLER_LIST``: declares a list of controller rumble settings +- ``CS_RUMBLE_CONTROLLER``: makes the controller rumble, you can control the duration and the strength +- ``CS_ACTOR_CUE_LIST`` and ``CS_PLAYER_CUE_LIST``: declares an actor cue list, this also defines the channel to use (except for player cues) +- ``CS_ACTOR_CUE`` and ``CS_PLAYER_CUE``: defines the action to execute at the desired frame, also defines the position/rotation +- ``CS_TEXT_LIST``: declares a list of textboxes to display on-screen, when a textbox is present the cutscene won't continue until it's closed if the textbox isn't closing automatically +- ``CS_TEXT``: starts a textbox at the desired frame +- ``CS_TEXT_NONE``: +- ``CS_TEXT_OCARINA_ACTION``: defines an ocarina action, this is used when learning new songs during a cutscene +- ``CS_TRANSITION``: fills the screen with a single color to create transitions, "half" types are used in the intro cutscene to make that "night" effect in Link's house, "trigger instance" is used in the intro part where the Deku Tree talks to Navi to keep the screen white for the duration of the command +- ``CS_START_SEQ_LIST``: declares a list of ``CS_START_SEQ`` commands +- ``CS_START_SEQ``: starts a sequence +- ``CS_STOP_SEQ_LIST``: declares a list of ``CS_STOP_SEQ`` commands +- ``CS_STOP_SEQ``: stops a sequence +- ``CS_FADE_OUT_SEQ_LIST``: declares a list of ``CS_FADE_OUT_SEQ`` commands +- ``CS_FADE_OUT_SEQ``: fades out a sequence player on the specified frame range +- ``CS_TIME_LIST``: declares a list of ``CS_TIME`` commands +- ``CS_TIME``: changes the time of day to the specified hour and minute +- ``CS_DESTINATION``: defines a scene change (a new destination), specific destination types are doing other actions like changing the player's age or setting flags + +### Cutscene Motion with Fast64 +Fast64 has an implementation for creating camera shots and actor cues. + +- A camera shot is defined by an Armature object and the camera motion is defined by the armature's bones. Due to the game's spline algorithm for camera motion, you always need one more key point at each end, to define how the camera is moving when it approaches the last normal point. So, the minimum number of bones in the armature is 4, if you want the camera to move between the positions indicated by bones 2 to 3. + +When the shot / armature is selected, in the Object Properties pane there are controls for the start frame of that shot and whether it's normal, relative to Link, or the single Eye/AT point mode. When a particular key point / bone is selected, you have controls for the number of frames, view angle (FoV), and roll of the camera at that position. + +At export, camera shots are sorted by name, so you should name them with something they will be in the correct order with at export (e.g. Shot01, Shot02, etc.). The bones / key frames are also sorted by name, so their names must be in the order you want the motion to have. These should both be previewed correctly (i.e. if it looks right in Blender, it should work right in game) + +When you add a new bone by duplicating the last bone in the sequence, you must switch out of edit mode and back in for the previewer to properly handle that new bone. This only needs to be done after adding bones; you can preview while editing bones normally. This is due to how Blender represents bones differently in edit mode vs. object mode. + +- An Actor Cue list is defined by an empty object and child objects. The list object lets you control which the "command type" (defines the channel to use) and each child object defines the action ID. Those child objects are called "Actor Cue Point". Each cue's ending frame is defined by the next cue's starting frame. The next one also defines the end position of the previous cue, this is the design made by the original OoT devs that's why it can look a bit weird at first glance. + +### Details about the camera +Additional informations about the camera, you can skip reading this. This is ported from [zcamedit's readme](https://github.com/sauraen/zcamedit#details). + +The camera system in game is weird, this is partly why the previewer exists. If the previewer is not behaving as you expect, it's probably working correctly! (Of course if the behavior differs between Blender and in-game, please report a bug.) + +First of all, the system is based on four-point spline interpolation. This means, in the simplest case you have four values A-B-C-D, and the output changes from B to C over the duration, except with the initial trajectory based on A-B and with the final trajectory based on C-D so you get a nice curve. This is used separately to interpolate eye (camera position) and at (target look-at position) as well as camera roll and view angle. If you have more values (with the caveats below), the system will move through all the values except the start and end values. So basically you need an extra camera point at the beginning and at the end to set how the camera is starting and stopping. + +Now, the game's version of this is weird for two reasons: + +
+1. Continue flag checking bug +If you don't care about the coding and just want to make cutscenes, you don't have to worry about this, Fast64 takes care of it at import/export. Just make sure every cutscene command has at least 4 key points (bones). + +There is a bug (in the actual game) where when incrementing to the next set of key points, the key point which is checked for whether it's the last point or not is the last point of the new set, not the last point of the old set. This means that you always need an additional extra point at the end (except for the case of exactly four points, see below). This is in addition to the extra point at the end (and the one at the beginning) which are used for the spline interpolation to set how the camera behaves at the start or the end. No data whatsoever is read from this second extra point (except for the flag that it's the last point, which is set up automatically on export). + +For the case of 4 points, the camera motion from B to C works correctly, but when it gets to C, it reads the continue flag out of bounds (which will be an unspecified value). In most cases that byte won't be 0xFF, which means that on the following frame it will take the case for 1/2/3 points, and not initialize the camera position values at all, potentially leading to garbage values being used for them. + +So in summary: +- Command has 0 points: Will fail to export, but probably crash in game +- Command has 1/2/3 points: Command will immediately end; the position and look will be uninitialized values, whatever was last on the stack (may cause a floating-point exception) +- Command has 4 points: Works, but don't let the cutscene get to the end of this command +- Command has 5 points: Works as if it had 4 points +- Command has 6 points: Works as if it had 5 points +- Etc. + +Fast64 will automatically add this second extra point at the end on export, and also automatically remove the extra point at the end on import unless the command has only four points. +
+ +
+2. Frames interpolation +The number of frames actually spent between key points is interpolated in reciprocals and in a varying way between the key points. This makes predicting the number of frames actually spent extremely difficult unless the frames values are the same. In fact it's so difficult that this plugin actually just simulates the cutscene frame-by-frame up to the current frame every time the Blender frame changes, because solving for the position at some future time without actually stepping through all the frames up to there is too hard. + +Note: It's a discretized differential equation, if time was continuous, i.e. the frame rate was infinite, it could be solved with calculus, but since it moves in discrete steps at the frames, even the calculus solution would be only approximate. On top of that, when it changes from going between B-C and going between C-D, the initial position near C depends on what happened at B, and so on. + +You can think of it as it will spend *about* ``N frames`` around each key point. So, if the camera moves from point B to C but B has a larger ``frames`` value than C, the camera will move more slowly near B and more quickly near C. Also, a value of 0 means infinity, not zero, if C has ``frames=0`` the camera will approach C but never reach it. + +Only the ``frames`` values of points B and C affect the result when the camera is between B and C. So, the ``frames`` values of the one extra points at the beginning and the end (in this case A and D) can be arbitrary. + +The actual algorithm is: +- Compute the increment in ``t`` value (percentage of the way from point B to C) at point B by 1 / ``B.frames``, or 0 if ``B.frames`` is 0 +- Compute the increment in ``t`` value at point C by 1 / ``C.frames`` or 0. +- Linearly interpolate between these based on the current ``t`` value. +- Add this increment to ``t``. + +So you can think of it like, if ``B.frames`` is 10 and ``C.frames`` is 30, the camera moves 1/10th of the way from B to C per frame when it's at B, and 1/30th of the way from B to C per frame when it's nearly at C. But, when it's halfway between B and C, it doesn't move 1/20th of the way per frame, it moves (1/10)/2 + (1/30)/2 = 1/15th of the way. And on top of that, it will cross that positional halfway point less than half the total number of frames it actually takes to get from B to C. +
+ +### +There is also an ``endFrame`` parameter in the cutscene data, however it is almost useless, so it is not included as a custom property in the armature. The ``endFrame`` parameter does not end or stop the camera command, running out of key points or another camera command starting does. It's only checked when the camera command starts. In a normal cutscene where time starts from 0 and advances one frame at a time, as long as ``endFrame`` >= start_frame + 2, the command will work the same. + +So, this plugin just sets it to a "reasonable value" on export, and just asserted for validity on import. It seems the original developers' tool used the sum of all the points, including the second extra point, as the ``endFrame`` parameter for ``CS_CAM_AT_SPLINE``, and used the sum of all the points without the second extra point, plus 1, for the ``CS_CAM_EYE_SPLINE`` (oh yeah, did I mention they're different?), so Fast64 replicates this behavior. diff --git a/fast64_internal/oot/data/oot_data.py b/fast64_internal/oot/data/oot_data.py index 465707b7e..c4619a925 100644 --- a/fast64_internal/oot/data/oot_data.py +++ b/fast64_internal/oot/data/oot_data.py @@ -14,8 +14,10 @@ class OoT_Data: """Contains data related to OoT, like actors or objects""" def __init__(self): + from .oot_enum_data import OoT_EnumData from .oot_object_data import OoT_ObjectData from .oot_actor_data import OoT_ActorData + self.enumData = OoT_EnumData() self.objectData = OoT_ObjectData() self.actorData = OoT_ActorData() diff --git a/fast64_internal/oot/data/oot_enum_data.py b/fast64_internal/oot/data/oot_enum_data.py new file mode 100644 index 000000000..0644c6f4b --- /dev/null +++ b/fast64_internal/oot/data/oot_enum_data.py @@ -0,0 +1,123 @@ +from dataclasses import dataclass, field +from os import path +from .oot_getters import getXMLRoot +from .oot_data import OoT_BaseElement + +# Note: "enumData" in this context refers to an OoT Object file (like ``gameplay_keep``) + + +@dataclass +class OoT_ItemElement(OoT_BaseElement): + parentKey: str + + def __post_init__(self): + # generate the name from the id + + if self.name is None: + keyToPrefix = { + "csCmd": "CS_CMD", + "csMiscType": "CS_MISC", + "csTextType": "CS_TEXT", + "csFadeOutSeqPlayer": "CS_FADE_OUT", + "csTransitionType": "CS_TRANS", + "csDestination": "CS_DEST", + "csPlayerCueId": "PLAYER_CUEID", + "naviQuestHintType": "NAVI_QUEST_HINTS", + "ocarinaSongActionId": "OCARINA_ACTION", + } + + self.name = self.id.removeprefix(f"{keyToPrefix[self.parentKey]}_") + + if self.parentKey in ["csCmd", "csPlayerCueId"]: + split = self.name.split("_") + if self.parentKey == "csCmd" and "ACTOR_CUE" in self.id: + self.name = f"Actor Cue {split[-2]}_{split[-1]}" + else: + self.name = f"Player Cue Id {split[-1]}" + else: + self.name = self.name.replace("_", " ").title() + + +@dataclass +class OoT_EnumElement(OoT_BaseElement): + items: list[OoT_ItemElement] + itemByKey: dict[str, OoT_ItemElement] = field(default_factory=dict) + itemByIndex: dict[int, OoT_ItemElement] = field(default_factory=dict) + itemById: dict[int, OoT_ItemElement] = field(default_factory=dict) + + def __post_init__(self): + self.itemByKey = {item.key: item for item in self.items} + self.itemByIndex = {item.index: item for item in self.items} + self.itemById = {item.id: item for item in self.items} + + +class OoT_EnumData: + """Cutscene and misc enum data""" + + def __init__(self): + # general enumData list + self.enumDataList: list[OoT_EnumElement] = [] + + # Path to the ``EnumData.xml`` file + enumDataXML = path.dirname(path.abspath(__file__)) + "/xml/EnumData.xml" + enumDataRoot = getXMLRoot(enumDataXML) + + for enum in enumDataRoot.iterfind("Enum"): + self.enumDataList.append( + OoT_EnumElement( + enum.attrib["ID"], + enum.attrib["Key"], + None, + None, + [ + OoT_ItemElement( + item.attrib["ID"], + item.attrib["Key"], + # note: the name sets automatically after the init if None + item.attrib["Name"] if enum.attrib["Key"] == "seqId" else None, + int(item.attrib["Index"]), + enum.attrib["Key"], + ) + for item in enum + ], + ) + ) + + # create list of tuples used by Blender's enum properties + self.deletedEntry = ("None", "(Deleted from the XML)", "None") + + self.ootEnumCsCmd: list[tuple[str, str, str]] = [] + self.ootEnumCsMiscType: list[tuple[str, str, str]] = [] + self.ootEnumCsTextType: list[tuple[str, str, str]] = [] + self.ootEnumCsFadeOutSeqPlayer: list[tuple[str, str, str]] = [] + self.ootEnumCsTransitionType: list[tuple[str, str, str]] = [] + self.ootEnumCsDestination: list[tuple[str, str, str]] = [] + self.ootEnumCsPlayerCueId: list[tuple[str, str, str]] = [] + self.ootEnumNaviQuestHintType: list[tuple[str, str, str]] = [] + self.ootEnumOcarinaSongActionId: list[tuple[str, str, str]] = [] + self.ootEnumSeqId: list[tuple[str, str, str]] = [] + + self.enumByID = {enum.id: enum for enum in self.enumDataList} + self.enumByKey = {enum.key: enum for enum in self.enumDataList} + + for key in self.enumByKey.keys(): + setattr(self, "ootEnum" + key[0].upper() + key[1:], self.getOoTEnumData(key)) + + def getOoTEnumData(self, enumKey: str): + enum = self.enumByKey[enumKey] + firstIndex = min(1, *(item.index for item in enum.items)) + lastIndex = max(1, *(item.index for item in enum.items)) + 1 + enumData = [self.deletedEntry] * lastIndex + custom = ("Custom", "Custom", "Custom") + + for item in enum.items: + if item.index < lastIndex: + identifier = item.key + enumData[item.index] = (identifier, item.name, item.id) + + if firstIndex > 0: + enumData[0] = custom + else: + enumData.insert(0, custom) + + return enumData diff --git a/fast64_internal/oot/data/xml/ActorList.xml b/fast64_internal/oot/data/xml/ActorList.xml index efcdad795..a53b1ebfc 100644 --- a/fast64_internal/oot/data/xml/ActorList.xml +++ b/fast64_internal/oot/data/xml/ActorList.xml @@ -129,7 +129,7 @@ for each sub element (of ) mentioned below: Large Large, Appears, Clear Flag - Boss Keyā€™s Chest + Boss Key's Chest Large, Falling, Switch Flag Large, Invisible Small @@ -489,16 +489,16 @@ Depending on the scene ID, they need certain objects loaded as the first one in Waterfall Small Stream Stream - Fire Templeā€™s Lower Ambient Noise - Fire Templeā€™s Higher Ambient Noise + Fire Temple's Lower Ambient Noise + Fire Temple's Higher Ambient Noise Water Dripping (Well) River Market gibberish Decrease current BGM volume - Proximity Sariaā€™s Song + Proximity Saria's Song Howling wind Gurgling - Temple of Lightā€™s dripping sounds + Temple of Light's dripping sounds Low booming-likish sound Quake/Collapse Fairy Fountain @@ -911,7 +911,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) Normal, flashes blue when struck with sword - Frickinā€™ huge, doesnā€™t flash when struck + Frickin' huge, doesn't flash when struck Even bigger? Ridiculously huge? Small size, flashes blue when struck @@ -931,12 +931,12 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) Line Loop Circle Loop - - - - - - + + + + + + @@ -1216,7 +1216,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) Blue Warp and Ruto Leaning Ruto //no collision data, targetable - Ruto, First Encounter //Plays cutscene from Jabu-Jabuā€™s Belly when you first meet her + Ruto, First Encounter //Plays cutscene from Jabu-Jabu's Belly when you first meet her Ruto, after falling down the hole in Jabu-Jabu @@ -1224,8 +1224,8 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) - Flies, lands, dies in a bit //try not to run into it, Linkā€™ll catch on fire - Flies, lands, creeps towards Link, dies in a bit //itā€™s blue, but itā€™s still fire + Flies, lands, dies in a bit //try not to run into it, Link'll catch on fire + Flies, lands, creeps towards Link, dies in a bit //it's blue, but it's still fire @@ -1732,7 +1732,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) Circular Trap Door Platform //Opens when wrong skull is chosen Gate //Only blocks one-way Skull Top - Glitchy graphics, canā€™t see object, behaves like the gate + Glitchy graphics, can't see object, behaves like the gate +0x3F00 = Switch Flag @@ -1753,7 +1753,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095) - Large shadow //looks like itā€™s reproduced, well-done though + Large shadow //looks like it's reproduced, well-done though Small Shadow //CRASH? No shadow //CRASH? @@ -2241,19 +2241,19 @@ ROTZ 0x0001 = Behavior after colliding with Link Dark! Narrow! Scary! / Well of Three Features Death Mountain / No passage without a / Royal Decree! Death Mountain Trail - Dodongoā€™s Cavern / Donā€™t enter without permission! + Dodongo's Cavern / Don't enter without permission! Land of the Gorons / Goron City - Zoraā€™s River / Watch out for swift current / and strong undertow. + Zora's River / Watch out for swift current / and strong undertow. The Shadow will yield only to one / with the eye of truth, handed / down in Kakariko Village. - Zoraā€™s Domain - Zoraā€™s Fountain / Donā€™t disturb Lord Jabu-Jabu! / ā€“King Zora XVI - Forest Training Center / Donā€™t recklessly cut signsā€“ / read them carefully! + Zora's Domain + Zora's Fountain / Don't disturb Lord Jabu-Jabu! / ā€“King Zora XVI + Forest Training Center / Don't recklessly cut signsā€“ / read them carefully! All those reckless enough to / venture into the desertā€“please drop by our shop. / Carpet Merchant - Just ahead: / Great Deku Treeā€™s Meadow + Just ahead: / Great Deku Tree's Meadow Forest Temple The Lost Woods - Talon and Malonā€™s / Lon Lon Ranch - The Great Ingoā€™s / Ingo Ranch + Talon and Malon's / Lon Lon Ranch + The Great Ingo's / Ingo Ranch Lake Hylia Lakeside Laboratory / Daily trying to get to the bottom / of the mysteries of Lake Hylia! / ā€“Lake Scientist Gerudo Valley @@ -2262,18 +2262,18 @@ ROTZ 0x0001 = Behavior after colliding with Link Haunted Wasteland / If you chase a mirage, the / desert will swallow you. / Only one path is true! Spirit Temple Kokiri Shop / We have original forest goods! - LINKā€™s House + LINK's House Forest folk shall not leave these woods. Follow the trail along the edge of / the cliff and you will reach / Goron City, home of the Gorons. Natural Wonder / Bomb Flower / Danger! Do not uproot! Death Mountain Summit / Entrance to the crater ahead / Beware of intense heat! - King Zoraā€™s Throne Room / To hear the Kingā€™s royal / proclamations, stand on the / platform and speak to him. + King Zora's Throne Room / To hear the King's royal / proclamations, stand on the / platform and speak to him. If you can stop my wild rolling, you might get something great. / ā€“Hot Rodder Goron Only one with the eye of truth / will find the stone umbrella / that protects against the / rain of blades. Only one who has sacred feet / can cross the valley of the dead. The record time of those / who raced against me was: / ##"##" / ā€“DampĆ© the Gravekeeper Shooting Gallery / etc. - Treasure Chest Shop / We donā€™t necessarily sell them... + Treasure Chest Shop / We don't necessarily sell them... High Dive Practice Spot / Are you confident / in your diving skill? 032c Mountain Summit / Danger Ahead - Keep Out @@ -2286,18 +2286,18 @@ ROTZ 0x0001 = Behavior after colliding with Link Heart-Pounding Gravedigging Tour! / From 18:00 to 21:00 Hyrule Time / ā€“DampĆ© the Gravekeeper Heart-Pounding Gravedigging Tour! / Tours are cancelled until a new / gravekeeper is found. We / apologize for any inconvenience. Thrust Attack Signs! / To thrust with your sword, press / CS toward your target while / Z Targeting, then press B. - Hole of ā€œZā€ / Letā€™s go through this small / hole! / Stand in front of the hole and / push CS towards it. When the / Action Icon shows ā€œEnter,ā€ press / A to crawl into the hole. / Pay attention to what the Action / Icon says! - Cut Grass With Your Sword / If you just swing with B, youā€™ll / cut horizontally. If you hold Z as / you swing, youā€™ll cut vertically. + Hole of ā€œZā€ / Let's go through this small / hole! / Stand in front of the hole and / push CS towards it. When the / Action Icon shows ā€œEnter,ā€ press / A to crawl into the hole. / Pay attention to what the Action / Icon says! + Cut Grass With Your Sword / If you just swing with B, you'll / cut horizontally. If you hold Z as / you swing, you'll cut vertically. Hyrule Castle / Lon Lon Ranch You are here: Hyrule Castle / This way to Lon Lon Ranch - Just Ahead / King Zoraā€™s Chamber / Show the proper respect! + Just Ahead / King Zora's Chamber / Show the proper respect! House of the Great Mido / Boss of the Kokiri House of the Know-It-All Brothers House of Twins - Sariaā€™s House - View Point with Z Targeting / When you have no object to look / at, you can just look forward / with Z. / Stop moving and then change the / direction you are facing, or hold / down Z for a little while. / This can help you get oriented in / the direction you want to face. / Itā€™s quite convenient! / If you hold down Z, you can / walk sideways while facing / straight ahead. / Walking sideways can be a very / important technique in dungeon / corridors. Turn around and try doing this right now. - Stepping Stones in the Pond / If you boldly go in the direction / you want to jump, you will leap / automatically. / If you hop around on the stones, / youā€™ll become happier! - No Diving Allowed / ā€“It wonā€™t do you any good! + Saria's House + View Point with Z Targeting / When you have no object to look / at, you can just look forward / with Z. / Stop moving and then change the / direction you are facing, or hold / down Z for a little while. / This can help you get oriented in / the direction you want to face. / It's quite convenient! / If you hold down Z, you can / walk sideways while facing / straight ahead. / Walking sideways can be a very / important technique in dungeon / corridors. Turn around and try doing this right now. + Stepping Stones in the Pond / If you boldly go in the direction / you want to jump, you will leap / automatically. / If you hop around on the stones, / you'll become happier! + No Diving Allowed / ā€“It won't do you any good! Switch Targeting / If you see a \/ icon above an / object, you can target it with Z. / ... / You can target the stones next to this sign for practice! Forest Stage / We are waiting to see your / beautiful face! / Win fabulous prizes! Visit the / House of the Know-It-All Brothers / to get answers to all your / item-related questions! @@ -2314,7 +2314,7 @@ ROTZ 0x0001 = Behavior after colliding with Link Non-solid cucco, hops oddly every once in awhile and only goes in one direction - Invisible, solid cucco, doesnā€™t move, can be attacked but will only smoke and molt + Invisible, solid cucco, doesn't move, can be attacked but will only smoke and molt Invisible cucco, cannot be attacked it seems, no idea what it does, but you can hear it @@ -2752,16 +2752,16 @@ ROTZ 0x0001 = Behavior after colliding with Link +0x3FC0 = Message ID //0x0200 + Value --0000 Hi! Iā€™m a talking door! //Unused +-0000 Hi! I'm a talking door! //Unused -0040 Strange... this door doesn't open... --0080 Strong iron bars are blocking the door. You canā€™t open them with your hands! +-0080 Strong iron bars are blocking the door. You can't open them with your hands! -00C0 You need a Key to open a door that is locked or chained. -0100 You need a special key to open this door. --0140 Be quiet! Itā€™s only time! I, DampĆ© the Gravekeeper, am in bed now! Go away and play! Maybe you can find a ghost in the daytime? --0180 Itā€™s time now. The Gravedigging Tour is over now! I, DampĆ© the gravekeeper, am in bed! Go away and play! Maybe youā€™ll find a ghost! +-0140 Be quiet! It's only time! I, DampĆ© the Gravekeeper, am in bed now! Go away and play! Maybe you can find a ghost in the daytime? +-0180 It's time now. The Gravedigging Tour is over now! I, DampĆ© the gravekeeper, am in bed! Go away and play! Maybe you'll find a ghost! -01C0 Happy Mask Shop / Please read this sign before you use this shop... -0200 Shadow Temple... here is gathered Hyrule's bloody history of greed and hatred... --0240 What is hidden in the darkness... Tricks full of ill will... You canā€™t see the way forward... +-0240 What is hidden in the darkness... Tricks full of ill will... You can't see the way forward... -0280 One who gains the eye of truth will be able to see what is hidden in the darkness. -02C0 Something strange is covering the entrance. You must solve the puzzle in this room to make the entrance open. -0300 Giant dead Dodongo... when it sees red, a new way to go will be open. @@ -2774,14 +2774,14 @@ ROTZ 0x0001 = Behavior after colliding with Link -04C0 One with the eye of truth shall be guided to the Spirit Temple by an inviting ghost. -0500 Those who wish to open the path sleeping at the bottom of the lake must play the song passed down by the Royal Family. -0540 Those who wish to open the gate on the far heights, play the song passed down by the Royal Family. --0580 Those who find a Small Key can advance to the next room. Those who donā€™t can go home! //Unused +-0580 Those who find a Small Key can advance to the next room. Those who don't can go home! //Unused -05C0 If you wish to speak to me, do so from the platform. -0600 Hi, LINK! Look this way! Look over here with Z, and talk to me with A. -0640 The current time is: time. -0680 Shine light on the living dead... -06C0 Those who break into the Royal Tomb will be obstructed by the lurkers in the dark. -0700 Hey, you! Young man, over there! Look over here, inside the cell! --0740 My little boy isnā€™t here right now... I think he went to play in the graveyard... +-0740 My little boy isn't here right now... I think he went to play in the graveyard... -0780 Oh, my boy is asleep right now. Please come back some other time to play with him! -07C0 When water fills the lake, shoot for the morning light. -0800 If you want to travel to the future, you should return here with the power of silver from the past. @@ -2789,17 +2789,17 @@ ROTZ 0x0001 = Behavior after colliding with Link -0880 This door is currently being refurbished. //Unused -08C0 It looks like something used to be set in this stand... -0900 Make my beak face the skull of truth. The alternative is descent into the deep darkness. --0940 This is not the correct key... the door wonā€™t open! --0980 Grannyā€™s Potion Shop / Closed / Gone for Field Study / Please come again! ā€“Granny --09C0 Whoā€™s there? What a bad kid, trying to enter from the rear door! Such a bad kid... I have to tell you some juicy gossip! The boss carpenter has a son... Heā€™s the guy who sits under the tree every night... Donā€™t tell the boss I told you that! +-0940 This is not the correct key... the door won't open! +-0980 Granny's Potion Shop / Closed / Gone for Field Study / Please come again! ā€“Granny +-09C0 Who's there? What a bad kid, trying to enter from the rear door! Such a bad kid... I have to tell you some juicy gossip! The boss carpenter has a son... He's the guy who sits under the tree every night... Don't tell the boss I told you that! -0A00 Look at this! --0A40 Malonā€™s gone to sleep! Iā€™m goinā€™ to sleep now, too. Come back again when itā€™s light out! --0A80 LINKā€™s Records! / Spiders squished: 0 / Largest fish caught: 0 pounds / Marathon time: 00"00" / Horse race time: 00"00" / Horseback archery: 0 points +-0A40 Malon's gone to sleep! I'm goin' to sleep now, too. Come back again when it's light out! +-0A80 LINK's Records! / Spiders squished: 0 / Largest fish caught: 0 pounds / Marathon time: 00"00" / Horse race time: 00"00" / Horseback archery: 0 points -0AC0 The crest of the Royal Family is inscribed here. -0B00 R.I.P. / Here lie the souls of those who swore fealty to the Royal Family of Hyrule / The Sheikah, guardians of the Royal Family and founders of Kakariko, watch over these spirits in their eternal slumber. -0B40 Sleepless Waterfall / The flow of this waterfall serves the King of Hyrule. When the king slumbers, so too do these falls. -0B80 Some frogs are looking at you from underwater... --0BC0 Youā€™re standing on a soft carpet for guests... it feels so plush under your feet! +-0BC0 You're standing on a soft carpet for guests... it feels so plush under your feet! -0C00 If you can overcome the trials in the chambers ahead, then and only then will you be qualified to hold our secret treasure! -0C40 If you desire to acquire our hidden treasure, you must strive to obtain the keys hidden in each chamber! -0C80 Defeat all the enemies in a limited time! @@ -2818,8 +2818,8 @@ ROTZ 0x0001 = Behavior after colliding with Link - Turns around, canā€™t move, whistle-blower - Wonā€™t turn around, canā€™t move, whistle-blower + Turns around, can't move, whistle-blower + Won't turn around, can't move, whistle-blower Purple Gerudo, acts like the one that gives you the membership card @@ -3035,7 +3035,7 @@ ROTZ 0x0001 = Behavior after colliding with Link - + 0xFFFF = Spawned Skulltula Variable (see actor 0095) @@ -3141,7 +3141,7 @@ Z ROTATION +0x3F = Collectible Flag //0000 to 1098 controls the size, where 0000 is normal size and 1098 is as large as is possible for the machine to handle (may vary depending on the level). //From F02F to FFFF, the Stalchild will appear but will somehow invert everything. In other words, it walks upside-down underneath the ground. -//Really weird. FFFF is smallest, F02F is supposedly largest (I havenā€™t been able to see it, though). +//Really weird. FFFF is smallest, F02F is supposedly largest (I haven't been able to see it, though). @@ -3224,15 +3224,15 @@ Z ROTATION +0x3F = Collectible Flag They say you can swim faster by continuously pressing B. They say there is a secret near the lone tree which is not far from the river in the northwest part of Hyrule Field. They say that there is a secret on the road that leads to Lake Hylia. - They say that Biggoronā€™s Sword is super sharp and will never break. - They say that Medigoron didnā€™t really think about his own size, so his store is really cramped. + They say that Biggoron's Sword is super sharp and will never break. + They say that Medigoron didn't really think about his own size, so his store is really cramped. They say that Malon set the original record in the obstacle course of Lon Lon Ranch. They say that Malon of Lon Lon Ranch hopes a knight in shining armor will come and sweep her off her feet someday. They say that Ruto, the Zora princess who is known for her selfish nature, likes a certain boy... They say that players who select the ā€œHOLDā€ option for ā€œZ TARGETINGā€ are the real ā€œZelda players!ā€ They say that there is a secret near a tree in Kakariko Village. They say that, contrary to her elegant image, Princess Zelda of Hyrule Castle is, in fact, a tomboy! - They say that Princess Zeldaā€™s nanny is actually one of the Sheikah, who many thought had died out. + They say that Princess Zelda's nanny is actually one of the Sheikah, who many thought had died out. They say there is a man who can always be found running around in Hyrule Field. They say that it is against the rules to use glasses at the Treasure Chest Shop in Hyrule Castle Town Market. They say that the chicken lady goes to the Lakeside Laboratory to study how to breed pocket-sized Cuccos. @@ -3246,9 +3246,9 @@ Z ROTATION +0x3F = Collectible Flag They say that strange owl, Kaepora Gaebora, may look big and heavy, but its character is rather lighthearted. They say that the horse Ganondorf rides is a solid black Gerudo stallion. They say that Ganondorf is not satisfied with ruling only the Gerudo and aims to conquer all of Hyrule! - They say that the treasure you can earn in the Gerudoā€™s Training Ground is not as great as you would expect, given its difficulty! + They say that the treasure you can earn in the Gerudo's Training Ground is not as great as you would expect, given its difficulty! They say that there is a switch that you can activate only by using the Spin Attack. - They say that itā€™s possible to find a total of 100 Gold Skulltulas throughout Hyrule. + They say that it's possible to find a total of 100 Gold Skulltulas throughout Hyrule. They say that when non-fairy folk enter the Lost Woods, they become monsters! They say that the small holes in the ground that you can find all over Hyrule make perfect breeding ground for bugs. They say that the Kokiri are always followed by small fairies. @@ -3433,56 +3433,56 @@ Z ROTATION +0x3F = Collectible Flag - + - + - + - + - + - - - - - + + + + + - + - + - + - - + + - + - + - + - - + + @@ -3490,18 +3490,18 @@ Z ROTATION +0x3F = Collectible Flag - + - - - + + + - - + + @@ -3538,6 +3538,7 @@ Z ROTATION +0x3F = Collectible Flag + @@ -3551,6 +3552,7 @@ Z ROTATION +0x3F = Collectible Flag + @@ -3586,9 +3588,9 @@ Z ROTATION +0x3F = Collectible Flag - - - + + + @@ -3607,9 +3609,9 @@ Z ROTATION +0x3F = Collectible Flag - - - + + + @@ -3628,7 +3630,7 @@ Z ROTATION +0x3F = Collectible Flag - + @@ -3642,11 +3644,11 @@ Z ROTATION +0x3F = Collectible Flag - - + + - + diff --git a/fast64_internal/oot/data/xml/EnumData.xml b/fast64_internal/oot/data/xml/EnumData.xml new file mode 100644 index 000000000..c379af7a4 --- /dev/null +++ b/fast64_internal/oot/data/xml/EnumData.xml @@ -0,0 +1,601 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/fast64_internal/oot/f3d/operators.py b/fast64_internal/oot/f3d/operators.py index b153c52fe..ef42719d9 100644 --- a/fast64_internal/oot/f3d/operators.py +++ b/fast64_internal/oot/f3d/operators.py @@ -6,7 +6,7 @@ from mathutils import Matrix from ...utility import CData, PluginError, raisePluginError, writeCData, toAlnum from ...f3d.f3d_parser import importMeshC, getImportData -from ...f3d.f3d_gbi import DLFormat, F3D, TextureExportSettings, ScrollMethod +from ...f3d.f3d_gbi import DLFormat, F3D, TextureExportSettings, ScrollMethod, get_F3D_GBI from ...f3d.f3d_writer import TriangleConverterInfo, removeDL, saveStaticModel, getInfoDict from ..oot_utility import ootGetObjectPath, getOOTScale from ..oot_model_classes import OOTF3DContext, ootGetIncludedAssetData @@ -28,8 +28,6 @@ def ootConvertMeshToC( originalObj: bpy.types.Object, finalTransform: mathutils.Matrix, - f3dType: str, - isHWv1: bool, DLFormat: DLFormat, saveTextures: bool, settings: OOTDLExportSettings, @@ -47,7 +45,7 @@ def ootConvertMeshToC( try: obj, allObjs = ootDuplicateHierarchy(originalObj, None, False, OOTObjectCategorizer()) - fModel = OOTModel(f3dType, isHWv1, name, DLFormat, drawLayer) + fModel = OOTModel(name, DLFormat, drawLayer) triConverterInfo = TriangleConverterInfo(obj, None, fModel.f3d, finalTransform, getInfoDict(obj)) fMeshes = saveStaticModel( triConverterInfo, fModel, obj, finalTransform, fModel.name, not saveTextures, False, "oot" @@ -123,7 +121,7 @@ def execute(self, context): paths = [ootGetObjectPath(isCustomImport, importPath, folderName)] data = getImportData(paths) - f3dContext = OOTF3DContext(F3D("F3DEX2/LX2", False), [name], basePath) + f3dContext = OOTF3DContext(get_F3D_GBI(), [name], basePath) scale = getOOTScale(settings.actorScale) if not isCustomImport: @@ -170,7 +168,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Mesh not selected.") obj = context.active_object - if type(obj.data) is not Mesh: + if obj.type != "MESH": raise PluginError("Mesh not selected.") finalTransform = Matrix.Scale(getOOTScale(obj.ootActorScale), 4) @@ -181,15 +179,11 @@ def execute(self, context): # context.scene.geoLevelOption) saveTextures = context.scene.saveTextures - isHWv1 = context.scene.isHWv1 - f3dType = context.scene.f3d_type exportSettings = context.scene.fast64.oot.DLExportSettings ootConvertMeshToC( obj, finalTransform, - f3dType, - isHWv1, DLFormat.Static, saveTextures, exportSettings, diff --git a/fast64_internal/oot/oot_collision.py b/fast64_internal/oot/oot_collision.py index 7cad0918a..f8238ed46 100644 --- a/fast64_internal/oot/oot_collision.py +++ b/fast64_internal/oot/oot_collision.py @@ -93,7 +93,7 @@ def updateBounds(position, bounds): def addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, bounds): - if isinstance(obj.data, bpy.types.Mesh) and not obj.ignore_collision: + if obj.type == "MESH" and not obj.ignore_collision: if len(obj.data.materials) == 0: raise PluginError(obj.name + " must have a material associated with it.") obj.data.calc_loop_triangles() diff --git a/fast64_internal/oot/oot_constants.py b/fast64_internal/oot/oot_constants.py index b456d59d0..6c1fbcc66 100644 --- a/fast64_internal/oot/oot_constants.py +++ b/fast64_internal/oot/oot_constants.py @@ -9,71 +9,15 @@ ("ROOM_SHAPE_TYPE_CULLABLE", "Cullable", "Cullable"), ] -ootRoomShapeStructs = [ - "RoomShapeNormal", - "RoomShapeImage", - "RoomShapeCullable", -] - -ootRoomShapeEntryStructs = [ - "RoomShapeDListsEntry", - "RoomShapeDListsEntry", - "RoomShapeCullableEntry", -] - - -ootEnumSceneMenu = [ - ("General", "General", "General"), - ("Lighting", "Lighting", "Lighting"), - ("Cutscene", "Cutscene", "Cutscene"), - ("Exits", "Exits", "Exits"), - ("Alternate", "Alternate", "Alternate"), -] - -ootEnumRenderScene = [ - ("General", "General", "General"), - ("Alternate", "Alternate", "Alternate"), -] - -ootEnumSceneMenuAlternate = [ - ("General", "General", "General"), - ("Lighting", "Lighting", "Lighting"), - ("Cutscene", "Cutscene", "Cutscene"), - ("Exits", "Exits", "Exits"), -] - -ootEnumRoomMenu = [ - ("General", "General", "General"), - ("Objects", "Objects", "Objects"), - ("Alternate", "Alternate", "Alternate"), -] - -ootEnumRoomMenuAlternate = [ - ("General", "General", "General"), - ("Objects", "Objects", "Objects"), -] - ootEnumHeaderMenu = [ ("Child Night", "Child Night", "Child Night"), ("Adult Day", "Adult Day", "Adult Day"), ("Adult Night", "Adult Night", "Adult Night"), ("Cutscene", "Cutscene", "Cutscene"), ] - ootEnumHeaderMenuComplete = [ ("Child Day", "Child Day", "Child Day"), - ("Child Night", "Child Night", "Child Night"), - ("Adult Day", "Adult Day", "Adult Day"), - ("Adult Night", "Adult Night", "Adult Night"), - ("Cutscene", "Cutscene", "Cutscene"), -] - -ootEnumLightGroupMenu = [ - ("Dawn", "Dawn", "Dawn"), - ("Day", "Day", "Day"), - ("Dusk", "Dusk", "Dusk"), - ("Night", "Night", "Night"), -] +] + ootEnumHeaderMenu ootEnumLinkIdle = [ ("Custom", "Custom", "Custom"), @@ -87,21 +31,6 @@ ("0xFF", "Hops On Epona", "Hops On Epona"), ] -# Make sure to add exceptions in utility.py - selectMeshChildrenOnly -ootEnumEmptyType = [ - ("None", "None", "None"), - ("Scene", "Scene", "Scene"), - ("Room", "Room", "Room"), - ("Actor", "Actor", "Actor"), - ("Transition Actor", "Transition Actor", "Transition Actor"), - ("Entrance", "Entrance", "Entrance"), - ("Water Box", "Water Box", "Water Box"), - ("Cull Group", "Custom Cull Group", "Cull Group"), - ("LOD", "LOD Group", "LOD Group"), - ("Cutscene", "Cutscene", "Cutscene"), - # ('Camera Volume', 'Camera Volume', 'Camera Volume'), -] - ootEnumCloudiness = [ ("Custom", "Custom", "Custom"), ("0x00", "Sunny", "Sunny"), @@ -189,114 +118,124 @@ ] ootEnumMusicSeq = [ + # see https://github.com/zeldaret/oot/blob/9f09505d34619883748a7dab05071883281c14fd/include/sequence.h#L4-L118 ("Custom", "Custom", "Custom"), - ("0x02", "Hyrule Field", "Hyrule Field"), - ("0x03", "Hyrule Field (Initial Segment From Loading Area)", "Hyrule Field (Initial Segment From Loading Area)"), - ("0x04", "Hyrule Field (Moving Segment 1)", "Hyrule Field (Moving Segment 1)"), - ("0x05", "Hyrule Field (Moving Segment 2)", "Hyrule Field (Moving Segment 2)"), - ("0x06", "Hyrule Field (Moving Segment 3)", "Hyrule Field (Moving Segment 3)"), - ("0x07", "Hyrule Field (Moving Segment 4)", "Hyrule Field (Moving Segment 4)"), - ("0x08", "Hyrule Field (Moving Segment 5)", "Hyrule Field (Moving Segment 5)"), - ("0x09", "Hyrule Field (Moving Segment 6)", "Hyrule Field (Moving Segment 6)"), - ("0x0A", "Hyrule Field (Moving Segment 7)", "Hyrule Field (Moving Segment 7)"), - ("0x0B", "Hyrule Field (Moving Segment 8)", "Hyrule Field (Moving Segment 8)"), - ("0x0C", "Hyrule Field (Moving Segment 9)", "Hyrule Field (Moving Segment 9)"), - ("0x0D", "Hyrule Field (Moving Segment 10)", "Hyrule Field (Moving Segment 10)"), - ("0x0E", "Hyrule Field (Moving Segment 11)", "Hyrule Field (Moving Segment 11)"), - ("0x0F", "Hyrule Field (Enemy Approaches)", "Hyrule Field (Enemy Approaches)"), - ("0x10", "Hyrule Field (Enemy Near Segment 1)", "Hyrule Field (Enemy Near Segment 1)"), - ("0x11", "Hyrule Field (Enemy Near Segment 2)", "Hyrule Field (Enemy Near Segment 2)"), - ("0x12", "Hyrule Field (Enemy Near Segment 3)", "Hyrule Field (Enemy Near Segment 3)"), - ("0x13", "Hyrule Field (Enemy Near Segment 4)", "Hyrule Field (Enemy Near Segment 4)"), - ("0x14", "Hyrule Field (Standing Still Segment 1)", "Hyrule Field (Standing Still Segment 1)"), - ("0x15", "Hyrule Field (Standing Still Segment 2)", "Hyrule Field (Standing Still Segment 2)"), - ("0x16", "Hyrule Field (Standing Still Segment 3)", "Hyrule Field (Standing Still Segment 3)"), - ("0x17", "Hyrule Field (Standing Still Segment 4)", "Hyrule Field (Standing Still Segment 4)"), - ("0x18", "Dodongo's Cavern", "Dodongo's Cavern"), - ("0x19", "Kakariko Village (Adult)", "Kakariko Village (Adult)"), - ("0x1A", "Enemy Battle", "Enemy Battle"), - ("0x1B", "Boss Battle 00", "Boss Battle 00"), - ("0x1C", "Inside the Deku Tree", "Inside the Deku Tree"), - ("0x1D", "Market", "Market"), - ("0x1E", "Title Theme", "Title Theme"), - ("0x1F", "Link's House", "Link's House"), - ("0x20", "Game Over", "Game Over"), - ("0x21", "Boss Clear", "Boss Clear"), - ("0x22", "Item Get", "Item Get"), - ("0x23", "Opening Ganon", "Opening Ganon"), - ("0x24", "Heart Get", "Heart Get"), - ("0x25", "Prelude Of Light", "Prelude Of Light"), - ("0x26", "Inside Jabu-Jabu's Belly", "Inside Jabu-Jabu's Belly"), - ("0x27", "Kakariko Village (Child)", "Kakariko Village (Child)"), - ("0x28", "Great Fairy's Fountain", "Great Fairy's Fountain"), - ("0x29", "Zelda's Theme", "Zelda's Theme"), - ("0x2A", "Fire Temple", "Fire Temple"), - ("0x2B", "Open Treasure Chest", "Open Treasure Chest"), - ("0x2C", "Forest Temple", "Forest Temple"), - ("0x2D", "Hyrule Castle Courtyard", "Hyrule Castle Courtyard"), - ("0x2E", "Ganondorf's Theme", "Ganondorf's Theme"), - ("0x2F", "Lon Lon Ranch", "Lon Lon Ranch"), - ("0x30", "Goron City", "Goron City "), - ("0x31", "Hyrule Field Morning Theme", "Hyrule Field Morning Theme"), - ("0x32", "Spiritual Stone Get", "Spiritual Stone Get"), - ("0x33", "Bolero of Fire", "Bolero of Fire"), - ("0x34", "Minuet of Woods", "Minuet of Woods"), - ("0x35", "Serenade of Water", "Serenade of Water"), - ("0x36", "Requiem of Spirit", "Requiem of Spirit"), - ("0x37", "Nocturne of Shadow", "Nocturne of Shadow"), - ("0x38", "Mini-Boss Battle", "Mini-Boss Battle"), - ("0x39", "Obtain Small Item", "Obtain Small Item"), - ("0x3A", "Temple of Time", "Temple of Time"), - ("0x3B", "Escape from Lon Lon Ranch", "Escape from Lon Lon Ranch"), - ("0x3C", "Kokiri Forest", "Kokiri Forest"), - ("0x3D", "Obtain Fairy Ocarina", "Obtain Fairy Ocarina"), - ("0x3E", "Lost Woods", "Lost Woods"), - ("0x3F", "Spirit Temple", "Spirit Temple"), - ("0x40", "Horse Race", "Horse Race"), - ("0x41", "Horse Race Goal", "Horse Race Goal"), - ("0x42", "Ingo's Theme", "Ingo's Theme"), - ("0x43", "Obtain Medallion", "Obtain Medallion"), - ("0x44", "Ocarina Saria's Song", "Ocarina Saria's Song"), - ("0x45", "Ocarina Epona's Song", "Ocarina Epona's Song"), - ("0x46", "Ocarina Zelda's Lullaby", "Ocarina Zelda's Lullaby"), - ("0x47", "Sun's Song", "Sun's Song"), - ("0x48", "Song of Time", "Song of Time"), - ("0x49", "Song of Storms", "Song of Storms"), - ("0x4A", "Fairy Flying", "Fairy Flying"), - ("0x4B", "Deku Tree", "Deku Tree"), - ("0x4C", "Windmill Hut", "Windmill Hut"), - ("0x4D", "Legend of Hyrule", "Legend of Hyrule"), - ("0x4E", "Shooting Gallery", "Shooting Gallery"), - ("0x4F", "Sheik's Theme", "Sheik's Theme"), - ("0x50", "Zora's Domain", "Zora's Domain"), - ("0x51", "Enter Zelda", "Enter Zelda"), - ("0x52", "Goodbye to Zelda", "Goodbye to Zelda"), - ("0x53", "Master Sword", "Master Sword"), - ("0x54", "Ganon Intro", "Ganon Intro"), - ("0x55", "Shop", "Shop"), - ("0x56", "Chamber of the Sages", "Chamber of the Sages"), - ("0x57", "File Select", "File Select"), - ("0x58", "Ice Cavern", "Ice Cavern"), - ("0x59", "Open Door of Temple of Time", "Open Door of Temple of Time"), - ("0x5A", "Kaepora Gaebora's Theme", "Kaepora Gaebora's Theme"), - ("0x5B", "Shadow Temple", "Shadow Temple"), - ("0x5C", "Water Temple", "Water Temple"), - ("0x5D", "Ganon's Castle Bridge", "Ganon's Castle Bridge"), - ("0x5E", "Ocarina of Time", "Ocarina of Time"), - ("0x5F", "Gerudo Valley", "Gerudo Valley"), - ("0x60", "Potion Shop", "Potion Shop"), - ("0x61", "Kotake & Koume's Theme", "Kotake & Koume's Theme"), - ("0x62", "Escape from Ganon's Castle", "Escape from Ganon's Castle"), - ("0x63", "Ganon's Castle Under Ground", "Ganon's Castle Under Ground"), - ("0x64", "Ganondorf Battle", "Ganondorf Battle"), - ("0x65", "Ganon Battle", "Ganon Battle"), - ("0x66", "Seal of Six Sages", "Seal of Six Sages"), - ("0x67", "End Credits I", "End Credits I"), - ("0x68", "End Credits II", "End Credits II"), - ("0x69", "End Credits III", "End Credits III"), - ("0x6A", "End Credits IV", "End Credits IV"), - ("0x6B", "King Dodongo & Volvagia Boss Battle", "King Dodongo & Volvagia Boss Battle"), - ("0x6C", "Mini-Game", "Mini-Game"), + ("NA_BGM_GENERAL_SFX", "General Sound Effects", "General Sound Effects"), + ("NA_BGM_NATURE_AMBIENCE", "Nature Ambiance", "Nature Ambiance"), + ("NA_BGM_FIELD_LOGIC", "Hyrule Field", "Hyrule Field"), + ( + "NA_BGM_FIELD_INIT", + "Hyrule Field (Initial Segment From Loading Area)", + "Hyrule Field (Initial Segment From Loading Area)", + ), + ("NA_BGM_FIELD_DEFAULT_1", "Hyrule Field (Moving Segment 1)", "Hyrule Field (Moving Segment 1)"), + ("NA_BGM_FIELD_DEFAULT_2", "Hyrule Field (Moving Segment 2)", "Hyrule Field (Moving Segment 2)"), + ("NA_BGM_FIELD_DEFAULT_3", "Hyrule Field (Moving Segment 3)", "Hyrule Field (Moving Segment 3)"), + ("NA_BGM_FIELD_DEFAULT_4", "Hyrule Field (Moving Segment 4)", "Hyrule Field (Moving Segment 4)"), + ("NA_BGM_FIELD_DEFAULT_5", "Hyrule Field (Moving Segment 5)", "Hyrule Field (Moving Segment 5)"), + ("NA_BGM_FIELD_DEFAULT_6", "Hyrule Field (Moving Segment 6)", "Hyrule Field (Moving Segment 6)"), + ("NA_BGM_FIELD_DEFAULT_7", "Hyrule Field (Moving Segment 7)", "Hyrule Field (Moving Segment 7)"), + ("NA_BGM_FIELD_DEFAULT_8", "Hyrule Field (Moving Segment 8)", "Hyrule Field (Moving Segment 8)"), + ("NA_BGM_FIELD_DEFAULT_9", "Hyrule Field (Moving Segment 9)", "Hyrule Field (Moving Segment 9)"), + ("NA_BGM_FIELD_DEFAULT_A", "Hyrule Field (Moving Segment 10)", "Hyrule Field (Moving Segment 10)"), + ("NA_BGM_FIELD_DEFAULT_B", "Hyrule Field (Moving Segment 11)", "Hyrule Field (Moving Segment 11)"), + ("NA_BGM_FIELD_ENEMY_INIT", "Hyrule Field (Enemy Approaches)", "Hyrule Field (Enemy Approaches)"), + ("NA_BGM_FIELD_ENEMY_1", "Hyrule Field (Enemy Near Segment 1)", "Hyrule Field (Enemy Near Segment 1)"), + ("NA_BGM_FIELD_ENEMY_2", "Hyrule Field (Enemy Near Segment 2)", "Hyrule Field (Enemy Near Segment 2)"), + ("NA_BGM_FIELD_ENEMY_3", "Hyrule Field (Enemy Near Segment 3)", "Hyrule Field (Enemy Near Segment 3)"), + ("NA_BGM_FIELD_ENEMY_4", "Hyrule Field (Enemy Near Segment 4)", "Hyrule Field (Enemy Near Segment 4)"), + ("NA_BGM_FIELD_STILL_1", "Hyrule Field (Standing Still Segment 1)", "Hyrule Field (Standing Still Segment 1)"), + ("NA_BGM_FIELD_STILL_2", "Hyrule Field (Standing Still Segment 2)", "Hyrule Field (Standing Still Segment 2)"), + ("NA_BGM_FIELD_STILL_3", "Hyrule Field (Standing Still Segment 3)", "Hyrule Field (Standing Still Segment 3)"), + ("NA_BGM_FIELD_STILL_4", "Hyrule Field (Standing Still Segment 4)", "Hyrule Field (Standing Still Segment 4)"), + ("NA_BGM_DUNGEON", "Dodongo's Cavern", "Dodongo's Cavern"), + ("NA_BGM_KAKARIKO_ADULT", "Kakariko Village (Adult)", "Kakariko Village (Adult)"), + ("NA_BGM_ENEMY", "Enemy Battle", "Enemy Battle"), + ("NA_BGM_BOSS", "Boss Battle 00", "Boss Battle 00"), + ("NA_BGM_INSIDE_DEKU_TREE", "Inside the Deku Tree", "Inside the Deku Tree"), + ("NA_BGM_MARKET", "Market", "Market"), + ("NA_BGM_TITLE", "Title Theme", "Title Theme"), + ("NA_BGM_LINK_HOUSE", "Link's House", "Link's House"), + ("NA_BGM_GAME_OVER", "Game Over", "Game Over"), + ("NA_BGM_BOSS_CLEAR", "Boss Clear", "Boss Clear"), + ("NA_BGM_ITEM_GET", "Item Get", "Item Get"), + ("NA_BGM_OPENING_GANON", "Opening Ganon", "Opening Ganon"), + ("NA_BGM_HEART_GET", "Heart Get", "Heart Get"), + ("NA_BGM_OCA_LIGHT", "Prelude Of Light", "Prelude Of Light"), + ("NA_BGM_JABU_JABU", "Inside Jabu-Jabu's Belly", "Inside Jabu-Jabu's Belly"), + ("NA_BGM_KAKARIKO_KID", "Kakariko Village (Child)", "Kakariko Village (Child)"), + ("NA_BGM_GREAT_FAIRY", "Great Fairy's Fountain", "Great Fairy's Fountain"), + ("NA_BGM_ZELDA_THEME", "Zelda's Theme", "Zelda's Theme"), + ("NA_BGM_FIRE_TEMPLE", "Fire Temple", "Fire Temple"), + ("NA_BGM_OPEN_TRE_BOX", "Open Treasure Chest", "Open Treasure Chest"), + ("NA_BGM_FOREST_TEMPLE", "Forest Temple", "Forest Temple"), + ("NA_BGM_COURTYARD", "Hyrule Castle Courtyard", "Hyrule Castle Courtyard"), + ("NA_BGM_GANON_TOWER", "Ganondorf's Theme", "Ganondorf's Theme"), + ("NA_BGM_LONLON", "Lon Lon Ranch", "Lon Lon Ranch"), + ("NA_BGM_GORON_CITY", "Goron City", "Goron City"), + ("NA_BGM_FIELD_MORNING", "Hyrule Field Morning Theme", "Hyrule Field Morning Theme"), + ("NA_BGM_SPIRITUAL_STONE", "Spiritual Stone Get", "Spiritual Stone Get"), + ("NA_BGM_OCA_BOLERO", "Bolero of Fire", "Bolero of Fire"), + ("NA_BGM_OCA_MINUET", "Minuet of Woods", "Minuet of Woods"), + ("NA_BGM_OCA_SERENADE", "Serenade of Water", "Serenade of Water"), + ("NA_BGM_OCA_REQUIEM", "Requiem of Spirit", "Requiem of Spirit"), + ("NA_BGM_OCA_NOCTURNE", "Nocturne of Shadow", "Nocturne of Shadow"), + ("NA_BGM_MINI_BOSS", "Mini-Boss Battle", "Mini-Boss Battle"), + ("NA_BGM_SMALL_ITEM_GET", "Obtain Small Item", "Obtain Small Item"), + ("NA_BGM_TEMPLE_OF_TIME", "Temple of Time", "Temple of Time"), + ("NA_BGM_EVENT_CLEAR", "Escape from Lon Lon Ranch", "Escape from Lon Lon Ranch"), + ("NA_BGM_KOKIRI", "Kokiri Forest", "Kokiri Forest"), + ("NA_BGM_OCA_FAIRY_GET", "Obtain Fairy Ocarina", "Obtain Fairy Ocarina"), + ("NA_BGM_SARIA_THEME", "Lost Woods", "Lost Woods"), + ("NA_BGM_SPIRIT_TEMPLE", "Spirit Temple", "Spirit Temple"), + ("NA_BGM_HORSE", "Horse Race", "Horse Race"), + ("NA_BGM_HORSE_GOAL", "Horse Race Goal", "Horse Race Goal"), + ("NA_BGM_INGO", "Ingo's Theme", "Ingo's Theme"), + ("NA_BGM_MEDALLION_GET", "Obtain Medallion", "Obtain Medallion"), + ("NA_BGM_OCA_SARIA", "Ocarina Saria's Song", "Ocarina Saria's Song"), + ("NA_BGM_OCA_EPONA", "Ocarina Epona's Song", "Ocarina Epona's Song"), + ("NA_BGM_OCA_ZELDA", "Ocarina Zelda's Lullaby", "Ocarina Zelda's Lullaby"), + ("NA_BGM_OCA_SUNS", "Sun's Song", "Sun's Song"), + ("NA_BGM_OCA_TIME", "Song of Time", "Song of Time"), + ("NA_BGM_OCA_STORM", "Song of Storms", "Song of Storms"), + ("NA_BGM_NAVI_OPENING", "Fairy Flying", "Fairy Flying"), + ("NA_BGM_DEKU_TREE_CS", "Deku Tree", "Deku Tree"), + ("NA_BGM_WINDMILL", "Windmill Hut", "Windmill Hut"), + ("NA_BGM_HYRULE_CS", "Legend of Hyrule", "Legend of Hyrule"), + ("NA_BGM_MINI_GAME", "Shooting Gallery", "Shooting Gallery"), + ("NA_BGM_SHEIK", "Sheik's Theme", "Sheik's Theme"), + ("NA_BGM_ZORA_DOMAIN", "Zora's Domain", "Zora's Domain"), + ("NA_BGM_APPEAR", "Enter Zelda", "Enter Zelda"), + ("NA_BGM_ADULT_LINK", "Goodbye to Zelda", "Goodbye to Zelda"), + ("NA_BGM_MASTER_SWORD", "Master Sword", "Master Sword"), + ("NA_BGM_INTRO_GANON", "Ganon Intro", "Ganon Intro"), + ("NA_BGM_SHOP", "Shop", "Shop"), + ("NA_BGM_CHAMBER_OF_SAGES", "Chamber of the Sages", "Chamber of the Sages"), + ("NA_BGM_FILE_SELECT", "File Select", "File Select"), + ("NA_BGM_ICE_CAVERN", "Ice Cavern", "Ice Cavern"), + ("NA_BGM_DOOR_OF_TIME", "Open Door of Temple of Time", "Open Door of Temple of Time"), + ("NA_BGM_OWL", "Kaepora Gaebora's Theme", "Kaepora Gaebora's Theme"), + ("NA_BGM_SHADOW_TEMPLE", "Shadow Temple", "Shadow Temple"), + ("NA_BGM_WATER_TEMPLE", "Water Temple", "Water Temple"), + ("NA_BGM_BRIDGE_TO_GANONS", "Ganon's Castle Bridge", "Ganon's Castle Bridge"), + ("NA_BGM_OCARINA_OF_TIME", "Ocarina of Time", "Ocarina of Time"), + ("NA_BGM_GERUDO_VALLEY", "Gerudo Valley", "Gerudo Valley"), + ("NA_BGM_POTION_SHOP", "Potion Shop", "Potion Shop"), + ("NA_BGM_KOTAKE_KOUME", "Kotake & Koume's Theme", "Kotake & Koume's Theme"), + ("NA_BGM_ESCAPE", "Escape from Ganon's Castle", "Escape from Ganon's Castle"), + ("NA_BGM_UNDERGROUND", "Ganon's Castle Under Ground", "Ganon's Castle Under Ground"), + ("NA_BGM_GANONDORF_BOSS", "Ganondorf Battle", "Ganondorf Battle"), + ("NA_BGM_GANON_BOSS", "Ganon Battle", "Ganon Battle"), + ("NA_BGM_END_DEMO", "Seal of Six Sages", "Seal of Six Sages"), + ("NA_BGM_STAFF_1", "End Credits I", "End Credits I"), + ("NA_BGM_STAFF_2", "End Credits II", "End Credits II"), + ("NA_BGM_STAFF_3", "End Credits III", "End Credits III"), + ("NA_BGM_STAFF_4", "End Credits IV", "End Credits IV"), + ("NA_BGM_FIRE_BOSS", "King Dodongo & Volvagia Boss Battle", "King Dodongo & Volvagia Boss Battle"), + ("NA_BGM_TIMED_MINI_GAME", "Mini-Game", "Mini-Game"), + ("NA_BGM_CUTSCENE_EFFECTS", "Various Cutscene Sounds", "Various Cutscene Sounds"), + ("NA_BGM_NO_MUSIC", "No Music", "No Music"), + ("NA_BGM_NATURE_SFX_RAIN", "Nature Ambiance: Rain", "Nature Ambiance: Rain"), ] ootEnumNightSeq = [ @@ -348,22 +287,6 @@ ("0x02", "Dungeon", "elf_message_ydan"), ] -ootEnumTransitionAnims = [ - ("Custom", "Custom", "Custom"), - ("0x00", "Spiky", "Spiky"), - ("0x01", "Triforce", "Triforce"), - ("0x02", "Slow Black Fade", "Slow Black Fade"), - ("0x03", "Slow Day/White, Slow Night/Black Fade", "Slow Day/White, Slow Night/Black Fade"), - ("0x04", "Fast Day/Black, Slow Night/Black Fade", "Fast Day/Black, Slow Night/Black Fade"), - ("0x05", "Fast Day/White, Slow Night/Black Fade", "Fast Day/White, Slow Night/Black Fade"), - ("0x06", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"), - ("0x07", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"), - ("0x0E", "Slow Sandstorm Fade", "Slow Sandstorm Fade"), - ("0x0F", "Fast Sandstorm Fade", "Fast Sandstorm Fade"), - ("0x20", "Iris Fade", "Iris Fade"), - ("0x2C", "Shortcut Transition", "Shortcut Transition"), -] - # The order of this list matters (normal OoT scene order as defined by ``scene_table.h``) ootEnumSceneID = [ ("Custom", "Custom", "Custom"), @@ -599,6 +522,7 @@ "SCENE_SASATEST": "sasatest", "SCENE_TESTROOM": "testroom", } +ootSceneNameToID = {val: key for key, val in ootSceneIDToName.items()} ootEnumCamTransition = [ ("Custom", "Custom", "Custom"), @@ -607,7 +531,7 @@ # ("0xFF", "0xFF", "0xFF"), ] -# see curRoom.unk_03 +# see curRoom.behaviorType1 ootEnumRoomBehaviour = [ ("Custom", "Custom", "Custom"), ("0x00", "Default", "Default"), @@ -618,17 +542,6 @@ ("0x05", "Disable Darker Screen Effect (NL/Spins)", "Disable Darker Screen Effect (NL/Spins)"), ] -ootEnumExitIndex = [ - ("Custom", "Custom", "Custom"), - ("Default", "Default", "Default"), -] - -ootEnumSceneSetupPreset = [ - ("Custom", "Custom", "Custom"), - ("All Scene Setups", "All Scene Setups", "All Scene Setups"), - ("All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups"), -] - ootEnumDrawConfig = [ ("Custom", "Custom", "Custom"), ("SDC_DEFAULT", "Default", "Default"), @@ -689,5 +602,3 @@ ("SDC_GANONS_TOWER_COLLAPSE_INTERIOR", "Ganon's Tower (Collapsing) (Ganon Sonogo)", "Ganon Sonogo"), ("SDC_INSIDE_GANONS_CASTLE_COLLAPSE", "Inside Ganon's Castle (Collapsing) (Ganontika Sonogo)", "Ganontika Sonogo"), ] - -ootSceneNameToID = {val: key for key, val in ootSceneIDToName.items()} diff --git a/fast64_internal/oot/oot_level_classes.py b/fast64_internal/oot/oot_level_classes.py index 185d03fb5..c6714fbab 100644 --- a/fast64_internal/oot/oot_level_classes.py +++ b/fast64_internal/oot/oot_level_classes.py @@ -1,5 +1,9 @@ -import bpy, os, shutil +import bpy +import os +import shutil + from typing import Optional +from bpy.types import Object from ..utility import PluginError, toAlnum, indent from .oot_collision_classes import OOTCollision from .oot_model_classes import OOTModel @@ -111,16 +115,10 @@ def __init__(self, name, model): self.cameraList = [] self.writeCutscene = False - self.csWriteType = "Embedded" + self.csWriteType = "Object" + self.csName = "" self.csWriteCustom = "" - self.csWriteObject = None - self.csEndFrame = 100 - self.csWriteTerminator = False - self.csTermIdx = 0 - self.csTermStart = 99 - self.csTermEnd = 100 - self.csLists = [] - self.extraCutscenes = [] + self.extraCutscenes: list[Object] = [] self.sceneTableEntry = OOTSceneTableEntry() @@ -161,9 +159,6 @@ def pathListName(self, headerIndex: int): def cameraListName(self): return self.sceneName() + "_cameraList" - def cutsceneDataName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_cutscene" - def alternateHeadersName(self): return self.sceneName() + "_alternateHeaders" @@ -210,9 +205,7 @@ def validatePathIndices(self): count = count + 1 def addRoom(self, roomIndex, roomName, roomShape): - roomModel = self.model.addSubModel( - OOTModel(self.model.f3d.F3D_VER, self.model.f3d._HW_VERSION_1, roomName + "_dl", self.model.DLFormat, None) - ) + roomModel = self.model.addSubModel(OOTModel(roomName + "_dl", self.model.DLFormat, None)) room = OOTRoom(roomIndex, roomName, roomModel, roomShape) if roomIndex in self.rooms: raise PluginError("Repeat room index " + str(roomIndex) + " for " + str(roomName)) diff --git a/fast64_internal/oot/oot_level_parser.py b/fast64_internal/oot/oot_level_parser.py index 887f090af..d43e2ffed 100644 --- a/fast64_internal/oot/oot_level_parser.py +++ b/fast64_internal/oot/oot_level_parser.py @@ -3,7 +3,7 @@ from collections import OrderedDict from ..utility import PluginError, readFile, parentObject, hexOrDecInt, gammaInverse, yUpToZUp from ..f3d.f3d_parser import parseMatrices, importMeshC -from ..f3d.f3d_gbi import F3D +from ..f3d.f3d_gbi import F3D, get_F3D_GBI from ..f3d.flipbook import TextureFlipbook from .collision.properties import OOTMaterialCollisionProperty from .oot_model_classes import OOTF3DContext @@ -12,6 +12,7 @@ from .scene.properties import OOTSceneHeaderProperty, OOTLightProperty, OOTImportSceneSettingsProperty from .room.properties import OOTRoomHeaderProperty from .actor.properties import OOTActorProperty, OOTActorHeaderProperty +from .cutscene.importer import importCutsceneData from .oot_utility import ( getHeaderSettings, @@ -147,6 +148,7 @@ def __init__( includeCameras: bool, includePaths: bool, includeWaterBoxes: bool, + includeCutscenes: bool, ): self.actorDict = {} # actor hash : blender object self.entranceDict = {} # actor hash : blender object @@ -162,6 +164,7 @@ def __init__( self.includeCameras = includeCameras self.includePaths = includePaths self.includeWaterBoxes = includeWaterBoxes + self.includeCutscenes = includeCutscenes def addHeaderIfItemExists(self, hash, itemType: str, headerIndex: int): if itemType == "Actor": @@ -192,8 +195,6 @@ def addHeaderIfItemExists(self, hash, itemType: str, headerIndex: int): def parseScene( - f3dType: str, - isHWv1: bool, settings: OOTImportSceneSettingsProperty, option: str, ): @@ -216,7 +217,8 @@ def parseScene( importSubdir = os.path.dirname(getSceneDirFromLevelName(sceneName)) + "/" sceneFolderPath = ootGetPath(importPath, settings.isCustomDest, importSubdir, sceneName, False, True) - sceneData = readFile(os.path.join(sceneFolderPath, f"{sceneName}_scene.c")) + filePath = os.path.join(sceneFolderPath, f"{sceneName}_scene.c") + sceneData = readFile(filePath) # roomData = "" # sceneFolderFiles = [f for f in listdir(sceneFolderPath) if isfile(join(sceneFolderPath, f))] @@ -231,7 +233,7 @@ def parseScene( bpy.context.mode = "OBJECT" # set scene default registers (see sDefaultDisplayList) - f3dContext = OOTF3DContext(F3D(f3dType, isHWv1), [], bpy.path.abspath(bpy.context.scene.ootDecompPath)) + f3dContext = OOTF3DContext(get_F3D_GBI(), [], bpy.path.abspath(bpy.context.scene.ootDecompPath)) f3dContext.mat().prim_color = (0.5, 0.5, 0.5, 0.5) f3dContext.mat().env_color = (0.5, 0.5, 0.5, 0.5) @@ -260,7 +262,12 @@ def parseScene( settings.includeCameras, settings.includePaths, settings.includeWaterBoxes, + settings.includeCutscenes, ) + + if settings.includeCutscenes: + bpy.context.scene.ootCSNumber = importCutsceneData(None, sceneData) + sceneObj = parseSceneCommands(sceneName, None, None, sceneCommandsName, sceneData, f3dContext, 0, sharedSceneData) bpy.context.scene.ootSceneExportObj = sceneObj @@ -363,9 +370,14 @@ def parseSceneCommands( if not (args[1] == "NULL" or args[1] == "0" or args[1] == "0x00"): lightsListName = stripName(args[1]) parseLightList(sceneObj, sceneHeader, sceneData, lightsListName, headerIndex) - elif command == "SCENE_CMD_CUTSCENE_DATA": - cutsceneName = args[0] - print("Cutscene command parsing not implemented.") + elif command == "SCENE_CMD_CUTSCENE_DATA" and sharedSceneData.includeCutscenes: + sceneHeader.writeCutscene = True + sceneHeader.csWriteType = "Object" + csObjName = f"Cutscene.{args[0]}" + try: + sceneHeader.csWriteObject = bpy.data.objects[csObjName] + except: + print(f"ERROR: Cutscene ``{csObjName}`` do not exist!") elif command == "SCENE_CMD_ALTERNATE_HEADER_LIST": # Delay until after rooms are parsed altHeadersListName = stripName(args[0]) @@ -715,12 +727,16 @@ def parseTransActorList( sharedSceneData.transDict[actorHash] = actorObj - if roomIndexFront != 255: - parentObject(roomObjs[roomIndexFront], actorObj) - transActorProp.roomIndex = roomIndexBack + fromRoom = roomObjs[roomIndexFront] + toRoom = roomObjs[roomIndexBack] + if roomIndexFront != roomIndexBack: + parentObject(fromRoom, actorObj) + transActorProp.fromRoom = fromRoom + transActorProp.toRoom = toRoom + transActorProp.isRoomTransition = True else: - parentObject(roomObjs[roomIndexBack], actorObj) - transActorProp.dontTransition = True + transActorProp.isRoomTransition = False + parentObject(toRoom, actorObj) setCustomProperty(transActorProp, "cameraTransitionFront", camFront, ootEnumCamTransition) setCustomProperty(transActorProp, "cameraTransitionBack", camBack, ootEnumCamTransition) @@ -808,6 +824,7 @@ def parseSpawnList( spawnObj.ootEmptyType = "Entrance" spawnObj.name = "Entrance" spawnProp = spawnObj.ootEntranceProperty + spawnProp.tiedRoom = roomObjs[roomIndex] spawnProp.spawnIndex = spawnIndex spawnProp.customActor = actorID != "ACTOR_PLAYER" actorProp = spawnProp.actor diff --git a/fast64_internal/oot/oot_level_writer.py b/fast64_internal/oot/oot_level_writer.py index 33da8d6bb..35da2d588 100644 --- a/fast64_internal/oot/oot_level_writer.py +++ b/fast64_internal/oot/oot_level_writer.py @@ -1,9 +1,9 @@ import bpy, os, math, mathutils from ..f3d.f3d_gbi import TextureExportSettings from ..f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict +from .scene.properties import OOTSceneProperties, OOTSceneHeaderProperty, OOTAlternateSceneHeaderProperty from .room.properties import OOTRoomHeaderProperty, OOTAlternateRoomHeaderProperty from .oot_constants import ootData -from .cutscene.exporter import convertCutsceneObject, readCutsceneData from .oot_spline import assertCurveValid, ootConvertPath from .oot_model_classes import OOTModel from .oot_collision import OOTCameraData, exportCollisionCommon @@ -104,14 +104,12 @@ def ootCombineSceneFiles(levelC): return sceneC -def ootExportSceneToC( - originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, savePNG, exportInfo, bootToSceneOptions -): +def ootExportSceneToC(originalSceneObj, transformMatrix, sceneName, DLFormat, savePNG, exportInfo, bootToSceneOptions): checkObjectReference(originalSceneObj, "Scene object") isCustomExport = exportInfo.isCustomExportPath exportPath = exportInfo.exportPath - scene = ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, not savePNG) + scene = ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, not savePNG) exportSubdir = "" if exportInfo.customSubPath is not None: @@ -120,7 +118,7 @@ def ootExportSceneToC( exportSubdir = os.path.dirname(getSceneDirFromLevelName(sceneName)) roomObjList = [ - obj for obj in originalSceneObj.children_recursive if obj.data is None and obj.ootEmptyType == "Room" + obj for obj in originalSceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" ] for roomObj in roomObjList: room = scene.rooms[roomObj.ootRoomHeader.roomIndex] @@ -229,7 +227,12 @@ def writeOtherSceneProperties(scene, exportInfo, levelC): modifySceneFiles(scene, exportInfo) -def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): +def readSceneData( + scene: OOTScene, + scene_properties: OOTSceneProperties, + sceneHeader: OOTSceneHeaderProperty, + alternateSceneHeaders: OOTAlternateSceneHeaderProperty, +): scene.write_dummy_room_list = scene_properties.write_dummy_room_list scene.sceneTableEntry.drawConfig = getCustomProperty(sceneHeader.sceneTableEntry, "drawConfig") scene.globalObject = getCustomProperty(sceneHeader, "globalObject") @@ -264,16 +267,10 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): scene.writeCutscene = getCustomProperty(sceneHeader, "writeCutscene") if scene.writeCutscene: scene.csWriteType = getattr(sceneHeader, "csWriteType") - if scene.csWriteType == "Embedded": - scene.csEndFrame = getCustomProperty(sceneHeader, "csEndFrame") - scene.csWriteTerminator = getCustomProperty(sceneHeader, "csWriteTerminator") - scene.csTermIdx = getCustomProperty(sceneHeader, "csTermIdx") - scene.csTermStart = getCustomProperty(sceneHeader, "csTermStart") - scene.csTermEnd = getCustomProperty(sceneHeader, "csTermEnd") - readCutsceneData(scene, sceneHeader) - elif scene.csWriteType == "Custom": + + if scene.csWriteType == "Custom": scene.csWriteCustom = getCustomProperty(sceneHeader, "csWriteCustom") - elif scene.csWriteType == "Object": + else: if sceneHeader.csWriteObject is None: raise PluginError("No object selected for cutscene reference") elif sceneHeader.csWriteObject.ootEmptyType != "Cutscene": @@ -281,12 +278,9 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): elif sceneHeader.csWriteObject.parent is not None: raise PluginError("Cutscene empty object should not be parented to anything") else: - scene.csWriteObject = convertCutsceneObject(sceneHeader.csWriteObject) + scene.csName = sceneHeader.csWriteObject.name.removeprefix("Cutscene.") if alternateSceneHeaders is not None: - for ec in sceneHeader.extraCutscenes: - scene.extraCutscenes.append(convertCutsceneObject(ec.csObject)) - scene.collision.cameraData = OOTCameraData(scene.name) if not alternateSceneHeaders.childNightHeader.usePreviousHeader: @@ -306,6 +300,9 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): cutsceneHeader = scene.getAlternateHeaderScene(scene.name) readSceneData(cutsceneHeader, scene_properties, cutsceneHeaderProp, None) scene.cutsceneHeaders.append(cutsceneHeader) + + for extraCS in sceneHeader.extraCutscenes: + scene.extraCutscenes.append(extraCS.csObject) else: if len(sceneHeader.extraCutscenes) > 0: raise PluginError( @@ -492,8 +489,8 @@ def readPathProp(pathProp, obj, scene, sceneObj, sceneName, transformMatrix): addActor(scene, ootConvertPath(sceneName, obj, relativeTransform), obj.ootSplineProperty, "pathList", obj.name) -def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, convertTextureData): - if originalSceneObj.data is not None or originalSceneObj.ootEmptyType != "Scene": +def ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, convertTextureData): + if originalSceneObj.type != "EMPTY" or originalSceneObj.ootEmptyType != "Scene": raise PluginError(originalSceneObj.name + ' is not an empty with the "Scene" empty type.') if bpy.context.scene.exportHiddenGeometry: @@ -505,19 +502,21 @@ def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneNam if bpy.context.scene.exportHiddenGeometry: restoreHiddenState(hiddenState) - roomObjs = [child for child in sceneObj.children_recursive if child.data is None and child.ootEmptyType == "Room"] + roomObjs = [ + child for child in sceneObj.children_recursive if child.type == "EMPTY" and child.ootEmptyType == "Room" + ] if len(roomObjs) == 0: raise PluginError("The scene has no child empties with the 'Room' empty type.") try: - scene = OOTScene(sceneName, OOTModel(f3dType, isHWv1, sceneName + "_dl", DLFormat, None)) + scene = OOTScene(sceneName, OOTModel(sceneName + "_dl", DLFormat, None)) readSceneData(scene, sceneObj.fast64.oot.scene, sceneObj.ootSceneHeader, sceneObj.ootAlternateSceneHeaders) processedRooms = set() for obj in sceneObj.children_recursive: translation, rotation, scale, orientedRotation = getConvertedTransform(transformMatrix, sceneObj, obj, True) - if obj.data is None and obj.ootEmptyType == "Room": + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": roomObj = obj roomHeader = roomObj.ootRoomHeader roomIndex = roomHeader.roomIndex @@ -545,13 +544,13 @@ def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneNam room.mesh.terminateDLs() room.mesh.removeUnusedEntries() ootProcessEmpties(scene, room, sceneObj, roomObj, transformMatrix) - elif obj.data is None and obj.ootEmptyType == "Water Box": + elif obj.type == "EMPTY" and obj.ootEmptyType == "Water Box": # 0x3F = -1 in 6bit value ootProcessWaterBox(sceneObj, obj, transformMatrix, scene, 0x3F) - elif isinstance(obj.data, bpy.types.Camera): + elif obj.type == "CAMERA": camPosProp = obj.ootCameraPositionProperty readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix) - elif isinstance(obj.data, bpy.types.Curve) and assertCurveValid(obj): + elif obj.type == "CURVE" and assertCurveValid(obj): if isPathObject(obj): readPathProp(obj.ootSplineProperty, obj, scene, sceneObj, sceneName, transformMatrix) else: @@ -620,7 +619,7 @@ def ootProcessMesh( relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world translation, rotation, scale = relativeTransform.decompose() - if obj.data is None and obj.ootEmptyType == "Cull Group": + if obj.type == "EMPTY" and obj.ootEmptyType == "Cull Group": if LODHierarchyObject is not None: raise PluginError( obj.name @@ -639,7 +638,7 @@ def ootProcessMesh( ) ).DLGroup - elif isinstance(obj.data, bpy.types.Mesh) and not obj.ignore_render: + elif obj.type == "MESH" and not obj.ignore_render: triConverterInfo = TriangleConverterInfo(obj, None, roomMesh.model.f3d, relativeTransform, getInfoDict(obj)) fMeshes = saveStaticModel( triConverterInfo, @@ -659,7 +658,7 @@ def ootProcessMesh( alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) for childObj in alphabeticalChildren: - if childObj.data is None and childObj.ootEmptyType == "LOD": + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": ootProcessLOD( roomMesh, DLGroup, @@ -703,7 +702,7 @@ def ootProcessLOD( childDLGroup = OOTDLGroup(name + str(index), roomMesh.model.DLFormat) index += 1 - if childObj.data is None and childObj.ootEmptyType == "LOD": + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": ootProcessLOD( roomMesh, childDLGroup, @@ -745,7 +744,7 @@ def ootProcessLOD( def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix): translation, rotation, scale, orientedRotation = getConvertedTransform(transformMatrix, sceneObj, obj, True) - if obj.data is None: + if obj.type == "EMPTY": if obj.ootEmptyType == "Actor": actorProp = obj.ootActorProperty @@ -784,12 +783,15 @@ def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix): elif obj.ootEmptyType == "Transition Actor": transActorProp = obj.ootTransitionActorProperty if transActorProp.actor.actorID != "None": - if transActorProp.dontTransition: - front = (255, getCustomProperty(transActorProp, "cameraTransitionBack")) - back = (room.roomIndex, getCustomProperty(transActorProp, "cameraTransitionFront")) + if transActorProp.isRoomTransition: + if transActorProp.fromRoom is None or transActorProp.toRoom is None: + raise PluginError("ERROR: Missing room empty object assigned to transition.") + fromIndex = transActorProp.fromRoom.ootRoomHeader.roomIndex + toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex else: - front = (room.roomIndex, getCustomProperty(transActorProp, "cameraTransitionFront")) - back = (transActorProp.roomIndex, getCustomProperty(transActorProp, "cameraTransitionBack")) + fromIndex = toIndex = room.roomIndex + front = (fromIndex, getCustomProperty(transActorProp, "cameraTransitionFront")) + back = (toIndex, getCustomProperty(transActorProp, "cameraTransitionBack")) transActorName = ( ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( @@ -818,8 +820,14 @@ def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix): ) elif obj.ootEmptyType == "Entrance": entranceProp = obj.ootEntranceProperty - spawnIndex = obj.ootEntranceProperty.spawnIndex - addActor(scene, OOTEntrance(room.roomIndex, spawnIndex), entranceProp.actor, "entranceList", obj.name) + spawnIndex = entranceProp.spawnIndex + + if entranceProp.tiedRoom is not None: + roomIndex = entranceProp.tiedRoom.ootRoomHeader.roomIndex + else: + raise PluginError("ERROR: Missing room empty object assigned to the entrance.") + + addActor(scene, OOTEntrance(roomIndex, spawnIndex), entranceProp.actor, "entranceList", obj.name) addStartPosition( scene, spawnIndex, @@ -835,10 +843,10 @@ def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix): ) elif obj.ootEmptyType == "Water Box": ootProcessWaterBox(sceneObj, obj, transformMatrix, scene, room.roomIndex) - elif isinstance(obj.data, bpy.types.Camera): + elif obj.type == "CAMERA": camPosProp = obj.ootCameraPositionProperty readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix) - elif isinstance(obj.data, bpy.types.Curve) and assertCurveValid(obj): + elif obj.type == "CURVE" and assertCurveValid(obj): if isPathObject(obj): readPathProp(obj.ootSplineProperty, obj, scene, sceneObj, scene.name, transformMatrix) else: diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index cee3b87b0..5d0b77869 100644 --- a/fast64_internal/oot/oot_model_classes.py +++ b/fast64_internal/oot/oot_model_classes.py @@ -95,11 +95,11 @@ def ootGetLinkData(basePath: str) -> str: class OOTModel(FModel): - def __init__(self, f3dType, isHWv1, name, DLFormat, drawLayerOverride): + def __init__(self, name, DLFormat, drawLayerOverride): self.drawLayerOverride = drawLayerOverride self.flipbooks: list[TextureFlipbook] = [] - FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteAll) + FModel.__init__(self, name, DLFormat, GfxMatWriteMethod.WriteAll) # Since dynamic textures are handled by scene draw config, flipbooks should only belong to scene model. # Thus we have this function. diff --git a/fast64_internal/oot/oot_upgrade.py b/fast64_internal/oot/oot_upgrade.py index 33a5237b7..b3dfd90de 100644 --- a/fast64_internal/oot/oot_upgrade.py +++ b/fast64_internal/oot/oot_upgrade.py @@ -1,7 +1,18 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING from bpy.types import Object, CollectionProperty from .data import OoT_ObjectData +from .oot_utility import getEvalParams +from .oot_constants import ootData +from .cutscene.constants import ootEnumCSMotionCamMode +if TYPE_CHECKING: + from .cutscene.properties import OOTCutsceneProperty + +##################################### +# Room Header +##################################### def upgradeObjectList(objList: CollectionProperty, objData: OoT_ObjectData): """Transition to the XML object system""" for obj in objList: @@ -34,3 +45,284 @@ def upgradeRoomHeaders(roomObj: Object, objData: OoT_ObjectData): upgradeObjectList(sceneLayer.objectList, objData) for i in range(len(altHeaders.cutsceneHeaders)): upgradeObjectList(altHeaders.cutsceneHeaders[i].objectList, objData) + + +##################################### +# Cutscene +##################################### +@dataclass +class Cutscene_UpgradeData: + oldPropName: str + newPropName: str + enumData: list[tuple[str, str, str]] # this is the list used for enum properties + + +def transferOldDataToNew(data, oldDataToNewData: dict[str, str]): + # conversion to the same prop type + # simply transfer the old data to the new one + for oldName, newName in oldDataToNewData.items(): + if oldName in data: + if newName is not None: + value = data[oldName] + + # special case for rumble subprops where it's a string to int conversion + # another special case for light setting index where the value need to be minus one + if newName in ["rumbleSourceStrength", "rumbleDuration", "rumbleDecreaseRate"]: + value = int(getEvalParams(data[oldName]), base=16) + elif newName == "lightSettingsIndex": + value -= 1 + + data[newName] = value + + del data[oldName] + + +def convertOldDataToEnumData(data, oldDataToEnumData: list[Cutscene_UpgradeData]): + # conversion to another prop type + for csUpgradeData in oldDataToEnumData: + if csUpgradeData.oldPropName in data: + # get the old data + oldData = data[csUpgradeData.oldPropName] + + # if anything goes wrong there set the value to custom to avoid any data loss + try: + if isinstance(oldData, str): + # get the value, doing an eval for strings + # account for custom elements in the enums by adding 1 + value = int(getEvalParams(oldData), base=16) + 1 + + # special cases for ocarina action enum + # since we don't have everything the value need to be shifted + if csUpgradeData.newPropName == "ocarinaAction": + if value in [0x00, 0x01, 0x0E] or value > 0x1A: + raise IndexError + + if value > 0x0E: + value -= 1 + + value -= 2 + + if csUpgradeData.newPropName == "csSeqID": + # the old fade out value is wrong, it assumes it's a seq id + # but it's not, it's a seq player id, + # hence why we raise an error so it defaults to "custom" to avoid any data loss + # @TODO: find a way to check properly which seq command it is + raise NotImplementedError + elif isinstance(oldData, int): + # account for custom elements in the enums by adding 1 + value = oldData + 1 + + # another special case, this time for the misc enum + if csUpgradeData.newPropName == "csMiscType": + if value in [0x00, 0x04, 0x05]: + raise IndexError + + if value > 0x05: + value -= 2 + + value -= 1 + else: + raise NotImplementedError + + # if the value is in the list find the identifier + if value < len(csUpgradeData.enumData): + setattr(data, csUpgradeData.newPropName, csUpgradeData.enumData[value][0]) + else: + # else raise an error to default to custom + raise IndexError + except: + setattr(data, csUpgradeData.newPropName, "Custom") + setattr(data, f"{csUpgradeData.newPropName}Custom", str(oldData)) + + # @TODO: find a way to check properly which seq command it is + if csUpgradeData.newPropName == "csSeqID": + setattr(data, "csSeqPlayer", "Custom") + setattr(data, "csSeqPlayerCustom", str(oldData)) + + del data[csUpgradeData.oldPropName] + + +def upgradeCutsceneSubProps(csListSubProp): + # ``csListSubProp`` types: OOTCSTextProperty | OOTCSSeqProperty | OOTCSMiscProperty | OOTCSRumbleProperty + # based on ``upgradeObjectList`` + + subPropsOldToNew = { + # TextBox + "messageId": "textID", + "topOptionBranch": "topOptionTextID", + "bottomOptionBranch": "bottomOptionTextID", + # Lighting + "index": "lightSettingsIndex", + # Rumble + "unk2": "rumbleSourceStrength", + "unk3": "rumbleDuration", + "unk4": "rumbleDecreaseRate", + # Unk (Deprecated) + "unk": None, + "unkType": None, + "unk1": None, + "unk2": None, + "unk3": None, + "unk4": None, + "unk5": None, + "unk6": None, + "unk7": None, + "unk8": None, + "unk9": None, + "unk10": None, + "unk11": None, + "unk12": None, + } + + subPropsToEnum = [ + # TextBox + Cutscene_UpgradeData("ocarinaSongAction", "ocarinaAction", ootData.enumData.ootEnumOcarinaSongActionId), + Cutscene_UpgradeData("type", "csTextType", ootData.enumData.ootEnumCsTextType), + # Seq + Cutscene_UpgradeData("value", "csSeqID", ootData.enumData.ootEnumSeqId), + # Misc + Cutscene_UpgradeData("operation", "csMiscType", ootData.enumData.ootEnumCsMiscType), + ] + + transferOldDataToNew(csListSubProp, subPropsOldToNew) + convertOldDataToEnumData(csListSubProp, subPropsToEnum) + + +def upgradeCSListProps(csListProp): + # ``csListProp`` type: ``OOTCSListProperty`` + + csListPropOldToNew = { + "textbox": "textList", + "lighting": "lightSettingsList", + "time": "timeList", + "bgm": "seqList", + "misc": "miscList", + "nine": "rumbleList", + "fxStartFrame": "transitionStartFrame", + "fxEndFrame": "transitionEndFrame", + } + + transferOldDataToNew(csListProp, csListPropOldToNew) + + # both are enums but the item list is different (the old one doesn't have a "custom" entry) + convertOldDataToEnumData( + csListProp, [Cutscene_UpgradeData("fxType", "transitionType", ootData.enumData.ootEnumCsTransitionType)] + ) + + +def upgradeCutsceneProperty(csProp: "OOTCutsceneProperty"): + csPropOldToNew = { + "csWriteTerminator": "csUseDestination", + "csTermStart": "csDestinationStartFrame", + "csTermEnd": None, + } + + transferOldDataToNew(csProp, csPropOldToNew) + convertOldDataToEnumData( + csProp, [Cutscene_UpgradeData("csTermIdx", "csDestination", ootData.enumData.ootEnumCsDestination)] + ) + + +def upgradeCutsceneMotion(csMotionObj: Object): + """Main upgrade logic for Cutscene Motion data from zcamedit""" + objName = csMotionObj.name + + if csMotionObj.type == "EMPTY": + csMotionProp = csMotionObj.ootCSMotionProperty + + if "zc_alist" in csMotionObj and ("Preview." in objName or "ActionList." in objName): + legacyData = csMotionObj["zc_alist"] + emptyTypeSuffix = "List" if "ActionList." in objName else "Preview" + csMotionObj.ootEmptyType = f"CS {'Player' if 'Link' in objName else 'Actor'} Cue {emptyTypeSuffix}" + + if "actor_id" in legacyData: + index = legacyData["actor_id"] + if index >= 0: + cmdEnum = ootData.enumData.enumByKey["csCmd"] + cmdType = cmdEnum.itemByIndex.get(index) + if cmdType is not None: + csMotionProp.actorCueListProp.commandType = cmdType.key + else: + csMotionProp.actorCueListProp.commandType = "Custom" + csMotionProp.actorCueListProp.commandTypeCustom = f"0x{index:04X}" + del legacyData["actor_id"] + + del csMotionObj["zc_alist"] + + if "zc_apoint" in csMotionObj and "Point." in objName: + isPlayer = "Link" in csMotionObj.parent.name + legacyData = csMotionObj["zc_apoint"] + csMotionObj.ootEmptyType = f"CS {'Player' if isPlayer else 'Actor'} Cue" + + if "start_frame" in legacyData: + csMotionProp.actorCueProp.cueStartFrame = legacyData["start_frame"] + del legacyData["start_frame"] + + if "action_id" in legacyData: + playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] + item = None + if isPlayer: + item = playerEnum.itemByIndex.get(int(legacyData["action_id"], base=16)) + + if isPlayer and item is not None: + csMotionProp.actorCueProp.playerCueID = item.key + else: + csMotionProp.actorCueProp.cueActionID = legacyData["action_id"] + del legacyData["action_id"] + + del csMotionObj["zc_apoint"] + + if csMotionObj.type == "ARMATURE": + camShotProp = csMotionObj.data.ootCamShotProp + + if "start_frame" in csMotionObj.data: + camShotProp.shotStartFrame = csMotionObj.data["start_frame"] + del csMotionObj.data["start_frame"] + + if "cam_mode" in csMotionObj.data: + camShotProp.shotCamMode = ootEnumCSMotionCamMode[csMotionObj.data["cam_mode"]][0] + del csMotionObj.data["cam_mode"] + + for bone in csMotionObj.data.bones: + camShotPointProp = bone.ootCamShotPointProp + + if "frames" in bone: + camShotPointProp.shotPointFrame = bone["frames"] + del bone["frames"] + + if "fov" in bone: + camShotPointProp.shotPointViewAngle = bone["fov"] + del bone["fov"] + + if "camroll" in bone: + camShotPointProp.shotPointRoll = bone["camroll"] + del bone["camroll"] + + +##################################### +# Actors +##################################### +def upgradeActors(actorObj: Object): + if actorObj.ootEmptyType == "Entrance": + entranceProp = actorObj.ootEntranceProperty + + for obj in bpy.data.objects: + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": + if actorObj in obj.children_recursive: + entranceProp.tiedRoom = obj + break + elif actorObj.ootEmptyType == "Transition Actor": + transActorProp = actorObj.ootTransitionActorProperty + transActorProp.isRoomTransition = actorObj["ootTransitionActorProperty"]["dontTransition"] == False + del actorObj["ootTransitionActorProperty"]["dontTransition"] + + if transActorProp.isRoomTransition: + for obj in bpy.data.objects: + if obj.type == "EMPTY": + if obj.ootEmptyType == "Room": + if actorObj in obj.children_recursive: + transActorProp.fromRoom = obj + + if obj.ootRoomHeader.roomIndex == actorObj["ootTransitionActorProperty"]["roomIndex"]: + transActorProp.toRoom = obj + del actorObj["ootTransitionActorProperty"]["roomIndex"] diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 1d7ca39d4..96c50f21b 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -1,5 +1,11 @@ -import bpy, math, os, re +import bpy +import math +import os +import re + from ast import parse, Expression, Num, UnaryOp, USub, Invert, BinOp +from mathutils import Vector +from bpy.types import Object from bpy.utils import register_class, unregister_class from typing import Callable from .oot_constants import ootSceneIDToName @@ -20,7 +26,7 @@ def isPathObject(obj: bpy.types.Object) -> bool: - return obj.data is not None and isinstance(obj.data, bpy.types.Curve) and obj.ootSplineProperty.splineType == "Path" + return obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" ootSceneDungeons = [ @@ -222,7 +228,7 @@ def __init__(self): def sortObjects(self, allObjs): for obj in allObjs: - if obj.data is None: + if obj.type == "EMPTY": if obj.ootEmptyType == "Actor": self.actors.append(obj) elif obj.ootEmptyType == "Transition Actor": @@ -235,7 +241,7 @@ def sortObjects(self, allObjs): self.roomObjs.append(obj) elif obj.ootEmptyType == "Scene": self.sceneObj = obj - elif isinstance(obj.data, bpy.types.Mesh): + elif obj.type == "MESH": self.meshes.append(obj) @@ -326,10 +332,8 @@ def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer): def ootSelectMeshChildrenOnly(obj, includeEmpties): - isMesh = isinstance(obj.data, bpy.types.Mesh) - isEmpty = ( - obj.data is None or isinstance(obj.data, bpy.types.Camera) or isinstance(obj.data, bpy.types.Curve) - ) and includeEmpties + isMesh = obj.type == "MESH" + isEmpty = (obj.type == "EMPTY" or obj.type == "CAMERA" or obj.type == "CURVE") and includeEmpties if isMesh or isEmpty: obj.select_set(True) obj.original_name = obj.name @@ -344,7 +348,7 @@ def ootCleanupScene(originalSceneObj, allObjs): def getSceneObj(obj): - while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Scene")): + while not (obj is None or (obj is not None and obj.type == "EMPTY" and obj.ootEmptyType == "Scene")): obj = obj.parent if obj is None: return None @@ -353,7 +357,7 @@ def getSceneObj(obj): def getRoomObj(obj): - while not (obj is None or (obj is not None and obj.data is None and obj.ootEmptyType == "Room")): + while not (obj is None or (obj is not None and obj.type == "EMPTY" and obj.ootEmptyType == "Room")): obj = obj.parent if obj is None: return None @@ -491,6 +495,13 @@ def getEnumName(enumItems, value): raise PluginError("Could not find enum value " + str(value)) +def getEnumIndex(enumItems, value): + for i, enumTuple in enumerate(enumItems): + if enumTuple[0] == value or enumTuple[1] == value: + return i + return None + + def ootConvertTranslation(translation): return [int(round(value)) for value in translation] @@ -501,7 +512,7 @@ def ootConvertRotation(rotation): # parse rotaion in Vec3s format -def ootParseRotation(values): +def ootParseRotation(values: list[int]): return [ math.radians( (int.from_bytes(value.to_bytes(2, "big", signed=value < 0x8000), "big", signed=False) / 2**16) * 360 @@ -674,7 +685,7 @@ def execute(self, context): def getHeaderSettings(actorObj: bpy.types.Object): itemType = actorObj.ootEmptyType - if actorObj.data is None: + if actorObj.type == "EMPTY": if itemType == "Actor": headerSettings = actorObj.ootActorProperty.headerSettings elif itemType == "Entrance": @@ -683,7 +694,7 @@ def getHeaderSettings(actorObj: bpy.types.Object): headerSettings = actorObj.ootTransitionActorProperty.actor.headerSettings else: headerSettings = None - elif actorObj.data is not None and isPathObject(actorObj): + elif isPathObject(actorObj): headerSettings = actorObj.ootSplineProperty.headerSettings else: headerSettings = None @@ -861,3 +872,70 @@ def _eval(node): raise ValueError(f"Unsupported AST node {node}") return f"0x{_eval(node.body):X}" + + +def getNewPath(type: str, isClosedShape: bool): + """ + Returns a new Curve Object with the selected spline shape + + Parameters: + - ``type``: the path's type (square, line, etc) + - ``isClosedShape``: choose if the spline should have an extra point to make a closed shape + """ + + # create a new curve + newCurve = bpy.data.curves.new("New Path", "CURVE") + newCurve.dimensions = "3D" + + # add a new spline to the curve + newSpline = newCurve.splines.new("NURBS") # comes with 1 point + + # generate shape based on 'type' parameter + scaleDivBy2 = bpy.context.scene.ootBlenderScale / 2 + match type: + case "Line": + newSpline.points.add(1) + for i, point in enumerate(newSpline.points): + point.co.x = i * bpy.context.scene.ootBlenderScale + point.co.w = 1 + case "Triangle": + newSpline.points.add(2) + for i, point in enumerate(newSpline.points): + point.co.x = i * scaleDivBy2 + if i == 1: + point.co.y = (len(newSpline.points) * scaleDivBy2) / 2 + point.co.w = 1 + case "Square" | "Trapezium": + newSpline.points.add(3) + for i, point in enumerate(newSpline.points): + point.co.x = i * scaleDivBy2 + if i in [1, 2]: + if type == "Square": + point.co.y = (len(newSpline.points) - 1) * scaleDivBy2 + if i == 1: + point.co.x = newSpline.points[0].co.x + else: + point.co.x = point.co.y + else: + point.co.y = 1 * scaleDivBy2 + point.co.w = 1 + case _: + raise PluginError("ERROR: Invalid Path Type!") + + if isClosedShape and type != "Line": + newSpline.points.add(1) + newSpline.points[-1].co = newSpline.points[0].co + + # make the curve's display accurate to the point's shape + newSpline.use_cyclic_u = True + newSpline.use_endpoint_u = False + newSpline.resolution_u = 64 + newSpline.order_u = 2 + + # create a new object and add the curve as data + newPath = bpy.data.objects.new("New Path", newCurve) + newPath.show_name = True + newPath.location = Vector(bpy.context.scene.cursor.location) + bpy.context.view_layer.active_layer_collection.collection.objects.link(newPath) + + return newPath diff --git a/fast64_internal/oot/props_panel_main.py b/fast64_internal/oot/props_panel_main.py index 39177faa3..7234a6707 100644 --- a/fast64_internal/oot/props_panel_main.py +++ b/fast64_internal/oot/props_panel_main.py @@ -1,12 +1,16 @@ import bpy from bpy.utils import register_class, unregister_class from ..utility import prop_split, gammaInverse -from .oot_constants import ootEnumEmptyType from .oot_utility import getSceneObj, getRoomObj from .scene.properties import OOTSceneProperties from .room.properties import OOTObjectProperty, OOTRoomHeaderProperty, OOTAlternateRoomHeaderProperty from .collision.properties import OOTWaterBoxProperty from .cutscene.properties import OOTCutsceneProperty +from .cutscene.motion.properties import ( + OOTCutsceneMotionProperty, + CutsceneCmdActorCueListProperty, + CutsceneCmdActorCueProperty, +) from .actor.properties import ( OOTActorProperty, @@ -14,6 +18,28 @@ OOTEntranceProperty, ) +# Make sure to add exceptions in utility.py - selectMeshChildrenOnly +ootEnumEmptyType = [ + ("None", "None", "None"), + ("Scene", "Scene", "Scene"), + ("Room", "Room", "Room"), + ("Actor", "Actor", "Actor"), + ("Transition Actor", "Transition Actor", "Transition Actor"), + ("Entrance", "Entrance", "Entrance"), + ("Water Box", "Water Box", "Water Box"), + ("Cull Group", "Custom Cull Group", "Cull Group"), + ("LOD", "LOD Group", "LOD Group"), + ("Cutscene", "Cutscene Main", "Cutscene"), + ("CS Actor Cue List", "CS Actor Cue List", "CS Actor Cue List"), + ("CS Actor Cue", "CS Actor Cue", "CS Actor Cue"), + ("CS Player Cue List", "CS Player Cue List", "CS Player Cue List"), + ("CS Player Cue", "CS Player Cue", "CS Player Cue"), + ("CS Actor Cue Preview", "CS Actor Cue Preview", "CS Actor Cue Preview"), + ("CS Player Cue Preview", "CS Player Cue Preview", "CS Player Cue Preview"), + ("CS Dummy Cue", "CS Dummy Cue", "CS Dummy Cue"), + # ('Camera Volume', 'Camera Volume', 'Camera Volume'), +] + def drawSceneHeader(box: bpy.types.UILayout, obj: bpy.types.Object): objName = obj.name @@ -91,7 +117,7 @@ class OOTObjectPanel(bpy.types.Panel): @classmethod def poll(cls, context): - return context.scene.gameEditorMode == "OOT" and (context.object is not None and context.object.data is None) + return context.scene.gameEditorMode == "OOT" and (context.object is not None and context.object.type == "EMPTY") def draw(self, context): prop_split(self.layout, context.scene, "gameEditorMode", "Game") @@ -144,6 +170,21 @@ def draw(self, context): csProp: OOTCutsceneProperty = obj.ootCutsceneProperty csProp.draw_props(box, obj) + elif obj.ootEmptyType in [ + "CS Actor Cue List", + "CS Player Cue List", + "CS Actor Cue Preview", + "CS Player Cue Preview", + ]: + labelPrefix = "Player" if "Player" in obj.ootEmptyType else "Actor" + actorCueListProp: CutsceneCmdActorCueListProperty = obj.ootCSMotionProperty.actorCueListProp + actorCueListProp.draw_props(box, obj.ootEmptyType == f"CS {labelPrefix} Cue Preview", labelPrefix, obj.name) + + elif obj.ootEmptyType in ["CS Actor Cue", "CS Player Cue", "CS Dummy Cue"]: + labelPrefix = "Player" if obj.parent.ootEmptyType == "CS Player Cue List" else "Actor" + actorCueProp: CutsceneCmdActorCueProperty = obj.ootCSMotionProperty.actorCueProp + actorCueProp.draw_props(box, labelPrefix, obj.ootEmptyType == "CS Dummy Cue", obj.name) + elif obj.ootEmptyType == "None": box.label(text="Geometry can be parented to this.") @@ -154,9 +195,30 @@ class OOT_ObjectProperties(bpy.types.PropertyGroup): @staticmethod def upgrade_changed_props(): for obj in bpy.data.objects: - if obj.data is None: + if obj.type == "EMPTY": if obj.ootEmptyType == "Room": OOTObjectProperty.upgrade_object(obj) + if obj.ootEmptyType in {"Entrance", "Transition Actor"}: + OOTActorProperty.upgrade_object(obj) + if any(obj.name.startswith(elem) for elem in ["ActionList.", "Point.", "Preview."]): + OOTCutsceneMotionProperty.upgrade_object(obj) + + if "Point." in obj.name: + parentObj = obj.parent + if parentObj is not None: + if parentObj.children[-1] == obj: + obj.ootEmptyType = "CS Dummy Cue" + else: + print("WARNING: An Actor Cue has been detected outside an Actor Cue List: " + obj.name) + elif obj.type == "ARMATURE": + parentObj = obj.parent + if parentObj is not None and ( + parentObj.name.startswith("Cutscene.") or parentObj.ootEmptyType == "Cutscene" + ): + OOTCutsceneMotionProperty.upgrade_object(obj) + + if obj.ootEmptyType == "Cutscene": + OOTCutsceneProperty.upgrade_object(obj) class OOTCullGroupProperty(bpy.types.PropertyGroup): diff --git a/fast64_internal/oot/room/properties.py b/fast64_internal/oot/room/properties.py index 1ad2c4437..3ef126804 100644 --- a/fast64_internal/oot/room/properties.py +++ b/fast64_internal/oot/room/properties.py @@ -19,14 +19,20 @@ from ..oot_constants import ( ootData, - ootEnumRoomMenu, - ootEnumRoomMenuAlternate, ootEnumRoomBehaviour, ootEnumLinkIdle, ootEnumRoomShapeType, ootEnumHeaderMenu, ) +ootEnumRoomMenuAlternate = [ + ("General", "General", "General"), + ("Objects", "Objects", "Objects"), +] +ootEnumRoomMenu = ootEnumRoomMenuAlternate + [ + ("Alternate", "Alternate", "Alternate"), +] + class OOTObjectProperty(PropertyGroup): expandTab: BoolProperty(name="Expand Tab") @@ -34,7 +40,7 @@ class OOTObjectProperty(PropertyGroup): objectIDCustom: StringProperty(default="OBJECT_CUSTOM") @staticmethod - def upgrade_object(obj): + def upgrade_object(obj: Object): print(f"Processing '{obj.name}'...") upgradeRoomHeaders(obj, ootData.objectData) diff --git a/fast64_internal/oot/scene/exporter/to_c/__init__.py b/fast64_internal/oot/scene/exporter/to_c/__init__.py index e9f7106ad..61a8fed1f 100644 --- a/fast64_internal/oot/scene/exporter/to_c/__init__.py +++ b/fast64_internal/oot/scene/exporter/to_c/__init__.py @@ -3,4 +3,4 @@ from .spec import editSpecFile from .scene_folder import modifySceneFiles, deleteSceneFiles from .scene_bootup import setBootupScene, clearBootupScene -from .scene_cutscene import ootCutsceneDataToC +from .scene_cutscene import getCutsceneC diff --git a/fast64_internal/oot/scene/exporter/to_c/actor.py b/fast64_internal/oot/scene/exporter/to_c/actor.py index 653a3f075..446efc331 100644 --- a/fast64_internal/oot/scene/exporter/to_c/actor.py +++ b/fast64_internal/oot/scene/exporter/to_c/actor.py @@ -29,14 +29,14 @@ def getActorEntry(actor: OOTActor): def getActorList(outRoom: OOTRoom, headerIndex: int): """Returns the actor list for the current header""" actorList = CData() - listName = f"ActorEntry {outRoom.actorListName(headerIndex)}" + declarationBase = f"ActorEntry {outRoom.actorListName(headerIndex)}" # .h - actorList.header = f"extern {listName}[];\n" + actorList.header = f"extern {declarationBase}[];\n" # .c actorList.source = ( - (f"{listName}[{outRoom.getActorLengthDefineName(headerIndex)}]" + " = {\n") + (f"{declarationBase}[{outRoom.getActorLengthDefineName(headerIndex)}]" + " = {\n") + "\n".join(getActorEntry(actor) for actor in outRoom.actorList) + "};\n\n" ) @@ -72,14 +72,14 @@ def getTransitionActorEntry(transActor: OOTTransitionActor): def getTransitionActorList(outScene: OOTScene, headerIndex: int): """Returns the transition actor list for the current header""" transActorList = CData() - listName = f"TransitionActorEntry {outScene.transitionActorListName(headerIndex)}" + declarationBase = f"TransitionActorEntry {outScene.transitionActorListName(headerIndex)}" # .h - transActorList.header = f"extern {listName}[];\n" + transActorList.header = f"extern {declarationBase}[];\n" # .c transActorList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + "\n".join(getTransitionActorEntry(transActor) for transActor in outScene.transitionActorList) + "};\n\n" ) @@ -93,14 +93,14 @@ def getTransitionActorList(outScene: OOTScene, headerIndex: int): def getSpawnActorList(outScene: OOTScene, headerIndex: int): """Returns the spawn actor list for the current header""" spawnActorList = CData() - listName = f"ActorEntry {outScene.startPositionsName(headerIndex)}" + declarationBase = f"ActorEntry {outScene.startPositionsName(headerIndex)}" # .h - spawnActorList.header = f"extern {listName}[];\n" + spawnActorList.header = f"extern {declarationBase}[];\n" # .c spawnActorList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + "".join(getActorEntry(spawnActor) for spawnActor in outScene.startPositions.values()) + "};\n\n" ) @@ -116,14 +116,14 @@ def getSpawnEntry(entrance: OOTEntrance): def getSpawnList(outScene: OOTScene, headerIndex: int): """Returns the spawn list for the current header""" spawnList = CData() - listName = f"Spawn {outScene.entranceListName(headerIndex)}" + declarationBase = f"Spawn {outScene.entranceListName(headerIndex)}" # .h - spawnList.header = f"extern {listName}[];\n" + spawnList.header = f"extern {declarationBase}[];\n" # .c spawnList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + (indent + "// { Spawn Actor List Index, Room Index }\n") + "".join(getSpawnEntry(entrance) for entrance in outScene.entranceList) + "};\n\n" diff --git a/fast64_internal/oot/scene/exporter/to_c/room_commands.py b/fast64_internal/oot/scene/exporter/to_c/room_commands.py index 7ef545997..1df1e6c8c 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_commands.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_commands.py @@ -53,7 +53,7 @@ def getActorListCmd(outRoom: OOTRoom, headerIndex: int): def getRoomCommandList(outRoom: OOTRoom, headerIndex: int): cmdListData = CData() - listName = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}" + declarationBase = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}" getCmdFuncList = [ getEchoSettingsCmd, @@ -73,9 +73,9 @@ def getRoomCommandList(outRoom: OOTRoom, headerIndex: int): ) # .h - cmdListData.header = f"extern {listName}[];\n" + cmdListData.header = f"extern {declarationBase}[];\n" # .c - cmdListData.source = f"{listName}[]" + " = {\n" + roomCmdData + "};\n\n" + cmdListData.source = f"{declarationBase}[]" + " = {\n" + roomCmdData + "};\n\n" return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/room_header.py b/fast64_internal/oot/scene/exporter/to_c/room_header.py index 333e764f2..ac93c14ce 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_header.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_header.py @@ -20,14 +20,14 @@ def getHeaderDefines(outRoom: OOTRoom, headerIndex: int): # Object List def getObjectList(outRoom: OOTRoom, headerIndex: int): objectList = CData() - listName = f"s16 {outRoom.objectListName(headerIndex)}" + declarationBase = f"s16 {outRoom.objectListName(headerIndex)}" # .h - objectList.header = f"extern {listName}[];\n" + objectList.header = f"extern {declarationBase}[];\n" # .c objectList.source = ( - (f"{listName}[{outRoom.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + (f"{declarationBase}[{outRoom.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + ",\n".join(indent + objectID for objectID in outRoom.objectIDList) + ",\n};\n\n" ) @@ -48,14 +48,14 @@ def getRoomData(outRoom: OOTRoom): for i, csHeader in enumerate(outRoom.cutsceneHeaders): roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - altHeaderPtrListName = f"SceneCmd* {outRoom.alternateHeadersName()}" + declarationBase = f"SceneCmd* {outRoom.alternateHeadersName()}" # .h - roomC.header = f"extern {altHeaderPtrListName}[];\n" + roomC.header = f"extern {declarationBase}[];\n" # .c altHeaderPtrList = ( - f"{altHeaderPtrListName}[]" + f"{declarationBase}[]" + " = {\n" + "\n".join( indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," diff --git a/fast64_internal/oot/scene/exporter/to_c/room_shape.py b/fast64_internal/oot/scene/exporter/to_c/room_shape.py index e4f6bdfde..c4029d05f 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_shape.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_shape.py @@ -1,9 +1,21 @@ from .....utility import CData, indent from .....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ....oot_model_classes import OOTGfxFormatter -from ....oot_constants import ootRoomShapeStructs, ootRoomShapeEntryStructs, ootEnumRoomShapeType +from ....oot_constants import ootEnumRoomShapeType from ....oot_level_classes import OOTRoom, OOTRoomMeshGroup, OOTRoomMesh +ootRoomShapeStructs = [ + "RoomShapeNormal", + "RoomShapeImage", + "RoomShapeCullable", +] + +ootRoomShapeEntryStructs = [ + "RoomShapeDListsEntry", + "RoomShapeDListsEntry", + "RoomShapeCullableEntry", +] + def getRoomShapeDLEntry(meshEntry: OOTRoomMeshGroup, roomShape: str): opaqueName = meshEntry.DLGroup.opaque.name if meshEntry.DLGroup.opaque is not None else "NULL" @@ -24,13 +36,13 @@ def getRoomShapeImageData(roomMesh: OOTRoomMesh, textureSettings: TextureExportS code = CData() if len(roomMesh.bgImages) > 1: - multiBgImageName = f"RoomShapeImageMultiBgEntry {roomMesh.getMultiBgStructName()}" + declarationBase = f"RoomShapeImageMultiBgEntry {roomMesh.getMultiBgStructName()}" # .h - code.header += f"extern {multiBgImageName}[{len(roomMesh.bgImages)}];\n" + code.header += f"extern {declarationBase}[{len(roomMesh.bgImages)}];\n" # .c - code.source += f"{multiBgImageName}[{len(roomMesh.bgImages)}] = {{\n" + code.source += f"{declarationBase}[{len(roomMesh.bgImages)}] = {{\n" for i in range(len(roomMesh.bgImages)): bgImage = roomMesh.bgImages[i] code.source += indent + "{\n" + bgImage.multiPropertiesC(2, i) + indent + "},\n" @@ -70,7 +82,7 @@ def getRoomShape(outRoom: OOTRoom): if mesh.roomShape != "ROOM_SHAPE_TYPE_IMAGE": entryName = mesh.entriesName() - dlEntryArrayName = f"{dlEntryType} {mesh.entriesName()}[{len(mesh.meshEntries)}]" + dlEntryDeclarationBase = f"{dlEntryType} {mesh.entriesName()}[{len(mesh.meshEntries)}]" roomShapeInfo.source = ( "\n".join( @@ -86,8 +98,8 @@ def getRoomShape(outRoom: OOTRoom): + "\n\n" ) - roomShapeDLArray.header = f"extern {dlEntryArrayName};\n" - roomShapeDLArray.source = dlEntryArrayName + " = {\n" + roomShapeDLArray.header = f"extern {dlEntryDeclarationBase};\n" + roomShapeDLArray.source = dlEntryDeclarationBase + " = {\n" for entry in mesh.meshEntries: roomShapeDLArray.source += indent + getRoomShapeDLEntry(entry, mesh.roomShape) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py index a972ee473..1082bb0ff 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py @@ -65,19 +65,17 @@ def getLightSettingsCmd(outScene: OOTScene, headerIndex: int): def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int): match outScene.csWriteType: - case "Embedded": - csDataName = outScene.cutsceneDataName(headerIndex) case "Object": - csDataName = outScene.csWriteObject.name + csDataName = outScene.csName case _: csDataName = outScene.csWriteCustom - return f"SCENE_CMD_CUTSCENE_DATA({csDataName})" + return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName})" def getSceneCommandList(outScene: OOTScene, headerIndex: int): cmdListData = CData() - listName = f"SceneCmd {outScene.sceneName()}_header{headerIndex:02}" + declarationBase = f"SceneCmd {outScene.sceneName()}_header{headerIndex:02}" getCmdFunc1ArgList = [ getSoundSettingsCmd, @@ -110,9 +108,9 @@ def getSceneCommandList(outScene: OOTScene, headerIndex: int): ) # .h - cmdListData.header = f"extern {listName}[]" + ";\n" + cmdListData.header = f"extern {declarationBase}[]" + ";\n" # .c - cmdListData.source = f"{listName}[]" + " = {\n" + sceneCmdData + "};\n\n" + cmdListData.source = f"{declarationBase}[]" + " = {\n" + sceneCmdData + "};\n\n" return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py index e182d9a1f..8f0ab5c09 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py @@ -1,161 +1,51 @@ -from .....utility import CData, PluginError +import bpy + +from .....utility import CData from ....oot_level_classes import OOTScene -from ....cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC +from ....cutscene.exporter import getNewCutsceneExport + + +def getCutsceneC(csName: str): + csData = CData() + declarationBase = f"CutsceneData {csName}[]" + + # .h + csData.header = f"extern {declarationBase};\n" + # .c + csData.source = ( + declarationBase + + " = {\n" + + getNewCutsceneExport(csName, bpy.context.scene.exportMotionOnly).getExportData() + + "};\n\n" + ) -def ootCutsceneDataToC(csParent, csName): - # csParent can be OOTCutscene or OOTScene - data = CData() - data.header = "extern CutsceneData " + csName + "[];\n" - data.source = "CutsceneData " + csName + "[] = {\n" - nentries = len(csParent.csLists) + (1 if csParent.csWriteTerminator else 0) - data.source += "\tCS_BEGIN_CUTSCENE(" + str(nentries) + ", " + str(csParent.csEndFrame) + "),\n" - if csParent.csWriteTerminator: - data.source += ( - "\tCS_TERMINATOR(" - + str(csParent.csTermIdx) - + ", " - + str(csParent.csTermStart) - + ", " - + str(csParent.csTermEnd) - + "),\n" - ) - for list in csParent.csLists: - data.source += "\t" + ootEnumCSListTypeListC[list.listType] + "(" - if list.listType == "Unk": - data.source += list.unkType + ", " - if list.listType == "FX": - data.source += list.fxType + ", " + str(list.fxStartFrame) + ", " + str(list.fxEndFrame) - else: - data.source += str(len(list.entries)) - data.source += "),\n" - for e in list.entries: - data.source += "\t\t" - if list.listType == "Textbox": - data.source += ootEnumCSTextboxTypeEntryC[e.textboxType] - else: - data.source += ootEnumCSListTypeEntryC[list.listType] - data.source += "(" - if list.listType == "Textbox": - if e.textboxType == "Text": - data.source += ( - e.messageId - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", " - + e.type - + ", " - + e.topOptionBranch - + ", " - + e.bottomOptionBranch - ) - elif e.textboxType == "None": - data.source += str(e.startFrame) + ", " + str(e.endFrame) - elif e.textboxType == "LearnSong": - data.source += ( - e.ocarinaSongAction - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", " - + e.ocarinaMessageId - ) - elif list.listType == "Lighting": - data.source += ( - str(e.index) + ", " + str(e.startFrame) + ", " + str(e.startFrame + 1) + ", 0, 0, 0, 0, 0, 0, 0, 0" - ) - elif list.listType == "Time": - data.source += ( - "1, " - + str(e.startFrame) - + ", " - + str(e.startFrame + 1) - + ", " - + str(e.hour) - + ", " - + str(e.minute) - + ", 0" - ) - elif list.listType in ["PlayBGM", "StopBGM", "FadeBGM"]: - data.source += e.value - if list.listType != "FadeBGM": - data.source += " + 1" # Game subtracts 1 to get actual seq - data.source += ", " + str(e.startFrame) + ", " + str(e.endFrame) + ", 0, 0, 0, 0, 0, 0, 0, 0" - elif list.listType == "Misc": - data.source += ( - str(e.operation) - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0" - ) - elif list.listType == "0x09": - data.source += ( - "0, " - + str(e.startFrame) - + ", " - + str(e.startFrame + 1) - + ", " - + e.unk2 - + ", " - + e.unk3 - + ", " - + e.unk4 - + ", 0, 0" - ) - elif list.listType == "Unk": - data.source += ( - e.unk1 - + ", " - + e.unk2 - + ", " - + e.unk3 - + ", " - + e.unk4 - + ", " - + e.unk5 - + ", " - + e.unk6 - + ", " - + e.unk7 - + ", " - + e.unk8 - + ", " - + e.unk9 - + ", " - + e.unk10 - + ", " - + e.unk11 - + ", " - + e.unk12 - ) - else: - raise PluginError("Internal error: invalid cutscene list type " + list.listType) - data.source += "),\n" - data.source += "\tCS_END(),\n" - data.source += "};\n\n" - return data + return csData def getSceneCutscenes(outScene: OOTScene): cutscenes: list[CData] = [] - altHeaders = [outScene, outScene.childNightHeader, outScene.adultDayHeader, outScene.adultNightHeader] + altHeaders: list[OOTScene] = [ + outScene, + outScene.childNightHeader, + outScene.adultDayHeader, + outScene.adultNightHeader, + ] altHeaders.extend(outScene.cutsceneHeaders) + csObjects = [] - for i, curHeader in enumerate(altHeaders): + for curHeader in altHeaders: # curHeader is either None or an OOTScene. This can either be the main scene itself, # or one of the alternate / cutscene headers. if curHeader is not None and curHeader.writeCutscene: - if curHeader.csWriteType == "Embedded": - cutscenes.append(ootCutsceneDataToC(curHeader, curHeader.cutsceneDataName(i))) - elif curHeader.csWriteType == "Object": - cutscenes.append(ootCutsceneDataToC(curHeader.csWriteObject, curHeader.csWriteObject.name)) + if curHeader.csWriteType == "Object" and curHeader.csName not in csObjects: + cutscenes.append(getCutsceneC(curHeader.csName)) + csObjects.append(curHeader.csName) - for extraCs in outScene.extraCutscenes: - cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name)) + for csObj in outScene.extraCutscenes: + name = csObj.name.removeprefix("Cutscene.") + if not name in csObjects: + cutscenes.append(getCutsceneC(name)) + csObjects.append(name) return cutscenes diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_header.py b/fast64_internal/oot/scene/exporter/to_c/scene_header.py index 37a7c8dc9..61b3c1621 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_header.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_header.py @@ -59,14 +59,14 @@ def getLightSettingsEntry(light: OOTLight, lightMode: str, isLightingCustom: boo def getLightSettings(outScene: OOTScene, headerIndex: int): lightSettingsData = CData() - lightName = f"EnvLightSettings {outScene.lightListName(headerIndex)}[{len(outScene.lights)}]" + declarationBase = f"EnvLightSettings {outScene.lightListName(headerIndex)}[{len(outScene.lights)}]" # .h - lightSettingsData.header = f"extern {lightName};\n" + lightSettingsData.header = f"extern {declarationBase};\n" # .c lightSettingsData.source = ( - (lightName + " = {\n") + (declarationBase + " = {\n") + "".join( getLightSettingsEntry(light, outScene.skyboxLighting, outScene.isSkyboxLightingCustom, i) for i, light in enumerate(outScene.lights) @@ -90,14 +90,14 @@ def getSceneModel(outScene: OOTScene, textureExportSettings: TextureExportSettin ############# def getExitList(outScene: OOTScene, headerIndex: int): exitList = CData() - listName = f"u16 {outScene.exitListName(headerIndex)}[{len(outScene.exitList)}]" + declarationBase = f"u16 {outScene.exitListName(headerIndex)}[{len(outScene.exitList)}]" # .h - exitList.header = f"extern {listName};\n" + exitList.header = f"extern {declarationBase};\n" # .c exitList.source = ( - (listName + " = {\n") + (declarationBase + " = {\n") # @TODO: use the enum name instead of the raw index + "\n".join(indent + f"{exitEntry.index}," for exitEntry in outScene.exitList) + "\n};\n\n" @@ -111,7 +111,7 @@ def getExitList(outScene: OOTScene, headerIndex: int): ############# def getRoomList(outScene: OOTScene): roomList = CData() - listName = f"RomFile {outScene.roomListName()}[]" + declarationBase = f"RomFile {outScene.roomListName()}[]" # generating segment rom names for every room segNames = [] @@ -120,7 +120,7 @@ def getRoomList(outScene: OOTScene): segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) # .h - roomList.header += f"extern {listName};\n" + roomList.header += f"extern {declarationBase};\n" if not outScene.write_dummy_room_list: # Write externs for rom segments @@ -129,7 +129,7 @@ def getRoomList(outScene: OOTScene): ) # .c - roomList.source = listName + " = {\n" + roomList.source = declarationBase + " = {\n" if outScene.write_dummy_room_list: roomList.source = ( @@ -207,9 +207,9 @@ def getSceneData(outScene: OOTScene): if i == 0: if outScene.hasAlternateHeaders(): - altHeaderListName = f"SceneCmd* {outScene.alternateHeadersName()}[]" - sceneC.header += f"extern {altHeaderListName};\n" - sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" + declarationBase = f"SceneCmd* {outScene.alternateHeadersName()}[]" + sceneC.header += f"extern {declarationBase};\n" + sceneC.source += declarationBase + " = {\n" + altHeaderPtrs + "\n};\n\n" # Write the room segment list sceneC.append(getRoomList(outScene)) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py b/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py index 8b6292ef2..48aad26bc 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py @@ -5,14 +5,14 @@ def getPathPointData(path: OOTPath, headerIndex: int, pathIndex: int): pathData = CData() - pathName = f"Vec3s {path.pathName(headerIndex, pathIndex)}" + declarationBase = f"Vec3s {path.pathName(headerIndex, pathIndex)}" # .h - pathData.header = f"extern {pathName}[];\n" + pathData.header = f"extern {declarationBase}[];\n" # .c pathData.source = ( - f"{pathName}[]" + f"{declarationBase}[]" + " = {\n" + "\n".join( indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in path.points @@ -26,13 +26,13 @@ def getPathPointData(path: OOTPath, headerIndex: int, pathIndex: int): def getPathData(outScene: OOTScene, headerIndex: int): pathData = CData() pathListData = CData() - listName = f"Path {outScene.pathListName(headerIndex)}[{len(outScene.pathList)}]" + declarationBase = f"Path {outScene.pathListName(headerIndex)}[{len(outScene.pathList)}]" # .h - pathListData.header = f"extern {listName};\n" + pathListData.header = f"extern {declarationBase};\n" # .c - pathListData.source = listName + " = {\n" + pathListData.source = declarationBase + " = {\n" # Parse in alphabetical order of names sortedPathList = sorted(outScene.pathList, key=lambda x: x.objName.lower()) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 0c28b72f3..44f1da117 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -1,13 +1,15 @@ -import bpy, os +import bpy +import os + from bpy.path import abspath -from bpy.types import Operator, UILayout +from bpy.types import Operator from bpy.props import EnumProperty, IntProperty, StringProperty from bpy.utils import register_class, unregister_class from bpy.ops import object from mathutils import Matrix, Vector from ...f3d.f3d_gbi import DLFormat from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader -from ..oot_utility import ExportInfo, sceneNameFromID, getEnumName +from ..oot_utility import ExportInfo, sceneNameFromID from ..oot_level_writer import ootExportSceneToC from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene @@ -37,14 +39,8 @@ def dummy_view_layer_update(context): def parseSceneFunc(): - context = bpy.context - settings = context.scene.ootSceneImportSettings - parseScene( - context.scene.f3d_type, - context.scene.isHWv1, - settings, - settings.option, - ) + settings = bpy.context.scene.ootSceneImportSettings + parseScene(settings, settings.option) class OOT_SearchSceneEnumOperator(Operator): @@ -81,7 +77,7 @@ class OOT_SearchMusicSeqEnumOperator(Operator): bl_property = "ootMusicSeq" bl_options = {"REGISTER", "UNDO"} - ootMusicSeq: EnumProperty(items=ootEnumMusicSeq, default="0x02") + ootMusicSeq: EnumProperty(items=ootEnumMusicSeq, default="NA_BGM_FIELD_LOGIC") headerIndex: IntProperty(default=0, min=0) objName: StringProperty() @@ -150,7 +146,7 @@ def execute(self, context): obj = context.scene.ootSceneExportObj if obj is None: raise PluginError("Scene object input not set.") - elif obj.data is not None or obj.ootEmptyType != "Scene": + elif obj.type != "EMPTY" or obj.ootEmptyType != "Scene": raise PluginError("The input object is not an empty with the Scene type.") scaleValue = context.scene.ootBlenderScale @@ -178,8 +174,6 @@ def execute(self, context): ootExportSceneToC( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, levelName, DLFormat.Static, context.scene.saveTextures, diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index b81af4cc9..7c8e2daa3 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -1,4 +1,4 @@ -import os, bpy +import bpy from bpy.types import PropertyGroup, Object, Light, UILayout, Scene from bpy.props import ( EnumProperty, @@ -12,8 +12,6 @@ from bpy.utils import register_class, unregister_class from ...render_settings import on_update_oot_render_settings from ...utility import prop_split, customExportWarning -from ..cutscene.properties import OOTCSListProperty -from ..cutscene.operators import drawCSListAddOp from ..cutscene.constants import ootEnumCSWriteType from ..oot_utility import ( @@ -22,15 +20,11 @@ drawCollectionOps, drawEnumWithCustom, drawAddButton, - getEnumName, ) from ..oot_constants import ( ootEnumMusicSeq, ootEnumSceneID, - ootEnumExitIndex, - ootEnumTransitionAnims, - ootEnumLightGroupMenu, ootEnumGlobalObject, ootEnumNaviHints, ootEnumSkybox, @@ -40,13 +34,49 @@ ootEnumCameraMode, ootEnumNightSeq, ootEnumAudioSessionPreset, - ootEnumSceneMenu, - ootEnumSceneMenuAlternate, ootEnumHeaderMenu, ootEnumDrawConfig, ootEnumHeaderMenuComplete, ) +ootEnumSceneMenuAlternate = [ + ("General", "General", "General"), + ("Lighting", "Lighting", "Lighting"), + ("Cutscene", "Cutscene", "Cutscene"), + ("Exits", "Exits", "Exits"), +] +ootEnumSceneMenu = ootEnumSceneMenuAlternate + [ + ("Alternate", "Alternate", "Alternate"), +] + +ootEnumLightGroupMenu = [ + ("Dawn", "Dawn", "Dawn"), + ("Day", "Day", "Day"), + ("Dusk", "Dusk", "Dusk"), + ("Night", "Night", "Night"), +] + +ootEnumTransitionAnims = [ + ("Custom", "Custom", "Custom"), + ("0x00", "Spiky", "Spiky"), + ("0x01", "Triforce", "Triforce"), + ("0x02", "Slow Black Fade", "Slow Black Fade"), + ("0x03", "Slow Day/White, Slow Night/Black Fade", "Slow Day/White, Slow Night/Black Fade"), + ("0x04", "Fast Day/Black, Slow Night/Black Fade", "Fast Day/Black, Slow Night/Black Fade"), + ("0x05", "Fast Day/White, Slow Night/Black Fade", "Fast Day/White, Slow Night/Black Fade"), + ("0x06", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"), + ("0x07", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"), + ("0x0E", "Slow Sandstorm Fade", "Slow Sandstorm Fade"), + ("0x0F", "Fast Sandstorm Fade", "Fast Sandstorm Fade"), + ("0x20", "Iris Fade", "Iris Fade"), + ("0x2C", "Shortcut Transition", "Shortcut Transition"), +] + +ootEnumExitIndex = [ + ("Custom", "Custom", "Custom"), + ("Default", "Default", "Default"), +] + class OOTSceneCommon: ootEnumBootMode = [ @@ -56,7 +86,7 @@ class OOTSceneCommon: ] def isSceneObj(self, obj): - return obj.data is None and obj.ootEmptyType == "Scene" + return obj.type == "EMPTY" and obj.ootEmptyType == "Scene" class OOTSceneProperties(PropertyGroup): @@ -219,7 +249,11 @@ def draw_props(self, layout: UILayout): class OOTExtraCutsceneProperty(PropertyGroup): - csObject: PointerProperty(name="Cutscene Object", type=Object) + csObject: PointerProperty( + name="Cutscene Object", + type=Object, + poll=lambda self, object: object.type == "EMPTY" and object.ootEmptyType == "Cutscene", + ) class OOTSceneHeaderProperty(PropertyGroup): @@ -250,7 +284,7 @@ class OOTSceneHeaderProperty(PropertyGroup): cameraMode: EnumProperty(name="Camera Mode", items=ootEnumCameraMode, default="0x00") cameraModeCustom: StringProperty(name="Camera Mode Custom", default="0x00") - musicSeq: EnumProperty(name="Music Sequence", items=ootEnumMusicSeq, default="0x02") + musicSeq: EnumProperty(name="Music Sequence", items=ootEnumMusicSeq, default="NA_BGM_FIELD_LOGIC") musicSeqCustom: StringProperty(name="Music Sequence ID", default="0x00") nightSeq: EnumProperty(name="Nighttime SFX", items=ootEnumNightSeq, default="0x00") nightSeqCustom: StringProperty(name="Nighttime SFX ID", default="0x00") @@ -262,24 +296,16 @@ class OOTSceneHeaderProperty(PropertyGroup): exitList: CollectionProperty(type=OOTExitProperty, name="Exit List") writeCutscene: BoolProperty(name="Write Cutscene") - csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Embedded") + csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Object") csWriteCustom: StringProperty(name="CS hdr var:", default="") - csWriteObject: PointerProperty(name="Cutscene Object", type=Object) - - # These properties are for the deprecated "Embedded" cutscene type. They have - # not been removed as doing so would break any existing scenes made with this - # type of cutscene data. - csEndFrame: IntProperty(name="End Frame", min=0, default=100) - csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)") - csTermIdx: IntProperty(name="Index", min=0) - csTermStart: IntProperty(name="Start Frm", min=0, default=99) - csTermEnd: IntProperty(name="End Frm", min=0, default=100) - csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists") + csWriteObject: PointerProperty( + name="Cutscene Object", + type=Object, + poll=lambda self, object: object.type == "EMPTY" and object.ootEmptyType == "Cutscene", + ) extraCutscenes: CollectionProperty(type=OOTExtraCutsceneProperty, name="Extra Cutscenes") - sceneTableEntry: PointerProperty(type=OOTSceneTableEntryProperty) - menuTab: EnumProperty(name="Menu", items=ootEnumSceneMenu, update=onMenuTabChange) altMenuTab: EnumProperty(name="Menu", items=ootEnumSceneMenuAlternate) @@ -355,25 +381,9 @@ def draw_props(self, layout: UILayout, dropdownLabel: str, headerIndex: int, obj r.prop(self, "csWriteType", text="Data") if self.csWriteType == "Custom": cutscene.prop(self, "csWriteCustom") - elif self.csWriteType == "Object": - cutscene.prop(self, "csWriteObject") else: - # This is the GUI setup / drawing for the properties for the - # deprecated "Embedded" cutscene type. They have not been removed - # as doing so would break any existing scenes made with this type - # of cutscene data. - cutscene.label(text='Embedded cutscenes are deprecated. Please use "Object" instead.') - cutscene.prop(self, "csEndFrame", text="End Frame") - cutscene.prop(self, "csWriteTerminator", text="Write Terminator (Code Execution)") - if self.csWriteTerminator: - r = cutscene.row() - r.prop(self, "csTermIdx", text="Index") - r.prop(self, "csTermStart", text="Start Frm") - r.prop(self, "csTermEnd", text="End Frm") - collectionType = "CSHdr." + str(0 if headerIndex is None else headerIndex) - for i, p in enumerate(self.csLists): - p.draw_props(cutscene, i, objName, collectionType) - drawCSListAddOp(cutscene, objName, collectionType) + cutscene.prop(self, "csWriteObject") + if headerIndex is None or headerIndex == 0: cutscene.label(text="Extra cutscenes (not in any header):") for i in range(len(self.extraCutscenes)): @@ -508,6 +518,7 @@ class OOTImportSceneSettingsProperty(PropertyGroup): includeCameras: BoolProperty(name="Cameras", default=True) includePaths: BoolProperty(name="Paths", default=True) includeWaterBoxes: BoolProperty(name="Water Boxes", default=True) + includeCutscenes: BoolProperty(name="Cutscenes", default=False) option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE") def draw_props(self, layout: UILayout, sceneOption: str): @@ -515,17 +526,19 @@ def draw_props(self, layout: UILayout, sceneOption: str): includeButtons1 = col.row(align=True) includeButtons1.prop(self, "includeMesh", toggle=1) includeButtons1.prop(self, "includeCollision", toggle=1) + includeButtons1.prop(self, "includeActors", toggle=1) includeButtons2 = col.row(align=True) - includeButtons2.prop(self, "includeActors", toggle=1) includeButtons2.prop(self, "includeCullGroups", toggle=1) includeButtons2.prop(self, "includeLights", toggle=1) + includeButtons2.prop(self, "includeCameras", toggle=1) includeButtons3 = col.row(align=True) - includeButtons3.prop(self, "includeCameras", toggle=1) includeButtons3.prop(self, "includePaths", toggle=1) includeButtons3.prop(self, "includeWaterBoxes", toggle=1) + includeButtons3.prop(self, "includeCutscenes", toggle=1) col.prop(self, "isCustomDest") + if self.isCustomDest: prop_split(col, self, "destPath", "Directory") prop_split(col, self, "name", "Name") @@ -534,8 +547,6 @@ def draw_props(self, layout: UILayout, sceneOption: str): prop_split(col, self, "subFolder", "Subfolder") prop_split(col, self, "name", "Name") - col.label(text="Cutscenes won't be imported.") - if "SCENE_JABU_JABU" in sceneOption: col.label(text="Pulsing wall effect won't be imported.", icon="ERROR") diff --git a/fast64_internal/oot/skeleton/exporter/functions.py b/fast64_internal/oot/skeleton/exporter/functions.py index 17b026849..171113483 100644 --- a/fast64_internal/oot/skeleton/exporter/functions.py +++ b/fast64_internal/oot/skeleton/exporter/functions.py @@ -210,8 +210,6 @@ def ootConvertArmatureToSkeletonWithMesh( def ootConvertArmatureToC( originalArmatureObj: bpy.types.Object, convertTransformMatrix: mathutils.Matrix, - f3dType: str, - isHWv1: bool, DLFormat: DLFormat, savePNG: bool, drawLayer: str, @@ -240,7 +238,7 @@ def ootConvertArmatureToC( removeVanillaData = settings.removeVanillaData optimize = settings.optimize - fModel = OOTModel(f3dType, isHWv1, skeletonName, DLFormat, drawLayer) + fModel = OOTModel(skeletonName, DLFormat, drawLayer) skeleton, fModel = ootConvertArmatureToSkeletonWithMesh( originalArmatureObj, convertTransformMatrix, fModel, skeletonName, not savePNG, drawLayer, optimize ) diff --git a/fast64_internal/oot/skeleton/importer/functions.py b/fast64_internal/oot/skeleton/importer/functions.py index bdf548442..04f25d088 100644 --- a/fast64_internal/oot/skeleton/importer/functions.py +++ b/fast64_internal/oot/skeleton/importer/functions.py @@ -1,5 +1,5 @@ import mathutils, bpy, math -from ....f3d.f3d_gbi import F3D +from ....f3d.f3d_gbi import F3D, get_F3D_GBI from ....f3d.f3d_parser import getImportData, parseF3D from ....utility import hexOrDecInt, applyRotation from ...oot_f3d_writer import ootReadActorScale @@ -233,7 +233,7 @@ def ootImportSkeletonC(basePath: str, importSettings: OOTSkeletonImportSettings) limbsData = matchResult.group(2) limbList = [entry.strip()[1:] for entry in limbsData.split(",") if entry.strip() != ""] - f3dContext = OOTF3DContext(F3D("F3DEX2/LX2", False), limbList, basePath) + f3dContext = OOTF3DContext(get_F3D_GBI(), limbList, basePath) f3dContext.mat().draw_layer.oot = drawLayer if overlayName is not None and importSettings.autoDetectActorScale: diff --git a/fast64_internal/oot/skeleton/operators.py b/fast64_internal/oot/skeleton/operators.py index bb4686aef..043bf8040 100644 --- a/fast64_internal/oot/skeleton/operators.py +++ b/fast64_internal/oot/skeleton/operators.py @@ -26,7 +26,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Armature not selected.") armatureObj = context.active_object - if type(armatureObj.data) is not Armature: + if armatureObj.type != "ARMATURE": raise PluginError("Armature not selected.") try: @@ -100,7 +100,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Armature not selected.") armatureObj = context.active_object - if type(armatureObj.data) is not Armature: + if armatureObj.type != "ARMATURE": raise PluginError("Armature not selected.") if len(armatureObj.children) == 0 or not isinstance(armatureObj.children[0].data, Mesh): @@ -120,13 +120,9 @@ def execute(self, context): exportSettings: OOTSkeletonExportSettings = context.scene.fast64.oot.skeletonExportSettings saveTextures = context.scene.saveTextures - isHWv1 = context.scene.isHWv1 - f3dType = context.scene.f3d_type drawLayer = armatureObj.ootDrawLayer - ootConvertArmatureToC( - armatureObj, finalTransform, f3dType, isHWv1, DLFormat.Static, saveTextures, drawLayer, exportSettings - ) + ootConvertArmatureToC(armatureObj, finalTransform, DLFormat.Static, saveTextures, drawLayer, exportSettings) self.report({"INFO"}, "Success!") return {"FINISHED"} diff --git a/fast64_internal/oot/skeleton/properties.py b/fast64_internal/oot/skeleton/properties.py index f0d81ac6e..a000d4b9e 100644 --- a/fast64_internal/oot/skeleton/properties.py +++ b/fast64_internal/oot/skeleton/properties.py @@ -14,7 +14,7 @@ def pollArmature(self, obj): - return isinstance(obj.data, Armature) + return obj.type == "ARMATURE" class OOTDynamicTransformProperty(PropertyGroup): diff --git a/fast64_internal/oot/skeleton/utility.py b/fast64_internal/oot/skeleton/utility.py index 489b0265a..a7897bb53 100644 --- a/fast64_internal/oot/skeleton/utility.py +++ b/fast64_internal/oot/skeleton/utility.py @@ -206,7 +206,7 @@ def ootDuplicateArmatureAndRemoveRotations(originalArmatureObj: bpy.types.Object # Duplicate objects to apply scale / modifiers / linked data bpy.ops.object.select_all(action="DESELECT") - for originalMeshObj in [obj for obj in originalArmatureObj.children if isinstance(obj.data, bpy.types.Mesh)]: + for originalMeshObj in [obj for obj in originalArmatureObj.children if obj.type == "MESH"]: originalMeshObj.select_set(True) originalMeshObj.original_name = originalMeshObj.name diff --git a/fast64_internal/oot/tools/operators.py b/fast64_internal/oot/tools/operators.py index 03b73e177..5b40e36c3 100644 --- a/fast64_internal/oot/tools/operators.py +++ b/fast64_internal/oot/tools/operators.py @@ -1,9 +1,14 @@ +import bpy + from mathutils import Vector from bpy.ops import mesh, object, curve -from bpy.types import Operator -from bpy.props import FloatProperty, StringProperty +from bpy.types import Operator, Object, Context +from bpy.props import FloatProperty, StringProperty, EnumProperty, BoolProperty from ...operators import AddWaterBox, addMaterialByName from ...utility import parentObject, setOrigin +from ..cutscene.motion.utility import setupCutscene, createNewCameraShot +from ..oot_utility import getNewPath +from .quick_import import QuickImportAborted, quick_import_exec class OOT_AddWaterBox(AddWaterBox): @@ -92,6 +97,7 @@ def execute(self, context): roomObj = context.view_layer.objects.active roomObj.ootEmptyType = "Room" roomObj.name = "Room" + entranceObj.ootEntranceProperty.tiedRoom = roomObj parentObject(roomObj, planeObj) location += Vector([0, 0, 2]) @@ -143,7 +149,9 @@ def execute(self, context): class OOT_AddCutscene(Operator): bl_idname = "object.oot_add_cutscene" bl_label = "Add Cutscene" - bl_options = {"REGISTER", "UNDO", "PRESET"} + bl_options = {"REGISTER", "UNDO"} + + csName: StringProperty(name="", default="Something", description="The Cutscene's Name without `Cutscene.`") def execute(self, context): if context.mode != "OBJECT": @@ -153,30 +161,135 @@ def execute(self, context): object.empty_add(type="ARROWS", radius=1, align="WORLD") csObj = context.view_layer.objects.active csObj.ootEmptyType = "Cutscene" - csObj.name = "Cutscene.Something" + csObj.name = f"Cutscene.{self.csName}" + createNewCameraShot(csObj) + setupCutscene(csObj) object.select_all(action="DESELECT") csObj.select_set(True) context.view_layer.objects.active = csObj return {"FINISHED"} + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=200) + + def draw(self, context): + layout = self.layout + layout.label(text="Set the Cutscene's Name") + split = layout.split(factor=0.30) + split.label(text="Cutscene.") + split.prop(self, "csName") + class OOT_AddPath(Operator): bl_idname = "object.oot_add_path" bl_label = "Add Path" - bl_options = {"REGISTER", "UNDO", "PRESET"} + bl_options = {"REGISTER", "UNDO"} + + isClosedShape: BoolProperty(name="", default=True) + pathType: EnumProperty( + name="", + items=[ + ("Line", "Line", "Line"), + ("Square", "Square", "Square"), + ("Triangle", "Triangle", "Triangle"), + ("Trapezium", "Trapezium", "Trapezium"), + ], + default="Line", + ) def execute(self, context): if context.mode != "OBJECT": object.mode_set(mode="OBJECT") object.select_all(action="DESELECT") - location = Vector(context.scene.cursor.location) - curve.primitive_nurbs_path_add(radius=1, align="WORLD", location=location[:]) - pathObj = context.view_layer.objects.active - pathObj.name = "New Path" + pathObj = getNewPath(self.pathType, self.isClosedShape) + activeObj = context.view_layer.objects.active + if activeObj.type == "EMPTY" and activeObj.ootEmptyType == "Scene": + pathObj.parent = activeObj object.select_all(action="DESELECT") pathObj.select_set(True) context.view_layer.objects.active = pathObj return {"FINISHED"} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=320) + + def draw(self, context): + layout = self.layout + layout.label(text="Path Settings") + props = [("Path Type", "pathType"), ("Closed Shape", "isClosedShape")] + + for desc, propName in props: + split = layout.split(factor=0.30) + split.label(text=desc) + split.prop(self, propName) + + +class OOTClearTransformAndLock(Operator): + bl_idname = "object.oot_clear_transform" + bl_label = "Clear Transform (Scenes & Cutscenes)" + bl_options = {"REGISTER", "UNDO"} + + def clearTransform(self, obj: Object): + print(obj.name) + prevSelect = obj.select_get() + obj.select_set(True) + object.location_clear() + object.rotation_clear() + object.scale_clear() + object.origin_clear() + if obj.type != "EMPTY": + object.transform_apply(location=True, rotation=True, scale=True) + obj.select_set(prevSelect) + + def execute(self, context: Context): + try: + for obj in bpy.data.objects: + if obj.type == "EMPTY": + if obj.ootEmptyType in ["Scene", "Cutscene"]: + self.clearTransform(obj) + for childObj in obj.children_recursive: + self.clearTransform(childObj) + self.report({"INFO"}, "Success!") + return {"FINISHED"} + except: + return {"CANCELLED"} + + +class OOTQuickImport(Operator): + bl_idname = "object.oot_quick_import" + bl_label = "Quick Import" + bl_options = {"REGISTER", "UNDO"} + bl_description = ( + "Import (almost) anything by inputting a symbol name from an object." + " This operator automatically finds the file to import from (within objects)" + ) + + sym_name: StringProperty( + name="Symbol name", + description=( + "Which symbol to import." + " This may be a display list (e.g. gBoomerangDL), " + "a skeleton (e.g. object_daiku_Skel_007958), " + "an animation (with the appropriate skeleton selected, e.g. object_daiku_Anim_008164)" + ), + ) + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + self.layout.prop(self, "sym_name", text="Symbol") + + def execute(self, context: Context): + try: + quick_import_exec( + context, + self.sym_name, + ) + except QuickImportAborted as e: + self.report({"ERROR"}, e.message) + return {"CANCELLED"} + return {"FINISHED"} diff --git a/fast64_internal/oot/tools/panel.py b/fast64_internal/oot/tools/panel.py index 1e5151701..e72ac48a8 100644 --- a/fast64_internal/oot/tools/panel.py +++ b/fast64_internal/oot/tools/panel.py @@ -7,6 +7,8 @@ OOT_AddRoom, OOT_AddCutscene, OOT_AddPath, + OOTClearTransformAndLock, + OOTQuickImport, ) @@ -22,6 +24,8 @@ def draw(self, context): col.operator(OOT_AddRoom.bl_idname) col.operator(OOT_AddCutscene.bl_idname) col.operator(OOT_AddPath.bl_idname) + col.operator(OOTClearTransformAndLock.bl_idname) + col.operator(OOTQuickImport.bl_idname) oot_operator_panel_classes = [ @@ -35,6 +39,8 @@ def draw(self, context): OOT_AddRoom, OOT_AddCutscene, OOT_AddPath, + OOTClearTransformAndLock, + OOTQuickImport, ] diff --git a/fast64_internal/oot/tools/quick_import.py b/fast64_internal/oot/tools/quick_import.py new file mode 100644 index 000000000..4dcb772d0 --- /dev/null +++ b/fast64_internal/oot/tools/quick_import.py @@ -0,0 +1,115 @@ +from pathlib import Path +import os +import re + +import bpy + +from ..f3d.properties import OOTDLImportSettings +from ..skeleton.properties import OOTSkeletonImportSettings +from ..animation.properties import OOTAnimImportSettingsProperty + + +class QuickImportAborted(Exception): + def __init__(self, message): + super().__init__(message) + self.message = message + + +def quick_import_exec(context: bpy.types.Context, sym_name: str): + sym_name = sym_name.strip() + if sym_name == "": + raise QuickImportAborted("No symbol name given") + if not all( + ( + "a" <= c <= "z" + or "A" <= c <= "Z" + or "0" <= c <= "9" + or c + in { + "_", + } + ) + for c in sym_name + ): + raise QuickImportAborted("Symbol names only have characters a-zA-Z0-9_") + + sym_def_pattern = re.compile(rf"([^\s]+)\s+{sym_name}\s*(\[[^\]]*\])?\s*=") + + base_dir_p = Path(context.scene.ootDecompPath) + assets_objects_dir_p = base_dir_p / "assets" / "objects" + + all_found_defs: dict[Path, list[tuple[str, str]]] = dict() + + for dirpath, dirnames, filenames in os.walk(assets_objects_dir_p): + dirpath_p = Path(dirpath) + for filename in filenames: + file_p = dirpath_p / filename + # Only look into C files + if file_p.suffix != ".c": + continue + source = file_p.read_text() + # Simple check to see if we should look into this file any further + if sym_name not in source: + continue + found_defs = sym_def_pattern.findall(source) + print(file_p, f"{found_defs=}") + all_found_defs[file_p] = found_defs + + # Ideally if for example sym_name was gLinkAdultHookshotTipDL, + # all_found_defs now contains: + # {Path('.../assets/objects/object_link_boy/object_link_boy.c'): [('Gfx', '[]')]} + # or with gButterflySkel: + # {Path('.../assets/objects/gameplay_field_keep/gameplay_field_keep.c'): [('SkeletonHeader', '')]} + + if len(all_found_defs) == 0: + raise QuickImportAborted(f"Couldn't find a definition of {sym_name}") + if len(all_found_defs) > 1: + raise QuickImportAborted( + f"Found definitions of {sym_name} in several files: " + + ", ".join(str(p.relative_to(assets_objects_dir_p)) for p in all_found_defs.keys()) + ) + assert len(all_found_defs) == 1 + sym_file_p, sym_defs = list(all_found_defs.items())[0] + if len(sym_defs) > 1: + raise QuickImportAborted( + f"Found several definitions of {sym_name} in {sym_file_p.relative_to(assets_objects_dir_p)}" + ) + + # We found a single definition of the symbol + sym_def_type, sym_def_array_decl = sym_defs[0] + is_array = sym_def_array_decl != "" + object_name = sym_file_p.relative_to(assets_objects_dir_p).parts[0] + + if sym_def_type == "Gfx" and is_array: + settings: OOTDLImportSettings = context.scene.fast64.oot.DLImportSettings + settings.name = sym_name + settings.folder = object_name + settings.actorOverlayName = "" + settings.isCustom = False + bpy.ops.object.oot_import_dl() + elif sym_def_type in {"SkeletonHeader", "FlexSkeletonHeader"} and not is_array: + settings: OOTSkeletonImportSettings = context.scene.fast64.oot.skeletonImportSettings + settings.isCustom = False + if sym_name == "gLinkAdultSkel": + settings.mode = "Adult Link" + elif sym_name == "gLinkChildSkel": + settings.mode = "Child Link" + else: + settings.mode = "Generic" + settings.name = sym_name + settings.folder = object_name + settings.actorOverlayName = "" + bpy.ops.object.oot_import_skeleton() + elif sym_def_type == "AnimationHeader" and not is_array: + settings: OOTAnimImportSettingsProperty = context.scene.fast64.oot.animImportSettings + settings.isCustom = False + settings.isLink = False + settings.animName = sym_name + settings.folderName = object_name + bpy.ops.object.oot_import_anim() + else: + raise QuickImportAborted( + f"Don't know how to import {sym_def_type}" + + ("[]" if is_array else "") + + f" (symbol found in {object_name})" + ) diff --git a/fast64_internal/panels.py b/fast64_internal/panels.py index 6c598a488..adc5f97db 100644 --- a/fast64_internal/panels.py +++ b/fast64_internal/panels.py @@ -1,20 +1,20 @@ - import bpy -sm64GoalImport = 'Import' # Not in enum, separate UI option +sm64GoalImport = "Import" # Not in enum, separate UI option sm64GoalTypeEnum = [ - ('All', 'All', 'All'), - ('Export Object/Actor/Anim', 'Export Object/Actor/Anim', 'Export Object/Actor/Anim'), - ('Export Level', 'Export Level', 'Export Level'), - ('Export Displaylist', 'Export Displaylist', 'Export Displaylist'), - ('Export UI Image', 'Export UI Image', 'Export UI Image'), + ("All", "All", "All"), + ("Export Object/Actor/Anim", "Export Object/Actor/Anim", "Export Object/Actor/Anim"), + ("Export Level", "Export Level", "Export Level"), + ("Export Displaylist", "Export Displaylist", "Export Displaylist"), + ("Export UI Image", "Export UI Image", "Export UI Image"), ] + class SM64_Panel(bpy.types.Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = 'SM64' - bl_options = {'DEFAULT_CLOSED'} + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "SM64" + bl_options = {"DEFAULT_CLOSED"} # goal refers to the selected sm64GoalTypeEnum, a different selection than this goal will filter this panel out goal = None # if this is True, the panel is hidden whenever the scene's exportType is not 'C' @@ -23,25 +23,26 @@ class SM64_Panel(bpy.types.Panel): @classmethod def poll(cls, context): sm64Props = bpy.context.scene.fast64.sm64 - if context.scene.gameEditorMode != 'SM64': + if context.scene.gameEditorMode != "SM64": return False elif not cls.goal: - return True # Panel should always be shown + return True # Panel should always be shown elif cls.goal == sm64GoalImport: # Only show if importing is enabled return sm64Props.showImportingMenus - elif cls.decomp_only and sm64Props.exportType != 'C': + elif cls.decomp_only and sm64Props.exportType != "C": return False sceneGoal = sm64Props.goal - return sceneGoal == 'All' or sceneGoal == cls.goal + return sceneGoal == "All" or sceneGoal == cls.goal + class OOT_Panel(bpy.types.Panel): - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = 'OOT' - bl_options = {'DEFAULT_CLOSED'} + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "OOT" + bl_options = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context): - return context.scene.gameEditorMode == 'OOT' + return context.scene.gameEditorMode == "OOT" diff --git a/fast64_internal/render_settings.py b/fast64_internal/render_settings.py index 850970e4e..3fa61049d 100644 --- a/fast64_internal/render_settings.py +++ b/fast64_internal/render_settings.py @@ -1,253 +1,265 @@ -import bpy -import mathutils -import math -from .utility import * - - -def on_update_sm64_render_settings(self, context: bpy.types.Context): - renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings - if renderSettings.sm64Area and renderSettings.useObjectRenderPreview: - area: bpy.types.Object = renderSettings.sm64Area - renderSettings.fogPreviewColor = tuple(c for c in area.area_fog_color) - renderSettings.fogPreviewPosition = tuple(round(p) for p in area.area_fog_position) - - renderSettings.clippingPlanes = tuple(float(p) for p in area.clipPlanes) - - -def on_update_oot_render_settings(self, context: bpy.types.Context): - renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings - if renderSettings.ootSceneObject is None or not renderSettings.useObjectRenderPreview: - return - header = ootGetSceneOrRoomHeader( - renderSettings.ootSceneObject, - renderSettings.ootSceneHeader, - False, - ) - if header is None: - return - lMode = header.skyboxLighting - if lMode == "0x01" or (lMode == "Custom" and not renderSettings.ootForceTimeOfDay): - if renderSettings.ootLightIdx >= len(header.lightList): - return - l = header.lightList[renderSettings.ootLightIdx] - renderSettings.ambientColor = tuple(c for c in l.ambient) - col0, dir0 = ootGetBaseOrCustomLight(l, 0, False, False) - renderSettings.lightColor = tuple(c for c in col0) - renderSettings.lightDirection = tuple(d for d in dir0) - # TODO: Implement light1 into shader nodes - # col1, dir1 = ootGetBaseOrCustomLight(l, 1, False, False) - # renderSettings.light1Color = tuple(c for c in col1) - # renderSettings.light1Direction = tuple(d for d in dir1) - renderSettings.fogPreviewColor = tuple(c for c in l.fogColor) - renderSettings.fogPreviewPosition = (l.fogNear, l.fogFar) - else: - if header.skyboxLighting == "0x00": - tod = header.timeOfDayLights - lights = [tod.dawn, tod.day, tod.dusk, tod.night] - else: - if renderSettings.ootLightIdx + 4 > len(header.lightList): - return - lights = header.lightList[renderSettings.ootLightIdx:renderSettings.ootLightIdx+4] - assert len(lights) == 4 - todTimes = [0.0, 4.0, 6.0, 8.0, 16.0, 17.0, 19.0, 24.0] - todSets = [ 3, 3, 0, 1, 1, 2, 3, 3] - t = renderSettings.ootTime - for i in range(len(todTimes) - 1): - assert t >= todTimes[i] - if t < todTimes[i+1]: - la, lb = lights[todSets[i]], lights[todSets[i+1]] - fade = (t - todTimes[i]) / (todTimes[i+1] - todTimes[i]) - break - else: - raise PluginError("OoT time of day out of range") - def interpColors(cola, colb, fade): - cola = mathutils.Vector(tuple(c for c in cola)) - colb = mathutils.Vector(tuple(c for c in colb)) - return cola + (colb - cola) * fade - renderSettings.ambientColor = interpColors(la.ambient, lb.ambient, fade) - col0a, _ = ootGetBaseOrCustomLight(la, 0, False, False) - col0b, _ = ootGetBaseOrCustomLight(lb, 0, False, False) - renderSettings.lightColor = col0a + (col0b - col0a) * fade - # TODO: Implement light1 into shader nodes - # col1a, _ = ootGetBaseOrCustomLight(la, 1, False, False) - # col1b, _ = ootGetBaseOrCustomLight(lb, 1, False, False) - # renderSettings.light1Color = col1a * fa + col1b * fb - sint, cost = math.sin(math.tau * t / 24.0), math.cos(math.tau * t / 24.0) - renderSettings.lightDirection = mathutils.Vector(( - sint * 120.0 / 127.0, - -cost * 120.0 / 127.0, - -cost * 20.0 / 127.0, - )).normalized() - # TODO: Implement light1 into shader nodes - # renderSettings.light1Direction = -renderSettings.lightDirection - renderSettings.fogColor = interpColors(la.fogColor, lb.fogColor, fade) - renderSettings.fogPreviewPosition = ( - la.fogNear + int(float(lb.fogNear - la.fogNear) * fade), - la.fogFar + int(float(lb.fogFar - la.fogFar ) * fade), - ) - - -def update_lighting_space(renderSettings: "Fast64RenderSettings_Properties"): - if renderSettings.useWorldSpaceLighting: - bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ - "GeometryNormal_WorldSpace" - ] - else: - bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ - "GeometryNormal_ViewSpace" - ] - - -def update_scene_props_from_render_settings( - context: bpy.types.Context, - sceneOutputs: bpy.types.NodeGroupOutput, - renderSettings: "Fast64RenderSettings_Properties", -): - enableFog = int(renderSettings.enableFogPreview) - sceneOutputs.inputs["FogEnable"].default_value = enableFog - - sceneOutputs.inputs["FogColor"].default_value = tuple(c for c in renderSettings.fogPreviewColor) - sceneOutputs.inputs["FogNear"].default_value = renderSettings.fogPreviewPosition[0] - sceneOutputs.inputs["FogFar"].default_value = renderSettings.fogPreviewPosition[1] - - sceneOutputs.inputs["F3D_NearClip"].default_value = float(renderSettings.clippingPlanes[0]) - sceneOutputs.inputs["F3D_FarClip"].default_value = float(renderSettings.clippingPlanes[1]) - - sceneOutputs.inputs["ShadeColor"].default_value = tuple(c for c in renderSettings.lightColor) - sceneOutputs.inputs["AmbientColor"].default_value = tuple(c for c in renderSettings.ambientColor) - sceneOutputs.inputs["LightDirection"].default_value = tuple( - d for d in (mathutils.Vector(renderSettings.lightDirection) @ transform_mtx_blender_to_n64()) - ) - - update_lighting_space(renderSettings) - - sceneOutputs.inputs["Blender_Game_Scale"].default_value = float(get_blender_to_game_scale(context)) - - -def on_update_render_preview_nodes(self, context: bpy.types.Context): - sceneProps = bpy.data.node_groups.get("SceneProperties") - if sceneProps == None: - print("Could not locate SceneProperties!") - return - - sceneOutputs: bpy.types.NodeGroupOutput = sceneProps.nodes["Group Output"] - renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings - update_scene_props_from_render_settings(context, sceneOutputs, renderSettings) - - -def on_update_render_settings(self, context: bpy.types.Context): - sceneProps = bpy.data.node_groups.get("SceneProperties") - if sceneProps == None: - print("Could not locate sceneProps!") - return - - match context.scene.gameEditorMode: - case "SM64": - on_update_sm64_render_settings(self, context) - case "OOT": - on_update_oot_render_settings(self, context) - case _: - pass - - on_update_render_preview_nodes(self, context) - - -def poll_sm64_area(self, object): - return object.sm64_obj_type == "Area Root" - - -def poll_oot_scene(self, object): - return object.ootEmptyType == "Scene" - - -def resync_scene_props(): - if "ShdCol_L" in bpy.data.node_groups and "GeometryNormal_WorldSpace" in bpy.data.node_groups: - renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings - # Lighting space needs to be updated due to the nodes being shared and reloaded - update_lighting_space(renderSettings) - - -class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): - enableFogPreview: bpy.props.BoolProperty(name="Enable Fog Preview", default=True, update=on_update_render_settings) - fogPreviewColor: bpy.props.FloatVectorProperty( - name="Fog Color", - subtype="COLOR", - size=4, - min=0, - max=1, - default=(1, 1, 1, 1), - update=on_update_render_preview_nodes, - ) - ambientColor: bpy.props.FloatVectorProperty( - name="Ambient Light", - subtype="COLOR", - size=4, - min=0, - max=1, - default=(0.5, 0.5, 0.5, 1), - update=on_update_render_preview_nodes, - ) - lightColor: bpy.props.FloatVectorProperty( - name="Light Color", - subtype="COLOR", - size=4, - min=0, - max=1, - default=(1, 1, 1, 1), - update=on_update_render_preview_nodes, - ) - lightDirection: bpy.props.FloatVectorProperty( - name="Light Direction", - subtype="DIRECTION", - size=3, - min=-1, - max=1, - default=mathutils.Vector((0.5, 0.5, 1)).normalized(), # pre normalized - update=on_update_render_preview_nodes, - ) - useWorldSpaceLighting: bpy.props.BoolProperty( - name="Use World Space Lighting", default=True, update=on_update_render_settings - ) - # Fog Preview is int because values reflect F3D values - fogPreviewPosition: bpy.props.IntVectorProperty( - name="Fog Position", size=2, min=0, max=0x7FFFFFFF, default=(985, 1000), update=on_update_render_preview_nodes - ) - # Clipping planes are float because values reflect F3D values - clippingPlanes: bpy.props.FloatVectorProperty( - name="Clipping Planes", size=2, min=0, default=(100, 30000), update=on_update_render_preview_nodes - ) - useObjectRenderPreview: bpy.props.BoolProperty( - name="Use Object Preview", default=True, update=on_update_render_settings - ) - # SM64 - sm64Area: bpy.props.PointerProperty( - name="Area Object", type=bpy.types.Object, update=on_update_sm64_render_settings, poll=poll_sm64_area - ) - # OOT - ootSceneObject: bpy.props.PointerProperty( - name="Scene Object", type=bpy.types.Object, update=on_update_oot_render_settings, poll=poll_oot_scene - ) - ootSceneHeader: bpy.props.IntProperty( - name="Header/Setup", - description="Scene header / setup to use lighting data from", - min=0, soft_max=10, default=0, - update=on_update_oot_render_settings, - ) - ootForceTimeOfDay: bpy.props.BoolProperty( - name="Force Time of Day", - description="Interpolate between four lights based on the time", - default=False, - update=on_update_oot_render_settings, - ) - ootLightIdx: bpy.props.IntProperty( - name="Light Index", - min=0, soft_max=10, default=0, - update=on_update_oot_render_settings, - ) - ootTime: bpy.props.FloatProperty( - name="Time of Day (Hours)", - description="Time of day to emulate lighting conditions at, in hours", - min=0.0, max=23.99, default = 10.0, precision=2, subtype="TIME", unit="TIME", - update=on_update_oot_render_settings, - ) - +import bpy +import mathutils +import math +from .utility import * + + +def on_update_sm64_render_settings(self, context: bpy.types.Context): + renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings + if renderSettings.sm64Area and renderSettings.useObjectRenderPreview: + area: bpy.types.Object = renderSettings.sm64Area + renderSettings.fogPreviewColor = tuple(c for c in area.area_fog_color) + renderSettings.fogPreviewPosition = tuple(round(p) for p in area.area_fog_position) + + renderSettings.clippingPlanes = tuple(float(p) for p in area.clipPlanes) + + +def on_update_oot_render_settings(self, context: bpy.types.Context): + renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings + if renderSettings.ootSceneObject is None or not renderSettings.useObjectRenderPreview: + return + header = ootGetSceneOrRoomHeader( + renderSettings.ootSceneObject, + renderSettings.ootSceneHeader, + False, + ) + if header is None: + return + lMode = header.skyboxLighting + if lMode == "0x01" or (lMode == "Custom" and not renderSettings.ootForceTimeOfDay): + if renderSettings.ootLightIdx >= len(header.lightList): + return + l = header.lightList[renderSettings.ootLightIdx] + renderSettings.ambientColor = tuple(c for c in l.ambient) + col0, dir0 = ootGetBaseOrCustomLight(l, 0, False, False) + renderSettings.lightColor = tuple(c for c in col0) + renderSettings.lightDirection = tuple(d for d in dir0) + # TODO: Implement light1 into shader nodes + # col1, dir1 = ootGetBaseOrCustomLight(l, 1, False, False) + # renderSettings.light1Color = tuple(c for c in col1) + # renderSettings.light1Direction = tuple(d for d in dir1) + renderSettings.fogPreviewColor = tuple(c for c in l.fogColor) + renderSettings.fogPreviewPosition = (l.fogNear, l.fogFar) + else: + if header.skyboxLighting == "0x00": + tod = header.timeOfDayLights + lights = [tod.dawn, tod.day, tod.dusk, tod.night] + else: + if renderSettings.ootLightIdx + 4 > len(header.lightList): + return + lights = header.lightList[renderSettings.ootLightIdx : renderSettings.ootLightIdx + 4] + assert len(lights) == 4 + todTimes = [0.0, 4.0, 6.0, 8.0, 16.0, 17.0, 19.0, 24.0] + todSets = [3, 3, 0, 1, 1, 2, 3, 3] + t = renderSettings.ootTime + for i in range(len(todTimes) - 1): + assert t >= todTimes[i] + if t < todTimes[i + 1]: + la, lb = lights[todSets[i]], lights[todSets[i + 1]] + fade = (t - todTimes[i]) / (todTimes[i + 1] - todTimes[i]) + break + else: + raise PluginError("OoT time of day out of range") + + def interpColors(cola, colb, fade): + cola = mathutils.Vector(tuple(c for c in cola)) + colb = mathutils.Vector(tuple(c for c in colb)) + return cola + (colb - cola) * fade + + renderSettings.ambientColor = interpColors(la.ambient, lb.ambient, fade) + col0a, _ = ootGetBaseOrCustomLight(la, 0, False, False) + col0b, _ = ootGetBaseOrCustomLight(lb, 0, False, False) + renderSettings.lightColor = col0a + (col0b - col0a) * fade + # TODO: Implement light1 into shader nodes + # col1a, _ = ootGetBaseOrCustomLight(la, 1, False, False) + # col1b, _ = ootGetBaseOrCustomLight(lb, 1, False, False) + # renderSettings.light1Color = col1a * fa + col1b * fb + sint, cost = math.sin(math.tau * t / 24.0), math.cos(math.tau * t / 24.0) + renderSettings.lightDirection = mathutils.Vector( + ( + sint * 120.0 / 127.0, + -cost * 120.0 / 127.0, + -cost * 20.0 / 127.0, + ) + ).normalized() + # TODO: Implement light1 into shader nodes + # renderSettings.light1Direction = -renderSettings.lightDirection + renderSettings.fogColor = interpColors(la.fogColor, lb.fogColor, fade) + renderSettings.fogPreviewPosition = ( + la.fogNear + int(float(lb.fogNear - la.fogNear) * fade), + la.fogFar + int(float(lb.fogFar - la.fogFar) * fade), + ) + + +def update_lighting_space(renderSettings: "Fast64RenderSettings_Properties"): + if renderSettings.useWorldSpaceLighting: + bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ + "GeometryNormal_WorldSpace" + ] + else: + bpy.data.node_groups["ShdCol_L"].nodes["GeometryNormal"].node_tree = bpy.data.node_groups[ + "GeometryNormal_ViewSpace" + ] + + +def update_scene_props_from_render_settings( + context: bpy.types.Context, + sceneOutputs: bpy.types.NodeGroupOutput, + renderSettings: "Fast64RenderSettings_Properties", +): + enableFog = int(renderSettings.enableFogPreview) + sceneOutputs.inputs["FogEnable"].default_value = enableFog + + sceneOutputs.inputs["FogColor"].default_value = tuple(c for c in renderSettings.fogPreviewColor) + sceneOutputs.inputs["FogNear"].default_value = renderSettings.fogPreviewPosition[0] + sceneOutputs.inputs["FogFar"].default_value = renderSettings.fogPreviewPosition[1] + + sceneOutputs.inputs["F3D_NearClip"].default_value = float(renderSettings.clippingPlanes[0]) + sceneOutputs.inputs["F3D_FarClip"].default_value = float(renderSettings.clippingPlanes[1]) + + sceneOutputs.inputs["ShadeColor"].default_value = tuple(c for c in renderSettings.lightColor) + sceneOutputs.inputs["AmbientColor"].default_value = tuple(c for c in renderSettings.ambientColor) + sceneOutputs.inputs["LightDirection"].default_value = tuple( + d for d in (mathutils.Vector(renderSettings.lightDirection) @ transform_mtx_blender_to_n64()) + ) + + update_lighting_space(renderSettings) + + sceneOutputs.inputs["Blender_Game_Scale"].default_value = float(get_blender_to_game_scale(context)) + + +def on_update_render_preview_nodes(self, context: bpy.types.Context): + sceneProps = bpy.data.node_groups.get("SceneProperties") + if sceneProps == None: + print("Could not locate SceneProperties!") + return + + sceneOutputs: bpy.types.NodeGroupOutput = sceneProps.nodes["Group Output"] + renderSettings: "Fast64RenderSettings_Properties" = context.scene.fast64.renderSettings + update_scene_props_from_render_settings(context, sceneOutputs, renderSettings) + + +def on_update_render_settings(self, context: bpy.types.Context): + sceneProps = bpy.data.node_groups.get("SceneProperties") + if sceneProps == None: + print("Could not locate sceneProps!") + return + + match context.scene.gameEditorMode: + case "SM64": + on_update_sm64_render_settings(self, context) + case "OOT": + on_update_oot_render_settings(self, context) + case _: + pass + + on_update_render_preview_nodes(self, context) + + +def poll_sm64_area(self, object): + return object.sm64_obj_type == "Area Root" + + +def poll_oot_scene(self, object): + return object.ootEmptyType == "Scene" + + +def resync_scene_props(): + if "ShdCol_L" in bpy.data.node_groups and "GeometryNormal_WorldSpace" in bpy.data.node_groups: + renderSettings: "Fast64RenderSettings_Properties" = bpy.context.scene.fast64.renderSettings + # Lighting space needs to be updated due to the nodes being shared and reloaded + update_lighting_space(renderSettings) + + +class Fast64RenderSettings_Properties(bpy.types.PropertyGroup): + enableFogPreview: bpy.props.BoolProperty(name="Enable Fog Preview", default=True, update=on_update_render_settings) + fogPreviewColor: bpy.props.FloatVectorProperty( + name="Fog Color", + subtype="COLOR", + size=4, + min=0, + max=1, + default=(1, 1, 1, 1), + update=on_update_render_preview_nodes, + ) + ambientColor: bpy.props.FloatVectorProperty( + name="Ambient Light", + subtype="COLOR", + size=4, + min=0, + max=1, + default=(0.5, 0.5, 0.5, 1), + update=on_update_render_preview_nodes, + ) + lightColor: bpy.props.FloatVectorProperty( + name="Light Color", + subtype="COLOR", + size=4, + min=0, + max=1, + default=(1, 1, 1, 1), + update=on_update_render_preview_nodes, + ) + lightDirection: bpy.props.FloatVectorProperty( + name="Light Direction", + subtype="DIRECTION", + size=3, + min=-1, + max=1, + default=mathutils.Vector((0.5, 0.5, 1)).normalized(), # pre normalized + update=on_update_render_preview_nodes, + ) + useWorldSpaceLighting: bpy.props.BoolProperty( + name="Use World Space Lighting", default=True, update=on_update_render_settings + ) + # Fog Preview is int because values reflect F3D values + fogPreviewPosition: bpy.props.IntVectorProperty( + name="Fog Position", size=2, min=0, max=0x7FFFFFFF, default=(985, 1000), update=on_update_render_preview_nodes + ) + # Clipping planes are float because values reflect F3D values + clippingPlanes: bpy.props.FloatVectorProperty( + name="Clipping Planes", size=2, min=0, default=(100, 30000), update=on_update_render_preview_nodes + ) + useObjectRenderPreview: bpy.props.BoolProperty( + name="Use Object Preview", default=True, update=on_update_render_settings + ) + # SM64 + sm64Area: bpy.props.PointerProperty( + name="Area Object", type=bpy.types.Object, update=on_update_sm64_render_settings, poll=poll_sm64_area + ) + # OOT + ootSceneObject: bpy.props.PointerProperty( + name="Scene Object", type=bpy.types.Object, update=on_update_oot_render_settings, poll=poll_oot_scene + ) + ootSceneHeader: bpy.props.IntProperty( + name="Header/Setup", + description="Scene header / setup to use lighting data from", + min=0, + soft_max=10, + default=0, + update=on_update_oot_render_settings, + ) + ootForceTimeOfDay: bpy.props.BoolProperty( + name="Force Time of Day", + description="Interpolate between four lights based on the time", + default=False, + update=on_update_oot_render_settings, + ) + ootLightIdx: bpy.props.IntProperty( + name="Light Index", + min=0, + soft_max=10, + default=0, + update=on_update_oot_render_settings, + ) + ootTime: bpy.props.FloatProperty( + name="Time of Day (Hours)", + description="Time of day to emulate lighting conditions at, in hours", + min=0.0, + max=23.99, + default=10.0, + precision=2, + subtype="TIME", + unit="TIME", + update=on_update_oot_render_settings, + ) diff --git a/fast64_internal/sm64/c_templates/tile_scroll.py b/fast64_internal/sm64/c_templates/tile_scroll.py index 90eb5c27f..6fa1adb69 100644 --- a/fast64_internal/sm64/c_templates/tile_scroll.py +++ b/fast64_internal/sm64/c_templates/tile_scroll.py @@ -1,4 +1,4 @@ -tile_scroll_c = '''#include +tile_scroll_c = """#include #include "game/memory.h" #include "game/tile_scroll.h" @@ -40,9 +40,9 @@ tile->v += t; } -''' +""" -tile_scroll_h = '''#include "types.h" +tile_scroll_h = """#include "types.h" #define PACK_TILESIZE(w, d) ((w << 2) + d) @@ -61,4 +61,4 @@ void shift_s_down(Gfx *dl, u32 cmd, u16 s); void shift_t_down(Gfx *dl, u32 cmd, u16 t); -''' \ No newline at end of file +""" diff --git a/fast64_internal/sm64/parse_function_map.py b/fast64_internal/sm64/parse_function_map.py index 06b3589bf..ae8842d84 100644 --- a/fast64_internal/sm64/parse_function_map.py +++ b/fast64_internal/sm64/parse_function_map.py @@ -1,23 +1,24 @@ from re import search -refresh_name = 'Refresh 13' -function_map_path = './sm64.us.map' -output_map_path = './sm64_function_map_output.py' +refresh_name = "Refresh 13" +function_map_path = "./sm64.us.map" +output_map_path = "./sm64_function_map_output.py" + def parse_func_map(): - mapfile = open(function_map_path, 'r') - outfile = open(output_map_path, 'w') + mapfile = open(function_map_path, "r") + outfile = open(output_map_path, "w") outfile.write('\t"' + refresh_name + '" : {\n') nextLine = mapfile.readline() - while nextLine != '' and nextLine != 'Linker script and memory map\n': + while nextLine != "" and nextLine != "Linker script and memory map\n": nextLine = mapfile.readline() - while nextLine != '' and nextLine != ' build/us/src/menu/level_select_menu.o(.text)\n': - if nextLine[:17] == ' ' * 16 + '0': + while nextLine != "" and nextLine != " build/us/src/menu/level_select_menu.o(.text)\n": + if nextLine[:17] == " " * 16 + "0": outfile.write('\t\t"' + nextLine[26:34] + '" : ') searchName = nextLine[34:] - searchResult = search(r'\s*(\S*).*', searchName) + searchResult = search(r"\s*(\S*).*", searchName) outfile.write('"' + searchResult.group(1) + '",\n') nextLine = mapfile.readline() outfile.write("\t}\n") diff --git a/fast64_internal/sm64/sm64_anim.py b/fast64_internal/sm64/sm64_anim.py index 0d0c669f5..8f3219d78 100644 --- a/fast64_internal/sm64/sm64_anim.py +++ b/fast64_internal/sm64/sm64_anim.py @@ -255,7 +255,6 @@ def exportAnimationC(armatureObj, loopAnim, dirPath, dirName, groupName, customE # if animation header isnĀ“t already in the table then add it. if sm64_anim.header.name not in stringData: - # search for the NULL value which represents the end of the table # (this value is not present in vanilla animation tables) footerIndex = stringData.rfind("\tNULL,\n") @@ -295,7 +294,6 @@ def exportAnimationC(armatureObj, loopAnim, dirPath, dirName, groupName, customE def exportAnimationBinary(romfile, exportRange, armatureObj, DMAAddresses, segmentData, isDMA, loopAnim): - startAddress = get64bitAlignedAddr(exportRange[0]) sm64_anim = exportAnimationCommon(armatureObj, loopAnim, armatureObj.name) @@ -552,6 +550,7 @@ def importAnimationToBlender(romfile, startAddress, armatureObj, segmentData, is stashActionInArmature(armatureObj, anim) armatureObj.animation_data.action = anim + def readAnimation(name, romfile, startAddress, segmentData, isDMA): animationHeader = readAnimHeader(name, romfile, startAddress, segmentData, isDMA) @@ -697,7 +696,7 @@ def readValueIndex(romfile, startAddress): # multiply 2 because value is the index in array of shorts (???) startOffset = int.from_bytes(romfile.read(2), "big") * 2 - #print(str(hex(startAddress)) + ": " + str(numFrames) + " " + str(startOffset)) + # print(str(hex(startAddress)) + ": " + str(numFrames) + " " + str(startOffset)) return SM64_AnimIndex(numFrames, startOffset) @@ -953,10 +952,12 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Armature not selected.") armatureObj = context.active_object - if type(armatureObj.data) is not bpy.types.Armature: + if armatureObj.type != "ARMATURE": raise PluginError("Armature not selected.") - importAnimationToBlender(romfileSrc, animStart, armatureObj, segmentData, context.scene.isDMAImport, "sm64_anim") + importAnimationToBlender( + romfileSrc, animStart, armatureObj, segmentData, context.scene.isDMAImport, "sm64_anim" + ) romfileSrc.close() self.report({"INFO"}, "Success!") except Exception as e: @@ -987,12 +988,12 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Armature not selected.") armatureObj = context.active_object - if type(armatureObj.data) is not bpy.types.Armature: + if armatureObj.type != "ARMATURE": raise PluginError("Armature not selected.") for adress, animName in marioAnimations: importAnimationToBlender(romfileSrc, adress, armatureObj, {}, context.scene.isDMAImport, animName) - + romfileSrc.close() self.report({"INFO"}, "Success!") except Exception as e: @@ -1012,8 +1013,8 @@ class SM64_ImportAnimPanel(SM64_Panel): # called every frame def draw(self, context): col = self.layout.column() - propsAnimImport = col.operator(SM64_ImportAnimMario.bl_idname) - propsMarioAnimsImport = col.operator(SM64_ImportAllMarioAnims.bl_idname) + propsAnimImport = col.operator(SM64_ImportAnimMario.bl_idname) + propsMarioAnimsImport = col.operator(SM64_ImportAllMarioAnims.bl_idname) col.prop(context.scene, "isDMAImport") if not context.scene.isDMAImport: diff --git a/fast64_internal/sm64/sm64_collision.py b/fast64_internal/sm64/sm64_collision.py index 3d84c96f3..b79e1aedf 100644 --- a/fast64_internal/sm64/sm64_collision.py +++ b/fast64_internal/sm64/sm64_collision.py @@ -295,7 +295,6 @@ def exportCollisionC( groupName, levelName, ): - dirPath, texDir = getExportDir(customExport, dirPath, headerType, levelName, "", name) name = toAlnum(name) @@ -396,7 +395,7 @@ def exportCollisionCommon(obj, transformMatrix, includeSpecials, includeChildren collision = Collision(toAlnum(name) + "_collision") for collisionType, faces in collisionDict.items(): collision.triangles[collisionType] = [] - for (faceVerts, specialParam, room) in faces: + for faceVerts, specialParam, room in faces: indices = [] for roundedPosition in faceVerts: index = collisionVertIndex(roundedPosition, collision.vertices) @@ -418,7 +417,7 @@ def exportCollisionCommon(obj, transformMatrix, includeSpecials, includeChildren def addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, areaIndex): - if isinstance(obj.data, bpy.types.Mesh) and not obj.ignore_collision: + if obj.type == "MESH" and not obj.ignore_collision: if len(obj.data.materials) == 0: raise PluginError(obj.name + " must have a material associated with it.") obj.data.calc_loop_triangles() diff --git a/fast64_internal/sm64/sm64_constants.py b/fast64_internal/sm64/sm64_constants.py index ed20b6dd0..a27ba8554 100644 --- a/fast64_internal/sm64/sm64_constants.py +++ b/fast64_internal/sm64/sm64_constants.py @@ -1801,214 +1801,214 @@ def __init__(self, geoAddr, level, switchDict): marioAnimations = [ -# ( Adress, "Animation name" ), - ( 5162640, "0 - Slow ledge climb up"), - ( 5165520, "1 - Fall over backwards"), - ( 5165544, "2 - Backward air kb"), - ( 5172396, "3 - Dying on back"), - ( 5177044, "4 - Backflip"), - ( 5179584, "5 - Climbing up pole"), - ( 5185656, "6 - Grab pole short"), - ( 5186824, "7 - Grab pole swing part 1"), - ( 5186848, "8 - Grab pole swing part 2"), - ( 5191920, "9 - Handstand idle"), - ( 5194740, "10 - Handstand jump"), - ( 5194764, "11 - Start handstand"), - ( 5188592, "12 - Return from handstand"), - ( 5196388, "13 - Idle on pole"), - ( 5197436, "14 - A pose"), - ( 5197792, "15 - Skid on ground"), - ( 5197816, "16 - Stop skid"), - ( 5199596, "17 - Crouch from fast longjump"), - ( 5201048, "18 - Crouch from a slow longjump"), - ( 5202644, "19 - Fast longjump"), - ( 5204600, "20 - Slow longjump"), - ( 5205980, "21 - Airborne on stomach"), - ( 5207188, "22 - Walk with light object"), - ( 5211916, "23 - Run with light object"), - ( 5215136, "24 - Slow walk with light object"), - ( 5219864, "25 - Shivering and warming hands"), - ( 5225496, "26 - Shivering return to idle "), - ( 5226920, "27 - Shivering"), - ( 5230056, "28 - Climb down on ledge"), - ( 5231112, "29 - Credits - Waving"), - ( 5232768, "30 - Credits - Look up"), - ( 5234576, "31 - Credits - Return from look up"), - ( 5235700, "32 - Credits - Raising hand"), - ( 5243100, "33 - Credits - Lowering hand"), - ( 5245988, "34 - Credits - Taking off cap"), - ( 5248016, "35 - Credits - Start walking and look up"), - ( 5256508, "36 - Credits - Look back then run"), - ( 5266160, "37 - Final Bowser - Raise hand and spin"), - ( 5274456, "38 - Final Bowser - Wing cap take off"), - ( 5282084, "39 - Credits - Peach sign"), - ( 5291340, "40 - Stand up from lava boost"), - ( 5292628, "41 - Fire/Lava burn"), - ( 5293488, "42 - Wing cap flying"), - ( 5295016, "43 - Hang on owl"), - ( 5296876, "44 - Land on stomach"), - ( 5296900, "45 - Air forward kb"), - ( 5302796, "46 - Dying on stomach"), - ( 5306100, "47 - Suffocating"), - ( 5313796, "48 - Coughing"), - ( 5319500, "49 - Throw catch key"), - ( 5330436, "50 - Dying fall over"), - ( 5338604, "51 - Idle on ledge"), - ( 5341720, "52 - Fast ledge grab"), - ( 5343296, "53 - Hang on ceiling"), - ( 5347276, "54 - Put cap on"), - ( 5351252, "55 - Take cap off then on"), - ( 5358356, "56 - Quickly put cap on"), - ( 5359476, "57 - Head stuck in ground"), - ( 5372172, "58 - Ground pound landing"), - ( 5372824, "59 - Triple jump ground-pound"), - ( 5374304, "60 - Start ground-pound"), - ( 5374328, "61 - Ground-pound"), - ( 5375380, "62 - Bottom stuck in ground"), - ( 5387148, "63 - Idle with light object"), - ( 5390520, "64 - Jump land with light object"), - ( 5391892, "65 - Jump with light object"), - ( 5392704, "66 - Fall land with light object"), - ( 5393936, "67 - Fall with light object"), - ( 5394296, "68 - Fall from sliding with light object"), - ( 5395224, "69 - Sliding on bottom with light object"), - ( 5395248, "70 - Stand up from sliding with light object"), - ( 5396716, "71 - Riding shell"), - ( 5397832, "72 - Walking"), - ( 5403208, "73 - Forward flip"), - ( 5404784, "74 - Jump riding shell"), - ( 5405676, "75 - Land from double jump"), - ( 5407340, "76 - Double jump fall"), - ( 5408288, "77 - Single jump"), - ( 5408312, "78 - Land from single jump"), - ( 5411044, "79 - Air kick"), - ( 5412900, "80 - Double jump rise"), - ( 5413596, "81 - Start forward spinning"), - ( 5414876, "82 - Throw light object"), - ( 5416032, "83 - Fall from slide kick"), - ( 5418280, "84 - Bend kness riding shell"), - ( 5419872, "85 - Legs stuck in ground"), - ( 5431416, "86 - General fall"), - ( 5431440, "87 - General land"), - ( 5433276, "88 - Being grabbed"), - ( 5434636, "89 - Grab heavy object"), - ( 5437964, "90 - Slow land from dive"), - ( 5441520, "91 - Fly from cannon"), - ( 5442516, "92 - Moving right while hanging"), - ( 5444052, "93 - Moving left while hanging"), - ( 5445472, "94 - Missing cap"), - ( 5457860, "95 - Pull door walk in"), - ( 5463196, "96 - Push door walk in"), - ( 5467492, "97 - Unlock door"), - ( 5480428, "98 - Start reach pocket"), - ( 5481448, "99 - Reach pocket"), - ( 5483352, "100 - Stop reach pocket"), - ( 5484876, "101 - Ground throw"), - ( 5486852, "102 - Ground kick"), - ( 5489076, "103 - First punch"), - ( 5489740, "104 - Second punch"), - ( 5490356, "105 - First punch fast"), - ( 5491396, "106 - Second punch fast"), - ( 5492732, "107 - Pick up light object"), - ( 5493948, "108 - Pushing"), - ( 5495508, "109 - Start riding shell"), - ( 5497072, "110 - Place light object"), - ( 5498484, "111 - Forward spinning"), - ( 5498508, "112 - Backward spinning"), - ( 5498884, "113 - Breakdance"), - ( 5501240, "114 - Running"), - ( 5501264, "115 - Running (unused)"), - ( 5505884, "116 - Soft back kb"), - ( 5508004, "117 - Soft front kb"), - ( 5510172, "118 - Dying in quicksand"), - ( 5515096, "119 - Idle in quicksand"), - ( 5517836, "120 - Move in quicksand"), - ( 5528568, "121 - Electrocution"), - ( 5532480, "122 - Shocked"), - ( 5533160, "123 - Backward kb"), - ( 5535796, "124 - Forward kb"), - ( 5538372, "125 - Idle heavy object"), - ( 5539764, "126 - Stand against wall"), - ( 5544580, "127 - Side step left"), - ( 5548480, "128 - Side step right"), - ( 5553004, "129 - Start sleep idle"), - ( 5557588, "130 - Start sleep scratch"), - ( 5563636, "131 - Start sleep yawn"), - ( 5568648, "132 - Start sleep sitting"), - ( 5573680, "133 - Sleep idle"), - ( 5574280, "134 - Sleep start laying"), - ( 5577460, "135 - Sleep laying"), - ( 5579300, "136 - Dive"), - ( 5579324, "137 - Slide dive"), - ( 5580860, "138 - Ground bonk"), - ( 5584116, "139 - Stop slide light object"), - ( 5587364, "140 - Slide kick"), - ( 5588288, "141 - Crouch from slide kick"), - ( 5589652, "142 - Slide motionless"), - ( 5589676, "143 - Stop slide"), - ( 5591572, "144 - Fall from slide"), - ( 5592860, "145 - Slide"), - ( 5593404, "146 - Tiptoe"), - ( 5599280, "147 - Twirl land"), - ( 5600160, "148 - Twirl"), - ( 5600516, "149 - Start twirl"), - ( 5601072, "150 - Stop crouching"), - ( 5602028, "151 - Start crouching"), - ( 5602720, "152 - Crouching"), - ( 5605756, "153 - Crawling"), - ( 5613048, "154 - Stop crawling"), - ( 5613968, "155 - Start crawling"), - ( 5614876, "156 - Summon star"), - ( 5620036, "157 - Return star approach door"), - ( 5622256, "158 - Backwards water kb"), - ( 5626540, "159 - Swim with object part 1"), - ( 5627592, "160 - Swim with object part 2"), - ( 5628260, "161 - Flutter kick with object"), - ( 5629456, "162 - Action end with object in water"), - ( 5631180, "163 - Stop holding object in water"), - ( 5634048, "164 - Holding object in water"), - ( 5635976, "165 - Drowning part 1"), - ( 5641400, "166 - Drowning part 2"), - ( 5646324, "167 - Dying in water"), - ( 5649660, "168 - Forward kb in water"), - ( 5653848, "169 - Falling from water"), - ( 5655852, "170 - Swimming part 1"), - ( 5657100, "171 - Swimming part 2"), - ( 5658128, "172 - Flutter kick"), - ( 5660112, "173 - Action end in water"), - ( 5662248, "174 - Pick up object in water"), - ( 5663480, "175 - Grab object in water part 2"), - ( 5665916, "176 - Grab object in water part 1"), - ( 5666632, "177 - Throw object in water"), - ( 5669328, "178 - Idle in water"), - ( 5671428, "179 - Star dance in water"), - ( 5678200, "180 - Return from in water star dance"), - ( 5680324, "181 - Grab bowser"), - ( 5680348, "182 - Swing bowser"), - ( 5682008, "183 - Release bowser"), - ( 5685264, "184 - Holding bowser"), - ( 5686316, "185 - Heavy throw"), - ( 5688660, "186 - Walk panting"), - ( 5689924, "187 - Walk with heavy object"), - ( 5694332, "188 - Turning part 1"), - ( 5694356, "189 - Turning part 2"), - ( 5696160, "190 - Side flip land"), - ( 5697196, "191 - Side flip"), - ( 5699408, "192 - Triple jump land"), - ( 5702136, "193 - Triple jump"), - ( 5704880, "194 - First person"), - ( 5710580, "195 - Idle head left"), - ( 5712800, "196 - Idle head right"), - ( 5715020, "197 - Idle head center"), - ( 5717240, "198 - Handstand left"), - ( 5719184, "199 - Handstand right"), - ( 5722304, "200 - Wake up from sleeping"), - ( 5724228, "201 - Wake up from laying"), - ( 5726444, "202 - Start tiptoeing"), - ( 5728720, "203 - Slide jump"), - ( 5728744, "204 - Start wallkick"), - ( 5730404, "205 - Star dance"), - ( 5735864, "206 - Return from star dance"), - ( 5737600, "207 - Forwards spinning flip"), - ( 5740584, "208 - Triple jump fly"), + # ( Adress, "Animation name" ), + (5162640, "0 - Slow ledge climb up"), + (5165520, "1 - Fall over backwards"), + (5165544, "2 - Backward air kb"), + (5172396, "3 - Dying on back"), + (5177044, "4 - Backflip"), + (5179584, "5 - Climbing up pole"), + (5185656, "6 - Grab pole short"), + (5186824, "7 - Grab pole swing part 1"), + (5186848, "8 - Grab pole swing part 2"), + (5191920, "9 - Handstand idle"), + (5194740, "10 - Handstand jump"), + (5194764, "11 - Start handstand"), + (5188592, "12 - Return from handstand"), + (5196388, "13 - Idle on pole"), + (5197436, "14 - A pose"), + (5197792, "15 - Skid on ground"), + (5197816, "16 - Stop skid"), + (5199596, "17 - Crouch from fast longjump"), + (5201048, "18 - Crouch from a slow longjump"), + (5202644, "19 - Fast longjump"), + (5204600, "20 - Slow longjump"), + (5205980, "21 - Airborne on stomach"), + (5207188, "22 - Walk with light object"), + (5211916, "23 - Run with light object"), + (5215136, "24 - Slow walk with light object"), + (5219864, "25 - Shivering and warming hands"), + (5225496, "26 - Shivering return to idle "), + (5226920, "27 - Shivering"), + (5230056, "28 - Climb down on ledge"), + (5231112, "29 - Credits - Waving"), + (5232768, "30 - Credits - Look up"), + (5234576, "31 - Credits - Return from look up"), + (5235700, "32 - Credits - Raising hand"), + (5243100, "33 - Credits - Lowering hand"), + (5245988, "34 - Credits - Taking off cap"), + (5248016, "35 - Credits - Start walking and look up"), + (5256508, "36 - Credits - Look back then run"), + (5266160, "37 - Final Bowser - Raise hand and spin"), + (5274456, "38 - Final Bowser - Wing cap take off"), + (5282084, "39 - Credits - Peach sign"), + (5291340, "40 - Stand up from lava boost"), + (5292628, "41 - Fire/Lava burn"), + (5293488, "42 - Wing cap flying"), + (5295016, "43 - Hang on owl"), + (5296876, "44 - Land on stomach"), + (5296900, "45 - Air forward kb"), + (5302796, "46 - Dying on stomach"), + (5306100, "47 - Suffocating"), + (5313796, "48 - Coughing"), + (5319500, "49 - Throw catch key"), + (5330436, "50 - Dying fall over"), + (5338604, "51 - Idle on ledge"), + (5341720, "52 - Fast ledge grab"), + (5343296, "53 - Hang on ceiling"), + (5347276, "54 - Put cap on"), + (5351252, "55 - Take cap off then on"), + (5358356, "56 - Quickly put cap on"), + (5359476, "57 - Head stuck in ground"), + (5372172, "58 - Ground pound landing"), + (5372824, "59 - Triple jump ground-pound"), + (5374304, "60 - Start ground-pound"), + (5374328, "61 - Ground-pound"), + (5375380, "62 - Bottom stuck in ground"), + (5387148, "63 - Idle with light object"), + (5390520, "64 - Jump land with light object"), + (5391892, "65 - Jump with light object"), + (5392704, "66 - Fall land with light object"), + (5393936, "67 - Fall with light object"), + (5394296, "68 - Fall from sliding with light object"), + (5395224, "69 - Sliding on bottom with light object"), + (5395248, "70 - Stand up from sliding with light object"), + (5396716, "71 - Riding shell"), + (5397832, "72 - Walking"), + (5403208, "73 - Forward flip"), + (5404784, "74 - Jump riding shell"), + (5405676, "75 - Land from double jump"), + (5407340, "76 - Double jump fall"), + (5408288, "77 - Single jump"), + (5408312, "78 - Land from single jump"), + (5411044, "79 - Air kick"), + (5412900, "80 - Double jump rise"), + (5413596, "81 - Start forward spinning"), + (5414876, "82 - Throw light object"), + (5416032, "83 - Fall from slide kick"), + (5418280, "84 - Bend kness riding shell"), + (5419872, "85 - Legs stuck in ground"), + (5431416, "86 - General fall"), + (5431440, "87 - General land"), + (5433276, "88 - Being grabbed"), + (5434636, "89 - Grab heavy object"), + (5437964, "90 - Slow land from dive"), + (5441520, "91 - Fly from cannon"), + (5442516, "92 - Moving right while hanging"), + (5444052, "93 - Moving left while hanging"), + (5445472, "94 - Missing cap"), + (5457860, "95 - Pull door walk in"), + (5463196, "96 - Push door walk in"), + (5467492, "97 - Unlock door"), + (5480428, "98 - Start reach pocket"), + (5481448, "99 - Reach pocket"), + (5483352, "100 - Stop reach pocket"), + (5484876, "101 - Ground throw"), + (5486852, "102 - Ground kick"), + (5489076, "103 - First punch"), + (5489740, "104 - Second punch"), + (5490356, "105 - First punch fast"), + (5491396, "106 - Second punch fast"), + (5492732, "107 - Pick up light object"), + (5493948, "108 - Pushing"), + (5495508, "109 - Start riding shell"), + (5497072, "110 - Place light object"), + (5498484, "111 - Forward spinning"), + (5498508, "112 - Backward spinning"), + (5498884, "113 - Breakdance"), + (5501240, "114 - Running"), + (5501264, "115 - Running (unused)"), + (5505884, "116 - Soft back kb"), + (5508004, "117 - Soft front kb"), + (5510172, "118 - Dying in quicksand"), + (5515096, "119 - Idle in quicksand"), + (5517836, "120 - Move in quicksand"), + (5528568, "121 - Electrocution"), + (5532480, "122 - Shocked"), + (5533160, "123 - Backward kb"), + (5535796, "124 - Forward kb"), + (5538372, "125 - Idle heavy object"), + (5539764, "126 - Stand against wall"), + (5544580, "127 - Side step left"), + (5548480, "128 - Side step right"), + (5553004, "129 - Start sleep idle"), + (5557588, "130 - Start sleep scratch"), + (5563636, "131 - Start sleep yawn"), + (5568648, "132 - Start sleep sitting"), + (5573680, "133 - Sleep idle"), + (5574280, "134 - Sleep start laying"), + (5577460, "135 - Sleep laying"), + (5579300, "136 - Dive"), + (5579324, "137 - Slide dive"), + (5580860, "138 - Ground bonk"), + (5584116, "139 - Stop slide light object"), + (5587364, "140 - Slide kick"), + (5588288, "141 - Crouch from slide kick"), + (5589652, "142 - Slide motionless"), + (5589676, "143 - Stop slide"), + (5591572, "144 - Fall from slide"), + (5592860, "145 - Slide"), + (5593404, "146 - Tiptoe"), + (5599280, "147 - Twirl land"), + (5600160, "148 - Twirl"), + (5600516, "149 - Start twirl"), + (5601072, "150 - Stop crouching"), + (5602028, "151 - Start crouching"), + (5602720, "152 - Crouching"), + (5605756, "153 - Crawling"), + (5613048, "154 - Stop crawling"), + (5613968, "155 - Start crawling"), + (5614876, "156 - Summon star"), + (5620036, "157 - Return star approach door"), + (5622256, "158 - Backwards water kb"), + (5626540, "159 - Swim with object part 1"), + (5627592, "160 - Swim with object part 2"), + (5628260, "161 - Flutter kick with object"), + (5629456, "162 - Action end with object in water"), + (5631180, "163 - Stop holding object in water"), + (5634048, "164 - Holding object in water"), + (5635976, "165 - Drowning part 1"), + (5641400, "166 - Drowning part 2"), + (5646324, "167 - Dying in water"), + (5649660, "168 - Forward kb in water"), + (5653848, "169 - Falling from water"), + (5655852, "170 - Swimming part 1"), + (5657100, "171 - Swimming part 2"), + (5658128, "172 - Flutter kick"), + (5660112, "173 - Action end in water"), + (5662248, "174 - Pick up object in water"), + (5663480, "175 - Grab object in water part 2"), + (5665916, "176 - Grab object in water part 1"), + (5666632, "177 - Throw object in water"), + (5669328, "178 - Idle in water"), + (5671428, "179 - Star dance in water"), + (5678200, "180 - Return from in water star dance"), + (5680324, "181 - Grab bowser"), + (5680348, "182 - Swing bowser"), + (5682008, "183 - Release bowser"), + (5685264, "184 - Holding bowser"), + (5686316, "185 - Heavy throw"), + (5688660, "186 - Walk panting"), + (5689924, "187 - Walk with heavy object"), + (5694332, "188 - Turning part 1"), + (5694356, "189 - Turning part 2"), + (5696160, "190 - Side flip land"), + (5697196, "191 - Side flip"), + (5699408, "192 - Triple jump land"), + (5702136, "193 - Triple jump"), + (5704880, "194 - First person"), + (5710580, "195 - Idle head left"), + (5712800, "196 - Idle head right"), + (5715020, "197 - Idle head center"), + (5717240, "198 - Handstand left"), + (5719184, "199 - Handstand right"), + (5722304, "200 - Wake up from sleeping"), + (5724228, "201 - Wake up from laying"), + (5726444, "202 - Start tiptoeing"), + (5728720, "203 - Slide jump"), + (5728744, "204 - Start wallkick"), + (5730404, "205 - Star dance"), + (5735864, "206 - Return from star dance"), + (5737600, "207 - Forwards spinning flip"), + (5740584, "208 - Triple jump fly"), ] diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 8feaa89e0..059e40815 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -16,6 +16,7 @@ from ..f3d.f3d_bleed import BleedGraphics from ..f3d.f3d_gbi import ( + get_F3D_GBI, GbiMacro, GfxTag, FMaterial, @@ -98,8 +99,8 @@ class SM64Model(FModel): - def __init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod): - FModel.__init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod) + def __init__(self, name, DLFormat, matWriteMethod): + FModel.__init__(self, name, DLFormat, matWriteMethod) def getDrawLayerV3(self, obj): return int(obj.draw_layer_static) @@ -153,8 +154,8 @@ def vertexScrollToC(self, fMaterial: FMaterial, vtxListName: str, vtxCount: int) return data -def exportTexRectToC(dirPath, texProp, f3dType, isHWv1, texDir, savePNG, name, exportToProject, projectExportData): - fTexRect = exportTexRectCommon(texProp, f3dType, isHWv1, name, not savePNG) +def exportTexRectToC(dirPath, texProp, texDir, savePNG, name, exportToProject, projectExportData): + fTexRect = exportTexRectCommon(texProp, name, not savePNG) if name is None or name == "": raise PluginError("Name cannot be empty.") @@ -270,7 +271,7 @@ def modifyDLForHUD(data): return data -def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): +def exportTexRectCommon(texProp, name, convertTextureData): tex = texProp.tex if tex is None: raise PluginError("No texture is selected.") @@ -285,7 +286,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData): texProp.T.mask = ceil(log(texProp.tex.size[1], 2) - 0.001) texProp.T.shift = 0 - fTexRect = FTexRect(f3dType, isHWv1, toAlnum(name), GfxMatWriteMethod.WriteDifferingAndRevert) + fTexRect = FTexRect(toAlnum(name), GfxMatWriteMethod.WriteDifferingAndRevert) fMaterial = FMaterial(toAlnum(name) + "_mat", DLFormat.Dynamic) # dl_hud_img_begin @@ -343,8 +344,6 @@ def sm64ExportF3DtoC( obj, DLFormat, transformMatrix, - f3dType, - isHWv1, texDir, savePNG, texSeparate, @@ -359,8 +358,6 @@ def sm64ExportF3DtoC( inline = bpy.context.scene.exportInlineF3D fModel = SM64Model( - f3dType, - isHWv1, name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, @@ -481,8 +478,8 @@ def sm64ExportF3DtoC( return fileStatus -def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, segmentData, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) +def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, segmentData, includeChildren): + fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) fMesh = fMeshes[fModel.getDrawLayerV3(obj)] fModel.freePalettes() @@ -501,8 +498,8 @@ def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv return fMesh.draw.startAddress, addrRange, segPointerData -def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, RAMAddr, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) +def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, RAMAddr, includeChildren): + fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) fMesh = fMeshes[fModel.getDrawLayerV3(obj)] segmentData = copy.copy(bank0Segment) @@ -521,14 +518,14 @@ def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType, return (fMesh.draw.startAddress, (startAddress, startAddress + len(data)), segPointerData) -def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, f3dType, isHWv1, includeChildren): - fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) +def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, includeChildren): + fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert) fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True) fMesh = fMeshes[fModel.getDrawLayerV3(obj)] data, startRAM = getBinaryBank0F3DData(fModel, 0, [0, 0xFFFFFF]) # must happen after getBinaryBank0F3DData - address_ptrs = fModel.get_ptr_addresses(f3dType) + address_ptrs = fModel.get_ptr_addresses(get_F3D_GBI()) writeInsertableFile(filepath, insertableBinaryTypes["Display List"], address_ptrs, fMesh.draw.startAddress, data) @@ -570,7 +567,7 @@ def execute(self, context): if len(allObjs) == 0: raise PluginError("No objects selected.") obj = context.selected_objects[0] - if not isinstance(obj.data, bpy.types.Mesh): + if obj.type != "MESH": raise PluginError("Object is not a mesh.") # T, R, S = obj.matrix_world.decompose() @@ -603,8 +600,6 @@ def execute(self, context): obj, DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, bpy.context.scene.DLTexDir, bpy.context.scene.saveTextures, bpy.context.scene.DLSeparateTextureDef, @@ -624,8 +619,6 @@ def execute(self, context): bpy.path.abspath(context.scene.DLInsertableBinaryPath), finalTransform, obj, - context.scene.f3d_type, - context.scene.isHWv1, bpy.context.scene.DLincludeChildren, ) self.report({"INFO"}, "Success! DL at " + context.scene.DLInsertableBinaryPath + ".") @@ -648,8 +641,6 @@ def execute(self, context): [int(context.scene.DLExportStart, 16), int(context.scene.DLExportEnd, 16)], finalTransform, obj, - context.scene.f3d_type, - context.scene.isHWv1, getAddressFromRAMAddress(int(context.scene.DLRAMAddr, 16)), bpy.context.scene.DLincludeChildren, ) @@ -659,8 +650,6 @@ def execute(self, context): [int(context.scene.DLExportStart, 16), int(context.scene.DLExportEnd, 16)], finalTransform, obj, - context.scene.f3d_type, - context.scene.isHWv1, segmentData, bpy.context.scene.DLincludeChildren, ) @@ -796,8 +785,6 @@ def execute(self, context): exportTexRectToC( bpy.path.abspath(exportPath), context.scene.texrect, - context.scene.f3d_type, - context.scene.isHWv1, "textures/segment2", context.scene.saveTextures, context.scene.TexRectName, diff --git a/fast64_internal/sm64/sm64_geolayout_bone.py b/fast64_internal/sm64/sm64_geolayout_bone.py index 20b9d6c2c..7ca6f0141 100644 --- a/fast64_internal/sm64/sm64_geolayout_bone.py +++ b/fast64_internal/sm64/sm64_geolayout_bone.py @@ -1,3 +1,4 @@ +import bpy from bpy.ops import object from bpy.types import Bone, Object, Panel, Operator, Armature, Mesh, Material, PropertyGroup from bpy.utils import register_class, unregister_class @@ -81,7 +82,6 @@ def drawGeoInfo(panel: Panel, bone: Bone): - panel.layout.box().label(text="Geolayout Inspector") if bone is None: panel.layout.label(text="Edit geolayout properties in Pose mode.") @@ -252,6 +252,8 @@ def draw(self, context): col.prop(obj, "ignore_render") col.prop(obj, "ignore_collision") col.prop(obj, "use_f3d_culling") + if context.scene.exportInlineF3D: + col.prop(obj, "bleed_independently") if obj_scale_is_unified(obj) and len(obj.modifiers) == 0: col.prop(obj, "scaleFromGeolayout") # prop_split(col, obj, 'room_num', 'Room') @@ -427,9 +429,14 @@ def draw(self, context): def getSwitchOptionBone(switchArmature): optionBones = [] - for poseBone in switchArmature.pose.bones: - if poseBone.bone_group is not None and poseBone.bone_group.name == "SwitchOption": - optionBones.append(poseBone.name) + if bpy.app.version >= (4, 0, 0): + for bone in switchArmature.data.bones: + if "SwitchOption" in bone.collections: + optionBones.append(bone.name) + else: + for poseBone in switchArmature.pose.bones: + if poseBone.bone_group is not None and poseBone.bone_group.name == "SwitchOption": + optionBones.append(poseBone.name) if len(optionBones) > 1: raise PluginError("There should only be one switch option bone in " + switchArmature.name + ".") elif len(optionBones) < 1: @@ -441,18 +448,15 @@ def getSwitchOptionBone(switchArmature): return optionBones[0] -def updateBone(self, context): - if not hasattr(context, "bone"): - print("No bone in context.") - return +def updateBone(bone, context): armatureObj = context.object createBoneGroups(armatureObj) - if context.bone.geo_cmd not in animatableBoneTypes: - addBoneToGroup(armatureObj, context.bone.name, context.bone.geo_cmd) + if bone.geo_cmd not in animatableBoneTypes: + addBoneToGroup(armatureObj, bone.name, bone.geo_cmd) object.mode_set(mode="POSE") else: - addBoneToGroup(armatureObj, context.bone.name, None) + addBoneToGroup(armatureObj, bone.name, None) object.mode_set(mode="POSE") diff --git a/fast64_internal/sm64/sm64_geolayout_classes.py b/fast64_internal/sm64/sm64_geolayout_classes.py index b8fa02f60..3b93d8d53 100644 --- a/fast64_internal/sm64/sm64_geolayout_classes.py +++ b/fast64_internal/sm64/sm64_geolayout_classes.py @@ -263,6 +263,7 @@ 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 + bleed_independently = False # base behavior, can be changed with obj boolProp def get_dl_address(self): if self.hasDL and (self.dlRef or self.DLmicrocode is not None): @@ -454,33 +455,41 @@ def to_c(self): class GeoLayoutBleed(BleedGraphics): def bleed_geo_layout_graph(self, fModel: FModel, geo_layout_graph: GeolayoutGraph, use_rooms: bool = False): last_materials = dict() # last used material should be kept track of per layer - + def walk(node, last_materials): base_node = node.node if type(base_node) == JumpNode: if base_node.geolayout: for node in base_node.geolayout.nodes: - last_materials = walk(node, last_materials if not use_rooms else dict()) if not use_rooms else dict() + last_materials = ( + walk(node, last_materials if not use_rooms else dict()) if not use_rooms else dict() + ) else: last_materials = dict() fMesh = getattr(base_node, "fMesh", None) if fMesh: cmd_list = fMesh.drawMatOverrides.get(base_node.override_hash, None) or fMesh.draw - lastMat = last_materials.get(base_node.drawLayer, None) + last_mat = last_materials.get(base_node.drawLayer, None) default_render_mode = fModel.getRenderMode(base_node.drawLayer) - lastMat = self.bleed_fmesh(fModel.f3d, fMesh, lastMat, cmd_list, default_render_mode) + last_mat = self.bleed_fmesh( + fMesh, + last_mat if not base_node.bleed_independently else None, + cmd_list, + fModel.getAllMaterials().items(), + default_render_mode, + ) # if the mesh has culling, it can be culled, and create invalid combinations of f3d to represent the current full DL if fMesh.cullVertexList: last_materials[base_node.drawLayer] = None else: - last_materials[base_node.drawLayer] = lastMat - # don't carry over lastmat if it is a switch node or geo asm node - if type(base_node) in [SwitchNode, FunctionNode]: - last_materials = dict() + last_materials[base_node.drawLayer] = last_mat + # don't carry over last_mat if it is a switch node or geo asm node for child in node.children: + if type(base_node) in [SwitchNode, FunctionNode]: + last_materials = dict() last_materials = walk(child, last_materials) return last_materials - + for node in geo_layout_graph.startGeolayout.nodes: last_materials = walk(node, last_materials) self.clear_gfx_lists(fModel) @@ -606,7 +615,6 @@ def to_c(self): class TranslateRotateNode(BaseDisplayListNode): def __init__(self, drawLayer, fieldLayout, hasDL, translate, rotate, dlRef: str = None): - self.drawLayer = drawLayer self.fieldLayout = fieldLayout self.hasDL = hasDL diff --git a/fast64_internal/sm64/sm64_geolayout_parser.py b/fast64_internal/sm64/sm64_geolayout_parser.py index 26ab1c0bb..34d1b4ae1 100644 --- a/fast64_internal/sm64/sm64_geolayout_parser.py +++ b/fast64_internal/sm64/sm64_geolayout_parser.py @@ -4,7 +4,7 @@ from ..panels import SM64_Panel, sm64GoalImport from .sm64_level_parser import parseLevelAtPointer from .sm64_constants import level_pointers, level_enums -from .sm64_geolayout_bone import enumShadowType +from .sm64_geolayout_bone import enumShadowType, animatableBoneTypes, enumBoneType from .sm64_geolayout_constants import getGeoLayoutCmdLength, nodeGroupCmds, GEO_BRANCH_STORE from ..utility import ( @@ -70,8 +70,6 @@ def parseGeoLayout( useArmature, ignoreSwitch, shadeSmooth, - f3dType, - isHWv1, ): currentAddress = startAddress romfile.seek(currentAddress) @@ -118,8 +116,6 @@ def parseGeoLayout( 0, 0, [None] * 16 * 16, - f3dType, - isHWv1, segmentData=segmentData, ) @@ -169,7 +165,7 @@ def parseGeoLayout( # bpy.ops.transform.rotate(value = math.radians(-90), orient_axis = 'X') # bpy.ops.object.transform_apply() - if useArmature: + if bpy.app.version < (4, 0, 0) and useArmature: armatureObj.data.layers[1] = True """ @@ -201,8 +197,6 @@ def parseNode( switchLevel, switchCount, vertexBuffer, - f3dType, - isHWv1, singleChild=False, endCmd=GEO_NODE_CLOSE, segmentData=None, @@ -296,8 +290,6 @@ def parseNode( switchLevel, switchCount, vertexBuffer, - f3dType, - isHWv1, singleChild=switchActive, segmentData=segmentData, ) @@ -346,8 +338,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_TRANSLATE: # 0x11 @@ -364,8 +354,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_ROTATE: # 0x12 @@ -382,8 +370,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_LOAD_DL_W_OFFSET: # 0x13 @@ -400,8 +386,6 @@ def parseNode( currentCmd, segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_BILLBOARD: # 0x14 @@ -418,8 +402,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_LOAD_DL: # 0x15 @@ -436,8 +418,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_START_W_SHADOW: # 0x16 @@ -490,8 +470,6 @@ def parseNode( nodeIndex[-1], segmentData, vertexBuffer, - f3dType, - isHWv1, ) elif currentCmd[0] == GEO_START_W_RENDERAREA: # 0x20 @@ -530,11 +508,18 @@ def parseNode( def generateMetarig(armatureObj): + armature = armatureObj.data startBones = findStartBones(armatureObj) createBoneGroups(armatureObj) for boneName in startBones: traverseArmatureForMetarig(armatureObj, boneName, None) - armatureObj.data.layers = createBoneLayerMask([boneLayers["visual"]]) + if bpy.app.version >= (4, 0, 0): + if not "visual" in armature.collections: + armature.collections.new(name="visual") + armature.collections["visual"].assign(armature.bones[boneName]) + else: + armatureObj.data.layers = createBoneLayerMask([boneLayers["visual"]]) + if bpy.context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") @@ -542,15 +527,32 @@ def generateMetarig(armatureObj): def traverseArmatureForMetarig(armatureObj, boneName, parentName): if bpy.context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") - poseBone = armatureObj.pose.bones[boneName] - if poseBone.bone_group is None: - processBoneMeta(armatureObj, boneName, parentName) - elif poseBone.bone_group.name == "Ignore": - return + armature = armatureObj.data + bone = armature.bones[boneName] poseBone = armatureObj.pose.bones[boneName] - nextParentName = boneName if poseBone.bone_group is None else parentName - childrenNames = [child.name for child in poseBone.children] + + if bpy.app.version >= (4, 0, 0): + if "Ignore" in bone.collections: + return + nonAnimatableBoneTypes = set([item[0] for item in enumBoneType]) - animatableBoneTypes + isAnimatableBone = not any([item in bone.collections for item in nonAnimatableBoneTypes]) + if isAnimatableBone: + processBoneMeta(armatureObj, boneName, parentName) + nextParentName = boneName if isAnimatableBone else parentName + bone = armature.bones[boneName] # re-obtain reference after edit mode changes + childrenNames = [child.name for child in bone.children] + + else: + if poseBone.bone_group is None: + processBoneMeta(armatureObj, boneName, parentName) + elif poseBone.bone_group.name == "Ignore": + return + + poseBone = armatureObj.pose.bones[boneName] # re-obtain reference after edit mode changes + nextParentName = boneName if poseBone.bone_group is None else parentName + childrenNames = [child.name for child in poseBone.children] + for childName in childrenNames: traverseArmatureForMetarig(armatureObj, childName, nextParentName) @@ -558,6 +560,7 @@ def traverseArmatureForMetarig(armatureObj, boneName, parentName): def processBoneMeta(armatureObj, boneName, parentName): bpy.ops.object.mode_set(mode="EDIT") bone = armatureObj.data.edit_bones[boneName] + armature = armatureObj.data # create meta bone, which the actual bone copies the rotation of metabone = armatureObj.data.edit_bones.new("meta_" + boneName) @@ -596,16 +599,33 @@ def processBoneMeta(armatureObj, boneName, parentName): translateConstraint.target = armatureObj translateConstraint.subtarget = metaboneName - metabone.layers = createBoneLayerMask([boneLayers["meta"]]) + if bpy.app.version >= (4, 0, 0): + if "meta" not in armature.collections: + armature.collections.new(name="meta") + armature.collections["meta"].assign(metabone) + + if not "visual" in armature.collections: + armature.collections.new(name="visual") + armature.collections["visual"].assign(visualBone) + + # Ignore collection should always be created, but check just in case + # (generateMetarig() calls createBoneGroups() before traverseArmatureForMetarig()) + if not "Ignore" in armature.collections: + armature.collections.new(name="Ignore") + armature.collections["Ignore"].assign(visualBone) + armature.collections["Ignore"].assign(metabone) + + else: + metabone.layers = createBoneLayerMask([boneLayers["meta"]]) + visualBone.layers = createBoneLayerMask([boneLayers["visual"]]) + + metabonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") + visualBonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") + metabone.use_deform = False metabonePose.lock_rotation = (True, True, True) - - visualBone.layers = createBoneLayerMask([boneLayers["visual"]]) visualBone.use_deform = False - metabonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") - visualBonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") - bpy.ops.object.mode_set(mode="EDIT") metabone = armatureObj.data.edit_bones[metaboneName] visualBone = armatureObj.data.edit_bones[visualBoneName] @@ -644,6 +664,7 @@ def processBoneMeta(armatureObj, boneName, parentName): def createConnectBone(armatureObj, childName, parentName): + armature = armatureObj.data child = armatureObj.data.edit_bones[childName] parent = armatureObj.data.edit_bones[parentName] @@ -665,8 +686,16 @@ def createConnectBone(armatureObj, childName, parentName): bpy.ops.object.mode_set(mode="OBJECT") connectPoseBone = armatureObj.pose.bones[connectBoneName] connectBone = armatureObj.data.bones[connectBoneName] - connectBone.layers = createBoneLayerMask([boneLayers["visual"]]) - connectPoseBone.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") + + if bpy.app.version > (4, 0, 0): + if not "visual" in armature.collections: + armature.collections.new(name="visual") + armature.collections["visual"].assign(connectBone) + armature.collections["Ignore"].assign(connectBone) + else: + connectBone.layers = createBoneLayerMask([boneLayers["visual"]]) + connectPoseBone.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore") + connectPoseBone.lock_rotation = (True, True, True) bpy.ops.object.mode_set(mode="EDIT") @@ -817,10 +846,7 @@ def parseDL( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): - drawLayer = bitMask(currentCmd[1], 0, 4) romfile.seek(currentAddress) @@ -841,8 +867,6 @@ def parseDL( nodeIndex, "DisplayList", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -865,8 +889,6 @@ def parseDLWithOffset( currentCmd, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("DL_OFFSET " + hex(currentAddress)) romfile.seek(currentAddress) @@ -968,10 +990,7 @@ def handleNodeCommon( nodeIndex, boneGroupName, vertexBuffer, - f3dType, - isHWv1, ): - boneName = format(nodeIndex, "03") + "-" + boneGroupName.lower() if armatureObj is not None: @@ -1012,8 +1031,6 @@ def parseScale( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("SCALE " + hex(currentAddress)) @@ -1042,8 +1059,6 @@ def parseScale( nodeIndex, "Scale", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -1069,8 +1084,6 @@ def parseTranslateRotate( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("TRANSLATE_ROTATE " + hex(currentAddress)) @@ -1127,8 +1140,6 @@ def parseTranslateRotate( nodeIndex, "TranslateRotate", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -1159,8 +1170,6 @@ def parseTranslate( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("TRANSLATE " + hex(currentAddress)) @@ -1193,8 +1202,6 @@ def parseTranslate( nodeIndex, "Translate", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -1220,8 +1227,6 @@ def parseRotate( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("ROTATE " + hex(currentAddress)) @@ -1254,8 +1259,6 @@ def parseRotate( nodeIndex, "Rotate", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -1280,8 +1283,6 @@ def parseBillboard( nodeIndex, segmentData, vertexBuffer, - f3dType, - isHWv1, ): print("BILLBOARD " + hex(currentAddress)) @@ -1314,8 +1315,6 @@ def parseBillboard( nodeIndex, "Billboard", vertexBuffer, - f3dType, - isHWv1, ) if armatureObj is not None: bone = armatureObj.data.bones[boneName] @@ -1461,7 +1460,6 @@ def getMarioBoneName(startRelativeAddr, armatureData, default="sm64_mesh"): def assignMarioGeoMetadata(obj, commandAddress, geoStartAddress, cmdType, armatureData, lastTransRotAddr=None): - # for geo_pointer reading offsets: # cmd = 0 # draw layer = 1 @@ -1558,8 +1556,6 @@ def execute(self, context): generateArmature, ignoreSwitch, True, - context.scene.f3d_type, - context.scene.isHWv1, ) romfileSrc.close() diff --git a/fast64_internal/sm64/sm64_geolayout_utility.py b/fast64_internal/sm64/sm64_geolayout_utility.py index 752c227e1..f42220558 100644 --- a/fast64_internal/sm64/sm64_geolayout_utility.py +++ b/fast64_internal/sm64/sm64_geolayout_utility.py @@ -55,21 +55,33 @@ def createBoneLayerMask(values): def createBoneGroups(armatureObj): - for (groupName, properties) in boneNodeProperties.items(): - if getBoneGroupByName(armatureObj, groupName) is None: - boneGroup = armatureObj.pose.bone_groups.new(name=groupName) - boneGroup.color_set = properties.theme + armature = armatureObj.data + for groupName, properties in boneNodeProperties.items(): + if bpy.app.version >= (4, 0, 0): + if groupName not in armature.collections: + boneGroup = armature.collections.new(name=groupName) + else: + if getBoneGroupByName(armatureObj, groupName) is None: + boneGroup = armatureObj.pose.bone_groups.new(name=groupName) + boneGroup.color_set = properties.theme def addBoneToGroup(armatureObj, boneName, groupName): + armature = armatureObj.data if groupName is None: if bpy.context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") posebone = armatureObj.pose.bones[boneName] - bone = armatureObj.data.bones[boneName] - posebone.bone_group = None + bone = armature.bones[boneName] bone.use_deform = True - bone.layers = createBoneLayerMask([boneLayers["anim"]]) + if bpy.app.version >= (4, 0, 0): + if not "anim" in armature.collections: + armature.collections.new(name="anim") + armature.collections["anim"].assign(bone) + else: + posebone.bone_group = None + bone.layers = createBoneLayerMask([boneLayers["anim"]]) + posebone.lock_location = (False, False, False) posebone.lock_rotation = (False, False, False) posebone.lock_scale = (False, False, False) @@ -80,13 +92,24 @@ def addBoneToGroup(armatureObj, boneName, groupName): if bpy.context.mode != "OBJECT": bpy.ops.object.mode_set(mode="OBJECT") + posebone = armatureObj.pose.bones[boneName] bone = armatureObj.data.bones[boneName] - posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName) + if bpy.app.version >= (4, 0, 0): + armature.collections[groupName].assign(bone) + else: + posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName) + if groupName != "Ignore": bone.use_deform = boneNodeProperties[groupName].deform if groupName != "DisplayList": - bone.layers = createBoneLayerMask([boneLayers["other"]]) + if bpy.app.version >= (4, 0, 0): + if not "other" in armature.collections: + armature.collections.new(name="other") + armature.collections["other"].assign(bone) + else: + bone.layers = createBoneLayerMask([boneLayers["other"]]) + if groupName != "SwitchOption": posebone.lock_location = (True, True, True) posebone.lock_rotation = (True, True, True) diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index 33c6a33a5..73c3abcb5 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -60,9 +60,12 @@ geoNodeRotateOrder, ) +from ..f3d.f3d_bleed import ( + find_material_from_jump_cmd, +) + from ..f3d.f3d_material import ( isTexturePointSampled, - isLightingDisabled, ) from ..f3d.f3d_writer import ( @@ -78,10 +81,10 @@ saveMeshWithLargeTexturesByFaces, saveMeshByFaces, getF3DVert, - convertVertexData, ) from ..f3d.f3d_gbi import ( + get_F3D_GBI, GfxList, GfxListTag, GfxMatWriteMethod, @@ -334,35 +337,46 @@ def getCameraObj(camera): def appendRevertToGeolayout(geolayoutGraph, fModel): - fModel.materialRevert = GfxList( + materialRevert = GfxList( fModel.name + "_" + "material_revert_render_settings", GfxListTag.MaterialRevert, fModel.DLFormat ) - revertMatAndEndDraw(fModel.materialRevert, [DPSetEnvColor(0xFF, 0xFF, 0xFF, 0xFF), DPSetAlphaCompare("G_AC_NONE")]) + revertMatAndEndDraw(materialRevert, [DPSetEnvColor(0xFF, 0xFF, 0xFF, 0xFF), DPSetAlphaCompare("G_AC_NONE")]) - # Get all draw layers, turn layers into strings (some are ints), deduplicate using a set - drawLayers = set(str(layer) for layer in geolayoutGraph.getDrawLayers()) + # walk the geo layout graph to find the last used DL for each layer + last_gfx_list = dict() + + def walk(node, last_gfx_list): + base_node = node.node + if type(base_node) == JumpNode: + if base_node.geolayout: + for node in base_node.geolayout.nodes: + last_gfx_list = walk(node, last_gfx_list) + else: + last_materials = dict() + fMesh = getattr(base_node, "fMesh", None) + if fMesh: + cmd_list = fMesh.drawMatOverrides.get(base_node.override_hash, None) or fMesh.draw + last_gfx_list[base_node.drawLayer] = cmd_list + for child in node.children: + last_gfx_list = walk(child, last_gfx_list) + return last_gfx_list + + for node in geolayoutGraph.startGeolayout.nodes: + last_gfx_list = walk(node, last_gfx_list) # Revert settings in each draw layer - for layer in sorted(drawLayers): # Must be sorted, otherwise ordering is random due to `set` behavior - dlNode = DisplayListNode(layer) - dlNode.DLmicrocode = fModel.materialRevert + for gfx_list in last_gfx_list.values(): + # remove SPEndDisplayList from gfx_list, materialRevert has its own SPEndDisplayList cmd + while SPEndDisplayList() in gfx_list.commands: + gfx_list.commands.remove(SPEndDisplayList()) - # Assume first node is start render area - # This is important, since a render area groups things separately. - # If we added these nodes outside the render area, they would not happen - # right after the nodes inside. - geolayoutGraph.startGeolayout.nodes[0].children.append(TransformNode(dlNode)) + gfx_list.commands.extend(materialRevert.commands) # Convert to Geolayout -def convertArmatureToGeolayout( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, name, DLFormat, convertTextureData -): - +def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, name, DLFormat, convertTextureData): inline = bpy.context.scene.exportInlineF3D fModel = SM64Model( - f3dType, - isHWv1, name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, @@ -426,14 +440,11 @@ def convertArmatureToGeolayout( # Camera is unused here def convertObjectToGeolayout( - obj, convertTransformMatrix, f3dType, isHWv1, camera, name, fModel: FModel, areaObj, DLFormat, convertTextureData + obj, convertTransformMatrix, camera, name, fModel: FModel, areaObj, DLFormat, convertTextureData ): - inline = bpy.context.scene.exportInlineF3D if fModel is None: fModel = SM64Model( - f3dType, - isHWv1, name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, @@ -454,7 +465,7 @@ def convertObjectToGeolayout( else: geolayoutGraph = GeolayoutGraph(name + "_geo") - if isinstance(obj.data, bpy.types.Mesh) and obj.use_render_area: + if obj.type == "MESH" and obj.use_render_area: rootNode = TransformNode(StartRenderAreaNode(obj.culling_radius)) else: rootNode = TransformNode(StartNode()) @@ -503,8 +514,6 @@ def exportGeolayoutArmatureC( armatureObj, obj, convertTransformMatrix, - f3dType, - isHWv1, dirPath, texDir, savePNG, @@ -519,7 +528,7 @@ def exportGeolayoutArmatureC( DLFormat, ): geolayoutGraph, fModel = convertArmatureToGeolayout( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, dirName, DLFormat, not savePNG + armatureObj, obj, convertTransformMatrix, camera, dirName, DLFormat, not savePNG ) return saveGeolayoutC( @@ -542,8 +551,6 @@ def exportGeolayoutArmatureC( def exportGeolayoutObjectC( obj, convertTransformMatrix, - f3dType, - isHWv1, dirPath, texDir, savePNG, @@ -558,7 +565,7 @@ def exportGeolayoutObjectC( DLFormat, ): geolayoutGraph, fModel = convertObjectToGeolayout( - obj, convertTransformMatrix, f3dType, isHWv1, camera, dirName, None, None, DLFormat, not savePNG + obj, convertTransformMatrix, camera, dirName, None, None, DLFormat, not savePNG ) return saveGeolayoutC( @@ -811,29 +818,27 @@ def saveGeolayoutC( # Insertable Binary -def exportGeolayoutArmatureInsertableBinary( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, filepath, camera -): +def exportGeolayoutArmatureInsertableBinary(armatureObj, obj, convertTransformMatrix, filepath, camera): geolayoutGraph, fModel = convertArmatureToGeolayout( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True + armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True ) - saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3dType) + saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath) -def exportGeolayoutObjectInsertableBinary(obj, convertTransformMatrix, f3dType, isHWv1, filepath, camera): +def exportGeolayoutObjectInsertableBinary(obj, convertTransformMatrix, filepath, camera): geolayoutGraph, fModel = convertObjectToGeolayout( - obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True + obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True ) - saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3dType) + saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath) -def saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3d): +def saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath): data, startRAM = getBinaryBank0GeolayoutData(fModel, geolayoutGraph, 0, [0, 0xFFFFFF]) address_ptrs = geolayoutGraph.get_ptr_addresses() - address_ptrs.extend(fModel.get_ptr_addresses(f3d)) + address_ptrs.extend(fModel.get_ptr_addresses(get_F3D_GBI())) writeInsertableFile( filepath, insertableBinaryTypes["Geolayout"], address_ptrs, geolayoutGraph.startGeolayout.startAddress, data @@ -850,14 +855,11 @@ def exportGeolayoutArmatureBinaryBank0( levelCommandPos, modelID, textDumpFilePath, - f3dType, - isHWv1, RAMAddr, camera, ): - geolayoutGraph, fModel = convertArmatureToGeolayout( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True + armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True ) return saveGeolayoutBinaryBank0( @@ -873,14 +875,11 @@ def exportGeolayoutObjectBinaryBank0( levelCommandPos, modelID, textDumpFilePath, - f3dType, - isHWv1, RAMAddr, camera, ): - geolayoutGraph, fModel = convertObjectToGeolayout( - obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True + obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True ) return saveGeolayoutBinaryBank0( @@ -940,13 +939,10 @@ def exportGeolayoutArmatureBinary( levelCommandPos, modelID, textDumpFilePath, - f3dType, - isHWv1, camera, ): - geolayoutGraph, fModel = convertArmatureToGeolayout( - armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True + armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True ) return saveGeolayoutBinary( @@ -963,13 +959,10 @@ def exportGeolayoutObjectBinary( levelCommandPos, modelID, textDumpFilePath, - f3dType, - isHWv1, camera, ): - geolayoutGraph, fModel = convertObjectToGeolayout( - obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True + obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True ) return saveGeolayoutBinary( @@ -1025,6 +1018,7 @@ def geoWriteTextDump(textDumpFilePath, geolayoutGraph, levelData): # are converted to SwitchOverrideNodes. During this process, any material # override geometry will be generated as well. + # Afterward, the node hierarchy is traversed again, and any SwitchOverride # nodes are converted to actual geolayout node hierarchies. def generateSwitchOptions(transformNode, geolayout, geolayoutGraph, prefix): @@ -1219,9 +1213,9 @@ def duplicateNode(transformNode, parentNode, index): def partOfGeolayout(obj): - useGeoEmpty = obj.data is None and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) + useGeoEmpty = obj.type == "EMPTY" and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) - return isinstance(obj.data, bpy.types.Mesh) or useGeoEmpty + return obj.type == "MESH" or useGeoEmpty def getSwitchChildren(areaRoot): @@ -1334,13 +1328,13 @@ def processMesh( ): # finalTransform = copy.deepcopy(transformMatrix) - useGeoEmpty = obj.data is None and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) + useGeoEmpty = obj.type == "EMPTY" and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) - useSwitchNode = obj.data is None and obj.sm64_obj_type == "Switch" + useSwitchNode = obj.type == "EMPTY" and obj.sm64_obj_type == "Switch" - useInlineGeo = obj.data is None and checkIsSM64InlineGeoLayout(obj.sm64_obj_type) + useInlineGeo = obj.type == "EMPTY" and checkIsSM64InlineGeoLayout(obj.sm64_obj_type) - addRooms = isRoot and obj.data is None and obj.sm64_obj_type == "Area Root" and obj.enableRoomSwitch + addRooms = isRoot and obj.type == "EMPTY" and obj.sm64_obj_type == "Area Root" and obj.enableRoomSwitch # if useAreaEmpty and areaIndex is not None and obj.areaIndex != areaIndex: # return @@ -1488,8 +1482,7 @@ def processMesh( transformNode = TransformNode(node) - if obj.data is not None and (obj.use_render_range or obj.add_shadow or obj.add_func): - + if obj.type != "EMPTY" and (obj.use_render_range or obj.add_shadow or obj.add_func): parentTransformNode.children.append(transformNode) transformNode.parent = parentTransformNode transformNode.node.hasDL = False @@ -1514,7 +1507,7 @@ def processMesh( # Make sure to add additional cases to if statement above - if obj.data is None: + if obj.type == "EMPTY": fMeshes = {} elif obj.get("instanced_mesh_name"): temp_obj = get_obj_temp_mesh(obj) @@ -1572,6 +1565,7 @@ def processMesh( if not firstNodeProcessed: node.DLmicrocode = fMesh.draw node.fMesh = fMesh + node.bleed_independently = obj.bleed_independently node.drawLayer = drawLayer # previous drawLayer assigments useless? firstNodeProcessed = True else: @@ -1582,6 +1576,7 @@ def processMesh( ) additionalNode.DLmicrocode = fMesh.draw additionalNode.fMesh = fMesh + additionalNode.bleed_independently = obj.bleed_independently additionalTransformNode = TransformNode(additionalNode) transformNode.children.append(additionalTransformNode) additionalTransformNode.parent = transformNode @@ -1631,7 +1626,6 @@ def processBone( ): bone = armatureObj.data.bones[boneName] poseBone = armatureObj.pose.bones[boneName] - boneGroup = poseBone.bone_group finalTransform = copy.deepcopy(transformMatrix) materialOverrides = copy.copy(materialOverrides) @@ -1937,7 +1931,7 @@ def processBone( + str(switchIndex) + ", the switch option armature is None." ) - elif not isinstance(optionArmature.data, bpy.types.Armature): + elif optionArmature.type != "ARMATURE": raise PluginError( "Error: In switch bone " + boneName @@ -1972,7 +1966,7 @@ def processBone( # the switch node. optionObjs = [] for childObj in optionArmature.children: - if isinstance(childObj.data, bpy.types.Mesh): + if childObj.type == "MESH": optionObjs.append(childObj) if len(optionObjs) > 1: raise PluginError( @@ -2139,6 +2133,7 @@ def checkIfFirstNonASMNode(childNode): # parent connects child node to itself # skinned node handled by child + # A skinned mesh node should be before a mesh node. # However, other transform nodes may exist in between two mesh nodes, # So the skinned mesh node must be inserted before any of those transforms. @@ -2218,7 +2213,6 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent highestChildIndex > 0 and type(highestChildNode.parent.children[highestChildIndex - 1].node) is FunctionNode ): - precedingFunctionCmds.insert(0, copy.deepcopy(highestChildNode.parent.children[highestChildIndex - 1])) highestChildIndex -= 1 # _____________ @@ -2390,7 +2384,6 @@ def saveModelGivenVertexGroup( fMeshes = {} fSkinnedMeshes = {} for drawLayer, materialFaces in skinnedFaces.items(): - meshName = getFMeshName(vertexGroup, namePrefix, drawLayer, False) checkUniqueBoneNames(fModel, meshName, vertexGroup) skinnedMeshName = getFMeshName(vertexGroup, namePrefix, drawLayer, True) @@ -2471,7 +2464,7 @@ def saveModelGivenVertexGroup( ) # Must be done after all geometry saved - for (material, specificMat, overrideType) in materialOverrides: + for material, specificMat, overrideType in materialOverrides: for drawLayer, fMesh in fMeshes.items(): saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, drawLayer, convertTextureData) for drawLayer, fMesh in fSkinnedMeshes.items(): @@ -2503,18 +2496,6 @@ def saveOverrideDraw( last_replaced = None command_index = 0 - def find_material_from_jump_cmd(material_list: tuple[tuple[bpy.types.Material, str, FAreaData], tuple[FMaterial, tuple[int, int]]], dl_jump: SPDisplayList): - if dl_jump.displayList.tag == GfxListTag.Geometry: - return None, None - for mat in material_list: - fmaterial = mat[1][0] - bpy_material = mat[0][0] - if dl_jump.displayList.tag == GfxListTag.MaterialRevert and fmaterial.revert == dl_jump.displayList: - return bpy_material, fmaterial - elif fmaterial.material == dl_jump.displayList: - return bpy_material, fmaterial - return None, None - while command_index < len(meshMatOverride.commands): command = meshMatOverride.commands[command_index] if not isinstance(command, SPDisplayList): @@ -2529,7 +2510,7 @@ def find_material_from_jump_cmd(material_list: tuple[tuple[bpy.types.Material, s # replace the material load if necessary # if we replaced the previous load with the same override, then remove the cmd to optimize DL - if command.displayList.tag == GfxListTag.Material: + if command.displayList.tag & GfxListTag.Material: curMaterial = fmaterial if shouldModify: last_replaced = fmaterial @@ -2553,14 +2534,14 @@ def find_material_from_jump_cmd(material_list: tuple[tuple[bpy.types.Material, s prev_material = curMaterial # replace the revert if the override has a revert, otherwise remove the command - if command.displayList.tag == GfxListTag.MaterialRevert and shouldModify: + if command.displayList.tag & GfxListTag.MaterialRevert and shouldModify: if fOverrideMat.revert is not None: command.displayList = fOverrideMat.revert else: meshMatOverride.commands.pop(command_index) command_index -= 1 - if not command.displayList.tag == GfxListTag.Geometry: + if not command.displayList.tag & GfxListTag.Geometry: command_index += 1 continue # If the previous command was a revert we added, remove it. All reverts must be followed by a load @@ -2585,7 +2566,7 @@ def find_material_from_jump_cmd(material_list: tuple[tuple[bpy.types.Material, s next_command = meshMatOverride.commands[command_index + 1] if ( isinstance(next_command, SPDisplayList) - and next_command.displayList.tag == GfxListTag.Material + and next_command.displayList.tag & GfxListTag.Material and next_command.displayList != prev_material.material ) or (isinstance(next_command, SPEndDisplayList)): meshMatOverride.commands.insert(command_index + 1, SPDisplayList(fOverrideMat.revert)) @@ -2635,16 +2616,15 @@ def splitSkinnedFacesIntoTwoGroups(skinnedFaces, fModel, obj, uv_data, drawLayer material = obj.material_slots[material_index].material fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - exportVertexColors = isLightingDisabled(material) - convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) + convertInfo = LoopConvertInfo(uv_data, obj, material) for skinnedFace in skinnedFaceArray: - for (face, loop) in skinnedFace.loopsInGroup: + for face, loop in skinnedFace.loopsInGroup: f3dVert = getF3DVert(loop, face, convertInfo, obj.data) bufferVert = BufferVertex(f3dVert, None, material_index) if bufferVert not in inGroupVerts: inGroupVerts.append(bufferVert) loopDict[loop] = f3dVert - for (face, loop) in skinnedFace.loopsNotInGroup: + for face, loop in skinnedFace.loopsNotInGroup: vert = obj.data.vertices[loop.vertex_index] if vert not in notInGroupBlenderVerts: notInGroupBlenderVerts.append(vert) @@ -2723,7 +2703,6 @@ def saveSkinnedMeshByMaterial( materialKey = (material, drawLayerKey, fModel.global_data.getCurrentAreaKey(material)) fMaterial, texDimensions = fModel.getMaterialAndHandleShared(materialKey) isPointSampled = isTexturePointSampled(material) - exportVertexColors = isLightingDisabled(material) skinnedTriGroup = fSkinnedMesh.tri_group_new(fMaterial) fSkinnedMesh.draw.commands.append(SPDisplayList(fMaterial.material)) @@ -2735,16 +2714,11 @@ def saveSkinnedMeshByMaterial( for bufferVert in vertData: skinnedTriGroup.vertexList.vertices.append( - convertVertexData( + bufferVert.f3dVert.toVtx( obj.data, - bufferVert.f3dVert.position, - bufferVert.f3dVert.uv, - bufferVert.f3dVert.stOffset, - bufferVert.f3dVert.getColorOrNormal(), texDimensions, parentMatrix, isPointSampled, - exportVertexColors, ) ) @@ -2796,33 +2770,6 @@ def saveSkinnedMeshByMaterial( ) return fMesh, fSkinnedMesh - # for material_index, skinnedFaceArray in skinnedFaces.items(): - # - # # We've already saved all materials, this just returns the existing ones. - # material = obj.material_slots[material_index].material - # fMaterial, texDimensions = \ - # saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData) - # isPointSampled = isTexturePointSampled(material) - # exportVertexColors = isLightingDisabled(material) - # - # triGroup = fMesh.tri_group_new(fMaterial) - # fMesh.draw.commands.append(SPDisplayList(fMaterial.material)) - # fMesh.draw.commands.append(SPDisplayList(triGroup.triList)) - # if fMaterial.revert is not None: - # fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) - # - # convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors) - # triConverter = TriangleConverter(triConverterInfo, texDimensions, material, - # None, triGroup.triList, triGroup.vertexList, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict)) - # saveTriangleStrip(triConverter, [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None, obj.data, True) - # saveTriangleStrip(triConverterClass, - # [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None, - # convertInfo, triGroup.triList, triGroup.vertexList, fModel.f3d, - # texDimensions, currentMatrix, isPointSampled, exportVertexColors, - # copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), - # infoDict, obj.data, None, True) - - return fMesh, fSkinnedMesh def writeDynamicMeshFunction(name, displayList): @@ -2860,8 +2807,8 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Object not selected.") obj = context.active_object - if type(obj.data) is not bpy.types.Mesh and not ( - obj.data is None and (obj.sm64_obj_type == "None" or obj.sm64_obj_type == "Switch") + if obj.type != "MESH" and not ( + obj.type == "EMPTY" and (obj.sm64_obj_type == "None" or obj.sm64_obj_type == "Switch") ): raise PluginError('Selected object must be a mesh or an empty with the "None" or "Switch" type.') # if context.scene.saveCameraSettings and \ @@ -2897,8 +2844,6 @@ def execute(self, context): exportGeolayoutObjectC( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, exportPath, bpy.context.scene.geoTexDir, saveTextures, @@ -2917,8 +2862,6 @@ def execute(self, context): exportGeolayoutObjectInsertableBinary( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath), None, ) @@ -2954,8 +2897,6 @@ def execute(self, context): finalTransform, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)), None, ) @@ -2968,8 +2909,6 @@ def execute(self, context): segmentData, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, None, ) @@ -3045,7 +2984,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Armature not selected.") armatureObj = context.active_object - if type(armatureObj.data) is not bpy.types.Armature: + if armatureObj.type != "ARMATURE": raise PluginError("Armature not selected.") if len(armatureObj.children) == 0 or not isinstance(armatureObj.children[0].data, bpy.types.Mesh): @@ -3069,7 +3008,7 @@ def execute(self, context): # IMPORTANT: Do this BEFORE rotation optionObjs = [] for childObj in linkedArmature.children: - if isinstance(childObj.data, bpy.types.Mesh): + if childObj.type == "MESH": optionObjs.append(childObj) if len(optionObjs) > 1: raise PluginError("Error: " + linkedArmature.name + " has more than one mesh child.") @@ -3108,8 +3047,6 @@ def execute(self, context): armatureObj, obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, exportPath, bpy.context.scene.geoTexDir, saveTextures, @@ -3130,8 +3067,6 @@ def execute(self, context): armatureObj, obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath), None, ) @@ -3168,8 +3103,6 @@ def execute(self, context): finalTransform, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)), None, ) @@ -3183,8 +3116,6 @@ def execute(self, context): segmentData, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, None, ) diff --git a/fast64_internal/sm64/sm64_level_writer.py b/fast64_internal/sm64/sm64_level_writer.py index 0e5e2ccab..0d51e99b0 100644 --- a/fast64_internal/sm64/sm64_level_writer.py +++ b/fast64_internal/sm64/sm64_level_writer.py @@ -697,10 +697,7 @@ def __init__(self): self.starSelectC = False -def exportLevelC( - obj, transformMatrix, f3dType, isHWv1, levelName, exportDir, savePNG, customExport, levelCameraVolumeName, DLFormat -): - +def exportLevelC(obj, transformMatrix, levelName, exportDir, savePNG, customExport, levelCameraVolumeName, DLFormat): fileStatus = SM64OptionalFileStatus() if customExport: @@ -725,8 +722,12 @@ def exportLevelC( puppycamVolumeString = "" inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model(f3dType, isHWv1, levelName + "_dl", DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll) - childAreas = [child for child in obj.children if child.data is None and child.sm64_obj_type == "Area Root"] + fModel = SM64Model( + levelName + "_dl", + DLFormat, + GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll, + ) + childAreas = [child for child in obj.children if child.type == "EMPTY" and child.sm64_obj_type == "Area Root"] if len(childAreas) == 0: raise PluginError("The level root has no child empties with the 'Area Root' object type.") @@ -767,8 +768,6 @@ def exportLevelC( geolayoutGraph, fModel = convertObjectToGeolayout( obj, transformMatrix, - f3dType, - isHWv1, child.areaCamera, levelName + "_" + areaName, fModel, @@ -1139,7 +1138,7 @@ def execute(self, context): if len(context.selected_objects) == 0: raise PluginError("Object not selected.") obj = context.selected_objects[0] - if obj.data is not None or obj.sm64_obj_type != "Level Root": + if obj.type != "EMPTY" or obj.sm64_obj_type != "Level Root": raise PluginError("The selected object is not an empty with the Level Root type.") except PluginError: # try to find parent level root @@ -1148,7 +1147,7 @@ def execute(self, context): if not obj.parent: break obj = obj.parent - if obj.data is None and obj.sm64_obj_type == "Level Root": + if obj.type == "EMPTY" and obj.sm64_obj_type == "Level Root": break if obj is None or obj.sm64_obj_type != "Level Root": raise PluginError("Cannot find level empty.") @@ -1181,8 +1180,6 @@ def execute(self, context): fileStatus = exportLevelC( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, levelName, exportPath, context.scene.saveTextures, diff --git a/fast64_internal/sm64/sm64_objects.py b/fast64_internal/sm64/sm64_objects.py index 3329fc408..7d82b49e1 100644 --- a/fast64_internal/sm64/sm64_objects.py +++ b/fast64_internal/sm64/sm64_objects.py @@ -803,7 +803,7 @@ def process_sm64_objects(obj, area, rootMatrix, transformMatrix, specialsOnly): # Hacky solution to handle Z-up to Y-up conversion rotation = originalRotation @ mathutils.Quaternion((1, 0, 0), math.radians(90.0)) - if obj.data is None: + if obj.type == "EMPTY": if obj.sm64_obj_type == "Area Root" and obj.areaIndex != area.index: return if specialsOnly: @@ -1088,7 +1088,9 @@ class SM64ObjectPanel(bpy.types.Panel): @classmethod def poll(cls, context): - return context.scene.gameEditorMode == "SM64" and (context.object is not None and context.object.data is None) + return context.scene.gameEditorMode == "SM64" and ( + context.object is not None and context.object.type == "EMPTY" + ) def draw_inline_obj(self, box: bpy.types.UILayout, obj: bpy.types.Object): obj_details: InlineGeolayoutObjConfig = inlineGeoLayoutObjects.get(obj.sm64_obj_type) diff --git a/fast64_internal/sm64/sm64_rom_tweaks.py b/fast64_internal/sm64/sm64_rom_tweaks.py index 76783296e..bd50b9cb8 100644 --- a/fast64_internal/sm64/sm64_rom_tweaks.py +++ b/fast64_internal/sm64/sm64_rom_tweaks.py @@ -3,7 +3,6 @@ def ExtendBank0x04(romfile, segmentData, segment4): - # Extend bank 0x04 romfile.seek(loadSegmentAddresses[0x04] + 4) oldStart = int.from_bytes(romfile.read(4), "big") diff --git a/fast64_internal/sm64/sm64_texscroll.py b/fast64_internal/sm64/sm64_texscroll.py index 441c2eaed..c9e6ec736 100644 --- a/fast64_internal/sm64/sm64_texscroll.py +++ b/fast64_internal/sm64/sm64_texscroll.py @@ -348,7 +348,6 @@ def modifyTexScrollHeadersGroup( def writeTexScrollHeadersGroup( exportDir: str, includeC: str, includeH: str, groupName: str, topLevelScrollFunc: str, dataInclude: str ): - # Create group scroll files fileStatus = createTexScrollHeadersGroup(exportDir, groupName, dataInclude) @@ -399,7 +398,6 @@ def writeTexScrollHeadersGroup( def removeTexScrollHeadersGroup(exportDir: str, includeC: str, includeH: str, groupName: str, topLevelScrollFunc: str): - includeH += "\n" includeC += "\n" diff --git a/fast64_internal/sm64/sm64_utility.py b/fast64_internal/sm64/sm64_utility.py index 9c72320d1..f06348683 100644 --- a/fast64_internal/sm64/sm64_utility.py +++ b/fast64_internal/sm64/sm64_utility.py @@ -1,17 +1,21 @@ import os + def starSelectWarning(operator, fileStatus): - if fileStatus is not None and not fileStatus.starSelectC: - operator.report({'WARNING'}, "star_select.c not found, skipping star select scrolling.") + if fileStatus is not None and not fileStatus.starSelectC: + operator.report({"WARNING"}, "star_select.c not found, skipping star select scrolling.") + def cameraWarning(operator, fileStatus): - if fileStatus is not None and not fileStatus.cameraC: - operator.report({'WARNING'}, "camera.c not found, skipping camera volume and zoom mask exporting.") + if fileStatus is not None and not fileStatus.cameraC: + operator.report({"WARNING"}, "camera.c not found, skipping camera volume and zoom mask exporting.") + + +ULTRA_SM64_MEMORY_C = "src/boot/memory.c" +SM64_MEMORY_C = "src/game/memory.c" -ULTRA_SM64_MEMORY_C = 'src/boot/memory.c' -SM64_MEMORY_C = 'src/game/memory.c' def getMemoryCFilePath(decompDir): - isUltra = os.path.exists(os.path.join(decompDir, ULTRA_SM64_MEMORY_C)) - relPath = ULTRA_SM64_MEMORY_C if isUltra else SM64_MEMORY_C - return os.path.join(decompDir, relPath) \ No newline at end of file + isUltra = os.path.exists(os.path.join(decompDir, ULTRA_SM64_MEMORY_C)) + relPath = ULTRA_SM64_MEMORY_C if isUltra else SM64_MEMORY_C + return os.path.join(decompDir, relPath) diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index be8bc51ca..4d301e7b8 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -1,8 +1,9 @@ import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator -from math import pi, ceil, degrees, radians +from math import pi, ceil, degrees, radians, copysign from mathutils import * from .utility_anim import * -from typing import Callable, Iterable, Any +from typing import Callable, Iterable, Any, Tuple, Optional +from bpy.types import UILayout CollectionProperty = Any # collection prop as defined by using bpy.props.CollectionProperty @@ -600,6 +601,21 @@ def cast_integer(value: int, bits: int, signed: bool): radians_to_s16 = lambda d: to_s16(d * 0x10000 / (2 * math.pi)) +def int_from_s16(value: int) -> int: + value &= 0xFFFF + if value >= 0x8000: + value -= 0x10000 + return value + + +def int_from_s16_str(value: str) -> int: + return int_from_s16(int(value, 0)) + + +def float_from_u16_str(value: str) -> float: + return float(int(value, 0)) / (2**16) + + def decompFolderMessage(layout): layout.box().label(text="This will export to your decomp folder.") @@ -841,7 +857,7 @@ def store_original_meshes(add_warning: Callable[[str], None]): instanced_meshes = set() active_obj = bpy.context.view_layer.objects.active for obj in yield_children(active_obj): - if obj.data is not None: + if obj.type != "EMPTY": has_modifiers = len(obj.modifiers) != 0 has_uneven_scale = not obj_scale_is_unified(obj) shares_mesh = obj.data.users > 1 @@ -961,18 +977,18 @@ def checkSM64EmptyUsesGeoLayout(sm64_obj_type): def selectMeshChildrenOnly(obj, ignoreAttr, includeEmpties, areaIndex): - checkArea = areaIndex is not None and obj.data is None + checkArea = areaIndex is not None and obj.type == "EMPTY" if checkArea and obj.sm64_obj_type == "Area Root" and obj.areaIndex != areaIndex: return ignoreObj = ignoreAttr is not None and getattr(obj, ignoreAttr) - isMesh = isinstance(obj.data, bpy.types.Mesh) - isEmpty = obj.data is None and includeEmpties and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) + isMesh = obj.type == "MESH" + isEmpty = obj.type == "EMPTY" and includeEmpties and checkSM64EmptyUsesGeoLayout(obj.sm64_obj_type) if (isMesh or isEmpty) and not ignoreObj: obj.select_set(True) obj.original_name = obj.name for child in obj.children: if checkArea and obj.sm64_obj_type == "Level Root": - if not (child.data is None and child.sm64_obj_type == "Area Root"): + if not (child.type == "EMPTY" and child.sm64_obj_type == "Area Root"): continue selectMeshChildrenOnly(child, ignoreAttr, includeEmpties, areaIndex) @@ -980,7 +996,7 @@ def selectMeshChildrenOnly(obj, ignoreAttr, includeEmpties, areaIndex): def cleanupDuplicatedObjects(selected_objects): meshData = [] for selectedObj in selected_objects: - if selectedObj.data is not None and isinstance(selectedObj.data, bpy.types.Mesh): + if selectedObj.type == "MESH": meshData.append(selectedObj.data) for selectedObj in selected_objects: bpy.data.objects.remove(selectedObj) @@ -1184,6 +1200,14 @@ def prop_split(layout, data, field, name, **prop_kwargs): split.prop(data, field, text="", **prop_kwargs) +def multilineLabel(layout: UILayout, text: str, icon: str = "NONE"): + layout = layout.column() + for i, line in enumerate(text.split("\n")): + r = layout.row() + r.label(text=line, icon=icon if i == 0 else "NONE") + r.scale_y = 0.75 + + def toAlnum(name, exceptions=[]): if name is None or name == "": return None @@ -1425,13 +1449,56 @@ def normToSigned8Vector(normal): return [int.from_bytes(int(value * 127).to_bytes(1, "big", signed=True), "big") for value in normal] -# Normal values are signed bytes (-128 to 127) -# Normalized magnitude = 127 -def convertNormal(normal): - F3DNormal = bytearray(0) - for axis in normal: - F3DNormal.extend(int(axis * 127).to_bytes(1, "big", signed=True)) - return F3DNormal +def unpackNormalS8(packedNormal: int) -> Tuple[int, int, int]: + assert isinstance(packedNormal, int) and packedNormal >= 0 and packedNormal <= 0xFFFF + xo, yo = packedNormal >> 8, packedNormal & 0xFF + # This is following the instructions in F3DEX3 + x, y = xo & 0x7F, yo & 0x7F + z = x + y + zNeg = bool(z & 0x80) + x2, y2 = x ^ 0x7F, y ^ 0x7F # this is actually producing 7F - x, 7F - y + z = z ^ 0x7F # 7F - x - y; using xor saves an instruction and a register on the RSP + if zNeg: + x, y = x2, y2 + x, y = -x if xo & 0x80 else x, -y if yo & 0x80 else y + z = z - 0x100 if z & 0x80 else z + assert abs(x) + abs(y) + abs(z) == 127 + return x, y, z + + +def unpackNormal(packedNormal: int) -> Vector: + # Convert constant-L1 norm to standard L2 norm + return Vector(unpackNormalS8(packedNormal)).normalized() + + +def packNormal(normal: Vector) -> int: + # Convert standard normal to constant-L1 normal + assert len(normal) == 3 + l1norm = abs(normal[0]) + abs(normal[1]) + abs(normal[2]) + xo, yo, zo = tuple([int(round(a * 127.0 / l1norm)) for a in normal]) + if abs(xo) + abs(yo) > 127: + yo = int(math.copysign(127 - abs(xo), yo)) + zo = int(math.copysign(127 - abs(xo) - abs(yo), zo)) + assert abs(xo) + abs(yo) + abs(zo) == 127 + # Pack normals + xsign, ysign = xo & 0x80, yo & 0x80 + x, y = abs(xo), abs(yo) + if zo < 0: + x, y = 0x7F - x, 0x7F - y + x, y = x | xsign, y | ysign + packedNormal = x << 8 | y + # The only error is in the float to int rounding above. The packing and unpacking + # will precisely restore the original int values. + assert (xo, yo, zo) == unpackNormalS8(packedNormal) + return packedNormal + + +def getRgbNormalSettings(f3d_mat: "F3DMaterialProperty") -> Tuple[bool, bool, bool]: + rdp_settings = f3d_mat.rdp_settings + has_packed_normals = bpy.context.scene.f3d_type == "F3DEX3" and rdp_settings.g_packed_normals + has_rgb = not rdp_settings.g_lighting or has_packed_normals + has_normal = rdp_settings.g_lighting + return has_rgb, has_normal, has_packed_normals def byteMask(data, offset, amount): diff --git a/fast64_internal/utility_anim.py b/fast64_internal/utility_anim.py index 7807dea84..982706a16 100644 --- a/fast64_internal/utility_anim.py +++ b/fast64_internal/utility_anim.py @@ -23,7 +23,6 @@ class ArmatureApplyWithMeshOperator(bpy.types.Operator): # Called on demand (i.e. button press, menu item) # Can also be called from operator search menu (Spacebar) def execute(self, context): - from .utility import PluginError, raisePluginError try: @@ -73,7 +72,7 @@ def attemptModifierApply(modifier): def armatureApplyWithMesh(armatureObj: bpy.types.Object, context: bpy.types.Context): for child in armatureObj.children: - if type(child.data) is not bpy.types.Mesh: + if child.type != "MESH": continue armatureModifier = None for modifier in child.modifiers: @@ -179,6 +178,7 @@ def getIntersectionInterval(): return range_get_by_choice[anim_range_choice]() + def stashActionInArmature(armatureObj: bpy.types.Object, action: bpy.types.Action): """ Stashes an animation (action) into an armatureĀ“s nla tracks. @@ -194,11 +194,12 @@ def stashActionInArmature(armatureObj: bpy.types.Object, action: bpy.types.Actio if strip.action.name == action.name: return - print(f"Stashing \"{action.name}\" in the object \"{armatureObj.name}\".") + print(f'Stashing "{action.name}" in the object "{armatureObj.name}".') track = armatureObj.animation_data.nla_tracks.new() track.strips.new(action.name, int(action.frame_range[0]), action) + classes = (ArmatureApplyWithMeshOperator,) @@ -209,6 +210,5 @@ def utility_anim_register(): # called on add-on disabling def utility_anim_unregister(): - for cls in classes: unregister_class(cls) diff --git a/mario.blend b/mario.blend index d399f5314..8ba071893 100644 Binary files a/mario.blend and b/mario.blend differ diff --git a/pyproject.toml b/pyproject.toml index 3340c81dc..2f96472a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,11 @@ target-version = [ # What files to exclude when running Black on directories (for example `black .`) # Use forward slashes for directory delimiters, even on Windows # This is a regular expression, escape dots with a backslash like '\.' -# Note: All whitespace seems to be trimmed (thankfully), probably by Black? +# Note: This is a verbose regex (whitespace and "comments" are ignored) +# https://docs.python.org/3/library/re.html#re.VERBOSE extend-exclude = ''' -^/fast64_internal/f3d/f3d_constants\.py +^( +# Addon updater is copied from another repo +/addon_updater\.py | /addon_updater_ops\.py +)$ '''