diff --git a/README.md b/README.md index 81c3052..5e21ef4 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 f63d4a6..6a4c147 100644 --- a/__init__.py +++ b/__init__.py @@ -161,7 +161,6 @@ class F3D_GlobalSettingsPanel(bpy.types.Panel): 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") @@ -427,7 +426,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 +470,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_enums.py b/fast64_internal/f3d/f3d_enums.py index d546b49..e17f108 100644 --- a/fast64_internal/f3d/f3d_enums.py +++ b/fast64_internal/f3d/f3d_enums.py @@ -171,7 +171,11 @@ enumCombKey = [ 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 @@ enumTextLUT = [ 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 @@ enumCycleType = [ 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 @@ enumBlendColor = [ "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 @@ enumBlendColor = [ 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,38 @@ maxTexelCount = { } 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 55301f7..a8af5eb 100644 --- a/fast64_internal/f3d/f3d_gbi.py +++ b/fast64_internal/f3d/f3d_gbi.py @@ -53,16 +53,8 @@ dlTypeEnum = [ ("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 +64,7 @@ vertexBufferSize = { "F3DLP.Rej": (80, 32), "F3DEX2/LX2": (32, 32), "F3DEX2.Rej/LX2.Rej": (64, 64), + "F3DEX3": (56, 56), } drawLayerRenderMode = { @@ -123,39 +116,41 @@ ACMUXDict = { } +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 +167,6 @@ class F3D: 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 +175,16 @@ class F3D: 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 +275,9 @@ class F3D: 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 +333,45 @@ class F3D: 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 +379,8 @@ class F3D: 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 +390,6 @@ class F3D: 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 +724,8 @@ class F3D: 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 +1423,15 @@ class F3D: 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 +1452,19 @@ class F3D: 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 +1491,25 @@ class F3D: 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 +1538,26 @@ class F3D: 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 +1588,42 @@ class F3D: 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 +1658,31 @@ class F3D: tile manipulation yourself. RJM. """ - if _HW_VERSION_1: - self.G_TX_LDBLK_MAX_TXL = 4095 - else: - self.G_TX_LDBLK_MAX_TXL = 2047 + 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 + 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 +1719,47 @@ class F3D: 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) - -g_F3D = {"GBI": None, "f3d_type": None, "isHWv1": None} + 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 -def get_cached_F3D_GBI(f3d_type: str, isHWv1: bool) -> F3D: +g_F3D = { + "GBI": None, + "f3d_type": None, +} + + +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 +1769,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 +2048,11 @@ class GfxFormatter: 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 +2062,7 @@ class Vtx: 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 +2072,8 @@ class Vtx: 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 +2290,6 @@ class FPaletteKey: class FModel: def __init__( self, - f3dType: F3D, - isHWv1: bool, name: str, DLFormat: "DLFormat", matWriteMethod: GfxMatWriteMethod, @@ -2253,7 +2306,7 @@ class FModel: # 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 @@ -2659,9 +2712,9 @@ class FModel: 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 +2914,13 @@ class FTriGroup: 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,12 +2931,16 @@ class FTriGroup: 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()) + for celTriList in self.celTriLists: + data.append(celTriList.to_c(f3d)) if self.triList: data.append(self.triList.to_c(f3d)) return data @@ -3069,8 +3133,9 @@ class Hilite: 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 +3151,36 @@ class Lights: 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 +3195,10 @@ class Lights: 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 +3223,7 @@ class LookAt: + spc(self.l[1].color) + "}, 0, " + "{" - + spc(self.l[0].normal) + + spc(self.l[1].normal) + "}, 0}}" + "}}\n" ) @@ -3256,6 +3338,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 +3371,9 @@ class GbiMacro: 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 +3455,9 @@ class SPViewport(GbiMacro): 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 +3489,13 @@ class SPBranchList(GbiMacro): 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 +3527,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 +3623,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 +3640,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 +3674,10 @@ class SP2Triangles(GbiMacro): 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 +3709,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 +3731,182 @@ class SPClipRatio(GbiMacro): # 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 @@ class SPSetLights(GbiMacro): 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,27 +4052,27 @@ class SPSetLights(GbiMacro): 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 @@ class SPTexture(GbiMacro): 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 @@ class SPTexture(GbiMacro): 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 @@ class DPSetPrimColor(GbiMacro): 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_material.py b/fast64_internal/f3d/f3d_material.py index 7f5e647..4fad597 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,23 @@ 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" + f3d_mat, ["ENVIRONMENT", "ENV_ALPHA"] ), "Shade": combiner_uses( - f3d_mat, ["SHADE", "SHADE_ALPHA"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" + f3d_mat, ["SHADE"], checkAlpha = False ), - "Shade Alpha": combiner_uses_alpha( - f3d_mat, ["SHADE"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" + "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"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE"), + "Key": combiner_uses(f3d_mat, ["CENTER", "SCALE"]), "LOD Fraction": combiner_uses( - f3d_mat, ["LOD_FRACTION"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE" + f3d_mat, ["LOD_FRACTION"] ), - "Convert": combiner_uses(f3d_mat, ["K4", "K5"], f3d_mat.rdp_settings.g_mdsft_cycletype == "G_CYC_2CYCLE"), + "Convert": combiner_uses(f3d_mat, ["K4", "K5"]), } return useDict @@ -404,21 +406,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 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: bpy.types.UILayout, useDropdown): - inputGroup: bpy.types.UILayout = layout.column() +def ui_upper_mode(settings, dataHolder, layout: UILayout, useDropdown): + inputGroup: UILayout = layout.column() if useDropdown: inputGroup.prop( dataHolder, @@ -427,11 +541,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 +563,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 +574,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 +593,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 +618,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" @@ -601,7 +710,7 @@ class F3DPanel(bpy.types.Panel): 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 +720,7 @@ class F3DPanel(bpy.types.Panel): 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 +822,22 @@ class F3DPanel(bpy.types.Panel): 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 +863,7 @@ class F3DPanel(bpy.types.Panel): 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 +876,45 @@ class F3DPanel(bpy.types.Panel): 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 +922,129 @@ class F3DPanel(bpy.types.Panel): 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="") + prop_split(inputGroup.row(), f3dMat, "fog_color", "Fog Color") + prop_split(inputGroup.row(), f3dMat, "fog_position", "Fog Range") - def drawVertexColorNotice(self, layout): + 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") + + 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() - noticeBox.label(text="There must be two vertex color layers.", icon="LINENUMBERS_ON") - noticeBox.label(text='They should be called "Col" and "Alpha".') - - def drawShadeAlphaNotice(self, layout): - layout.box().column().label(text='There must be a vertex color layer called "Alpha".', icon="IMAGE_ALPHA") + 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 +1064,7 @@ class F3DPanel(bpy.types.Panel): 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 +1099,9 @@ class F3DPanel(bpy.types.Panel): 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 +1109,41 @@ class F3DPanel(bpy.types.Panel): 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() + 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.") - 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.") - - 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() + combinerCol2 = combinerBox2.row().split(factor=0.45) + drawCCProps(combinerCol2, f3dMat.combiner2, False) + drawCCProps(combinerCol2, f3dMat.combiner2, True) - 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 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") - 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", - ) - - combinerBox2.label(text="Note: In second cycle, texture 0 and texture 1 are flipped.") if menuTab == "Sources": self.ui_uvCheck(layout, context) @@ -936,7 +1181,7 @@ class F3DPanel(bpy.types.Panel): 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 +1206,7 @@ class F3DPanel(bpy.types.Panel): 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,14 +1217,27 @@ class F3DPanel(bpy.types.Panel): 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) else: 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): @@ -1093,6 +1352,24 @@ 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 +1420,14 @@ alpha_combiner_inputs = { } -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 +1484,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 +1513,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 +1521,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 +1546,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 +1556,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 +1589,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 +1665,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 +1677,7 @@ def get_color_input_update_callback(attr_name="", prefix=""): 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 +1729,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 +1754,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 +1765,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 +1803,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 +1839,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 +1853,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 +1870,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 +1943,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 +1980,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 +2060,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 +2115,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 +2133,58 @@ 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 +2239,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,7 +2259,7 @@ def convertColorAttribute(mesh: bpy.types.Mesh, attr_name="Col"): mesh.attributes.active_index = prev_index -def addColorAttributesToModel(obj: bpy.types.Object): +def addColorAttributesToModel(obj: Object): if obj.type != "MESH": return @@ -1963,7 +2269,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 +2287,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 +2331,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 +2347,7 @@ class CreateFast3DMaterial(bpy.types.Operator): 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 +2358,18 @@ class ReloadDefaultF3DPresets(bpy.types.Operator): 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 +2383,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 +2400,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 +2439,7 @@ class TextureFieldProperty(bpy.types.PropertyGroup): 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 +2448,9 @@ class SetTileSizeScrollProperty(bpy.types.PropertyGroup): 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 +2545,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 +2564,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 +2685,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 +2763,7 @@ class CombinerProperty(bpy.types.PropertyGroup): ) -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 +2787,7 @@ class ProceduralAnimProperty(bpy.types.PropertyGroup): ) -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 +2806,7 @@ class ProcAnimVectorProperty(bpy.types.PropertyGroup): ) -class PrimDepthSettings(bpy.types.PropertyGroup): +class PrimDepthSettings(PropertyGroup): z: bpy.props.IntProperty( name="Prim Depth: Z", default=0, @@ -2531,64 +2837,122 @@ class PrimDepthSettings(bpy.types.PropertyGroup): 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 +2962,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 +2970,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 +3018,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 +3040,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 +3056,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 +3102,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 +3194,16 @@ class RDPSettings(bpy.types.PropertyGroup): 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 +3245,7 @@ class RDPSettings(bpy.types.PropertyGroup): ) -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 +3268,102 @@ class DefaultRDPSettingsPanel(bpy.types.Panel): 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 +3439,11 @@ class MATERIAL_MT_f3d_presets(Menu): 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 +3590,7 @@ class AddPresetF3D(AddPresetBase, Operator): 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 +3760,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 +3779,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 +3987,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 +4096,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 +4113,18 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): 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 +4150,10 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): 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 +4171,7 @@ class F3DMaterialProperty(bpy.types.PropertyGroup): ) -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 +4183,7 @@ class UnlinkF3DImage0(bpy.types.Operator): 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 +4195,7 @@ class UnlinkF3DImage1(bpy.types.Operator): 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 +4222,7 @@ class UpdateF3DNodes(bpy.types.Operator): 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 +4304,7 @@ class F3DRenderSettingsPanel(bpy.types.Panel): def draw_f3d_render_settings(self, context): - layout: bpy.types.UILayout = self.layout + layout: UILayout = self.layout layout.popover(F3DRenderSettingsPanel.bl_idname) @@ -3771,6 +4325,10 @@ mat_classes = ( PrimDepthSettings, RDPSettings, DefaultRDPSettingsPanel, + CelLevelProperty, + CelShadingProperty, + CelLevelAdd, + CelLevelRemove, F3DMaterialProperty, ReloadDefaultF3DPresets, UpdateF3DNodes, @@ -3815,61 +4373,60 @@ 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() + 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() - 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) + 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) - bpy.types.Scene.f3dUserPresetsOnly = bpy.props.BoolProperty(name="User Presets Only") - bpy.types.Scene.f3d_simple = bpy.props.BoolProperty(name="Display Simple", default=True) + Scene.f3dUserPresetsOnly = bpy.props.BoolProperty(name="User Presets Only") + Scene.f3d_simple = bpy.props.BoolProperty(name="Display Simple", default=True) - bpy.types.Object.use_f3d_culling = bpy.props.BoolProperty( + 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.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) + 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 + 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.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 aa3e269..bcdfa07 100644 --- a/fast64_internal/f3d/f3d_material_presets.py +++ b/fast64_internal/f3d/f3d_material_presets.py @@ -57,6 +57,10 @@ f3d_mat.f3d_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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_light4 = None 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.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 = 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 @@ bpy.context.material.f3d_update_flag = False 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 @@ material_presets = { "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 8212a6a..cb3f973 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 ( @@ -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 @@ class F3DContext: # 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 @@ class F3DContext: 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 @@ class F3DContext: # decomp format "\{\s*\{\s*" + "\{([^,\}]*),([^,\}]*),([^,\}]*)\}\s*," - + "[^,\}]*,\s*" + + "([^,\}]*),\s*" + "\{([^,\}]*),([^,\}]*)\}\s*,\s*" + "\{([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\}\s*" + "\}\s*\}", # nusys format "\{\s*" + "([^,\}]*),([^,\}]*),([^,\}]*)," - + "[^,\}]*," + + "([^,\}]*)," + "([^,\}]*),([^,\}]*)," + "([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\s*" + "\}", @@ -641,7 +642,7 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3DContext: 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 @@ class F3D_ImportDL(bpy.types.Operator): 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 @@ class F3D_ImportDL(bpy.types.Operator): 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_texture_writer.py b/fast64_internal/f3d/f3d_texture_writer.py index 9c93c64..1671f7a 100644 --- a/fast64_internal/f3d/f3d_texture_writer.py +++ b/fast64_internal/f3d/f3d_texture_writer.py @@ -1061,35 +1061,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 9f923a0..bc8906b 100644 --- a/fast64_internal/f3d/f3d_writer.py +++ b/fast64_internal/f3d/f3d_writer.py @@ -1,6 +1,8 @@ -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 @@ -9,9 +11,9 @@ 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 +119,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: @@ -184,7 +184,6 @@ def fixLargeUVs(obj): uvOffset = [0, 0] for i in range(2): - # Move any UVs close to or straddling edge minDiff = (-cellSize[i] + 2) - minUV[i] if minDiff > 0: @@ -306,8 +305,7 @@ def saveMeshWithLargeTexturesByFaces( texDimensions, material, currentGroupIndex, - triGroup.triList, - triGroup.vertexList, + triGroup, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), ) @@ -320,7 +318,7 @@ def saveMeshWithLargeTexturesByFaces( firstFace = False triGroup.triList.commands.append(SPEndDisplayList()) - + if fMaterial.revert is not None: fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) @@ -409,18 +407,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, ) ) @@ -632,8 +630,7 @@ def saveMeshByFaces( texDimensions, material, currentGroupIndex, - triGroup.triList, - triGroup.vertexList, + triGroup, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), ) @@ -646,28 +643,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): @@ -682,21 +662,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): @@ -705,17 +685,50 @@ class F3DVert: 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 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 + 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 + + 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) @@ -777,8 +790,7 @@ class TriangleConverter: texDimensions: tuple[int, int], material: bpy.types.Material, currentGroupIndex, - triList, - vtxList, + triGroup: FTriGroup, existingVertexData: list[BufferVertex], existingVertexMaterialRegions, ): @@ -794,15 +806,15 @@ class TriangleConverter: 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): @@ -843,16 +855,11 @@ class TriangleConverter: # 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, ) ) @@ -878,16 +885,11 @@ class TriangleConverter: # 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, ) ) @@ -895,9 +897,129 @@ class TriangleConverter: 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 = [] @@ -943,179 +1065,35 @@ class TriangleConverter: 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) + 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, 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) +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) @@ -1123,8 +1101,7 @@ def is3_2_or_above(): return bpy.app.version[0] >= 3 and bpy.app.version[1] >= 2 -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") @@ -1146,57 +1123,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 @@ -1321,6 +1263,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 @@ -1357,9 +1326,8 @@ def saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData): f3dMat.rdp_settings, multitexManager.getTT(), defaults, - fModel.f3d._HW_VERSION_1, fModel.matWriteMethod, - fModel.f3d + fModel.f3d, ) saveOtherModeLDefinition(fMaterial, f3dMat.rdp_settings, defaults, defaultRM, fModel.matWriteMethod, fModel.f3d) saveOtherDefinition(fMaterial, f3dMat, defaults) @@ -1466,11 +1434,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)) @@ -1513,21 +1481,41 @@ def saveBitGeoF3DEX2(value, defaultValue, flagName, geo, matWriteMethod): geo.clearFlagList.append(flagName) +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 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) + + def saveGeoModeDefinitionF3DEX2(fMaterial, settings, defaults, matWriteMethod): geo = SPGeometryMode([], []) - - 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) - - # 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) + saveGeoModeCommon(saveBitGeoF3DEX2, settings, defaults, (geo, matWriteMethod)) if len(geo.clearFlagList) != 0 or len(geo.setFlagList) != 0: if len(geo.clearFlagList) == 0: @@ -1542,33 +1530,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) @@ -1586,22 +1552,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) @@ -1609,96 +1573,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): @@ -1817,12 +1740,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: @@ -1917,8 +1837,6 @@ class F3D_ExportDL(bpy.types.Operator): 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 @@ -1930,8 +1848,6 @@ class F3D_ExportDL(bpy.types.Operator): obj, dlFormat, finalTransform, - f3dType, - isHWv1, texDir, savePNG, separateTexDef, diff --git a/fast64_internal/mm/data/xml/ActorList.xml b/fast64_internal/mm/data/xml/ActorList.xml new file mode 100644 index 0000000..11e2868 --- /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 69ba2e0..e336c3d 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. @@ -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 +### Cutscenes +For more informations about cutscenes [click here](cutscene_docs.md) + **Creating the cutscene itself:** -To create custom cutscenes you need to get [zcamedit, made by Sauraen](https://github.com/sauraen/zcamedit). - -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 @@ -171,3 +179,9 @@ Note that you can have the actual address of your cutscene if you use ``sym_info - 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. diff --git a/fast64_internal/oot/__init__.py b/fast64_internal/oot/__init__.py index d9f19d7..ecd7b5f 100644 --- a/fast64_internal/oot/__init__.py +++ b/fast64_internal/oot/__init__.py @@ -37,6 +37,12 @@ from .animation.properties import ( 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 @@ -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 65a6508..50f0a70 100644 --- a/fast64_internal/oot/actor/properties.py +++ b/fast64_internal/oot/actor/properties.py @@ -2,7 +2,8 @@ from bpy.types import Object, PropertyGroup, UILayout 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 @@ from ..oot_utility import ( 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 @@ class OOTActorProperty(PropertyGroup): 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 @@ class OOTTransitionActorProperty(PropertyGroup): 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 @@ class OOTTransitionActorProperty(PropertyGroup): 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/cutscene/motion/constants.py b/fast64_internal/oot/cutscene/motion/constants.py new file mode 100644 index 0000000..236da19 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/constants.py @@ -0,0 +1,92 @@ +from ...oot_constants import ootData + +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")) + +ootCSMotionLegacyToNewCmdNames = { + "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", +} + +ootCSMotionListCommands = [ + "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", +] + +ootCSMotionListEntryCommands = [ + "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", +] + +ootCSMotionSingleCommands = [ + "CS_BEGIN_CUTSCENE", + "CS_END", + "CS_TRANSITION", + "CS_DESTINATION", +] + +ootCSMotionListAndSingleCommands = ootCSMotionSingleCommands + ootCSMotionListCommands +ootCSMotionListAndSingleCommands.remove("CS_BEGIN_CUTSCENE") +ootCSMotionCSCommands = ootCSMotionSingleCommands + ootCSMotionListCommands + ootCSMotionListEntryCommands diff --git a/fast64_internal/oot/cutscene/motion/exporter/__init__.py b/fast64_internal/oot/cutscene/motion/exporter/__init__.py new file mode 100644 index 0000000..b4ff461 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/exporter/__init__.py @@ -0,0 +1 @@ +from .functions import getCutsceneMotionData diff --git a/fast64_internal/oot/cutscene/motion/exporter/classes.py b/fast64_internal/oot/cutscene/motion/exporter/classes.py new file mode 100644 index 0000000..67a3fea --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/exporter/classes.py @@ -0,0 +1,272 @@ +import math +import bpy + +from dataclasses import dataclass +from bpy.types import Object +from .....utility import PluginError, indent +from ....oot_constants import ootData + +from ..io_classes import ( + OOTCSMotionActorCueList, + OOTCSMotionActorCue, + OOTCSMotionCamEyeSpline, + OOTCSMotionCamATSpline, + OOTCSMotionCamEyeSplineRelToPlayer, + OOTCSMotionCamATSplineRelToPlayer, + OOTCSMotionCamEye, + OOTCSMotionCamAT, + OOTCSMotionCamPoint, +) + + +class OOTCSMotionExportCommands: + """This class contains functions to create the cutscene commands""" + + def getActorCueListCmd(self, actorCueList: OOTCSMotionActorCueList, isPlayerActor: bool): + return indent + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" + + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" + + f"{actorCueList.entryTotal}),\n" + ) + + def getActorCueCmd(self, actorCue: OOTCSMotionActorCue, isPlayerActor: bool): + return indent * 2 + ( + 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 + f"{cmdName}({startFrame}, {endFrame}),\n" + + def getCamEyeSplineCmd(self, camEyeSpline: OOTCSMotionCamEyeSpline): + return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) + + def getCamATSplineCmd(self, camATSpline: OOTCSMotionCamATSpline): + return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) + + def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: OOTCSMotionCamEyeSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame + ) + + def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: OOTCSMotionCamATSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame + ) + + def getCamEyeCmd(self, camEye: OOTCSMotionCamEye): + return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) + + def getCamATCmd(self, camAT: OOTCSMotionCamAT): + return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) + + def getCamPointCmd(self, camPoint: OOTCSMotionCamPoint): + return indent * 2 + ( + 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 OOTCSMotionExport(OOTCSMotionExportCommands): + """This class contains functions to create the new cutscene data""" + + csMotionObjects: dict[str, list[Object]] + useDecomp: bool + addBeginEndCmds: bool + entryTotal: int = 0 + frameCount: 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.csMotionObjects[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 + + actorCueList = OOTCSMotionActorCueList(commandType, entryTotal - 1) # ignoring dummy cue + 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 = OOTCSMotionActorCue( + 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[OOTCSMotionCamPoint] = [] + + 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( + OOTCSMotionCamPoint( + ("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(OOTCSMotionCamPoint("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": OOTCSMotionCamATSpline if useAT else OOTCSMotionCamEyeSpline, + "splineEyeOrATRelPlayer": OOTCSMotionCamATSplineRelToPlayer + if useAT + else OOTCSMotionCamEyeSplineRelToPlayer, + "eyeOrAT": OOTCSMotionCamAT if useAT else OOTCSMotionCamEye, + } + + 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)(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.csMotionObjects["camShot"] + cameraShotData = "" + + if len(shotObjects) > 0: + frameCount = -1 + for shotObj in shotObjects: + cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) + frameCount = max(frameCount, self.camEndFrame + 1) + self.frameCount += frameCount + self.entryTotal += len(shotObjects) * 2 + + return cameraShotData + + def getExportData(self): + """Returns the cutscene data""" + + data = self.getActorCueListData(False) + self.getActorCueListData(True) + self.getCameraShotData() + if self.addBeginEndCmds: + data = ( + indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n" + data + indent + "CS_END(),\n" + ) + return data diff --git a/fast64_internal/oot/cutscene/motion/exporter/functions.py b/fast64_internal/oot/cutscene/motion/exporter/functions.py new file mode 100644 index 0000000..8996254 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/exporter/functions.py @@ -0,0 +1,46 @@ +import bpy + +from bpy.types import Object +from .....utility import PluginError +from .classes import OOTCSMotionExport + + +def getCSMotionObjects(csName: str): + """Returns the object list containing every object from the cutscene to export""" + + csMotionObjects: dict[str, list[Object]] = { + "Cutscene": [], + "CS Actor Cue List": [], + "CS Player Cue List": [], + "camShot": [], + } + + 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 csMotionObjects.keys(): + csMotionObjects[obj.ootEmptyType].append(obj) + + if obj.type == "ARMATURE" and obj.parent.ootEmptyType == "Cutscene": + csMotionObjects["camShot"].append(obj) + + return csMotionObjects + + +def getCutsceneMotionData(csName: str, addBeginEndCmds: bool): + """Returns the initialised cutscene exporter""" + + # this allows us to change the exporter's variables to get what we need + return OOTCSMotionExport( + getCSMotionObjects(csName), + bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.useDecompFeatures, + addBeginEndCmds, + ) diff --git a/fast64_internal/oot/cutscene/motion/importer/__init__.py b/fast64_internal/oot/cutscene/motion/importer/__init__.py new file mode 100644 index 0000000..2bca31f --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/importer/__init__.py @@ -0,0 +1 @@ +from .functions import importCutsceneData diff --git a/fast64_internal/oot/cutscene/motion/importer/classes.py b/fast64_internal/oot/cutscene/motion/importer/classes.py new file mode 100644 index 0000000..4d04af3 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/importer/classes.py @@ -0,0 +1,559 @@ +import bpy + +from dataclasses import dataclass +from struct import unpack +from bpy.types import Object, Armature +from .....utility import PluginError +from ....oot_constants import ootData +from ..utility import setupCutscene, getBlenderPosition, getRotation + +from ..constants import ( + ootCSMotionLegacyToNewCmdNames, + ootCSMotionListCommands, + ootCSMotionCSCommands, + ootCSMotionListEntryCommands, + ootCSMotionSingleCommands, + ootCSMotionListAndSingleCommands, +) + +from ..io_classes import ( + OOTCSMotionActorCueList, + OOTCSMotionActorCue, + OOTCSMotionCamEyeSpline, + OOTCSMotionCamATSpline, + OOTCSMotionCamEyeSplineRelToPlayer, + OOTCSMotionCamATSplineRelToPlayer, + OOTCSMotionCamEye, + OOTCSMotionCamAT, + OOTCSMotionCamPoint, + OOTCSMotionCutscene, + OOTCSMotionObjectFactory, + OOTCSMotionMisc, + OOTCSMotionMiscList, + OOTCSMotionTransition, +) + + +@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()`` + + +class OOTCSMotionImportCommands: + """This class contains functions to create the cutscene dataclasses""" + + def getCmdParams(self, data: str, cmdName: str, paramNumber: int): + """Returns the list of every parameter of the given command""" + + params = data.strip().removeprefix(f"{cmdName}(").replace(" ", "").removesuffix(")").split(",") + if len(params) != paramNumber: + raise PluginError( + f"ERROR: The number of expected parameters for `{cmdName}` " + + "and the number of found ones is not the same!" + ) + return params + + def getInteger(self, 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 getNewCutscene(self, csData: str, name: str): + params = self.getCmdParams(csData, "CS_BEGIN_CUTSCENE", OOTCSMotionCutscene.paramNumber) + return OOTCSMotionCutscene(name, self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewActorCueList(self, cmdData: str, isPlayer: bool): + paramNumber = OOTCSMotionActorCueList.paramNumber + paramNumber = paramNumber - 1 if isPlayer else paramNumber + params = self.getCmdParams(cmdData, f"CS_{'PLAYER' if isPlayer else 'ACTOR'}_CUE_LIST", paramNumber) + + if isPlayer: + actorCueList = OOTCSMotionActorCueList("Player", params[0]) + else: + commandType = params[0] + if commandType.startswith("0x"): + # make it a 4 digit hex + commandType = commandType.removeprefix("0x") + commandType = "0x" + "0" * (4 - len(commandType)) + commandType + actorCueList = OOTCSMotionActorCueList(commandType, self.getInteger(params[1].strip())) + + return actorCueList + + def getNewCamEyeSpline(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_CAM_EYE_SPLINE", OOTCSMotionCamEyeSpline.paramNumber) + return OOTCSMotionCamEyeSpline(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewCamATSpline(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_CAM_AT_SPLINE", OOTCSMotionCamATSpline.paramNumber) + return OOTCSMotionCamATSpline(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewCamEyeSplineRelToPlayer(self, cmdData: str): + params = self.getCmdParams( + cmdData, "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", OOTCSMotionCamEyeSplineRelToPlayer.paramNumber + ) + return OOTCSMotionCamEyeSplineRelToPlayer(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewCamATSplineRelToPlayer(self, cmdData: str): + params = self.getCmdParams( + cmdData, "CS_CAM_AT_SPLINE_REL_TO_PLAYER", OOTCSMotionCamATSplineRelToPlayer.paramNumber + ) + return OOTCSMotionCamATSplineRelToPlayer(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewCamEye(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_CAM_EYE", OOTCSMotionCamEye.paramNumber) + return OOTCSMotionCamEye(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewCamAT(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_CAM_AT", OOTCSMotionCamAT.paramNumber) + return OOTCSMotionCamAT(self.getInteger(params[0]), self.getInteger(params[1])) + + def getNewActorCue(self, cmdData: str, isPlayer: bool): + params = self.getCmdParams( + cmdData, f"CS_{'PLAYER' if isPlayer else 'ACTOR'}_CUE", OOTCSMotionActorCue.paramNumber + ) + + return OOTCSMotionActorCue( + self.getInteger(params[1]), + self.getInteger(params[2]), + self.getInteger(params[0]), + [getRotation(params[3]), getRotation(params[4]), getRotation(params[5])], + [self.getInteger(params[6]), self.getInteger(params[7]), self.getInteger(params[8])], + [self.getInteger(params[9]), self.getInteger(params[10]), self.getInteger(params[11])], + ) + + def getNewCamPoint(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_CAM_POINT", OOTCSMotionCamPoint.paramNumber) + + return OOTCSMotionCamPoint( + params[0], + self.getInteger(params[1]), + self.getInteger(params[2]), + float(params[3][:-1]), + [self.getInteger(params[4]), self.getInteger(params[5]), self.getInteger(params[6])], + ) + + def getNewMisc(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_MISC", OOTCSMotionMisc.paramNumber) + miscEnum = ootData.enumData.enumByKey["csMiscType"] + item = miscEnum.itemById.get(params[0]) + if item is None: + item = miscEnum.itemByIndex[self.getInteger(params[0])] + miscType = item.id + return OOTCSMotionMisc(self.getInteger(params[1]), self.getInteger(params[2]), miscType) + + def getNewMiscList(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_MISC_LIST", OOTCSMotionMiscList.paramNumber) + return OOTCSMotionMiscList(params[0]) + + def getNewTransition(self, cmdData: str): + params = self.getCmdParams(cmdData, "CS_TRANSITION", OOTCSMotionTransition.paramNumber) + transitionEnum = ootData.enumData.enumByKey["csTransitionType"] + item = transitionEnum.itemById.get(params[0]) + if item is None: + item = transitionEnum.itemByIndex[self.getInteger(params[0])] + transType = item.id + return OOTCSMotionTransition(self.getInteger(params[1]), self.getInteger(params[2]), transType) + + +@dataclass +class OOTCSMotionImport(OOTCSMotionImportCommands, OOTCSMotionObjectFactory): + """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 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(ootCSMotionLegacyToNewCmdNames.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"{ootCSMotionLegacyToNewCmdNames[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 ootCSMotionCSCommands: + line = line.removesuffix(",") + "\n" + + if curCmd in ootCSMotionSingleCommands and curCmd != "CS_END": + parsedData += line + + if not cmdListFound and curCmd in ootCSMotionListCommands: + cmdListFound = True + parsedData = "" + + # camera and lighting have "non-standard" list names + if curCmd.startswith("CS_CAM"): + curCmdPrefix = "CS_CAM" + elif curCmd.startswith("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 ootCSMotionListEntryCommands: + 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 ootCSMotionListAndSingleCommands: + 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[OOTCSMotionCutscene] = [] + cmdDataList = [ + ("ACTOR_CUE_LIST", self.getNewActorCueList, self.getNewActorCue, "actorCue"), + ("PLAYER_CUE_LIST", self.getNewActorCueList, self.getNewActorCue, "playerCue"), + ("CAM_EYE_SPLINE", self.getNewCamEyeSpline, self.getNewCamPoint, "camEyeSpline"), + ("CAM_AT_SPLINE", self.getNewCamATSpline, self.getNewCamPoint, "camATSpline"), + ( + "CAM_EYE_SPLINE_REL_TO_PLAYER", + self.getNewCamEyeSplineRelToPlayer, + self.getNewCamPoint, + "camEyeSplineRelPlayer", + ), + ( + "CAM_AT_SPLINE_REL_TO_PLAYER", + self.getNewCamATSplineRelToPlayer, + self.getNewCamPoint, + "camATSplineRelPlayer", + ), + ("CAM_EYE", self.getNewCamEye, self.getNewCamPoint, "camEye"), + ("CAM_AT", self.getNewCamAT, self.getNewCamPoint, "camAT"), + ("MISC_LIST", self.getNewMiscList, self.getNewMisc, "misc"), + ("TRANSITION", self.getNewTransition, None, "transition"), + ] + + # 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: + # create a new cutscene data + if "CS_BEGIN_CUTSCENE(" in data: + cutscene = self.getNewCutscene(data, parsedCS.csName) + + # if we have a cutscene, create and add the commands data in it + if cutscene is not None: + cmdData = data.removesuffix("\n").split("\n") + cmdListData = cmdData.pop(0) + for cmd, getListFunc, getFunc, listName in cmdDataList: + isPlayer = cmd == "PLAYER_CUE_LIST" + + if f"CS_{cmd}(" in data: + cmdList = getattr(cutscene, f"{listName}List") + + if getListFunc is not None: + if not isPlayer and not cmd == "ACTOR_CUE_LIST": + commandData = getListFunc(cmdListData) + else: + commandData = getListFunc(cmdListData, isPlayer) + + if getFunc is not None: + foundEndCmd = False + for d in cmdData: + if not isPlayer and not cmd == "ACTOR_CUE_LIST": + listEntry = getFunc(d) + if "CAM" in cmd: + 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 + else: + listEntry = getFunc(d, isPlayer) + commandData.entries.append(listEntry) + + cmdList.append(commandData) + + # 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[OOTCSMotionActorCueList], 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: OOTCSMotionCutscene): + """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[OOTCSMotionCamPoint, OOTCSMotionCamPoint]], 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 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}"!') + self.validateCameraData(cutscene) + csName = f"Cutscene.{cutscene.name}" + csObj = self.getNewCutsceneObject(csName, cutscene.frameCount, None) + csNumber = i + + print("Importing Actor Cues...") + self.setActorCueData(csObj, cutscene.actorCueList, "Actor", i) + self.setActorCueData(csObj, cutscene.playerCueList, "Player", i) + print("Done!") + + print("Importing Camera Shots...") + 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 len(cutscene.miscList) > 0: + for miscList in cutscene.miscList: + for miscCmd in miscList.entries: + miscProp = csObj.ootCutsceneProperty.preview.miscList.add() + miscProp.startFrame = miscCmd.startFrame + miscProp.endFrame = miscCmd.endFrame + miscProp.type = miscCmd.type + + if len(cutscene.transitionList) > 0: + for transition in cutscene.transitionList: + transitionProp = csObj.ootCutsceneProperty.preview.transitionList.add() + transitionProp.startFrame = transition.startFrame + transitionProp.endFrame = transition.endFrame + transitionProp.type = transition.type + + # Init camera + preview objects and setup the scene + setupCutscene(csObj) + print("Done!") + bpy.ops.object.select_all(action="DESELECT") + + # ``csNumber`` makes sure there's no duplicates + return csNumber + 1 diff --git a/fast64_internal/oot/cutscene/motion/importer/functions.py b/fast64_internal/oot/cutscene/motion/importer/functions.py new file mode 100644 index 0000000..16a5fbc --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/importer/functions.py @@ -0,0 +1,10 @@ +import bpy + +from .classes import OOTCSMotionImport + + +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 = OOTCSMotionImport(filePath, sceneData) + return csMotionImport.setCutsceneData(bpy.context.scene.ootCSNumber) diff --git a/fast64_internal/oot/cutscene/motion/io_classes.py b/fast64_internal/oot/cutscene/motion/io_classes.py new file mode 100644 index 0000000..462eeb2 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/io_classes.py @@ -0,0 +1,258 @@ +import bpy + +from dataclasses import dataclass, field +from bpy.types import Object +from ...oot_constants import ootData +from .utility import getBlenderPosition, getBlenderRotation + + +# 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 OOTCSMotionBase: + """This class contains common Cutscene data""" + + startFrame: int + endFrame: int + + +@dataclass +class OOTCSMotionCamPoint: + """This class contains a single Camera Point command data""" + + continueFlag: str + camRoll: int + frame: int + viewAngle: float + pos: list[int, int, int] + paramNumber: int = 8 + + +@dataclass +class OOTCSMotionActorCue(OOTCSMotionBase): + """This class contains a single Actor Cue command data""" + + actionID: int + rot: list[str, str, str] + startPos: list[int, int, int] + endPos: list[int, int, int] + paramNumber: int = 15 + + +@dataclass +class OOTCSMotionActorCueList: + """This class contains the Actor Cue List command data""" + + commandType: str + entryTotal: int + entries: list[OOTCSMotionActorCue] = field(default_factory=list) + paramNumber: int = 2 + + +@dataclass +class OOTCSMotionCamEyeSpline(OOTCSMotionBase): + """This class contains the Camera Eye Spline data""" + + entries: list[OOTCSMotionCamPoint] = field(default_factory=list) + paramNumber: int = 2 + + +@dataclass +class OOTCSMotionCamATSpline(OOTCSMotionBase): + """This class contains the Camera AT (look-at) Spline data""" + + entries: list[OOTCSMotionCamPoint] = field(default_factory=list) + paramNumber: int = 2 + + +@dataclass +class OOTCSMotionCamEyeSplineRelToPlayer(OOTCSMotionBase): + """This class contains the Camera Eye Spline Relative to the Player data""" + + entries: list[OOTCSMotionCamPoint] = field(default_factory=list) + paramNumber: int = 2 + + +@dataclass +class OOTCSMotionCamATSplineRelToPlayer(OOTCSMotionBase): + """This class contains the Camera AT Spline Relative to the Player data""" + + entries: list[OOTCSMotionCamPoint] = field(default_factory=list) + paramNumber: int = 2 + + +@dataclass +class OOTCSMotionCamEye(OOTCSMotionBase): + """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 + + +@dataclass +class OOTCSMotionCamAT(OOTCSMotionBase): + """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 + + +@dataclass +class OOTCSMotionMisc(OOTCSMotionBase): + """This class contains a single misc command entry""" + + type: str # see ``CutsceneMiscType`` in decomp + paramNumber: int = 14 + + +@dataclass +class OOTCSMotionMiscList: + """This class contains Misc command data""" + + entryTotal: int + entries: list[OOTCSMotionMisc] = field(default_factory=list) + paramNumber: int = 1 + + +@dataclass +class OOTCSMotionTransition(OOTCSMotionBase): + """This class contains Transition command data""" + + type: str + paramNumber: int = 3 + + +@dataclass +class OOTCSMotionCutscene: + """This class contains a Cutscene's data, including every commands' data""" + + name: str + totalEntries: int + frameCount: int + paramNumber: int = 2 + + actorCueList: list[OOTCSMotionActorCueList] = field(default_factory=list) + playerCueList: list[OOTCSMotionActorCueList] = field(default_factory=list) + camEyeSplineList: list[OOTCSMotionCamEyeSpline] = field(default_factory=list) + camATSplineList: list[OOTCSMotionCamATSpline] = field(default_factory=list) + camEyeSplineRelPlayerList: list[OOTCSMotionCamEyeSplineRelToPlayer] = field(default_factory=list) + camATSplineRelPlayerList: list[OOTCSMotionCamATSplineRelToPlayer] = field(default_factory=list) + camEyeList: list[OOTCSMotionCamEye] = field(default_factory=list) + camATList: list[OOTCSMotionCamAT] = field(default_factory=list) + miscList: list[OOTCSMotionMiscList] = field(default_factory=list) + transitionList: list[OOTCSMotionTransition] = field(default_factory=list) + + +class OOTCSMotionObjectFactory: + """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/motion/operators.py b/fast64_internal/oot/cutscene/motion/operators.py new file mode 100644 index 0000000..e028b69 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/operators.py @@ -0,0 +1,371 @@ +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 .io_classes import OOTCSMotionObjectFactory +from .constants import ootEnumCSActorCueListCommandType +from ..preview import initFirstFrame, setupCompositorNodes +from .utility import ( + setupActorCuePreview, + metersToBlend, + getCSMotionValidateObj, + getNameInformations, + createNewBone, + createNewCameraShot, + getCutsceneEndFrame, +) + + +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 = OOTCSMotionObjectFactory() + 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 OOTCSMotionPlayPreview(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 + cameraObj = None + for childObj in csObj.children: + if childObj.type == "CAMERA": + cameraObj = childObj + break + + # 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) + bpy.context.scene.ootCSPreviewCSObj = csObj + bpy.context.scene.ootCSPreviewNodesReady = False + setupCompositorNodes() + initFirstFrame(csObj, bpy.context.scene.ootCSPreviewNodesReady, cameraObj) + bpy.ops.screen.animation_cancel() + bpy.ops.screen.animation_play() + return {"FINISHED"} + except: + return {"CANCELLED"} + + +class OOTCSMotionAddBone(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 OOTCSMotionAddActorCue(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 = OOTCSMotionObjectFactory() + 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 OOTCSMotionCreateActorCuePreview(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 OOTCSMotionCreateCameraShot(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 OOTCSMotionCreatePlayerCueList(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 OOTCSMotionCreateActorCueList(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 = ( + OOTCSMotionPlayPreview, + OOTCSMotionAddBone, + OOTCSMotionAddActorCue, + OOTCSMotionCreateActorCuePreview, + OOTCSMotionCreateCameraShot, + OOTCSMotionCreatePlayerCueList, + OOTCSMotionCreateActorCueList, + 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 0000000..8b5a975 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/panels.py @@ -0,0 +1,50 @@ +from bpy.types import Panel +from bpy.utils import register_class, unregister_class +from .properties import OOTCSMotionCameraShotProperty, OOTCSMotionCameraShotPointProperty + + +class OOT_CSMotionCameraShotPanel(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: OOTCSMotionCameraShotProperty = obj.data.ootCamShotProp + camShotPointProp: OOTCSMotionCameraShotPointProperty = 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 0000000..9c9007d --- /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 parentObj.ootCutsceneProperty.preview.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 0000000..0138cda --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/properties.py @@ -0,0 +1,277 @@ +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 ( + OOTCSMotionAddActorCue, + OOTCSMotionCreateActorCuePreview, + OOT_SearchActorCueCmdTypeEnumOperator, + OOTCSMotionAddBone, + 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 OOTCSMotionActorCueListProperty(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(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp.isPlayer = isPlayer + split.operator(OOTCSMotionCreateActorCuePreview.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 OOTCSMotionActorCueProperty(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(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp.isPlayer = isPlayer + + +class OOTCSMotionCameraShotProperty(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(OOTCSMotionAddBone.bl_idname) + + +class OOTCSMotionCameraShotPointProperty(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=OOTCSMotionActorCueListProperty) + actorCueProp: PointerProperty(type=OOTCSMotionActorCueProperty) + + @staticmethod + def upgrade_object(csObj: Object): + print(f"Processing '{csObj.name}'...") + upgradeCutsceneMotion(csObj) + print("Done!") + + +classes = ( + OOTCSMotionActorCueListProperty, + OOTCSMotionActorCueProperty, + OOTCSMotionCameraShotProperty, + OOTCSMotionCameraShotPointProperty, + OOTCutsceneMotionProperty, +) + + +def csMotion_props_register(): + for cls in classes: + register_class(cls) + + Object.ootCSMotionProperty = PointerProperty(type=OOTCutsceneMotionProperty) + Armature.ootCamShotProp = PointerProperty(type=OOTCSMotionCameraShotProperty) + Bone.ootCamShotPointProp = PointerProperty(type=OOTCSMotionCameraShotPointProperty) + EditBone.ootCamShotPointProp = PointerProperty(type=OOTCSMotionCameraShotPointProperty) + Scene.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", + ) + + +def csMotion_props_unregister(): + del Scene.previewPlayerAge + 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 0000000..a7c4c89 --- /dev/null +++ b/fast64_internal/oot/cutscene/motion/utility.py @@ -0,0 +1,275 @@ +import bpy + +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 .io_classes import OOTCSMotionObjectFactory # circular import fix + + index, csPrefix = getNameInformations(csObj, "Camera Shot", None) + + # create a basic armature + name = f"{csPrefix}.Camera Shot {index:02}" + newCameraShotObj = OOTCSMotionObjectFactory().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 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 .io_classes import OOTCSMotionObjectFactory # 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 = OOTCSMotionObjectFactory().getNewActorCuePreviewObject(name, selectObject, csObj) + + actorHeight = 1.5 + if actorOrPlayer == "Player": + actorHeight = 1.7 if bpy.context.scene.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 .io_classes import OOTCSMotionObjectFactory # circular import fix + + objFactory = OOTCSMotionObjectFactory() + context = bpy.context + bpy.context.scene.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 diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index 5d3fd53..97426e9 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -1,4 +1,6 @@ import os +import re + from bpy.path import abspath from bpy.ops import object from bpy.props import StringProperty, EnumProperty, IntProperty @@ -9,6 +11,8 @@ from ..oot_utility import getCollection from ..scene.exporter.to_c import ootCutsceneDataToC from .exporter import convertCutsceneObject from .constants import ootEnumCSTextboxType, ootEnumCSListType, ootEnumCSListTypeIcons +from .motion.importer import importCutsceneData +from .motion.exporter import getCutsceneMotionData def checkGetFilePaths(context: Context): @@ -56,7 +60,69 @@ def drawCSListAddOp(layout: UILayout, objName: str, collectionType): row = box.row(align=True) for _ in range(3): addButton(row) - box.label(text="Install zcamedit for camera/actor motion.") + + +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 = getCutsceneMotionData(csName, False) + 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 OOTCSTextboxAdd(Operator): @@ -92,6 +158,26 @@ class OOTCSListAdd(Operator): 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" @@ -113,7 +199,11 @@ class OOT_ExportCutscene(Operator): 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(ootCutsceneDataToC(converted, converted.name)) writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported cutscene") @@ -132,19 +222,27 @@ class OOT_ExportAllCutscenes(Operator): 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.type != "EMPTY" 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") + + converted = convertCutsceneObject(obj) + if context.scene.exportMotionOnly: + raise PluginError("ERROR: Not implemented yet.") + else: + csdata.append(ootCutsceneDataToC(converted, converted.name)) + 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"} @@ -156,6 +254,7 @@ class OOT_ExportAllCutscenes(Operator): oot_cutscene_classes = ( OOTCSTextboxAdd, OOTCSListAdd, + OOT_ImportCutscene, OOT_ExportCutscene, OOT_ExportAllCutscenes, ) @@ -166,10 +265,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 f7a8323..4a0dddb 100644 --- a/fast64_internal/oot/cutscene/panels.py +++ b/fast64_internal/oot/cutscene/panels.py @@ -1,7 +1,9 @@ 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_CutscenePanel(OOT_Panel): @@ -12,20 +14,65 @@ 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,) 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 0000000..7693099 --- /dev/null +++ b/fast64_internal/oot/cutscene/preview.py @@ -0,0 +1,278 @@ +import bpy + +from math import isclose +from bpy.types import Scene, Object, Node +from bpy.app.handlers import persistent +from ...utility import gammaInverse, hexOrDecInt + + +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.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.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 == "CS_TRANS_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 == "CS_MISC_SET_LOCKED_VIEWPOINT" and not None in cameraObjects: + bpy.context.scene.camera = cameraObjects[int(csObj.ootCutsceneProperty.preview.isFixedCamSet)] + csObj.ootCutsceneProperty.preview.isFixedCamSet ^= True + + if miscCmd.type == "CS_MISC_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 ["CS_MISC_VISMONO_SEPIA", "CS_MISC_VISMONO_BLACK_AND_WHITE"]: + if miscCmd.type == "CS_MISC_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 + + if miscCmd.type == "CS_MISC_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""" + csObj: Object = bpy.context.scene.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, None] + for obj in csObj.children: + if obj.type == "CAMERA": + cameraObjects[1] = obj + break + + 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 + bpy.context.scene.ootCSPreviewNodesReady = False + setupCompositorNodes() + + # execute the main preview logic + previewProp = csObj.ootCutsceneProperty.preview + 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, bpy.context.scene.ootCSPreviewNodesReady, cameraObjects) + else: + # Simulate cutscene for all frames up to present + for i in range(bpy.context.scene.frame_current): + processCurrentFrame(csObj, i, bpy.context.scene.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 ae6d25a..f6f5625 100644 --- a/fast64_internal/oot/cutscene/properties.py +++ b/fast64_internal/oot/cutscene/properties.py @@ -1,10 +1,19 @@ -from bpy.types import PropertyGroup, Object, UILayout +import bpy +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 .motion.preview import previewFrameHandler + +from .motion.operators import ( + OOTCSMotionPlayPreview, + OOTCSMotionCreateCameraShot, + OOTCSMotionCreatePlayerCueList, + OOTCSMotionCreateActorCueList, +) # Perhaps this should have been called something like OOTCSParentPropertyType, @@ -219,6 +228,43 @@ class OOTCSListProperty(PropertyGroup): addOp.objName = objName +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): + useWidescreen: BoolProperty( + name="Use Widescreen Camera", default=False, update=lambda self, context: self.updateWidescreen(context) + ) + + 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) + + 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) + + class OOTCutsceneProperty(PropertyGroup): csEndFrame: IntProperty(name="End Frame", min=0, default=100) csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)") @@ -227,7 +273,23 @@ class OOTCutsceneProperty(PropertyGroup): csTermEnd: IntProperty(name="End Frm", min=0, default=100) csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists") + preview: PointerProperty(type=OOTCutscenePreviewProperty) + def draw_props(self, layout: UILayout, obj: Object): + split = layout.split(factor=0.5) + split.label(text="Player Age for Preview") + split.prop(bpy.context.scene, "previewPlayerAge", text="") + + split = layout.split(factor=0.5) + split.operator(OOTCSMotionCreateCameraShot.bl_idname, icon="VIEW_CAMERA") + split.operator(OOTCSMotionPlayPreview.bl_idname, icon="RESTRICT_VIEW_OFF") + + split = layout.split(factor=0.5) + split.operator(OOTCSMotionCreatePlayerCueList.bl_idname) + split.operator(OOTCSMotionCreateActorCueList.bl_idname) + + layout.prop(self.preview, "useWidescreen") + layout.prop(self, "csEndFrame") layout.prop(self, "csWriteTerminator") if self.csWriteTerminator: @@ -250,6 +312,9 @@ classes = ( OOTCS0x09Property, OOTCSUnkProperty, OOTCSListProperty, + OOTCutsceneTransitionProperty, + OOTCutsceneMiscProperty, + OOTCutscenePreviewProperty, OOTCutsceneProperty, ) @@ -259,9 +324,13 @@ def cutscene_props_register(): register_class(cls) Object.ootCutsceneProperty = PointerProperty(type=OOTCutsceneProperty) + Scene.ootCSPreviewNodesReady = BoolProperty(default=False) + Scene.ootCSPreviewCSObj = PointerProperty(type=Object) def cutscene_props_unregister(): + del Scene.ootCSPreviewCSObj + del Scene.ootCSPreviewNodesReady 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 0000000..e22acb5 --- /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 465707b..c4619a9 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 0000000..df5cf7e --- /dev/null +++ b/fast64_internal/oot/data/oot_enum_data.py @@ -0,0 +1,92 @@ +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): + pass + + +@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"], + item.attrib["ID"], + int(item.attrib["Index"]), + ) + 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.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 efcdad7..a53b1eb 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 0000000..9627768 --- /dev/null +++ b/fast64_internal/oot/data/xml/EnumData.xml @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/fast64_internal/oot/f3d/operators.py b/fast64_internal/oot/f3d/operators.py index a45e09f..ef42719 100644 --- a/fast64_internal/oot/f3d/operators.py +++ b/fast64_internal/oot/f3d/operators.py @@ -6,7 +6,7 @@ from bpy.utils import register_class, unregister_class 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 @@ from ..oot_utility import ( 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 @@ class OOT_ImportDL(Operator): 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: @@ -181,15 +179,11 @@ class OOT_ExportDL(Operator): # 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_constants.py b/fast64_internal/oot/oot_constants.py index b456d59..a3feafe 100644 --- a/fast64_internal/oot/oot_constants.py +++ b/fast64_internal/oot/oot_constants.py @@ -9,71 +9,15 @@ ootEnumRoomShapeType = [ ("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 @@ ootEnumLinkIdle = [ ("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"), @@ -348,22 +277,6 @@ ootEnumNaviHints = [ ("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 +512,7 @@ ootSceneIDToName = { "SCENE_SASATEST": "sasatest", "SCENE_TESTROOM": "testroom", } +ootSceneNameToID = {val: key for key, val in ootSceneIDToName.items()} ootEnumCamTransition = [ ("Custom", "Custom", "Custom"), @@ -607,7 +521,7 @@ ootEnumCamTransition = [ # ("0xFF", "0xFF", "0xFF"), ] -# see curRoom.unk_03 +# see curRoom.behaviorType1 ootEnumRoomBehaviour = [ ("Custom", "Custom", "Custom"), ("0x00", "Default", "Default"), @@ -618,17 +532,6 @@ ootEnumRoomBehaviour = [ ("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 +592,3 @@ ootEnumDrawConfig = [ ("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 185d03f..b26695c 100644 --- a/fast64_internal/oot/oot_level_classes.py +++ b/fast64_internal/oot/oot_level_classes.py @@ -210,9 +210,7 @@ class OOTScene(OOTCommonCommands): 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 887f090..d932485 100644 --- a/fast64_internal/oot/oot_level_parser.py +++ b/fast64_internal/oot/oot_level_parser.py @@ -3,7 +3,7 @@ from random import random 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.exporter.to_c import getDrawConfig from .scene.properties import OOTSceneHeaderProperty, OOTLightProperty, OOTImportSceneSettingsProperty from .room.properties import OOTRoomHeaderProperty from .actor.properties import OOTActorProperty, OOTActorHeaderProperty +from .cutscene.motion.importer import importCutsceneData from .oot_utility import ( getHeaderSettings, @@ -147,6 +148,7 @@ class SharedSceneData: includeCameras: bool, includePaths: bool, includeWaterBoxes: bool, + includeCutscenes: bool, ): self.actorDict = {} # actor hash : blender object self.entranceDict = {} # actor hash : blender object @@ -162,6 +164,7 @@ class SharedSceneData: 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 @@ class SharedSceneData: 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 121eb28..a637b8b 100644 --- a/fast64_internal/oot/oot_level_writer.py +++ b/fast64_internal/oot/oot_level_writer.py @@ -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: @@ -492,7 +490,7 @@ 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): +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.') @@ -510,7 +508,7 @@ def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneNam 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() @@ -784,12 +782,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 +819,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, diff --git a/fast64_internal/oot/oot_model_classes.py b/fast64_internal/oot/oot_model_classes.py index cee3b87..5d0b778 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 33a5237..1b34eda 100644 --- a/fast64_internal/oot/oot_upgrade.py +++ b/fast64_internal/oot/oot_upgrade.py @@ -1,5 +1,9 @@ +import bpy + from bpy.types import Object, CollectionProperty from .data import OoT_ObjectData +from .cutscene.motion.constants import ootEnumCSMotionCamMode +from .oot_constants import ootData def upgradeObjectList(objList: CollectionProperty, objData: OoT_ObjectData): @@ -34,3 +38,105 @@ def upgradeRoomHeaders(roomObj: Object, objData: OoT_ObjectData): upgradeObjectList(sceneLayer.objectList, objData) for i in range(len(altHeaders.cutsceneHeaders)): upgradeObjectList(altHeaders.cutsceneHeaders[i].objectList, objData) + + +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"] + + +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"] diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 1638b7c..2d22bf6 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 @@ -491,6 +497,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 +514,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 @@ -861,3 +874,70 @@ def getEvalParams(input: str): 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 e77c7bf..97e31b5 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, + OOTCSMotionActorCueListProperty, + OOTCSMotionActorCueProperty, +) from .actor.properties import ( OOTActorProperty, @@ -14,6 +18,28 @@ from .actor.properties import ( 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 @@ -144,6 +170,21 @@ class OOTObjectPanel(bpy.types.Panel): 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: OOTCSMotionActorCueListProperty = 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: OOTCSMotionActorCueProperty = 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.") @@ -157,6 +198,21 @@ class OOT_ObjectProperties(bpy.types.PropertyGroup): 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": + if obj.parent.name.startswith("Cutscene.") or obj.parent.ootEmptyType == "Cutscene": + OOTCutsceneMotionProperty.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 1ad2c44..3ef1268 100644 --- a/fast64_internal/oot/room/properties.py +++ b/fast64_internal/oot/room/properties.py @@ -19,14 +19,20 @@ from bpy.props import ( 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/room_shape.py b/fast64_internal/oot/scene/exporter/to_c/room_shape.py index e4f6bdf..fd7dd90 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" 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 a972ee4..0df9e91 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py @@ -72,7 +72,7 @@ def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int): 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): 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 e182d9a..59a0dca 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py @@ -1,15 +1,23 @@ -from .....utility import CData, PluginError +from .....utility import CData, PluginError, indent from ....oot_level_classes import OOTScene from ....cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC +from ....cutscene.motion.exporter import getCutsceneMotionData def ootCutsceneDataToC(csParent, csName): # csParent can be OOTCutscene or OOTScene + motionExporter = getCutsceneMotionData(csName, False) + motionData = motionExporter.getExportData() 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" + frameDiff = 0 + if motionExporter.frameCount > csParent.csEndFrame: + frameDiff = motionExporter.frameCount - csParent.csEndFrame + data.source += ( + indent + f"CS_BEGIN_CUTSCENE({nentries + motionExporter.entryTotal}, " + ) + f"{csParent.csEndFrame + frameDiff}),\n" if csParent.csWriteTerminator: data.source += ( "\tCS_TERMINATOR(" @@ -136,6 +144,7 @@ def ootCutsceneDataToC(csParent, csName): else: raise PluginError("Internal error: invalid cutscene list type " + list.listType) data.source += "),\n" + data.source += motionData data.source += "\tCS_END(),\n" data.source += "};\n\n" return data @@ -143,8 +152,14 @@ def ootCutsceneDataToC(csParent, csName): 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): # curHeader is either None or an OOTScene. This can either be the main scene itself, @@ -152,10 +167,13 @@ def getSceneCutscenes(outScene: OOTScene): if curHeader is not None and curHeader.writeCutscene: if curHeader.csWriteType == "Embedded": cutscenes.append(ootCutsceneDataToC(curHeader, curHeader.cutsceneDataName(i))) - elif curHeader.csWriteType == "Object": + elif curHeader.csWriteType == "Object" and curHeader.csWriteObject.name not in csObjects: cutscenes.append(ootCutsceneDataToC(curHeader.csWriteObject, curHeader.csWriteObject.name)) + csObjects.append(curHeader.csWriteObject.name) for extraCs in outScene.extraCutscenes: - cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name)) + if not extraCs.name in csObjects: + cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name)) + csObjects.append(extraCs.name) return cutscenes diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index f87a435..675fcdb 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -37,14 +37,8 @@ def run_ops_without_view_layer_update(func): 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): @@ -178,8 +172,6 @@ class OOT_ExportScene(Operator): 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 2abfbb3..2810090 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -28,9 +28,6 @@ from ..oot_utility import ( from ..oot_constants import ( ootEnumMusicSeq, ootEnumSceneID, - ootEnumExitIndex, - ootEnumTransitionAnims, - ootEnumLightGroupMenu, ootEnumGlobalObject, ootEnumNaviHints, ootEnumSkybox, @@ -40,13 +37,49 @@ from ..oot_constants import ( 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 = [ @@ -219,7 +252,11 @@ class OOTSceneTableEntryProperty(PropertyGroup): 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): @@ -264,7 +301,11 @@ class OOTSceneHeaderProperty(PropertyGroup): writeCutscene: BoolProperty(name="Write Cutscene") csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Embedded") csWriteCustom: StringProperty(name="CS hdr var:", default="") - csWriteObject: PointerProperty(name="Cutscene Object", type=Object) + csWriteObject: PointerProperty( + name="Cutscene Object", + type=Object, + poll=lambda self, object: object.type == "EMPTY" and object.ootEmptyType == "Cutscene", + ) # 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 @@ -508,6 +549,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 +557,19 @@ class OOTImportSceneSettingsProperty(PropertyGroup): 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 +578,6 @@ class OOTImportSceneSettingsProperty(PropertyGroup): 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 17b0268..1711134 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 bdf5484..04f25d0 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 e49f82e..043bf80 100644 --- a/fast64_internal/oot/skeleton/operators.py +++ b/fast64_internal/oot/skeleton/operators.py @@ -120,13 +120,9 @@ class OOT_ExportSkeleton(Operator): 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/tools/operators.py b/fast64_internal/oot/tools/operators.py index 03b73e1..022a08b 100644 --- a/fast64_internal/oot/tools/operators.py +++ b/fast64_internal/oot/tools/operators.py @@ -1,9 +1,13 @@ +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 class OOT_AddWaterBox(AddWaterBox): @@ -92,6 +96,7 @@ class OOT_AddScene(Operator): 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 +148,9 @@ class OOT_AddRoom(Operator): 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 +160,98 @@ class OOT_AddCutscene(Operator): 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"} diff --git a/fast64_internal/oot/tools/panel.py b/fast64_internal/oot/tools/panel.py index 1e51517..759e109 100644 --- a/fast64_internal/oot/tools/panel.py +++ b/fast64_internal/oot/tools/panel.py @@ -7,6 +7,7 @@ from .operators import ( OOT_AddRoom, OOT_AddCutscene, OOT_AddPath, + OOTClearTransformAndLock, ) @@ -22,6 +23,7 @@ class OoT_ToolsPanel(OOT_Panel): col.operator(OOT_AddRoom.bl_idname) col.operator(OOT_AddCutscene.bl_idname) col.operator(OOT_AddPath.bl_idname) + col.operator(OOTClearTransformAndLock.bl_idname) oot_operator_panel_classes = [ @@ -35,6 +37,7 @@ toolOpsToRegister = [ OOT_AddRoom, OOT_AddCutscene, OOT_AddPath, + OOTClearTransformAndLock, ] diff --git a/fast64_internal/sm64/sm64_f3d_writer.py b/fast64_internal/sm64/sm64_f3d_writer.py index 33c432b..059e408 100644 --- a/fast64_internal/sm64/sm64_f3d_writer.py +++ b/fast64_internal/sm64/sm64_f3d_writer.py @@ -16,6 +16,7 @@ from typing import Tuple, Union, Iterable from ..f3d.f3d_bleed import BleedGraphics from ..f3d.f3d_gbi import ( + get_F3D_GBI, GbiMacro, GfxTag, FMaterial, @@ -98,8 +99,8 @@ enumHUDPaths = { 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 @@ class SM64GfxFormatter(GfxFormatter): 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) @@ -603,8 +600,6 @@ class SM64_ExportDL(bpy.types.Operator): 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 @@ class SM64_ExportDL(bpy.types.Operator): 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 @@ class SM64_ExportDL(bpy.types.Operator): [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 @@ class SM64_ExportDL(bpy.types.Operator): [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 @@ class ExportTexRectDraw(bpy.types.Operator): 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 20b9d6c..57f36a0 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 @@ enumMatOverrideOptions = [ 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.") @@ -427,9 +427,14 @@ class GeolayoutBoneSidePanel(Panel): 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 +446,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_parser.py b/fast64_internal/sm64/sm64_geolayout_parser.py index 26ab1c0..34d1b4a 100644 --- a/fast64_internal/sm64/sm64_geolayout_parser.py +++ b/fast64_internal/sm64/sm64_geolayout_parser.py @@ -4,7 +4,7 @@ from ..f3d.f3d_parser import createBlankMaterial, parseF3DBinary 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 @@ class SM64_ImportGeolayout(bpy.types.Operator): 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 752c227..f422205 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 24f4a62..0996cb3 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -62,7 +62,6 @@ from ..utility import ( from ..f3d.f3d_material import ( isTexturePointSampled, - isLightingDisabled, ) from ..f3d.f3d_writer import ( @@ -78,10 +77,10 @@ from ..f3d.f3d_writer import ( saveMeshWithLargeTexturesByFaces, saveMeshByFaces, getF3DVert, - convertVertexData, ) from ..f3d.f3d_gbi import ( + get_F3D_GBI, GfxList, GfxListTag, GfxMatWriteMethod, @@ -355,14 +354,9 @@ def appendRevertToGeolayout(geolayoutGraph, fModel): # 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 +420,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, @@ -503,8 +494,6 @@ def exportGeolayoutArmatureC( armatureObj, obj, convertTransformMatrix, - f3dType, - isHWv1, dirPath, texDir, savePNG, @@ -519,7 +508,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 +531,6 @@ def exportGeolayoutArmatureC( def exportGeolayoutObjectC( obj, convertTransformMatrix, - f3dType, - isHWv1, dirPath, texDir, savePNG, @@ -558,7 +545,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 +798,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 +835,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 +855,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 +919,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 +939,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 +998,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): @@ -1489,7 +1463,6 @@ def processMesh( transformNode = TransformNode(node) 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 @@ -1631,7 +1604,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) @@ -2139,6 +2111,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 +2191,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 +2362,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 +2442,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,7 +2474,10 @@ 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): + 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: @@ -2635,16 +2609,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 +2696,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 +2707,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 +2763,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): @@ -2897,8 +2837,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter): exportGeolayoutObjectC( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, exportPath, bpy.context.scene.geoTexDir, saveTextures, @@ -2917,8 +2855,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter): exportGeolayoutObjectInsertableBinary( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath), None, ) @@ -2954,8 +2890,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter): finalTransform, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)), None, ) @@ -2968,8 +2902,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter): segmentData, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, None, ) @@ -3108,8 +3040,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): armatureObj, obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, exportPath, bpy.context.scene.geoTexDir, saveTextures, @@ -3130,8 +3060,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): armatureObj, obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath), None, ) @@ -3168,8 +3096,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): finalTransform, *modelLoadInfo, textDumpFilePath, - context.scene.f3d_type, - context.scene.isHWv1, getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)), None, ) @@ -3183,8 +3109,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): 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 2680d13..0d51e99 100644 --- a/fast64_internal/sm64/sm64_level_writer.py +++ b/fast64_internal/sm64/sm64_level_writer.py @@ -697,10 +697,7 @@ class SM64OptionalFileStatus: 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,7 +722,11 @@ def exportLevelC( puppycamVolumeString = "" inline = bpy.context.scene.exportInlineF3D - fModel = SM64Model(f3dType, isHWv1, levelName + "_dl", DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll) + 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, @@ -1181,8 +1180,6 @@ class SM64_ExportLevel(ObjectDataExporter): fileStatus = exportLevelC( obj, finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, levelName, exportPath, context.scene.saveTextures, diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index 7971c1b..c0775c8 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 @@ to_s16 = lambda x: cast_integer(round(x), 16, True) 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.") @@ -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/mario.blend b/mario.blend index d399f53..8ba0718 100644 Binary files a/mario.blend and b/mario.blend differ