commit 41538347bdb1bd406577a62067a505c6d4d65663 Author: kurethedead Date: Mon Nov 18 17:40:14 2019 -0800 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc91187 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ + +# Fast64 + +This requires Blender 2.81. +![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_running.gif) +This is a Blender plugin that allows one to export display lists, geolayouts, and animations to Super Mario 64, either directly into a ROM or to C code. It supports custom color combiners / geometry modes, and different geolayout node types. + +Make sure to save often, as this plugin is prone to crashing when undoing quickly. + +![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mat_inspector.png) +![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/bone_inspector.png) +### Features (import means import into Blender) +- Geolayout import/export +- Display List import/export +- Animation import/export +- Collision export +- Skinned mesh support +- Custom normals + +### Restrictions +- No YUV textures supported. +- When importing display lists / geolayouts from SM64 to Blender, only the mesh is imported (no material) + +### Credits +Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing. + +# Instructions +### Installation +Unzip and drag the entire folder into your blender addons folder (usually at C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons). Note that you must drag the ENTIRE folder, not just the 'fast64_internal' folder. Then in Blender, go to Edit -> Preferences -> Add-Ons and find/check the 'Fast64' plugin. If it does not show up, go to Edit -> Preferences -> Save&Load and make sure 'Auto Run Python Scripts' is enabled. + +### Tool Locations +The tools can be found in the properties sidebar under the 'Tools' tab (toggled by pressing N). +The F3D material inspector can be found in the properties editor under the material tab. +The geolayout inspector can be found in the properties editor under the bone tab in Pose Mode. +The RDP default settings can be found in the properties editor under the world tab. + +### ROM File Settings +When importing from a ROM, the plugin will import from the ROM at filepath 'Import ROM', When exporting to a ROM, the plugin will make a copy of the file at 'Export ROM', modify it, and save the file to 'Output ROM'. The ROM must be expanded. + +### Where Can I Export To? +By default, when exporting to ROM, the plugin will copy the contents of bank 4 to the range (0x11A35B8 - 0x11FFF00). This will leave you with free space to export assets to range (0x11D8930 - 0x11FFF00). + +### Vertex Colors +To use vertex colors, select the "Vertex Colored Texture" preset and add two vertex color layers to your mesh named 'Col' and 'Alpha'. The alpha layer will use the greyscale value of the vertex color to determine alpha. + +### F3D Materials +Any exported meshes must have an F3D Material, which can be added by the 'Create F3D Material' button in the material inspector window. + +### RDP State Optimization +A material will not set every single geometry mode / other mode option. Instead, each property will be checked against the default settings located in the world tab in the properties sidebar. If the values are the same, then the code to change it is not added. + +### Geolayouts +There are many different geolayout node types. In Blender, each node type is represented by a bone group. Imported geolayouts will already have these bone groups, but if you want to add them to your own armature use the 'Add Bone Groups' operator in the SM64 Armature Tools header. All regular bones (aka 0x13 commands) and 0x15 command bones are placed on armature layer 0. All other geolayout commands are placed on layer 1. Metarig generated bones are placed on layers 3 and 4 (see 'Animating Existing Geolayouts'). If you change a bone's geolayout command type and it dissapears, it might have been moved to a different layer. Make sure to go to the armature properties editor and set both layers 0 and 1 to be visible. Geolayout command types can be editted in the properties editor under the bone tab in pose mode. + +### Exporting Geolayouts and Skinned Meshes +The N64 supports binary skinning, meaning each vertex will be influenced by one bone only. When skinning an exported geolayout, do NOT use automatic skinning, as this results in a smooth weight falloff. Instead, when weight painting set the weight to either 1 or 0, and set the brush Falloff to square. Skinning can only occur between an immediate parent and a child deform bone, not between siblings / across ancestors. + +### Replacing Existing SM64 Geolayout Geometry +SM64 geolayouts are often in strange rest poses, which makes it hard to modify their geometry. It often helps to import an animation belonging to that geolayout to see what the idle pose of a geolayout should be. Once you know, you can rotate the bones of the armature in pose mode to a usable position and then use the 'Apply as Rest Pose' operator under the SM64 Armature Tools header. Skin your new mesh to that armature, then rotate the bones back to the original position and use 'Apply as Rest Pose' again. You can now export the geolayout to SM64 and it will be able to use existing animations. + +### Importing An Animation To Blender +To import an animation to blender, select an armature for the animation to be exported to, and press 'Import animation'. Note that the armature's root 0x13 (i.e. regular) bone must be named 'root'. Animations can be found by looking at behaviour scripts where 27 and 28 commands are. + +### Exporting Mario Animations +Mario animations use a DMA table, which contains 8 byte entries of (offset from table start, animation size). Documentation about this table is here: +https://dudaw.webs.com/sm64docs/sm64_marios_animation_table.txt +Basically, Mario's DMA table starts at 0x4EC000. There is an 8 byte header, and then the animation entries afterward. Thus the 'climb up ledge' DMA entry is at 0x4EC008. The first 8 bytes at that address indicate the offset from 0x4EC000 at which the actual animation exists. Using this table you can find animations you want to overwrite. + +### Animating Existing Geolayouts +Often times it is hard to rig an existing SM64 geolayout, as there are many intermediate non-deform bones and bones don't point to their children. To make this easier you can use the 'Create Animatable Metarig' operator in the SM64 Armature Tools header. This will generate a metarig which can be used with IK. The metarig bones will be placed on armature layers 3 and 4. + +### Exporting Geolayouts to C +When you export in C, there will be 3 paths. Geo Filepath is for geolayout data. DL Filepath is for f3d data. Definitions Filepath is for extern definitions of objects. +To replace a model in decomp, replace its geo.inc.c and model.inc.c contents with the geolayout file and the dl file respectively. Use the contents of the define file to replace existing defines in one of the group header files (ex. mario is in group0.h). Make sure that the name of your geolayout is the same the name of the geolayout you're replacing. + +### Switch Statements +To add a switch mesh node, duplicate your switch bone and move it off to the side. Set the bone geolayout command to be Switch Option. Any nodes in this switch option must be children of this bone. Add any other bones to this bone. Add your switch geometry into your existing mesh in the correct position and skin it. + +### Common Issues +Inivisible Mesh : Bone is not set to deform, or geometry is skinned to more than one bone. +Game crashes: Invalid function address for switch/function/held object bones. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b233f8e --- /dev/null +++ b/__init__.py @@ -0,0 +1,1899 @@ +import sys +import tempfile +import copy +import shutil +import bpy +import traceback +import os +from pathlib import Path +from .fast64_internal import * + +import cProfile +import pstats + +# info about add on +bl_info = { + "name": "Fast64", + "category": "Object", + "blender": (2, 80, 0), + } + +axis_enums = [ + ('X', 'X', 'X'), + ('Y', 'Y', 'Y'), + ('-X', '-X', '-X'), + ('-Y', '-Y', '-Y'), +] + +enumExportType = [ + ('C', 'C', 'C'), + ('Binary', 'Binary', 'Binary') +] + +panelSeparatorSize = 5 + +def checkExpanded(filepath): + size = os.path.getsize(filepath) + if size < 9000000: # check if 8MB + raise ValueError("ROM at " + filepath + " is too small. You may be using an unexpanded ROM. You can expand a ROM by opening it in SM64 Editor or ROM Manager.") + +class ArmatureApplyWithMesh(bpy.types.Operator): + # set bl_ properties + bl_description = 'Applies current pose as default pose. Useful for ' + \ + "rigging an armature that is not in T/A pose. Note that when using " +\ + " with an SM64 armature, you must revert to the default pose after " +\ + "skinning." + bl_idname = 'object.armature_apply_w_mesh' + bl_label = "Apply As Rest Pose" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT' and context.mode != 'POSE': + raise ValueError("Operator can only be used in object or pose mode.") + elif context.mode == 'POSE': + bpy.ops.object.mode_set(mode = "OBJECT") + + if len(context.selected_objects) == 0: + raise ValueError("Armature not selected.") + elif type(context.selected_objects[0].data) is not\ + bpy.types.Armature: + raise ValueError("Armature not selected.") + + armatureObj = context.selected_objects[0] + for child in armatureObj.children: + if type(child.data) is not bpy.types.Mesh: + continue + armatureModifier = None + for modifier in child.modifiers: + if isinstance(modifier, bpy.types.ArmatureModifier): + armatureModifier = modifier + if armatureModifier is None: + continue + print(armatureModifier.name) + bpy.ops.object.select_all(action = "DESELECT") + context.view_layer.objects.active = child + bpy.ops.object.modifier_copy(modifier=armatureModifier.name) + print(len(child.modifiers)) + bpy.ops.object.modifier_apply(modifier=armatureModifier.name) + + bpy.ops.object.select_all(action = "DESELECT") + context.view_layer.objects.active = armatureObj + bpy.ops.object.mode_set(mode = "POSE") + bpy.ops.pose.armature_apply() + + self.report({'INFO'}, 'Applied armature with mesh.') + return {'FINISHED'} # must return a set + +class AddBoneGroups(bpy.types.Operator): + # set bl_ properties + bl_description = 'Add bone groups respresenting other node types in ' +\ + 'SM64 geolayouts (ex. Shadow, Switch, Function).' + bl_idname = 'object.add_bone_groups' + bl_label = "Add Bone Groups" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT' and context.mode != 'POSE': + raise ValueError("Operator can only be used in object or pose mode.") + elif context.mode == 'POSE': + bpy.ops.object.mode_set(mode = "OBJECT") + + if len(context.selected_objects) == 0: + raise ValueError("Armature not selected.") + elif type(context.selected_objects[0].data) is not\ + bpy.types.Armature: + raise ValueError("Armature not selected.") + + armatureObj = context.selected_objects[0] + createBoneGroups(armatureObj) + + self.report({'INFO'}, 'Created bone groups.') + return {'FINISHED'} # must return a set + +class CreateMetarig(bpy.types.Operator): + # set bl_ properties + bl_description = 'SM64 imported armatures are usually not good for ' + \ + 'rigging. There are often intermediate bones between deform bones ' + \ + 'and they don\'t usually point to their children. This operator ' +\ + 'creates a metarig on armature layer 4 useful for IK.' + bl_idname = 'object.create_metarig' + bl_label = "Create Animatable Metarig" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT' and context.mode != 'POSE': + raise ValueError("Operator can only be used in object or pose mode.") + elif context.mode == 'POSE': + bpy.ops.object.mode_set(mode = "OBJECT") + + if len(context.selected_objects) == 0: + raise ValueError("Armature not selected.") + elif type(context.selected_objects[0].data) is not\ + bpy.types.Armature: + raise ValueError("Armature not selected.") + + armatureObj = context.selected_objects[0] + generateMetarig(armatureObj) + + self.report({'INFO'}, 'Created metarig.') + return {'FINISHED'} # must return a set + +class N64_AddF3dMat(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.add_f3d_mat' + bl_label = "Add Fast3D Material" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + + if len(context.selected_objects) == 0: + raise ValueError("Mesh not selected.") + elif type(context.selected_objects[0].data) is not\ + bpy.types.Mesh: + raise ValueError("Mesh not selected.") + + obj = context.selected_objects[0] + createF3DMat(obj) + + self.report({'INFO'}, 'Created F3D material.') + return {'FINISHED'} # must return a set + +# See SM64GeoLayoutPtrsByLevels.txt by VLTone +class SM64_ImportGeolayout(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_import_geolayout' + bl_label = "Import Geolayout" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + geoImportAddr : bpy.props.StringProperty(name ='Start Address', default = '1F1D60') + generateArmature : bpy.props.BoolProperty(name ='Generate Armature?') + levelGeoImport : bpy.props.EnumProperty(items = level_enums, name = 'Level Used By Geolayout Import', default = 'HMC') + importRom : bpy.props.StringProperty(name ='Import ROM', subtype = 'FILE_PATH') + ignoreSwitch : bpy.props.BoolProperty(name = 'Ignore Switch Nodes', default = True) + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + #finalTransform = mathutils.Matrix.Rotation(math.radians(-90), 4, 'X') + finalTransform = mathutils.Matrix.Identity(4) + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + try: + romfileSrc = open(bpy.path.abspath(self.importRom), 'rb') + checkExpanded(bpy.path.abspath(self.importRom)) + + armatureObj = None + #cProfile.runctx('armatureMeshGroups, armatureObj = parseGeoLayout(romfileSrc, int(self.geoImportAddr, 16),context.scene, self.levelGeoImport, finalTransform, self.generateArmature, self.ignoreSwitch, True, context.scene.f3d_type, context.scene.isHWv1)', globals(), locals(), "E:/Non-Steam Games/emulators/Project 64 1.6/SM64 Romhack Tools/_Data/blender.prof") + #p = pstats.Stats("E:/Non-Steam Games/emulators/Project 64 1.6/SM64 Romhack Tools/_Data/blender.prof") + #p.sort_stats("cumulative").print_stats(2000) + + # Armature mesh groups includes armatureObj. + armatureMeshGroups, armatureObj = parseGeoLayout(romfileSrc, + int(self.geoImportAddr, 16), + context.scene, self.levelGeoImport, + finalTransform, self.generateArmature, + self.ignoreSwitch, True, context.scene.f3d_type, + context.scene.isHWv1) + romfileSrc.close() + + bpy.ops.object.select_all(action = 'DESELECT') + if armatureObj is not None: + for armatureMeshGroup in armatureMeshGroups: + armatureMeshGroup[0].select_set(True) + bpy.ops.transform.rotate(value = math.radians(-90), + orient_axis='X') + + for armatureMeshGroup in armatureMeshGroups: + bpy.ops.object.select_all(action = 'DESELECT') + armatureMeshGroup[0].select_set(True) + bpy.context.view_layer.objects.active = armatureMeshGroup[0] + bpy.ops.object.make_single_user(obdata = True) + bpy.ops.object.transform_apply(location = False, + rotation = True, scale = False, properties = False) + else: + bpy.ops.transform.rotate(value = math.radians(-90), + orient_axis='X') + bpy.ops.object.select_all(action = 'DESELECT') + #objs[-1].select_set(True) + + self.report({'INFO'}, 'Generic import succeeded.') + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + + romfileSrc.close() + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ImportGeolayoutPanel(bpy.types.Panel): + bl_idname = "SM64_PT_import_geolayout" + bl_label = "SM64 Geolayout Importer" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsGeoI = col.operator(SM64_ImportGeolayout.bl_idname) + + propsGeoI.generateArmature = context.scene.generateArmature + propsGeoI.geoImportAddr = context.scene.geoImportAddr + propsGeoI.levelGeoImport = context.scene.levelGeoImport + propsGeoI.importRom = context.scene.importRom + propsGeoI.ignoreSwitch = context.scene.ignoreSwitch + + #col.prop(context.scene, 'rotationOrder') + #col.prop(context.scene, 'rotationAxis') + #col.prop(context.scene, 'rotationAngle') + prop_split(col, context.scene, 'geoImportAddr', 'Start Address') + col.prop(context.scene, 'levelGeoImport') + col.prop(context.scene, 'generateArmature') + col.prop(context.scene, 'ignoreSwitch') + if not context.scene.ignoreSwitch: + boxLayout = col.box() + boxLayout.label(text = "WARNING: May take a long time.") + boxLayout.label(text = "Switch nodes won't be setup.") + col.box().label(text = "Only Fast3D mesh importing allowed.") + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportGeolayout(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_export_geolayout' + bl_label = "Export Geolayout" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + geoExportStart : bpy.props.StringProperty(name = 'Start', + default = '11D8930') + geoExportEnd : bpy.props.StringProperty(name = 'End', + default = '11FFF00') + overwriteModelLoad: bpy.props.BoolProperty( + name = 'Overwrite model load in level script?', default = True) + modelLoadLevelScriptCmd : bpy.props.StringProperty( + name = 'Level script model load command', default = '2ABD90') + modelID : bpy.props.IntProperty(name = 'Model ID', default = 0xBB) + geoUseBank0 : bpy.props.BoolProperty(name = 'Use Bank 0') + geoRAMAddr : bpy.props.StringProperty(name = 'RAM Address', + default = '80000000') + + exportRom : bpy.props.StringProperty(name ='Export ROM', subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', subtype = 'FILE_PATH') + levelGeoExport : bpy.props.EnumProperty(items = level_enums, + name = 'Level Used By Geolayout Export', default = 'CG') + + textDumpGeo : bpy.props.BoolProperty( + name = 'Dump geolayout as text', default = False) + textDumpGeoPath : bpy.props.StringProperty( + name ='Text Dump Path', subtype = 'FILE_PATH') + + geoExportType : bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + geoExportPath : bpy.props.StringProperty( + name = 'Geo Path', subtype = 'FILE_PATH') + geoExportPathDL : bpy.props.StringProperty( + name = 'DL Path', subtype = 'FILE_PATH') + geoDefinePath : bpy.props.StringProperty( + name = 'Defines Path', subtype = 'FILE_PATH') + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + tempROM = tempName(self.outputRom) + armatureObj = None + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + if len(context.selected_objects) == 0: + raise ValueError("Armature not selected.") + armatureObj = context.active_object + if type(armatureObj.data) is not bpy.types.Armature: + raise ValueError("Armature not selected.") + + obj = armatureObj.children[0] + finalTransform = mathutils.Matrix.Identity(4) + + # get all switch option armatures as well + linkedArmatures = [armatureObj] + getAllArmatures(armatureObj, linkedArmatures) + + linkedArmatureDict = {} + + for linkedArmature in linkedArmatures: + # IMPORTANT: Do this BEFORE rotation + optionObjs = [] + for childObj in linkedArmature.children: + if isinstance(childObj.data, bpy.types.Mesh): + optionObjs.append(childObj) + if len(optionObjs) > 1: + raise ValueError('Error: ' + linkedArmature.name +\ + ' has more than one mesh child.') + elif len(optionObjs) < 1: + raise ValueError('Error: ' + linkedArmature.name +\ + ' has no mesh children.') + linkedMesh = optionObjs[0] + prepareGeolayoutExport(linkedArmature, linkedMesh) + linkedArmatureDict[linkedArmature] = linkedMesh + + # Rotate all armatures 90 degrees + bpy.ops.object.select_all(action = "DESELECT") + for linkedArmature in linkedArmatures: + linkedArmature.select_set(True) + armatureObj.select_set(True) + + bpy.ops.transform.rotate(value = math.radians(90), orient_axis='X') + bpy.ops.object.transform_apply(location = False, rotation = True, + scale = False, properties = False) + + # You must ALSO apply object rotation after armature rotation. + bpy.ops.object.select_all(action = "DESELECT") + for linkedArmature, linkedMesh in linkedArmatureDict.items(): + linkedMesh.select_set(True) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.transform_apply(location = False, rotation = True, + scale = False, properties = False) + + try: + if self.geoExportType == 'C': + exportGeolayoutC(armatureObj, obj, finalTransform, + context.scene.f3d_type, context.scene.isHWv1, + bpy.path.abspath(self.geoExportPath), + bpy.path.abspath(self.geoExportPathDL), + bpy.path.abspath(self.geoDefinePath)) + self.report({'INFO'}, 'Success! Geolayout at ' + \ + self.geoExportPath + ', DL at ' + self.geoExportPathDL) + else: + checkExpanded(bpy.path.abspath(self.exportRom)) + romfileExport = open(bpy.path.abspath(self.exportRom), 'rb') + shutil.copy(bpy.path.abspath(self.exportRom), + bpy.path.abspath(tempROM)) + romfileExport.close() + romfileOutput = open(bpy.path.abspath(tempROM), 'rb+') + + levelParsed = parseLevelAtPointer(romfileOutput, + level_pointers[self.levelGeoExport]) + segmentData = levelParsed.segmentData + + if context.scene.extendBank4: + ExtendBank0x04(romfileOutput, segmentData, + defaultExtendSegment4) + + exportRange = [int(self.geoExportStart, 16), + int(self.geoExportEnd, 16)] + textDumpFilePath = bpy.path.abspath(self.textDumpGeoPath) \ + if self.textDumpGeo else None + if self.overwriteModelLoad: + modelLoadInfo = (int(self.modelLoadLevelScriptCmd, 16), + self.modelID) + else: + modelLoadInfo = (None, None) + + if self.geoUseBank0: + addrRange, startRAM = exportGeolayoutBinaryBank0( + romfileOutput, armatureObj, obj, exportRange, + finalTransform, *modelLoadInfo, textDumpFilePath, + context.scene.f3d_type, context.scene.isHWv1, + getAddressFromRAMAddress(int(self.geoRAMAddr, 16))) + else: + addrRange, segPointer = exportGeolayoutBinary( + romfileOutput, armatureObj, obj, + exportRange, finalTransform, segmentData, + *modelLoadInfo, textDumpFilePath, + context.scene.f3d_type, context.scene.isHWv1) + + romfileOutput.close() + bpy.ops.object.select_all(action = 'DESELECT') + armatureObj.select_set(True) + context.view_layer.objects.active = armatureObj + + os.remove(bpy.path.abspath(self.outputRom)) + os.rename(bpy.path.abspath(tempROM), + bpy.path.abspath(self.outputRom)) + + if self.geoUseBank0: + self.report({'INFO'}, 'Success! Geolayout at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ + '), to write to RAM Address ' + hex(startRAM)) + else: + self.report({'INFO'}, 'Success! Geolayout at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ + ') (Seg. ' + segPointer + ').') + + bpy.ops.object.select_all(action = "DESELECT") + for linkedArmature in linkedArmatures: + linkedArmature.select_set(True) + armatureObj.select_set(True) + context.view_layer.objects.active = armatureObj + bpy.ops.transform.rotate(value = math.radians(-90), orient_axis='X') + bpy.ops.object.transform_apply(location = False, rotation = True, + scale = False, properties = False) + + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + + bpy.ops.object.select_all(action = "DESELECT") + for linkedArmature in linkedArmatures: + linkedArmature.select_set(True) + armatureObj.select_set(True) + context.view_layer.objects.active = armatureObj + bpy.ops.transform.rotate(value = math.radians(-90), orient_axis='X') + bpy.ops.object.transform_apply(location = False, rotation = True, + scale = False, properties = False) + + romfileOutput.close() + os.remove(bpy.path.abspath(tempROM)) + if armatureObj is not None: + armatureObj.select_set(True) + context.view_layer.objects.active = armatureObj + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ExportGeolayoutPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_geolayout" + bl_label = "SM64 Geolayout Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsGeoE = col.operator(SM64_ExportGeolayout.bl_idname) + propsGeoE.geoExportStart = context.scene.geoExportStart + propsGeoE.geoExportEnd = context.scene.geoExportEnd + propsGeoE.levelGeoExport = context.scene.levelGeoExport + propsGeoE.exportRom = context.scene.exportRom + propsGeoE.outputRom = context.scene.outputRom + propsGeoE.overwriteModelLoad = context.scene.overwriteModelLoad + propsGeoE.modelLoadLevelScriptCmd = context.scene.modelLoadLevelScriptCmd + propsGeoE.modelID = context.scene.modelID + propsGeoE.textDumpGeo = context.scene.textDumpGeo + propsGeoE.textDumpGeoPath = context.scene.textDumpGeoPath + propsGeoE.geoExportType = context.scene.geoExportType + propsGeoE.geoExportPath = context.scene.geoExportPath + propsGeoE.geoExportPathDL = context.scene.geoExportPathDL + propsGeoE.geoDefinePath = context.scene.geoDefinePath + propsGeoE.geoUseBank0 = context.scene.geoUseBank0 + propsGeoE.geoRAMAddr = context.scene.geoRAMAddr + + col.prop(context.scene, 'geoExportType') + if propsGeoE.geoExportType == 'C': + col.prop(context.scene, 'geoExportPath') + col.prop(context.scene, 'geoExportPathDL') + col.prop(context.scene, 'geoDefinePath') + else: + prop_split(col, context.scene, 'geoExportStart', 'Start Address') + prop_split(col, context.scene, 'geoExportEnd', 'End Address') + + col.prop(context.scene, 'geoUseBank0') + if propsGeoE.geoUseBank0: + prop_split(col, context.scene, 'geoRAMAddr', 'RAM Address') + else: + col.prop(context.scene, 'levelGeoExport') + + col.prop(context.scene, 'overwriteModelLoad') + if context.scene.overwriteModelLoad: + prop_split(col, context.scene, 'modelLoadLevelScriptCmd', 'Model Load Command') + col.prop(context.scene, 'modelID') + col.prop(context.scene, 'textDumpGeo') + if context.scene.textDumpGeo: + col.prop(context.scene, 'textDumpGeoPath') + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ArmatureToolsPanel(bpy.types.Panel): + bl_idname = "SM64_PT_armature_tools" + bl_label = "SM64 Armature Tools" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + col.operator(ArmatureApplyWithMesh.bl_idname) + col.operator(AddBoneGroups.bl_idname) + col.operator(CreateMetarig.bl_idname) + #col.operator(N64_AddF3dMat.bl_idname) + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ImportDL(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_import_dl' + bl_label = "Import Display List" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + DLImportStart : bpy.props.StringProperty(name ='Import Start', + default = 'A3BE1C') + levelDLImport : bpy.props.EnumProperty(items = level_enums, name = 'Level Used By Display List', default = 'CG') + importRom : bpy.props.StringProperty(name ='Import ROM', subtype = 'FILE_PATH') + isSegmentedAddrDLImport : bpy.props.BoolProperty(name = 'Is Segmented Address', default = False) + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + try: + checkExpanded(bpy.path.abspath(self.importRom)) + romfileSrc = open(bpy.path.abspath(self.importRom), 'rb') + levelParsed = parseLevelAtPointer(romfileSrc, + level_pointers[self.levelDLImport]) + segmentData = levelParsed.segmentData + start = decodeSegmentedAddr( + int(self.DLImportStart, 16).to_bytes(4, 'big'), segmentData)\ + if self.isSegmentedAddrDLImport else \ + int(self.DLImportStart, 16) + readObj = F3DtoBlenderObject(romfileSrc, start, + context.scene, 'sm64_mesh', + blenderToSM64Rotation.to_4x4().inverted(), + segmentData, True) + romfileSrc.close() + + self.report({'INFO'}, 'Generic import succeeded.') + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + romfileSrc.close() + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ImportDLPanel(bpy.types.Panel): + bl_idname = "SM64_PT_import_dl" + bl_label = "SM64 DL Importer" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsDLI = col.operator(SM64_ImportDL.bl_idname) + + propsDLI.DLImportStart = context.scene.DLImportStart + propsDLI.levelDLImport = context.scene.levelDLImport + propsDLI.importRom = context.scene.importRom + propsDLI.isSegmentedAddrDLImport = context.scene.isSegmentedAddrDLImport + prop_split(col, context.scene, 'DLImportStart', 'Start Address') + col.prop(context.scene, 'levelDLImport') + col.prop(context.scene, 'isSegmentedAddrDLImport') + col.box().label(text = "Only Fast3D mesh importing allowed.") + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportDL(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_export_dl' + bl_label = "Export Display List" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + exportRom : bpy.props.StringProperty(name ='Export ROM', subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', subtype = 'FILE_PATH') + levelDLExport : bpy.props.EnumProperty(items = level_enums, + name = 'Level Used By Display List', default = 'CG') + DLExportStart : bpy.props.StringProperty( + name = 'Start', default = 'A3BE1C') + DLExportEnd : bpy.props.StringProperty( + name = 'End', default = 'A4BE1C') + DLExportGeoPtr : bpy.props.StringProperty( + name ='Geolayout Pointer', default = 'A3BE1C') + overwriteGeoPtr : bpy.props.BoolProperty(name = "Overwrite geolayout pointer", default = False) + DLExportType : bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + DLExportPath : bpy.props.StringProperty( + name = 'Path', subtype = 'FILE_PATH') + DLDefinePath : bpy.props.StringProperty( + name = 'Defines Path', subtype = 'FILE_PATH') + DLExportisStatic : bpy.props.BoolProperty(name = 'Static DL', + default = True) + DLUseBank0 : bpy.props.BoolProperty(name = 'Use Bank 0') + DLRAMAddr : bpy.props.StringProperty(name = 'RAM Address', + default = '80000000') + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + checkExpanded(bpy.path.abspath(self.exportRom)) + tempROM = tempName(self.outputRom) + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + allObjs = context.selected_objects + if len(allObjs) == 0: + raise ValueError("No objects selected.") + obj = context.selected_objects[0] + if not isinstance(obj.data, bpy.types.Mesh): + raise ValueError("Object is not a mesh.") + + T, R, S = obj.matrix_world.decompose() + objTransform = R.to_matrix().to_4x4() @ \ + mathutils.Matrix.Diagonal(S).to_4x4() + finalTransform = (blenderToSM64Rotation * \ + (1/sm64ToBlenderScale)).to_4x4() @ objTransform + + try: + if self.DLExportType == 'C': + exportF3DtoC(bpy.path.abspath(self.DLExportPath), + bpy.path.abspath(self.DLDefinePath), obj, + self.DLExportisStatic, finalTransform, + context.scene.f3d_type, context.scene.isHWv1) + self.report({'INFO'}, 'Success! DL at ' + \ + self.DLExportPath + '.') + return {'FINISHED'} # must return a set + else: + romfileExport = open(bpy.path.abspath(self.exportRom), 'rb') + shutil.copy(bpy.path.abspath(self.exportRom), + bpy.path.abspath(tempROM)) + romfileExport.close() + romfileOutput = open(bpy.path.abspath(tempROM), 'rb+') + + levelParsed = parseLevelAtPointer(romfileOutput, + level_pointers[self.levelDLExport]) + segmentData = levelParsed.segmentData + if context.scene.extendBank4: + ExtendBank0x04(romfileOutput, segmentData, + defaultExtendSegment4) + + if self.DLUseBank0: + startAddress, addrRange, segPointerData = \ + exportF3DtoBinaryBank0(romfileOutput, + [int(self.DLExportStart, 16), int(self.DLExportEnd, 16)], + finalTransform, obj, context.scene.f3d_type, + context.scene.isHWv1, + getAddressFromRAMAddress(int(self.DLRAMAddr, 16))) + else: + startAddress, addrRange, segPointerData = \ + exportF3DtoBinary(romfileOutput, + [int(self.DLExportStart, 16), int(self.DLExportEnd, 16)], + finalTransform, obj, context.scene.f3d_type, + context.scene.isHWv1, segmentData) + + if self.overwriteGeoPtr: + romfileOutput.seek(int(self.DLExportGeoPtr, 16)) + romfileOutput.write(segPointerData) + + romfileOutput.close() + os.remove(bpy.path.abspath(self.outputRom)) + os.rename(bpy.path.abspath(tempROM), + bpy.path.abspath(self.outputRom)) + + if self.DLUseBank0: + self.report({'INFO'}, 'Success! DL at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ + '), ' +\ + 'to write to RAM address ' + \ + hex(startAddress + 0x80000000)) + else: + + self.report({'INFO'}, 'Success! DL at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ + ') (Seg. ' + bytesToHex(segPointerData) + ').') + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + romfileOutput.close() + os.remove(bpy.path.abspath(tempROM)) + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ExportDLPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_dl" + bl_label = "SM64 DL Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsDLE = col.operator(SM64_ExportDL.bl_idname) + propsDLE.DLExportStart= context.scene.DLExportStart + propsDLE.DLExportEnd = context.scene.DLExportEnd + propsDLE.exportRom = context.scene.exportRom + propsDLE.outputRom = context.scene.outputRom + propsDLE.levelDLExport = context.scene.levelDLExport + propsDLE.DLExportGeoPtr = context.scene.DLExportGeoPtr + propsDLE.overwriteGeoPtr = context.scene.overwriteGeoPtr + propsDLE.DLExportType = context.scene.DLExportType + propsDLE.DLExportPath = context.scene.DLExportPath + propsDLE.DLExportisStatic = context.scene.DLExportisStatic + propsDLE.DLDefinePath = context.scene.DLDefinePath + propsDLE.DLUseBank0 = context.scene.DLUseBank0 + propsDLE.DLRAMAddr = context.scene.DLRAMAddr + + col.prop(context.scene, 'DLExportType') + if propsDLE.DLExportType == 'C': + col.prop(context.scene, 'DLExportPath') + col.prop(context.scene, 'DLExportisStatic') + col.prop(context.scene, 'DLDefinePath') + else: + prop_split(col, context.scene, 'DLExportStart', 'Start Address') + prop_split(col, context.scene, 'DLExportEnd', 'End Address') + col.prop(context.scene, 'DLUseBank0') + if propsDLE.DLUseBank0: + prop_split(col, context.scene, 'DLRAMAddr', 'RAM Address') + else: + col.prop(context.scene, 'levelDLExport') + col.prop(context.scene, 'overwriteGeoPtr') + if context.scene.overwriteGeoPtr: + prop_split(col, context.scene, 'DLExportGeoPtr', + 'Geolayout Pointer') + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportMario(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_export' + bl_label = "Export Character" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + exportRom : bpy.props.StringProperty(name ='Export ROM', subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', subtype = 'FILE_PATH') + useFaceAnimation : bpy.props.BoolProperty(name ='Use Face Animation?') + overwriteWingPositions : bpy.props.BoolProperty(name ='Overwrite Wing Positions? (See attatched diagram)') + segment4 : bpy.props.IntVectorProperty(name="Segment 4", + default=defaultExtendSegment4, size = 2) + exportRange : bpy.props.IntVectorProperty(name="Export range", + default=marioFullRomInterval, size = 2) + useCustomSegment4 : bpy.props.BoolProperty(name = "Use custom segment 4?", default = True) + sm64_character : bpy.props.EnumProperty( + items = character_enums, name = 'SM64 Character', default = 'Mario' + ) + + useLogFile : bpy.props.BoolProperty(name ='Write log file?') + logFilePath : bpy.props.StringProperty(name = 'Log File', subtype = 'FILE_PATH') + + def execute(self, context): + tempROM = tempName(self.outputRom) + try: + romfileExport = open(self.exportRom, 'rb') + shutil.copy(self.exportRom, tempROM) + romfileExport.close() + romfileOutput = open(tempROM, 'rb+') + + segmentData = {} + readSegment4(romfileOutput, segmentData) + print("Export range: " + hex(self.exportRange[0]) + " - " + hex(self.exportRange[1])) + + armatureData = character_data[self.sm64_character] + isMario = self.sm64_character == 'Mario' + + if self.useCustomSegment4: + ExtendBank0x04(romfileOutput, segmentData, self.segment4) + + if isMario: + DisableLowPolyMario(romfileOutput, armatureData.geolayoutStart) + + # Find armature. + armatureObj = None + armature = None + for obj in bpy.context.selected_objects: + if isinstance(obj.data, bpy.types.Armature): + armatureObj = obj + armature = armatureObj.data + break + if armature is None: + romfileOutput.close() + self.report({'ERROR'}, 'Mario armature not selected.') + return {'CANCELLED'} # must return a set + + # Update geolayout translation offsets + + if isMario and not self.overwriteWingPositions: + saveExistingGeolayoutTransforms(romfileOutput, armatureObj, + armatureData.geolayoutStart, armatureData, blenderToSM64Rotation, + True) + elif isMario: + saveExistingGeolayoutTransforms(romfileOutput, armatureObj, + armatureData.geolayoutStart, armatureData, blenderToSM64Rotation, False) + + bodyParts = [] + for obj in bpy.context.scene.objects: + if isinstance(obj.data, bpy.types.Mesh) and\ + len(obj.data.loops) > 0: + + for constraint in obj.constraints: + if isinstance(constraint, bpy.types.ChildOfConstraint) and\ + constraint.target == armatureObj: + + bodyParts.append(obj) + + if isMario: + success = importMario(bodyParts, self, romfileOutput, context, 'Y', segmentData, armatureObj, + self.exportRange[0], self.exportRange[1]) + else: + success = importGeolayout(bodyParts, self, romfileOutput, context, 'Y', segmentData, + armatureObj, self.exportRange[0], self.exportRange[1], + armatureData, False) + + armatureObj.select_set(True) + romfileOutput.close() + os.remove(self.outputRom) + os.rename(tempROM, self.outputRom) + + if success: + self.report({'INFO'}, 'Export succeeded.') + return {'FINISHED'} # must return a set + else: + self.report({'INFO'}, 'Export failed.') + return {'CANCELLED'} # must return a set + except: + self.report({'ERROR'}, traceback.format_exc()) + romfileOutput.close() + os.remove(tempROM) + return {'CANCELLED'} # must return a set + +class SM64_ExportCharacterPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_character" + bl_label = "SM64 Character Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsE = col.operator(SM64_ExportMario.bl_idname) + propsE.sm64_character = context.scene.sm64_character + propsE.exportRom = context.scene.exportRom + propsE.outputRom = context.scene.outputRom + propsE.useFaceAnimation = context.scene.useFaceAnimation + propsE.overwriteWingPositions = context.scene.overwriteWingPositions + propsE.exportRange = context.scene.exportRange + propsE.useCustomSegment4 = context.scene.useCustomSegment4 + propsE.segment4 = context.scene.segment4 + propsE.useLogFile = context.scene.useLogFile + propsE.logFilePath = context.scene.logFilePath + + col.prop(context.scene, 'useFaceAnimation') + col.prop(context.scene, 'overwriteWingPositions') + col.prop(context.scene, 'useCustomSegment4') + if context.scene.useCustomSegment4: + col.prop(context.scene, 'segment4') + col.prop(context.scene, 'exportRange') + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ImportMario(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_import' + bl_label = "Import Character" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + sm64_character : bpy.props.EnumProperty( + items = character_enums, name = 'SM64 Character', default = 'Mario' + ) + + importRom : bpy.props.StringProperty(name ='Import ROM', subtype = 'FILE_PATH') + characterIgnoreSwitch : bpy.props.BoolProperty(name = 'Ignore Switch Nodes', default = True) + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + #finalTransform = mathutils.Matrix.Rotation(math.radians(-90), 4, 'X') + finalTransform = mathutils.Matrix.Identity(4) + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + try: + romfileSrc = open(bpy.path.abspath(self.importRom), 'rb') + checkExpanded(bpy.path.abspath(self.importRom)) + + characterImportData = sm64_character_data[self.sm64_character] + + # Armature mesh groups includes armatureObj. + armatureMeshGroups, armatureObj = parseGeoLayout(romfileSrc, + int(characterImportData.geoAddr, 16), + context.scene, characterImportData.level, + finalTransform, True, + self.characterIgnoreSwitch, True, context.scene.f3d_type, + context.scene.isHWv1, characterImportData.switchDict) + romfileSrc.close() + + bpy.ops.object.select_all(action = 'DESELECT') + if armatureObj is not None: + for armatureMeshGroup in armatureMeshGroups: + armatureMeshGroup[0].select_set(True) + bpy.ops.transform.rotate(value = math.radians(-90), + orient_axis='X') + + for armatureMeshGroup in armatureMeshGroups: + bpy.ops.object.select_all(action = 'DESELECT') + armatureMeshGroup[0].select_set(True) + bpy.context.view_layer.objects.active = armatureMeshGroup[0] + bpy.ops.object.make_single_user(obdata = True) + bpy.ops.object.transform_apply(location = False, + rotation = True, scale = False, properties = False) + else: + bpy.ops.transform.rotate(value = math.radians(-90), + orient_axis='X') + bpy.ops.object.select_all(action = 'DESELECT') + #objs[-1].select_set(True) + + self.report({'INFO'}, 'Generic import succeeded.') + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + + romfileSrc.close() + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ImportCharacterPanel(bpy.types.Panel): + bl_idname = "SM64_PT_import_character" + bl_label = "SM64 Character Importer" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + col.prop(context.scene, 'sm64_character') + col.prop(context.scene, 'characterIgnoreSwitch') + propsI = col.operator(SM64_ImportMario.bl_idname) + propsI.importRom = context.scene.importRom + propsI.sm64_character = context.scene.sm64_character + propsI.characterIgnoreSwitch = context.scene.characterIgnoreSwitch + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ImportLevel(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_import_lvl' + bl_label = "Import Level" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + importRom : bpy.props.StringProperty(name ='Import ROM', subtype = 'FILE_PATH') + levelLevel : bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'CG') + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + try: + romfileSrc = open(self.importRom, 'rb') + levelParsed = parseLevelAtPointer(romfileSrc, level_pointers[self.levelLevel]) + segmentData = levelParsed.segmentData + + areaGeos = [parseGeoLayout(romfileSrc, decodeSegmentedAddr( + area.geoAddress, segmentData), context.scene, self.levelLevel, + mathutils.Matrix.Rotation(math.radians(90), 4, 'X'), False, + True, False, context.scene.f3d_type, context.scene.isHWv1) \ + for area in levelParsed.areas] + + romfileSrc.close() + + return {'FINISHED'} # must return a set + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + self.report({'ERROR'}, traceback.format_exc()) + romfileSrc.close() + return {'CANCELLED'} # must return a set + +class SM64_ImportLevelPanel(bpy.types.Panel): + bl_idname = "SM64_PT_import_level" + bl_label = "SM64 Level Importer" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsLI = col.operator(SM64_ImportLevel.bl_idname) + #propsLE = col.operator(SM64_ExportLevel.bl_idname) + + propsLI.levelLevel = context.scene.levelLevel + propsLI.importRom = context.scene.importRom + col.prop(context.scene, 'levelLevel') + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportLevel(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_export_lvl' + bl_label = "Export Level" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + exportRom : bpy.props.StringProperty(name ='Export ROM', subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', subtype = 'FILE_PATH') + levelLevel : bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'CG') + + def execute(self, context): + tempROM = tempName(self.outputRom) + try: + romfileExport = open(bpy.path.abspath(self.exportRom), 'rb') + shutil.copy(bpy.path.abspath(self.exportRom), + bpy.path.abspath(tempROM)) + romfileExport.close() + romfileOutput = open(bpy.path.abspath(tempROM), 'rb+') + + segmentData = {} + ExtendBank0x04(romfileOutput, segmentData, defaultExtendSegment4) + DisableLowPolyMario(romfileOutput, character_data["Mario"].geolayoutStart) + + allObjs = context.selected_objects + bpy.ops.object.select_all(action = 'DESELECT') + for obj in allObjs: + if 'sm64_rom_start' in obj and \ + 'sm64_rom_end' in obj and \ + 'sm64_geolayout_pointers' in obj: + + start = obj['sm64_rom_start'] + end = obj['sm64_rom_end'] + geo = obj['sm64_geolayout_pointers'] + + if 'sm64_draw_layer' in obj: + drawLayer = obj['sm64_draw_layer'] + else: + drawLayer = 0x01 + + success = importGeneric(romfileOutput, obj, context, 'Y', start, + end, levelEnum, geo, blenderToSM64Rotation, + drawLayer, verbose = True) + if not success: + self.report({'ERROR'}, 'Model is too large.') + romfileOutput.close() + return {'CANCELLED'} # must return a set + + obj.select_set(True) + + else: + self.report({'ERROR'}, obj.name + \ + " does not have correct custom properties.") + + + romfileOutput.close() + os.remove(self.outputRom) + os.rename(tempROM, self.outputRom) + self.report({'INFO'}, 'Export succeeded.') + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + romfileOutput.close() + os.remove(tempROM) + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ExportLevelPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_level" + bl_label = "SM64 Level Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsLE.exportRom = context.scene.exportRom + propsLE.outputRom = context.scene.outputRom + propsLE.levelLevel = context.scene.levelLevel + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ImportAnimMario(bpy.types.Operator): + bl_idname = 'object.sm64_import_anim' + bl_label = "Import Animation" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + animStartImport : bpy.props.StringProperty(name ='Import Start', + default = '4EC690') + importRom : bpy.props.StringProperty(name ='Import ROM', subtype = 'FILE_PATH') + isDMAImport: bpy.props.BoolProperty(name = 'Is DMA Animation Import') + levelAnimImport : bpy.props.EnumProperty(items = level_enums, name = 'Level Used By Animation', default = 'CG') + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + checkExpanded(bpy.path.abspath(self.importRom)) + romfileSrc = open(bpy.path.abspath(self.importRom), 'rb') + try: + # Note actual level doesn't matter for Mario, since he is in all of them + #levelParsed = parseLevelAtPointer(romfileSrc, level_pointers['CC']) + #segmentData = levelParsed.segmentData + if len(context.selected_objects) == 0: + raise ValueError("Armature not selected.") + armatureObj = context.active_object + if type(armatureObj.data) is not bpy.types.Armature: + raise ValueError("Armature not selected.") + levelParsed = parseLevelAtPointer(romfileSrc, level_pointers [self.levelAnimImport]) + segmentData = levelParsed.segmentData + importAnimationToBlender(romfileSrc, + int(self.animStartImport, 16), armatureObj, + segmentData, self.isDMAImport) + romfileSrc.close() + except: + romfileSrc.close() + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + + return {'FINISHED'} # must return a set + +class SM64_ImportAnimPanel(bpy.types.Panel): + bl_idname = "SM64_PT_import_anim" + bl_label = "SM64 Animation Importer" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsAnimImport = col.operator(SM64_ImportAnimMario.bl_idname) + propsAnimImport.animStartImport = context.scene.animStartImport + propsAnimImport.importRom = context.scene.importRom + propsAnimImport.isDMAImport = context.scene.isDMAImport + propsAnimImport.levelAnimImport = context.scene.levelAnimImport + col.prop(context.scene, 'isDMAImport') + prop_split(col, context.scene, 'animStartImport', 'Start Address') + col.prop(context.scene, 'levelAnimImport') + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportAnimMario(bpy.types.Operator): + bl_idname = 'object.sm64_export_anim' + bl_label = "Export Animation" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + animExportStart : bpy.props.StringProperty(name ='Start', + default = '11D8930') + animExportEnd : bpy.props.StringProperty(name ='End', + default = '11FFF00') + exportRom : bpy.props.StringProperty(name ='Export ROM', subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', subtype = 'FILE_PATH') + isDMAExport : bpy.props.BoolProperty(name = 'Is DMA Animation Export') + DMAEntryAddress : bpy.props.StringProperty(name = 'DMA Entry Address') + DMAStartAddress : bpy.props.StringProperty(name = 'DMA Start Address') + levelAnimExport : bpy.props.EnumProperty(items = level_enums, name = 'Level Used By Animation', default = 'CG') + loopAnimation : bpy.props.BoolProperty(name = 'Loop Animation', default = True) + overwrite_0x27 : bpy.props.BoolProperty( + name = 'Overwrite 0x27 behaviour command') + overwrite_0x28 : bpy.props.BoolProperty( + name = 'Overwrite 0x28 behaviour command') + addr_0x27 : bpy.props.StringProperty(name = '0x27 Command Address', + default = '21CD00') + addr_0x28 : bpy.props.StringProperty(name = '0x28 Command Address', + default = '21CD08') + animExportType : bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + animExportPath : bpy.props.StringProperty( + name = 'Path', subtype = 'FILE_PATH') + animOverwriteDMAEntry : bpy.props.BoolProperty(name = 'Overwrite DMA Entry') + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + checkExpanded(bpy.path.abspath(self.exportRom)) + tempROM = tempName(self.outputRom) + if len(context.selected_objects) == 0 or not \ + isinstance(context.selected_objects[0].data, bpy.types.Armature): + raise ValueError("Armature not selected.") + armatureObj = context.selected_objects[0] + + if self.animExportType == 'C': + try: + exportAnimationC(armatureObj, self.loopAnimation, + bpy.path.abspath(self.animExportPath)) + self.report({'INFO'}, 'Success! Animation at ' +\ + self.animExportPath) + except: + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + else: + try: + romfileExport = open(bpy.path.abspath(self.exportRom), 'rb') + shutil.copy(bpy.path.abspath(self.exportRom), + bpy.path.abspath(tempROM)) + romfileExport.close() + romfileOutput = open(bpy.path.abspath(tempROM), 'rb+') + + # Note actual level doesn't matter for Mario, since he is in all of them + levelParsed = parseLevelAtPointer(romfileOutput, level_pointers [self.levelAnimExport]) + segmentData = levelParsed.segmentData + if context.scene.extendBank4: + ExtendBank0x04(romfileOutput, segmentData, + defaultExtendSegment4) + + DMAAddresses = None + if self.animOverwriteDMAEntry: + DMAAddresses = {} + DMAAddresses['start'] = int(self.DMAStartAddress, 16) + DMAAddresses['entry'] = int(self.DMAEntryAddress, 16) + + addrRange, nonDMAListPtr = exportAnimationBinary( + romfileOutput, [int(self.animExportStart, 16), + int(self.animExportEnd, 16)], bpy.context.active_object, + DMAAddresses, segmentData, self.isDMAExport, + self.loopAnimation) + + if not self.isDMAExport: + segmentedPtr = encodeSegmentedAddr(nonDMAListPtr, segmentData) + if self.overwrite_0x27: + romfileOutput.seek(int(self.addr_0x27, 16) + 4) + romfileOutput.write(segmentedPtr) + if self.overwrite_0x28: + romfileOutput.seek(int(self.addr_0x28, 16) + 1) + romfileOutput.write(bytearray([0x00])) + else: + segmentedPtr = None + + romfileOutput.close() + os.remove(bpy.path.abspath(self.outputRom)) + os.rename(bpy.path.abspath(tempROM), + bpy.path.abspath(self.outputRom)) + + if not self.isDMAExport: + self.report({'INFO'}, 'Sucess! Animation table at ' + \ + hex(nonDMAListPtr) + ' (Seg. ' + \ + bytesToHex(segmentedPtr) + '), animation at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + ').') + else: + self.report({'INFO'}, 'Success! Animation at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + ').') + except: + romfileOutput.close() + os.remove(bpy.path.abspath(tempROM)) + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + + return {'FINISHED'} # must return a set + +class SM64_ExportAnimPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_anim" + bl_label = "SM64 Animation Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsAnimExport = col.operator(SM64_ExportAnimMario.bl_idname) + propsAnimExport.animExportStart = context.scene.animExportStart + propsAnimExport.animExportEnd = context.scene.animExportEnd + propsAnimExport.exportRom = context.scene.exportRom + propsAnimExport.outputRom = context.scene.outputRom + propsAnimExport.isDMAExport = context.scene.isDMAExport + propsAnimExport.DMAEntryAddress = context.scene.DMAEntryAddress + propsAnimExport.DMAStartAddress = context.scene.DMAStartAddress + propsAnimExport.levelAnimExport = context.scene.levelAnimExport + propsAnimExport.loopAnimation = context.scene.loopAnimation + propsAnimExport.overwrite_0x27 = context.scene.overwrite_0x27 + propsAnimExport.addr_0x27 = context.scene.addr_0x27 + propsAnimExport.overwrite_0x28 = context.scene.overwrite_0x28 + propsAnimExport.addr_0x28 = context.scene.addr_0x28 + propsAnimExport.animExportType = context.scene.animExportType + propsAnimExport.animExportPath = context.scene.animExportPath + propsAnimExport.animOverwriteDMAEntry = \ + context.scene.animOverwriteDMAEntry + + col.prop(context.scene, 'animExportType') + col.prop(context.scene, 'loopAnimation') + if propsAnimExport.animExportType == 'C': + col.prop(context.scene, 'animExportPath') + else: + col.prop(context.scene, 'isDMAExport') + if context.scene.isDMAExport: + col.prop(context.scene, 'animOverwriteDMAEntry') + if context.scene.animOverwriteDMAEntry: + prop_split(col, context.scene, 'DMAStartAddress', + 'DMA Start Address') + prop_split(col, context.scene, 'DMAEntryAddress', + 'DMA Entry Address') + else: + col.prop(context.scene, 'overwrite_0x27') + if context.scene.overwrite_0x27: + prop_split(col, context.scene, 'addr_0x27', + '27 Command Address') + col.prop(context.scene, 'overwrite_0x28') + if context.scene.overwrite_0x28: + prop_split(col, context.scene, 'addr_0x28', + '28 Command Address') + col.prop(context.scene, 'levelAnimExport') + col.separator() + prop_split(col, context.scene, 'animExportStart', 'Start Address') + prop_split(col, context.scene, 'animExportEnd', 'End Address') + + + for i in range(panelSeparatorSize): + col.separator() + +class SM64_ExportCollision(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.sm64_export_collision' + bl_label = "Export Collision" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + colStartAddr : bpy.props.StringProperty(name ='Start Address', + default = '11D8930') + colEndAddr : bpy.props.StringProperty(name ='Start Address', + default = '11FFF00') + exportRom : bpy.props.StringProperty(name ='Export ROM', + subtype = 'FILE_PATH') + outputRom : bpy.props.StringProperty(name ='Output ROM', + subtype = 'FILE_PATH') + colExportPath : bpy.props.StringProperty( + name = 'Path', subtype = 'FILE_PATH') + colExportType : bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + colExportLevel : bpy.props.EnumProperty(items = level_enums, + name = 'Level Used By Collision', default = 'CG') + addr_0x2A : bpy.props.StringProperty(name = '0x2A Behaviour Command Address', + default = '0') + set_addr_0x2A : bpy.props.BoolProperty( + name = 'Overwrite 0x2A Behaviour Command') + + def execute(self, context): + tempROM = tempName(self.outputRom) + obj = None + if context.mode != 'OBJECT': + raise ValueError("Operator can only be used in object mode.") + if len(context.selected_objects) == 0: + raise ValueError("Object not selected.") + obj = context.active_object + if type(obj.data) is not bpy.types.Mesh: + raise ValueError("Mesh not selected.") + + T, R, S = obj.matrix_world.decompose() + objTransform = R.to_matrix().to_4x4() @ \ + mathutils.Matrix.Diagonal(S).to_4x4() + finalTransform = (blenderToSM64Rotation * \ + (1/sm64ToBlenderScale)).to_4x4() @ objTransform + + try: + if self.colExportType == 'C': + exportCollisionC(obj, finalTransform, + bpy.path.abspath(self.colExportPath), False) + self.report({'INFO'}, 'Success! Collision at ' + \ + self.colExportPath) + else: + checkExpanded(bpy.path.abspath(self.exportRom)) + romfileExport = open(bpy.path.abspath(self.exportRom), 'rb') + shutil.copy(bpy.path.abspath(self.exportRom), + bpy.path.abspath(tempROM)) + romfileExport.close() + romfileOutput = open(bpy.path.abspath(tempROM), 'rb+') + + levelParsed = parseLevelAtPointer(romfileOutput, + level_pointers[self.colExportLevel]) + segmentData = levelParsed.segmentData + + if context.scene.extendBank4: + ExtendBank0x04(romfileOutput, segmentData, + defaultExtendSegment4) + + addrRange = \ + exportCollisionBinary(obj, finalTransform, romfileOutput, + int(self.colStartAddr, 16), int(self.colEndAddr, 16), + False) + + segAddress = encodeSegmentedAddr(addrRange[0], segmentData) + if self.set_addr_0x2A: + romfileOutput.seek(int(self.addr_0x2A, 16) + 4) + romfileOutput.write(segAddress) + segPointer = bytesToHex(segAddress) + + romfileOutput.close() + bpy.ops.object.select_all(action = 'DESELECT') + obj.select_set(True) + context.view_layer.objects.active = obj + + os.remove(bpy.path.abspath(self.outputRom)) + os.rename(bpy.path.abspath(tempROM), + bpy.path.abspath(self.outputRom)) + + self.report({'INFO'}, 'Success! Collision at (' + \ + hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ + ') (Seg. ' + segPointer + ').') + + return {'FINISHED'} # must return a set + + except: + if context.mode != 'OBJECT': + bpy.ops.object.mode_set(mode = 'OBJECT') + + bpy.ops.object.select_all(action = "DESELECT") + obj.select_set(True) + context.view_layer.objects.active = obj + + romfileOutput.close() + os.remove(bpy.path.abspath(tempROM)) + obj.select_set(True) + context.view_layer.objects.active = obj + self.report({'ERROR'}, traceback.format_exc()) + return {'CANCELLED'} # must return a set + +class SM64_ExportCollisionPanel(bpy.types.Panel): + bl_idname = "SM64_PT_export_collision" + bl_label = "SM64 Collision Exporter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + propsColE = col.operator(SM64_ExportCollision.bl_idname) + + propsColE.colExportType = context.scene.colExportType + propsColE.colStartAddr = context.scene.colStartAddr + propsColE.colEndAddr = context.scene.colEndAddr + propsColE.colExportPath = context.scene.colExportPath + propsColE.colExportLevel = context.scene.colExportLevel + propsColE.exportRom = context.scene.exportRom + propsColE.outputRom = context.scene.outputRom + propsColE.addr_0x2A = context.scene.addr_0x2A + propsColE.set_addr_0x2A = context.scene.set_addr_0x2A + + col.prop(context.scene, 'colExportType') + if propsColE.colExportType == 'C': + col.prop(context.scene, 'colExportPath') + else: + prop_split(col, context.scene, 'colStartAddr', 'Start Address') + prop_split(col, context.scene, 'colEndAddr', 'End Address') + prop_split(col, context.scene, 'colExportLevel', + 'Level Used By Collision') + col.prop(context.scene, 'set_addr_0x2A') + if propsColE.set_addr_0x2A: + prop_split(col, context.scene, 'addr_0x2A', + '0x2A Behaviour Command Address') + for i in range(panelSeparatorSize): + col.separator() + +class F3D_GlobalSettingsPanel(bpy.types.Panel): + bl_idname = "F3D_PT_global_settings" + bl_label = "F3D Global Settings" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + col.prop(context.scene, 'f3d_type') + col.prop(context.scene, 'isHWv1') + +class SM64_FileSettingsPanel(bpy.types.Panel): + bl_idname = "SM64_PT_file_settings" + bl_label = "SM64 File Settings" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Tools' + + @classmethod + def poll(cls, context): + return True + + # called every frame + def draw(self, context): + col = self.layout.column() + col.prop(context.scene, 'importRom') + col.prop(context.scene, 'exportRom') + col.prop(context.scene, 'outputRom') + col.prop(context.scene, 'extendBank4') + + +# called on add-on enabling +# register operators and panels here +# append menu layout drawing function to an existing window +def register(): + sm64_collision.register() # register first, so panel goes above mat panel + f3d_material.register() + sm64_geolayout_bone.register() + + bpy.utils.register_class(ArmatureApplyWithMesh) + bpy.utils.register_class(AddBoneGroups) + bpy.utils.register_class(CreateMetarig) + bpy.utils.register_class(N64_AddF3dMat) + + bpy.utils.register_class(F3D_GlobalSettingsPanel) + bpy.utils.register_class(SM64_FileSettingsPanel) + #bpy.utils.register_class(SM64_ImportCharacterPanel) + #bpy.utils.register_class(SM64_ExportCharacterPanel) + bpy.utils.register_class(SM64_ImportGeolayoutPanel) + bpy.utils.register_class(SM64_ExportGeolayoutPanel) + bpy.utils.register_class(SM64_ArmatureToolsPanel) + bpy.utils.register_class(SM64_ImportAnimPanel) + bpy.utils.register_class(SM64_ExportAnimPanel) + bpy.utils.register_class(SM64_ImportDLPanel) + bpy.utils.register_class(SM64_ExportDLPanel) + #bpy.utils.register_class(SM64_ImportLevelPanel) + #bpy.utils.register_class(SM64_ExportLevelPanel) + bpy.utils.register_class(SM64_ExportCollisionPanel) + + #bpy.utils.register_class(SM64_ImportMario) + #bpy.utils.register_class(SM64_ExportMario) + bpy.utils.register_class(SM64_ImportGeolayout) + bpy.utils.register_class(SM64_ExportGeolayout) + bpy.utils.register_class(SM64_ImportDL) + bpy.utils.register_class(SM64_ExportDL) + bpy.utils.register_class(SM64_ImportAnimMario) + bpy.utils.register_class(SM64_ExportAnimMario) + #bpy.utils.register_class(SM64_ImportLevel) + #bpy.utils.register_class(SM64_ExportLevel) + bpy.utils.register_class(SM64_ExportCollision) + + # Character + bpy.types.Scene.rotationAxis = bpy.props.FloatVectorProperty( + size = 3, default = (1,0,0)) + bpy.types.Scene.rotationAngle = bpy.props.FloatProperty(default = 90) + bpy.types.Scene.rotationOrder = bpy.props.StringProperty(default = 'XYZ') + + bpy.types.Scene.sm64_character = bpy.props.EnumProperty( + items = character_enums, name = 'SM64 Character', default = 'Mario') + bpy.types.Scene.useFaceAnimation = bpy.props.BoolProperty( + name ='Use Face Animation?') + bpy.types.Scene.overwriteWingPositions = bpy.props.BoolProperty( + name ='Overwrite Wing Positions? (See attatched diagram)') + bpy.types.Scene.exportRange = bpy.props.IntVectorProperty(name="Export range", + default=marioFullRomInterval, size = 2, min = 0) + bpy.types.Scene.segment4 = bpy.props.IntVectorProperty(name="Segment 4", + default=defaultExtendSegment4, size = 2, min = 0) + bpy.types.Scene.useCustomSegment4 = bpy.props.BoolProperty( + name = "Use custom segment 4?", default = True) + bpy.types.Scene.useLogFile = bpy.props.BoolProperty(name ='Write log file?') + bpy.types.Scene.logFilePath = bpy.props.StringProperty( + name = 'Log File', subtype = 'FILE_PATH') + + # Display List + bpy.types.Scene.DLImportStart = bpy.props.StringProperty( + name ='Start Address', default = 'A3BE1C') + bpy.types.Scene.DLExportStart = bpy.props.StringProperty( + name = 'Start', default = '11D8930') + bpy.types.Scene.DLExportEnd = bpy.props.StringProperty( + name = 'End', default = '11FFF00') + bpy.types.Scene.levelDLImport = bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'CG') + bpy.types.Scene.levelDLExport = bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'WF') + bpy.types.Scene.DLExportGeoPtr = bpy.props.StringProperty( + name ='Geolayout Pointer', default = '132AA8') + bpy.types.Scene.overwriteGeoPtr = bpy.props.BoolProperty( + name = "Overwrite geolayout pointer", default = False) + bpy.types.Scene.isSegmentedAddrDLImport = bpy.props.BoolProperty( + name = 'Is Segmented Address', default = False) + bpy.types.Scene.DLExportType = bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + bpy.types.Scene.DLExportPath = bpy.props.StringProperty( + name = 'Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.DLExportisStatic = bpy.props.BoolProperty( + name = 'Static DL', default = True) + bpy.types.Scene.DLDefinePath = bpy.props.StringProperty( + name = 'Definitions Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.DLUseBank0 = bpy.props.BoolProperty(name = 'Use Bank 0') + bpy.types.Scene.DLRAMAddr = bpy.props.StringProperty(name = 'RAM Address', + default = '80000000') + + # Geolayouts + bpy.types.Scene.levelGeoImport = bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'HMC') + bpy.types.Scene.levelGeoExport = bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'HMC') + bpy.types.Scene.geoExportStart = bpy.props.StringProperty( + name = 'Start', default = '11D8930') + bpy.types.Scene.geoExportEnd = bpy.props.StringProperty( + name = 'End', default = '11FFF00') + bpy.types.Scene.generateArmature = bpy.props.BoolProperty( + name ='Generate Armature?', default = True) + bpy.types.Scene.geoImportAddr = bpy.props.StringProperty( + name ='Start Address', default = '1F1D60') + bpy.types.Scene.overwriteModelLoad = bpy.props.BoolProperty( + name = 'Modify level script', default = True) + bpy.types.Scene.modelLoadLevelScriptCmd = bpy.props.StringProperty( + name = 'Level script model load command', default = '2ABCE0') + bpy.types.Scene.modelID = bpy.props.IntProperty(name = 'Model ID', + default = 1, min = 0) + bpy.types.Scene.ignoreSwitch = bpy.props.BoolProperty( + name = 'Ignore Switch Nodes', default = True) + bpy.types.Scene.textDumpGeo = bpy.props.BoolProperty( + name = 'Dump geolayout as text', default = False) + bpy.types.Scene.textDumpGeoPath = bpy.props.StringProperty( + name ='Text Dump Path', subtype = 'FILE_PATH') + bpy.types.Scene.geoExportType = bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + bpy.types.Scene.geoExportPath = bpy.props.StringProperty( + name = 'Geo Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.geoExportPathDL = bpy.props.StringProperty( + name = 'DL Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.geoDefinePath = bpy.props.StringProperty( + name = 'Definitions Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.geoUseBank0 = bpy.props.BoolProperty(name = 'Use Bank 0') + bpy.types.Scene.geoRAMAddr = bpy.props.StringProperty(name = 'RAM Address', + default = '80000000') + + # Level + bpy.types.Scene.levelLevel = bpy.props.EnumProperty(items = level_enums, + name = 'Level', default = 'CG') + + # Animation + bpy.types.Scene.animStartImport = bpy.props.StringProperty( + name ='Import Start', default = '4EC690') + bpy.types.Scene.animExportStart = bpy.props.StringProperty( + name ='Start', default = '11D8930') + bpy.types.Scene.animExportEnd = bpy.props.StringProperty( + name ='End', default = '11FFF00') + bpy.types.Scene.isDMAImport = bpy.props.BoolProperty(name = 'Is DMA Animation', default = True) + bpy.types.Scene.isDMAExport = bpy.props.BoolProperty(name = 'Is DMA Animation') + bpy.types.Scene.DMAEntryAddress = bpy.props.StringProperty(name = 'DMA Entry Address', default = '4EC008') + bpy.types.Scene.DMAStartAddress = bpy.props.StringProperty(name = 'DMA Start Address', default = '4EC000') + bpy.types.Scene.levelAnimImport = bpy.props.EnumProperty(items = level_enums, name = 'Level', default = 'IC') + bpy.types.Scene.levelAnimExport = bpy.props.EnumProperty(items = level_enums, name = 'Level', default = 'IC') + bpy.types.Scene.loopAnimation = bpy.props.BoolProperty(name = 'Loop Animation', default = True) + bpy.types.Scene.overwrite_0x27 = bpy.props.BoolProperty(name = 'Overwrite 0x27 behaviour command', default = True) + bpy.types.Scene.overwrite_0x28 = bpy.props.BoolProperty(name = 'Overwrite 0x28 behaviour command', default = True) + bpy.types.Scene.addr_0x27 = bpy.props.StringProperty( + name = '0x27 Command Address', default = '21CD00') + bpy.types.Scene.addr_0x28 = bpy.props.StringProperty( + name = '0x28 Command Address', default = '21CD08') + bpy.types.Scene.animExportType = bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + bpy.types.Scene.animExportPath = bpy.props.StringProperty( + name = 'Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.animOverwriteDMAEntry = bpy.props.BoolProperty( + name = 'Overwrite DMA Entry') + + # Collision + bpy.types.Scene.colExportPath = bpy.props.StringProperty( + name = 'Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.colExportType = bpy.props.EnumProperty( + items = enumExportType, name = 'Export', default = 'Binary') + bpy.types.Scene.colExportLevel = bpy.props.EnumProperty(items = level_enums, + name = 'Level Used By Collision', default = 'WF') + bpy.types.Scene.addr_0x2A = bpy.props.StringProperty( + name = '0x2A Behaviour Command Address', default = '21A9CC') + bpy.types.Scene.set_addr_0x2A = bpy.props.BoolProperty( + name = 'Overwrite 0x2A Behaviour Command') + bpy.types.Scene.colStartAddr = bpy.props.StringProperty(name ='Start Address', + default = '11D8930') + bpy.types.Scene.colEndAddr = bpy.props.StringProperty(name ='Start Address', + default = '11FFF00') + + # ROM + bpy.types.Scene.importRom = bpy.props.StringProperty( + name ='Import ROM', subtype = 'FILE_PATH') + bpy.types.Scene.exportRom = bpy.props.StringProperty( + name ='Export ROM', subtype = 'FILE_PATH') + bpy.types.Scene.outputRom = bpy.props.StringProperty( + name ='Output ROM', subtype = 'FILE_PATH') + bpy.types.Scene.extendBank4 = bpy.props.BoolProperty( + name = 'Extend Bank 4 on Export?', default = True, + description = 'Sets bank 4 range to (' +\ + hex(defaultExtendSegment4[0]) + ', ' + \ + hex(defaultExtendSegment4[1]) + ') and copies data from old bank') + + bpy.types.Scene.characterIgnoreSwitch = \ + bpy.props.BoolProperty(name = 'Ignore Switch Nodes', default = True) + + +# called on add-on disabling +def unregister(): + del bpy.types.Scene.rotationAxis + del bpy.types.Scene.rotationAngle + del bpy.types.Scene.rotationOrder + + # Geolayout + del bpy.types.Scene.geoExportStart + del bpy.types.Scene.geoExportEnd + del bpy.types.Scene.overwriteModelLoad + del bpy.types.Scene.modelLoadLevelScriptCmd + del bpy.types.Scene.modelID + del bpy.types.Scene.textDumpGeo + del bpy.types.Scene.textDumpGeoPath + del bpy.types.Scene.geoExportType + del bpy.types.Scene.geoExportPath + del bpy.types.Scene.geoExportPathDL + del bpy.types.Scene.geoDefinePath + del bpy.types.Scene.geoUseBank0 + del bpy.types.Scene.geoRAMAddr + + # Animation + del bpy.types.Scene.animStartImport + del bpy.types.Scene.animExportStart + del bpy.types.Scene.animExportEnd + del bpy.types.Scene.levelAnimImport + del bpy.types.Scene.levelAnimExport + del bpy.types.Scene.isDMAImport + del bpy.types.Scene.isDMAExport + del bpy.types.Scene.DMAStartAddress + del bpy.types.Scene.DMAEntryAddress + del bpy.types.Scene.loopAnimation + del bpy.types.Scene.overwrite_0x27 + del bpy.types.Scene.overwrite_0x28 + del bpy.types.Scene.addr_0x27 + del bpy.types.Scene.addr_0x28 + del bpy.types.Scene.animExportType + del bpy.types.Scene.animExportPath + del bpy.types.Scene.animOverwriteDMAEntry + + # Character + del bpy.types.Scene.characterIgnoreSwitch + del bpy.types.Scene.sm64_character + del bpy.types.Scene.useFaceAnimation + del bpy.types.Scene.overwriteWingPositions + del bpy.types.Scene.generateArmature + del bpy.types.Scene.geoImportAddr + del bpy.types.Scene.levelGeoImport + del bpy.types.Scene.levelGeoExport + del bpy.types.Scene.ignoreSwitch + + # Display List + del bpy.types.Scene.levelDLImport + del bpy.types.Scene.levelDLExport + del bpy.types.Scene.DLImportStart + del bpy.types.Scene.DLExportStart + del bpy.types.Scene.DLExportEnd + del bpy.types.Scene.DLExportGeoPtr + del bpy.types.Scene.overwriteGeoPtr + del bpy.types.Scene.isSegmentedAddrDLImport + del bpy.types.Scene.DLExportType + del bpy.types.Scene.DLExportPath + del bpy.types.Scene.DLExportisStatic + del bpy.types.Scene.DLDefinePath + del bpy.types.Scene.DLUseBank0 + del bpy.types.Scene.DLRAMAddr + + # Level + del bpy.types.Scene.levelLevel + del bpy.types.Scene.exportRange + del bpy.types.Scene.segment4 + del bpy.types.Scene.useCustomSegment4 + del bpy.types.Scene.useLogFile + del bpy.types.Scene.logFilePath + + # Collision + del bpy.types.Scene.colExportPath + del bpy.types.Scene.colExportType + del bpy.types.Scene.colExportLevel + del bpy.types.Scene.addr_0x2A + del bpy.types.Scene.set_addr_0x2A + del bpy.types.Scene.colStartAddr + del bpy.types.Scene.colEndAddr + + # ROM + del bpy.types.Scene.importRom + del bpy.types.Scene.exportRom + del bpy.types.Scene.outputRom + del bpy.types.Scene.extendBank4 + + f3d_material.unregister() + sm64_geolayout_bone.unregister() + sm64_collision.unregister() + bpy.utils.unregister_class(ArmatureApplyWithMesh) + bpy.utils.unregister_class(AddBoneGroups) + bpy.utils.unregister_class(CreateMetarig) + bpy.utils.unregister_class(N64_AddF3dMat) + + bpy.utils.unregister_class(F3D_GlobalSettingsPanel) + bpy.utils.unregister_class(SM64_FileSettingsPanel) + #bpy.utils.unregister_class(SM64_ImportCharacterPanel) + #bpy.utils.unregister_class(SM64_ExportCharacterPanel) + bpy.utils.unregister_class(SM64_ImportGeolayoutPanel) + bpy.utils.unregister_class(SM64_ExportGeolayoutPanel) + bpy.utils.unregister_class(SM64_ArmatureToolsPanel) + bpy.utils.unregister_class(SM64_ImportAnimPanel) + bpy.utils.unregister_class(SM64_ExportAnimPanel) + bpy.utils.unregister_class(SM64_ImportDLPanel) + bpy.utils.unregister_class(SM64_ExportDLPanel) + #bpy.utils.unregister_class(SM64_ImportLevelPanel) + #bpy.utils.unregister_class(SM64_ExportLevelPanel) + bpy.utils.unregister_class(SM64_ExportCollisionPanel) + bpy.utils.unregister_class(SM64_ExportCollision) + + #bpy.utils.unregister_class(SM64_ImportMario) + #bpy.utils.unregister_class(SM64_ExportMario) + bpy.utils.unregister_class(SM64_ImportGeolayout) + bpy.utils.unregister_class(SM64_ExportGeolayout) + bpy.utils.unregister_class(SM64_ImportDL) + bpy.utils.unregister_class(SM64_ExportDL) + bpy.utils.unregister_class(SM64_ImportAnimMario) + bpy.utils.unregister_class(SM64_ExportAnimMario) + #bpy.utils.unregister_class(SM64_ImportLevel) + #bpy.utils.unregister_class(SM64_ExportLevel) \ No newline at end of file diff --git a/fast64_internal/__init__.py b/fast64_internal/__init__.py new file mode 100644 index 0000000..efebcba --- /dev/null +++ b/fast64_internal/__init__.py @@ -0,0 +1,10 @@ +from .f3d_parser import * +from .sm64_geolayout_writer import * +from .sm64_geolayout_parser import * +from .sm64_level_parser import * +from .sm64_constants import * +from .sm64_rom_tweaks import * +from .sm64_anim import * +from .f3d_material import * +from .sm64_geolayout_bone import * +from .sm64_collision import * \ No newline at end of file diff --git a/fast64_internal/__pycache__/__init__.cpython-37.pyc b/fast64_internal/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..0e9f1b1 Binary files /dev/null and b/fast64_internal/__pycache__/__init__.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/f3d_enums.cpython-37.pyc b/fast64_internal/__pycache__/f3d_enums.cpython-37.pyc new file mode 100644 index 0000000..c2de369 Binary files /dev/null and b/fast64_internal/__pycache__/f3d_enums.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/f3d_gbi.cpython-37.pyc b/fast64_internal/__pycache__/f3d_gbi.cpython-37.pyc new file mode 100644 index 0000000..f85d7a5 Binary files /dev/null and b/fast64_internal/__pycache__/f3d_gbi.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/f3d_material.cpython-37.pyc b/fast64_internal/__pycache__/f3d_material.cpython-37.pyc new file mode 100644 index 0000000..c99c166 Binary files /dev/null and b/fast64_internal/__pycache__/f3d_material.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/f3d_parser.cpython-37.pyc b/fast64_internal/__pycache__/f3d_parser.cpython-37.pyc new file mode 100644 index 0000000..c08d0cd Binary files /dev/null and b/fast64_internal/__pycache__/f3d_parser.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/f3d_writer.cpython-37.pyc b/fast64_internal/__pycache__/f3d_writer.cpython-37.pyc new file mode 100644 index 0000000..0c6bd9d Binary files /dev/null and b/fast64_internal/__pycache__/f3d_writer.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_anim.cpython-37.pyc b/fast64_internal/__pycache__/sm64_anim.cpython-37.pyc new file mode 100644 index 0000000..d980dff Binary files /dev/null and b/fast64_internal/__pycache__/sm64_anim.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_constants.cpython-37.pyc b/fast64_internal/__pycache__/sm64_constants.cpython-37.pyc new file mode 100644 index 0000000..fe6d9cf Binary files /dev/null and b/fast64_internal/__pycache__/sm64_constants.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_data.cpython-36.pyc b/fast64_internal/__pycache__/sm64_data.cpython-36.pyc new file mode 100644 index 0000000..cded075 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_data.cpython-36.pyc differ diff --git a/fast64_internal/__pycache__/sm64_geolayout_bone.cpython-37.pyc b/fast64_internal/__pycache__/sm64_geolayout_bone.cpython-37.pyc new file mode 100644 index 0000000..8d880f4 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_geolayout_bone.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_geolayout_constants.cpython-37.pyc b/fast64_internal/__pycache__/sm64_geolayout_constants.cpython-37.pyc new file mode 100644 index 0000000..d6e4721 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_geolayout_constants.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_geolayout_parser.cpython-37.pyc b/fast64_internal/__pycache__/sm64_geolayout_parser.cpython-37.pyc new file mode 100644 index 0000000..6ec71a7 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_geolayout_parser.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_geolayout_utility.cpython-37.pyc b/fast64_internal/__pycache__/sm64_geolayout_utility.cpython-37.pyc new file mode 100644 index 0000000..4707515 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_geolayout_utility.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_geolayout_writer.cpython-37.pyc b/fast64_internal/__pycache__/sm64_geolayout_writer.cpython-37.pyc new file mode 100644 index 0000000..e09fb39 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_geolayout_writer.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_level_constants.cpython-37.pyc b/fast64_internal/__pycache__/sm64_level_constants.cpython-37.pyc new file mode 100644 index 0000000..30d8e15 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_level_constants.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_level_parser.cpython-37.pyc b/fast64_internal/__pycache__/sm64_level_parser.cpython-37.pyc new file mode 100644 index 0000000..75ace3f Binary files /dev/null and b/fast64_internal/__pycache__/sm64_level_parser.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/sm64_rom_tweaks.cpython-37.pyc b/fast64_internal/__pycache__/sm64_rom_tweaks.cpython-37.pyc new file mode 100644 index 0000000..027ce26 Binary files /dev/null and b/fast64_internal/__pycache__/sm64_rom_tweaks.cpython-37.pyc differ diff --git a/fast64_internal/__pycache__/utility.cpython-37.pyc b/fast64_internal/__pycache__/utility.cpython-37.pyc new file mode 100644 index 0000000..2eb4fa3 Binary files /dev/null and b/fast64_internal/__pycache__/utility.cpython-37.pyc differ diff --git a/fast64_internal/f3d_enums.py b/fast64_internal/f3d_enums.py new file mode 100644 index 0000000..3e2c9ba --- /dev/null +++ b/fast64_internal/f3d_enums.py @@ -0,0 +1,287 @@ + +combiner_enums = { + 'Case A' : ( + ('COMBINED', 'Combined Color', 'Combined Color'), + ('TEXEL0', 'Texture 0', 'Texture 0'), + ('TEXEL1', 'Texture 1', 'Texture 1'), + ('PRIMITIVE', 'Primitive Color', 'Primitive Color'), + ('SHADE', 'Shade Color', 'Shade Color'), + ('ENVIRONMENT', 'Environment Color', 'Environment Color'), + ('1', '1', '1'), + ('NOISE', 'Noise', 'Noise'), + ('0', '0', '0'), + ), + + 'Case B' : ( + ('COMBINED', 'Combined Color', 'Combined Color'), + ('TEXEL0', 'Texture 0', 'Texture 0'), + ('TEXEL1', 'Texture 1', 'Texture 1'), + ('PRIMITIVE', 'Primitive Color', 'Primitive Color'), + ('SHADE', 'Shade Color', 'Shade Color'), + ('ENVIRONMENT', 'Environment Color', 'Environment Color'), + ('CENTER', 'Chroma Key Center', 'Chroma Key Center'), + ('K4', 'YUV Convert K4', 'YUV Convert K4'), + ('0', '0', '0') + ), + + 'Case C' : ( + ('COMBINED' , 'Combined Color', 'Combined Color'), + ('TEXEL0' , 'Texture 0', 'Texture 0'), + ('TEXEL1' , 'Texture 1', 'Texture 1'), + ('PRIMITIVE' , 'Primitive Color', 'Primitive Color'), + ('SHADE' , 'Shade Color', 'Shade Color'), + ('ENVIRONMENT' , 'Environment Color', 'Environment Color'), + ('SCALE' , 'Chroma Key Scale', 'Chroma Key Scale'), + ('COMBINED_ALPHA' , 'Combined Color Alpha', 'Combined Color Alpha'), + ('TEXEL0_ALPHA' , 'Texture 0 Alpha', 'Texture 0 Alpha'), + ('TEXEL1_ALPHA' , 'Texture 1 Alpha', 'Texture 1 Alpha'), + ('PRIMITIVE_ALPHA' , 'Primitive Color Alpha', 'Primitive Color Alpha'), + ('SHADE_ALPHA' , 'Shade Color Alpha', 'Shade Color Alpha'), + ('ENV_ALPHA' , 'Environment Color Alpha', 'Environment Color Alpha'), + ('LOD_FRACTION' , 'LOD Fraction', 'LOD Fraction'), + ('PRIM_LOD_FRAC' , 'Primitive LOD Fraction', 'Primitive LOD Fraction'), + ('K5' , 'YUV Convert K5', 'YUV Convert K5'), + ('0' , '0', '0'), + ), + + 'Case D' : ( + ('COMBINED', 'Combined Color', 'Combined Color'), + ('TEXEL0', 'Texture 0', 'Texture 0'), + ('TEXEL1', 'Texture 1', 'Texture 1'), + ('PRIMITIVE', 'Primitive Color', 'Primitive Color'), + ('SHADE', 'Shade Color', 'Shade Color'), + ('ENVIRONMENT', 'Environment Color', 'Environment Color'), + ('1', '1', '1'), + ('0', '0', '0'), + ), + + 'Case A Alpha' : ( + ('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'), + ('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'), + ('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'), + ('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'), + ('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'), + ('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'), + ('1', '1', '1'), + ('0', '0', '0'), + ), + + 'Case B Alpha' : ( + ('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'), + ('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'), + ('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'), + ('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'), + ('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'), + ('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'), + ('1', '1', '1'), + ('0', '0', '0'), + ), + + 'Case C Alpha' : ( + ('LOD_FRACTION', 'LOD Fraction', 'LOD Fraction'), + ('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'), + ('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'), + ('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'), + ('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'), + ('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'), + ('PRIM_LOD_FRAC', 'Primitive LOD Fraction', 'Primitive LOD Fraction'), + ('0', '0', '0'), + ), + + 'Case D Alpha' : ( + ('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'), + ('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'), + ('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'), + ('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'), + ('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'), + ('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'), + ('1', '1', '1'), + ('0', '0', '0'), + ), +} + +caseTemplateDict = { + 'Case A' : 'Fast3D_A', + 'Case B' : 'Fast3D_B', + 'Case C' : 'Fast3D_C', + 'Case D' : 'Fast3D_D', + 'Case A Alpha' : 'Fast3D_A_alpha', + 'Case B Alpha' : 'Fast3D_B_alpha', + 'Case C Alpha' : 'Fast3D_C_alpha', + 'Case D Alpha' : 'Fast3D_D_alpha', +} + +# hardware v2 +enumAlphaDither = [ + ('G_AD_PATTERN', 'Pattern', 'Pattern'), + ('G_AD_NOTPATTERN', 'NOT Pattern', 'NOT Pattern'), + ('G_AD_NOISE', 'Noise', 'Noise'), + ('G_AD_DISABLE', 'Disable', 'Disable'), +] + +# hardware v2 +enumRGBDither = [ + ('G_CD_MAGICSQ', 'Magic Square', 'Magic Square'), + ('G_CD_BAYER', 'Bayer', 'Bayer'), + ('G_CD_NOISE', 'Noise', 'Noise'), + ('G_CD_DISABLE', 'Disable', 'Disable'), + ('G_CD_ENABLE', 'Enable', 'Enable') +] + +enumCombKey = [ + ('G_CK_NONE', 'None', 'None'), + ('G_CK_KEY', 'Key', 'Key'), +] + +enumTextConv = [ + ('G_TC_CONV', 'Convert', 'Convert'), + ('G_TC_FILTCONV', 'Filter And Convert', 'Filter And Convert'), + ('G_TC_FILT', 'Filter', 'Filter') +] + +enumTextFilt = [ + ('G_TF_POINT', 'Point', 'Point'), + ('G_TF_AVERAGE', 'Average', 'Average'), + ('G_TF_BILERP', 'Bilinear', 'Bilinear'), +] + +enumTextLUT = [ + ('G_TT_NONE', 'None', 'None'), + ('G_TT_RGBA16', 'RGBA16', 'RGBA16'), + ('G_TT_IA16', 'IA16', 'IA16'), +] + +enumTextLOD = [ + ('G_TL_TILE', 'Tile', 'Tile'), + ('G_TL_LOD', 'LOD', 'LOD'), +] + +enumTextDetail = [ + ('G_TD_CLAMP', 'Clamp', 'Clamp'), + ('G_TD_SHARPEN', 'Sharpen', 'Sharpen'), + ('G_TD_DETAIL', 'Detail', 'Detail'), +] + +enumTextPersp = [ + ('G_TP_NONE', 'None', 'None'), + ('G_TP_PERSP', 'Perspective', 'Perspective'), +] + +enumCycleType = [ + ('G_CYC_1CYCLE', '1 Cycle', '1 Cycle'), + ('G_CYC_2CYCLE', '2 Cycle', '2 Cycle'), + ('G_CYC_COPY', 'Copy', 'Copy'), + ('G_CYC_FILL', 'Fill', 'Fill'), +] + +enumColorDither = [ + ('G_CD_DISABLE', 'Disable', 'Disable'), + ('G_CD_ENABLE', 'Enable', 'Enable') +] + +enumPipelineMode = [ + ('G_PM_1PRIMITIVE', '1 Primitive', '1 Primitive'), + ('G_PM_NPRIMITIVE', 'N Primitive', 'N Primitive') +] + +enumAlphaCompare = [ + ('G_AC_NONE', 'None', 'None'), + ('G_AC_THRESHOLD', 'Threshold', 'Threshold'), + ('G_AC_DITHER', 'Dither', 'Dither'), +] + +enumDepthSource = [ + ('G_ZS_PIXEL', 'Pixel', 'Pixel'), + ('G_ZS_PRIM', 'Primitive', 'Primitive'), +] + +enumCoverage = [ + ('CVG_DST_CLAMP', 'Clamp', 'Clamp'), + ('CVG_DST_WRAP', 'Wrap', 'Wrap'), + ('CVG_DST_FULL', 'Full', 'Full'), + ('CVG_DST_SAVE', 'Save', 'Save') +] + +enumZMode = [ + ('ZMODE_OPA', 'Opaque', 'Opaque'), + ('ZMODE_INTER', 'Interpenetrating', 'Interpenetrating'), + ('ZMODE_XLU', 'Transparent (XLU)', 'Transparent (XLU)'), + ('ZMODE_DEC', 'Decal', 'Decal') +] + +enumBlendColor = [ + ('G_BL_CLR_IN', 'Input', 'Input'), + ('G_BL_CLR_MEM', 'Memory', 'Memory'), + ('G_BL_CLR_BL', 'Blend', 'Blend'), + ('G_BL_CLR_FOG', 'Fog', 'Fog') +] + +enumBlendAlpha = [ + ('G_BL_A_IN', 'Input', 'Input'), + ('G_BL_A_FOG', 'Fog', 'Fog'), + ('G_BL_A_SHADE', 'Shade', 'Shade'), + ('G_BL_0', '0', '0') +] + +enumBlendMix = [ + ('G_BL_1MA', '1 - Source Alpha', '1 - Source Alpha'), + ('G_BL_A_MEM', 'Memory Alpha', 'Memory Alpha'), + ('G_BL_1', '1', '1'), + ('G_BL_0', '0', '0') +] + +enumTexFormat = [ + ('I4','Intensity 4-bit', 'Intensity 4-bit'), + ('I8','Intensity 8-bit', 'Intensity 8-bit'), + ('IA4','Intensity Alpha 4-bit', 'Intensity Alpha 4-bit'), + ('IA8','Intensity Alpha 8-bit', 'Intensity Alpha 8-bit'), + ('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'), + ('CI4','Color Index 4-bit', 'Color Index 4-bit'), + ('CI8','Color Index 8-bit', 'Color Index 8-bit'), + ('RGBA16','RGBA 16-bit', 'RGBA 16-bit'), + ('RGBA32','RGBA 32-bit', 'RGBA 32-bit'), + #('YUV16','YUV 16-bit', 'YUV 16-bit'), +] + +enumCIFormat = [ + ('RGBA16','RGBA 16-bit', 'RGBA 16-bit'), + ('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'), +] + +enumTexUV = [ + ('TEXEL0', 'Texture 0', 'Texture 0'), + ('TEXEL1', 'Texture 1', 'Texture 1'), +] + +texBitSize = { + 'I4' : 4, + 'IA4' : 4, + 'CI4' : 4, + 'I8' : 8, + 'IA8' : 8, + 'CI8' : 8, + 'RGBA16' : 16, + 'IA16' : 16, + 'YUV16' : 16, + 'RGBA32' : 32, +} + +# 512 words * 64 bits / n bitSize +maxTexelCount = { + 32 : 1024, + 16 : 2048, + 8 : 4096, + 4 : 8192, +} + +enumF3D = [ + #('F3D_GBI', 'Fast3D', "Fast3D"), + #('F3DEX_GBI', 'F3DEX', "F3DEX"), + #('F3DEX_GBI_2', 'F3DEX2', "F3DEX2"), + ('F3D', 'F3D', 'F3D'), + ('F3DEX/LX', 'F3DEX/LX', 'F3DEX/LX'), + ('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'), +] \ No newline at end of file diff --git a/fast64_internal/f3d_gbi.py b/fast64_internal/f3d_gbi.py new file mode 100644 index 0000000..92f1b05 --- /dev/null +++ b/fast64_internal/f3d_gbi.py @@ -0,0 +1,4311 @@ +# Macros are all copied over from gbi.h +from .utility import decodeSegmentedAddr, encodeSegmentedAddr, get64bitAlignedAddr +from math import ceil + +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, +} + +# tuple of max buffer size, max load count. +vertexBufferSize = { + 'F3D' : (16, 16), + 'F3DEX/LX' : (32, 32), + 'F3DLX.Rej' : (64, 32), + 'F3DLP.Rej' : (80, 32), + 'F3DEX2/LX2' : (32, 32), + 'F3DEX2.Rej/LX2.Rej' : (64, 64), +} + +class F3D: + 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 ValueError("Invalid F3D version " + F3D_VER + ".") + + 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._LANGUAGE_ASSEMBLY = _LANGUAGE_ASSEMBLY + + 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 + self.G_SETOTHERMODE_L = 0xe2 + self.G_RDPHALF_1 = 0xe1 + self.G_SPNOOP = 0xe0 + self.G_ENDDL = 0xdf + self.G_DL = 0xde + self.G_LOAD_UCODE = 0xdd + self.G_MOVEMEM = 0xdc + self.G_MOVEWORD = 0xdb + self.G_MTX = 0xda + self.G_GEOMETRYMODE = 0xd9 + 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 + self.G_CULLDL = 0x03 + self.G_BRANCH_Z = 0x04 + self.G_TRI1 = 0x05 + self.G_TRI2 = 0x06 + self.G_QUAD = 0x07 + self.G_LINE3D = 0x08 + + else: + # DMA commands + self.G_SPNOOP = 0 # handle 0 gracefully + self.G_MTX = 1 + self.G_RESERVED0 = 2 # not implemeted + self.G_MOVEMEM = 3 # move a block of memory (up to 4 words) to dmem + self.G_VTX = 4 + self.G_RESERVED1 = 5 # not implemeted + self.G_DL = 6 + self.G_RESERVED2 = 7 # not implemeted + self.G_RESERVED3 = 8 # not implemeted + self.G_SPRITE2D_BASE = 9 # sprite command + + # IMMEDIATE commands + self.G_IMMFIRST = -65 + self.G_TRI1 = (self.G_IMMFIRST-0) + self.G_CULLDL = (self.G_IMMFIRST-1) + self.G_POPMTX = (self.G_IMMFIRST-2) + self.G_MOVEWORD = (self.G_IMMFIRST-3) + self.G_TEXTURE = (self.G_IMMFIRST-4) + self.G_SETOTHERMODE_H = (self.G_IMMFIRST-5) + self.G_SETOTHERMODE_L = (self.G_IMMFIRST-6) + self.G_ENDDL = (self.G_IMMFIRST-7) + self.G_SETGEOMETRYMODE = (self.G_IMMFIRST-8) + self.G_CLEARGEOMETRYMODE = (self.G_IMMFIRST-9) + self.G_LINE3D = (self.G_IMMFIRST-10) + self.G_RDPHALF_1 = (self.G_IMMFIRST-11) + self.G_RDPHALF_2 = (self.G_IMMFIRST-12) + if F3DEX_GBI or F3DLP_GBI: + self.G_MODIFYVTX = (self.G_IMMFIRST-13) + self.G_TRI2 = (self.G_IMMFIRST-14) + self.G_BRANCH_Z = (self.G_IMMFIRST-15) + self.G_LOAD_UCODE = (self.G_IMMFIRST-16) + else: + self.G_RDPHALF_CONT = (self.G_IMMFIRST-13) + + # We are overloading 2 of the immediate commands + # to keep the byte alignment of dmem the same + + self.G_SPRITE2D_SCALEFLIP = (self.G_IMMFIRST-1) + self.G_SPRITE2D_DRAW = (self.G_IMMFIRST-2) + + # RDP commands + self.G_NOOP = 0xc0 + + # RDP commands + self.G_SETCIMG = 0xff # -1 + self.G_SETZIMG = 0xfe # -2 + self.G_SETTIMG = 0xfd # -3 + self.G_SETCOMBINE = 0xfc # -4 + self.G_SETENVCOLOR = 0xfb # -5 + self.G_SETPRIMCOLOR = 0xfa # -6 + self.G_SETBLENDCOLOR = 0xf9 # -7 + self.G_SETFOGCOLOR = 0xf8 # -8 + self.G_SETFILLCOLOR = 0xf7 # -9 + self.G_FILLRECT = 0xf6 # -10 + self.G_SETTILE = 0xf5 # -11 + self.G_LOADTILE = 0xf4 # -12 + self.G_LOADBLOCK = 0xf3 # -13 + self.G_SETTILESIZE = 0xf2 # -14 + self.G_LOADTLUT = 0xf0 # -16 + self.G_RDPSETOTHERMODE = 0xef # -17 + self.G_SETPRIMDEPTH = 0xee # -18 + self.G_SETSCISSOR = 0xed # -19 + self.G_SETCONVERT = 0xec # -20 + self.G_SETKEYR = 0xeb # -21 + self.G_SETKEYGB = 0xea # -22 + self.G_RDPFULLSYNC = 0xe9 # -23 + self.G_RDPTILESYNC = 0xe8 # -24 + self.G_RDPPIPESYNC = 0xe7 # -25 + self.G_RDPLOADSYNC = 0xe6 # -26 + self.G_TEXRECTFLIP = 0xe5 # -27 + self.G_TEXRECT = 0xe4 # -28 + + self.G_TRI_FILL = 0xc8 # fill triangle: 11001000 + self.G_TRI_SHADE = 0xcc # shade triangle: 11001100 + self.G_TRI_TXTR = 0xca # texture triangle: 11001010 + self.G_TRI_SHADE_TXTR = 0xce # shade, texture triangle: 11001110 + self.G_TRI_FILL_ZBUFF = 0xc9 # fill, zbuff triangle: 11001001 + self.G_TRI_SHADE_ZBUFF = 0xcd # shade, zbuff triangle: 11001101 + self.G_TRI_TXTR_ZBUFF = 0xcb # texture, zbuff triangle: 11001011 + self.G_TRI_SHADE_TXTR_ZBUFF=0xcf # shade, txtr, zbuff trngl: 11001111 + + # masks to build RDP triangle commands + self.G_RDP_TRI_FILL_MASK = 0x08 + self.G_RDP_TRI_SHADE_MASK = 0x04 + 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 + self.G_RDPCMDSIZ = 64 + + # Coordinate shift values, number of bits of fraction + self.G_TEXTURE_IMAGE_FRAC = 2 + self.G_TEXTURE_SCALE_FRAC = 16 + self.G_SCALE_FRAC = 8 + self.G_ROTATE_FRAC = 16 + + self.G_MAXFBZ = 0x3fff # 3b exp, 11b mantissa + + + # G_MTX: parameter flags + + if F3DEX_GBI_2: + self.G_MTX_MODELVIEW = 0x00 # matrix types + self.G_MTX_PROJECTION = 0x04 + self.G_MTX_MUL = 0x00 # concat or load + self.G_MTX_LOAD = 0x02 + self.G_MTX_NOPUSH = 0x00 # push or not + self.G_MTX_PUSH = 0x01 + else: + self.G_MTX_MODELVIEW = 0x00 # matrix types + self.G_MTX_PROJECTION = 0x01 + self.G_MTX_MUL = 0x00 # concat or load + self.G_MTX_LOAD = 0x02 + self.G_MTX_NOPUSH = 0x00 # push or not + self.G_MTX_PUSH = 0x04 + + self.G_ZBUFFER = 0x00000001 + self.G_SHADE = 0x00000004 # enable Gouraud interp + # rest of low byte reserved for setup ucode + if F3DEX_GBI_2: + self.G_TEXTURE_ENABLE = 0x00000000 # Ignored + self.G_SHADING_SMOOTH = 0x00200000 # flat or smooth shaded + self.G_CULL_FRONT = 0x00000200 + self.G_CULL_BACK = 0x00000400 + self.G_CULL_BOTH = 0x00000600 # To make code cleaner + else: + self.G_TEXTURE_ENABLE = 0x00000002 # Microcode use only + self.G_SHADING_SMOOTH = 0x00000200 # flat or smooth shaded + self.G_CULL_FRONT = 0x00001000 + self.G_CULL_BACK = 0x00002000 + self.G_CULL_BOTH = 0x00003000 # To make code cleaner + self.G_FOG = 0x00010000 + self.G_LIGHTING = 0x00020000 + self.G_TEXTURE_GEN = 0x00040000 + self.G_TEXTURE_GEN_LINEAR = 0x00080000 + self.G_LOD = 0x00100000 # NOT IMPLEMENTED + if F3DEX_GBI or F3DLP_GBI: + self.G_CLIPPING = 0x00800000 + else: + self.G_CLIPPING = 0x00000000 + + #if _LANGUAGE_ASSEMBLY: + 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) + self.G_TEXTURE_GEN_LINEAR_H = (self.G_TEXTURE_GEN_LINEAR/0x10000) + 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 + + self.G_TX_NOMIRROR = 0 + self.G_TX_WRAP = 0 + self.G_TX_MIRROR = 0x1 + self.G_TX_CLAMP = 0x2 + self.G_TX_NOMASK = 0 + self.G_TX_NOLOD = 0 + #endif + + self.G_TX_VARS = { + 'G_TX_NOMIRROR' : 0, + 'G_TX_WRAP' : 0, + 'G_TX_MIRROR' : 1, + 'G_TX_CLAMP' : 2, + 'G_TX_NOMASK' : 0, + 'G_TX_NOLOD' : 0, + } + + # G_SETIMG fmt: set image formats + self.G_IM_FMT_RGBA = 0 + self.G_IM_FMT_YUV = 1 + self.G_IM_FMT_CI = 2 + self.G_IM_FMT_IA = 3 + self.G_IM_FMT_I = 4 + + self.G_IM_FMT_VARS = { + '0' : 0, + 'G_IM_FMT_RGBA' : 0, + 'G_IM_FMT_YUV' : 1, + 'G_IM_FMT_CI' : 2, + 'G_IM_FMT_IA' : 3, + 'G_IM_FMT_I' : 4, + } + + # G_SETIMG siz: set image pixel size + self.G_IM_SIZ_4b = 0 + self.G_IM_SIZ_8b = 1 + self.G_IM_SIZ_16b = 2 + self.G_IM_SIZ_32b = 3 + self.G_IM_SIZ_DD = 5 + + self.G_IM_SIZ_4b_BYTES = 0 + self.G_IM_SIZ_4b_TILE_BYTES = self.G_IM_SIZ_4b_BYTES + self.G_IM_SIZ_4b_LINE_BYTES = self.G_IM_SIZ_4b_BYTES + + self.G_IM_SIZ_8b_BYTES = 1 + self.G_IM_SIZ_8b_TILE_BYTES = self.G_IM_SIZ_8b_BYTES + self.G_IM_SIZ_8b_LINE_BYTES = self.G_IM_SIZ_8b_BYTES + + self.G_IM_SIZ_16b_BYTES = 2 + self.G_IM_SIZ_16b_TILE_BYTES = self.G_IM_SIZ_16b_BYTES + self.G_IM_SIZ_16b_LINE_BYTES = self.G_IM_SIZ_16b_BYTES + + self.G_IM_SIZ_32b_BYTES = 4 + self.G_IM_SIZ_32b_TILE_BYTES = 2 + self.G_IM_SIZ_32b_LINE_BYTES = 2 + + self.G_IM_SIZ_4b_LOAD_BLOCK = self.G_IM_SIZ_16b + self.G_IM_SIZ_8b_LOAD_BLOCK = self.G_IM_SIZ_16b + self.G_IM_SIZ_16b_LOAD_BLOCK = self.G_IM_SIZ_16b + self.G_IM_SIZ_32b_LOAD_BLOCK = self.G_IM_SIZ_32b + + self.G_IM_SIZ_4b_SHIFT = 2 + self.G_IM_SIZ_8b_SHIFT = 1 + self.G_IM_SIZ_16b_SHIFT = 0 + self.G_IM_SIZ_32b_SHIFT = 0 + + self.G_IM_SIZ_4b_INCR = 3 + self.G_IM_SIZ_8b_INCR = 1 + self.G_IM_SIZ_16b_INCR = 0 + self.G_IM_SIZ_32b_INCR = 0 + + self.G_IM_SIZ_VARS = { + '0' : 0, + 'G_IM_SIZ_4b' : 0, + 'G_IM_SIZ_8b' : 1, + 'G_IM_SIZ_16b' : 2, + 'G_IM_SIZ_32b' : 3, + 'G_IM_SIZ_DD' : 5, + 'G_IM_SIZ_4b_BYTES' : 0, + 'G_IM_SIZ_4b_TILE_BYTES' : self.G_IM_SIZ_4b_BYTES, + 'G_IM_SIZ_4b_LINE_BYTES' : self.G_IM_SIZ_4b_BYTES, + 'G_IM_SIZ_8b_BYTES' : 1, + 'G_IM_SIZ_8b_TILE_BYTES' : self.G_IM_SIZ_8b_BYTES, + 'G_IM_SIZ_8b_LINE_BYTES' : self.G_IM_SIZ_8b_BYTES, + 'G_IM_SIZ_16b_BYTES' : 2, + 'G_IM_SIZ_16b_TILE_BYTES' : self.G_IM_SIZ_16b_BYTES, + 'G_IM_SIZ_16b_LINE_BYTES' : self.G_IM_SIZ_16b_BYTES, + 'G_IM_SIZ_32b_BYTES' : 4, + 'G_IM_SIZ_32b_TILE_BYTES' : 2, + 'G_IM_SIZ_32b_LINE_BYTES' : 2, + 'G_IM_SIZ_4b_LOAD_BLOCK' : self.G_IM_SIZ_16b, + 'G_IM_SIZ_8b_LOAD_BLOCK' : self.G_IM_SIZ_16b, + 'G_IM_SIZ_16b_LOAD_BLOCK' : self.G_IM_SIZ_16b, + 'G_IM_SIZ_32b_LOAD_BLOCK' : self.G_IM_SIZ_32b, + 'G_IM_SIZ_4b_SHIFT' : 2, + 'G_IM_SIZ_8b_SHIFT' : 1, + 'G_IM_SIZ_16b_SHIFT' : 0, + 'G_IM_SIZ_32b_SHIFT' : 0, + 'G_IM_SIZ_4b_INCR' : 3, + 'G_IM_SIZ_8b_INCR' : 1, + 'G_IM_SIZ_16b_INCR' : 0, + 'G_IM_SIZ_32b_INCR' : 0 + } + + # G_SETCOMBINE: color combine modes + + # Color combiner constants: + self.G_CCMUX_COMBINED = 0 + self.G_CCMUX_TEXEL0 = 1 + self.G_CCMUX_TEXEL1 = 2 + self.G_CCMUX_PRIMITIVE = 3 + self.G_CCMUX_SHADE = 4 + self.G_CCMUX_ENVIRONMENT = 5 + self.G_CCMUX_CENTER = 6 + self.G_CCMUX_SCALE = 6 + self.G_CCMUX_COMBINED_ALPHA = 7 + self.G_CCMUX_TEXEL0_ALPHA = 8 + self.G_CCMUX_TEXEL1_ALPHA = 9 + self.G_CCMUX_PRIMITIVE_ALPHA = 10 + self.G_CCMUX_SHADE_ALPHA = 11 + self.G_CCMUX_ENV_ALPHA = 12 + self.G_CCMUX_LOD_FRACTION = 13 + self.G_CCMUX_PRIM_LOD_FRAC = 14 + self.G_CCMUX_NOISE = 7 + self.G_CCMUX_K4 = 7 + self.G_CCMUX_K5 = 15 + self.G_CCMUX_1 = 6 + self.G_CCMUX_0 = 31 + + self.CCMUXDict = { + 'COMBINED' : 0, + 'TEXEL0' : 1, + 'TEXEL1' : 2, + 'PRIMITIVE' : 3, + 'SHADE' : 4, + 'ENVIRONMENT' : 5, + 'CENTER' : 6, + 'SCALE' : 6, + 'COMBINED_ALPHA' : 7, + 'TEXEL0_ALPHA' : 8, + 'TEXEL1_ALPHA' : 9, + 'PRIMITIVE_ALPHA' : 10, + 'SHADE_ALPHA' : 11, + 'ENV_ALPHA' : 12, + 'LOD_FRACTION' : 13, + 'PRIM_LOD_FRAC' : 14, + 'NOISE' : 7, + 'K4' : 7, + 'K5' : 15, + '1' : 6, + '0' : 31 + } + + self.ACMUXDict = { + 'COMBINED' : 0, + 'TEXEL0' : 1, + 'TEXEL1' : 2, + 'PRIMITIVE' : 3, + 'SHADE' : 4, + 'ENVIRONMENT' : 5, + 'LOD_FRACTION' : 0, + 'PRIM_LOD_FRAC' : 6, + '1' : 6, + '0' : 7, + } + + # Alpha combiner constants: + self.G_ACMUX_COMBINED = 0 + self.G_ACMUX_TEXEL0 = 1 + self.G_ACMUX_TEXEL1 = 2 + self.G_ACMUX_PRIMITIVE = 3 + self.G_ACMUX_SHADE = 4 + self.G_ACMUX_ENVIRONMENT = 5 + self.G_ACMUX_LOD_FRACTION = 0 + self.G_ACMUX_PRIM_LOD_FRAC = 6 + self.G_ACMUX_1 = 6 + self.G_ACMUX_0 = 7 + + # typical CC cycle 1 modes + self.G_CC_PRIMITIVE = '0', '0', '0', 'PRIMITIVE', '0', '0', '0', 'PRIMITIVE' + self.G_CC_SHADE = '0', '0', '0', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_MODULATEI = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'SHADE' + self.G_CC_MODULATEIA = 'TEXEL0', '0', 'SHADE', '0', 'TEXEL0', '0', 'SHADE', '0' + self.G_CC_MODULATEIDECALA = 'TEXEL0', '0', 'SHADE', '0', '0', '0', '0', 'TEXEL0' + self.G_CC_MODULATERGB = self.G_CC_MODULATEI + self.G_CC_MODULATERGBA = self.G_CC_MODULATEIA + self.G_CC_MODULATERGBDECALA = self.G_CC_MODULATEIDECALA + self.G_CC_MODULATEI_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE' + self.G_CC_MODULATEIA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', 'TEXEL0', '0', 'PRIMITIVE', '0' + self.G_CC_MODULATEIDECALA_PRIM = 'TEXEL0', '0', 'PRIMITIVE', '0', '0', '0', '0', 'TEXEL0' + self.G_CC_MODULATERGB_PRIM = self.G_CC_MODULATEI_PRIM + self.G_CC_MODULATERGBA_PRIM = self.G_CC_MODULATEIA_PRIM + self.G_CC_MODULATERGBDECALA_PRIM = self.G_CC_MODULATEIDECALA_PRIM + self.G_CC_DECALRGB = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'SHADE' + self.G_CC_DECALRGBA = '0', '0', '0', 'TEXEL0', '0', '0', '0', 'TEXEL0' + self.G_CC_BLENDI = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_BLENDIA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', 'TEXEL0', '0', 'SHADE', '0' + self.G_CC_BLENDIDECALA = 'ENVIRONMENT', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_BLENDRGBA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_BLENDRGBDECALA = 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_ADDRGB = '1', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_ADDRGBDECALA = '1', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_REFLECTRGB = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_REFLECTRGBDECALA = 'ENVIRONMENT', '0', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_HILITERGB = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_HILITERGBA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE' + self.G_CC_HILITERGBDECALA = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_SHADEDECALA = '0', '0', '0', 'SHADE', '0', '0', '0', 'TEXEL0' + self.G_CC_BLENDPE = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', 'TEXEL0', '0', 'SHADE', '0' + self.G_CC_BLENDPEDECALA = 'PRIMITIVE', 'ENVIRONMENT', 'TEXEL0', 'ENVIRONMENT', '0', '0', '0', 'TEXEL0' + + # oddball 'modes' + self._G_CC_BLENDPE = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', '0', 'SHADE', '0' + self._G_CC_BLENDPEDECALA = 'ENVIRONMENT', 'PRIMITIVE', 'TEXEL0', 'PRIMITIVE', '0', '0', '0', 'TEXEL0' + self._G_CC_TWOCOLORTEX = 'PRIMITIVE', 'SHADE', 'TEXEL0', 'SHADE', '0', '0', '0', 'SHADE' + # used for 1-cycle sparse 'mip-maps', primitive color has color of lowest 'LOD' + self._G_CC_SPARSEST = 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'PRIMITIVE', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0' + self.G_CC_TEMPLERP = 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'PRIM_LOD_FRAC', 'TEXEL0' + + # typical CC cycle 1 'modes', usually followed by other cycle 2 'modes' + self.G_CC_TRILERP = 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0', 'TEXEL1', 'TEXEL0', 'LOD_FRACTION', 'TEXEL0' + self.G_CC_INTERFERENCE = 'TEXEL0', '0', 'TEXEL1', '0', 'TEXEL0', '0', 'TEXEL1', '0' + + # One-cycle color convert 'operation' + self.G_CC_1CYUV2RGB = 'TEXEL0', 'K4', 'K5', 'TEXEL0', '0', '0', '0', 'SHADE' + + ''' + * NOTE: YUV2RGB expects TF step1 color conversion to occur in 2nd clock. + * 'Therefore', CC looks for step1 results in TEXEL1 + ''' + self.G_CC_YUV2RGB = 'TEXEL1', 'K4', 'K5', 'TEXEL1', '0', '0', '0', 0 + + # typical CC cycle 2 modes + self.G_CC_PASS2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'COMBINED' + self.G_CC_MODULATEI2 = 'COMBINED', '0', 'SHADE', '0', '0', '0', '0', 'SHADE' + self.G_CC_MODULATEIA2 = 'COMBINED', '0', 'SHADE', '0', 'COMBINED', '0', 'SHADE', '0' + self.G_CC_MODULATERGB2 = self.G_CC_MODULATEI2 + self.G_CC_MODULATERGBA2 = self.G_CC_MODULATEIA2 + self.G_CC_MODULATEI_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', '0', '0', '0', 'PRIMITIVE' + self.G_CC_MODULATEIA_PRIM2 = 'COMBINED', '0', 'PRIMITIVE', '0', 'COMBINED', '0', 'PRIMITIVE', '0' + self.G_CC_MODULATERGB_PRIM2 = self.G_CC_MODULATEI_PRIM2 + self.G_CC_MODULATERGBA_PRIM2 = self.G_CC_MODULATEIA_PRIM2 + self.G_CC_DECALRGB2 = '0', '0', '0', 'COMBINED', '0', '0', '0', 'SHADE' + ''' + * ? + self.G_CC_DECALRGBA2 = 'COMBINED', 'SHADE', 'COMBINED_ALPHA', 'SHADE', '0', '0', '0', 'SHADE' + ''' + self.G_CC_BLENDI2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', '0', '0', '0', 'SHADE' + self.G_CC_BLENDIA2 = 'ENVIRONMENT', 'SHADE', 'COMBINED', 'SHADE', 'COMBINED', '0', 'SHADE', '0' + self.G_CC_CHROMA_KEY2 = 'TEXEL0', 'CENTER', 'SCALE', '0', '0', '0', '0', '0' + self.G_CC_HILITERGB2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'SHADE' + self.G_CC_HILITERGBA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED' + self.G_CC_HILITERGBDECALA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'TEXEL0' + self.G_CC_HILITERGBPASSA2 = 'ENVIRONMENT', 'COMBINED', 'TEXEL0', 'COMBINED', '0', '0', '0', 'COMBINED' + + # G_SETOTHERMODE_L sft: shift count + + self.G_MDSFT_ALPHACOMPARE = G_MDSFT_ALPHACOMPARE = 0 + self.G_MDSFT_ZSRCSEL = G_MDSFT_ZSRCSEL = 2 + self.G_MDSFT_RENDERMODE = G_MDSFT_RENDERMODE = 3 + self.G_MDSFT_BLENDER = G_MDSFT_BLENDER = 16 + + + # G_SETOTHERMODE_H sft: shift count + + self.G_MDSFT_BLENDMASK = G_MDSFT_BLENDMASK = 0 # unsupported + self.G_MDSFT_ALPHADITHER = G_MDSFT_ALPHADITHER = 4 + self.G_MDSFT_RGBDITHER = G_MDSFT_RGBDITHER = 6 + + self.G_MDSFT_COMBKEY = G_MDSFT_COMBKEY = 8 + self.G_MDSFT_TEXTCONV = G_MDSFT_TEXTCONV = 9 + self.G_MDSFT_TEXTFILT = G_MDSFT_TEXTFILT = 12 + self.G_MDSFT_TEXTLUT = G_MDSFT_TEXTLUT = 14 + self.G_MDSFT_TEXTLOD = G_MDSFT_TEXTLOD = 16 + self.G_MDSFT_TEXTDETAIL = G_MDSFT_TEXTDETAIL = 17 + self.G_MDSFT_TEXTPERSP = G_MDSFT_TEXTPERSP = 19 + self.G_MDSFT_CYCLETYPE = G_MDSFT_CYCLETYPE = 20 + self.G_MDSFT_COLORDITHER = G_MDSFT_COLORDITHER = 22 # unsupported in HW 2.0 + self.G_MDSFT_PIPELINE = G_MDSFT_PIPELINE = 23 + + # G_SETOTHERMODE_H gPipelineMode + self.G_PM_1PRIMITIVE = (1 << G_MDSFT_PIPELINE) + self.G_PM_NPRIMITIVE = (0 << G_MDSFT_PIPELINE) + + # G_SETOTHERMODE_H gSetCycleType + self.G_CYC_1CYCLE = (0 << G_MDSFT_CYCLETYPE) + self.G_CYC_2CYCLE = (1 << G_MDSFT_CYCLETYPE) + self.G_CYC_COPY = (2 << G_MDSFT_CYCLETYPE) + self.G_CYC_FILL = (3 << G_MDSFT_CYCLETYPE) + + # G_SETOTHERMODE_H gSetTexturePersp + self.G_TP_NONE = (0 << G_MDSFT_TEXTPERSP) + self.G_TP_PERSP = (1 << G_MDSFT_TEXTPERSP) + + # G_SETOTHERMODE_H gSetTextureDetail + self.G_TD_CLAMP = (0 << G_MDSFT_TEXTDETAIL) + self.G_TD_SHARPEN = (1 << G_MDSFT_TEXTDETAIL) + self.G_TD_DETAIL = (2 << G_MDSFT_TEXTDETAIL) + + # G_SETOTHERMODE_H gSetTextureLOD + self.G_TL_TILE = (0 << G_MDSFT_TEXTLOD) + self.G_TL_LOD = (1 << G_MDSFT_TEXTLOD) + + # G_SETOTHERMODE_H gSetTextureLUT + self.G_TT_NONE = (0 << G_MDSFT_TEXTLUT) + self.G_TT_RGBA16 = (2 << G_MDSFT_TEXTLUT) + self.G_TT_IA16 = (3 << G_MDSFT_TEXTLUT) + + # G_SETOTHERMODE_H gSetTextureFilter + self.G_TF_POINT = (0 << G_MDSFT_TEXTFILT) + self.G_TF_AVERAGE = (3 << G_MDSFT_TEXTFILT) + self.G_TF_BILERP = (2 << G_MDSFT_TEXTFILT) + + # G_SETOTHERMODE_H gSetTextureConvert + self.G_TC_CONV = (0 << G_MDSFT_TEXTCONV) + self.G_TC_FILTCONV = (5 << G_MDSFT_TEXTCONV) + self.G_TC_FILT = (6 << G_MDSFT_TEXTCONV) + + # G_SETOTHERMODE_H gSetCombineKey + self.G_CK_NONE = (0 << G_MDSFT_COMBKEY) + self.G_CK_KEY = (1 << G_MDSFT_COMBKEY) + + # G_SETOTHERMODE_H gSetColorDither + 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) + + # G_SETOTHERMODE_H gSetAlphaDither + self.G_AD_PATTERN = (0 << G_MDSFT_ALPHADITHER) + self.G_AD_NOTPATTERN = (1 << G_MDSFT_ALPHADITHER) + self.G_AD_NOISE = (2 << G_MDSFT_ALPHADITHER) + self.G_AD_DISABLE = (3 << G_MDSFT_ALPHADITHER) + + # G_SETOTHERMODE_L gSetAlphaCompare + self.G_AC_NONE = (0 << G_MDSFT_ALPHACOMPARE) + self.G_AC_THRESHOLD = (1 << G_MDSFT_ALPHACOMPARE) + self.G_AC_DITHER = (3 << G_MDSFT_ALPHACOMPARE) + + # G_SETOTHERMODE_L gSetDepthSource + self.G_ZS_PIXEL = (0 << G_MDSFT_ZSRCSEL) + self.G_ZS_PRIM = (1 << G_MDSFT_ZSRCSEL) + + # G_SETOTHERMODE_L gSetRenderMode + self.AA_EN = AA_EN = 0x8 + self.Z_CMP = Z_CMP = 0x10 + self.Z_UPD = Z_UPD = 0x20 + self.IM_RD = IM_RD = 0x40 + self.CLR_ON_CVG = CLR_ON_CVG = 0x80 + self.CVG_DST_CLAMP = CVG_DST_CLAMP = 0 + self.CVG_DST_WRAP = CVG_DST_WRAP = 0x100 + self.CVG_DST_FULL = CVG_DST_FULL = 0x200 + self.CVG_DST_SAVE = CVG_DST_SAVE = 0x300 + self.ZMODE_OPA = ZMODE_OPA = 0 + self.ZMODE_INTER = ZMODE_INTER = 0x400 + self.ZMODE_XLU = ZMODE_XLU = 0x800 + self.ZMODE_DEC = ZMODE_DEC = 0xc00 + self.CVG_X_ALPHA = CVG_X_ALPHA = 0x1000 + self.ALPHA_CVG_SEL = ALPHA_CVG_SEL = 0x2000 + self.FORCE_BL = FORCE_BL = 0x4000 + self.TEX_EDGE = TEX_EDGE = 0x0000 # used to be 0x8000 + + self.G_BL_CLR_IN = G_BL_CLR_IN = 0 + self.G_BL_CLR_MEM = G_BL_CLR_MEM = 1 + self.G_BL_CLR_BL = G_BL_CLR_BL = 2 + self.G_BL_CLR_FOG = G_BL_CLR_FOG = 3 + self.G_BL_1MA = G_BL_1MA = 0 + self.G_BL_A_MEM = G_BL_A_MEM = 1 + self.G_BL_A_IN = G_BL_A_IN = 0 + self.G_BL_A_FOG = G_BL_A_FOG = 1 + self.G_BL_A_SHADE = G_BL_A_SHADE = 2 + self.G_BL_1 = G_BL_1 = 2 + self.G_BL_0 = G_BL_0 = 3 + + def GBL_c1(m1a, m1b, m2a, m2b): + return (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18 + def GBL_c2(m1a, m1b, m2a, m2b): + return (m1a) << 28 | (m1b) << 24 | (m2a) << 20 | (m2b) << 16 + + def RM_AA_ZB_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_RA_ZB_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_XLU_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_WRAP | CLR_ON_CVG | \ + FORCE_BL | ZMODE_XLU | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_OPA_DECAL(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_WRAP | ALPHA_CVG_SEL | \ + ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_RA_ZB_OPA_DECAL(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | CVG_DST_WRAP | ALPHA_CVG_SEL | \ + ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_XLU_DECAL(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_WRAP | CLR_ON_CVG | \ + FORCE_BL | ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_OPA_INTER(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + ALPHA_CVG_SEL | ZMODE_INTER | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_RA_ZB_OPA_INTER(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | CVG_DST_CLAMP | \ + ALPHA_CVG_SEL | ZMODE_INTER | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_XLU_INTER(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_WRAP | CLR_ON_CVG | \ + FORCE_BL | ZMODE_INTER | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_XLU_LINE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_CLAMP | CVG_X_ALPHA | \ + ALPHA_CVG_SEL | FORCE_BL | ZMODE_XLU | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_DEC_LINE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | IM_RD | CVG_DST_SAVE | CVG_X_ALPHA | \ + ALPHA_CVG_SEL | FORCE_BL | ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_TEX_EDGE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + CVG_X_ALPHA | ALPHA_CVG_SEL | ZMODE_OPA | TEX_EDGE | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_TEX_INTER(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + CVG_X_ALPHA | ALPHA_CVG_SEL | ZMODE_INTER | TEX_EDGE | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_SUB_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_FULL | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_ZB_PCL_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | self.G_AC_DITHER | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_OPA_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_TEX_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | \ + CVG_X_ALPHA | ALPHA_CVG_SEL | ZMODE_OPA | TEX_EDGE | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_ZB_SUB_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_FULL | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + + def RM_AA_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_RA_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_XLU_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_WRAP | CLR_ON_CVG | FORCE_BL | \ + ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_XLU_LINE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | CVG_X_ALPHA | \ + ALPHA_CVG_SEL | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_DEC_LINE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_FULL | CVG_X_ALPHA | \ + ALPHA_CVG_SEL | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_TEX_EDGE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | \ + CVG_X_ALPHA | ALPHA_CVG_SEL | ZMODE_OPA | TEX_EDGE | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_SUB_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_FULL | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_AA_PCL_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | self.G_AC_DITHER | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_OPA_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_TEX_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_CLAMP | \ + CVG_X_ALPHA | ALPHA_CVG_SEL | ZMODE_OPA | TEX_EDGE | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_AA_SUB_TERR(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return AA_EN | IM_RD | CVG_DST_FULL | \ + ZMODE_OPA | ALPHA_CVG_SEL | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + + def RM_ZB_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | Z_UPD | CVG_DST_FULL | ALPHA_CVG_SEL | \ + ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_ZB_XLU_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | IM_RD | CVG_DST_FULL | FORCE_BL | ZMODE_XLU | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_ZB_OPA_DECAL(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | CVG_DST_FULL | ALPHA_CVG_SEL | ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_A_MEM) + + def RM_ZB_XLU_DECAL(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | IM_RD | CVG_DST_FULL | FORCE_BL | ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_ZB_CLD_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | IM_RD | CVG_DST_SAVE | FORCE_BL | ZMODE_XLU | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_ZB_OVL_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | IM_RD | CVG_DST_SAVE | FORCE_BL | ZMODE_DEC | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_ZB_PCL_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return Z_CMP | Z_UPD | CVG_DST_FULL | ZMODE_OPA | \ + self.G_AC_DITHER | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + + def RM_OPA_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return CVG_DST_CLAMP | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + def RM_XLU_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return IM_RD | CVG_DST_FULL | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_TEX_EDGE(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return CVG_DST_CLAMP | CVG_X_ALPHA | ALPHA_CVG_SEL | FORCE_BL |\ + ZMODE_OPA | TEX_EDGE | AA_EN | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + def RM_CLD_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return IM_RD | CVG_DST_SAVE | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA) + + def RM_PCL_SURF(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return CVG_DST_FULL | FORCE_BL | ZMODE_OPA | \ + self.G_AC_DITHER | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + def RM_ADD(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return IM_RD | CVG_DST_SAVE | FORCE_BL | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_A_FOG, G_BL_CLR_MEM, G_BL_1) + + def RM_NOOP(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return func(0, 0, 0, 0) + + def RM_VISCVG(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return IM_RD | FORCE_BL | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_BL, G_BL_A_MEM) + + # for rendering to an 8-bit framebuffer + def RM_OPA_CI(clk): + func = GBL_c1 if clk == 1 else GBL_c2 + return CVG_DST_CLAMP | ZMODE_OPA | \ + func(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + self.G_RM_AA_ZB_OPA_SURF = RM_AA_ZB_OPA_SURF(1) + self.G_RM_AA_ZB_OPA_SURF2 = RM_AA_ZB_OPA_SURF(2) + self.G_RM_AA_ZB_XLU_SURF = RM_AA_ZB_XLU_SURF(1) + self.G_RM_AA_ZB_XLU_SURF2 = RM_AA_ZB_XLU_SURF(2) + self.G_RM_AA_ZB_OPA_DECAL = RM_AA_ZB_OPA_DECAL(1) + self.G_RM_AA_ZB_OPA_DECAL2 = RM_AA_ZB_OPA_DECAL(2) + self.G_RM_AA_ZB_XLU_DECAL = RM_AA_ZB_XLU_DECAL(1) + self.G_RM_AA_ZB_XLU_DECAL2 = RM_AA_ZB_XLU_DECAL(2) + self.G_RM_AA_ZB_OPA_INTER = RM_AA_ZB_OPA_INTER(1) + self.G_RM_AA_ZB_OPA_INTER2 = RM_AA_ZB_OPA_INTER(2) + self.G_RM_AA_ZB_XLU_INTER = RM_AA_ZB_XLU_INTER(1) + self.G_RM_AA_ZB_XLU_INTER2 = RM_AA_ZB_XLU_INTER(2) + self.G_RM_AA_ZB_XLU_LINE = RM_AA_ZB_XLU_LINE(1) + self.G_RM_AA_ZB_XLU_LINE2 = RM_AA_ZB_XLU_LINE(2) + self.G_RM_AA_ZB_DEC_LINE = RM_AA_ZB_DEC_LINE(1) + self.G_RM_AA_ZB_DEC_LINE2 = RM_AA_ZB_DEC_LINE(2) + self.G_RM_AA_ZB_TEX_EDGE = RM_AA_ZB_TEX_EDGE(1) + self.G_RM_AA_ZB_TEX_EDGE2 = RM_AA_ZB_TEX_EDGE(2) + self.G_RM_AA_ZB_TEX_INTER = RM_AA_ZB_TEX_INTER(1) + self.G_RM_AA_ZB_TEX_INTER2 = RM_AA_ZB_TEX_INTER(2) + self.G_RM_AA_ZB_SUB_SURF = RM_AA_ZB_SUB_SURF(1) + self.G_RM_AA_ZB_SUB_SURF2 = RM_AA_ZB_SUB_SURF(2) + self.G_RM_AA_ZB_PCL_SURF = RM_AA_ZB_PCL_SURF(1) + self.G_RM_AA_ZB_PCL_SURF2 = RM_AA_ZB_PCL_SURF(2) + self.G_RM_AA_ZB_OPA_TERR = RM_AA_ZB_OPA_TERR(1) + self.G_RM_AA_ZB_OPA_TERR2 = RM_AA_ZB_OPA_TERR(2) + self.G_RM_AA_ZB_TEX_TERR = RM_AA_ZB_TEX_TERR(1) + self.G_RM_AA_ZB_TEX_TERR2 = RM_AA_ZB_TEX_TERR(2) + self.G_RM_AA_ZB_SUB_TERR = RM_AA_ZB_SUB_TERR(1) + self.G_RM_AA_ZB_SUB_TERR2 = RM_AA_ZB_SUB_TERR(2) + + self.G_RM_RA_ZB_OPA_SURF = RM_RA_ZB_OPA_SURF(1) + self.G_RM_RA_ZB_OPA_SURF2 = RM_RA_ZB_OPA_SURF(2) + self.G_RM_RA_ZB_OPA_DECAL = RM_RA_ZB_OPA_DECAL(1) + self.G_RM_RA_ZB_OPA_DECAL2 = RM_RA_ZB_OPA_DECAL(2) + self.G_RM_RA_ZB_OPA_INTER = RM_RA_ZB_OPA_INTER(1) + self.G_RM_RA_ZB_OPA_INTER2 = RM_RA_ZB_OPA_INTER(2) + + self.G_RM_AA_OPA_SURF = RM_AA_OPA_SURF(1) + self.G_RM_AA_OPA_SURF2 = RM_AA_OPA_SURF(2) + self.G_RM_AA_XLU_SURF = RM_AA_XLU_SURF(1) + self.G_RM_AA_XLU_SURF2 = RM_AA_XLU_SURF(2) + self.G_RM_AA_XLU_LINE = RM_AA_XLU_LINE(1) + self.G_RM_AA_XLU_LINE2 = RM_AA_XLU_LINE(2) + self.G_RM_AA_DEC_LINE = RM_AA_DEC_LINE(1) + self.G_RM_AA_DEC_LINE2 = RM_AA_DEC_LINE(2) + self.G_RM_AA_TEX_EDGE = RM_AA_TEX_EDGE(1) + self.G_RM_AA_TEX_EDGE2 = RM_AA_TEX_EDGE(2) + self.G_RM_AA_SUB_SURF = RM_AA_SUB_SURF(1) + self.G_RM_AA_SUB_SURF2 = RM_AA_SUB_SURF(2) + self.G_RM_AA_PCL_SURF = RM_AA_PCL_SURF(1) + self.G_RM_AA_PCL_SURF2 = RM_AA_PCL_SURF(2) + self.G_RM_AA_OPA_TERR = RM_AA_OPA_TERR(1) + self.G_RM_AA_OPA_TERR2 = RM_AA_OPA_TERR(2) + self.G_RM_AA_TEX_TERR = RM_AA_TEX_TERR(1) + self.G_RM_AA_TEX_TERR2 = RM_AA_TEX_TERR(2) + self.G_RM_AA_SUB_TERR = RM_AA_SUB_TERR(1) + self.G_RM_AA_SUB_TERR2 = RM_AA_SUB_TERR(2) + + self.G_RM_RA_OPA_SURF = RM_RA_OPA_SURF(1) + self.G_RM_RA_OPA_SURF2 = RM_RA_OPA_SURF(2) + + self.G_RM_ZB_OPA_SURF = RM_ZB_OPA_SURF(1) + self.G_RM_ZB_OPA_SURF2 = RM_ZB_OPA_SURF(2) + self.G_RM_ZB_XLU_SURF = RM_ZB_XLU_SURF(1) + self.G_RM_ZB_XLU_SURF2 = RM_ZB_XLU_SURF(2) + self.G_RM_ZB_OPA_DECAL = RM_ZB_OPA_DECAL(1) + self.G_RM_ZB_OPA_DECAL2 = RM_ZB_OPA_DECAL(2) + self.G_RM_ZB_XLU_DECAL = RM_ZB_XLU_DECAL(1) + self.G_RM_ZB_XLU_DECAL2 = RM_ZB_XLU_DECAL(2) + self.G_RM_ZB_CLD_SURF = RM_ZB_CLD_SURF(1) + self.G_RM_ZB_CLD_SURF2 = RM_ZB_CLD_SURF(2) + self.G_RM_ZB_OVL_SURF = RM_ZB_OVL_SURF(1) + self.G_RM_ZB_OVL_SURF2 = RM_ZB_OVL_SURF(2) + self.G_RM_ZB_PCL_SURF = RM_ZB_PCL_SURF(1) + self.G_RM_ZB_PCL_SURF2 = RM_ZB_PCL_SURF(2) + + self.G_RM_OPA_SURF = RM_OPA_SURF(1) + self.G_RM_OPA_SURF2 = RM_OPA_SURF(2) + self.G_RM_XLU_SURF = RM_XLU_SURF(1) + self.G_RM_XLU_SURF2 = RM_XLU_SURF(2) + self.G_RM_CLD_SURF = RM_CLD_SURF(1) + self.G_RM_CLD_SURF2 = RM_CLD_SURF(2) + self.G_RM_TEX_EDGE = RM_TEX_EDGE(1) + self.G_RM_TEX_EDGE2 = RM_TEX_EDGE(2) + self.G_RM_PCL_SURF = RM_PCL_SURF(1) + self.G_RM_PCL_SURF2 = RM_PCL_SURF(2) + self.G_RM_ADD = RM_ADD(1) + self.G_RM_ADD2 = RM_ADD(2) + self.G_RM_NOOP = RM_NOOP(1) + self.G_RM_NOOP2 = RM_NOOP(2) + self.G_RM_VISCVG = RM_VISCVG(1) + self.G_RM_VISCVG2 = RM_VISCVG(2) + self.G_RM_OPA_CI = RM_OPA_CI(1) + self.G_RM_OPA_CI2 = RM_OPA_CI(2) + + + self.G_RM_FOG_SHADE_A = GBL_c1(G_BL_CLR_FOG, G_BL_A_SHADE, G_BL_CLR_IN, G_BL_1MA) + self.G_RM_FOG_PRIM_A = GBL_c1(G_BL_CLR_FOG, G_BL_A_FOG, G_BL_CLR_IN, G_BL_1MA) + self.G_RM_PASS = GBL_c1(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1) + + + # G_SETCONVERT: K0-5 + + self.G_CV_K0 = 175 + self.G_CV_K1 = -43 + self.G_CV_K2 = -89 + self.G_CV_K3 = 222 + self.G_CV_K4 = 114 + self.G_CV_K5 = 42 + + + # G_SETSCISSOR: interlace mode + + self.G_SC_NON_INTERLACE = 0 + self.G_SC_ODD_INTERLACE = 3 + self.G_SC_EVEN_INTERLACE = 2 + + # flags to inhibit pushing of the display list (on branch) + self.G_DL_PUSH = 0x00 + self.G_DL_NOPUSH = 0x01 + + # Some structs here + + self.G_MAXZ = 0x03ff # 10 bits of integer screen-Z precision + + # more structs here + + ''' + MOVEMEM indices + + Each of these indexes an entry in a dmem table + which points to a 1-4 word block of dmem in + which to store a 1-4 word DMA. + ''' + + if F3DEX_GBI_2: + # 0,4 are reserved by G_MTX + self.G_MV_MMTX = 2 + 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) + else: + self.G_MV_VIEWPORT = 0x80 + self.G_MV_LOOKATY = 0x82 + self.G_MV_LOOKATX = 0x84 + self.G_MV_L0 = 0x86 + self.G_MV_L1 = 0x88 + self.G_MV_L2 = 0x8a + self.G_MV_L3 = 0x8c + self.G_MV_L4 = 0x8e + self.G_MV_L5 = 0x90 + self.G_MV_L6 = 0x92 + self.G_MV_L7 = 0x94 + self.G_MV_TXTATT = 0x96 + self.G_MV_MATRIX_1 = 0x9e # NOTE: this is in moveword table + self.G_MV_MATRIX_2 = 0x98 + self.G_MV_MATRIX_3 = 0x9a + self.G_MV_MATRIX_4 = 0x9c + + ''' + MOVEWORD indices + + Each of these indexes an entry in a dmem table + which points to a word in dmem in dmem where + an immediate word will be stored. + ''' + + self.G_MW_MATRIX = 0x00 # NOTE: also used by movemem + self.G_MW_NUMLIGHT = 0x02 + 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 + + + # These are offsets from the address in the dmem table + + self.G_MWO_NUMLIGHT = 0x00 + self.G_MWO_CLIP_RNX = 0x04 + self.G_MWO_CLIP_RNY = 0x0c + self.G_MWO_CLIP_RPX = 0x14 + self.G_MWO_CLIP_RPY = 0x1c + self.G_MWO_SEGMENT_0 = 0x00 + self.G_MWO_SEGMENT_1 = 0x01 + self.G_MWO_SEGMENT_2 = 0x02 + self.G_MWO_SEGMENT_3 = 0x03 + self.G_MWO_SEGMENT_4 = 0x04 + self.G_MWO_SEGMENT_5 = 0x05 + self.G_MWO_SEGMENT_6 = 0x06 + self.G_MWO_SEGMENT_7 = 0x07 + self.G_MWO_SEGMENT_8 = 0x08 + self.G_MWO_SEGMENT_9 = 0x09 + self.G_MWO_SEGMENT_A = 0x0a + self.G_MWO_SEGMENT_B = 0x0b + self.G_MWO_SEGMENT_C = 0x0c + self.G_MWO_SEGMENT_D = 0x0d + self.G_MWO_SEGMENT_E = 0x0e + self.G_MWO_SEGMENT_F = 0x0f + self.G_MWO_FOG = 0x00 + self.G_MWO_aLIGHT_1 = 0x00 + self.G_MWO_bLIGHT_1 = 0x04 + + if F3DEX_GBI_2: + self.G_MWO_aLIGHT_2 = 0x18 + self.G_MWO_bLIGHT_2 = 0x1c + self.G_MWO_aLIGHT_3 = 0x30 + self.G_MWO_bLIGHT_3 = 0x34 + self.G_MWO_aLIGHT_4 = 0x48 + self.G_MWO_bLIGHT_4 = 0x4c + self.G_MWO_aLIGHT_5 = 0x60 + self.G_MWO_bLIGHT_5 = 0x64 + self.G_MWO_aLIGHT_6 = 0x78 + self.G_MWO_bLIGHT_6 = 0x7c + self.G_MWO_aLIGHT_7 = 0x90 + self.G_MWO_bLIGHT_7 = 0x94 + self.G_MWO_aLIGHT_8 = 0xa8 + self.G_MWO_bLIGHT_8 = 0xac + else: + self.G_MWO_aLIGHT_2 = 0x20 + self.G_MWO_bLIGHT_2 = 0x24 + self.G_MWO_aLIGHT_3 = 0x40 + self.G_MWO_bLIGHT_3 = 0x44 + self.G_MWO_aLIGHT_4 = 0x60 + self.G_MWO_bLIGHT_4 = 0x64 + self.G_MWO_aLIGHT_5 = 0x80 + self.G_MWO_bLIGHT_5 = 0x84 + self.G_MWO_aLIGHT_6 = 0xa0 + self.G_MWO_bLIGHT_6 = 0xa4 + self.G_MWO_aLIGHT_7 = 0xc0 + self.G_MWO_bLIGHT_7 = 0xc4 + 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 + self.G_MWO_POINT_RGBA = 0x10 + self.G_MWO_POINT_ST = 0x14 + self.G_MWO_POINT_XYSCREEN = 0x18 + self.G_MWO_POINT_ZSCREEN = 0x1c + + + # Texturing macros + + # These are also defined defined above for Sprite Microcode + self.G_TX_LOADTILE = 7 + self.G_TX_RENDERTILE = 0 + + self.G_TX_NOMIRROR = 0 + self.G_TX_WRAP = 0 + self.G_TX_MIRROR = 0x1 + self.G_TX_CLAMP = 0x2 + self.G_TX_NOMASK = 0 + self.G_TX_NOLOD = 0 + + ''' + Dxt is the inverse of the number of 64-bit words in a line of + the texture being loaded using the load_block command. If + there are any 1's to the right of the 11th fractional bit, + dxt should be rounded up. The following macros accomplish + this. The 4b macros are a special case since 4-bit textures + are loaded as 8-bit textures. Dxt is fixed point 1.11. RJM + ''' + self.G_TX_DXT_FRAC = 11 + + ''' + For RCP 2.0, the maximum number of texels that can be loaded + using a load_block command is 2048. In order to load the total + 4kB of Tmem, change the texel size when loading to be G_IM_SIZ_16b, + then change the tile to the proper texel size after the load. + The g*DPLoadTextureBlock macros already do this, so this change + will be transparent if you use these macros. If you use + the g*DPLoadBlock macros directly, you will need to handle this + tile manipulation yourself. RJM. + ''' + + if _HW_VERSION_1: + self.G_TX_LDBLK_MAX_TXL = 4095 + else: + self.G_TX_LDBLK_MAX_TXL = 2047 + + + # Clipping Macros + self.FR_NEG_FRUSTRATIO_1 = 0x00000001 + self.FR_POS_FRUSTRATIO_1 = 0x0000ffff + self.FR_NEG_FRUSTRATIO_2 = 0x00000002 + self.FR_POS_FRUSTRATIO_2 = 0x0000fffe + self.FR_NEG_FRUSTRATIO_3 = 0x00000003 + self.FR_POS_FRUSTRATIO_3 = 0x0000fffd + self.FR_NEG_FRUSTRATIO_4 = 0x00000004 + self.FR_POS_FRUSTRATIO_4 = 0x0000fffc + self.FR_NEG_FRUSTRATIO_5 = 0x00000005 + self.FR_POS_FRUSTRATIO_5 = 0x0000fffb + self.FR_NEG_FRUSTRATIO_6 = 0x00000006 + self.FR_POS_FRUSTRATIO_6 = 0x0000fffa + + self.G_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, + } + + # macros for command parsing + def GDMACMD(self, x): return (x) + def GIMMCMD(self, x): return (self.G_IMMFIRST-(x)) + def GRDPCMD(self, x): return (0xff-(x)) + + def GPACK_RGBA5551(self, r, g, b, a): + return ((((r)<<8) & 0xf800) | \ + (((g)<<3) & 0x7c0) | \ + (((b)>>2) & 0x3e) | ((a) & 0x1)) + def GPACK_ZDZ(self, z, dz): + return ((z) << 2 | (dz)) + + def TXL2WORDS(self, txls, b_txl): return int(max(1, ((txls)*(b_txl)/8))) + def CALC_DXT(self, width, b_txl): + return int(((1 << self.G_TX_DXT_FRAC) + self.TXL2WORDS(width, b_txl) -\ + 1) / self.TXL2WORDS(width, b_txl)) + + def TXL2WORDS_4b(self, txls): return int(max(1, ((txls)/16))) + def CALC_DXT_4b(self, width): + return int(((1 << self.G_TX_DXT_FRAC) + self.TXL2WORDS_4b(width) - 1)/\ + self.TXL2WORDS_4b(width)) + + def NUML(self, n): + 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 + else: raise ValueError('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 + else: raise ValueError('Invalid G_MWO_b value for lights: ' + n) + +def _SHIFTL(value, amount, mask): + return (int(value) & ((1 << mask) - 1)) << amount + +VTX_SIZE = 16 +GFX_SIZE = 8 +VP_SIZE = 8 +LIGHT_SIZE = 16 # 12, but padded to 64bit alignment +AMBIENT_SIZE = 8 +HILITE_SIZE = 16 + +class Vtx: + def __init__(self, position, uv, colorOrNormal): + self.position = position + self.uv = uv + self.colorOrNormal = colorOrNormal + + def to_binary(self): + return 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.uv[0].to_bytes(2, 'big', signed = False) +\ + self.uv[1].to_bytes(2, 'big', signed = False) +\ + bytearray(self.colorOrNormal) + + def to_c(self): + return '{{' + \ + '{' +\ + str(self.position[0]) + ', ' + \ + str(self.position[1]) + ', ' + \ + str(self.position[2]) + \ + '},' +\ + '0, ' + \ + '{' +\ + '0x' + format(self.uv[0], 'X') + ", " +\ + '0x' + format(self.uv[1], 'X') + \ + '},' +\ + '{' +\ + '0x' + format(self.colorOrNormal[0], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[1], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[2], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[3], 'X') + \ + '}' + '}}' + + def to_sm64_decomp_s(self): + return 'vertex ' + \ + str(self.position[0]) + ', ' + \ + str(self.position[1]) + ', ' + \ + str(self.position[2]) + ', ' + \ + '0, ' + \ + '0x' + format(self.uv[0], 'X') + ", " +\ + '0x' + format(self.uv[1], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[0], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[1], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[2], 'X') + ', ' + \ + '0x' + format(self.colorOrNormal[3], 'X') + +class VtxList: + def __init__(self, name): + self.vertices = [] + self.name = name + self.startAddress = 0 + + def set_addr(self, startAddress): + startAddress = get64bitAlignedAddr(startAddress) + self.startAddress = startAddress + print('VtxList ' + self.name + ': ' + str(startAddress) + \ + ', ' + str(self.size())) + return startAddress, startAddress + self.size() + + def save_binary(self, romfile): + romfile.seek(self.startAddress) + romfile.write(self.to_binary()) + + def size(self): + return len(self.vertices) * VTX_SIZE + + def to_binary(self): + data = bytearray(0) + for vert in self.vertices: + data.extend(vert.to_binary()) + return data + + def to_c(self): + data = 'static const Vtx ' + self.name + '[] = {\n' + for vert in self.vertices: + data += '\t' + vert.to_c() + ',\n' + data += '};\n' + return data + + def to_sm64_decomp_s(self): + data = self.name + ':\n' + for vert in self.vertices: + data += vert.to_sm64_decomp_s() + '\n' + return data + +class GfxList: + def __init__(self, name): + self.commands = [] + self.name = name + self.startAddress = 0 + + def set_addr(self, startAddress, f3d): + startAddress = get64bitAlignedAddr(startAddress) + self.startAddress = startAddress + print('GfxList ' + self.name + ': ' + str(startAddress) + \ + ', ' + str(self.size(f3d))) + return startAddress, startAddress + self.size(f3d) + + def save_binary(self, romfile, f3d, segments): + print('GfxList ' + self.name + ': ' + str(self.startAddress) + \ + ', ' + str(self.size(f3d))) + romfile.seek(self.startAddress) + romfile.write(self.to_binary(f3d, segments)) + + def size(self, f3d): + size = 0 + for command in self.commands: + size += command.size(f3d) + return size + + def to_binary(self, f3d, segments): + data = bytearray(0) + for command in self.commands: + data.extend(command.to_binary(f3d, segments)) + + return data + + def to_c(self, static = True): + if static: + data = 'const Gfx ' + self.name + '[] = {\n' + for command in self.commands: + data += '\t' + command.to_c(True) + ',\n' + data += '};\n' + else: + data = 'Gfx* ' + self.name + '(Gfx* glistp) {\n' + for command in self.commands: + data += '\t' + command.to_c(False) + ';\n' + data += '\treturn glistp;\n}\n' + return data + + def to_c_def(self): + return 'extern const Gfx ' + self.name + '[];\n' + + def to_sm64_decomp_s(self): + data = 'glabel ' + self.name + '\n' + for command in self.commands: + data += command.to_sm64_decomp_s() + '\n' + return data + +class FModel: + def __init__(self, f3dType, isHWv1): + # dict of light name : Lights + self.lights = {} + # dict of texture name : FImage + self.textures = {} + # dict of material: (FMaterial, (width, height)) + self.materials = {} + # dict of body part name : FMeshGroup + self.meshGroups = {} + # F3D library + self.f3d = F3D(f3dType, isHWv1) + + def set_addr(self, startAddress): + addrRange = (startAddress, startAddress) + startAddrSet = False + # Important to set mesh groups first, so that + # export address corrseponds to drawing start. + for name, meshGroup in self.meshGroups.items(): + addrRange = meshGroup.set_addr(addrRange[1], self.f3d) + if not startAddrSet: + startAddrSet = True + startAddress = addrRange[0] + for name, light in self.lights.items(): + addrRange = light.set_addr(addrRange[1]) + if not startAddrSet: + startAddrSet = True + startAddress = addrRange[0] + for name, texture in self.textures.items(): + addrRange = texture.set_addr(addrRange[1]) + if not startAddrSet: + startAddrSet = True + startAddress = addrRange[0] + for name, (material, texDimensions) in self.materials.items(): + addrRange = material.set_addr(addrRange[1], self.f3d) + if not startAddrSet: + startAddrSet = True + startAddress = addrRange[0] + return startAddress, addrRange[1] + + def save_binary(self, romfile, segments): + for name, light in self.lights.items(): + light.save_binary(romfile) + for name, texture in self.textures.items(): + texture.save_binary(romfile) + for name, (material, texDimensions) in self.materials.items(): + material.save_binary(romfile, self.f3d, segments) + for name, meshGroup in self.meshGroups.items(): + meshGroup.save_binary(romfile, self.f3d, segments) + + def to_c(self, static): + data = '' + for name, light in self.lights.items(): + data += light.to_c() + '\n' + for name, texture in self.textures.items(): + data += texture.to_c() + '\n' + for name, (material, texDimensions) in self.materials.items(): + data += material.to_c(static) + '\n' + for name, meshGroup in self.meshGroups.items(): + data += meshGroup.to_c(static) + '\n' + return data + + def to_c_def(self): + data = '' + for name, (material, texDimensions) in self.materials.items(): + data += material.to_c_def() + for name, meshGroup in self.meshGroups.items(): + data += meshGroup.to_c_def() + return data + '\n' + +class FMeshGroup: + def __init__(self, name, mesh, skinnedMesh): + self.name = name + # FMesh + self.mesh = mesh + # FMesh + self.skinnedMesh = skinnedMesh + + def set_addr(self, startAddress, f3d): + addrRange = (startAddress, startAddress) + if self.mesh is not None: + addrRange = self.mesh.set_addr(addrRange[1], f3d) + startAddress = addrRange[0] + if self.skinnedMesh is not None: + addrRange = self.skinnedMesh.set_addr(addrRange[1], f3d) + if self.mesh is None: + startAddress = addrRange[0] + return startAddress, addrRange[1] + + def save_binary(self, romfile, f3d, segments): + if self.mesh is not None: + self.mesh.save_binary(romfile, f3d, segments) + if self.skinnedMesh is not None: + self.skinnedMesh.save_binary(romfile, f3d, segments) + + def to_c(self, static): + data = "" + if self.mesh is not None: + data += self.mesh.to_c(static) + '\n' + if self.skinnedMesh is not None: + data += self.skinnedMesh.to_c(static) + '\n' + return data + + def to_c_def(self): + data = "" + if self.mesh is not None: + data += self.mesh.to_c_def() + if self.skinnedMesh is not None: + data += self.skinnedMesh.to_c_def() + return data + +class FMesh: + def __init__(self, name): + self.name = name + # GfxList + self.draw = GfxList(name) + # list of GfxList + self.triangleLists = [] + # VtxList + self.vertexList = VtxList(name + '_vtx') + # dict of (override Material, specified Material to override) : GfxList + self.drawMatOverrides = {} + + def tri_list_new(self): + triList = GfxList(self.name + '_tri_' + str(len(self.triangleLists))) + self.triangleLists.append(triList) + return triList + + def set_addr(self, startAddress, f3d): + addrRange = self.draw.set_addr(startAddress, f3d) + startAddress = addrRange[0] + for triangleList in self.triangleLists: + addrRange = triangleList.set_addr(addrRange[1], f3d) + addrRange = self.vertexList.set_addr(addrRange[1]) + for materialTuple, drawOverride in self.drawMatOverrides.items(): + addrRange = drawOverride.set_addr(addrRange[1], f3d) + return startAddress, addrRange[1] + + def save_binary(self, romfile, f3d, segments): + self.draw.save_binary(romfile, f3d, segments) + for triangleList in self.triangleLists: + triangleList.save_binary(romfile, f3d, segments) + self.vertexList.save_binary(romfile) + for materialTuple, drawOverride in self.drawMatOverrides.items(): + drawOverride.save_binary(romfile, f3d, segments) + + def to_c(self, static): + data = self.vertexList.to_c() + '\n' + for triangleList in self.triangleLists: + data += triangleList.to_c(static) + '\n' + data += self.draw.to_c(static) + '\n' + for materialTuple, drawOverride in self.drawMatOverrides.items(): + data += drawOverride.to_c(static) + '\n' + return data + + def to_c_def(self): + data = self.draw.to_c_def() + for triangleList in self.triangleLists: + data += triangleList.to_c_def() + return data + +class FMaterial: + def __init__(self, name): + self.material = GfxList('mat_' + name) + self.revert = GfxList('mat_revert_' + name) + + def set_addr(self, startAddress, f3d): + addrRange = self.material.set_addr(startAddress, f3d) + startAddress = addrRange[0] + if self.revert is not None: + addrRange = self.revert.set_addr(addrRange[1], f3d) + return startAddress, addrRange[1] + + def save_binary(self, romfile, f3d, segments): + self.material.save_binary(romfile, f3d, segments) + if self.revert is not None: + self.revert.save_binary(romfile, f3d, segments) + + def to_c(self, static): + data = self.material.to_c(static) + '\n' + if self.revert is not None: + data += self.revert.to_c(static) + '\n' + return data + + def to_c_def(self): + data = self.material.to_c_def() + if self.revert is not None: + data += self.revert.to_c_def() + return data + +# viewport +# NOTE: unfinished +class Vp: + def __init__(self, scale, translation): + self.startAddress = 0 + +class Light: + def __init__(self, color, normal): + self.color = color + self.normal = normal + + def to_binary(self): + return bytearray(self.color + [0x00] + self.color + [0x00] + \ + self.normal + [0x00] + [0x00] * 4) + + def to_c(self): + return \ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + ', ' +\ + '0x' + format(self.normal[0], 'X') + ', ' +\ + '0x' + format(self.normal[1], 'X') + ', ' +\ + '0x' + format(self.normal[2], 'X') + + def to_sm64_decomp_s(self): + return '.byte ' +\ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + ', ' +\ + '0x00, ' + \ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + ', ' +\ + '0x00 \n' +\ + '.byte ' + \ + '0x' + format(self.normal[0], 'X') + ', ' +\ + '0x' + format(self.normal[1], 'X') + ', ' +\ + '0x' + format(self.normal[2], 'X') + ', ' +\ + '0x00, ' + \ + '0x00, 0x00, 0x00, 0x00\n' + +class Ambient: + def __init__(self, color): + self.color = color + + def to_binary(self): + return bytearray(self.color + [0x00] + self.color + [0x00]) + + def to_c(self): + return \ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + + def to_sm64_decomp_s(self): + return '.byte ' +\ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + ', ' +\ + '0x00, ' + \ + '0x' + format(self.color[0], 'X') + ', ' +\ + '0x' + format(self.color[1], 'X') + ', ' +\ + '0x' + format(self.color[2], 'X') + ', ' +\ + '0x00\n' + +class Hilite: + def __init__(self, name, x1, y1, x2, y2): + self.name = name + self.startAddress = 0 + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + + def to_binary(self): + return self.x1.to_bytes(4, 'big') +\ + self.y1.to_bytes(4, 'big') +\ + self.x2.to_bytes(4, 'big') +\ + self.y2.to_bytes(4, 'big') + + def to_c(self): + return 'Hilite ' + self.name + ' = {' + \ + str(self.x1) + ', ' +\ + str(self.y1) + ', ' +\ + str(self.x2) + ', ' +\ + str(self.y2) + '}' + + def to_sm64_decomp_s(self): + return self.name + ':\n' + '.word ' +\ + str(self.x1) + ', ' +\ + str(self.y1) + ', ' +\ + str(self.x2) + ', ' +\ + str(self.y2) + '\n' + +class Lights: + def __init__(self, name): + self.name = name + self.startAddress = 0 + self.a = None + self.l = [] + + def set_addr(self, startAddress): + startAddress = get64bitAlignedAddr(startAddress) + self.startAddress = startAddress + print('Lights ' + self.name + ': ' + str(startAddress) + ', ' + str(self.size())) + return (startAddress, startAddress + self.size()) + + def save_binary(self, romfile): + romfile.seek(self.startAddress) + romfile.write(self.to_binary()) + + def size(self): + return max(len(self.l), 1) * LIGHT_SIZE + AMBIENT_SIZE + + def getLightPointer(self, i): + return self.startAddress + AMBIENT_SIZE + i * LIGHT_SIZE + + def getAmbientPointer(self): + return self.startAddress + + def to_binary(self): + data = self.a.to_binary() + if len(self.l) == 0: + data += Light([0,0,0],[0,0,0]).to_binary() + else: + for i in range(len(self.l)): + data += self.l[i].to_binary() + return data + + def to_c(self): + data = 'static const Lights' + str(len(self.l)) + " " + self.name + " = " +\ + 'gdSPDefLights' + str(len(self.l)) + '(\n' + data += '\t' + self.a.to_c() + for light in self.l: + data += ',\n\t' + light.to_c() + data += ');\n' + return data + + def to_sm64_decomp_s(self): + data = '.balign 8\n' + self.name + ':\n' + data += self.name + '_a:\n' + self.a.to_sm64_decomp_s() + '\n' + if len(self.l) == 0: + data += self.name + '_l0:\n' + \ + Light([0,0,0],[0,0,0]).to_sm64_decomp_s() + '\n' + else: + for i in range(len(self.l)): + data += self.name + '_l' + str(i) + ':\n' + \ + self.l[i].to_sm64_decomp_s() + '\n' + return data + +class LookAt: + def __init__(self, name): + self.name = name + self.startAddress = 0 + self.l = [] #2 lights + + def to_binary(self): + return self.l[0].to_binary() + self.l[1].to_binary() + + def to_c(self): + # {{}} => lookat, light array, + # {{}} => light, light_t + return 'LookAt ' + self.name + ' = {{' + \ + '{{' + \ + "{" + \ + str(self.l[0].color[0]) + ', ' + \ + str(self.l[0].color[1]) + ', ' + \ + str(self.l[0].color[2]) + \ + '}, 0, ' +\ + "{" + \ + str(self.l[0].normal[0]) + ', ' + \ + str(self.l[0].normal[1]) + ', ' + \ + str(self.l[0].normal[2]) + \ + '}, 0' +\ + '}}' + \ + '{{' + \ + "{" + \ + str(self.l[1].color[0]) + ', ' + \ + str(self.l[1].color[1]) + ', ' + \ + str(self.l[1].color[2]) + \ + '}, 0, ' +\ + "{" + \ + str(self.l[1].normal[0]) + ', ' + \ + str(self.l[1].normal[1]) + ', ' + \ + str(self.l[1].normal[2]) + \ + '}, 0' +\ + '}}' + '}}\n' + + def to_sm64_decomp_s(self): + data = '.balign 8\n' + self.name + ':\n' + data += self.name + ':\n' + data += self.l[0].to_sm64_decomp_s() + '\n' + data += self.l[1].to_sm64_decomp_s() + '\n' + return data + +# A palette is just a RGBA16 texture with width = 1. +class FImage: + def __init__(self, name, fmt, bitSize, width, height): + self.name = name + self.fmt = fmt + self.bitSize = bitSize + self.width = width + self.height = height + self.startAddress = 0 + self.data = bytearray(0) + + def size(self): + return len(self.data) + + def to_binary(self): + return self.data + + def to_c(self): + code = 'static const u8 ' + self.name + '[] = {\n\t' + counter = 0 + for i in range(len(self.data)): + code += '0x' + format(self.data[i], 'X') + ', ' + counter += 1 + if counter > 8: + code += '\n\t' + counter = 0 + code += '\n};\n' + return code + + + def set_addr(self, startAddress): + startAddress = get64bitAlignedAddr(startAddress) + self.startAddress = startAddress + print('Image ' + self.name + ': ' + str(startAddress) + \ + ', ' + str(self.size())) + return startAddress, startAddress + self.size() + + def save_binary(self, romfile): + romfile.seek(self.startAddress) + romfile.write(self.to_binary()) + +# second arg of Dma is a pointer. +def gsDma0p(c, s, l): + words = _SHIFTL(c, 24, 8) | _SHIFTL(l, 0, 24), int(s) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsDma1p(c, s, l, p): + words = _SHIFTL(c, 24, 8) | _SHIFTL(p, 16, 8) | \ + _SHIFTL(l, 0, 16), int(s) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsDma2p(c, adrs, length, idx, ofs): + words = _SHIFTL(c,24,8) | _SHIFTL((length-1)/8,19,5) | \ + _SHIFTL(ofs/8,8,8) | _SHIFTL(idx,0,8), int(adrs) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsSPNoOp(f3d): + return gsDma0p(f3d.G_SPNOOP, 0, 0) + +# TODO: Divide vertlist into sections +# Divide mesh drawing by materials into separate gfx +class SPVertex: + # v = seg pointer, n = count, v0 = ? + def __init__(self, vertList, offset, count, index): + self.vertList = vertList + self.offset = offset + self.count = count + self.index = index + + def to_binary(self, f3d, segments): + vertPtr = int.from_bytes(encodeSegmentedAddr( + self.vertList.startAddress + self.offset * VTX_SIZE, + segments), 'big') + + if f3d.F3DEX_GBI_2: + words = _SHIFTL(f3d.G_VTX, 24, 8) | _SHIFTL(self.count, 12, 8) | \ + _SHIFTL(self.index + self.count, 1, 7), vertPtr + + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + elif f3d.F3DEX_GBI or f3d.F3DLP_GBI: + return gsDma1p(f3d.G_VTX, vertPtr, \ + (self.count << 10) | (VTX_SIZE * self.count -1), \ + self.index * 2) + + else: + return gsDma1p(f3d.G_VTX, vertPtr, \ + VTX_SIZE * self.count, (self.count - 1) << 4 | \ + self.index) + + def to_c(self, static = True): + header = 'gsSPVertex(' if static else 'gSPVertex(glistp++, ' + return header + self.vertList.name + " + " + str(self.offset) + ", " + \ + str(self.count) + ', ' + str(self.index) + ')' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + return 'gsSPVertex ' + self.vertList.name + ", " + \ + str(self.count) + ', ' + str(self.index) + + def size(self, f3d): + return GFX_SIZE + +class SPViewport: + # v = seg pointer, n = count, v0 = ? + def __init__(self, viewport): + self.viewport = viewport + + def to_binary(self, f3d, segments): + vpPtr = int.from_bytes(encodeSegmentedAddr( + self.viewport.startAddress, segments), 'big') + + if f3d.F3DEX_GBI_2: + return gsDma2p(f3d.G_MOVEMEM, vpPtr, VP_SIZE, f3d.G_MV_VIEWPORT, 0) + else: + return gsDma1p(f3d.G_MOVEMEM, vpPtr, VP_SIZE, f3d.G_MV_VIEWPORT) + + def to_c(self, static = True): + header = 'gsSPViewport(' if static else 'gSPViewport(glistp++, ' + return header + '&' + self.viewport.name + ')' + + def to_sm64_decomp_s(self): + return 'gsSPViewport ' + self.viewport.name + + def size(self, f3d): + return GFX_SIZE + +class SPDisplayList: + def __init__(self, displayList): + self.displayList = displayList + + def to_binary(self, f3d, segments): + dlPtr = int.from_bytes(encodeSegmentedAddr( + self.displayList.startAddress, segments), 'big') + return gsDma1p(f3d.G_DL, dlPtr, 0, f3d.G_DL_PUSH) + + def to_c(self, static = True): + if static: + return 'gsSPDisplayList(' + self.displayList.name + ')' + else: + return 'glistp = ' + self.displayList.name + '(glistp)' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + return 'gsSPDisplayList ' + self.displayList.name + + def size(self, f3d): + return GFX_SIZE + +class SPBranchList: + def __init__(self, displayList): + self.displayList = displayList + + def to_binary(self, f3d, segments): + dlPtr = int.from_bytes(encodeSegmentedAddr( + self.displayList.startAddress, segments), 'big') + return gsDma1p(f3d.G_DL, dlPtr, 0, f3d.G_DL_NOPUSH) + + def to_c(self, static = True): + header = 'gsSPBranchList(' if static else 'gSPBranchList(glistp++, ' + return header + '&' + self.displayList.name + ')' + + def to_sm64_decomp_s(self): + return 'gsSPBranchList ' + self.displayList.name + + def size(self, f3d): + return GFX_SIZE + +# SPSprite2DBase + +# RSP short command (no DMA required) macros +def gsImmp0(c): + words = _SHIFTL((c), 24, 8), 0 + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsImmp1(c, p0): + words = _SHIFTL((c), 24, 8), int(p0) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsImmp2(c, p0, p1): + words = _SHIFTL((c), 24, 8), _SHIFTL((p0), 16, 16) | _SHIFTL((p1), 8, 8) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsImmp3(c, p0, p1, p2): + words = _SHIFTL((c), 24, 8), (_SHIFTL((p0), 16, 16) | \ + _SHIFTL((p1), 8, 8) | _SHIFTL((p2), 0, 8)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +# last arg of Immp21 is a pointer. +def gsImmp21(c, p0, p1, dat): + words = _SHIFTL((c), 24, 8) | _SHIFTL((p0), 8, 16) | _SHIFTL((p1), 0, 8),\ + int(dat) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsMoveWd(index, offset, data, f3d): + if f3d.F3DEX_GBI_2: + return gsDma1p(f3d.G_MOVEWORD, data, offset, index) + else: + return gsImmp21(f3d.G_MOVEWORD, data, offset, index) + +# SPSprite2DScaleFlip +# SPSprite2DDraw + +# Note: the SP1Triangle() and line macros multiply the vertex indices +# by 10, this is an optimization for the microcode. + +def _gsSP1Triangle_w1(v0, v1, v2): + return (_SHIFTL((v0)*2,16,8)|_SHIFTL((v1)*2,8,8)|_SHIFTL((v2)*2,0,8)) + +def _gsSP1Triangle_w1f(v0, v1, v2, flag, f3d): + if f3d.F3DLP_GBI or f3d.F3DEX_GBI: + if flag == 0: + return _gsSP1Triangle_w1(v0, v1, v2) + elif flag == 1: + return _gsSP1Triangle_w1(v1, v2, v0) + else: + return _gsSP1Triangle_w1(v2, v0, v1) + else: + return (_SHIFTL((flag), 24,8)|_SHIFTL((v0)*10,16,8)|\ + _SHIFTL((v1)*10, 8,8)|_SHIFTL((v2)*10, 0,8)) + +def _gsSPLine3D_w1(v0, v1, wd): + return (_SHIFTL((v0)*2,16,8)|_SHIFTL((v1)*2,8,8)|_SHIFTL((wd),0,8)) + +def _gsSPLine3D_w1f(v0, v1, wd, flag, f3d): + if f3d.F3DLP_GBI or f3d.F3DEX_GBI: + if flag == 0: + return _gsSPLine3D_w1(v0, v1, wd) + else: + return _gsSPLine3D_w1(v1, v0, wd) + else: + return (_SHIFTL((flag), 24,8)|_SHIFTL((v0)*10,16,8)| \ + _SHIFTL((v1)*10, 8,8)|_SHIFTL((wd),0,8)) + +def _gsSP1Quadrangle_w1f(v0, v1, v2, v3, flag): + if flag == 0: + return _gsSP1Triangle_w1(v0, v1, v2) + elif flag == 1: + return _gsSP1Triangle_w1(v1, v2, v3) + elif flag == 2: + return _gsSP1Triangle_w1(v2, v3, v0) + else: + return _gsSP1Triangle_w1(v3, v0, v1) + +def _gsSP1Quadrangle_w2f(v0, v1, v2, v3, flag): + if flag == 0: + return _gsSP1Triangle_w1(v0, v2, v3) + elif flag == 1: + return _gsSP1Triangle_w1(v1, v3, v0) + elif flag == 1: + return _gsSP1Triangle_w1(v2, v0, v1) + else: + return _gsSP1Triangle_w1(v3, v1, v2) + +class SP1Triangle: + def __init__(self, v0, v1, v2, flag): + self.v0 = v0 + self.v1 = v1 + self.v2 = v2 + self.flag = flag + + def to_binary(self, f3d, segments): + if f3d.F3DEX_GBI_2: + words = _SHIFTL(f3d.G_TRI1, 24, 8) | \ + _gsSP1Triangle_w1f(self.v0, self.v1, self.v2, + self.flag, f3d), 0 + else: + words = _SHIFTL(f3d.G_TRI1, 24, 8), _gsSP1Triangle_w1f( + self.v0, self.v1, self.v2, self.flag, f3d) + + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + + def to_c(self, static = True): + header = 'gsSP1Triangle(' if static else 'gSP1Triangle(glistp++, ' + return header + str(self.v0) + ', ' + str(self.v1) + ', ' + \ + str(self.v2) + ', ' + str(self.flag) + ')' + + def to_sm64_decomp_s(self): + return 'gsSP1Triangle ' + str(self.v0) + ', ' + str(self.v1) + \ + ', ' + str(self.v2) + ', ' + str(self.flag) + + def size(self, f3d): + return GFX_SIZE + +class SPLine3D: + def __init__(self, v0, v1,flag): + self.v0 = v0 + self.v1 = v1 + self.flag = flag + + def to_binary(self, f3d, segments): + if 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) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsSPLine3D(' if static else 'gSPLine3D(glistp++, ' + return header + str(self.v0) + ', ' + str(self.v1) + ', ' + \ + str(self.flag) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPLine3D ' + str(self.v0) + ', ' + str(self.v1) + \ + ', ' + str(self.flag) + + def size(self, f3d): + return GFX_SIZE + +class SPLineW3D: + def __init__(self, v0, v1, wd, flag): + self.v0 = v0 + self.v1 = v1 + self.wd = wd + self.flag = flag + + def to_binary(self, f3d, segments): + if 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) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsSPLineW3D(' if static else 'gSPLineW3D(glistp++, ' + return header + str(self.v0) + ', ' + str(self.v1) + ', ' + \ + str(self.wd) + ', ' + str(self.flag) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPLineW3D ' + str(self.v0) + ', ' + str(self.v1) + ', ' + \ + str(self.wd) + ', ' + str(self.flag) + + def size(self, f3d): + return GFX_SIZE + +# SP1Quadrangle + +class SP2Triangles: + def __init__(self, v00, v01, v02, flag0, v10, v11, v12, flag1): + self.v00 = v00 + self.v01 = v01 + self.v02 = v02 + self.flag0 = flag0 + self.v10 = v10 + self.v11 = v11 + self.v12 = v12 + self.flag1 = flag1 + + def to_binary(self, f3d, segments): + if f3d.F3DLP_GBI or f3d.F3DEX_GBI: + words = (_SHIFTL(f3d.G_TRI2, 24, 8) | \ + _gsSP1Triangle_w1f( + self.v00, self.v01, self.v02, self.flag0, f3d)), \ + _gsSP1Triangle_w1f( + self.v10, self.v11, self.v12, self.flag1, f3d) + else: + raise ValueError("SP2Triangles not available in Fast3D.") + + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + + def to_c(self, static = True): + header = 'gsSP2Triangles(' if static else 'gSP2Triangles(glistp++, ' + return header + str(self.v00) + ', ' + str(self.v01) + ', ' + \ + str(self.v02) + ', ' + str(self.flag0) + ', ' + str(self.v10) + \ + ', ' + str(self.v11) + ', ' + str(self.v12) + ', ' + \ + str(self.flag1) + ')' + + def to_sm64_decomp_s(self): + return 'gsSP2Triangles ' + str(self.v00) + ', ' + str(self.v01) + \ + ', ' + str(self.v02) + ', ' + str(self.flag0) + ', ' + \ + str(self.v10) + ', ' + str(self.v11) + ', ' + str(self.v12) + \ + ', ' + str(self.flag1) + + def size(self, f3d): + return GFX_SIZE + +class SPCullDisplayList: + def __init__(self, vstart, vend): + self.vstart = vstart + self.vend = vend + + def to_binary(self, f3d, segments): + if f3d.F3DLP_GBI or f3d.F3DEX_GBI: + words = _SHIFTL(f3d.G_CULLDL, 24, 8) | \ + _SHIFTL((self.vstart)*2, 0, 16), _SHIFTL((self.vend)*2, 0, 16) + else: + words = _SHIFTL(f3d.G_CULLDL, 24, 8) | ((0x0f & (self.vstart))*40),\ + ((0x0f & ((self.vend)+1))*40) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsSPCullDisplayList(' if static else \ + 'gSPCullDisplayList(glistp++, ' + return header + str(self.vstart) + ', ' + str(self.vend) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPCullDisplayList ' + str(self.vstart) + ', ' + str(self.vend) + + def size(self, f3d): + return GFX_SIZE + +class SPSegment: + def __init__(self, segment, base): + self.segment = segment + self.base = base + + def to_binary(self, f3d, segments): + return gsMoveWd(f3d.G_MW_SEGMENT, (self.segment)*4, self.base, f3d) + + def to_c(self, static = True): + header = 'gsSPSegment(' if static else 'gSPSegment(glistp++, ' + return header + str(self.segment) + ', ' + '0x' + \ + format(self.base, 'X') + ')' + + def to_sm64_decomp_s(self): + return 'gsSPSegment ' + str(self.segment) + ', 0x' + \ + format(self.base, 'X') + + def size(self, f3d): + return GFX_SIZE + +# SPClipRatio +# SPInsertMatrix +# SPForceMatrix + +class SPModifyVertex: + def __init__(self, vtx, where, val): + self.vtx = vtx + self.where = where + self.val = val + + def to_binary(self, f3d, segments): + if f3d.F3DLP_GBI or f3d.F3DEX_GBI: + words = _SHIFTL(f3d.G_MODIFYVTX,24,8) | \ + _SHIFTL((self.where),16,8) | \ + _SHIFTL((self.vtx)*2,0,16), self.val + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + else: + return gsMoveWd(f3d.G_MW_POINTS, (self.vtx)*40+(self.where), \ + self.val, f3d) + + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsSPModifyVertex(' if static else 'gSPMoidfyVertex(glistp++, ' + return header + str(self.vtx) + ', ' + str(self.where) + ', ' + \ + str(self.val) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPModifyVertex ' + str(self.vtx) + ', ' + \ + str(self.where) + ', ' + str(self.val) + + def size(self, f3d): + return GFX_SIZE + +# LOD commands? +# SPBranchLessZ +# SPBranchLessZraw + +# SPLoadUcode (RSP) + +# SPDma_io +# SPDmaRead +# SPDmaRead +# SPDmaWrite +# SPDmaWrite + +class SPNumLights: + # n is macro name (string) + def __init__(self, n): + self.n = n + + def to_binary(self, f3d, segments): + return gsMoveWd(f3d.G_MW_NUMLIGHT, f3d.G_MWO_NUMLIGHT, \ + f3d.NUML(self.n), f3d) + + def to_c(self, static = True): + header = 'gsSPNumLights(' if static else 'gSPNumLights(glistp++, ' + return header + str(self.n) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPNumLights ' + str(self.n) + + def size(self, f3d): + return GFX_SIZE + +class SPLight: + # n is macro name (string) + def __init__(self, light, n): + self.light = light # start address of light + self.n = n + + def to_binary(self, f3d, segments): + lightPtr = int.from_bytes(encodeSegmentedAddr( + self.light, segments), 'big') + if f3d.F3DEX_GBI_2: + data = gsDma2p(f3d.G_MOVEMEM, lightPtr, LIGHT_SIZE, \ + f3d.G_MV_LIGHT, lightIndex[self.n] * 24 + 24) + else: + data = gsDma1p(f3d.G_MOVEMEM, lightPtr, LIGHT_SIZE, \ + (lightIndex[self.n]-1) * 2 + f3d.G_MV_L0) + return data + + def to_c(self, static = True): + header = 'gsSPLight(' if static else 'gSPLight(glistp++, ' + return header + self.light.name + ', ' + str(self.n) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPLight ' + self.light.name + ', ' + str(self.n) + + def size(self, f3d): + return GFX_SIZE + +class SPLightColor: + # n is macro name (string) + def __init__(self, n, col): + self.n = n + self.col = col + + def to_binary(self, f3d, segments): + 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) + + def to_c(self, static = True): + header = 'gsSPLightColor(' if static else 'gSPLightColor(glistp++, ' + return header + str(self.n) + ', 0x' + format(self.col, '08X') + ')' + + def to_sm64_decomp_s(self): + return 'gsSPLightColor ' + str(self.n) + ', 0x' + \ + format(self.col, '08X') + + def size(self, f3d): + return GFX_SIZE + +class SPSetLights: + def __init__(self, lights): + self.lights = lights + + def to_binary(self, f3d, segments): + data = SPNumLights('NUMLIGHTS_' + str(len(self.lights.l))).to_binary( + f3d, segments) + if 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) + data += SPLight(self.lights.getAmbientPointer(), 'LIGHT_2' + ).to_binary(f3d, segments) + else: + for i in range(len(self.lights.l)): + data += SPLight(self.lights.getLightPointer(i), + 'LIGHT_' + str(i+1)).to_binary(f3d, segments) + data += SPLight(self.lights.getAmbientPointer(), + 'LIGHT_' + str(len(self.lights.l) + 1)).to_binary(f3d, segments) + 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++, ' + return header + self.lights.name + ')' + + def to_sm64_decomp_s(self): + return 'gsSPSetLights ' + self.lights.name + + def size(self, f3d): + return GFX_SIZE * (2 + max(len(self.lights.l), 1)) + +# Reflection/Hiliting Macros + +def gsSPLookAtX(l, f3d): + if f3d.F3DEX_GBI_2: + return gsDma2p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LIGHT, + f3d.G_MVO_LOOKATX) + else: + return gsDma1p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LOOKATX) + +def gsSPLookAtY(l, f3d): + if f3d.F3DEX_GBI_2: + return gsDma2p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LIGHT, + f3d.G_MVO_LOOKATY) + else: + return gsDma1p(f3d.G_MOVEMEM, l, LIGHT_SIZE, f3d.G_MV_LOOKATY) + +class SPLookAt: + def __init__(self, la): + self.la = la + + def to_binary(self, f3d, segments): + light0Ptr = int.from_bytes(encodeSegmentedAddr( + self.la.startAddress, segments), 'big') + return gsSPLookAtX(light0Ptr, f3d) + gsSPLookAtY(light0Ptr + 16, f3d) + + def to_c(self, static = True): + header = 'gsSPLookAt(' if static else 'gSPLookAt(glistp++, ' + return header + '&' + self.la.name + ')' + + def to_sm64_decomp_s(self): + return 'gsSPLookAt ' + self.la.name + + def size(self, f3d): + return GFX_SIZE + +class DPSetHilite1Tile: + def __init__(self, tile, hilite, width, height): + self.tile = tile + self.hilite = hilite + self.width = width + self.height = height + + def to_binary(self, f3d, segments): + return DPSetTileSize(self.tile, + self.hilite.x1 & 0xfff, self.hilite.y1 & 0xfff, + ((self.width - 1) * 4 + self.hilite.x1) & 0xfff, + ((self.height - 1) * 4 + self.hilite.y1) & 0xfff).to_binary( + f3d,segments) + + def to_c(self, static = True): + header = 'gsDPSetHilite1Tile(' if static else \ + 'gDPSetHilite1Tile(glistp++, ' + return header + str(self.tile) + ', ' + '&' + self.hilite.name + \ + ', ' + str(self.width) + ', ' + str(self.height) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetHilite1Tile ' + str(self.tile) + ', ' + \ + self.hilite.name + ', ' + str(self.width) + ', ' + str(self.height) + + def size(self, f3d): + return GFX_SIZE + +class DPSetHilite2Tile: + def __init__(self, tile, hilite, width, height): + self.tile = tile + self.hilite = hilite + self.width = width + self.height = height + + def to_binary(self, f3d, segments): + return DPSetTileSize(self.tile, + self.hilite.x2 & 0xfff, self.hilite.y2 & 0xfff, + ((self.width - 1) * 4 + self.hilite.x2) & 0xfff, + ((self.height - 1) * 4 + self.hilite.y2) & 0xfff).to_binary( + f3d,segments) + + def to_c(self, static = True): + header = 'gsDPSetHilite2Tile(' if static else \ + 'gDPSetHilite2Tile(glistp++, ' + return header + str(self.tile) + ', ' + '&' + self.hilite.name + \ + ', ' + str(self.width) + ', ' + str(self.height) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetHilite2Tile ' + str(self.tile) + ', ' + \ + self.hilite.name + ', ' + str(self.width) + ', ' + str(self.height) + + def size(self, f3d): + return GFX_SIZE + +class SPFogFactor: + def __init__(self, fm, fo): + self.fm = fm + self.fo = fo + + def to_binary(self, f3d, segments): + return gsMoveWd(f3d.G_MW_FOG, f3d.G_MWO_FOG, \ + (_SHIFTL(self.fm,16,16) | _SHIFTL(self.fo,0,16)), f3d) + + def to_c(self, static = True): + header = 'gsSPFogFactor(' if static else 'gSPFogFactor(glistp++, ' + return header + str(self.fm) + ', ' + str(self.fo) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPFogFactor ' + str(self.fm) + ', ' + str(self.fo) + + def size(self, f3d): + return GFX_SIZE + +class SPFogPosition: + def __init__(self, minVal, maxVal): + self.minVal = minVal + self.maxVal = maxVal + + def to_binary(self, f3d, segments): + return gsMoveWd(f3d.G_MW_FOG, f3d.G_MWO_FOG,\ + (_SHIFTL((128000/((self.maxVal)-(self.minVal))),16,16) | \ + _SHIFTL(((500-(self.minVal))*256/((self.maxVal)-(self.minVal))), + 0,16)), f3d) + + def to_c(self, static = True): + header = 'gsSPFogPosition(' if static else 'gSPFogPosition(glistp++, ' + return header + str(self.minVal) + ', ' + str(self.maxVal) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPFogPosition ' + str(self.minVal) + ', ' + str(self.maxVal) + + def size(self, f3d): + return GFX_SIZE + +class SPTexture: + def __init__(self, s, t, level, tile, on): + self.s = s + self.t = t + self.level = level + self.tile = tile + self.on = on + + def to_binary(self, f3d, segments): + if f3d.F3DEX_GBI_2: + words = (_SHIFTL(f3d.G_TEXTURE,24,8) | \ + _SHIFTL(f3d.BOWTIE_VAL,16,8) | \ + _SHIFTL((self.level),11,3) | _SHIFTL((self.tile),8,3) | \ + _SHIFTL((self.on),1,7)), (_SHIFTL((self.s),16,16) | \ + _SHIFTL((self.t),0,16)) + 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)), (_SHIFTL((self.s),16,16) | \ + _SHIFTL((self.t),0,16)) + + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsSPTexture(' if static else 'gSPTexture(glistp++, ' + return header + str(self.s) + ', ' + str(self.t) + ', ' + \ + str(self.level) + ', ' + str(self.tile) + ', ' + str(self.on) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPTexture ' + str(self.s) + ', ' + str(self.t) + ', ' + \ + str(self.level) + ', ' + str(self.tile) + ', ' + str(self.on) + + def size(self, f3d): + return GFX_SIZE + +# SPTextureL + +class SPPerspNormalize: + def __init__(self, s): + self.s = s + + def to_binary(self, f3d, segments): + return gsMoveWd(f3d.G_MW_PERSPNORM, 0, (self.s), f3d) + + def to_c(self, static = True): + header = 'gsSPPerspNormalize(' if static else \ + 'gSPPerspNormalize(glistp++, ' + return header + str(self.s) + ')' + + def to_sm64_decomp_s(self): + return 'gsSPPerspNormalize ' + str(self.s) + + def size(self, f3d): + return GFX_SIZE + +# SPPopMatrixN +# SPPopMatrix + +class SPEndDisplayList: + def __init__(self): + pass + + 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 to_c(self, static = True): + return 'gsSPEndDisplayList()' if static else \ + 'gSPSPEndDisplayList(glistp++)' + + def to_sm64_decomp_s(self): + return 'gsSPEndDisplayList' + + def size(self, f3d): + return GFX_SIZE + +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') + +def gsSPGeometryMode_Non_F3DEX_GBI_2(word, f3d): + words = _SHIFTL(f3d.G_SETGEOMETRYMODE, 24, 8), word + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +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 + else: raise ValueError("Invalid geometry mode flag " + name) + + return word + +class SPSetGeometryMode: + def __init__(self, flagList): + self.flagList = flagList + + def to_binary(self, f3d, segments): + word = geoFlagListToWord(self.flagList, f3d) + if f3d.F3DEX_GBI_2: + return gsSPGeometryMode_F3DEX_GBI_2(0, word, f3d) + else: + words = _SHIFTL(f3d.G_SETGEOMETRYMODE, 24, 8), word + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + data = 'gsSPSetGeometryMode(' if static else \ + 'gSPSetGeometryMode(glistp++, ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + ')' + + def to_sm64_decomp_s(self): + data = 'gsSPSetGeometryMode ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + + def size(self, f3d): + return GFX_SIZE + +class SPClearGeometryMode: + def __init__(self, flagList): + self.flagList = flagList + + def to_binary(self, f3d, segments): + word = geoFlagListToWord(self.flagList, f3d) + if f3d.F3DEX_GBI_2: + return gsSPGeometryMode_F3DEX_GBI_2(word, 0, f3d) + else: + words = _SHIFTL(f3d.G_CLEARGEOMETRYMODE, 24, 8), word + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + data = 'gsSPClearGeometryMode(' if static else \ + 'gSPClearGeometryMode(glistp++, ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + ')' + + def to_sm64_decomp_s(self): + data = 'gsSPClearGeometryMode ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + + def size(self, f3d): + return GFX_SIZE + +class SPLoadGeometryMode: + def __init__(self, flagList): + self.flagList = flagList + + def to_binary(self, f3d, segments): + word = geoFlagListToWord(self.flagList, f3d) + if f3d.F3DEX_GBI_2: + return gsSPGeometryMode_F3DEX_GBI_2(-1, word, f3d) + else: + raise ValueError("LoadGeometryMode only available in F3DEX_GBI_2.") + + def to_c(self, static = True): + data = 'gsSPLoadGeometryMode(' if static else \ + 'gSPLoadGeometryMode(glistp++, ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + ')' + + def to_sm64_decomp_s(self): + data = 'gsSPLoadGeometryMode ' + for flag in self.flagList: + data += flag + ' | ' + return data[:-3] + + def size(self, f3d): + return GFX_SIZE + +def gsSPSetOtherMode(cmd, sft, length, data, f3d): + if f3d.F3DEX_GBI_2: + words = _SHIFTL(cmd,24,8) | _SHIFTL(32-(sft)-(length),8,8) | \ + _SHIFTL((length)-1,0,8), data + else: + words = _SHIFTL(cmd, 24, 8) | _SHIFTL(sft, 8, 8) | \ + _SHIFTL(length, 0, 8), (data) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +class DPPipelineMode: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_PM_1PRIMITIVE': modeVal = f3d.G_PM_1PRIMITIVE + elif self.mode == 'G_PM_NPRIMITIVE': modeVal = f3d.G_PM_NPRIMITIVE + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_PIPELINE, 1, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPPipelineMode(' if static else \ + 'gsDPPipelineMode(glistp++, ' + return header + self.mode + ')' + + + def to_sm64_decomp_s(self): + return 'gsDPPipelineMode ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetCycleType: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_CYC_1CYCLE': modeVal = f3d.G_CYC_1CYCLE + elif self.mode == 'G_CYC_2CYCLE': modeVal = f3d.G_CYC_2CYCLE + elif self.mode == 'G_CYC_COPY': modeVal = f3d.G_CYC_COPY + elif self.mode == 'G_CYC_FILL': modeVal = f3d.G_CYC_FILL + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_CYCLETYPE, 2, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetCycleType(' if static else \ + 'gsDPSetCycleType(glistp++, ' + return header + self.mode + ')' + + + def to_sm64_decomp_s(self): + return 'gsDPSetCycleType ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTexturePersp: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TP_NONE': modeVal = f3d.G_TP_NONE + elif self.mode == 'G_TP_PERSP': modeVal = f3d.G_TP_PERSP + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTPERSP, 1, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTexturePersp(' if static else \ + 'gsDPSetTexturePersp(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTexturePersp ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTextureDetail: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TD_CLAMP': modeVal = f3d.G_TD_CLAMP + elif self.mode == 'G_TD_SHARPEN': modeVal = f3d.G_TD_SHARPEN + elif self.mode == 'G_TD_DETAIL': modeVal = f3d.G_TD_DETAIL + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTDETAIL, 2, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTextureDetail(' if static else \ + 'gsDPSetTextureDetail(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureDetail ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTextureLOD: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TL_TILE': modeVal = f3d.G_TL_TILE + elif self.mode == 'G_TL_LOD': modeVal = f3d.G_TL_LOD + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTLOD, 1, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTextureLOD(' if static else \ + 'gsDPSetTextureLOD(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureLOD ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTextureLUT: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TT_NONE': modeVal = f3d.G_TT_NONE + elif self.mode == 'G_TT_RGBA16': modeVal = f3d.G_TT_RGBA16 + elif self.mode == 'G_TT_IA16': modeVal = f3d.G_TT_IA16 + else: print("Invalid LUT mode " + str(self.mode)) + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTLUT, 2, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTextureLUT(' if static else \ + 'gsDPSetTextureLUT(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureLUT ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTextureFilter: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TF_POINT': modeVal = f3d.G_TF_POINT + elif self.mode == 'G_TF_AVERAGE': modeVal = f3d.G_TF_AVERAGE + elif self.mode == 'G_TF_BILERP': modeVal = f3d.G_TF_BILERP + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTFILT, 2, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTextureFilter(' if static else \ + 'gsDPSetTextureFilter(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureFilter ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetTextureConvert: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_TC_CONV': modeVal = f3d.G_TC_CONV + elif self.mode == 'G_TC_FILTCONV': modeVal = f3d.G_TC_FILTCONV + elif self.mode == 'G_TC_FILT': modeVal = f3d.G_TC_FILT + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_TEXTCONV, 3, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetTextureConvert(' if static else \ + 'gsDPSetTextureConvert(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureConvert ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetCombineKey: + # mode is a string + def __init__(self, mode): + self.mode = mode + + def to_binary(self, f3d, segments): + if self.mode == 'G_CK_NONE': modeVal = f3d.G_CK_NONE + elif self.mode == 'G_CK_KEY': modeVal = f3d.G_CK_KEY + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_H, + f3d.G_MDSFT_COMBKEY, 1, modeVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetCombineKey(' if static else \ + 'gsDPSetCombineKey(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetCombineKey ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetColorDither: + # mode is a string + def __init__(self, mode): + self.mode = mode + + 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) + + def to_c(self, static = True): + header = 'gsDPSetColorDither(' if static else \ + 'gsDPSetColorDither(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetColorDither ' + self.mode + + def size(self, f3d): + return GFX_SIZE + +class DPSetAlphaDither: + # mode is a string + def __init__(self, mode): + self.mode = mode + + 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 ValueError("SetAlphaDither not available in HW v1.") + + def to_c(self, static = True): + header = 'gsDPSetAlphaDither(' if static else \ + 'gsDPSetAlphaDither(glistp++, ' + return header + self.mode + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetAlphaDither ' + self.mode + +class DPSetAlphaCompare: + # mask is a string + def __init__(self, mask): + self.mask = mask + + def to_binary(self, f3d, segments): + if self.mask == 'G_AC_NONE': maskVal = f3d.G_AC_NONE + elif self.mask == 'G_AC_THRESHOLD': maskVal = f3d.G_AC_THRESHOLD + elif self.mask == 'G_AC_DITHER': maskVal = f3d.G_AC_DITHER + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, + f3d.G_MDSFT_ALPHACOMPARE, 2, maskVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetAlphaCompare(' if static else \ + 'gsDPSetAlphaCompare(glistp++, ' + return header + self.mask + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetAlphaCompare ' + self.mask + + def size(self, f3d): + return GFX_SIZE + +class DPSetDepthSource: + # src is a string + def __init__(self, src): + self.src = src + + def to_binary(self, f3d, segments): + if self.src == 'G_ZS_PIXEL': srcVal = f3d.G_ZS_PIXEL + elif self.src == 'G_ZS_PRIM': srcVal = f3d.G_ZS_PRIM + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, + f3d.G_MDSFT_ZSRCSEL, 1, srcVal, f3d) + + def to_c(self, static = True): + header = 'gsDPSetDepthSource(' if static else \ + 'gsDPSetDepthSource(glistp++, ' + return header + self.src + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetDepthSource ' + self.src + + def size(self, f3d): + return GFX_SIZE + +def renderFlagListToWord(flagList, f3d): + word = 0 + for name in flagList: + if name == 'AA_EN': word += f3d.AA_EN + elif name == 'Z_CMP': word += f3d.Z_CMP + elif name == 'Z_UPD': word += f3d.Z_UPD + elif name == 'IM_RD': word += f3d.IM_RD + elif name == 'CLR_ON_CVG': word += f3d.CLR_ON_CVG + elif name == 'CVG_DST_CLAMP': word += f3d.CVG_DST_CLAMP + elif name == 'CVG_DST_WRAP': word += f3d.CVG_DST_WRAP + elif name == 'CVG_DST_FULL': word += f3d.CVG_DST_FULL + elif name == 'CVG_DST_SAVE': word += f3d.CVG_DST_SAVE + elif name == 'ZMODE_OPA': word += f3d.ZMODE_OPA + elif name == 'ZMODE_INTER': word += f3d.ZMODE_INTER + elif name == 'ZMODE_XLU': word += f3d.ZMODE_XLU + elif name == 'ZMODE_DEC': word += f3d.ZMODE_DEC + elif name == 'CVG_X_ALPHA': word += f3d.CVG_X_ALPHA + elif name == 'ALPHA_CVG_SEL': word += f3d.ALPHA_CVG_SEL + elif name == 'FORCE_BL': word += f3d.FORCE_BL + elif name == 'TEX_EDGE': word += f3d.TEX_EDGE + else: raise ValueError("Invalid render mode flag " + name) + + return word + +def GBL_c1(m1a, m1b, m2a, m2b): + return (m1a) << 30 | (m1b) << 26 | (m2a) << 22 | (m2b) << 18 +def GBL_c2(m1a, m1b, m2a, m2b): + return (m1a) << 28 | (m1b) << 24 | (m2a) << 20 | (m2b) << 16 + +class DPSetRenderMode: + # bl0-3 are string for each blender enum + def __init__(self, flagList, bl00, bl01, bl02, bl03, + bl10, bl11, bl12, bl13): + self.flagList = flagList + self.bl00 = bl00 + self.bl01 = bl01 + self.bl02 = bl02 + self.bl03 = bl03 + self.bl10 = bl10 + self.bl11 = bl11 + self.bl12 = bl12 + self.bl13 = bl13 + + def getGBL_c(self, f3d): + bl00 = getattr(f3d, self.bl00) + bl01 = getattr(f3d, self.bl01) + bl02 = getattr(f3d, self.bl02) + bl03 = getattr(f3d, self.bl03) + bl10 = getattr(f3d, self.bl10) + bl11 = getattr(f3d, self.bl11) + bl12 = getattr(f3d, self.bl12) + bl13 = getattr(f3d, self.bl13) + return GBL_c1(bl00, bl01, bl02, bl03) | \ + GBL_c2(bl10, bl11, bl12, bl13) + + def to_binary(self, f3d, segments): + flagWord = renderFlagListToWord(self.flagList, f3d) + return gsSPSetOtherMode(f3d.G_SETOTHERMODE_L, + f3d.G_MDSFT_RENDERMODE, 29, flagWord | self.getGBL_c(f3d), f3d) + + def to_c(self, static = True): + data = 'gsDPSetRenderMode(' if static else \ + 'gsDPSetRenderMode(glistp++, ' + data += 'GBL_c1(' + self.bl00 + ', ' + self.bl01 + ', ' + \ + self.bl02 + ', ' + self.bl03 + ') | GBL_c2(' + self.bl10 + \ + ', ' + self.bl11 + ', ' + self.bl12 + ', ' + self.bl13 + '), ' + for name in self.flagList: + data += name + ' | ' + return data[:-3] + ')' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + data = 'gsDPSetRenderMode ' + data += format(self.getGBL_c(f3d), 'X') + ', ' + for name in self.flagList: + data += name + ' | ' + return data[:-3] + + ''' + # G_SETOTHERMODE_L gSetRenderMode + self.AA_EN = AA_EN = 0x8 + self.Z_CMP = Z_CMP = 0x10 + self.Z_UPD = Z_UPD = 0x20 + self.IM_RD = IM_RD = 0x40 + self.CLR_ON_CVG = CLR_ON_CVG = 0x80 + self.CVG_DST_CLAMP = CVG_DST_CLAMP = 0 + self.CVG_DST_WRAP = CVG_DST_WRAP = 0x100 + self.CVG_DST_FULL = CVG_DST_FULL = 0x200 + self.CVG_DST_SAVE = CVG_DST_SAVE = 0x300 + self.ZMODE_OPA = ZMODE_OPA = 0 + self.ZMODE_INTER = ZMODE_INTER = 0x400 + self.ZMODE_XLU = ZMODE_XLU = 0x800 + self.ZMODE_DEC = ZMODE_DEC = 0xc00 + self.CVG_X_ALPHA = CVG_X_ALPHA = 0x1000 + self.ALPHA_CVG_SEL = ALPHA_CVG_SEL = 0x2000 + self.FORCE_BL = FORCE_BL = 0x4000 + self.TEX_EDGE = TEX_EDGE = 0x0000 # used to be 0x8000 + + self.G_BL_CLR_IN = G_BL_CLR_IN = 0 + self.G_BL_CLR_MEM = G_BL_CLR_MEM = 1 + self.G_BL_CLR_BL = G_BL_CLR_BL = 2 + self.G_BL_CLR_FOG = G_BL_CLR_FOG = 3 + self.G_BL_1MA = G_BL_1MA = 0 + self.G_BL_A_MEM = G_BL_A_MEM = 1 + self.G_BL_A_IN = G_BL_A_IN = 0 + self.G_BL_A_FOG = G_BL_A_FOG = 1 + self.G_BL_A_SHADE = G_BL_A_SHADE = 2 + self.G_BL_1 = G_BL_1 = 2 + self.G_BL_0 = G_BL_0 = 3 + ''' + + def size(self, f3d): + return GFX_SIZE + +def gsSetImage(cmd, fmt, siz, width, i): + words = _SHIFTL(cmd, 24, 8) | _SHIFTL(fmt, 21, 3) | \ + _SHIFTL(siz, 19, 2) | _SHIFTL((width)-1, 0, 12), i + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +# DPSetColorImage +# DPSetDepthImage + +class DPSetTextureImage: + def __init__(self, fmt, siz, width, img): + self.fmt = fmt # string + self.siz = siz # string + self.width = width + self.image = img + + def to_binary(self, f3d, segments): + fmt = f3d.G_IM_FMT_VARS[self.fmt] + siz = f3d.G_IM_SIZ_VARS[self.siz] + imagePtr = int.from_bytes(encodeSegmentedAddr( + self.image.startAddress, segments), 'big') + return gsSetImage(f3d.G_SETTIMG, fmt, siz, self.width, imagePtr) + + def to_c(self, static = True): + header = 'gsDPSetTextureImage(' if static else \ + 'gsDPSetTextureImage(glistp++, ' + return header + self.fmt + ', ' + self.siz + ', ' + \ + str(self.width) + ', ' + self.image.name + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetTextureImage ' + self.fmt + ', ' + self.siz + \ + ', ' + str(self.width) + ', ' + self.image.name + + def size(self, f3d): + return GFX_SIZE + +def gsDPSetCombine(muxs0, muxs1, f3d): + words = _SHIFTL(f3d.G_SETCOMBINE, 24, 8) | _SHIFTL(muxs0, 0, 24), muxs1 + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def GCCc0w0(saRGB0, mRGB0, saA0, mA0): + return (_SHIFTL((saRGB0), 20, 4) | _SHIFTL((mRGB0), 15, 5) | \ + _SHIFTL((saA0), 12, 3) | _SHIFTL((mA0), 9, 3)) + +def GCCc1w0(saRGB1, mRGB1): + return (_SHIFTL((saRGB1), 5, 4) | _SHIFTL((mRGB1), 0, 5)) + +def GCCc0w1(sbRGB0, aRGB0, sbA0, aA0): + return (_SHIFTL((sbRGB0), 28, 4) | _SHIFTL((aRGB0), 15, 3) |\ + _SHIFTL((sbA0), 12, 3) | _SHIFTL((aA0), 9, 3)) + +def GCCc1w1(sbRGB1, saA1, mA1, aRGB1, sbA1, aA1): + return (_SHIFTL((sbRGB1), 24, 4) | _SHIFTL((saA1), 21, 3) | \ + _SHIFTL((mA1), 18, 3) | _SHIFTL((aRGB1), 6, 3) | \ + _SHIFTL((sbA1), 3, 3) | _SHIFTL((aA1), 0, 3)) + +class DPSetCombineMode: + # all strings + def __init__(self, a0, b0, c0, d0, Aa0, Ab0, Ac0, Ad0, + a1, b1, c1, d1, Aa1, Ab1, Ac1, Ad1): + self.a0 = a0 + self.b0 = b0 + self.c0 = c0 + self.d0 = d0 + self.Aa0 = Aa0 + self.Ab0 = Ab0 + self.Ac0 = Ac0 + self.Ad0 = Ad0 + + self.a1 = a1 + self.b1 = b1 + self.c1 = c1 + self.d1 = d1 + self.Aa1 = Aa1 + self.Ab1 = Ab1 + self.Ac1 = Ac1 + self.Ad1 = Ad1 + + def to_binary(self, f3d, segments): + words = _SHIFTL(f3d.G_SETCOMBINE, 24, 8) | \ + _SHIFTL( + GCCc0w0( + f3d.CCMUXDict[self.a0], f3d.CCMUXDict[self.c0], \ + f3d.ACMUXDict[self.Aa0], f3d.ACMUXDict[self.Ac0]) | \ + GCCc1w0( + f3d.CCMUXDict[self.a1], f3d.CCMUXDict[self.c1]), \ + 0, 24),\ + GCCc0w1( + f3d.CCMUXDict[self.b0], f3d.CCMUXDict[self.d0], \ + f3d.ACMUXDict[self.Ab0], f3d.ACMUXDict[self.Ad0]) | \ + GCCc1w1( + f3d.CCMUXDict[self.b1], f3d.ACMUXDict[self.Aa1], \ + f3d.ACMUXDict[self.Ac1], f3d.CCMUXDict[self.d1], \ + f3d.ACMUXDict[self.Ab1], f3d.ACMUXDict[self.Ad1]) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + a0 = self.a0 # 'G_CCMUX_' + self.a0 + b0 = self.b0 # 'G_CCMUX_' + self.b0 + c0 = self.c0 # 'G_CCMUX_' + self.c0 + d0 = self.d0 # 'G_CCMUX_' + self.d0 + + Aa0 = self.Aa0 # 'G_ACMUX_' + self.Aa0 + Ab0 = self.Ab0 # 'G_ACMUX_' + self.Ab0 + Ac0 = self.Ac0 # 'G_ACMUX_' + self.Ac0 + Ad0 = self.Ad0 # 'G_ACMUX_' + self.Ad0 + + a1 = self.a1 # 'G_CCMUX_' + self.a1 + b1 = self.b1 # 'G_CCMUX_' + self.b1 + c1 = self.c1 # 'G_CCMUX_' + self.c1 + d1 = self.d1 # 'G_CCMUX_' + self.d1 + + Aa1 = self.Aa1 # 'G_ACMUX_' + self.Aa1 + Ab1 = self.Ab1 # 'G_ACMUX_' + self.Ab1 + Ac1 = self.Ac1 # 'G_ACMUX_' + self.Ac1 + Ad1 = self.Ad1 # 'G_ACMUX_' + self.Ad1 + + header = 'gsDPSetCombineLERP(' if static else \ + 'gDPSetCombineLERP(glistp++, ' + return header + a0 + ', ' + b0 + ', ' + c0 + ', ' + \ + d0 + ',\n\t\t' + Aa0 + ', ' + Ab0 + ', ' + \ + Ac0 + ', ' + Ad0 + ',\n\t\t' + a1 + ', ' + \ + b1 + ', ' + c1 + ', ' + d1 + ',\n\t\t' + Aa1 + \ + ', ' + Ab1 + ', ' + Ac1 + ', ' + Ad1 + ')' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + return 'gsDPSetCombineMode ' + a0 + ', ' + b0 + ', ' +\ + c0 + ', ' + d0 + ', ' + Aa0 + ', ' + \ + Ab0 + ', ' + Ac0 + ', ' + Ad0 + ', ' + \ + a1 + ', ' + b1 + ', ' + c1 + ', ' + d1 + \ + ', ' + Aa1 + ', ' + Ab1 + ', ' + Ac1 + \ + ', ' + Ad1 + + def size(self, f3d): + return GFX_SIZE + +def gsDPSetColor(c, d): + words = _SHIFTL(c, 24, 8), d + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def sDPRGBColor(cmd, r, g, b, a): + return gsDPSetColor(cmd, \ + (_SHIFTL(r, 24, 8) | _SHIFTL(g, 16, 8) | \ + _SHIFTL(b, 8, 8) | _SHIFTL(a, 0, 8))) + +class DPSetEnvColor: + def __init__(self, r, g, b, a): + self.r = r + self.g = g + self.b = b + self.a = a + + def to_binary(self, f3d, segments): + return sDPRGBColor(f3d.G_SETENVCOLOR, self.r, self.g, self.b, self.a) + + def to_c(self, static = True): + header = 'gsDPSetEnvColor(' if static else 'gDPSetEnvColor(glistp++, ' + return header + str(self.r) + ', ' + str(self.g) + ', ' + \ + str(self.b) + ', ' + str(self.a) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetEnvColor ' + str(self.r) + ', ' + str(self.g) + \ + ', ' + str(self.b) + ', ' + str(self.a) + + def size(self, f3d): + return GFX_SIZE + +class DPSetBlendColor: + def __init__(self, r, g, b, a): + self.r = r + self.g = g + self.b = b + self.a = a + + def to_binary(self, f3d, segments): + return sDPRGBColor(f3d.G_SETBLENDCOLOR, self.r, self.g, self.b, self.a) + + def to_c(self, static = True): + header = 'gsDPSetBlendColor(' if static else \ + 'gDPSetBlendColor(glistp++, ' + return header + str(self.r) + ', ' + str(self.g) + ', ' + \ + str(self.b) + ', ' + str(self.a) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetBlendColor ' + str(self.r) + ', ' + str(self.g) + \ + ', ' + str(self.b) + ', ' + str(self.a) + + def size(self, f3d): + return GFX_SIZE + +class DPSetFogColor: + def __init__(self, r, g, b, a): + self.r = r + self.g = g + self.b = b + self.a = a + + def to_binary(self, f3d, segments): + return sDPRGBColor(f3d.G_SETFOGCOLOR, self.r, self.g, self.b, self.a) + + def to_c(self, static = True): + header = 'gsDPSetFogColor(' if static else \ + 'gDPSetFogColor(glistp++, ' + return header + str(self.r) + ', ' + str(self.g) + ', ' + \ + str(self.b) + ', ' + str(self.a) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetFogColor ' + str(self.r) + ', ' + str(self.g) + \ + ', ' + str(self.b) + ', ' + str(self.a) + + def size(self, f3d): + return GFX_SIZE + +class DPSetFillColor: + def __init__(self, d): + self.d = d + + def to_binary(self, f3d, segments): + return gsDPSetColor(f3d.G_SETFILLCOLOR, self.d) + + def to_c(self, static = True): + header = 'gsDPSetFillColor(' if static else \ + 'gDPSetFillColor(glistp++, ' + return header + str(self.d) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetFillColor ' + str(self.d) + + def size(self, f3d): + return GFX_SIZE + +class DPSetPrimDepth: + def __init__(self, z, dz): + self.z = z + self.dz = dz + + def to_binary(self, f3d, segments): + return gsDPSetColor(f3d.G_SETPRIMDEPTH, _SHIFTL(self.z, 16, 16) | \ + _SHIFTL(self.dz, 0, 16)) + + def to_c(self, static = True): + header = 'gsDPSetPrimDepth(' if static else \ + 'gDPSetPrimDepth(glistp++, ' + return header + str(self.z) + ', ' + str(self.dz) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetPrimDepth ' + str(self.z) + ', ' + str(self.dz) + + def size(self, f3d): + return GFX_SIZE + +class DPSetPrimColor: + def __init__(self, m, l, r, g, b, a): + self.m = m + self.l = l + self.r = r + self.g = g + self.b = b + self.a = a + + def to_binary(self, f3d, segments): + words = (_SHIFTL(f3d.G_SETPRIMCOLOR, 24, 8) | _SHIFTL(self.m, 8, 8) | \ + _SHIFTL(self.l, 0, 8)), (_SHIFTL(self.r, 24, 8) | \ + _SHIFTL(self.g, 16, 8) | _SHIFTL(self.b, 8, 8) | \ + _SHIFTL(self.a, 0, 8)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetPrimColor(' if static else \ + 'gDPSetPrimColor(glistp++, ' + return header + str(self.m) + ', ' + str(self.l) + ', ' + \ + str(self.r) + ', ' + str(self.g) + ', ' + \ + str(self.b) + ', ' + str(self.a) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetPrimColor ' + str(self.m) + ', ' + str(self.l) + \ + ', ' + str(self.r) + ', ' + str(self.g) + \ + ', ' + str(self.b) + ', ' + str(self.a) + + def size(self, f3d): + return GFX_SIZE + +class DPSetOtherMode: + def __init__(self, mode0, mode1): + self.mode0 = mode0 + self.mode1 = mode1 + + def to_binary(self, f3d, segments): + words = _SHIFTL(f3d.G_RDPSETOTHERMODE,24,8) | \ + _SHIFTL(self.mode0,0,24), self.mode1 + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetOtherMode(' if static else \ + 'gDPSetOtherMode(glistp++, ' + return header + str(self.mode0) + ', ' + str(self.mode1) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetOtherMode ' + str(self.mode0) + ', ' + str(self.mode1) + + def size(self, f3d): + return GFX_SIZE + +def gsDPLoadTileGeneric(c, tile, uls, ult, lrs, lrt): + words = _SHIFTL(c, 24, 8) | _SHIFTL(uls, 12, 12) | _SHIFTL(ult, 0, 12), \ + _SHIFTL(tile, 24, 3) | _SHIFTL(lrs, 12, 12) | _SHIFTL(lrt, 0, 12) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +class DPSetTileSize: + def __init__(self, t, uls, ult, lrs, lrt): + self.t = t + self.uls = uls + self.ult = ult + self.lrs = lrs + self.lrt = lrt + + def to_binary(self, f3d, segments): + return gsDPLoadTileGeneric(f3d.G_SETTILESIZE, self.t, + self.uls, self.ult, self.lrs, self.lrt) + + def to_c(self, static = True): + header = 'gsDPSetTileSize(' if static else 'gDPSetTileSize(glistp++, ' + return header + str(self.t) + ', ' + str(self.uls) + ', ' + \ + str(self.ult) + ', ' + str(self.lrs) + ', ' + str(self.lrt) + ')' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + return 'gsDPSetTileSize ' + self.t + ', ' + self.uls + ', ' + \ + self.ult + ', ' + self.lrs + ', ' + self.lrt + + def size(self, f3d): + return GFX_SIZE + +class DPLoadTile: + def __init__(self, t, uls, ult, lrs, lrt): + self.t = t + self.uls = uls + self.ult = ult + self.lrs = lrs + self.lrt = lrt + + def to_binary(self, f3d, segments): + return gsDPLoadTileGeneric(f3d.G_LOADTILE, self.t, + self.uls, self.ult, self.lrs, self.lrt) + + def to_c(self, static = True): + header = 'gsDPLoadTile(' if static else 'gDPLoadTile(glistp++, ' + return header + str(self.t) + ', ' + str(self.uls) + ', ' + \ + str(self.ult) + ', ' + str(self.lrs) + ', ' + str(self.lrt) + ')' + + def to_sm64_decomp_s(self): + raise ValueError('gbi.inc is DEAD') + return 'gsDPLoadTile ' + self.t + ', ' + self.uls + ', ' + \ + self.ult + ', ' + self.lrs + ', ' + self.lrt + + def size(self, f3d): + return GFX_SIZE + +class DPSetTile: + def __init__(self, fmt, siz, line, tmem, tile, palette, cmt, \ + maskt, shiftt, cms, masks, shifts): + self.fmt = fmt # is a string + self.siz = siz # is a string + self.line = line + self.tmem = tmem + self.tile = tile + self.palette = palette + self.cmt = cmt # list of two strings + self.maskt = maskt + self.shiftt = shiftt + self.cms = cms # list of two strings + self.masks = masks + self.shifts = shifts + + def to_binary(self, f3d, segments): + cms = f3d.G_TX_VARS[self.cms[0]] + f3d.G_TX_VARS[self.cms[1]] + cmt = f3d.G_TX_VARS[self.cmt[0]] + f3d.G_TX_VARS[self.cmt[1]] + + words = (_SHIFTL(f3d.G_SETTILE, 24, 8) | \ + _SHIFTL(f3d.G_IM_FMT_VARS[self.fmt], 21, 3) | \ + _SHIFTL(f3d.G_IM_SIZ_VARS[self.siz], 19, 2) | \ + _SHIFTL(self.line, 9, 9) | \ + _SHIFTL(self.tmem, 0, 9)), (_SHIFTL(self.tile, 24, 3) | \ + _SHIFTL(self.palette, 20, 4) | _SHIFTL(cmt, 18, 2) | \ + _SHIFTL(self.maskt, 14, 4) | _SHIFTL(self.shiftt, 10, 4) | \ + _SHIFTL(cms, 8, 2) | _SHIFTL(self.masks, 4, 4) | \ + _SHIFTL(self.shifts, 0, 4)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetTile(' if static else 'gDPSetTile(glistp++, ' + return header + self.fmt + ', ' + self.siz + ', ' + \ + str(self.line) + ', ' + str(self.tmem) + ', ' + \ + str(self.tile) + ', ' + str(self.palette) + ',\n\t\t' + \ + self.cmt[0] + ' | ' + self.cmt[1] + ', ' + str(self.maskt) + \ + ', ' + str(self.shiftt) + ', ' + self.cms[0] + ' | ' + \ + self.cms[1] + ', ' + str(self.masks) + ', ' + str(self.shifts) + ')' + + def to_sm64_decomp_s(self): + raise ValueError("gbi.inc is DEAD") + return 'gsDPSetTile ' + self.fmt + ', ' + self.siz + ', ' + \ + str(self.line) + ', ' + str(self.tmem) + ', ' + \ + str(self.tile) + ', ' + str(self.palette) + ', ' + \ + str(self.cmt) + ', ' + str(self.maskt) + ', ' + \ + str(self.shiftt) + ', ' + str(self.cms) + ', ' + \ + str(self.masks) + ', ' + str(self.shifts) + + def size(self, f3d): + return GFX_SIZE + +class DPLoadBlock: + def __init__(self, tile, uls, ult, lrs, dxt): + self.tile = tile + self.uls = uls + self.ult = ult + self.lrs = lrs + self.dxt = dxt + + def to_binary(self, f3d, segments): + words = (_SHIFTL(f3d.G_LOADBLOCK, 24, 8) | _SHIFTL(self.uls, 12, 12) | \ + _SHIFTL(self.ult, 0, 12)), (_SHIFTL(self.tile, 24, 3) | \ + _SHIFTL((min(self.lrs,f3d.G_TX_LDBLK_MAX_TXL)), 12, 12) | \ + _SHIFTL(self.dxt, 0, 12)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPLoadBlock(' if static else 'gDPLoadBlock(glistp++, ' + return header + str(self.tile) + ', ' + str(self.uls) + ', ' + \ + str(self.ult) + ', ' + str(self.lrs) + ', ' + str(self.dxt) + ')' + + def to_sm64_decomp_s(self): + raise ValueError("gbi.inc is DEAD") + return 'gsDPLoadBlock ' + self.tile + ', ' + self.uls + ', ' + \ + self.ult + ', ' + self.lrs + ', ' + self.dxt + + def size(self, f3d): + return GFX_SIZE + +class DPLoadTLUTCmd: + def __init__(self, tile, count): + self.tile = tile + self.count = count + + def to_binary(self, f3d, segments): + words = _SHIFTL(f3d.G_LOADTLUT, 24, 8), \ + _SHIFTL((self.tile), 24, 3) | _SHIFTL((self.count), 14, 10) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPLoadTLUTCmd(' if static else 'gDPLoadTLUTCmd(glistp++, ' + return header + str(self.tile) + ', ' + str(self.count) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTLUTCmd ' + str(self.tile) + ', ' + str(self.count) + + def size(self, f3d): + return GFX_SIZE + +class DPLoadTextureBlock: + def __init__(self, timg, fmt, siz, width, height, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.fmt = fmt # string + self.siz = siz # string + self.width = width + self.height = height + self.pal = pal + self.cms = cms # list of two strings + self.cmt = cmt # list of two strings + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, self.siz + '_LOAD_BLOCK', \ + 1, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz + '_LOAD_BLOCK', + 0, 0, f3d.G_TX_LOADTILE, 0, self.cmt, self.maskt, self.shiftt, \ + self.cms, self.masks, self.shifts).to_binary(f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadBlock(f3d.G_TX_LOADTILE, 0, 0, \ + (((self.width)*(self.height) + \ + f3d.G_IM_SIZ_VARS[self.siz + '_INCR']) >> \ + f3d.G_IM_SIZ_VARS[self.siz + '_SHIFT'])-1, \ + f3d.CALC_DXT(self.width, f3d.G_IM_SIZ_VARS[self.siz + \ + '_BYTES'])).to_binary(f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz, ((((self.width) * \ + f3d.G_IM_SIZ_VARS[self.siz + '_LINE_BYTES'])+7)>>3), 0, \ + f3d.G_TX_RENDERTILE, self.pal, self.cmt, self.maskt, \ + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, 0, 0, \ + ((self.width)-1) << f3d.G_TEXTURE_IMAGE_FRAC, \ + ((self.height)-1) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTextureBlock(' if static else \ + 'gDPLoadTextureBlock(glistp++, ' + return header + '&' + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTextureBlock ' + \ + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +class DPLoadTextureBlockYuv: + def __init__(self, timg, fmt, siz, width, height, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.fmt = fmt # string + self.siz = siz # string + self.width = width + self.height = height + self.pal = pal + self.cms = cms + self.cmt = cmt + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, self.siz + '_LOAD_BLOCK', \ + 1, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz + '_LOAD_BLOCK', + 0, 0, f3d.G_TX_LOADTILE, 0, self.cmt, self.maskt, self.shiftt, \ + self.cms, self.masks, self.shifts).to_binary(f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadBlock(f3d.G_TX_LOADTILE, 0, 0, \ + (((self.width)*(self.height) + \ + f3d.G_IM_SIZ_VARS[self.siz + '_INCR']) >> \ + f3d.G_IM_SIZ_VARS[self.siz + '_SHIFT'])-1, \ + f3d.CALC_DXT(self.width, f3d.G_IM_SIZ_VARS[self.siz + \ + '_BYTES'])).to_binary(f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz, \ + ((((self.width) * 1)+7)>>3), 0, \ + f3d.G_TX_RENDERTILE, self.pal, self.cmt, self.maskt, \ + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, 0, 0, \ + ((self.width)-1) << f3d.G_TEXTURE_IMAGE_FRAC, \ + ((self.height)-1) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTextureBlockYuv(' if static else \ + 'gDPLoadTextureBlockYuv(glistp++, ' + return header + '&' + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTextureBlockYuv ' + \ + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +# gsDPLoadTextureBlockS +# gsDPLoadMultiBlockS +# gsDPLoadTextureBlockYuvS + +class _DPLoadTextureBlock: + def __init__(self, timg, tmem, fmt, siz, width, height, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.tmem = tmem + self.fmt = fmt # string + self.siz = siz # string + self.width = width + self.height = height + self.pal = pal + self.cms = cms + self.cmt = cmt + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, self.siz + '_LOAD_BLOCK', \ + 1, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz + '_LOAD_BLOCK', + 0, self.tmem, f3d.G_TX_LOADTILE, 0, self.cmt, self.maskt, + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadBlock(f3d.G_TX_LOADTILE, 0, 0, \ + (((self.width)*(self.height) + \ + f3d.G_IM_SIZ_VARS[self.siz + '_INCR']) >> \ + f3d.G_IM_SIZ_VARS[self.siz + '_SHIFT'])-1, \ + f3d.CALC_DXT(self.width, f3d.G_IM_SIZ_VARS[self.siz + \ + '_BYTES'])).to_binary(f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz, ((((self.width) * \ + f3d.G_IM_SIZ_VARS[self.siz + '_LINE_BYTES'])+7)>>3), \ + self.tmem, f3d.G_TX_RENDERTILE, self.pal, self.cmt, \ + self.maskt, self.shiftt, self.cms, self.masks, \ + self.shifts).to_binary(f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, 0, 0, \ + ((self.width)-1) << f3d.G_TEXTURE_IMAGE_FRAC, \ + ((self.height)-1) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = '_gsDPLoadTextureBlock(' if static else \ + '_gDPLoadTextureBlock(glistp++, ' + return header + '&' + self.timg.name + ', ' + str(self.tmem) + ', ' +\ + self.fmt + ', ' + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return '_gsDPLoadTextureBlock ' + \ + self.timg.name + ', ' + str(self.tmem) + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +# _gsDPLoadTextureBlockTile +# gsDPLoadMultiBlock +# gsDPLoadMultiBlockS + +class DPLoadTextureBlock_4b: + def __init__(self, timg, fmt, width, height, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.fmt = fmt # string + self.width = width + self.height = height + self.pal = pal + self.cms = cms + self.cmt = cmt + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, 'G_IM_SIZ_16b', \ + 1, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, 'G_IM_SIZ_16b', + 0, 0, f3d.G_TX_LOADTILE, 0, self.cmt, self.maskt, self.shiftt, \ + self.cms, self.masks, self.shifts).to_binary(f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadBlock(f3d.G_TX_LOADTILE, 0, 0, \ + (((self.width)*(self.height) + 3) >> 2) - 1, \ + f3d.CALC_DXT_4b(self.width)).to_binary(f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, 'G_IM_SIZ_4b', \ + (((self.width >> 1) + 7) >> 3), 0, \ + f3d.G_TX_RENDERTILE, self.pal, self.cmt, self.maskt, \ + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, 0, 0, \ + ((self.width)-1) << f3d.G_TEXTURE_IMAGE_FRAC, \ + ((self.height)-1) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTextureBlock_4b(' if static else \ + 'gDPLoadTextureBlock_4b(glistp++, ' + return header + '&' + self.timg.name + ', ' + self.fmt + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTextureBlock_4b ' + \ + self.timg.name + ', ' + self.fmt + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +# gsDPLoadTextureBlock_4bS +# gsDPLoadMultiBlock_4b +# gsDPLoadMultiBlock_4bS +# _gsDPLoadTextureBlock_4b + +class DPLoadTextureTile: + def __init__(self, timg, fmt, siz, width, height, + uls, ult, lrs, lrt, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.fmt = fmt # string + self.siz = siz # string + self.width = width + self.height = height + self.uls = uls + self.ult = ult + self.lrs = lrs + self.lrt = lrt + self.pal = pal + self.cms = cms + self.cmt = cmt + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, self.siz, \ + self.width, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz, \ + (((self.lrs-self.uls+1) * f3d.G_IM_SIZ_VARS[self.siz +\ + '_TILE_BYTES'] + 7)>>3), 0, f3d.G_TX_LOADTILE, 0, \ + self.cmt, self.maskt, self.shiftt, self.cms, self.masks,\ + self.shifts).to_binary(f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadTile(f3d.G_TX_LOADTILE, + (self.uls) << f3d.G_TEXTURE_IMAGE_FRAC, + (self.ult) << f3d.G_TEXTURE_IMAGE_FRAC, + (self.lrs) << f3d.G_TEXTURE_IMAGE_FRAC, + (self.lrt) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary( + f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, self.siz, + ((((self.lrs-self.uls+1) * \ + f3d.G_IM_SIZ_VARS[self.siz + '_LINE_BYTES'])+7)>>3), 0, \ + f3d.G_TX_RENDERTILE, self.pal, self.cmt, self.maskt, \ + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, \ + (self.uls) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.ult) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.lrs) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.lrt) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTextureTile(' if static else \ + 'gDPLoadTextureTile(glistp++, ' + return header + '&' + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + self.uls + ', ' + str(self.ult) + \ + ', ' + str(self.lrs) + ', ' + str(self.lrt) + str(self.pal) + \ + ', ' + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTextureTile ' + \ + self.timg.name + ', ' + self.fmt + ', ' + \ + self.siz + ', ' + str(self.width) + ', ' + \ + str(self.height) + ', ' + self.uls + ', ' + str(self.ult) + \ + ', ' + str(self.lrs) + ', ' + str(self.lrt)+ ', ' + \ + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +# gsDPLoadMultiTile + +class DPLoadTextureTile_4b: + def __init__(self, timg, fmt, width, height, + uls, ult, lrs, lrt, + pal, cms, cmt, masks, maskt, shifts, shiftt): + self.timg = timg # FImage object + self.fmt = fmt # string + self.width = width + self.height = height + self.uls = uls + self.ult = ult + self.lrs = lrs + self.lrt = lrt + self.pal = pal + self.cms = cms + self.cmt = cmt + self.masks = masks + self.maskt = maskt + self.shifts = shifts + self.shiftt = shiftt + + def to_binary(self, f3d, segments): + return \ + DPSetTextureImage(self.fmt, 'G_IM_SIZ_8b', \ + self.width >> 1, self.timg).to_binary(f3d, segments) + \ + DPSetTile(self.fmt, 'G_IM_SIZ_8b', \ + ((((self.lrs-self.uls+1) >> 1) + 7)>>3), 0, f3d.G_TX_LOADTILE, \ + 0, self.cmt, self.maskt, self.shiftt, self.cms, self.masks,\ + self.shifts).to_binary(f3d, segments) + \ + DPLoadSync().to_binary(f3d, segments) + \ + DPLoadTile(f3d.G_TX_LOADTILE, + (self.uls) << (f3d.G_TEXTURE_IMAGE_FRAC - 1), + (self.ult) << f3d.G_TEXTURE_IMAGE_FRAC, + (self.lrs) << (f3d.G_TEXTURE_IMAGE_FRAC - 1), + (self.lrt) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary( + f3d, segments) + \ + DPPipeSync().to_binary(f3d, segments) + \ + DPSetTile(self.fmt, 'G_IM_SIZ_4b', + ((((self.lrs-self.uls+1) >> 1)+7)>>3), 0, \ + f3d.G_TX_RENDERTILE, self.pal, self.cmt, self.maskt, \ + self.shiftt, self.cms, self.masks, self.shifts).to_binary(\ + f3d, segments) + \ + DPSetTileSize(f3d.G_TX_RENDERTILE, \ + (self.uls) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.ult) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.lrs) << f3d.G_TEXTURE_IMAGE_FRAC, \ + (self.lrt) << f3d.G_TEXTURE_IMAGE_FRAC).to_binary(\ + f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTextureTile_4b(' if static else \ + 'gDPLoadTextureTile_4b(glistp++, ' + return header + '&' + self.timg.name + ', ' + self.fmt + ', ' + \ + str(self.width) + ', ' + \ + str(self.height) + ', ' + self.uls + ', ' + str(self.ult) + \ + ', ' + str(self.lrs) + ', ' + str(self.lrt) + str(self.pal) + \ + ', ' + self.cms[0] + ' | ' + self.cms[1] + ', ' + self.cmt[0] + \ + ' | ' + self.cmt[1] + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTextureTile_4b ' + \ + self.timg.name + ', ' + self.fmt + ', ' + \ + str(self.width) + ', ' + \ + str(self.height) + ', ' + self.uls + ', ' + str(self.ult) + \ + ', ' + str(self.lrs) + ', ' + str(self.lrt)+ ', ' + \ + str(self.pal) + ', ' + \ + str(self.cms) + ', ' + str(self.cmt) + ', ' + \ + str(self.masks) + ', ' + str(self.maskt) + ', ' + \ + str(self.shifts) + ', ' + str(self.shiftt) + + def size(self, f3d): + return GFX_SIZE * 7 + +# gsDPLoadMultiTile_4b + +class DPLoadTLUT_pal16: + def __init__(self, pal, dram): + self.pal = pal + self.dram = dram # pallete object + + 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).to_binary(f3d, segments) + + def to_c(self, static = True): + header = 'gsDPLoadTLUT_pal16(' if static else \ + 'gDPLoadTLUT_pal16(glistp++, ' + return header + str(self.pal) + ', ' + '&' + self.dram.name + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTLUT_pal16 ' + str(self.pal) + ', ' + self.dram.name + + def size(self, f3d): + if not f3d._HW_VERSION_1: + return GFX_SIZE * 6 + else: + return GFX_SIZE * 7 + +class DPLoadTLUT_pal256: + def __init__(self, dram): + self.dram = dram # pallete object + + 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) + + def to_c(self, static = True): + header = 'gsDPLoadTLUT_pal256(' if static else \ + 'gDPLoadTLUT_pal256(glistp++, ' + return header + '&' + self.dram.name + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTLUT_pal256 ' + self.dram.name + + def size(self, f3d): + if not f3d._HW_VERSION_1: + return GFX_SIZE * 6 + else: + return GFX_SIZE * 7 + +class DPLoadTLUT: + def __init__(self, count, tmemaddr, dram): + self.count = count + self.tmemaddr = tmemaddr + self.dram = dram # pallete object + + 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) + + def to_c(self, static = True): + header = 'gsDPLoadTLUT(' if static else \ + 'gDPLoadTLUT(glistp++, ' + return header + str(self.count) + ', ' + str(self.tmemaddr) + ', ' + \ + '&' + self.dram.name + ')' + + def to_sm64_decomp_s(self): + return 'gsDPLoadTLUT ' + str(self.count) + ', ' + \ + str(self.tmemaddr) + ', ' + self.dram.name + + def size(self, f3d): + if not f3d._HW_VERSION_1: + return GFX_SIZE * 6 + else: + return GFX_SIZE * 7 + +# gsDPSetScissor +# gsDPSetScissorFrac + +# gsDPFillRectangle + +class DPSetConvert: + def __init__(self, k0, k1, k2, k3, k4, k5): + self.k0 = k0 + self.k1 = k1 + self.k2 = k2 + self.k3 = k3 + self.k4 = k4 + self.k5 = k5 + + def to_binary(self, f3d, segments): + words = (_SHIFTL(f3d.G_SETCONVERT, 24, 8) | \ + _SHIFTL(self.k0, 13, 9) | _SHIFTL(self.k1, 4, 9) | \ + _SHIFTL(self.k2, 5, 4)), (_SHIFTL(self.k2, 27, 5) | \ + _SHIFTL(self.k3, 18, 9) | _SHIFTL(self.k4, 9, 9) | \ + _SHIFTL(self.k5, 0, 9)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetConvert(' if static else 'gDPSetConvert(glistp++, ' + return header + str(self.k0) + ', ' + str(self.k1) + ', ' + \ + str(self.k2) + ', ' + str(self.k3) + ', ' + str(self.k4) + \ + ', ' + str(self.k5) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetConvert ' + str(self.k0) + ', ' + str(self.k1) + ', ' + \ + str(self.k2) + ', ' + str(self.k3) + ', ' + str(self.k4) + \ + ', ' + str(self.k5) + + def size(self, f3d): + return GFX_SIZE + +class DPSetKeyR: + def __init__(self, cR, sR, wR): + self.cR = cR + self.sR = sR + self.wR = wR + + def to_binary(self, f3d, segments): + words = _SHIFTL(f3d.G_SETKEYR, 24, 8), \ + _SHIFTL(self.wR, 16, 12) | _SHIFTL(self.cR, 8, 8) | \ + _SHIFTL(self.sR, 0, 8) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetKeyR(' if static else 'gDPSetKeyR(glistp++, ' + return header + str(self.cR) + ', ' + str(self.sR) + ', ' + \ + str(self.wR) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetKeyR ' + str(self.cR) + ', ' + str(self.sR) + ', ' + \ + str(self.wR) + + def size(self, f3d): + return GFX_SIZE + +class DPSetKeyGB: + def __init__(self, cG, sG, wG, cB, sB, wB): + self.cG = cG + self.sG = sG + self.wG = wG + self.cB = cB + self.sB = sB + self.wB = wB + + def to_binary(self, f3d, segments): + words = (_SHIFTL(f3d.G_SETKEYGB, 24, 8) | _SHIFTL(self.wG, 12, 12) |\ + _SHIFTL(self.wB, 0, 12)), (_SHIFTL(self.cG, 24, 8) | \ + _SHIFTL(self.sG, 16, 8) | _SHIFTL(self.cB, 8, 8) | \ + _SHIFTL(self.sB, 0, 8)) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + + def to_c(self, static = True): + header = 'gsDPSetKeyGB(' if static else 'gDPSetKeyGB(glistp++, ' + return header + str(self.cG) + ', ' + str(self.sG) + ', ' + \ + str(self.wG) + ', ' + str(self.cB) + ', ' + str(self.sB) + \ + ', ' + str(self.wB) + ')' + + def to_sm64_decomp_s(self): + return 'gsDPSetKeyGB ' + str(self.cG) + ', ' + str(self.sG) + ', ' + \ + str(self.wG) + ', ' + str(self.cB) + ', ' + str(self.sB) + \ + ', ' + str(self.wB) + + def size(self, f3d): + return GFX_SIZE + +def gsDPNoParam(cmd): + words = _SHIFTL(cmd, 24, 8), 0 + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +def gsDPParam(cmd, param): + words = _SHIFTL(cmd, 24, 8), (param) + return words[0].to_bytes(4, 'big') + words[1].to_bytes(4, 'big') + +# gsDPTextureRectangle +# gsDPTextureRectangleFlip +# gsSPTextureRectangle +# gSPScisTextureRectangle +# gsSPTextureRectangleFlip +# gsDPWord + +class DPFullSync: + def __init__(self): + pass + + def to_binary(self, f3d, segments): + return gsDPNoParam(f3d.G_RDPFULLSYNC) + + def to_c(self, static = True): + return 'gsDPFullSync()' if static else 'gDPFullSync(glistp++)' + + def to_sm64_decomp_s(self): + return 'gsDPFullSync' + + def size(self, f3d): + return GFX_SIZE + +class DPTileSync: + def __init__(self): + pass + + def to_binary(self, f3d, segments): + return gsDPNoParam(f3d.G_RDPTILESYNC) + + def to_c(self, static = True): + return 'gsDPTileSync()' if static else 'gDPTileSync(glistp++)' + + def to_sm64_decomp_s(self): + return 'gsDPTileSync' + + def size(self, f3d): + return GFX_SIZE + +class DPPipeSync: + def __init__(self): + pass + + def to_binary(self, f3d, segments): + return gsDPNoParam(f3d.G_RDPPIPESYNC) + + def to_c(self, static = True): + return 'gsDPPipeSync()' if static else 'gDPPipeSync(glistp++)' + + def to_sm64_decomp_s(self): + return 'gsDPPipeSync' + + def size(self, f3d): + return GFX_SIZE + +class DPLoadSync: + def __init__(self): + pass + + def to_binary(self, f3d, segments): + return gsDPNoParam(f3d.G_RDPLOADSYNC) + + def to_c(self, static = True): + return 'gsDPLoadSync()' if static else 'gDPLoadSync(glistp++)' + + def to_sm64_decomp_s(self): + return 'gsDPLoadSync' + + def size(self, f3d): + return GFX_SIZE \ No newline at end of file diff --git a/fast64_internal/f3d_material.py b/fast64_internal/f3d_material.py new file mode 100644 index 0000000..88fab3b --- /dev/null +++ b/fast64_internal/f3d_material.py @@ -0,0 +1,1711 @@ +import bpy +from bpy.app.handlers import persistent +import math +import mathutils +import sys +from bpy.types import Node, NodeSocket, NodeSocketInterface, ShaderNode, ShaderNodeGroup, Panel +import nodeitems_utils +from nodeitems_utils import NodeCategory, NodeItem +from .f3d_gbi import F3D +from .f3d_enums import * +from .f3d_material_nodes import * +from .sm64_constants import * +from .utility import prop_split +from bpy.utils import register_class, unregister_class + +def F3DOrganizeLights(self, context): + # Flag to prevent infinite recursion on update callback + if self.f3d_update_flag: + return + self.f3d_update_flag = True + lightList = [] + if self.f3d_light1 is not None: lightList.append(self.f3d_light1) + if self.f3d_light2 is not None: lightList.append(self.f3d_light2) + if self.f3d_light3 is not None: lightList.append(self.f3d_light3) + if self.f3d_light4 is not None: lightList.append(self.f3d_light4) + if self.f3d_light5 is not None: lightList.append(self.f3d_light5) + if self.f3d_light5 is not None: lightList.append(self.f3d_light6) + if self.f3d_light6 is not None: lightList.append(self.f3d_light7) + + self.f3d_light1 = lightList[0] if len(lightList) > 0 else None + self.f3d_light2 = lightList[1] if len(lightList) > 1 else None + self.f3d_light3 = lightList[2] if len(lightList) > 2 else None + self.f3d_light4 = lightList[3] if len(lightList) > 3 else None + self.f3d_light5 = lightList[4] if len(lightList) > 4 else None + self.f3d_light6 = lightList[5] if len(lightList) > 5 else None + self.f3d_light7 = lightList[6] if len(lightList) > 6 else None + self.f3d_update_flag = False + +def combiner_uses(material, checkList, is2Cycle): + display = False + nodes = material.node_tree.nodes + for value in checkList: + display |= nodes['Case A 1'].inA == value + if is2Cycle: + display |= nodes['Case A 2'].inA == value + + display |= nodes['Case B 1'].inB == value + if is2Cycle: + display |= nodes['Case B 2'].inB == value + + display |= nodes['Case C 1'].inC == value + if is2Cycle: + display |= nodes['Case C 2'].inC == value + + display |= nodes['Case D 1'].inD == value + if is2Cycle: + display |= nodes['Case D 2'].inD == value + + + display |= nodes['Case A Alpha 1'].inA_alpha == value + if is2Cycle: + display |= nodes['Case A Alpha 2'].inA_alpha == value + + display |= nodes['Case B Alpha 1'].inB_alpha == value + if is2Cycle: + display |= nodes['Case B Alpha 2'].inB_alpha == value + + display |= nodes['Case C Alpha 1'].inC_alpha == value + if is2Cycle: + display |= nodes['Case C Alpha 2'].inC_alpha == value + + display |= nodes['Case D Alpha 1'].inD_alpha == value + if is2Cycle: + display |= nodes['Case D Alpha 2'].inD_alpha == value + + return display + +def all_combiner_uses(material, is2Cycle): + useDict = { + 'Texture' : combiner_uses(material, + ['TEXEL0', 'TEXEL0_ALPHA', 'TEXEL1', 'TEXEL1_ALPHA', + 'COMBINED', 'COMBINED_ALPHA'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Texture 0' : combiner_uses(material, + ['TEXEL0', 'TEXEL0_ALPHA'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Texture 1' : combiner_uses(material, + ['TEXEL1', 'TEXEL1_ALPHA'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Primitive' : combiner_uses(material, + ['PRIMITIVE', 'PRIMITIVE_ALPHA', 'PRIM_LOD_FRAC'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Environment' : combiner_uses(material, + ['ENVIRONMENT', 'ENV_ALPHA'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Shade' : combiner_uses(material, + ['SHADE', 'SHADE_ALPHA'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Key' : combiner_uses(material, ['CENTER', 'SCALE'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'LOD Fraction' : combiner_uses(material, ['LOD_FRACTION'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + + 'Convert' : combiner_uses(material, ['K4', 'K5'], + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE'), + } + return useDict + +def ui_geo_mode(settings, dataHolder, layout): + inputGroup = layout.column() + inputGroup.prop(dataHolder, 'menu_geo', + text = 'Geometry Mode Settings', + icon = 'TRIA_DOWN' if dataHolder.menu_geo else 'TRIA_RIGHT') + if 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') + if isinstance(dataHolder, bpy.types.Material) and \ + settings.g_fog: + material = dataHolder + layout.prop(material, 'set_fog', text = 'Set Fog') + fogGroup = layout.column() + fogColorGroup = fogGroup.row().split(factor = 0.5) + fogColorGroup.label(text = 'Fog Color') + fogColorGroup.prop(material, 'fog_color', text = '') + fogPositionGroup = fogGroup.row().split(factor = 0.5) + fogPositionGroup.label(text = 'Fog Range') + fogPositionGroup.prop(material, 'fog_position', text = '') + fogGroup.enabled = material.set_fog + inputGroup.prop(settings, 'g_lighting', text = 'Lighting') + inputGroup.prop(settings, 'g_tex_gen', text = 'Texture UV Generate') + inputGroup.prop(settings, 'g_tex_gen_linear', + text = 'Texture UV Generate Linear') + inputGroup.prop(settings, 'g_shade_smooth', text = 'Smooth Shading') + if bpy.context.scene.f3d_type == 'F3DEX_GBI_2' or \ + bpy.context.scene.f3d_type == 'F3DEX_GBI': + inputGroup.prop(settings, 'g_clipping', text = 'Clipping') + +def ui_upper_mode(settings, dataHolder, layout): + inputGroup = layout.column() + inputGroup.prop(dataHolder, 'menu_upper', + text = 'Other Mode Upper Settings', + icon = 'TRIA_DOWN' if dataHolder.menu_upper else 'TRIA_RIGHT') + if 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_combkey', 'Chroma Key') + prop_split(inputGroup, settings, 'g_mdsft_textconv', 'Texture Convert') + prop_split(inputGroup, settings, 'g_mdsft_text_filt', 'Texture Filter') + #prop_split(inputGroup, settings, 'g_mdsft_textlut', 'Texture LUT') + prop_split(inputGroup, settings, 'g_mdsft_textlod', 'Texture LOD') + prop_split(inputGroup, settings, 'g_mdsft_textdetail', 'Texture Detail') + prop_split(inputGroup, settings, 'g_mdsft_textpersp', 'Texture Perspective Correction') + prop_split(inputGroup, settings, 'g_mdsft_cycletype', 'Cycle Type') + + prop_split(inputGroup, settings, 'g_mdsft_pipeline', 'Pipeline Span Buffer Coherency') + +def ui_lower_mode(settings, dataHolder, layout): + inputGroup = layout.column() + inputGroup.prop(dataHolder, 'menu_lower', + text = 'Other Mode Lower Settings', + icon = 'TRIA_DOWN' if dataHolder.menu_lower else 'TRIA_RIGHT') + if dataHolder.menu_lower: + prop_split(inputGroup, settings, 'g_mdsft_alpha_compare', 'Alpha Compare') + prop_split(inputGroup, settings, 'g_mdsft_zsrcsel', 'Z Source Selection') + +# UI Assumptions: +# shading = 1 +# lighting = 1 +# cycle type = 1 cycle +class F3DPanel(bpy.types.Panel): + bl_label = "F3D Material" + bl_idname = "F3D_Inspector" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "material" + bl_options = {'HIDE_HEADER'} + + #def hasNecessaryNodes(self, nodes): + # result = True + # for name, nodeType in caseTemplateDict.items(): + # result &= (name in nodes) + # return result + + def ui_image(self, material, layout, textureProp, name): + nodes = material.node_tree.nodes + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(textureProp, 'tex_set', text = name) + + prop_input.prop(textureProp, 'menu', text = 'Texture Properties', + icon = 'TRIA_DOWN' if textureProp.menu else 'TRIA_RIGHT') + if textureProp.menu: + prop_input.template_image(textureProp, 'tex', + nodes[name].image_user) + prop_input.enabled = textureProp.tex_set + + #prop_input.box().label(text = "YUV and CI not yet supported.") + prop_input.prop(textureProp, 'tex_format', text = 'Format') + if textureProp.tex_format[:2] == 'CI': + prop_input.prop(textureProp, 'ci_format', text = 'CI Format') + prop_input.prop(textureProp.S, "clamp", text = 'Clamp S') + prop_input.prop(textureProp.T, "clamp", text = 'Clamp T') + prop_input.prop(textureProp.S, "mirror", text = 'Mirror S') + prop_input.prop(textureProp.T, "mirror", text = 'Mirror T') + + prop_input.prop(textureProp, 'autoprop', + text = 'Auto Set Other Properties') + + if not textureProp.autoprop: + prop_input.prop(textureProp.S, "mask", text = 'Mask S') + prop_input.prop(textureProp.T, "mask", text = 'Mask T') + prop_input.prop(textureProp.S, "shift", text = 'Shift S') + prop_input.prop(textureProp.T, "shift", text = 'Shift T') + prop_input.prop(textureProp.S, "low", text = 'S Low') + prop_input.prop(textureProp.T, "low", text = 'T Low') + prop_input.prop(textureProp.S, "high", text = 'S High') + prop_input.prop(textureProp.T, "high", text = 'T High') + + tex = textureProp.tex + if tex is not None and tex.size[0] > 0 and tex.size[1] > 0 and \ + (math.log(tex.size[0], 2) % 1 > 0.000001 or \ + math.log(tex.size[1], 2) % 1 > 0.000001): + warnBox = layout.box() + warnBox.label( + text = 'Warning: Texture dimensions are not power of 2.') + warnBox.label(text = 'Wrapping only occurs on power of 2 bounds.') + + def ui_prop(self, material, layout, name, setName, setProp): + nodes = material.node_tree.nodes + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, setName, text = name) + prop_input.prop(nodes[name].outputs[0], 'default_value', text='') + prop_input.enabled = setProp + return inputGroup + + def ui_prop_non_node(self, material, layout, label, name, setName, setProp): + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, setName, text = name) + prop_input.prop(material, name, text='') + prop_input.enabled = setProp + return inputGroup + + def ui_scale(self, material, layout): + inputGroup = layout.row().split(factor = 0.5) + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.label(text = 'Texture Scale') + prop_input.prop(material, 'scale_autoprop', text='Auto Set') + if not material.scale_autoprop: + prop_input_group = prop_input.row() + prop_input_group.prop(material, 'tex_scale', text='') + return inputGroup + + def ui_prim(self, material, layout, setName, setProp): + nodes = material.node_tree.nodes + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, setName, text = 'Primitive Color') + prop_input.prop(nodes['Primitive Color'].outputs[0], 'default_value', + text='') + prop_input.prop(material, 'prim_lod_frac', + text='Prim LOD Fraction') + prop_input.prop(material, 'prim_lod_min', + text='Min LOD Ratio') + prop_input.enabled = setProp + return inputGroup + + def ui_chroma(self, material, layout, name, setName, setProp): + nodes = material.node_tree.nodes + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, setName, text = 'Chroma Key') + prop_input.prop(nodes['Chroma Key Center'].outputs[0], + 'default_value', text='Center') + prop_input.prop(material, 'key_scale', text = 'Scale') + prop_input.prop(material, 'key_width', text = 'Width') + if material.key_width[0] > 1 or material.key_width[1] > 1 or \ + material.key_width[2] > 1: + layout.box().label(text = \ + "NOTE: Keying is disabled for channels with width > 1.") + prop_input.enabled = setProp + return inputGroup + + def ui_lights(self, material, layout, name): + nodes = material.node_tree.nodes + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, 'set_lights', text = name) + prop_input_name.enabled = material.rdp_settings.g_lighting and \ + material.rdp_settings.g_shade + if material.rdp_settings.g_lighting: + prop_input.prop(nodes['Ambient Color'].outputs[0],'default_value', + text='Ambient Color') + + prop_input.prop_search(material, 'f3d_light1', + bpy.data, 'lights', text = '') + if material.f3d_light1 is not None: + prop_input.prop_search(material, 'f3d_light2', + bpy.data, 'lights', text = '') + if material.f3d_light2 is not None: + prop_input.prop_search(material, 'f3d_light3', + bpy.data, 'lights', text = '') + if material.f3d_light3 is not None: + prop_input.prop_search(material, 'f3d_light4', + bpy.data, 'lights', text = '') + if material.f3d_light4 is not None: + prop_input.prop_search(material, 'f3d_light5', + bpy.data, 'lights', text = '') + if material.f3d_light5 is not None: + prop_input.prop_search(material, 'f3d_light6', + bpy.data, 'lights', text = '') + if material.f3d_light6 is not None: + prop_input.prop_search(material, 'f3d_light7', + bpy.data, 'lights', text = '') + layout.box().label(text = "Note: Lighting preview is not 100% accurate.") + layout.box().label(text = "For vertex colors, clear 'Lighting'.") + prop_input.enabled = material.set_lights and \ + material.rdp_settings.g_lighting and \ + material.rdp_settings.g_shade + + return inputGroup + + def ui_convert(self, material, layout): + inputGroup = layout.row() + prop_input_name = inputGroup.column() + prop_input = inputGroup.column() + prop_input_name.prop(material, 'set_k0_5', text = 'YUV Convert') + + prop_k0 = prop_input.row() + prop_k0.prop(material, 'k0', text='K0') + prop_k0.label(text = str(int(material.k0 * 255))) + + prop_k1 = prop_input.row() + prop_k1.prop(material, 'k1', text='K1') + prop_k1.label(text = str(int(material.k1 * 255))) + + prop_k2 = prop_input.row() + prop_k2.prop(material, 'k2', text='K2') + prop_k2.label(text = str(int(material.k2 * 255))) + + prop_k3 = prop_input.row() + prop_k3.prop(material, 'k3', text='K3') + prop_k3.label(text = str(int(material.k3 * 255))) + + prop_k4 = prop_input.row() + prop_k4.prop(material, 'k4', text='K4') + prop_k4.label(text = str(int(material.k4 * 255))) + + prop_k5 = prop_input.row() + prop_k5.prop(material, 'k5', text='K5') + prop_k5.label(text = str(int(material.k5 * 255))) + + prop_input.enabled = material.set_k0_5 + return inputGroup + + def ui_lower_render_mode(self, material, layout): + # cycle independent + inputGroup = layout.column() + inputGroup.prop(material, 'menu_lower_render', + text = 'Other Mode Lower Render Settings', + icon = 'TRIA_DOWN' if material.menu_lower_render else 'TRIA_RIGHT') + if material.menu_lower_render: + inputGroup.prop(material, 'set_rendermode', + text ='Set Render Mode?') + + renderGroup = inputGroup.column() + prop_split(renderGroup, material.rdp_settings, 'aa_en', 'Antialiasing') + prop_split(renderGroup, material.rdp_settings, 'z_cmp', 'Z Testing') + prop_split(renderGroup, material.rdp_settings, 'z_upd', 'Z Writing') + prop_split(renderGroup, material.rdp_settings, 'im_rd', 'IM_RD (?)') + prop_split(renderGroup, material.rdp_settings, 'clr_on_cvg', + 'Clear On Coverage') + prop_split(renderGroup, material.rdp_settings, 'cvg_dst', + 'Coverage Destination') + prop_split(renderGroup, material.rdp_settings, 'zmode', 'Z Mode') + prop_split(renderGroup, material.rdp_settings, 'cvg_x_alpha', + 'Multiply Coverage And Alpha') + prop_split(renderGroup, material.rdp_settings, 'alpha_cvg_sel', + 'Use Coverage For Alpha') + prop_split(renderGroup, material.rdp_settings, 'force_bl', 'Force Blending') + + # cycle dependent - (P * A + M - B) / (A + B) + combinerBox = renderGroup.box() + combinerBox.label(text='Blender (Color = (P * A + M - B) / (A + B)') + combinerCol = combinerBox.row() + rowColor = combinerCol.column() + rowAlpha = combinerCol.column() + rowColor.prop(material.rdp_settings, 'blend_p1', text = 'P') + rowColor.prop(material.rdp_settings, 'blend_m1', text = 'M') + rowAlpha.prop(material.rdp_settings, 'blend_a1', text = 'A') + rowAlpha.prop(material.rdp_settings, 'blend_b1', text = 'B') + + if material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE': + combinerBox2 = renderGroup.box() + combinerBox2.label(text='Blender Cycle 2') + combinerCol2 = combinerBox2.row() + rowColor2 = combinerCol2.column() + rowAlpha2 = combinerCol2.column() + rowColor2.prop(material.rdp_settings, 'blend_p2', text = 'P') + rowColor2.prop(material.rdp_settings, 'blend_m2', text = 'M') + rowAlpha2.prop(material.rdp_settings, 'blend_a2', text = 'A') + rowAlpha2.prop(material.rdp_settings, 'blend_b2', text = 'B') + + renderGroup.enabled = material.set_rendermode + + def ui_procAnimVec(self, procAnimVec, layout, name, vecType): + layout.prop(procAnimVec, 'menu', text = name, + icon = 'TRIA_DOWN' if procAnimVec.menu else 'TRIA_RIGHT') + if procAnimVec.menu: + box = layout.box() + self.ui_procAnimField(procAnimVec.x, box, vecType[0]) + self.ui_procAnimField(procAnimVec.y, box, vecType[1]) + if len(vecType) > 2: + self.ui_procAnimField(procAnimVec.z, box, vecType[2]) + + def ui_procAnimField(self, procAnimField, layout, name): + box = layout + box.prop(procAnimField, 'animate', text = name) + if procAnimField.animate: + if name not in 'XYZ': + split0 = box.row().split(factor = 0.5) + split0.prop(procAnimField, 'speed') + split0.prop(procAnimField, 'scrollOffset') + split1 = box.row().split(factor = 0.5) + split1.prop(procAnimField, 'amplitude') + split1.prop(procAnimField, 'frequency') + + layout.row().prop(procAnimField, 'spaceFrequency') + + split2 = box.row().split(factor = 0.5) + split2.prop(procAnimField, 'offset') + split2.prop(procAnimField, 'noiseAmplitude') + + def ui_procAnim(self, material, layout, useTex0, useTex1): + layout.prop(material, 'menu_procAnim', + text = 'Procedural Animation', + icon = 'TRIA_DOWN' if material.menu_procAnim else 'TRIA_RIGHT') + if material.menu_procAnim: + procAnimBox = layout.box() + if useTex0: + self.ui_procAnimVec(material.UVanim_tex0, procAnimBox, + "UV Texture 0", 'UV') + if useTex1: + self.ui_procAnimVec(material.UVanim_tex1, procAnimBox, + "UV Texture 1", 'UV') + self.ui_procAnimVec(material.positionAnim, procAnimBox, + "Position", 'XYZ') + self.ui_procAnimVec(material.colorAnim, procAnimBox, "Color", + 'RGB') + + # texture convert/LUT controlled by texture settings + # add node support for geo mode settings + def draw(self, context): + layout = self.layout + + layout.operator(CreateFast3DMaterial.bl_idname) + material = context.material + if material is None: + pass + elif not(material.use_nodes and material.is_f3d): + layout.label(text="This is not a Fast3D material.") + else: + layout.box().label(text = 'Note: Do not copy paste materials.') + layout = layout.box() + titleCol = layout.column() + titleCol.label(text = "F3D Material Inspector") + + prop_split(layout, material, 'f3d_preset', 'Preset Material') + + if not material.rdp_settings.g_lighting: + noticeBox = layout.box() + noticeBox.label( + text = 'Note: There must be two vertex color layers.') + noticeBox.label( + text = 'They should be called "Col" and "Alpha".') + + combinerBox = layout.box() + combinerBox.prop(material, 'set_combiner', + text = 'Color Combiner (Color = (A - B) * C + D)') + combinerCol = combinerBox.row() + combinerCol.enabled = material.set_combiner + rowColor = combinerCol.column() + rowAlpha = combinerCol.column() + + rowColor.prop(material.combiner1, 'A') + rowColor.prop(material.combiner1, 'B') + rowColor.prop(material.combiner1, 'C') + rowColor.prop(material.combiner1, 'D') + rowAlpha.prop(material.combiner1, 'A_alpha') + rowAlpha.prop(material.combiner1, 'B_alpha') + rowAlpha.prop(material.combiner1, 'C_alpha') + rowAlpha.prop(material.combiner1, 'D_alpha') + + if material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE': + combinerBox2 = layout.box() + combinerBox2.label(text = 'Color Combiner Cycle 2') + combinerBox2.enabled = material.set_combiner + combinerCol2 = combinerBox2.row() + rowColor2 = combinerCol2.column() + rowAlpha2 = combinerCol2.column() + + rowColor2.prop(material.combiner2, 'A') + rowColor2.prop(material.combiner2, 'B') + rowColor2.prop(material.combiner2, 'C') + rowColor2.prop(material.combiner2, 'D') + rowAlpha2.prop(material.combiner2, 'A_alpha') + rowAlpha2.prop(material.combiner2, 'B_alpha') + rowAlpha2.prop(material.combiner2, 'C_alpha') + rowAlpha2.prop(material.combiner2, 'D_alpha') + + layout.box().label( + text = 'Note: Alpha preview is not 100% accurate.') + + inputCol = layout.column() + useDict = all_combiner_uses(material, + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE') + + if useDict['Texture']: + self.ui_scale(material, inputCol) + + if useDict['Texture 0']: + self.ui_image(material, inputCol, material.tex0, 'Texture 0') + + if useDict['Texture 1']: + self.ui_image(material, inputCol, material.tex1, 'Texture 1') + + if useDict['Texture 0'] and useDict['Texture 1']: + inputCol.prop(material, 'uv_basis', text = 'UV Basis') + + if useDict['Primitive']: + self.ui_prim(material, inputCol, 'set_prim', material.set_prim) + + if useDict['Environment']: + self.ui_prop(material, inputCol, 'Environment Color', 'set_env', + material.set_env) + + if useDict['Shade']: + self.ui_lights(material, inputCol, 'Shade Color') + + if useDict['Key']: + self.ui_chroma(material, inputCol, 'Chroma Key Center', + 'set_key', material.set_key) + + if useDict['Convert']: + self.ui_convert(material, inputCol) + + #self.ui_procAnim(material, inputCol, + # useDict['Texture 0'], useDict['Texture 1']) + + ui_geo_mode(material.rdp_settings, material, layout) + ui_upper_mode(material.rdp_settings, material, layout) + ui_lower_mode(material.rdp_settings, material, layout) + layout.box().label(text = \ + 'WARNING: Render mode settings not reset after drawing.') + self.ui_lower_render_mode(material, layout) + +def update_node_values(self, context): + if hasattr(context.scene, 'world') and \ + self == context.scene.world.rdp_defaults: + pass + elif hasattr(context, 'material') and context.material is not None: + material = context.material # Handles case of texture property groups + if not material.is_f3d or material.f3d_update_flag: + return + material.f3d_update_flag = True + update_node_values_of_material(material, context) + material.f3d_preset = 'Custom' + material.f3d_update_flag = False + else: + pass + #print('No material in context.') + +def update_node_values_directly(material, context): + if not material.is_f3d or material.f3d_update_flag: + return + material.f3d_update_flag = True + update_node_values_of_material(material, context) + material.f3d_preset = 'Custom' + material.f3d_update_flag = False + +def update_node_values_of_material(material, context): + nodes = material.node_tree.nodes + + nodes['Case A 1'].inA = material.combiner1.A + nodes['Case B 1'].inB = material.combiner1.B + nodes['Case C 1'].inC = material.combiner1.C + nodes['Case D 1'].inD = material.combiner1.D + nodes['Case A Alpha 1'].inA_alpha = material.combiner1.A_alpha + nodes['Case B Alpha 1'].inB_alpha = material.combiner1.B_alpha + nodes['Case C Alpha 1'].inC_alpha = material.combiner1.C_alpha + nodes['Case D Alpha 1'].inD_alpha = material.combiner1.D_alpha + nodes['Case A 2'].inA = material.combiner2.A + nodes['Case B 2'].inB = material.combiner2.B + nodes['Case C 2'].inC = material.combiner2.C + nodes['Case D 2'].inD = material.combiner2.D + nodes['Case A Alpha 2'].inA_alpha = material.combiner2.A_alpha + nodes['Case B Alpha 2'].inB_alpha = material.combiner2.B_alpha + nodes['Case C Alpha 2'].inC_alpha = material.combiner2.C_alpha + nodes['Case D Alpha 2'].inD_alpha = material.combiner2.D_alpha + + nodes['Cycle Type'].outputs[0].default_value = 1 if \ + material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE' else 0 + nodes['Texture Gen'].outputs[0].default_value = 1 if \ + material.rdp_settings.g_tex_gen else 0 + nodes['Texture Gen Linear'].outputs[0].default_value = 1 if \ + material.rdp_settings.g_tex_gen_linear else 0 + nodes['Shading'].outputs[0].default_value = 0 if \ + not material.rdp_settings.g_shade else 1 + nodes['Lighting'].outputs[0].default_value = 0 if \ + not material.rdp_settings.g_lighting else 1 + + nodes['Chroma Key Scale'].outputs[0].default_value = \ + [value for value in material.key_scale] + [1] + nodes['Primitive LOD Fraction'].outputs[0].default_value = \ + material.prim_lod_frac + + # nodes['YUV Convert K0'].outputs[0].default_value = material.k0 + # nodes['YUV Convert K1'].outputs[0].default_value = material.k1 + # nodes['YUV Convert K2'].outputs[0].default_value = material.k2 + # nodes['YUV Convert K3'].outputs[0].default_value = material.k3 + nodes['YUV Convert K4'].outputs[0].default_value = material.k4 + nodes['YUV Convert K5'].outputs[0].default_value = material.k5 + + nodes['Cull Front'].outputs[0].default_value = \ + 0 if material.rdp_settings.g_cull_front else 1 + material.show_transparent_back = material.rdp_settings.g_cull_front + nodes['Cull Back'].outputs[0].default_value = \ + 0 if material.rdp_settings.g_cull_back else 1 + + + #nodes['Texture 0'].interpolation = 'Closest' if \ + # material.rdp_settings.g_mdsft_text_filt == '0' else 'Cubic' + #nodes['Texture 1'].interpolation = 'Closest' if \ + # material.rdp_settings.g_mdsft_text_filt == '0' else 'Cubic' + update_tex_values_manual(material, context) + +def update_tex_values_field(self, fieldProperty, texCoordNode, pixelLength, + isTexGen, uvBasisScale, scale, autoprop): + clamp = fieldProperty.clamp + mirror = fieldProperty.mirror + + texCoordNode['Clamp'].outputs[0].default_value = 1 if clamp else 0 + texCoordNode['Mirror'].outputs[0].default_value = 1 if mirror else 0 + texCoordNode['Normalized Half Pixel'].outputs[0].default_value = \ + 1 / (2 * pixelLength) + + if autoprop: + fieldProperty.low = 0 + fieldProperty.high = pixelLength - 1 + fieldProperty.mask = math.ceil(math.log(pixelLength, 2) - 0.001) + fieldProperty.shift = 0 + + L = fieldProperty.low + H = fieldProperty.high + mask = fieldProperty.mask + shift = fieldProperty.shift + + texCoordNode['Normalized L'].outputs[0].default_value = L / pixelLength + texCoordNode['Normalized H'].outputs[0].default_value = (H + 1)/pixelLength + texCoordNode['Normalized Mask'].outputs[0].default_value = \ + (2 ** mask) / pixelLength if mask > 0 else 0 + + texCoordNode['Shift'].outputs[0].default_value = shift + texCoordNode['Scale'].outputs[0].default_value = scale * uvBasisScale + +def update_tex_values_index(self, context, texProperty, texNodeName, + uvNodeName, isTexGen, uvBasisScale, scale): + nodes = self.node_tree.nodes + + tex_x = nodes[uvNodeName].node_tree.nodes[\ + 'Create Tex Coord'].node_tree.nodes + tex_y = nodes[uvNodeName].node_tree.nodes[\ + 'Create Tex Coord.001'].node_tree.nodes + + nodes[texNodeName].image = texProperty.tex + if nodes[texNodeName].image is not None: + tex_size = nodes[texNodeName].image.size + if tex_size[0] > 0 and tex_size[1] > 0: + + # 1024 == 2^16 / 2^6 (converting 0.16 to 10.5 fixed point) + nodes[uvNodeName].node_tree.nodes['Image Width Factor'].outputs[0\ + ].default_value = 1024 / tex_size[0] + nodes[uvNodeName].node_tree.nodes['Image Height Factor'].outputs[0\ + ].default_value = 1024 / tex_size[1] + update_tex_values_field(self, texProperty.S, tex_x, tex_size[0], + self.rdp_settings.g_tex_gen or self.rdp_settings.g_tex_gen_linear, + uvBasisScale[0], scale[0], texProperty.autoprop) + update_tex_values_field(self, texProperty.T, tex_y, tex_size[1], + self.rdp_settings.g_tex_gen or self.rdp_settings.g_tex_gen_linear, + uvBasisScale[1], scale[1], texProperty.autoprop) + + texFormat = texProperty.tex_format + ciFormat = texProperty.ci_format + nodes[texNodeName + ' Is Greyscale'].outputs[0].default_value = \ + 1 if (texFormat[0] == 'I' or \ + (texFormat[:2] == 'CI' and ciFormat[0] == 'I')) else 0 + nodes[texNodeName + ' Has Alpha'].outputs[0].default_value = \ + 1 if ('A' in texFormat or \ + (texFormat[:2] == 'CI' and 'A' in ciFormat))else 0 + +def update_tex_values(self, context): + if hasattr(context, 'material') and context.material is not None: + material = context.material # Handles case of texture property groups + if material.f3d_update_flag: + return + material.f3d_update_flag = True + update_tex_values_manual(material, context) + material.f3d_update_flag = False + +def update_tex_values_manual(self, context): + isTexGen = self.rdp_settings.g_tex_gen or self.rdp_settings.g_tex_gen_linear + + if self.scale_autoprop: + tex_size = None + if self.tex0.tex is not None and self.tex1.tex is not None: + tex_size = self.tex0.tex.size if self.uv_basis == 'TEXEL0' else \ + self.tex1.tex.size + elif self.tex0.tex is not None: + tex_size = self.tex0.tex.size + elif self.tex1.tex is not None: + tex_size = self.tex1.tex.size + + if isTexGen and tex_size is not None: + self.tex_scale = ((tex_size[0] - 1) / 1024, + (tex_size[1] - 1) / 1024) + else: + self.tex_scale = (1,1) + + if self.tex0.tex is not None and self.tex1.tex is not None and\ + self.tex0.tex.size[0] > 0 and self.tex0.tex.size[1] > 0 and\ + self.tex1.tex.size[0] > 0 and self.tex1.tex.size[1] > 0: + if self.uv_basis == 'TEXEL0': + uvBasisScale0 = (1,1) + uvBasisScale1 = (self.tex0.tex.size[0] / self.tex1.tex.size[0], + self.tex0.tex.size[1] / self.tex1.tex.size[1]) + else: + uvBasisScale1 = (1,1) + uvBasisScale0 = (self.tex1.tex.size[0] / self.tex0.tex.size[0], + self.tex1.tex.size[1] / self.tex0.tex.size[1]) + else: + uvBasisScale0 = (1,1) + uvBasisScale1 = (1,1) + + update_tex_values_index(self, context, self.tex0, 'Texture 0', + 'Get UV', isTexGen, uvBasisScale0, self.tex_scale) + update_tex_values_index(self, context, self.tex1, 'Texture 1', + 'Get UV.001', isTexGen, uvBasisScale1, self.tex_scale) + +def update_preset(self, context): + if hasattr(context, 'material') and context.material is not None: + if context.material.f3d_preset != 'Custom': + materialSettings = materialPresetDict[context.material.f3d_preset] + materialSettings.applyToMaterial(context.material) + +def update_preset_manual(material, context): + if material.f3d_preset != 'Custom': + materialSettings = materialPresetDict[material.f3d_preset] + materialSettings.applyToMaterial(material) + +def createF3DMat(obj): + material = bpy.data.materials.new('sm64_material') + obj.data.materials.append(material) + + material.is_f3d = True + + material.use_nodes = True + material.blend_method = 'BLEND' + material.show_transparent_back = False + + # Remove default shader + node_tree = material.node_tree + nodes = material.node_tree.nodes + links = material.node_tree.links + nodes.remove(nodes.get('Principled BSDF')) + material_output = nodes.get('Material Output') + + nodePos = [600, 0] + caseNodeDict1 = addNodeListAtWithZeroAddNode(node_tree, + caseTemplateDict, *nodePos, 1) + + nodePos = [600, -1600] + caseNodeDict2 = addNodeListAtWithZeroAddNode(node_tree, + caseTemplateDict, *nodePos, 2) + + nodePos = [0, 0] + nodeDict = addNodeListAt(node_tree, { + 'Combined Color': 'ShaderNodeTexImage', + 'Texture 0': 'ShaderNodeTexImage', + 'Texture 1': 'ShaderNodeTexImage', + 'Primitive Color': 'ShaderNodeRGB', + #'Shade Color': 'ShaderNodeBsdfDiffuse', + 'Environment Color': 'ShaderNodeRGB', + 'Chroma Key Center': 'ShaderNodeRGB', + 'Chroma Key Scale': 'ShaderNodeRGB', + #'Primitive Alpha': 'ShaderNodeValue', + #'Shade Alpha': 'ShaderNodeValue', + #'Environment Alpha' : 'ShaderNodeValue', + 'LOD Fraction' : 'ShaderNodeValue', + 'Primitive LOD Fraction' : 'ShaderNodeValue', + 'Noise' : 'ShaderNodeTexNoise', + 'YUV Convert K4' : 'ShaderNodeValue', + 'YUV Convert K5' : 'ShaderNodeValue', + '1' : 'ShaderNodeValue', + '0' : 'ShaderNodeValue', + }, *nodePos) + + # create uv nodes + # Must be done before texture format mixes, which overwrite nodeDict + + # Note: Because of modulo operations on UVs, aliasing occurs + # due to mipmapping when 'Linear' filtering is used. + # When using 'Cubic', clamping doesn't work correctly either. + # Thus 'Closest' is used instead. + nodes['Texture 0'].interpolation = 'Closest' + nodes['Texture 1'].interpolation = 'Closest' + + texGenNode, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + 'Texture Gen', -500, -200) + texGenLinearNode, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + 'Texture Gen Linear', -500, -300) + uvNode0 = createUVNode(node_tree, [-300, -200], + texGenNode, texGenLinearNode) + uvNode1 = createUVNode(node_tree, [-300,-400], + texGenNode, texGenLinearNode) + links.new(nodeDict['Texture 0'].inputs[0], uvNode0.outputs[0]) + links.new(nodeDict['Texture 1'].inputs[0], uvNode1.outputs[0]) + + # Add texture format mixes + y = createTexFormatNodes(node_tree, [300, 0], 0, nodeDict) + createTexFormatNodes(node_tree, [300, y], 1, nodeDict) + + # create shade node + lightingNode, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + 'Lighting', -900, 0) + shadingNode, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + 'Shading', -900, y) + ambientNode, x, y = addNodeAt(node_tree, 'ShaderNodeRGB', + 'Ambient Color', -900, y) + nodeDict['Shade Color'] = createShadeNode(node_tree, [-900, y], + shadingNode, lightingNode, ambientNode) + + # Create combiner nodes + groupNode = createNodeFinal(node_tree, caseNodeDict1, nodeDict, + ['Combined Color', 'Shade Color'], ['Environment Color', 'Primitive Color'], 1) + groupNode.location = [1200,0] + + groupNode2 = createNodeFinal(node_tree, caseNodeDict2, nodeDict, + ['Combined Color', 'Shade Color'], ['Environment Color', 'Primitive Color'], 2) + groupNode2.location = [1200, -800] + + links.new(groupNode2.inputs[8], groupNode.outputs[0]) + links.new(groupNode2.inputs[9], groupNode.outputs[1]) + + mixCycleNodeRGB, x, y = \ + addNodeAt(node_tree, 'ShaderNodeMixRGB', 'Cycle Mix RGB', 1500, -600) + mixCycleNodeAlpha, x, y = \ + addNodeAt(node_tree, 'ShaderNodeMixRGB', 'Cycle Mix Alpha', 1500, -900) + cycleTypeNode, x, y = \ + addNodeAt(node_tree, 'ShaderNodeValue', 'Cycle Type', 1500, -1200) + + links.new(mixCycleNodeRGB.inputs[1], groupNode.outputs[0]) + links.new(mixCycleNodeRGB.inputs[2], groupNode2.outputs[0]) + links.new(mixCycleNodeAlpha.inputs[1], groupNode.outputs[1]) + links.new(mixCycleNodeAlpha.inputs[2], groupNode2.outputs[1]) + links.new(mixCycleNodeRGB.inputs[0], cycleTypeNode.outputs[0]) + links.new(mixCycleNodeAlpha.inputs[0], cycleTypeNode.outputs[0]) + + cullFront, x, y = \ + addNodeAt(node_tree, 'ShaderNodeValue', 'Cull Front', 1800, -600) + cullBack, x, y = \ + addNodeAt(node_tree, 'ShaderNodeValue', 'Cull Back', 1800, -800) + backFacing, x, y = \ + addNodeAt(node_tree, 'ShaderNodeNewGeometry', 'Is Backfacing', + 1800, -1000) + + multCullFront,x,y = \ + addNodeAt(node_tree, 'ShaderNodeMath','Multiply Cull Front', 2100, -600) + multCullFront.operation = 'MULTIPLY' + multCullBack,x,y = \ + addNodeAt(node_tree, 'ShaderNodeMath','Multiply Cull Back', 2100, -800) + multCullBack.operation = 'MULTIPLY' + + finalCullAlpha,x,y = \ + addNodeAt(node_tree, 'ShaderNodeMixRGB','Cull Alpha', 2400, -600) + + links.new(multCullFront.inputs[0], cullFront.outputs[0]) + links.new(multCullBack.inputs[0], cullBack.outputs[0]) + links.new(multCullFront.inputs[1], mixCycleNodeAlpha.outputs[0]) + links.new(multCullBack.inputs[1], mixCycleNodeAlpha.outputs[0]) + links.new(finalCullAlpha.inputs[0], backFacing.outputs[6]) + links.new(finalCullAlpha.inputs[1], multCullFront.outputs[0]) + links.new(finalCullAlpha.inputs[2], multCullBack.outputs[0]) + + # Create mix shader to allow for alpha blending + # we cannot input alpha directly to material output, but we can mix between + # our final color and a completely transparent material based on alpha + + material_output.location = [2100,0] + mixShaderNode = nodes.new('ShaderNodeMixShader') + mixShaderNode.location = [1800, 0] + clearNode = nodes.new('ShaderNodeEeveeSpecular') + clearNode.location = [1500, 0] + clearNode.inputs[4].default_value = 1 # transparency + links.new(mixShaderNode.inputs[2], mixCycleNodeRGB.outputs[0]) + links.new(mixShaderNode.inputs[0], finalCullAlpha.outputs[0]) + links.new(mixShaderNode.inputs[1], clearNode.outputs[0]) + + # link new node output to material_output input + links.new(material_output.inputs[0], mixShaderNode.outputs[0]) + + #update_node_values_directly(material, bpy.context) + #update_tex_values(material, bpy.context) + + # This won't update because material is not in context + material.f3d_preset = 'Shaded Texture' + # That's why we force update + update_preset_manual(material, bpy.context) + #materialPresetDict['Shaded Texture'].applyToMaterial(material) + + return material + +class CreateFast3DMaterial(bpy.types.Operator): + # set bl_ properties + bl_idname = 'object.create_f3d_mat' + bl_label = "Create Fast3D Material" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + # Called on demand (i.e. button press, menu item) + # Can also be called from operator search menu (Spacebar) + def execute(self, context): + obj = bpy.context.view_layer.objects.active + if obj is None: + self.report({'ERROR'}, 'No active object selected.') + else: + createF3DMat(obj) + self.report({'INFO'}, 'Created new Fast3D material.') + return {'FINISHED'} # must return a set + +class F3DMaterialSettings: + def __init__(self): + self.color_combiner = tuple(S_SHADED_TEX + S_SHADED_TEX) + self.set_texture0 = True + self.set_texture1 = True + self.set_prim = True + self.set_lights = True + self.set_env = True + self.set_key = True + self.set_k0_5 = True + self.set_combiner = True + self.set_rendermode = False + self.scale_autoprop = True + + # geometry mode + self.g_zbuffer = True + self.g_shade = True + self.g_cull_front = False + self.g_cull_back = True + self.g_fog = False + self.g_lighting = True + self.g_tex_gen = False + self.g_tex_gen_linear = False + self.g_shade_smooth = True + # f3dlx2 only + self.g_clipping = False + + # upper half mode + self.g_mdsft_alpha_dither = 'G_AD_NOISE' + self.g_mdsft_rgb_dither = 'G_CD_MAGICSQ' + self.g_mdsft_combkey = 'G_CK_NONE' + self.g_mdsft_textconv = 'G_TC_FILT' + self.g_mdsft_text_filt = 'G_TF_BILERP' + self.g_mdsft_textlut = 'G_TT_NONE' + self.g_mdsft_textlod = 'G_TL_TILE' + self.g_mdsft_textdetail = 'G_TD_CLAMP' + self.g_mdsft_textpersp = 'G_TP_PERSP' + self.g_mdsft_cycletype = 'G_CYC_1CYCLE' + # v1 only + self.g_mdsft_color_dither = 'G_CD_ENABLE' + self.g_mdsft_pipeline = 'G_PM_1PRIMITIVE' + + # lower half mode + self.g_mdsft_alpha_compare = 'G_AC_NONE' + self.g_mdsft_zsrcsel = 'G_ZS_PIXEL' + + # cycle independent + self.aa_en = False + self.z_cmp = False + self.z_upd = False + self.im_rd = False + self.clr_on_cvg = False + self.aa_en = False + self.cvg_dst = 'CVG_DST_CLAMP' + self.zmode = 'ZMODE_OPA' + self.cvg_x_alpha = False + self.alpha_cvg_sel = False + self.force_bl = False + + # cycle dependent - (P * A + M - B) / (A + B) + self.blend_p1 = None + self.blend_p2 = None + self.blend_m1 = None + self.blend_m2 = None + self.blend_a1 = None + self.blend_a2 = None + self.blend_b1 = None + self.blend_b2 = None + + def loadFromMaterial(self, material): + if not material.is_f3d: + print(material.name + ' is not an f3d material.') + return + + self.color_combiner = ( + material.combiner1.A, + material.combiner1.B, + material.combiner1.C, + material.combiner1.D, + material.combiner1.A_alpha, + material.combiner1.B_alpha, + material.combiner1.C_alpha, + material.combiner1.D_alpha, + material.combiner2.A, + material.combiner2.B, + material.combiner2.C, + material.combiner2.D, + material.combiner1.A_alpha, + material.combiner1.B_alpha, + material.combiner1.C_alpha, + material.combiner1.D_alpha, + ) + self.set_texture0 = material.tex0.tex_set + self.set_texture1 = material.tex1.tex_set + self.set_prim = material.set_prim + self.set_lights = material.set_lights + self.set_env = material.set_env + self.set_key = material.set_key + self.set_k0_5 = material.set_k0_5 + self.set_combiner = material.set_combiner + self.set_rendermode = material.set_rendermode + self.scale_autoprop = material.scale_autoprop + + # geometry mode + self.g_zbuffer = material.rdp_settings.g_zbuffer + self.g_shade = material.rdp_settings.g_shade + self.g_cull_front = material.rdp_settings.g_cull_front + self.g_cull_back = material.rdp_settings.g_cull_back + self.g_fog = material.rdp_settings.g_fog + self.g_lighting = material.rdp_settings.g_lighting + self.g_tex_gen = material.rdp_settings.g_tex_gen + self.g_tex_gen_linear = material.rdp_settings.g_tex_gen_linear + self.g_shade_smooth = material.rdp_settings.g_shade_smooth + # f3dlx2 only + self.g_clipping = material.rdp_settings.g_clipping + + # upper half mode + self.g_mdsft_alpha_dither = material.rdp_settings.g_mdsft_alpha_dither + self.g_mdsft_rgb_dither = material.rdp_settings.g_mdsft_rgb_dither + self.g_mdsft_combkey = material.rdp_settings.g_mdsft_combkey + self.g_mdsft_textconv = material.rdp_settings.g_mdsft_textconv + self.g_mdsft_text_filt = material.rdp_settings.g_mdsft_text_filt + self.g_mdsft_textlut = material.rdp_settings.g_mdsft_textlut + self.g_mdsft_textlod = material.rdp_settings.g_mdsft_textlod + self.g_mdsft_textdetail = material.rdp_settings.g_mdsft_textdetail + self.g_mdsft_textpersp = material.rdp_settings.g_mdsft_textpersp + self.g_mdsft_cycletype = material.rdp_settings.g_mdsft_cycletype + # v1 only + self.g_mdsft_color_dither = material.rdp_settings.g_mdsft_color_dither + self.g_mdsft_pipeline = material.rdp_settings.g_mdsft_pipeline + + # lower half mode + self.g_mdsft_alpha_compare = material.rdp_settings.g_mdsft_alpha_compare + self.g_mdsft_zsrcsel = material.rdp_settings.g_mdsft_zsrcsel + + # cycle independent + self.aa_en = material.rdp_settings.aa_en + self.z_cmp = material.rdp_settings.z_cmp + self.z_upd = material.rdp_settings.z_upd + self.im_rd = material.rdp_settings.im_rd + self.clr_on_cvg = material.rdp_settings.clr_on_cvg + self.aa_en = material.rdp_settings.aa_en + self.cvg_dst = material.rdp_settings.cvg_dst + self.zmode = material.rdp_settings.zmode + self.cvg_x_alpha = material.rdp_settings.cvg_x_alpha + self.alpha_cvg_sel = material.rdp_settings.alpha_cvg_sel + self.force_bl = material.rdp_settings.force_bl + + # cycle dependent - (P * A + M - B) / (A + B) + self.blend_p1 = material.rdp_settings.blend_p1 + self.blend_p2 = material.rdp_settings.blend_p2 + self.blend_m1 = material.rdp_settings.blend_m1 + self.blend_m2 = material.rdp_settings.blend_m2 + self.blend_a1 = material.rdp_settings.blend_a1 + self.blend_a2 = material.rdp_settings.blend_a2 + self.blend_b1 = material.rdp_settings.blend_b1 + self.blend_b2 = material.rdp_settings.blend_b2 + + def applyToMaterial(self, material): + if not material.is_f3d: + print(material.name + ' is not an f3d material.') + return + + material.f3d_update_flag = True + material.combiner1.A = self.color_combiner[0] + material.combiner1.B = self.color_combiner[1] + material.combiner1.C = self.color_combiner[2] + material.combiner1.D = self.color_combiner[3] + material.combiner1.A_alpha = self.color_combiner[4] + material.combiner1.B_alpha = self.color_combiner[5] + material.combiner1.C_alpha = self.color_combiner[6] + material.combiner1.D_alpha = self.color_combiner[7] + material.combiner2.A = self.color_combiner[8] + material.combiner2.B = self.color_combiner[9] + material.combiner2.C = self.color_combiner[10] + material.combiner2.D = self.color_combiner[11] + material.combiner1.A_alpha = self.color_combiner[12] + material.combiner1.B_alpha = self.color_combiner[13] + material.combiner1.C_alpha = self.color_combiner[14] + material.combiner1.D_alpha = self.color_combiner[15] + + material.tex0.tex_set = self.set_texture0 + material.tex1.tex_set = self.set_texture1 + material.set_prim = self.set_prim + material.set_lights = self.set_lights + material.set_env = self.set_env + material.set_key = self.set_key + material.set_k0_5 = self.set_k0_5 + material.set_combiner = self.set_combiner + material.set_rendermode = self.set_rendermode + material.scale_autoprop = self.scale_autoprop + + # geometry mode + material.rdp_settings.g_zbuffer = self.g_zbuffer + material.rdp_settings.g_shade = self.g_shade + material.rdp_settings.g_cull_front = self.g_cull_front + material.rdp_settings.g_cull_back = self.g_cull_back + material.rdp_settings.g_fog = self.g_fog + material.rdp_settings.g_lighting = self.g_lighting + material.rdp_settings.g_tex_gen = self.g_tex_gen + material.rdp_settings.g_tex_gen_linear = self.g_tex_gen_linear + material.rdp_settings.g_shade_smooth = self.g_shade_smooth + # f3dlx2 only + material.rdp_settings.g_clipping = self.g_clipping + + # upper half mode + material.rdp_settings.g_mdsft_alpha_dither = self.g_mdsft_alpha_dither + material.rdp_settings.g_mdsft_rgb_dither = self.g_mdsft_rgb_dither + material.rdp_settings.g_mdsft_combkey = self.g_mdsft_combkey + material.rdp_settings.g_mdsft_textconv = self.g_mdsft_textconv + material.rdp_settings.g_mdsft_text_filt = self.g_mdsft_text_filt + material.rdp_settings.g_mdsft_textlut = self.g_mdsft_textlut + material.rdp_settings.g_mdsft_textlod = self.g_mdsft_textlod + material.rdp_settings.g_mdsft_textdetail = self.g_mdsft_textdetail + material.rdp_settings.g_mdsft_textpersp = self.g_mdsft_textpersp + material.rdp_settings.g_mdsft_cycletype = self.g_mdsft_cycletype + # v1 only + material.rdp_settings.g_mdsft_color_dither = self.g_mdsft_color_dither + material.rdp_settings.g_mdsft_pipeline = self.g_mdsft_pipeline + + # lower half mode + material.rdp_settings.g_mdsft_alpha_compare = self.g_mdsft_alpha_compare + material.rdp_settings.g_mdsft_zsrcsel = self.g_mdsft_zsrcsel + + # cycle independent + if self.set_rendermode: + material.rdp_settings.aa_en = self.aa_en + material.rdp_settings.z_cmp = self.z_cmp + material.rdp_settings.z_upd = self.z_upd + material.rdp_settings.im_rd = self.im_rd + material.rdp_settings.clr_on_cvg = self.clr_on_cvg + material.rdp_settings.aa_en = self.aa_en + material.rdp_settings.cvg_dst = self.cvg_dst + material.rdp_settings.zmode = self.zmode + material.rdp_settings.cvg_x_alpha = self.cvg_x_alpha + material.rdp_settings.alpha_cvg_sel = self.alpha_cvg_sel + material.rdp_settings.force_bl = self.force_bl + + # cycle dependent - (P * A + M - B) / (A + B) + material.rdp_settings.blend_p1 = self.blend_p1 + material.rdp_settings.blend_p2 = self.blend_p2 + material.rdp_settings.blend_m1 = self.blend_m1 + material.rdp_settings.blend_m2 = self.blend_m2 + material.rdp_settings.blend_a1 = self.blend_a1 + material.rdp_settings.blend_a2 = self.blend_a2 + material.rdp_settings.blend_b1 = self.blend_b1 + material.rdp_settings.blend_b2 = self.blend_b2 + + update_node_values_of_material(material, bpy.context) + material.f3d_update_flag = False + +class TextureFieldProperty(bpy.types.PropertyGroup): + clamp : bpy.props.BoolProperty(name = 'Clamp', + update = update_tex_values) + mirror : bpy.props.BoolProperty(name = 'Mirror', + update = update_tex_values) + low : bpy.props.FloatProperty(name = 'Low', min = 0, max = 1023.75, + update = update_tex_values) + high : bpy.props.FloatProperty(name = 'High', min = 0, max = 1023.75, + update = update_tex_values) + mask : bpy.props.IntProperty(min = 0, max = 15, + update = update_tex_values, default = 5) + shift : bpy.props.IntProperty(min = -5, max = 10, + update = update_tex_values) + +class TextureProperty(bpy.types.PropertyGroup): + tex : bpy.props.PointerProperty( + type = bpy.types.Image, name = 'Texture', + update = update_tex_values) + + # this is done in material so that greyscale/nonalpha formats will + # be reflected in preview + tex_format : bpy.props.EnumProperty( + name = 'Format', items = enumTexFormat, default = 'RGBA16', + update = update_tex_values) + ci_format : bpy.props.EnumProperty(name = 'CI Format', items = enumCIFormat, + default = 'RGBA16', update = update_tex_values) + S : bpy.props.PointerProperty(type = TextureFieldProperty) + T : bpy.props.PointerProperty(type = TextureFieldProperty) + + menu : bpy.props.BoolProperty() + tex_set : bpy.props.BoolProperty(default = True, update = update_node_values) + autoprop : bpy.props.BoolProperty(name = 'Autoprop', + update = update_tex_values, default = True) + +class CombinerProperty(bpy.types.PropertyGroup): + A : bpy.props.EnumProperty( + name = "A", description = "A", items = combiner_enums['Case A'], + default = 'TEXEL0', update = update_node_values) + + B : bpy.props.EnumProperty( + name = "B", description = "B", items = combiner_enums['Case B'], + default = '0', update = update_node_values) + + C : bpy.props.EnumProperty( + name = "C", description = "C", items = combiner_enums['Case C'], + default = 'SHADE', update = update_node_values) + + D : bpy.props.EnumProperty( + name = "D", description = "D", items = combiner_enums['Case D'], + default = '0', update = update_node_values) + + A_alpha : bpy.props.EnumProperty( + name = "A Alpha", description = "A Alpha", + items = combiner_enums['Case A Alpha'], + default = '0', update = update_node_values) + + B_alpha : bpy.props.EnumProperty( + name = "B Alpha", description = "B Alpha", + items = combiner_enums['Case B Alpha'], + default = '0', update = update_node_values) + + C_alpha : bpy.props.EnumProperty( + name = "C Alpha", description = "C Alpha", + items = combiner_enums['Case C Alpha'], + default = '0', update = update_node_values) + + D_alpha : bpy.props.EnumProperty( + name = "D Alpha", description = "D Alpha", + items = combiner_enums['Case D Alpha'], + default = 'ENVIRONMENT', update = update_node_values) + +class ProceduralAnimProperty(bpy.types.PropertyGroup): + speed : bpy.props.FloatProperty(name = 'Scroll Speed', default = 1) + amplitude : bpy.props.FloatProperty(name = 'Amplitude', default = 0) + frequency : bpy.props.FloatProperty(name = 'Time Frequency', default = 1) + #spaceFrequency : bpy.props.FloatVectorProperty(name = 'Space Frequency', + # size = 3, default = (0,0,0)) + spaceFrequency : bpy.props.FloatProperty(name = 'Space Frequency', + default = 0) + offset : bpy.props.FloatProperty(name = 'Sin Offset', default = 0) + scrollOffset : bpy.props.FloatProperty(name = 'Scroll Offset', default = 0) + noiseAmplitude : bpy.props.FloatProperty(name = 'Noise', default = 0) + animate : bpy.props.BoolProperty() + +class ProcAnimVectorProperty(bpy.types.PropertyGroup): + x : bpy.props.PointerProperty(type = ProceduralAnimProperty) + y : bpy.props.PointerProperty(type = ProceduralAnimProperty) + z : bpy.props.PointerProperty(type = ProceduralAnimProperty) + menu : bpy.props.BoolProperty() + +class RDPSettings(bpy.types.PropertyGroup): + g_zbuffer : bpy.props.BoolProperty(name = 'Z Buffer', default = True, + update = update_node_values) + g_shade : bpy.props.BoolProperty(name = 'Shading', default = True, + update = update_node_values) + #v1/2 difference + g_cull_front : bpy.props.BoolProperty(name = 'Cull Front', + update = update_node_values) + #v1/2 difference + g_cull_back : bpy.props.BoolProperty(name = 'Cull Back', default = True, + update = update_node_values) + g_fog : bpy.props.BoolProperty(name = 'Fog', + update = update_node_values) + g_lighting : bpy.props.BoolProperty(name = 'Lighting', default = True, + update = update_node_values) + g_tex_gen : bpy.props.BoolProperty(name = 'Texture UV Generate', + update = update_node_values) + g_tex_gen_linear : bpy.props.BoolProperty( + name = 'Texture UV Generate Linear', + update = update_node_values) + #v1/2 difference + g_shade_smooth : bpy.props.BoolProperty(name = 'Smooth Shading', + default = True, update = update_node_values) + # f3dlx2 only + g_clipping : bpy.props.BoolProperty(name = 'Clipping', + update = update_node_values) + + # upper half mode + # v2 only + g_mdsft_alpha_dither : bpy.props.EnumProperty( + name = 'Alpha Dither', items = enumAlphaDither, default = 'G_AD_NOISE') + # v2 only + g_mdsft_rgb_dither : bpy.props.EnumProperty( + name = 'RGB Dither', items = enumRGBDither, default = 'G_CD_MAGICSQ') + g_mdsft_combkey : bpy.props.EnumProperty( + name = 'Chroma Key', items = enumCombKey, default = 'G_CK_NONE') + g_mdsft_textconv : bpy.props.EnumProperty( + name = 'Texture Convert', items = enumTextConv, default = 'G_TC_FILT') + g_mdsft_text_filt : bpy.props.EnumProperty( + name = 'Texture Filter', items = enumTextFilt, default = 'G_TF_BILERP', + update = update_node_values) + g_mdsft_textlut : bpy.props.EnumProperty( + name = 'Texture LUT', items = enumTextLUT, default = 'G_TT_NONE') + g_mdsft_textlod : bpy.props.EnumProperty( + name = 'Texture LOD', items = enumTextLOD, default = 'G_TL_TILE') + g_mdsft_textdetail : bpy.props.EnumProperty( + name = 'Texture Detail', items = enumTextDetail, default = 'G_TD_CLAMP') + g_mdsft_textpersp : bpy.props.EnumProperty( + name = 'Texture Perspective Correction', items = enumTextPersp, + default = 'G_TP_PERSP') + g_mdsft_cycletype : bpy.props.EnumProperty( + name = 'Cycle Type', items = enumCycleType, default = 'G_CYC_1CYCLE', + update = update_node_values) + # v1 only + g_mdsft_color_dither : bpy.props.EnumProperty( + name = 'Color Dither', items = enumColorDither, default = 'G_CD_ENABLE') + g_mdsft_pipeline : bpy.props.EnumProperty( + name = 'Pipeline Span Buffer Coherency', items = enumPipelineMode, + default = 'G_PM_1PRIMITIVE') + + # lower half mode + g_mdsft_alpha_compare : bpy.props.EnumProperty( + name = 'Alpha Compare', items = enumAlphaCompare, + default = 'G_AC_NONE') + g_mdsft_zsrcsel : bpy.props.EnumProperty( + name = 'Z Source Selection', items = enumDepthSource, + default = 'G_ZS_PIXEL') + + # cycle independent + set_rendermode : bpy.props.BoolProperty(default = False) + aa_en : bpy.props.BoolProperty() + z_cmp : bpy.props.BoolProperty() + z_upd : bpy.props.BoolProperty() + im_rd : bpy.props.BoolProperty() + clr_on_cvg : bpy.props.BoolProperty() + cvg_dst : bpy.props.EnumProperty( + name = 'Coverage Destination', items = enumCoverage) + zmode : bpy.props.EnumProperty( + name = 'Z Mode', items = enumZMode) + cvg_x_alpha : bpy.props.BoolProperty() + alpha_cvg_sel : bpy.props.BoolProperty() + force_bl : bpy.props.BoolProperty() + + # cycle dependent - (P * A + M - B) / (A + B) + blend_p1 : bpy.props.EnumProperty( + name = 'Color Source 1', items = enumBlendColor) + blend_p2 : bpy.props.EnumProperty( + name = 'Color Source 1', items = enumBlendColor) + blend_m1 : bpy.props.EnumProperty( + name = 'Color Source 2', items = enumBlendColor) + blend_m2 : bpy.props.EnumProperty( + name = 'Color Source 2', items = enumBlendColor) + blend_a1 : bpy.props.EnumProperty( + name = 'Alpha Source', items = enumBlendAlpha) + blend_a2 : bpy.props.EnumProperty( + name = 'Alpha Source', items = enumBlendAlpha) + blend_b1 : bpy.props.EnumProperty( + name = 'Alpha Mix', items = enumBlendMix) + blend_b2 : bpy.props.EnumProperty( + name = 'Alpha Mix', items = enumBlendMix) + +class DefaultRDPSettingsPanel(bpy.types.Panel): + bl_label = "RDP Default Settings" + bl_idname = "RDP_Default_Inspector" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "world" + bl_options = {'HIDE_HEADER'} + + def draw(self, context): + world = context.scene.world + layout = self.layout + layout.box().label(text = 'RDP Default Settings') + layout.label(text = "If a material setting is a same as a default " +\ + "setting, then it won't be set.") + ui_geo_mode(world.rdp_defaults, world, layout) + ui_upper_mode(world.rdp_defaults, world, layout) + ui_lower_mode(world.rdp_defaults, world, layout) + +### Node Categories ### +# Node categories are a python system for automatically +# extending the Add menu, toolbar panels and search operator. +# For more examples see release/scripts/startup/nodeitems_builtins.py + +# all categories in a list +node_categories = [ + # identifier, label, items list + NodeCategory('CUSTOM', 'Custom', items = [ + NodeItem("GetAlphaFromColor",label="Get Alpha From Color", settings={}), + ]), + NodeCategory('FAST3D', "Fast3D", items=[ + # the node item can have additional settings, + # which are applied to new nodes + # NB: settings values are stored as string expressions, + # for this reason they should be converted to strings using repr() + NodeItem("Fast3D_A", label="A", settings={}), + NodeItem("Fast3D_B", label="B", settings={}), + NodeItem("Fast3D_C", label="C", settings={}), + NodeItem("Fast3D_D", label="D", settings={}), + NodeItem("Fast3D_A_alpha", label="A Alpha", settings={}), + NodeItem("Fast3D_B_alpha", label="B Alpha", settings={}), + NodeItem("Fast3D_C_alpha", label="C Alpha", settings={}), + NodeItem("Fast3D_D_alpha", label="D Alpha", settings={}), + ''' + NodeItem("Test_NodeType", label="Full", settings={ + "my_string_prop": repr("consectetur adipisicing elit"), + "my_float_prop": repr(2.0), + }), + NodeItem("Fast3D_NodeType", label="Shaded Texture", settings={ + "inA": repr('1'), "inB": repr('8'), "inC": repr('4'), + "inD": repr('7'), "inA_alpha": repr('7'), "inB_alpha": repr('7'), + "inC_alpha": repr('7'), "inD_alpha": repr('5'), + }), + NodeItem("Fast3DSplitter_NodeType", label="Splitter", settings={ + }), + ''' + ]), +] + +classes = ( + #MyCustomSocket, + #MyCustomNode, + #F3DNode, + #F3DOptionSplitterNode, + F3DNodeA, + F3DNodeB, + F3DNodeC, + F3DNodeD, + F3DNodeA_alpha, + F3DNodeB_alpha, + F3DNodeC_alpha, + F3DNodeD_alpha, + F3DPanel, + CreateFast3DMaterial, + GetAlphaFromColor, + TextureFieldProperty, + TextureProperty, + CombinerProperty, + ProceduralAnimProperty, + ProcAnimVectorProperty, + RDPSettings, + DefaultRDPSettingsPanel + #F3DLightCollectionProperty, +) + +global_time = 0 +def set_global_time(): + global global_time + timestep = 0.033333 + global_time = (global_time + timestep) % sys.maxsize + for material in bpy.data.materials: + if material.is_f3d: + nodes = material.node_tree.nodes + if 'Global Time' in nodes: + nodes['Global Time'].outputs[0].default_value = \ + global_time + return timestep + +@persistent +def loadTimer(param): + bpy.app.timers.register(set_global_time) + +def register(): + #bpy.app.handlers.load_post.append(loadTimer) + for cls in classes: + register_class(cls) + + nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories) + + bpy.types.Material.f3d_preset = bpy.props.EnumProperty(name = 'F3D Preset', + items = enumMaterialPresets, default = 'Custom', + update = update_preset) + + bpy.types.Scene.update_flag = bpy.props.BoolProperty() + bpy.types.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.Material.scale_autoprop = bpy.props.BoolProperty( + name = 'Auto Set Scale', default = True, + update = update_node_values) + bpy.types.Material.uv_basis = bpy.props.EnumProperty( + name = 'UV Basis', default = 'TEXEL0', + update = update_tex_values, items = enumTexUV) + + # Combiners + bpy.types.Material.combiner1 = bpy.props.PointerProperty(type = \ + CombinerProperty) + bpy.types.Material.combiner2 = bpy.props.PointerProperty(type = \ + CombinerProperty) + + # Texture animation + bpy.types.Material.menu_procAnim = bpy.props.BoolProperty() + bpy.types.Material.positionAnim = bpy.props.PointerProperty( + type = ProcAnimVectorProperty) + bpy.types.Material.UVanim_tex0 = bpy.props.PointerProperty( + type = ProcAnimVectorProperty) + bpy.types.Material.UVanim_tex1 = bpy.props.PointerProperty( + type = ProcAnimVectorProperty) + bpy.types.Material.colorAnim = bpy.props.PointerProperty( + type = ProcAnimVectorProperty) + + # material textures + bpy.types.Material.tex_scale = bpy.props.FloatVectorProperty( + min = 0, max = 1, size = 2, default = (1,1), step = 1, + update = update_tex_values) + bpy.types.Material.tex0 = bpy.props.PointerProperty(type = TextureProperty) + bpy.types.Material.tex1 = bpy.props.PointerProperty(type = TextureProperty) + + # Should Set? + bpy.types.Material.is_f3d = bpy.props.BoolProperty() + bpy.types.Material.f3d_update_flag = bpy.props.BoolProperty() + bpy.types.Material.set_prim = bpy.props.BoolProperty(default = True, + update = update_node_values) + bpy.types.Material.set_lights = bpy.props.BoolProperty(default = True, + update = update_node_values) + bpy.types.Material.set_env = bpy.props.BoolProperty(default = False, + update = update_node_values) + bpy.types.Material.set_key = bpy.props.BoolProperty(default = True, + update = update_node_values) + bpy.types.Material.set_k0_5 = bpy.props.BoolProperty(default = True, + update = update_node_values) + bpy.types.Material.set_combiner = bpy.props.BoolProperty(default = True, + update = update_node_values) + + # Chroma + bpy.types.Material.key_scale = bpy.props.FloatVectorProperty( + name = 'Key Scale', min = 0, max = 1, step = 1, + update = update_node_values) + bpy.types.Material.key_width = bpy.props.FloatVectorProperty( + name = 'Key Width', min = 0, max = 16, + update = update_node_values) + + # Convert + bpy.types.Material.k0 = bpy.props.FloatProperty(min = -1, max = 1, + default = 175/255, step = 1, update = update_node_values) + bpy.types.Material.k1 = bpy.props.FloatProperty(min = -1, max = 1, + default = -43/255, step = 1, update = update_node_values) + bpy.types.Material.k2 = bpy.props.FloatProperty(min = -1, max = 1, + default = -89/255, step = 1, update = update_node_values) + bpy.types.Material.k3 = bpy.props.FloatProperty(min = -1, max = 1, + default = 222/255, step = 1, update = update_node_values) + bpy.types.Material.k4 = bpy.props.FloatProperty(min = -1, max = 1, + default = 114/255, step = 1, update = update_node_values) + bpy.types.Material.k5 = bpy.props.FloatProperty(min = -1, max = 1, + default = 42/255, step = 1, update = update_node_values) + + # Prim + bpy.types.Material.prim_lod_frac = bpy.props.FloatProperty( + name = 'Prim LOD Frac', min = 0, max = 1, step = 1, + update = update_node_values) + bpy.types.Material.prim_lod_min = bpy.props.FloatProperty( + name = 'Min LOD Ratio', min = 0, max = 1, step = 1, + update = update_node_values) + + # lights + bpy.types.Material.f3d_light1 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light2 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light3 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light4 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light5 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light6 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + bpy.types.Material.f3d_light7 = bpy.props.PointerProperty( + type = bpy.types.Light, update = F3DOrganizeLights) + + # Fog Properties + bpy.types.Material.fog_color = bpy.props.FloatVectorProperty( + name = 'Fog Color', subtype='COLOR', size = 4, min = 0, max = 1, default = (0,0,0,1)) + bpy.types.Material.fog_position = bpy.props.IntVectorProperty( + name = 'Fog Range', size = 2, min = 0, max = 1000, default = (500,1000)) + bpy.types.Material.set_fog = bpy.props.BoolProperty() + + # geometry mode + bpy.types.Material.menu_geo = bpy.props.BoolProperty() + bpy.types.Material.menu_upper = bpy.props.BoolProperty() + bpy.types.Material.menu_lower = bpy.props.BoolProperty() + bpy.types.Material.menu_lower_render = bpy.props.BoolProperty() + bpy.types.Material.set_rendermode = bpy.props.BoolProperty(default = False) + bpy.types.Material.rdp_settings = bpy.props.PointerProperty( + type = RDPSettings) + +def unregister(): + nodeitems_utils.unregister_node_categories('CUSTOM_NODES') + for cls in reversed(classes): + unregister_class(cls) + +if __name__ == "__main__": + register() + +#from .f3d_material import * + +# Presets +sm64_unlit_texture = F3DMaterialSettings() +sm64_unlit_texture.color_combiner = tuple(S_UNLIT_TEX + S_UNLIT_TEX) +sm64_unlit_texture.set_env = False + +sm64_shaded_texture = F3DMaterialSettings() +sm64_shaded_texture.color_combiner = tuple(S_SHADED_TEX + S_SHADED_TEX) +sm64_shaded_texture.set_env = False + +sm64_shaded_solid = F3DMaterialSettings() +sm64_shaded_solid.color_combiner = tuple(S_SHADED_SOLID + S_SHADED_SOLID) +sm64_shaded_solid.set_env = False + +sm64_shaded_texture_cutout = F3DMaterialSettings() +sm64_shaded_texture_cutout.color_combiner = \ + tuple(S_SHADED_TEX_CUTOUT + S_SHADED_TEX_CUTOUT) +sm64_shaded_texture_cutout.set_env = False +sm64_shaded_texture_cutout.g_cull_back = False + +sm64_unlit_env_map = F3DMaterialSettings() +sm64_unlit_env_map.color_combiner = tuple(S_UNLIT_TEX + S_UNLIT_TEX) +sm64_unlit_env_map.g_tex_gen = True +sm64_unlit_env_map.set_env = False + +sm64_decal = F3DMaterialSettings() +sm64_decal.color_combiner = tuple(S_UNLIT_DECAL_ON_SHADED_SOLID + \ + S_UNLIT_DECAL_ON_SHADED_SOLID) +sm64_decal.set_env = False + +sm64_vert_colored_tex = F3DMaterialSettings() +sm64_vert_colored_tex.color_combiner = tuple(S_VERTEX_COLORED_TEX + \ + S_VERTEX_COLORED_TEX) +sm64_vert_colored_tex.g_lighting = False +sm64_vert_colored_tex.set_env = False + +sm64_prim_transparent_shade = F3DMaterialSettings() +sm64_prim_transparent_shade.color_combiner = tuple(S_PRIM_TRANSPARENT_SHADE + \ + S_PRIM_TRANSPARENT_SHADE) +sm64_prim_transparent_shade.set_env = False +sm64_prim_transparent_shade.g_cull_back = False + +enumMaterialPresets = [ + ('Custom', 'Custom', 'Custom'), + ('Unlit Texture', 'Unlit Texture', 'Unlit Texture'), + ('Shaded Solid', 'Shaded Solid', 'Shaded Solid'), + ('Decal On Shaded Solid', 'Decal On Shaded Solid', 'Decal On Shaded Solid'), + ('Shaded Texture', 'Shaded Texture', 'Shaded Texture'), + ('Shaded Texture Cutout', 'Shaded Texture Cutout', 'Shaded Texture Cutout'), + ('Shaded Texture Transparent', 'Shaded Texture Transparent', 'Shaded Texture Transparent'), + ('Vertex Colored Texture', 'Vertex Colored Texture', 'Vertex Colored Texture'), + ('Environment Mapped', 'Environment Mapped', 'Environment Mapped'), +] + +materialPresetDict = { + 'Unlit Texture' : sm64_unlit_texture, + 'Shaded Solid' : sm64_shaded_solid, + 'Shaded Texture' : sm64_shaded_texture, + 'Shaded Texture Cutout' : sm64_shaded_texture_cutout, + 'Shaded Texture Transparent' : sm64_prim_transparent_shade, + 'Environment Mapped' : sm64_unlit_env_map, + 'Decal On Shaded Solid' : sm64_decal, + 'Vertex Colored Texture' : sm64_vert_colored_tex, + +} \ No newline at end of file diff --git a/fast64_internal/f3d_material_nodes.py b/fast64_internal/f3d_material_nodes.py new file mode 100644 index 0000000..0458022 --- /dev/null +++ b/fast64_internal/f3d_material_nodes.py @@ -0,0 +1,1157 @@ +import bpy +import math +import mathutils +from bpy.types import Node, NodeSocket, NodeSocketInterface, ShaderNode, ShaderNodeGroup, Panel +import nodeitems_utils +from nodeitems_utils import NodeCategory, NodeItem +from .f3d_gbi import F3D +from .f3d_enums import * +from bpy.utils import register_class, unregister_class + +def addNodeAt(node_tree, name, label, x, y): + node = node_tree.nodes.new(name) + if label is not None: + node.label = label + node.name = label + node.location = (x,y) + if label == '1': + node.outputs[0].default_value = 1 + elif label == '0': + node.outputs[0].default_value = 0 + elif label == 'Shade Color': + node.label = 'Shade Shader' + colorNode = node_tree.nodes.new('ShaderNodeShaderToRGB') + node.inputs[0].default_value = (1,1,1,1) + if label is not None: + colorNode.label = label + node_tree.links.new(colorNode.inputs[0], node.outputs[0]) + colorNode.location = (x,y) + node.location = (x - 300, y) + node = colorNode + elif label == 'Environment Color' or label == 'Primitive Color': + alphaSplitNode = node_tree.nodes.new('GetAlphaFromColor') + node_tree.links.new(alphaSplitNode.inputs[0], node.outputs[0]) + alphaSplitNode.location = (x-300, y) + + addNode = node_tree.nodes.new('ShaderNodeMath') + addNode.operation = 'ADD' + addNode.inputs[1].default_value = 0 + node_tree.links.new(addNode.inputs[0], alphaSplitNode.outputs[1]) + addNode.location = (x,y) + + mixNode = node_tree.nodes.new('ShaderNodeMixRGB') + mixNode.inputs[0].default_value = 0 + node_tree.links.new(mixNode.inputs[1], alphaSplitNode.outputs[0]) + mixNode.location = (x,y - 100) + + node.location = (x-600,y) + node = mixNode + elif label == 'Noise': + node.inputs[1].default_value = 40 # scale + node.inputs[2].default_value = 0 # detail + + return (node, x + (node.width + 150), y - (node.height + 100)) + +def addNodeListAt(node_tree, nodeDict, x,y): + newDict = {} + for label, typename in nodeDict.items(): + node, xDiscard, y = addNodeAt(node_tree, typename, label, x, y) + newDict[label] = node + return newDict + +# In 2.8 the Node.update function does not work. +# We can bypass this by adding an update callback to a property in the node. +# However, forcing an output socket update does NOT work when the output +# is a group node input. Thus we must add an add node in between to fix this. +def addNodeListAtWithZeroAddNode(node_tree, nodeDict, x,y, cycleIndex): + newDict = {} + for label, typename in nodeDict.items(): + name = label + " " + str(cycleIndex) + node, nextX, nextY = addNodeAt(node_tree, typename, name, x, y) + bridge, nextX, nextY = addNodeAt( + node_tree, 'ShaderNodeMath', name + ' Bridge', nextX, y) + bridge.operation = 'ADD' + bridge.inputs[1].default_value = 0 + node_tree.links.new(bridge.inputs[0], node.outputs[0]) + newDict[name] = bridge + y = nextY + return newDict + +# Assumes ascending order of cases. +def createNodeSwitch(node_tree, caseDict, caseSocket, caseName, + location, socketDict): + group_tree = bpy.data.node_groups.new(type="ShaderNodeTree", name = 'Switch') + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.location = location + location[1] = location[1] - (groupNode.height + 100) + groupNode.node_tree = group_tree + input_node = group_tree.nodes.new("NodeGroupInput") + output_node = group_tree.nodes.new("NodeGroupOutput") + input_node.location = (-300, 0) + output_node.location = (600, 0) + + # Add case node links + groupNode.inputs.new('NodeSocketInt', caseName) + node_tree.links.new(groupNode.inputs[0], caseSocket) + caseNodeInternal = input_node.outputs[0] + + internalSocketDict = socketDictToInternalSocket(node_tree, + groupNode, input_node, socketDict, 1) + + nodePos = [0,0] + for case in reversed(range (len(caseDict))): + name = caseDict[case] + if case == len(caseDict) - 1: + prevSocket = internalSocketDict[name] + else: + greaterThanNode, mixNodeX, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, *nodePos) + greaterThanNode.operation = 'GREATER_THAN' + mixNode, x, nodePos[1] = addNodeAt(group_tree, + 'ShaderNodeMixRGB', None, mixNodeX, nodePos[1]) + + group_tree.links.new(greaterThanNode.inputs[0], caseNodeInternal) + group_tree.links.new(mixNode.inputs[0], + greaterThanNode.outputs[0]) + greaterThanNode.inputs[1].default_value = case + + # Connect group input to nodes + group_tree.links.new(mixNode.inputs[1], + internalSocketDict[name]) + group_tree.links.new(mixNode.inputs[2], + prevSocket) + + prevSocket = mixNode.outputs[0] + + group_tree.links.new(output_node.inputs[0], prevSocket) + + return groupNode + +def createNodeCombiner(node_tree, nodeA, nodeB, nodeC, nodeD, location, isAlpha): + # (A-B)*C + D + group_tree = bpy.data.node_groups.new(type="ShaderNodeTree", name = 'Combiner') + input_node = group_tree.nodes.new("NodeGroupInput") + input_node.location = (-300, 0) + output_node = group_tree.nodes.new("NodeGroupOutput") + output_node.location = (600, 0) + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.node_tree = group_tree + groupNode.location = location + location[1] = location[1] - (groupNode.height + 100) + + # Add input source to group input + socketType = 'NodeSocketColor' if not isAlpha else 'NodeSocketFloat' + groupNode.inputs.new(socketType, 'A') + inputA = groupNode.inputs[0] + node_tree.links.new(inputA, nodeA.outputs[0]) + + groupNode.inputs.new(socketType, 'B') + inputB = groupNode.inputs[1] + node_tree.links.new(inputB, nodeB.outputs[0]) + + groupNode.inputs.new(socketType, 'C') + inputC = groupNode.inputs[2] + node_tree.links.new(inputC, nodeC.outputs[0]) + + groupNode.inputs.new(socketType, 'D') + inputD = groupNode.inputs[3] + node_tree.links.new(inputD, nodeD.outputs[0]) + + nodePos = [0,0] + if not isAlpha: + nodeSubtract, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMixRGB', 'Subtract', *nodePos) + nodeMultiply, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMixRGB', 'Multiply', *nodePos) + nodeAdd, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMixRGB', 'Add', *nodePos) + + nodeSubtract.blend_type = 'SUBTRACT' + nodeMultiply.blend_type = 'MULTIPLY' + nodeAdd.blend_type = 'ADD' + + nodeSubtract.inputs['Fac'].default_value = 1 + nodeMultiply.inputs['Fac'].default_value = 1 + nodeAdd.inputs['Fac'].default_value = 1 + else: + nodeSubtract, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMath', 'Subtract', *nodePos) + nodeMultiply, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMath', 'Multiply', *nodePos) + nodeAdd, x, nodePos[1] = \ + addNodeAt(group_tree, 'ShaderNodeMath', 'Add', *nodePos) + + nodeSubtract.operation = 'SUBTRACT' + nodeMultiply.operation = 'MULTIPLY' + nodeAdd.operation = 'ADD' + + index1 = 1 if not isAlpha else 0 + index2 = 2 if not isAlpha else 1 + group_tree.links.new(nodeSubtract.inputs[index1], input_node.outputs[0]) + group_tree.links.new(nodeSubtract.inputs[index2], input_node.outputs[1]) + group_tree.links.new(nodeMultiply.inputs[index1], nodeSubtract.outputs[0]) + group_tree.links.new(nodeMultiply.inputs[index2], input_node.outputs[2]) + group_tree.links.new(nodeAdd.inputs[index1], nodeMultiply.outputs[0]) + group_tree.links.new(nodeAdd.inputs[index2], input_node.outputs[3]) + group_tree.links.new(output_node.inputs[0], nodeAdd.outputs[0]) + + return groupNode + +def createNodeFinal(node_tree, caseNodeDict, nodeDict, texAlphaList, + texAlphaWithBridgeList, cycleIndex): + group_tree = bpy.data.node_groups.new( + type="ShaderNodeTree", name = 'Color Combiner') + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.node_tree = group_tree + + input_node = group_tree.nodes.new("NodeGroupInput") + input_node.location = (-300, 0) + output_node = group_tree.nodes.new("NodeGroupOutput") + output_node.location = (900, 0) + + caseSocketDict, nextIndex = \ + nodeDictToInternalSocket(node_tree, groupNode, input_node, + caseNodeDict, [], [], 0) + + socketDict, nextIndex = \ + nodeDictToInternalSocket(node_tree, groupNode, input_node, + nodeDict, texAlphaList, texAlphaWithBridgeList, nextIndex) + + nodePos = [300, 0] + + caseNodes = {} + for name, socket in caseSocketDict.items(): + caseNodes[name] = createNodeSwitch(group_tree, + [item[1] for item in combiner_enums[name[:-2]]], + socket, name , nodePos, socketDict) + + nodePos = [600, 0] + out1 = createNodeCombiner( + group_tree, caseNodes['Case A ' + str(cycleIndex)], caseNodes['Case B ' + str(cycleIndex)], + caseNodes['Case C ' + str(cycleIndex)], caseNodes['Case D ' + str(cycleIndex)], nodePos, False) + out_alpha1 = createNodeCombiner(group_tree, caseNodes['Case A Alpha ' + str(cycleIndex)], + caseNodes['Case B Alpha ' + str(cycleIndex)], caseNodes['Case C Alpha ' + str(cycleIndex)], + caseNodes['Case D Alpha ' + str(cycleIndex)], nodePos, True) + + groupNode.outputs.new('NodeSocketColor', 'Color Combiner') + groupNode.outputs.new('NodeSocketFloat', 'Color Combiner Alpha') + group_tree.links.new(output_node.inputs[0], out1.outputs[0]) + group_tree.links.new(output_node.inputs[1], out_alpha1.outputs[0]) + + return groupNode + +def createNodeToGroupLink(node, outputIndex, groupNode, groupInputNode, + node_tree, nodeIndex, name): + return createSocketToGroupLink(node.outputs[outputIndex], groupNode, + groupInputNode, node_tree, nodeIndex, name) + +def createSocketToGroupLink(socket, groupNode, groupInputNode, + node_tree, nodeIndex, name): + inputType = str(type(socket))[18:-2] # convert class to string + groupNode.inputs.new(inputType, name) + nodeExternal = groupNode.inputs[nodeIndex] + node_tree.links.new(nodeExternal, socket) + + nodeInternal = groupInputNode.outputs[nodeIndex] + return nodeInternal + +def nodeDictToInternalSocket(node_tree, groupNode, groupInputNode, nodeDict, + texAlphaList, texAlphaWithBridgeList, startIndex): + nodeIndex = startIndex + newDict = {} + for name, node in nodeDict.items(): + newDict[name] = createNodeToGroupLink(node, 0 if name != 'Noise' else 1, + groupNode, groupInputNode, node_tree, nodeIndex, name) + nodeIndex += 1 + if name in texAlphaList: + newDict[name + " Alpha"] = createNodeToGroupLink(node, 1, groupNode, + groupInputNode, node_tree, nodeIndex, name + " Alpha") + nodeIndex += 1 + elif name in texAlphaWithBridgeList: + alphaSplitNode = node.inputs[1].links[0].from_socket.node + bridgeNode = alphaSplitNode.outputs[1].links[0].to_socket.node + newDict[name + " Alpha"] = createNodeToGroupLink(bridgeNode, 0, + groupNode, groupInputNode, node_tree, nodeIndex, + name + " Alpha") + nodeIndex += 1 + + return newDict, nodeIndex + +def socketDictToInternalSocket(node_tree, groupNode, groupInputNode, socketDict, + startIndex): + nodeIndex = startIndex + newDict = {} + for name, socket in socketDict.items(): + newDict[name] = createSocketToGroupLink(socket, groupNode, + groupInputNode, node_tree, nodeIndex, name) + nodeIndex += 1 + + return newDict + +def createTexCoordNode(node_tree, location, uvSocket): + group_tree = bpy.data.node_groups.new( + type="ShaderNodeTree", name = 'Create Tex Coord') + links = group_tree.links + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.location = location + groupNode.name = 'Create Tex Coord' + location[1] = location[1] - (groupNode.height + 100) + groupNode.node_tree = group_tree + input_node = group_tree.nodes.new("NodeGroupInput") + output_node = group_tree.nodes.new("NodeGroupOutput") + input_node.location = (-800, 0) + output_node.location = (2800, 0) + + output_node.inputs.new('NodeSocketVector', 'UV') + + uvSocket = \ + createSocketToGroupLink(uvSocket, groupNode, + input_node, node_tree, 0, 'UV') + + normLNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Normalized L', -600, 0) + normLSocket = normLNode.outputs[0] + normLSocket.default_value = 0 + + normHNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Normalized H', -600, y) + normHSocket = normHNode.outputs[0] + normHSocket.default_value = 1 + + clampNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Clamp', -600, y) + clampSocket = clampNode.outputs[0] + clampSocket.default_value = 0 + + normMaskNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Normalized Mask', -600, y) + normMaskSocket = normMaskNode.outputs[0] + normMaskSocket.default_value = 1 + + mirrorNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Mirror', -600, y) + mirrorSocket = mirrorNode.outputs[0] + mirrorSocket.default_value = 0 + + shiftNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Shift', -600, y) + shiftSocket = shiftNode.outputs[0] + shiftSocket.default_value = 0 + + scaleNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue', 'Scale', -600, y) + scaleSocket = scaleNode.outputs[0] + scaleSocket.default_value = 1 + + # Makes sure clamp works correctly + normHalfPixelNode, x, y = \ + addNodeAt(group_tree, 'ShaderNodeValue','Normalized Half Pixel',-600, y) + normHalfPixelSocket = normHalfPixelNode.outputs[0] + normHalfPixelSocket.default_value = 1 / 64 + + # Apply shift + shiftPower, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, -400, 0) + shiftPower.operation = "POWER" + shiftPower.inputs[0].default_value = 0.5 + links.new(shiftPower.inputs[1], shiftSocket) + + shiftMult, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, -400, -200) + shiftMult.operation = 'MULTIPLY' + links.new(shiftMult.inputs[0], uvSocket) + links.new(shiftMult.inputs[1], shiftPower.outputs[0]) + + # Apply scale + scaleMult, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, -200,-200) + scaleMult.operation = 'MULTIPLY' + links.new(scaleMult.inputs[0], shiftMult.outputs[0]) + links.new(scaleMult.inputs[1], scaleSocket) + + # Add L + # offsetting by L means subtracting L from UV + addL, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 0, 0) + addL.operation = 'SUBTRACT' + links.new(addL.inputs[0], scaleMult.outputs[0]) + links.new(addL.inputs[1], normLSocket) + + # Clamp using H + clampLow, x, y = addNodeAt(group_tree, 'ShaderNodeMath', + 'Max of NOT zero', 200, 0) + clampLow.operation = 'MAXIMUM' + clampLow.inputs[0].default_value = 0.0000001 # so negative clamping works + links.new(clampLow.inputs[0], normHalfPixelSocket) + links.new(clampLow.inputs[1], addL.outputs[0]) + + clampHighHalfPixelOffset, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 400, 200) + clampHighHalfPixelOffset.operation = 'SUBTRACT' + links.new(clampHighHalfPixelOffset.inputs[0], normHSocket) + links.new(clampHighHalfPixelOffset.inputs[1], normHalfPixelSocket) + + clampHigh, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 400, 0) + clampHigh.operation = 'MINIMUM' + links.new(clampHigh.inputs[0], clampHighHalfPixelOffset.outputs[0]) + links.new(clampHigh.inputs[1], clampLow.outputs[0]) + + clampMix, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 600, 0) + links.new(clampMix.inputs[0], clampSocket) + links.new(clampMix.inputs[1], addL.outputs[0]) + links.new(clampMix.inputs[2], clampHigh.outputs[0]) + + # Apply mask + maskPositive, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 800,-400) + maskPositive.operation = "MODULO" + links.new(maskPositive.inputs[0], clampMix.outputs[0]) + links.new(maskPositive.inputs[1], normMaskSocket) + + ifNegative, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 800, 0) + ifNegative.operation = 'LESS_THAN' + links.new(ifNegative.inputs[0], clampMix.outputs[0]) + ifNegative.inputs[1].default_value = 0 + + maskNegative, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 800,-200) + links.new(maskNegative.inputs[0], normMaskSocket) + links.new(maskNegative.inputs[1], maskPositive.outputs[0]) + + maskSignMix, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB',None,1000,-200) + links.new(maskSignMix.inputs[0], ifNegative.outputs[0]) + links.new(maskSignMix.inputs[1], maskPositive.outputs[0]) + links.new(maskSignMix.inputs[2], maskNegative.outputs[0]) + + # Apply mirror + mirrorAbs, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 1000,-600) + mirrorAbs.operation = "ABSOLUTE" + links.new(mirrorAbs.inputs[0], clampMix.outputs[0]) + mirrorAbs.inputs[1].default_value = 0 + + mirrorMaskDiv, x, y = addNodeAt(group_tree, 'ShaderNodeMath',None,1200,-600) + mirrorMaskDiv.operation = 'DIVIDE' + links.new(mirrorMaskDiv.inputs[0], mirrorAbs.outputs[0]) + links.new(mirrorMaskDiv.inputs[1], normMaskSocket) + + mirrorFloor, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 1400,-600) + mirrorFloor.operation = 'FLOOR' + links.new(mirrorFloor.inputs[0], mirrorMaskDiv.outputs[0]) + mirrorFloor.inputs[1].default_value = 0 + + mirrorMod, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 1600,-600) + mirrorMod.operation = 'MODULO' + links.new(mirrorMod.inputs[0], mirrorFloor.outputs[0]) + mirrorMod.inputs[1].default_value = 2 + + mirrorSub, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 1600,-800) + mirrorSub.operation = "SUBTRACT" + mirrorSub.inputs[0].default_value = 1 + links.new(mirrorSub.inputs[1], mirrorMod.outputs[0]) + + mirrorToggleMix, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', + None, 1800, -600) + links.new(mirrorToggleMix.inputs[0], ifNegative.outputs[0]) + links.new(mirrorToggleMix.inputs[1], mirrorMod.outputs[0]) + links.new(mirrorToggleMix.inputs[2], mirrorSub.outputs[0]) + + mirrorCheck, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 2000,-400) + mirrorCheck.operation = 'MULTIPLY' + links.new(mirrorCheck.inputs[0], mirrorToggleMix.outputs[0]) + links.new(mirrorCheck.inputs[1], mirrorSocket) + + mirrored, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 2000,-200) + mirrored.operation = 'SUBTRACT' + links.new(mirrored.inputs[0], normMaskSocket) + links.new(mirrored.inputs[1], maskSignMix.outputs[0]) + + mirrorMix, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 2200,-200) + links.new(mirrorMix.inputs[0], mirrorCheck.outputs[0]) + links.new(mirrorMix.inputs[1], maskSignMix.outputs[0]) + links.new(mirrorMix.inputs[2], mirrored.outputs[0]) + + # Handle 0 Mask + check0Mask, x, y = addNodeAt(group_tree, 'ShaderNodeMath', None, 800,-1000) + check0Mask.operation = 'GREATER_THAN' + links.new(check0Mask.inputs[0], normMaskSocket) + check0Mask.inputs[1].default_value = 0 + + mix0Mask, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 2400,-200) + links.new(mix0Mask.inputs[0], check0Mask.outputs[0]) + links.new(mix0Mask.inputs[1], clampHigh.outputs[0]) + links.new(mix0Mask.inputs[2], mirrorMix.outputs[0]) + + # Output + links.new(output_node.inputs[0], mix0Mask.outputs[0]) + return groupNode + +def createUVNode(node_tree, location, texGenNode, texGenLinearNode): + group_tree = bpy.data.node_groups.new(type="ShaderNodeTree", name = 'Get UV') + links = group_tree.links + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.location = location + groupNode.name = 'Get UV' + location[1] = location[1] - (groupNode.height + 100) + groupNode.node_tree = group_tree + input_node = group_tree.nodes.new("NodeGroupInput") + output_node = group_tree.nodes.new("NodeGroupOutput") + input_node.location = (-300, 0) + output_node.location = (2400, 0) + + output_node.inputs.new('NodeSocketVector', 'UV') + + internalTexGenSocket = \ + createSocketToGroupLink(texGenNode.outputs[0], groupNode, + input_node, node_tree, 0, 'Texture Generation Flag') + internalTexGenLinearSocket = \ + createSocketToGroupLink(texGenLinearNode.outputs[0], groupNode, + input_node, node_tree, 1, 'Texture Generation Linear Flag') + + # Regular UVs + UVMapNode, x, y = addNodeAt(group_tree, 'ShaderNodeUVMap', None, 1200, 0) + UVMapNode.uv_map = 'UVMap' + + # Texture size + heightNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeValue", 'Image Height Factor', + -300, -400) + widthNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeValue", 'Image Width Factor', + -300, -200) + heightNode.outputs[0].default_value = 1024 / 32 + widthNode.outputs[0].default_value = 1024 / 32 + + # Get normal + geometryNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeNewGeometry", None, 0, 0) + + # Convert to screen space normal + transformNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeVectorTransform", None, 0, y) + transformNode.convert_from = 'WORLD' + transformNode.convert_to = 'CAMERA' + transformNode.vector_type = 'NORMAL' + links.new(transformNode.inputs[0], geometryNode.outputs[1]) + + # Convert [-1,1] to [0,1] + addOneNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeVectorMath", None, 0, y) + addOneNode.inputs[1].default_value = (1,1,1) + links.new(addOneNode.inputs[0], transformNode.outputs[0]) + + separateNode, x, y = \ + addNodeAt(group_tree, "ShaderNodeSeparateXYZ", None, 0, y) + links.new(separateNode.inputs[0], addOneNode.outputs[0]) + + divideTwoX, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 300, 200) + divideTwoY, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 300, y) + divideTwoX.operation = 'DIVIDE' + divideTwoY.operation = 'DIVIDE' + divideTwoX.inputs[1].default_value = -2 # Must be negative (env, not sphere) + divideTwoY.inputs[1].default_value = -2 + links.new(divideTwoX.inputs[0], separateNode.outputs[0]) + links.new(divideTwoY.inputs[0], separateNode.outputs[1]) + + # Normalize values based on tex size. + normalizeX, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 300, y) + normalizeY, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 300, y) + normalizeX.operation = 'MULTIPLY' + normalizeY.operation = 'MULTIPLY' + links.new(normalizeX.inputs[0], divideTwoX.outputs[0]) + links.new(normalizeY.inputs[0], divideTwoY.outputs[0]) + links.new(normalizeX.inputs[1], widthNode.outputs[0]) + links.new(normalizeY.inputs[1], heightNode.outputs[0]) + + # Get UVs for tex gen, scaled by texture scale. + + texGenCombine, x, y = \ + addNodeAt(group_tree, 'ShaderNodeCombineXYZ', None, 1200, -300) + links.new(texGenCombine.inputs[0], normalizeX.outputs[0]) + links.new(texGenCombine.inputs[1], normalizeY.outputs[0]) + + # Get UVs for tex gen linear, scaled by texture scale. + texGenLinearAcosX, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 600, -600) + texGenLinearAcosY, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 600, y) + texGenLinearAcosX.operation = 'ARCCOSINE' + texGenLinearAcosY.operation = 'ARCCOSINE' + links.new(texGenLinearAcosX.inputs[0], divideTwoX.outputs[0]) + links.new(texGenLinearAcosY.inputs[0], divideTwoY.outputs[0]) + texGenLinearAcosX.inputs[1].default_value = 0 + texGenLinearAcosY.inputs[1].default_value = 0 + + # Normalize values based on tex size. + normalizeLinearX, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 600, y) + normalizeLinearY, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMath', None, 600, y) + normalizeLinearX.operation = 'MULTIPLY' + normalizeLinearY.operation = 'MULTIPLY' + links.new(normalizeLinearX.inputs[0], texGenLinearAcosX.outputs[0]) + links.new(normalizeLinearY.inputs[0], texGenLinearAcosY.outputs[0]) + links.new(normalizeLinearX.inputs[1], widthNode.outputs[0]) + links.new(normalizeLinearY.inputs[1], heightNode.outputs[0]) + + texGenLinearCombine, x, y = \ + addNodeAt(group_tree, 'ShaderNodeCombineXYZ', None, 1200, -600) + links.new(texGenLinearCombine.inputs[0], normalizeLinearX.outputs[0]) + links.new(texGenLinearCombine.inputs[1], normalizeLinearY.outputs[0]) + + # Mix UV based on flags + mixTexGen, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 1500, 0) + links.new(mixTexGen.inputs[0], internalTexGenSocket) + links.new(mixTexGen.inputs[1], UVMapNode.outputs[0]) + links.new(mixTexGen.inputs[2], texGenCombine.outputs[0]) + + mixTexGenLinear, x, y = \ + addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 1500, y) + links.new(mixTexGenLinear.inputs[0], internalTexGenLinearSocket) + links.new(mixTexGenLinear.inputs[1], mixTexGen.outputs[0]) + links.new(mixTexGenLinear.inputs[2], texGenLinearCombine.outputs[0]) + + # Apply tile attributes + uvSplit, x, y = \ + addNodeAt(group_tree, 'ShaderNodeSeparateXYZ', None, 1800, 500) + links.new(uvSplit.inputs[0], mixTexGenLinear.outputs[0]) + + uv_xNode = createTexCoordNode(group_tree, [2000, 500], uvSplit.outputs[0]) + uv_yNode = createTexCoordNode(group_tree, [2000, 300], uvSplit.outputs[1]) + + links.new(uv_xNode.inputs[0], uvSplit.outputs[0]) + links.new(uv_yNode.inputs[0], uvSplit.outputs[1]) + + uvCombine, x, y = \ + addNodeAt(group_tree, 'ShaderNodeCombineXYZ', None, 2200, 500) + + links.new(uvCombine.inputs[0], uv_xNode.outputs[0]) + links.new(uvCombine.inputs[1], uv_yNode.outputs[0]) + + links.new(output_node.inputs[0], uvCombine.outputs[0]) + return groupNode + +def createShadeNode(node_tree, location, shadingNode, lightingNode, ambientNode): + group_tree = bpy.data.node_groups.new(type="ShaderNodeTree", + name = 'Get Shade Color') + links = group_tree.links + groupNode = node_tree.nodes.new("ShaderNodeGroup") + groupNode.name = 'Shade Color' + groupNode.label = 'Shade Color' + groupNode.location = location + location[1] = location[1] - (groupNode.height + 100) + groupNode.node_tree = group_tree + input_node = group_tree.nodes.new("NodeGroupInput") + output_node = group_tree.nodes.new("NodeGroupOutput") + input_node.location = (-300, 0) + output_node.location = (600, 0) + + shadingNodeInternal = createSocketToGroupLink(shadingNode.outputs[0], + groupNode, input_node, node_tree, 0, 'Shading') + lightingNodeInternal = createSocketToGroupLink(lightingNode.outputs[0], + groupNode, input_node, node_tree, 1, 'Lighting') + ambientInternal = createSocketToGroupLink(ambientNode.outputs[0], + groupNode, input_node, node_tree, 2, 'Ambient Color') + groupNode.outputs.new('NodeSocketColor', 'Color') + groupNode.outputs.new('NodeSocketFloat', 'Alpha') + + diffuseNode, x, y = addNodeAt(group_tree, 'ShaderNodeBsdfDiffuse', None, 0, 0) + colorNode, x, y = addNodeAt(group_tree, 'ShaderNodeRGB', None, 0, y) + toRGBNode, x, y = addNodeAt(group_tree, 'ShaderNodeShaderToRGB', None, 200, 0) + vertColorNode, x, y = addNodeAt(group_tree,'ShaderNodeAttribute',None, 200, y) + vertAlphaNode, x, y = addNodeAt(group_tree,'ShaderNodeAttribute',None, 200, y) + + addAmbient, x, y = \ + addNodeAt(group_tree, 'ShaderNodeVectorMath', None, 400, 0) + + mixRGB, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 600, 0) + mixRGBShadeless, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None,600, y) + mixAlpha, x, y = addNodeAt(group_tree, 'ShaderNodeMixRGB', None, 600, y) + + colorNode.outputs[0].default_value = (1,1,1,1) + vertColorNode.attribute_name = 'Col' + vertAlphaNode.attribute_name = 'Alpha' + + #links.new(diffuseNode.inputs[0], ambientInternal) + links.new(toRGBNode.inputs[0], diffuseNode.outputs[0]) + links.new(addAmbient.inputs[0], ambientInternal) + links.new(addAmbient.inputs[1], toRGBNode.outputs[0]) + links.new(mixRGB.inputs[0], lightingNodeInternal) + links.new(mixRGB.inputs[1], vertColorNode.outputs[0]) + links.new(mixRGB.inputs[2], addAmbient.outputs[0]) + + links.new(mixRGBShadeless.inputs[0], shadingNodeInternal) + links.new(mixRGBShadeless.inputs[1], colorNode.outputs[0]) + links.new(mixRGBShadeless.inputs[2], mixRGB.outputs[0]) + + links.new(mixAlpha.inputs[0], lightingNodeInternal) + links.new(mixAlpha.inputs[1], vertAlphaNode.outputs[2]) + links.new(mixAlpha.inputs[2], toRGBNode.outputs[1]) + + links.new(output_node.inputs[0], mixRGBShadeless.outputs[0]) + links.new(output_node.inputs[1], mixAlpha.outputs[0]) + + return groupNode + +def createTexFormatNodes(node_tree, location, index, nodeDict): + # Add texture format mixes + nodes = node_tree.nodes + links = node_tree.links + + greyNode, x, y = addNodeAt(node_tree, 'ShaderNodeSeparateHSV', + None, location[0], location[1]) + links.new(greyNode.inputs[0], nodes['Texture ' + str(index)].outputs[0]) + + isGreyScale, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + "Texture " + str(index) + " Is Greyscale", location[0], y) + isGreyScale.outputs[0].default_value = 0 + hasAlpha, x, y = addNodeAt(node_tree, 'ShaderNodeValue', + 'Texture ' + str(index) + ' Has Alpha', location[0], y) + hasAlpha.outputs[0].default_value = 1 + + greyMix, x, y = addNodeAt(node_tree, 'ShaderNodeMixRGB', + None, location[0], y) + links.new(greyMix.inputs[0], isGreyScale.outputs[0]) + links.new(greyMix.inputs[1], nodes['Texture ' + str(index)].outputs[0]) + links.new(greyMix.inputs[2], greyNode.outputs[2]) + nodeDict['Texture ' + str(index)] = greyMix + + alphaMix, x, y = addNodeAt(node_tree, 'ShaderNodeMixRGB', + None, location[0], y) + links.new(alphaMix.inputs[0], hasAlpha.outputs[0]) + links.new(alphaMix.inputs[2], nodes['Texture ' + str(index)].outputs[1]) + alphaMix.inputs[1].default_value = (1,1,1,1) + nodeDict['Texture ' + str(index) + ' Alpha'] = alphaMix + + return y + +''' +class F3DLightCollectionProperty(bpy.types.PropertyGroup): + light1 : bpy.props.PointerProperty(type = bpy.types.Light) + light2 : bpy.props.PointerProperty(type = bpy.types.Light) + light3 : bpy.props.PointerProperty(type = bpy.types.Light) + light4 : bpy.props.PointerProperty(type = bpy.types.Light) + light5 : bpy.props.PointerProperty(type = bpy.types.Light) + light6 : bpy.props.PointerProperty(type = bpy.types.Light) + light7 : bpy.props.PointerProperty(type = bpy.types.Light) +''' + + +class GetAlphaFromColor(ShaderNode): + + bl_idname = 'GetAlphaFromColor' + # Label for nice name display + bl_label = "Get Alpha From Color" + # Icon identifier + bl_icon = 'NODE' + + def update_GetAlphaFromColor(self, context): + inputSocket = self.inputs[0] + if inputSocket.is_linked: + for link in inputSocket.links: + if link.is_valid: + self.inputs[0].default_value = \ + link.from_socket.default_value + + if len(self.outputs) >= 2: + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = self.inputs[0].default_value + + outAlpha = self.outputs[1] + if outAlpha.is_linked: + for link in outAlpha.links: + if link.is_valid: + link.to_socket.default_value = self.inputs[0].default_value[3] + + inColor : bpy.props.FloatVectorProperty( + name = 'Input Color', subtype='COLOR', size = 4, + update = update_GetAlphaFromColor) + + def init(self, context): + self.inputs.new("NodeSocketColor", "Input Color") + self.outputs.new("NodeSocketColor", "Output Color") + self.outputs.new("NodeSocketFloat", "Output Alpha") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + pass + #layout.prop(self, 'inA') + + def draw_label(self): + return "Get Alpha From Color" + + def update(self): + inputSocket = self.inputs[0] + if inputSocket.is_linked: + for link in inputSocket.links: + if link.is_valid: + self.inputs[0].default_value = \ + link.from_socket.default_value + + if len(self.outputs) >= 2: + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = self.inputs[0].default_value + + outAlpha = self.outputs[1] + if outAlpha.is_linked: + for link in outAlpha.links: + if link.is_valid: + link.to_socket.default_value = self.inputs[0].default_value[3] + +class F3DNodeA(ShaderNode): + + bl_idname = 'Fast3D_A' + # Label for nice name display + bl_label = "Case A" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeA(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inA] + + inA : bpy.props.EnumProperty(name = "A", description = "A", + items = combiner_enums['Case A'], default = 'TEXEL0', update = update_F3DNodeA) + + def init(self, context): + self.outputs.new("NodeSocketInt", "A") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inA') + + def draw_label(self): + return "Fast3D Node A" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inA] + +class F3DNodeB(ShaderNode): + + bl_idname = 'Fast3D_B' + # Label for nice name display + bl_label = "Case B" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeB(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inB] + + inB : bpy.props.EnumProperty(name = "B", description = "B", + items = combiner_enums['Case B'], default = '0', update = update_F3DNodeB) + + def init(self, context): + self.outputs.new("NodeSocketInt", "B") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inB') + + def draw_label(self): + return "Fast3D Node B" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inB] + +class F3DNodeC(ShaderNode): + + bl_idname = 'Fast3D_C' + # Label for nice name display + bl_label = "Case C" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeC(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inC] + + inC : bpy.props.EnumProperty(name = "C", description = "C", + items = combiner_enums['Case C'], default = 'SHADE', update = update_F3DNodeC) + + def init(self, context): + self.outputs.new("NodeSocketInt", "C") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inC') + + def draw_label(self): + return "Fast3D Node C" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inC] + +class F3DNodeD(ShaderNode): + + bl_idname = 'Fast3D_D' + # Label for nice name display + bl_label = "Case D" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeD(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inD] + + inD : bpy.props.EnumProperty(name = "D", description = "D", + items = combiner_enums['Case D'], default = '0', update = update_F3DNodeD) + + def init(self, context): + self.outputs.new("NodeSocketInt", "D") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inD') + + def draw_label(self): + return "Fast3D Node D" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).CCMUXDict[self.inD] + +class F3DNodeA_alpha(ShaderNode): + + bl_idname = 'Fast3D_A_alpha' + # Label for nice name display + bl_label = "Case A Alpha" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeA_alpha(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inA_alpha] + + inA_alpha : bpy.props.EnumProperty(name = "A Alpha", + description = "A Alpha", items = combiner_enums['Case A Alpha'], + default = '0', update = update_F3DNodeA_alpha) + + def init(self, context): + self.outputs.new("NodeSocketInt", "A Alpha") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inA_alpha') + + def draw_label(self): + return "Fast3D Node A Alpha" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inA_alpha] + +class F3DNodeB_alpha(ShaderNode): + + bl_idname = 'Fast3D_B_alpha' + # Label for nice name display + bl_label = "Case B Alpha" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeB_alpha(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inB_alpha] + + inB_alpha : bpy.props.EnumProperty(name = "B Alpha", description = "B Alpha", + items = combiner_enums['Case B Alpha'], default = '0', update = update_F3DNodeB_alpha) + + def init(self, context): + self.outputs.new("NodeSocketInt", "B Alpha") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inB_alpha') + + def draw_label(self): + return "Fast3D Node B Alpha" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inB_alpha] + +class F3DNodeC_alpha(ShaderNode): + + bl_idname = 'Fast3D_C_alpha' + # Label for nice name display + bl_label = "Case C Alpha" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeC_alpha(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inC_alpha] + + inC_alpha : bpy.props.EnumProperty(name = "C Alpha", description = "C Alpha", + items = combiner_enums['Case C Alpha'], default = '0', update = update_F3DNodeC_alpha) + + def init(self, context): + self.outputs.new("NodeSocketInt", "C Alpha") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inC_alpha') + + def draw_label(self): + return "Fast3D Node C Alpha" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inC_alpha] + +class F3DNodeD_alpha(ShaderNode): + + bl_idname = 'Fast3D_D_alpha' + # Label for nice name display + bl_label = "Case D Alpha" + # Icon identifier + bl_icon = 'NODE' + + def update_F3DNodeD_alpha(self, context): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = F3D('F3D', False).ACMUXDict[self.inD_alpha] + + inD_alpha : bpy.props.EnumProperty(name = "D Alpha", description = "D Alpha", + items = combiner_enums['Case D Alpha'], default = 'ENVIRONMENT', update = update_F3DNodeD_alpha) + + def init(self, context): + self.outputs.new("NodeSocketInt", "D Alpha") + + # Copy function to initialize a copied node from an existing one. + def copy(self, node): + print("Copying from node ", node) + + # Free function to clean up on removal. + def free(self): + print("Removing node ", self, ", Goodbye!") + + # Additional buttons displayed on the node. + def draw_buttons(self, context, layout): + layout.prop(self, 'inD_alpha') + + def draw_label(self): + return "Fast3D Node D Alpha" + + def update(self): + out = self.outputs[0] + if out.is_linked: + for link in out.links: + if link.is_valid: + link.to_socket.default_value = \ + F3D('F3D', False).ACMUXDict[self.inD_alpha] diff --git a/fast64_internal/f3d_parser.py b/fast64_internal/f3d_parser.py new file mode 100644 index 0000000..d23beb5 --- /dev/null +++ b/fast64_internal/f3d_parser.py @@ -0,0 +1,333 @@ +import bmesh +import bpy +import mathutils +import pprint +from .f3d_gbi import * +from .utility import * +from .sm64_constants import * + +def getAxisVector(enumValue): + sign = -1 if enumValue[0] == '-' else 1 + axis = enumValue[0] if sign == 1 else enumValue[1] + return ( + sign if axis == 'X' else 0, + sign if axis == 'Y' else 0, + sign if axis == 'Z' else 0 + ) + +def getExportRotation(forwardAxisEnum, convertTransformMatrix): + if 'Z' in forwardAxisEnum: + print("Z axis reserved for verticals.") + return None + elif forwardAxisEnum == 'X': + rightAxisEnum = '-Y' + elif forwardAxisEnum == '-Y': + rightAxisEnum = '-X' + elif forwardAxisEnum == '-X': + rightAxisEnum = 'Y' + else: + rightAxisEnum = 'X' + + forwardAxis = getAxisVector(forwardAxisEnum) + rightAxis = getAxisVector(rightAxisEnum) + + upAxis = (0, 0, 1) + + # Z assumed to be up + columns = [rightAxis, forwardAxis, upAxis] + localToBlenderRotation = mathutils.Matrix([ + [col[0] for col in columns], + [col[1] for col in columns], + [col[2] for col in columns] + ]).to_quaternion() + + return convertTransformMatrix.to_quaternion() @ localToBlenderRotation + +def F3DtoBlenderObject(romfile, startAddress, scene, + newname, transformMatrix, + segmentData, shadeSmooth): + + mesh = bpy.data.meshes.new(newname + '-mesh') + obj = bpy.data.objects.new(newname, mesh) + scene.collection.objects.link(obj) + createBlankMaterial(obj) + + bMesh = bmesh.new() + bMesh.from_mesh(mesh) + + parseF3D(romfile, startAddress, scene, bMesh, obj, \ + transformMatrix, newname, segmentData, \ + [None] * 16 * 16) + + #bmesh.ops.rotate(bMesh, cent = [0,0,0], + # matrix = blenderToSM64Rotation, + # verts = bMesh.verts) + bMesh.to_mesh(mesh) + bMesh.free() + mesh.update() + + if shadeSmooth: + bpy.ops.object.select_all(action = 'DESELECT') + obj.select_set(True) + bpy.ops.object.shade_smooth() + + return obj + +def getOrMakeVertexGroup(obj, groupName): + for group in obj.vertex_groups: + if group.name == groupName: + return group + return obj.vertex_groups.new(name = groupName) + +def cmdToPositiveInt(cmd): + return cmd if cmd >= 0 else 256 + cmd + +def parseF3D(romfile, startAddress, scene, + bMesh, obj, transformMatrix, groupName, segmentData, vertexBuffer): + f3d = F3D('F3D', False) + currentAddress = startAddress + romfile.seek(currentAddress) + command = romfile.read(8) + + faceSeq = bMesh.faces + vertSeq = bMesh.verts + uv_layer = bMesh.loops.layers.uv.verify() + deform_layer = bMesh.verts.layers.deform.verify() + vertexGroup = getOrMakeVertexGroup(obj, groupName) + groupIndex = vertexGroup.index + + textureSize = [32, 32] + + currentTextureAddr = -1 + jumps = [startAddress] + + # Used for remove_double op at end + vertList = [] + + while len(jumps) > 0: + # FD, FC, B7 (tex, shader, geomode) + #print(format(command[0], '#04x') + ' at ' + hex(currentAddress)) + if command[0] == cmdToPositiveInt(f3d.G_TRI1): + try: + newVerts = interpretDrawTriangle(command, vertexBuffer, + faceSeq, vertSeq, uv_layer, deform_layer, groupIndex) + vertList.extend(newVerts) + except TypeError: + print("Ignoring triangle from unloaded vertices.") + + elif command[0] == cmdToPositiveInt(f3d.G_VTX): + interpretLoadVertices(romfile, vertexBuffer, transformMatrix, + command, segmentData) + + elif command[0] == cmdToPositiveInt(f3d.G_SETTILESIZE): + textureSize = interpretSetTileSize( + int.from_bytes(command[4:8], 'big')) + + elif command[0] == cmdToPositiveInt(f3d.G_DL): + if command[1] == 0: + jumps.append(currentAddress) + currentAddress = decodeSegmentedAddr(command[4:8], + segmentData = segmentData) + romfile.seek(currentAddress) + command = romfile.read(8) + continue + + elif command[0] == cmdToPositiveInt(f3d.G_ENDDL): + currentAddress = jumps.pop() + + elif command[0] == cmdToPositiveInt(f3d.G_SETGEOMETRYMODE): + pass + elif command[0] == cmdToPositiveInt(f3d.G_SETCOMBINE): + pass + + elif command[0] == cmdToPositiveInt(f3d.G_SETTIMG): + currentTextureAddr =\ + interpretSetTImage(command, segmentData) + + elif command[0] == cmdToPositiveInt(f3d.G_LOADBLOCK): + # for now only 16bit RGBA is supported. + interpretLoadBlock(command, romfile, currentTextureAddr, textureSize, + 'RGBA', 16) + + elif command[0] == cmdToPositiveInt(f3d.G_SETTILE): + interpretSetTile(int.from_bytes(command[4:8], 'big'), None) + + else: + pass + #print(format(command[0], '#04x') + ' at ' + hex(currentAddress)) + + currentAddress += 8 + romfile.seek(currentAddress) + command = romfile.read(8) + + bmesh.ops.remove_doubles(bMesh, verts = vertList, dist = 0.0001) + return vertexBuffer + +def getPosition(vertexBuffer, index): + xStart = index * 16 + 0 + yStart = index * 16 + 2 + zStart = index * 16 + 4 + + xBytes = vertexBuffer[xStart : xStart + 2] + yBytes = vertexBuffer[yStart : yStart + 2] + zBytes = vertexBuffer[zStart : zStart + 2] + + x = int.from_bytes(xBytes, 'big', signed=True) * sm64ToBlenderScale + y = int.from_bytes(yBytes, 'big', signed=True) * sm64ToBlenderScale + z = int.from_bytes(zBytes, 'big', signed=True) * sm64ToBlenderScale + + return (x, y, z) + +def getNormalorColor(vertexBuffer, index, isNormal = True): + xByte = bytes([vertexBuffer[index * 16 + 12]]) + yByte = bytes([vertexBuffer[index * 16 + 13]]) + zByte = bytes([vertexBuffer[index * 16 + 14]]) + wByte = bytes([vertexBuffer[index * 16 + 15]]) + + if isNormal: + x = int.from_bytes(xByte, 'big', signed=True) + y = int.from_bytes(yByte, 'big', signed=True) + z = int.from_bytes(zByte, 'big', signed=True) + return (x,y,z) + + else: # vertex color + r = int.from_bytes(xByte, 'big') / 255 + g = int.from_bytes(yByte, 'big') / 255 + b = int.from_bytes(zByte, 'big') / 255 + a = int.from_bytes(wByte, 'big') / 255 + return (r,g,b,a) + +def getUV(vertexBuffer, index, textureDimensions = [32,32]): + uStart = index * 16 + 8 + vStart = index * 16 + 10 + + uBytes = vertexBuffer[uStart : uStart + 2] + vBytes = vertexBuffer[vStart : vStart + 2] + + u = int.from_bytes(uBytes, 'big', signed = True) / 32 + v = int.from_bytes(vBytes, 'big', signed = True) / 32 + + # We don't know texture size, so assume 32x32. + u /= textureDimensions[0] + v /= textureDimensions[1] + v = 1 - v + + return (u,v) + +def interpretSetTile(data, texture): + clampMirrorFlags = bitMask(data, 18, 2) + +def interpretSetTileSize(data): + hVal = bitMask(data, 0, 12) + wVal = bitMask(data, 12, 12) + + height = hVal >> 2 + 1 + width = wVal >> 2 + 1 + + return (width, height) + +def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command, + segmentData = None): + command = int.from_bytes(command, 'big', signed=True) + + numVerts = bitMask(command, 52, 4) + 1 + startIndex = bitMask(command, 48, 4) + dataLength = bitMask(command, 32, 16) + segmentedAddr = bitMask(command, 0, 32) + + dataStartAddr = decodeSegmentedAddr(segmentedAddr.to_bytes(4, 'big'), + segmentData = segmentData) + + romfile.seek(dataStartAddr) + data = romfile.read(dataLength) + + for i in range(numVerts): + vert = mathutils.Vector(readVectorFromShorts(data, i * 16)) + vert = transformMatrix @ vert + transformedVert = bytearray(6) + writeVectorToShorts(transformedVert, 0, vert) + + start = (startIndex + i) * 16 + vertexBuffer[start: start + 6] = transformedVert + vertexBuffer[start + 6: start + 16] = data[i * 16 + 6: i * 16 + 16] + + +# Note the divided by 0x0A, which is due to the way BF command stores indices. +# Without this the triangles are drawn incorrectly. +def interpretDrawTriangle(command, vertexBuffer, + faceSeq, vertSeq, uv_layer, deform_layer, groupIndex): + + verts = [None, None, None] + + index0 = int(command[5] / 0x0A) + 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)) + + verts[0] = vertSeq.new(vert0) + verts[1] = vertSeq.new(vert1) + verts[2] = vertSeq.new(vert2) + + tri = faceSeq.new(verts) + + # Assign vertex group + for vert in tri.verts: + vert[deform_layer][groupIndex] = 1 + + loopIndex = 0 + for loop in tri.loops: + loop[uv_layer].uv = mathutils.Vector( + getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A))) + loopIndex += 1 + + return verts + +def interpretSetTImage(command, levelData): + segmentedAddr = command[4:8] + return decodeSegmentedAddr(segmentedAddr, levelData) + +def interpretLoadBlock(command, romfile, textureStart, textureSize, colorFormat, colorDepth): + numTexels = ((int.from_bytes(command[6:8], 'big')) >> 12) + 1 + + # This is currently broken. + #createNewTextureMaterial(romfile, textureStart, textureSize, numTexels, colorFormat, colorDepth, obj) + +def printvbuf(vertexBuffer): + for i in range(0, int(len(vertexBuffer) / 16)): + print(getPosition(vertexBuffer, i)) + print(getNormalorColor(vertexBuffer, i)) + print(getUV(vertexBuffer, i)) + + +def createBlankMaterial(obj): + newMat = bpy.data.materials.new('sm64_material') + obj.data.materials.append(newMat) + +def createNewTextureMaterial(romfile, textureStart, textureSize, texelCount, colorFormat, colorDepth, obj): + newMat = bpy.data.materials.new('sm64_material') + newTex = bpy.data.textures.new('sm64_texture', 'IMAGE') + newImg = bpy.data.images.new('sm64_image', *textureSize, True, True) + + newTex.image = newImg + newSlot = newMat.texture_slots.add() + newSlot.texture = newTex + + obj.data.materials.append(newMat) + + romfile.seek(textureStart) + texelSize = int(colorDepth / 8) + dataLength = texelCount * texelSize + textureData = romfile.read(dataLength) + + if colorDepth != 16: + print("Warning: Only 16bit RGBA supported, input was " + \ + str(colorDepth) + 'bit ' + colorFormat) + else: + print(str(texelSize) + " " + str(colorDepth)) + for n in range(0, dataLength, texelSize): + oldPixel = textureData[n : n + texelSize] + newImg.pixels[n : n+4] = read16bitRGBA( + int.from_bytes(oldPixel, 'big')) \ No newline at end of file diff --git a/fast64_internal/f3d_writer.py b/fast64_internal/f3d_writer.py new file mode 100644 index 0000000..e0bb60a --- /dev/null +++ b/fast64_internal/f3d_writer.py @@ -0,0 +1,1344 @@ +import bpy +import bmesh +import mathutils +from math import pi +from io import BytesIO + +import copy +from math import pi, ceil +from .utility import * +from .sm64_constants import * +from .f3d_material import all_combiner_uses +from .f3d_gbi import * +from .f3d_gbi import _DPLoadTextureBlock + +bitSizeDict = { + 'G_IM_SIZ_4b' : 4, + 'G_IM_SIZ_8b' : 8, + 'G_IM_SIZ_16b' : 16, + 'G_IM_SIZ_32b' : 32, +} + +texBitSizeOf = { + 'I4' : 'G_IM_SIZ_4b', + 'IA4' : 'G_IM_SIZ_4b', + 'CI4' : 'G_IM_SIZ_4b', + 'I8' : 'G_IM_SIZ_8b', + 'IA8' : 'G_IM_SIZ_8b', + 'CI8' : 'G_IM_SIZ_8b', + 'RGBA16' : 'G_IM_SIZ_16b', + 'IA16' : 'G_IM_SIZ_16b', + 'YUV16' : 'G_IM_SIZ_16b', + 'RGBA32' : 'G_IM_SIZ_32b', +} + +texFormatOf = { + 'I4' : 'G_IM_FMT_I', + 'IA4' : 'G_IM_FMT_IA', + 'CI4' : 'G_IM_FMT_CI', + 'I8' : 'G_IM_FMT_I', + 'IA8' : 'G_IM_FMT_IA', + 'CI8' : 'G_IM_FMT_CI', + 'RGBA16' : 'G_IM_FMT_RGBA', + 'IA16' : 'G_IM_FMT_IA', + 'YUV16' : 'G_IM_FMT_YUV', + 'RGBA32' : 'G_IM_FMT_RGBA', +} + +def getEdgeToFaceDict(mesh): + edgeDict = {} + for face in mesh.loop_triangles: + for edgeKey in face.edge_keys: + if edgeKey not in edgeDict: + edgeDict[edgeKey] = [] + if face not in edgeDict[edgeKey]: + edgeDict[edgeKey].append(face) + return edgeDict + +def getVertToFaceDict(mesh): + vertDict = {} + for face in mesh.loop_triangles: + for vertIndex in face.vertices: + if vertIndex not in vertDict: + vertDict[vertIndex] = [] + if face not in vertDict[vertIndex]: + vertDict[vertIndex].append(face) + return vertDict + +def exportF3DCommon(obj, f3dType, isHWv1, transformMatrix): + checkForF3DMaterial(obj) + bpy.ops.object.select_all(action = 'DESELECT') + obj.select_set(True) + + obj.data.calc_loop_triangles() + obj.data.calc_normals_split() + edgeDict = getEdgeToFaceDict(obj.data) + + fModel = FModel(f3dType, isHWv1) + fMeshGroup = FMeshGroup(toAlnum(obj.name), + FMesh(toAlnum(obj.name) + '_mesh'), None) + fModel.meshGroups[obj.name] = fMeshGroup + + for material_index in range(len(obj.data.materials)): + material = obj.data.materials[material_index] + saveMeshByFaces(material, + [face for face in obj.data.loop_triangles if \ + face.material_index == material_index], + fModel, fMeshGroup.mesh, obj, transformMatrix, edgeDict) + + revertMatAndEndDraw(fMeshGroup.mesh.draw) + return fModel, fMeshGroup + +def exportF3DtoC(filePath, cDefPath, obj, isStatic, transformMatrix, + f3dType, isHWv1): + fModel, fMeshGroup = exportF3DCommon(obj, f3dType, isHWv1, transformMatrix) + + data = fModel.to_c(isStatic) + outFile = open(filePath, 'w') + outFile.write(data) + outFile.close() + + cDefine = fModel.to_c_def() + cDefFile = open(cDefPath, 'w') + cDefFile.write(cDefine) + cDefFile.close() + + bpy.ops.object.mode_set(mode = 'OBJECT') + +def exportF3DtoBinary(romfile, exportRange, transformMatrix, + obj, f3dType, isHWv1, segmentData): + fModel, fMeshGroup = exportF3DCommon(obj, f3dType, isHWv1, transformMatrix) + + addrRange = fModel.set_addr(exportRange[0]) + if addrRange[1] > exportRange[1]: + raise ValueError('Size too big: Data ends at ' + hex(addrRange[1]) +\ + ', which is larger than the specified range.') + fModel.save_binary(romfile, segmentData) + bpy.ops.object.mode_set(mode = 'OBJECT') + + segPointerData = encodeSegmentedAddr( + fMeshGroup.mesh.draw.startAddress, segmentData) + + return fMeshGroup.mesh.draw.startAddress, addrRange, segPointerData + +def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, + obj, f3dType, isHWv1, RAMAddr): + fModel, fMeshGroup = exportF3DCommon(obj, f3dType, isHWv1, transformMatrix) + segmentData = copy.copy(bank0Segment) + + addrRange = fModel.set_addr(RAMAddr) + if addrRange[1] - RAMAddr > exportRange[1] - exportRange[0]: + raise ValueError('Size too big: Data ends at ' + hex(addrRange[1]) +\ + ', which is larger than the specified range.') + + bytesIO = BytesIO() + #actualRAMAddr = get64bitAlignedAddr(RAMAddr) + bytesIO.seek(RAMAddr) + fModel.save_binary(bytesIO, segmentData) + data = bytesIO.getvalue()[RAMAddr:] + bytesIO.close() + + startAddress = get64bitAlignedAddr(exportRange[0]) + romfile.seek(startAddress) + romfile.write(data) + + bpy.ops.object.mode_set(mode = 'OBJECT') + + segPointerData = encodeSegmentedAddr( + fMeshGroup.mesh.draw.startAddress, segmentData) + + return (fMeshGroup.mesh.draw.startAddress, \ + (startAddress, startAddress + len(data)), segPointerData) + +def checkForF3DMaterial(obj): + if len(obj.material_slots) == 0: + raise ValueError(obj.name + " has no Fast3D material.") + for materialSlot in obj.material_slots: + if materialSlot.material is None or \ + not materialSlot.material.is_f3d: + raise ValueError(obj.name + " has either empty material slots " +\ + 'or non-Fast3D materials.') + +def revertMatAndEndDraw(gfxList): + gfxList.commands.extend([ + DPPipeSync(), + SPSetGeometryMode(['G_LIGHTING']), + SPClearGeometryMode(['G_TEXTURE_GEN']), + DPSetCombineMode(*S_SHADED_SOLID), + SPTexture(0xFFFF, 0xFFFF, 0, 0, 0), + SPEndDisplayList()]) + +def getCommonEdge(face1, face2, mesh): + for edgeKey1 in face1.edge_keys: + for edgeKey2 in face2.edge_keys: + if edgeKey1 == edgeKey2: + return edgeKey1 + raise ValueError("No common edge between faces " + str(face1.index) + \ + ' and ' + str(face2.index)) + +def edgeValid(face1, face2, convertInfo, mesh): + edgeKey = getCommonEdge(face1, face2, mesh) + + vertPairs = [[],[]] + + for loopIndex in face1.loops: + vertIndex = mesh.loops[loopIndex].vertex_index + if vertIndex in edgeKey: + index = 0 if vertIndex == edgeKey[0] else 1 + vertPairs[index].append([loopIndex, face1]) + + for loopIndex in face2.loops: + vertIndex = mesh.loops[loopIndex].vertex_index + if vertIndex in edgeKey: + index = 0 if vertIndex == edgeKey[0] else 1 + vertPairs[index].append([loopIndex, face2]) + + vertPairs[0] = [(mesh.loops[i], face) for i, face in vertPairs[0]] + vertPairs[1] = [(mesh.loops[i], face) for i, face in vertPairs[1]] + + for vertPair in vertPairs: + if getF3DVert(vertPair[0][0], vertPair[0][1], convertInfo, mesh) != \ + getF3DVert(vertPair[1][0], vertPair[1][1], convertInfo, mesh): + return False + + return True + +def getNumUnvisitedNeighbors(face, faces, visitedNeighbors, convertInfo, + edgeDict, mesh): + neighbors = [] + + for edgeKey in face.edge_keys: + for linkedFace in edgeDict[edgeKey]: + if linkedFace.index != face.index: + if linkedFace in faces and linkedFace not in neighbors and\ + linkedFace not in visitedNeighbors and \ + edgeValid(face, linkedFace, convertInfo, mesh): + neighbors.append(linkedFace) + return len(neighbors) + +def getLowestUnvisitedNeighborCountFace(faces, visitedFaces, convertInfo, + edgeDict, mesh): + lowestNeighborFace = None + for face in faces: + if face not in visitedFaces: + lowestNeighborFace = face + break + lowestNeighborCount = getNumUnvisitedNeighbors( + lowestNeighborFace, faces, visitedFaces, convertInfo, edgeDict, mesh) + for face in faces: + if face in visitedFaces: + continue + neighborCount = getNumUnvisitedNeighbors( + face, faces, visitedFaces, convertInfo, edgeDict, mesh) + if neighborCount < lowestNeighborCount: + lowestNeighborFace = face + lowestNeighborCount = neighborCount + return lowestNeighborFace + +def getNextNeighborFace(faces, face, lastEdgeKey, visitedFaces, possibleFaces, + convertInfo, mesh, edgeDict): + + if lastEdgeKey is not None: + handledEdgeKeys = [lastEdgeKey] + nextEdgeKey = face.edge_keys[ + (face.edge_keys.index(lastEdgeKey) + 1) % 3] + else: + handledEdgeKeys = [] + nextEdgeKey = face.edge_keys[0] + + nextFaceAndEdge = (None, None) + while nextEdgeKey not in handledEdgeKeys: + for linkedFace in edgeDict[nextEdgeKey]: + if linkedFace == face or linkedFace not in faces: + continue + elif edgeValid(linkedFace, face, convertInfo, mesh) and \ + linkedFace not in visitedFaces: + if nextFaceAndEdge[0] is None: + #print(nextLoop.face) + nextFaceAndEdge = (linkedFace, nextEdgeKey) + else: + # Move face to front of queue + if linkedFace in possibleFaces: + possibleFaces.remove(linkedFace) + possibleFaces.insert(0, linkedFace) + handledEdgeKeys.append(nextEdgeKey) + nextEdgeKey = face.edge_keys[ + (face.edge_keys.index(nextEdgeKey) + 1) % 3] + return nextFaceAndEdge + +def saveTriangleStrip(faces, convertInfo, triList, vtxList, f3d, + texDimensions, transformMatrix, isPointSampled, exportVertexColors, + existingVertexData, existingVertexMaterialRegions, edgeDict, mesh): + visitedFaces = [] + possibleFaces = [] + lastEdgeKey = None + neighborFace = getLowestUnvisitedNeighborCountFace( + faces, visitedFaces, convertInfo, edgeDict, mesh) + + triConverter = TriangleConverter(mesh, convertInfo, triList, vtxList, f3d, + texDimensions, transformMatrix, isPointSampled, exportVertexColors, + existingVertexData, existingVertexMaterialRegions) + + while len(visitedFaces) < len(faces): + #print(str(len(visitedFaces)) + " " + str(len(bFaces))) + if neighborFace is None: + if len(possibleFaces) > 0: + #print("get neighbor from queue") + neighborFace = possibleFaces[0] + lastEdgeKey = None + possibleFaces = [] + else: + #print('get new neighbor') + neighborFace = getLowestUnvisitedNeighborCountFace( + faces, visitedFaces, convertInfo, edgeDict, mesh) + lastEdgeKey = None + + triConverter.addFace(neighborFace) + if neighborFace in visitedFaces: + raise ValueError("Repeated face") + visitedFaces.append(neighborFace) + if neighborFace in possibleFaces: + possibleFaces.remove(neighborFace) + + neighborFace, lastEdgeKey = getNextNeighborFace(faces, + neighborFace, lastEdgeKey, visitedFaces, possibleFaces, convertInfo, + mesh, edgeDict) + + triConverter.finish() + +# Necessary for UV half pixel offset (see 13.7.5.3) +def isTexturePointSampled(material): + return material.rdp_settings.g_mdsft_text_filt == 'G_TF_POINT' + +def isLightingDisabled(material): + return not material.rdp_settings.g_lighting + +# Necessary as G_SHADE_SMOOTH actually does nothing +def checkIfFlatShaded(material): + return not material.rdp_settings.g_shade_smooth + +def saveMeshByFaces(material, faces, fModel, fMesh, obj, transformMatrix, + edgeDict): + if len(faces) == 0: + print('0 Faces Provided.') + return + fMaterial, texDimensions = \ + saveOrGetF3DMaterial(material, fModel, obj) + isPointSampled = isTexturePointSampled(material) + exportVertexColors = isLightingDisabled(material) + uv_layer = obj.data.uv_layers.active.data + convertInfo = LoopConvertInfo(uv_layer, obj, exportVertexColors) + + fMesh.draw.commands.append(SPDisplayList(fMaterial.material)) + triList = fMesh.tri_list_new() + fMesh.draw.commands.append(SPDisplayList(triList)) + + #saveGeometry(obj, triList, fMesh.vertexList, bFaces, + # bMesh, texDimensions, transformMatrix, isPointSampled, isFlatShaded, + # exportVertexColors, fModel.f3d) + saveTriangleStrip(faces, convertInfo, triList, fMesh.vertexList, + fModel.f3d, texDimensions, transformMatrix, isPointSampled, + exportVertexColors, None, None, edgeDict, obj.data) + + if fMaterial.revert is not None: + fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) + +def get8bitRoundedNormal(normal): + return mathutils.Vector( + (round(normal[0] * 128) / 128, + round(normal[1] * 128) / 128, + round(normal[2] * 128) / 128) + ) + +class LoopConvertInfo: + def __init__(self, uv_layer, obj, exportVertexColors): + self.uv_layer = uv_layer + self.obj = obj + self.exportVertexColors = exportVertexColors + +def getNewIndices(existingIndices, bufferStart): + n = bufferStart + newIndices = [] + for index in existingIndices: + if index is None: + newIndices.append(n) + n += 1 + else: + newIndices.append(index) + return newIndices + +class TriangleConverter: + def __init__(self, mesh, convertInfo, triList, vtxList, f3d, + texDimensions, transformMatrix, isPointSampled, exportVertexColors, + existingVertexData, existingVertexMaterialRegions): + self.convertInfo = convertInfo + self.mesh = mesh + + # Existing data assumed to be already loaded in. + if existingVertexData is not None: + # [(position, uv, colorOrNormal)] + self.vertBuffer = existingVertexData + else: + self.vertBuffer = [] + self.existingVertexMaterialRegions = existingVertexMaterialRegions + self.bufferStart = len(self.vertBuffer) + self.vertexBufferTriangles = [] # [(index0, index1, index2)] + self.f3d = f3d + self.triList = triList + self.vtxList = vtxList + + self.texDimensions = texDimensions + self.transformMatrix = transformMatrix + self.isPointSampled = isPointSampled + self.exportVertexColors = exportVertexColors + + def vertInBuffer(self, f3dVert, material_index): + if self.existingVertexMaterialRegions is None: + if f3dVert in self.vertBuffer: + return self.vertBuffer.index(f3dVert) + else: + return None + else: + if material_index in self.existingVertexMaterialRegions: + matRegion = self.existingVertexMaterialRegions[material_index] + for i in range(matRegion[0], matRegion[1]): + if self.vertBuffer[i] == f3dVert: + return i + for i in range(self.bufferStart, len(self.vertBuffer)): + if self.vertBuffer[i] == f3dVert: + return i + return None + + def addFace(self, face): + triIndices = [] + existingVertIndices = [] + addedVerts = [] # verts added to existing vertexBuffer + allVerts = [] # all verts not in 'untouched' buffer region + + for loopIndex in face.loops: + loop = self.mesh.loops[loopIndex] + f3dVert = getF3DVert(loop, face, self.convertInfo, self.mesh) + vertIndex = self.vertInBuffer(f3dVert, face.material_index) + if vertIndex is not None: + triIndices.append(vertIndex) + else: + addedVerts.append(f3dVert) + triIndices.append(len(self.vertBuffer) + len(addedVerts) - 1) + + if f3dVert in self.vertBuffer[:self.bufferStart]: + existingVertIndices.append(self.vertBuffer.index(f3dVert)) + else: + allVerts.append(f3dVert) + existingVertIndices.append(None) + + # We care only about load size, since loading is what takes up time. + # Even if vert_buffer is larger, its still another load to fill it. + if len(self.vertBuffer) + len(addedVerts) > self.f3d.vert_load_size: + self.triList.commands.append( + SPVertex(self.vtxList, len(self.vtxList.vertices), + len(self.vertBuffer) - self.bufferStart, self.bufferStart)) + self.triList.commands.extend(createTriangleCommands( + self.vertexBufferTriangles, self.f3d.F3DEX_GBI)) + for vertData in self.vertBuffer[self.bufferStart:]: + self.vtxList.vertices.append(convertVertexData(self.convertInfo.obj.data, + vertData[0], vertData[1], vertData[2], self.texDimensions, + self.transformMatrix, self.isPointSampled, + self.exportVertexColors)) + self.vertBuffer = self.vertBuffer[:self.bufferStart] + allVerts + self.vertexBufferTriangles = \ + [getNewIndices(existingVertIndices, self.bufferStart)] + else: + self.vertBuffer.extend(addedVerts) + self.vertexBufferTriangles.append(triIndices) + + def finish(self): + if len(self.vertexBufferTriangles) > 0: + self.triList.commands.append(SPVertex(self.vtxList, + len(self.vtxList.vertices), + len(self.vertBuffer) - self.bufferStart, self.bufferStart)) + self.triList.commands.extend(createTriangleCommands( + self.vertexBufferTriangles, self.f3d.F3DEX_GBI)) + for vertData in self.vertBuffer[self.bufferStart:]: + self.vtxList.vertices.append(convertVertexData( + self.convertInfo.obj.data, vertData[0], vertData[1], + vertData[2], self.texDimensions, self.transformMatrix, + self.isPointSampled, self.exportVertexColors)) + + self.triList.commands.append(SPEndDisplayList()) + +def getF3DVert(loop, face, convertInfo, mesh): + position = mesh.vertices[loop.vertex_index].co.copy().freeze() + uv = convertInfo.uv_layer[loop.index].uv.copy().freeze() + colorOrNormal = getLoopColorOrNormal(loop, face, + convertInfo.obj.data, convertInfo.obj, convertInfo.exportVertexColors) + + return (position, uv, colorOrNormal) + +def getLoopNormal(loop, face, isFlatShaded): + if isFlatShaded: + normal = -face.normal #??? + else: + normal = -loop.normal #??? + return get8bitRoundedNormal(normal).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, loopColorOrNormal, + texDimensions, transformMatrix, isPointSampled, exportVertexColors): + #uv_layer = mesh.uv_layers.active + #color_layer = mesh.vertex_colors['Col'] + #alpha_layer = mesh.vertex_colors['Alpha'] + + # 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 if isPointSampled else 0.5 + uv = [ + convertFloatToFixed16(loopUV[0] * texDimensions[0] - pixelOffset), + convertFloatToFixed16(loopUV[1] * texDimensions[1] - pixelOffset) + ] + + # Color/Normal (4 bytes) + if exportVertexColors: + colorOrNormal = [ + int(round(loopColorOrNormal[0] * 255)).to_bytes(1, 'big')[0], + int(round(loopColorOrNormal[1] * 255)).to_bytes(1, 'big')[0], + int(round(loopColorOrNormal[2] * 255)).to_bytes(1, 'big')[0], + int(round(loopColorOrNormal[3] * 255)).to_bytes(1, 'big')[0] + ] + 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], + 0xFF + ] + + return Vtx(position, uv, colorOrNormal) + +def getLoopColor(loop, mesh): + color_layer = mesh.vertex_colors['Col'].data + alpha_layer = mesh.vertex_colors['Alpha'].data + + if color_layer is not None: + normalizedRGB = color_layer[loop.index].color + else: + normalizedRGB = [1,1,1] + if alpha_layer is not None: + normalizedAColor = alpha_layer[loop.index].color + normalizedA = mathutils.Color(normalizedAColor[0:3]).v + else: + normalizedA = 1 + + return (normalizedRGB[0], normalizedRGB[1], normalizedRGB[2], normalizedA) + +def getLoopColorOrNormal(loop, face, mesh, obj, exportVertexColors): + material = obj.data.materials[face.material_index] + isFlatShaded = checkIfFlatShaded(material) + if exportVertexColors: + return getLoopColor(loop, mesh) + else: + return getLoopNormal(loop, face, isFlatShaded) + +def createTriangleCommands(triangles, useSP2Triangle): + triangles = copy.deepcopy(triangles) + commands = [] + if useSP2Triangle: + while len(triangles) > 0: + if len(triangles) >= 2: + commands.append(SP2Triangles( + triangles[0][0], triangles[0][1], triangles[0][2], 0, + triangles[1][0], triangles[1][1], triangles[1][2], 0)) + triangles = triangles[2:] + else: + commands.append(SP1Triangle( + triangles[0][0], triangles[0][1], triangles[0][2], 0)) + triangles = [] + else: + while len(triangles) > 0: + commands.append(SP1Triangle( + triangles[0][0], triangles[0][1], triangles[0][2], 0)) + triangles = triangles[1:] + + return commands + +''' +def saveGeometry(obj, triList, vtxList, bFaces, bMesh, + texDimensions, transformMatrix, isPointSampled, isFlatShaded, + exportVertexColors, f3d): + + uv_layer = bMesh.loops.layers.uv.verify() + convertInfo = LoopConvertInfo(uv_layer, bMesh, obj, exportVertexColors) + triangleConverter = TriangleConverter(obj.data, convertInfo, triList, + vtxList, f3d, texDimensions, transformMatrix, isPointSampled, + exportVertexColors, None, None) + + for bFace in bFaces: + triangleConverter.addFace(bFace) + triangleConverter.finish() +''' + +# white diffuse, grey ambient, normal = (1,1,1) +defaultLighting = [ + (mathutils.Vector((1,1,1)), mathutils.Vector((1, 1, 1)).normalized()), + (mathutils.Vector((0.5, 0.5, 0.5)), mathutils.Vector((1, 1, 1)).normalized())] + +def saveOrGetF3DMaterial(material, fModel, obj): + if material in fModel.materials: + return fModel.materials[material] + + if len(obj.data.materials) == 0: + raise ValueError("Mesh must have at least one material.") + + fMaterial = FMaterial(toAlnum(material.name)) + fMaterial.material.commands.append(DPPipeSync()) + fMaterial.revert.commands.append(DPPipeSync()) + + if not material.is_f3d: + raise ValueError("Material named " + material.name + \ + ' is not an F3D material.') + nodes = material.node_tree.nodes + + if material.set_combiner: + if material.rdp_settings.g_mdsft_cycletype == 'G_CYC_2CYCLE': + fMaterial.material.commands.append( + DPSetCombineMode( + nodes['Case A 1'].inA, + nodes['Case B 1'].inB, + nodes['Case C 1'].inC, + nodes['Case D 1'].inD, + nodes['Case A Alpha 1'].inA_alpha, + nodes['Case B Alpha 1'].inB_alpha, + nodes['Case C Alpha 1'].inC_alpha, + nodes['Case D Alpha 1'].inD_alpha, + nodes['Case A 2'].inA, + nodes['Case B 2'].inB, + nodes['Case C 2'].inC, + nodes['Case D 2'].inD, + nodes['Case A Alpha 2'].inA_alpha, + nodes['Case B Alpha 2'].inB_alpha, + nodes['Case C Alpha 2'].inC_alpha, + nodes['Case D Alpha 2'].inD_alpha + )) + else: + fMaterial.material.commands.append( + DPSetCombineMode( + nodes['Case A 1'].inA, + nodes['Case B 1'].inB, + nodes['Case C 1'].inC, + nodes['Case D 1'].inD, + nodes['Case A Alpha 1'].inA_alpha, + nodes['Case B Alpha 1'].inB_alpha, + nodes['Case C Alpha 1'].inC_alpha, + nodes['Case D Alpha 1'].inD_alpha, + nodes['Case A 1'].inA, + nodes['Case B 1'].inB, + nodes['Case C 1'].inC, + nodes['Case D 1'].inD, + nodes['Case A Alpha 1'].inA_alpha, + nodes['Case B Alpha 1'].inB_alpha, + nodes['Case C Alpha 1'].inC_alpha, + nodes['Case D Alpha 1'].inD_alpha + )) + + if material.set_fog: + fMaterial.material.commands.extend([ + DPSetFogColor( + int(round(material.fog_color[0] * 255)), + int(round(material.fog_color[1] * 255)), + int(round(material.fog_color[2] * 255)), + int(round(material.fog_color[3] * 255))), + SPFogPosition(material.fog_position[0], material.fog_position[1]) + ]) + + useDict = all_combiner_uses(material, + material.rdp_settings.g_mdsft_cycletype == '1') + + defaults = bpy.context.scene.world.rdp_defaults + saveGeoModeDefinition(fMaterial, material.rdp_settings, defaults) + saveOtherModeHDefinition(fMaterial, material.rdp_settings, defaults) + saveOtherModeLDefinition(fMaterial, material.rdp_settings, defaults) + + # Set scale + s = int(material.tex_scale[0] * 0xFFFF) + t = int(material.tex_scale[1] * 0xFFFF) + fMaterial.material.commands.append( + SPTexture(s, t, 0, fModel.f3d.G_TX_RENDERTILE, 1)) + + # Save textures + texDimensions0, nextTmem = saveTextureIndex(useDict, material, fModel, + fMaterial, material.tex0, 0, 'Texture 0', 0) + texDimensions1, nextTmem = saveTextureIndex(useDict, material, fModel, + fMaterial, material.tex1, 1, 'Texture 1', nextTmem) + + # Used so we know how to convert normalized UVs when saving verts. + if texDimensions0 is not None and texDimensions1 is not None: + texDimensions = texDimensions0 if material.uv_basis == 'TEXEL0' \ + else texDimensions1 + elif texDimensions0 is not None: + texDimensions = texDimensions0 + elif texDimensions1 is not None: + texDimensions = texDimensions1 + else: + texDimensions = [32, 32] + + if useDict['Primitive'] and material.set_prim: + color = nodes['Primitive Color'].outputs[0].default_value + color = gammaCorrect(color[0:3]) + [color[3]] + fMaterial.material.commands.append( + DPSetPrimColor( + int(material.prim_lod_min * 255), + int(material.prim_lod_frac * 255), + int(color[0] * 255), + int(color[1] * 255), + int(color[2] * 255), + int(color[3] * 255))) + + if useDict['Environment'] and material.set_env: + color = nodes['Environment Color'].outputs[0].default_value + color = gammaCorrect(color[0:3]) + [color[3]] + fMaterial.material.commands.append( + DPSetEnvColor( + int(color[0] * 255), + int(color[1] * 255), + int(color[2] * 255), + int(color[3] * 255))) + + if useDict['Shade'] and material.set_lights: + fLights = saveLightsDefinition(fModel, material, + fMaterial.material.name + '_lights') + fMaterial.material.commands.extend([ + SPSetLights(fLights) # TODO: handle synching: NO NEED? + ]) + + if useDict['Key'] and material.set_key: + center = nodes['Chroma Key Center'].outputs[0].default_value + scale = nodes['Chroma Key Scale'].outputs[0].default_value + width = material.key_width + fMaterial.material.commands.extend([ + DPSetCombineKey('G_CK_KEY'), + # TODO: Add UI handling width + DPSetKeyR(int(center[0] * 255), int(scale[0] * 255), + int(width[0] * 2**8)), + DPSetKeyGB(int(center[1] * 255), int(scale[1] * 255), + int(width[1] * 2**8), + int(center[2] * 255), int(scale[2] * 255), + int(width[2] * 2**8)) + ]) + + # all k0-5 set at once + # make sure to handle this in node shader + # or don't, who cares + if useDict['Convert'] and material.set_k0_5: + fMaterial.material.commands.extend([ + DPSetTextureConvert('G_TC_FILTCONV'), # TODO: allow filter option + DPSetConvert( + int(material.k0 * 255), + int(material.k1 * 255), + int(material.k2 * 255), + int(material.k3 * 255), + int(material.k4 * 255), + int(material.k5 * 255)) + ]) + + # End Display List + fMaterial.material.commands.append(SPEndDisplayList()) + #revertMatAndEndDraw(fMaterial.revert) + if len(fMaterial.revert.commands) > 1: # 1 being the pipe sync + fMaterial.revert.commands.append(SPEndDisplayList()) + else: + fMaterial.revert = None + fModel.materials[material] = (fMaterial, texDimensions) + + return fMaterial, texDimensions + +def saveTextureIndex(useDict, material, fModel, fMaterial, texProp, + index, name, tmem): + if useDict[name] and texProp.tex_set: + tex = texProp.tex + if tex is None: + raise ValueError('No ' + name + ' selected.') + texFormat = texProp.tex_format + palFormat = texProp.ci_format + texName = getNameFromPath(tex.filepath) + '_' + texFormat + + nextTmem = tmem + ceil(bitSizeDict[texBitSizeOf[texFormat]] * \ + tex.size[0] * tex.size[1] / 64) + if nextTmem > (512 if texFormat[:2] != 'CI' else 256): + print(nextTmem) + raise ValueError("Textures are too big. Max TMEM size is 4k " + \ + "bytes, ex. 2 32x32 RGBA 16 bit textures.") + if tex.size[0] > 1024 or tex.size[1] > 1024: + raise ValueError("Any side of an image cannot be greater " +\ + "than 1024.") + + clamp_S = texProp.S.clamp + mirror_S = texProp.S.mirror + tex_SL = texProp.S.low + tex_SH = texProp.S.high + mask_S = texProp.S.mask + shift_S = texProp.S.shift + + clamp_T = texProp.T.clamp + mirror_T = texProp.T.mirror + tex_TL = texProp.T.low + tex_TH = texProp.T.high + mask_T = texProp.T.mask + shift_T = texProp.T.shift + + if texFormat == 'CI4' or texFormat == 'CI8': + fImage, fPalette = savePaletteDefinition( + fModel, tex, texName, texFormatOf[texFormat], + texFormatOf[palFormat], texBitSizeOf[texFormat]) + fModel.textures[texName] = fImage + fModel.textures[fPalette.name] = fPalette + savePaletteLoading(fModel, fMaterial, fPalette, + palFormat, 0, fPalette.height, fModel.f3d) + else: + fImage = saveTextureDefinition(fModel, tex, texName, + texFormatOf[texFormat], texBitSizeOf[texFormat]) + fModel.textures[texName] = fImage + + saveTextureLoading(fImage, fMaterial, clamp_S, + mirror_S, clamp_T, mirror_T, + mask_S, mask_T, shift_S, + shift_T, tex_SL, tex_TL, tex_SH, + tex_TH, material.tex0.tex_format, index, fModel.f3d, tmem) + texDimensions = fImage.width, fImage.height + #fImage = saveTextureDefinition(fModel, tex, texName, + # texFormatOf[texFormat], texBitSizeOf[texFormat]) + #fModel.textures[texName] = fImage + + else: + texDimensions = None + nextTmem = tmem + + return texDimensions, nextTmem + +# texIndex: 0 for texture0, 1 for texture1 +def saveTextureLoading(fImage, fMaterial, clamp_S, mirror_S, clamp_T, + mirror_T, mask_S, mask_T, shift_S, shift_T, + SL, TL, SH, TH, tex_format, texIndex, f3d, tmem): + cms = [('G_TX_CLAMP' if clamp_S else 'G_TX_WRAP'), + ('G_TX_MIRROR' if mirror_S else 'G_TX_NOMIRROR')] + cmt = [('G_TX_CLAMP' if clamp_T else 'G_TX_WRAP'), + ('G_TX_MIRROR' if mirror_T else 'G_TX_NOMIRROR')] + masks = mask_S + maskt = mask_T + shifts = shift_S if shift_S > 0 else (shift_S + 16) + shiftt = shift_T if shift_T > 0 else (shift_T + 16) + + #print('Low ' + str(SL) + ' ' + str(TL)) + sl = int(SL * (2 ** f3d.G_TEXTURE_IMAGE_FRAC)) + tl = int(TL * (2 ** f3d.G_TEXTURE_IMAGE_FRAC)) + sh = int(SH * (2 ** f3d.G_TEXTURE_IMAGE_FRAC)) + th = int(TH * (2 ** f3d.G_TEXTURE_IMAGE_FRAC)) + + fmt = texFormatOf[tex_format] + siz = texBitSizeOf[tex_format] + pal = 0 if fmt[:2] != 'CI' else 0 # handle palettes + dxt = f3d.CALC_DXT_4b(fImage.width) if siz == 'G_IM_SIZ_4b' else \ + f3d.CALC_DXT(fImage.width, f3d.G_IM_SIZ_VARS[siz + '_BYTES']) + + # Note that _LINE_BYTES and _TILE_BYTES variables are the same. + line = (((fImage.width >> 1) + 7) >> 3) if siz == 'G_IM_SIZ_4b' else \ + (((fImage.width * f3d.G_IM_SIZ_VARS[siz + '_LINE_BYTES']) + 7) >> 3) + + # LoadTile will pad rows to 64 bit word alignment, while + # LoadBlock assumes this is already done. + + fMaterial.material.commands.extend([ + DPTileSync(), # added in + + # Load Block version + #DPSetTextureImage(fmt, siz + '_LOAD_BLOCK', 1, fImage), + #DPSetTile(fmt, siz + '_LOAD_BLOCK', 0, tmem, + # f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, + # cms, masks, shifts), + #DPLoadSync(), + #DPLoadBlock(f3d.G_TX_LOADTILE - texIndex, 0, 0, \ + # (((fImage.width)*(fImage.height) + \ + # f3d.G_IM_SIZ_VARS[siz + '_INCR']) >> \ + # f3d.G_IM_SIZ_VARS[siz + '_SHIFT'])-1, \ + # dxt), + + # Load Tile version + DPSetTextureImage(fmt, siz, fImage.width, fImage), + DPSetTile(fmt, siz + '_LOAD_BLOCK', line, tmem, + f3d.G_TX_LOADTILE - texIndex, 0, cmt, maskt, shiftt, + cms, masks, shifts), + DPLoadSync(), + DPLoadTile(f3d.G_TX_LOADTILE - texIndex, 0, 0, + (fImage.width - 1) << f3d.G_TEXTURE_IMAGE_FRAC, + (fImage.height - 1) << f3d.G_TEXTURE_IMAGE_FRAC), + + DPPipeSync(), + DPSetTile(fmt, siz, line, tmem, \ + f3d.G_TX_RENDERTILE + texIndex, pal, cmt, maskt, \ + shiftt, cms, masks, shifts), + DPSetTileSize(f3d.G_TX_RENDERTILE + texIndex, sl, tl, sh, th) + ]) # added in + +# palette stored in upper half of TMEM (words 256-511) +# pal is palette number (0-16), for CI8 always set to 0 +def savePaletteLoading(fModel, fMaterial, fPalette, palFormat, pal, + colorCount, f3d): + palFmt = texFormatOf[palFormat] + cms = ['G_TX_WRAP', 'G_TX_NOMIRROR'] + cmt = ['G_TX_WRAP', 'G_TX_NOMIRROR'] + + fMaterial.material.commands.append(DPSetTextureLUT( + 'G_TT_RGBA16' if palFmt == 'G_IM_FMT_RGBA' else 'G_TT_IA16')) + fMaterial.revert.commands.append(DPSetTextureLUT('G_TT_NONE')) + + if not f3d._HW_VERSION_1: + fMaterial.material.commands.extend([ + DPSetTextureImage(palFmt, 'G_IM_SIZ_16b', 1, fPalette), + DPTileSync(), + DPSetTile('0', '0', 0, (256+(((pal)&0xf)*16)),\ + f3d.G_TX_LOADTILE, 0, cmt, 0, 0, cms, 0, 0), + DPLoadSync(), + DPLoadTLUTCmd(f3d.G_TX_LOADTILE, colorCount - 1), + DPPipeSync()]) + else: + fMaterial.material.commands.extend([ + _DPLoadTextureBlock(fPalette, \ + (256+(((pal)&0xf)*16)), \ + palFmt, 'G_IM_SIZ_16b', 4*colorCount, 1, + pal, cms, cmt, 0, 0, 0, 0)]) + +def savePaletteDefinition(fModel, image, imageName, texFormat, palFormat, + bitSize): + # If image already loaded, return that data. + paletteName = toAlnum(imageName) + '_pal_' + palFormat + if paletteName in fModel.textures: + return fModel.textures[paletteName] + + palette = [] + texture = [] + maxColors = 16 if bitSize == 'G_IM_SIZ_4b' else 256 + for i in range(image.size[0] * image.size[1]): + color = [1,1,1,1] + for field in range(image.channels): + color[field] = image.pixels[i * image.channels + field] + if palFormat == 'G_IM_FMT_RGBA': + pixelColor = \ + ((int(color[0] * 0x1F) & 0x1F) << 11) | \ + ((int(color[1] * 0x1F) & 0x1F) << 6) | \ + ((int(color[2] * 0x1F) & 0x1F) << 1) | \ + (1 if color[3] > 0.5 else 0) + elif palFormat == 'G_IM_FMT_IA': + intensity = mathutils.Color(color[0:3]).v + alpha = color[3] + pixelColor = (int(intensity * 0xFF) << 8) | int(alpha * 0xFF) + else: + raise ValueError("Invalid combo: " + palFormat + ', ' + bitSize) + + if pixelColor not in palette: + palette.append(pixelColor) + if len(palette) > maxColors: + raise ValueError('Texture ' + imageName + ' has more than ' + \ + str(maxColors) + ' colors.') + texture.append(palette.index(pixelColor)) + + fImage = FImage(toAlnum(imageName), texFormat, bitSize, + image.size[0], image.size[1]) + + fPalette = FImage(paletteName, palFormat, 'G_IM_SIZ_16b', 1, len(palette)) + + for color in palette: + fPalette.data.extend(color.to_bytes(2, 'big')) + + if bitSize == 'G_IM_SIZ_4b': + fImage.data = compactNibbleArray(texture, image.size[0], image.size[1]) + else: + fImage.data = bytearray(texture) + + return fImage, fPalette + +def compactNibbleArray(texture, width, height): + nibbleData = bytearray(0) + dataSize = int(width * height / 2) + + for i in range(dataSize): + nibbleData.append( + ((texture[i * 2] & 0xF) << 4) |\ + (texture[i * 2 + 1] & 0xF) + ) + + if (width * height) % 2 == 1: + nibbleData.append((texture[-1] & 0xF) << 4) + + return nibbleData + +def saveTextureDefinition(fModel, image, imageName, fmt, bitSize): + # If image already loaded, return that data. + if toAlnum(imageName) in fModel.textures: + return fModel.textures[toAlnum(imageName)] + + fImage = FImage(toAlnum(imageName), fmt, bitSize, + image.size[0], image.size[1]) + + for i in range(image.size[0] * image.size[1]): + color = [1,1,1,1] + for field in range(image.channels): + color[field] = image.pixels[i * image.channels + field] + if fmt == 'G_IM_FMT_RGBA': + if bitSize == 'G_IM_SIZ_16b': + words = \ + ((int(color[0] * 0x1F) & 0x1F) << 11) | \ + ((int(color[1] * 0x1F) & 0x1F) << 6) | \ + ((int(color[2] * 0x1F) & 0x1F) << 1) | \ + (1 if color[3] > 0.5 else 0) + fImage.data.extend(bytearray(words.to_bytes(2, 'big'))) + elif bitSize == 'G_IM_SIZ_32b': + fImage.data.extend(bytearray([ + int(value * 0xFF) & 0xFF for value in color])) + else: + raise ValueError("Invalid combo: " + fmt + ', ' + bitSize) + + elif fmt == 'G_IM_FMT_YUV': + raise ValueError("YUV not yet implemented.") + if bitSize == 'G_IM_SIZ_16b': + pass + else: + raise ValueError("Invalid combo: " + fmt + ', ' + bitSize) + + elif fmt == 'G_IM_FMT_CI': + raise ValueError("CI not yet implemented.") + + elif fmt == 'G_IM_FMT_IA': + intensity = mathutils.Color(color[0:3]).v + alpha = color[3] + if bitSize == 'G_IM_SIZ_4b': + fImage.data.append( + ((int(intensity * 0x7) & 0x7) << 1) | \ + (1 if alpha > 0.5 else 0)) + elif bitSize == 'G_IM_SIZ_8b': + fImage.data.append( + ((int(intensity * 0xF) & 0xF) << 4) | \ + (int(alpha * 0xF) & 0xF)) + elif bitSize == 'G_IM_SIZ_16b': + fImage.data.extend(bytearray( + [int(intensity * 0xFF), int(alpha * 0xFF)])) + else: + raise ValueError("Invalid combo: " + fmt + ', ' + bitSize) + elif fmt == 'G_IM_FMT_I': + intensity = mathutils.Color(color[0:3]).v + if bitSize == 'G_IM_SIZ_4b': + fImage.data.append(int(intensity * 0xF)) + elif bitSize == 'G_IM_SIZ_8b': + fImage.data.append(int(intensity * 0xFF)) + else: + raise ValueError("Invalid combo: " + fmt + ', ' + bitSize) + else: + raise ValueError("Invalid image format " + fmt) + + # We stored 4bit values in byte arrays, now to convert + if bitSize == 'G_IM_SIZ_4b': + fImage.data = \ + compactNibbleArray(fImage.data, image.size[0], image.size[1]) + + return fImage + +def saveLightsDefinition(fModel, material, lightsName): + nodes = material.node_tree.nodes + ambientColor = gammaCorrect(nodes['Ambient Color'].outputs[0].default_value) + lights = Lights(toAlnum(lightsName)) + lights.a = Ambient( + [int(ambientColor[0] * 255), + int(ambientColor[1] * 255), + int(ambientColor[2] * 255)]) + + if material.f3d_light1 is not None: + addLightDefinition(material.f3d_light1, lights) + if material.f3d_light2 is not None: + addLightDefinition(material.f3d_light2, lights) + if material.f3d_light3 is not None: + addLightDefinition(material.f3d_light3, lights) + if material.f3d_light4 is not None: + addLightDefinition(material.f3d_light4, lights) + if material.f3d_light5 is not None: + addLightDefinition(material.f3d_light5, lights) + if material.f3d_light6 is not None: + addLightDefinition(material.f3d_light6, lights) + if material.f3d_light7 is not None: + addLightDefinition(material.f3d_light7, lights) + + fModel.lights[lightsName] = lights + return lights + +def addLightDefinition(f3d_light, fLights): + lightObj = None + for obj in bpy.context.scene.objects: + if obj.data == f3d_light: + lightObj = obj + break + if lightObj is None: + raise ValueError( + "You are referencing a light that is no longer in the scene.") + + #spaceRot = blenderToSM64Rotation.to_4x4().to_quaternion() + spaceRot = mathutils.Euler((-pi / 2, 0, 0)).to_quaternion() + rotation = spaceRot @ getObjectQuaternion(lightObj) + + normal = (rotation @ mathutils.Vector((0,0,-1))).normalized() + color = gammaCorrect(f3d_light.color) + + fLights.l.append(Light( + [ + int(color[0] * 255), + int(color[1] * 255), + int(color[2] * 255) + ], + [ + # Make sure to handle negative values + int.from_bytes(int(normal[0] * 127).to_bytes(1, 'big', + signed = True), 'big'), + int.from_bytes(int(normal[1] * 127).to_bytes(1, 'big', + signed = True), 'big'), + int.from_bytes(int(normal[2] * 127).to_bytes(1, 'big', + signed = True), 'big') + ], + )) + +def saveBitGeo(value, defaultValue, flagName, setGeo, clearGeo): + if value != defaultValue: + if value: + setGeo.flagList.append(flagName) + else: + clearGeo.flagList.append(flagName) + +def saveGeoModeDefinition(fMaterial, settings, defaults): + setGeo = SPSetGeometryMode([]) + clearGeo = SPClearGeometryMode([]) + + saveBitGeo(settings.g_zbuffer, defaults.g_zbuffer, 'G_ZBUFFER', + setGeo, clearGeo) + saveBitGeo(settings.g_shade, defaults.g_shade, 'G_SHADE', + setGeo, clearGeo) + saveBitGeo(settings.g_cull_front, defaults.g_cull_front, 'G_CULL_FRONT', + setGeo, clearGeo) + saveBitGeo(settings.g_cull_back, defaults.g_cull_back, 'G_CULL_BACK', + setGeo, clearGeo) + saveBitGeo(settings.g_fog, defaults.g_fog, 'G_FOG', setGeo, clearGeo) + saveBitGeo(settings.g_lighting, defaults.g_lighting, 'G_LIGHTING', + setGeo, clearGeo) + + # make sure normals are saved correctly. + saveBitGeo(settings.g_tex_gen, defaults.g_tex_gen, 'G_TEXTURE_GEN', + setGeo, clearGeo) + saveBitGeo(settings.g_tex_gen_linear, defaults.g_tex_gen_linear, + 'G_TEXTURE_GEN_LINEAR', setGeo, clearGeo) + saveBitGeo(settings.g_shade_smooth, defaults.g_shade_smooth, + 'G_SHADING_SMOOTH', setGeo, clearGeo) + if bpy.context.scene.f3d_type == 'F3DEX_GBI_2' or \ + bpy.context.scene.f3d_type == 'F3DEX_GBI': + saveBitGeo(settings.g_clipping, defaults.g_clipping, 'G_CLIPPING', + setGeo, clearGeo) + + if len(setGeo.flagList) > 0: + fMaterial.material.commands.append(setGeo) + fMaterial.revert.commands.append(SPClearGeometryMode(setGeo.flagList)) + if len(clearGeo.flagList) > 0: + fMaterial.material.commands.append(clearGeo) + fMaterial.revert.commands.append(SPSetGeometryMode(clearGeo.flagList)) + +def saveModeSetting(fMaterial, value, defaultValue, cmdClass): + if value != defaultValue: + fMaterial.material.commands.append(cmdClass(value)) + fMaterial.revert.commands.append(cmdClass(defaultValue)) + +def saveOtherModeHDefinition(fMaterial, settings, defaults): + saveModeSetting(fMaterial, settings.g_mdsft_alpha_dither, + defaults.g_mdsft_alpha_dither, DPSetAlphaDither) + + if not bpy.context.scene.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_textconv, + defaults.g_mdsft_textconv, DPSetTextureConvert) + + saveModeSetting(fMaterial, settings.g_mdsft_text_filt, + defaults.g_mdsft_text_filt, DPSetTextureFilter) + + saveModeSetting(fMaterial, settings.g_mdsft_textlut, + defaults.g_mdsft_textlut, 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 bpy.context.scene.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): + saveModeSetting(fMaterial, settings.g_mdsft_alpha_compare, + defaults.g_mdsft_alpha_compare, DPSetAlphaCompare) + + saveModeSetting(fMaterial, settings.g_mdsft_zsrcsel, + defaults.g_mdsft_zsrcsel, DPSetDepthSource) + + # cycle independent + if settings.set_rendermode: + if settings.g_mdsft_cycletype == 'G_CYC_2CYCLE': + renderModeSet = DPSetRenderMode([], + settings.blend_p1, settings.blend_a1, + settings.blend_m1, settings.blend_b1, + settings.blend_p2, settings.blend_a2, + settings.blend_m2, settings.blend_b2) + else: + renderModeSet = DPSetRenderMode([], + settings.blend_p1, settings.blend_a1, + settings.blend_m1, settings.blend_b1, + settings.blend_p1, settings.blend_a1, + settings.blend_m1, settings.blend_b1) + + if settings.aa_en: + renderModeSet.flagList.append("AA_EN") + if settings.z_cmp: + renderModeSet.flagList.append("Z_CMP") + if settings.z_upd: + renderModeSet.flagList.append("Z_UPD") + if settings.im_rd: + renderModeSet.flagList.append("IM_RD") + if settings.clr_on_cvg: + renderModeSet.flagList.append("CLR_ON_CVG") + + renderModeSet.flagList.append(settings.cvg_dst) + renderModeSet.flagList.append(settings.zmode) + + if settings.cvg_x_alpha: + renderModeSet.flagList.append("CVG_X_ALPHA") + if settings.alpha_cvg_sel: + renderModeSet.flagList.append("ALPHA_CVG_SEL") + if settings.force_bl: + renderModeSet.flagList.append("FORCE_BL") + + fMaterial.material.commands.append(renderModeSet) + #fMaterial.revert.commands.append(defaultRenderMode) \ No newline at end of file diff --git a/fast64_internal/sm64_anim.py b/fast64_internal/sm64_anim.py new file mode 100644 index 0000000..d29dfd5 --- /dev/null +++ b/fast64_internal/sm64_anim.py @@ -0,0 +1,501 @@ +import bpy +import mathutils +import math +import re +from .utility import * +from .sm64_constants import * +from math import pi + +sm64_anim_types = {'ROTATE', 'TRANSLATE'} + +class SM64_Animation: + def __init__(self, name): + self.name = name + self.header = None + self.indices = SM64_ShortArray(name + '_indices', False) + self.values = SM64_ShortArray(name + '_values', False) + + def to_binary(self, segmentData, isDMA, startAddress): + return self.header.to_binary(segmentData, isDMA, startAddress) + \ + self.indices.to_binary() + \ + self.values.to_binary() + + def to_c(self): + return self.values.to_c() + '\n' +\ + self.indices.to_c() + '\n' +\ + self.header.to_c() + '\n' + +class SM64_ShortArray: + def __init__(self, name, signed): + self.name = name + self.shortData = [] + self.signed = signed + + def to_binary(self): + data = bytearray(0) + for short in self.shortData: + #print(self.name + str(short)) + data += short.to_bytes(2, 'big', signed = self.signed) + return data + + def to_c(self): + data = 'static const ' + ('s' if self.signed else 'u') + \ + '16 ' + self.name + '[] = {\n\t' + wrapCounter = 0 + for short in self.shortData: + data += '0x' + format(short, '04X') + ', ' + wrapCounter += 1 + if wrapCounter > 8: + data += '\n\t' + wrapCounter = 0 + data += '\n};\n' + return data + +class SM64_AnimationHeader: + def __init__(self, name, repetitions, marioYOffset, frameInterval, + nodeCount, transformValuesStart, transformIndicesStart, animSize): + self.name = name + self.repetitions = repetitions + self.marioYOffset = marioYOffset + self.frameInterval = frameInterval + self.nodeCount = nodeCount + self.transformValuesStart = transformValuesStart + self.transformIndicesStart = transformIndicesStart + self.animSize = animSize # DMA animations only + + self.transformIndices = [] + + # presence of segmentData indicates DMA. + def to_binary(self, segmentData, isDMA, startAddress): + if isDMA: + transformValuesStart = self.transformValuesStart + transformIndicesStart = self.transformIndicesStart + else: + transformValuesStart = self.transformValuesStart + startAddress + transformIndicesStart = self.transformIndicesStart + startAddress + + data = bytearray(0) + data.extend(self.repetitions.to_bytes(2, byteorder='big')) + data.extend(self.marioYOffset.to_bytes(2, byteorder='big')) # y offset, only used for mario + data.extend([0x00, 0x00]) # unknown, common with secondary anims, variable length animations? + data.extend(int(round(self.frameInterval[0])).to_bytes(2, byteorder='big')) + data.extend(int(round(self.frameInterval[1] - 1)).to_bytes(2, byteorder='big')) + data.extend(self.nodeCount.to_bytes(2, byteorder='big')) + if not isDMA: + data.extend(encodeSegmentedAddr(transformValuesStart, segmentData)) + data.extend(encodeSegmentedAddr(transformIndicesStart, segmentData)) + data.extend(bytearray([0x00] * 6)) + else: + data.extend(transformValuesStart.to_bytes(4, byteorder='big')) + data.extend(transformIndicesStart.to_bytes(4, byteorder='big')) + data.extend(self.animSize.to_bytes(4, byteorder='big')) + data.extend(bytearray([0x00] * 2)) + return data + + def to_c(self): + data = 'static const struct Animation ' + self.name + '[] = {\n' +\ + '\t' + str(self.repetitions) + ',\n' + \ + '\t' + str(self.marioYOffset) + ',\n' + \ + '\t0,\n' + \ + '\t' + str(int(round(self.frameInterval[0]))) + ',\n' + \ + '\t' + str(int(round(self.frameInterval[1] - 1))) + ',\n' + \ + '\tANIMINDEX_NUMPARTS(' + self.name + '_indices),\n' + \ + '\t' + self.name + '_values,\n' + \ + '\t' + self.name + '_indices,\n' + \ + '\t0,\n' + \ + '};\n' + return data + +class SM64_AnimIndexNode: + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + +class SM64_AnimIndex: + def __init__(self, numFrames, startOffset): + self.startOffset = startOffset + self.numFrames = numFrames + +def getLastKeyframeTime(keyframes): + last = keyframes[0].co[0] + for keyframe in keyframes: + if keyframe.co[0] > last: + last = keyframe.co[0] + return last + +def exportAnimationC(armatureObj, loopAnim, filePath): + sm64_anim = exportAnimationCommon(armatureObj, loopAnim) + + data = sm64_anim.to_c() + outFile = open(filePath, 'w') + outFile.write(data) + outFile.close() + +def exportAnimationBinary(romfile, exportRange, armatureObj, DMAAddresses, + segmentData, isDMA, loopAnim): + + startAddress = get64bitAlignedAddr(exportRange[0]) + sm64_anim = exportAnimationCommon(armatureObj, loopAnim) + + animData = sm64_anim.to_binary(segmentData, isDMA, startAddress) + + if startAddress + len(animData) > exportRange[1]: + raise ValueError('Size too big: Data ends at ' + \ + hex(startAddress + len(animData)) +\ + ', which is larger than the specified range.') + + romfile.seek(startAddress) + romfile.write(animData) + + addrRange = (startAddress, startAddress + len(animData)) + + if not isDMA: + animTablePointer = get64bitAlignedAddr(startAddress + len(animData)) + romfile.seek(animTablePointer) + romfile.write(encodeSegmentedAddr(startAddress, segmentData)) + return addrRange, animTablePointer + else: + if DMAAddresses is not None: + romfile.seek(DMAAddresses['entry']) + romfile.write((startAddress - DMAAddresses['start']).to_bytes( + 4, byteorder = 'big')) + romfile.seek(DMAAddresses['entry'] + 4) + romfile.write(len(animData).to_bytes(4, byteorder='big')) + return addrRange, None + +def exportAnimationCommon(armatureObj, loopAnim): + if armatureObj.animation_data is None or \ + armatureObj.animation_data.action is None: + raise ValueError("No active animation selected.") + anim = armatureObj.animation_data.action + sm64_anim = SM64_Animation(toAlnum(anim.name)) + + nodeCount = len(armatureObj.data.bones) + translationData, armatureFrameData = convertAnimationData(anim, armatureObj) + + repetitions = 0 if loopAnim else 1 + marioYOffset = 0x00 # ??? Seems to be this value for most animations + frameInterval = [int(round(anim.frame_range[0])), + int(round(anim.frame_range[1])) + 1] + + transformValuesOffset = 0 + headerSize = 0x1A + transformIndicesStart = headerSize #0x18 if including animSize? + + # all node rotations + root translation + # *3 for each property (xyz) and *4 for entry size + # each keyframe stored as 2 bytes + # transformValuesStart = transformIndicesStart + (nodeCount + 1) * 3 * 4 + transformValuesStart = transformIndicesStart + + for translationFrameProperty in translationData: + frameCount = len(translationFrameProperty) + sm64_anim.indices.shortData.append(frameCount) + sm64_anim.indices.shortData.append(transformValuesOffset) + if(transformValuesOffset) > 2**16 - 1: + raise ValueError('Animation is too large.') + transformValuesOffset += frameCount + transformValuesStart += 4 + for value in translationFrameProperty: + sm64_anim.values.shortData.append(int.from_bytes(value.to_bytes(2,'big', signed = True), byteorder = 'big', signed = False)) + + for boneFrameData in armatureFrameData: + for boneFrameDataProperty in boneFrameData: + frameCount = len(boneFrameDataProperty) + sm64_anim.indices.shortData.append(frameCount) + sm64_anim.indices.shortData.append(transformValuesOffset) + if(transformValuesOffset) > 2**16 - 1: + raise ValueError('Animation is too large.') + transformValuesOffset += frameCount + transformValuesStart += 4 + for value in boneFrameDataProperty: + sm64_anim.values.shortData.append(value) + + animSize = headerSize + len(sm64_anim.indices.shortData) * 2 + \ + len(sm64_anim.values.shortData) * 2 + + sm64_anim.header = SM64_AnimationHeader(sm64_anim.name, repetitions, + marioYOffset, frameInterval, nodeCount, transformValuesStart, + transformIndicesStart, animSize) + + return sm64_anim + +def saveQuaternionFrame(frameData, rotation): + for i in range(3): + field = rotation.to_euler()[i] + value = (math.degrees(field) % 360 ) / 360 + frameData[i].append(min(int(round(value * (2**16 - 1))), 2**16 - 1)) + +def removeTrailingFrames(frameData): + for i in range(3): + if len(frameData[i]) < 2: + continue + lastUniqueFrame = len(frameData[i]) - 1 + while lastUniqueFrame > 0: + if frameData[i][lastUniqueFrame] == \ + frameData[i][lastUniqueFrame - 1]: + lastUniqueFrame -= 1 + else: + break + frameData[i] = frameData[i][:lastUniqueFrame + 1] + +def saveTranslationFrame(frameData, translation): + for i in range(3): + frameData[i].append(min(int(round(translation[i] / sm64ToBlenderScale)), + 2**16 - 1)) + +def convertAnimationData(anim, armatureObj): + if 'root' not in armatureObj.data.bones: + raise ValueError('Cannot find bone named "root". The first animatable' +\ + ' (0x13) bone in the armature must be named "root."') + + currentBone = armatureObj.data.bones["root"] + bonesToProcess = [currentBone.name] + animBones = [] + + # Get animation bones in order + while len(bonesToProcess) > 0: + boneName = bonesToProcess[0] + currentBone = armatureObj.data.bones[boneName] + currentPoseBone = armatureObj.pose.bones[boneName] + bonesToProcess = bonesToProcess[1:] + + # Only handle 0x13 bones for animation + if currentPoseBone.bone_group is None: + animBones.append(boneName) + + # Traverse children in alphabetical order. + childrenNames = sorted([bone.name for bone in currentBone.children]) + bonesToProcess = childrenNames + bonesToProcess + + # list of boneFrameData, which is [[x frames], [y frames], [z frames]] + translationData = [[],[],[]] + armatureFrameData = [] + for i in range(len(animBones)): + armatureFrameData.append([[],[],[]]) + for frame in range(int(round(anim.frame_range[1])) + 1): + bpy.context.scene.frame_set(frame) + translation = armatureObj.pose.bones["root"].location + saveTranslationFrame(translationData, translation) + + for boneIndex in range(len(animBones)): + boneName = animBones[boneIndex] + currentBone = armatureObj.data.bones[boneName] + currentPoseBone = armatureObj.pose.bones[boneName] + + rotationValue = currentPoseBone.matrix.to_quaternion() + if currentBone.parent is not None: + rotationValue = (currentPoseBone.parent.matrix.inverted() @ \ + currentPoseBone.matrix).to_quaternion() + + saveQuaternionFrame(armatureFrameData[boneIndex], rotationValue) + + removeTrailingFrames(translationData) + for frameData in armatureFrameData: + removeTrailingFrames(frameData) + + return translationData, armatureFrameData + +def getNextBone(boneStack, armatureObj): + if len(boneStack) == 0: + raise ValueError("More bones in animation than on armature.") + bone = armatureObj.data.bones[boneStack[0]] + boneStack = boneStack[1:] + boneStack = sorted([child.name for child in bone.children]) + boneStack + + # Only return 0x13 bone + while armatureObj.pose.bones[bone.name].bone_group is not None: + if len(boneStack) == 0: + raise ValueError("More bones in animation than on armature.") + bone = armatureObj.data.bones[boneStack[0]] + boneStack = boneStack[1:] + boneStack = sorted([child.name for child in bone.children]) + boneStack + + return bone, boneStack + +def importAnimationToBlender(romfile, startAddress, armatureObj, segmentData, isDMA): + if 'root' not in armatureObj.data.bones: + raise ValueError('Cannot find bone named "root". The first animatable' +\ + '(0x13) bone in the armature must be named "root."') + + animationHeader, armatureFrameData = \ + readAnimation('sm64_anim', romfile, startAddress, segmentData, isDMA) + + if len(armatureFrameData) > len(armatureObj.data.bones) + 1: + raise ValueError('More bones in animation than on armature.') + + #bpy.context.scene.render.fps = 30 + bpy.context.scene.frame_end = animationHeader.frameInterval[1] + anim = bpy.data.actions.new("sm64_anim") + + boneStack = ['root'] + + isRootTranslation = True + # boneFrameData = [[x keyframes], [y keyframes], [z keyframes]] + # len(armatureFrameData) should be = number of bones + # property index = 0,1,2 (aka x,y,z) + for boneFrameData in armatureFrameData: + if isRootTranslation: + for propertyIndex in range(3): + fcurve = anim.fcurves.new( + data_path = 'pose.bones["root"].location', + index = propertyIndex, + action_group = 'root') + for frame in range(len(boneFrameData[propertyIndex])): + fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame]) + isRootTranslation = False + else: + bone, boneStack = getNextBone(boneStack, armatureObj) + print(bone.name) + for propertyIndex in range(3): + fcurve = anim.fcurves.new( + data_path = 'pose.bones["' + bone.name + '"].rotation_euler', + index = propertyIndex, + action_group = bone.name) + for frame in range(len(boneFrameData[propertyIndex])): + fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame]) + + if armatureObj.animation_data is None: + armatureObj.animation_data_create() + armatureObj.animation_data.action = anim + +def readAnimation(name, romfile, startAddress, segmentData, isDMA): + animationHeader = readAnimHeader(name, romfile, startAddress, segmentData, isDMA) + + print("Frames: " + str(animationHeader.frameInterval[1]) + " / Nodes: " + str(animationHeader.nodeCount)) + + animationHeader.transformIndices = readAnimIndices( + romfile, animationHeader.transformIndicesStart, animationHeader.nodeCount) + + armatureFrameData = [] #list of list of frames + + # sm64 space -> blender space -> pose space + # BlenderToSM64: YZX (set rotation mode of bones) + # SM64toBlender: ZXY (set anim keyframes and model armature) + # new bones should extrude in +Y direction + + # handle root translation + boneFrameData = [[],[],[]] + rootIndexNode = animationHeader.transformIndices[0] + boneFrameData[0] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.x)] + boneFrameData[1] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.y)] + boneFrameData[2] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.z)] + armatureFrameData.append(boneFrameData) + + # handle rotations + for boneIndexNode in animationHeader.transformIndices[1:]: + boneFrameData = [[],[],[]] + + # Transforming SM64 space to Blender space + boneFrameData[0] = [n for n in \ + getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.x)] + boneFrameData[1] = [n for n in \ + getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.y)] + boneFrameData[2] = [n for n in \ + getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.z)] + + armatureFrameData.append(boneFrameData) + + return (animationHeader, armatureFrameData) + +def getKeyFramesRotation(romfile, transformValuesStart, boneIndex): + ptrToValue = transformValuesStart + boneIndex.startOffset + romfile.seek(ptrToValue) + + keyframes = [] + for frame in range(boneIndex.numFrames): + romfile.seek(ptrToValue + frame * 2) + value = int.from_bytes(romfile.read(2), 'big') * 360 / (2**16) + keyframes.append(math.radians(value)) + + return keyframes + +def getKeyFramesTranslation(romfile, transformValuesStart, boneIndex): + ptrToValue = transformValuesStart + boneIndex.startOffset + romfile.seek(ptrToValue) + + keyframes = [] + for frame in range(boneIndex.numFrames): + romfile.seek(ptrToValue + frame * 2) + keyframes.append(int.from_bytes(romfile.read(2), 'big', signed = True) *\ + sm64ToBlenderScale) + + return keyframes + +def readAnimHeader(name, romfile, startAddress, segmentData, isDMA): + frameInterval = [0,0] + + romfile.seek(startAddress + 0x00) + numRepeats = int.from_bytes(romfile.read(2), 'big') + + romfile.seek(startAddress + 0x02) + marioYOffset = int.from_bytes(romfile.read(2), 'big') + + romfile.seek(startAddress + 0x06) + frameInterval[0] = int.from_bytes(romfile.read(2), 'big') + + romfile.seek(startAddress + 0x08) + frameInterval[1] = int.from_bytes(romfile.read(2), 'big') + + romfile.seek(startAddress + 0x0A) + numNodes = int.from_bytes(romfile.read(2), 'big') + + romfile.seek(startAddress + 0x0C) + transformValuesOffset = int.from_bytes(romfile.read(4), 'big') + if isDMA: + transformValuesStart = startAddress + transformValuesOffset + else: + transformValuesStart = decodeSegmentedAddr( + transformValuesOffset.to_bytes(4, byteorder='big'), segmentData) + + romfile.seek(startAddress + 0x10) + transformIndicesOffset = int.from_bytes(romfile.read(4), 'big') + if isDMA: + transformIndicesStart = startAddress + transformIndicesOffset + else: + transformIndicesStart = decodeSegmentedAddr( + transformIndicesOffset.to_bytes(4, byteorder='big'), segmentData) + + romfile.seek(startAddress + 0x14) + animSize = int.from_bytes(romfile.read(4), 'big') + + return SM64_AnimationHeader(name, numRepeats, marioYOffset, frameInterval, numNodes, + transformValuesStart, transformIndicesStart, animSize) + +def readAnimIndices(romfile, ptrAddress, nodeCount): + indices = [] + + # Handle root transform + rootPosIndex = readTransformIndex(romfile, ptrAddress) + indices.append(rootPosIndex) + + # Handle rotations + for i in range(nodeCount): + rotationIndex = readTransformIndex(romfile, ptrAddress + (i+1) * 12) + indices.append(rotationIndex) + + return indices + +def readTransformIndex(romfile, startAddress): + x = readValueIndex(romfile, startAddress + 0) + y = readValueIndex(romfile, startAddress + 4) + z = readValueIndex(romfile, startAddress + 8) + + return SM64_AnimIndexNode(x, y, z) + +def readValueIndex(romfile, startAddress): + romfile.seek(startAddress) + numFrames = int.from_bytes(romfile.read(2), 'big') + romfile.seek(startAddress + 2) + + # multiply 2 because value is the index in array of shorts (???) + startOffset = int.from_bytes(romfile.read(2), 'big') * 2 + print(str(hex(startAddress)) + ": " + str(numFrames) + " " + str(startOffset)) + return SM64_AnimIndex(numFrames, startOffset) + +def writeAnimation(romfile, startAddress, segmentData): + pass + +def writeAnimHeader(romfile, startAddress, segmentData): + pass \ No newline at end of file diff --git a/fast64_internal/sm64_collision.py b/fast64_internal/sm64_collision.py new file mode 100644 index 0000000..cf480ca --- /dev/null +++ b/fast64_internal/sm64_collision.py @@ -0,0 +1,936 @@ +from .utility import * +from .sm64_constants import * +from bpy.utils import register_class, unregister_class +import bpy, bmesh + +class CollisionVertex: + def __init__(self, position): + self.position = position + + def to_binary(self): + data = bytearray(0) + if len(self.position) > 3: + raise ValueError("Vertex position should not be " + \ + str(len(self.position) + ' fields long.')) + for field in self.position: + data.extend(int(round(field)).to_bytes(2, 'big', signed = True)) + return data + + def to_c(self): + return 'COL_VERTEX(' + \ + str(int(round(self.position[0]))) + ', ' + \ + str(int(round(self.position[1]))) + ', ' + \ + str(int(round(self.position[2]))) + '),\n' + +class CollisionTriangle: + def __init__(self, indices): + self.indices = indices + self.specialParam = None + + def to_binary(self): + data = bytearray(0) + if len(self.indices) > 3: + raise ValueError("Triangle indices should not be " + \ + str(len(self.indices) + ' fields long.')) + for index in self.indices: + data.extend(int(round(index)).to_bytes(2, 'big', signed = False)) + if self.specialParam is not None: + data.extend(int(self.specialParam).to_bytes(2, 'big', signed=False)) + return data + + def to_c(self): + if self.specialParam is None: + return 'COL_TRI(' + \ + str(int(round(self.indices[0]))) + ', ' + \ + str(int(round(self.indices[1]))) + ', ' + \ + str(int(round(self.indices[2]))) + '),\n' + else: + return 'COL_TRI_SPECIAL(' + \ + str(int(round(self.indices[0]))) + ', ' + \ + str(int(round(self.indices[1]))) + ', ' + \ + str(int(round(self.indices[2]))) + ', ' + \ + str(int(self.specialParam)) + '),\n' + +class CollisionSpecial: + def __init__(self): + self.preset = None + self.position = [None] * 3 + self.yaw = None + self.specialParam = None + + def to_binary(self): + data = int(self.preset).to_bytes(2, 'big') + if len(self.position) > 3: + raise ValueError("Object position should not be " + \ + str(len(self.position) + ' fields long.')) + for index in self.position: + data.extend(int(round(index)).to_bytes(2, 'big', signed = False)) + if self.yaw is not None: + data.extend(int(self.yaw).to_bytes(2, 'big')) + if self.specialParam is not None: + data.extend(int(self.specialParam).to_bytes(2, 'big')) + return data + + def to_c(self): + if self.yaw is None: + data = 'SPECIAL_OBJECT(' + elif self.specialParam is None: + data = 'SPECIAL_OBJECT_WITH_YAW(' + else: + data = 'SPECIAL_OBJECT_WITH_YAW_AND_PARAM(' + + data += str(self.preset) + ', ' +\ + str(int(round(self.position[0]))) + ', ' + \ + str(int(round(self.position[1]))) + ', ' + \ + str(int(round(self.position[2]))) + + if self.yaw is None: + data += '),\n' + elif self.specialParam is None: + data += ', ' + str(self.yaw) + '),\n' + else: + data += ', ' + str(self.yaw) + ', ' + str(self.specialParam) + '),\n' + return data + +class CollisionWaterBox: + def __init__(self): + self.waterBoxType = 'Water' + self.low = (0,0) + self.high = (0,0) + self.height = 0 + + def to_binary(self): + data = bytearray([0x00, 0x00 if self.waterBoxType == 'Water' else 0x32]) + data.extend(int(round(self.low[0])).to_bytes(2, 'big', signed=True)) + data.extend(int(round(self.low[1])).to_bytes(2, 'big', signed=True)) + data.extend(int(round(self.high[0])).to_bytes(2, 'big', signed=True)) + data.extend(int(round(self.high[1])).to_bytes(2, 'big', signed=True)) + data.extend(int(round(self.height)).to_bytes(2, 'big', signed=True)) + return data + + def to_c(self): + data = 'COL_WATER_BOX(' + \ + ('0x00' if self.waterBoxType == 'Water' else '0x32') + ', ' + \ + str(int(round(self.low[0]))) + ', ' + \ + str(int(round(self.low[1]))) + ', ' + \ + str(int(round(self.high[0]))) + ', ' + \ + str(int(round(self.high[1]))) + ', ' + \ + str(int(round(self.height))) + '),\n' + return data + +class Collision: + def __init__(self, name): + self.name = name + self.startAddress = 0 + self.vertices = [] + # dict of collision type : triangle list + self.triangles = {} + self.specials = [] + self.waterBoxes = [] + + def set_addr(self, startAddress): + startAddress = get64bitAlignedAddr(startAddress) + self.startAddress = startAddress + print('Collision ' + self.name + ': ' + str(startAddress) + \ + ', ' + str(self.size())) + return startAddress, startAddress + self.size() + + def save_binary(self, romfile): + romfile.seek(self.startAddress) + romfile.write(self.to_binary()) + + def size(self): + return len(self.to_binary()) + + def to_c(self): + data = 'const Collision ' + self.name + '[] = {\n' + data += '\tCOL_INIT(),\n' + data += '\tCOL_VERTEX_INIT(' + str(len(self.vertices)) + '),\n' + for vertex in self.vertices: + data += '\t' + vertex.to_c() + for collisionType, triangles in self.triangles.items(): + data += '\tCOL_TRI_INIT(' + collisionType + ', ' +\ + str(len(triangles)) + '),\n' + for triangle in triangles: + data += '\t' + triangle.to_c() + data += '\tCOL_TRI_STOP(),\n' + if len(self.specials) > 0: + data += '\tCOL_SPECIAL_INIT(' + str(len(self.specials)) + '),\n' + for special in self.specials: + data += '\t' + special.to_c() + if len(self.waterBoxes) > 0: + data += '\tCOL_WATER_BOX_INIT(' + str(len(self.waterBoxes)) + '),\n' + for waterBox in self.waterBoxes: + data += '\t' + waterBox.to_c() + data += '\tCOL_END()\n' + '};\n' + return data + + def to_binary(self): + colTypeDef = CollisionTypeDefinition() + data = bytearray([0x00, 0x40]) + data += len(self.vertices).to_bytes(2, 'big') + for vertex in self.vertices: + data += vertex.to_binary() + for collisionType, triangles in self.triangles.items(): + data += getattr(colTypeDef, collisionType).to_bytes(2, 'big') + data += len(triangles).to_bytes(2, 'big') + for triangle in triangles: + data += triangle.to_binary() + data += bytearray([0x00, 0x41]) + if len(self.specials) > 0: + data += bytearray([0x00, 0x43]) + data += len(self.specials).to_bytes(2, 'big') + for special in self.specials: + data += special.to_binary() + if len(self.waterBoxes) > 0: + data += bytearray([0x00, 0x44]) + data += len(self.waterBoxes).to_bytes(2, 'big') + for waterBox in self.waterBoxes: + data += waterBox.to_binary() + data += bytearray([0x00, 0x42]) + return data + +class CollisionPanel(bpy.types.Panel): + bl_label = "Collision Inspector" + bl_idname = "SM64_Collision_Inspector" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "material" + bl_options = {'HIDE_HEADER'} + + @classmethod + def poll(cls, context): + return (context.material is not None) + + def draw(self, context): + box = self.layout.box() + #box.label(text = 'Collision Inspector') + material = context.material + if not material.collision_all_options: + prop_split(box, material, 'collision_type_simple', + 'SM64 Collision Type') + else: + prop_split(box, material, 'collision_type', + 'SM64 Collision Type All') + + split = box.split(factor = 0.5) + split.label(text = '') + split.prop(material, 'collision_all_options') + #infoBox = box.box() + #infoBox.label(text = \ + # 'For special params, make a vert color layer named "Collision."') + #infoBox.label(text = 'The red value 0-1 will be converted to 0-65535.') + +class SM64ObjectPanel(bpy.types.Panel): + bl_label = "Object Inspector" + bl_idname = "SM64_Object_Inspector" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + bl_options = {'HIDE_HEADER'} + + @classmethod + def poll(cls, context): + return (context.object is not None) + + def draw(self, context): + box = self.layout.box() + box.label(text = 'SM64 Object Inspector') + obj = context.object + prop_split(box, obj, 'sm64_obj_type', 'Special Preset') + if obj.sm64_obj_type == 'Special': + prop_split(box, obj, 'sm64_special_preset', 'Special Preset') + elif obj.sm64_obj_type == 'Water Box': + prop_split(box, obj, 'sm64_water_box', 'Water Box Type') + +def exportCollisionBinary(obj, transformMatrix, romfile, startAddress, + endAddress, addEntities): + collision = exportCollisionCommon(obj, transformMatrix, addEntities) + start, end = collision.set_addr(startAddress) + if end > endAddress: + raise ValueError('Size too big: Data ends at ' + hex(end) +\ + ', which is larger than the specified range.') + collision.save_binary(romfile) + return start, end + +def exportCollisionC(obj, transformMatrix, filePath, addEntities): + fileObj = open(filePath, 'w') + collision = exportCollisionCommon(obj, transformMatrix, addEntities) + fileObj.write(collision.to_c()) + fileObj.close() + +def exportCollisionCommon(obj, transformMatrix, addEntities): + bpy.ops.object.select_all(action = 'DESELECT') + obj.select_set(True) + + bMesh = bmesh.new() + bMesh.from_mesh(obj.data) + bmesh.ops.triangulate(bMesh, faces = bMesh.faces[:]) + bMesh.verts.ensure_lookup_table() + bMesh.edges.ensure_lookup_table() + bMesh.verts.index_update() + bMesh.faces.index_update() + + if len(obj.data.materials) == 0: + raise ValueError(obj.name + " must have a material associated with it.") + + # dict of collisionType : faces + collisionDict = {} + for face in bMesh.faces: + material = obj.data.materials[face.material_index] + colType = material.collision_type if material.collision_all_options else \ + material.collision_type_simple + if colType not in collisionDict: + collisionDict[colType] = [] + collisionDict[colType].append(face) + + collision = Collision(toAlnum(obj.name) + '_collision') + for collisionType, faces in collisionDict.items(): + collision.triangles[collisionType] = [] + for face in faces: + indices = [] + for vert in face.verts: + roundedPosition = roundPosition(transformMatrix @ vert.co) + index = collisionVertIndex(roundedPosition, collision.vertices) + if index is None: + collision.vertices.append(CollisionVertex(roundedPosition)) + indices.append(len(collision.vertices) - 1) + else: + indices.append(index) + collision.triangles[collisionType].append(CollisionTriangle(indices)) + + if addEntities: + childArray = obj.children + while len(childArray) > 0: + childObj = childArray[0] + childArray = childArray[1:] + if childObj.sm64_obj_type == 'Special': + special = CollisionSpecial() + special.preset = childObj.sm64_special_preset + special.position = roundPosition(transformMatrix @ \ + childObj.location) + collision.specials.append(special) + elif childObj.sm64_obj_type == 'Water Box': + pass + childArray.extend(childObj.children) + + return collision + +def roundPosition(position): + return (int(round(position[0])), + int(round(position[1])), + int(round(position[2]))) + +def collisionVertIndex(vert, vertArray): + for i in range(len(vertArray)): + colVert = vertArray[i] + if colVert.position == vert: + return i + return None + +classes = ( + CollisionPanel, + #SM64ObjectPanel, +) + +def register(): + for cls in classes: + register_class(cls) + + bpy.types.Material.collision_type = bpy.props.EnumProperty( + name = 'Collision Type', items = enumCollisionType, + default = 'SURFACE_DEFAULT') + + bpy.types.Material.collision_type_simple = bpy.props.EnumProperty( + name = 'Collision Type', items = enumCollisionTypeSimple, + default = 'SURFACE_DEFAULT') + + bpy.types.Material.collision_all_options = bpy.props.BoolProperty( + name = 'Show All Options') + + bpy.types.Object.sm64_obj_type = bpy.props.EnumProperty( + name = 'SM64 Object Type', items = enumObjectType, default = 'None') + + bpy.types.Object.sm64_water_box = bpy.props.EnumProperty( + name = 'SM64 Water Box', items = enumWaterBoxType, default = 'Water') + + bpy.types.Object.sm64_special_preset = bpy.props.EnumProperty( + name = 'SM64 Special', items = enumSpecialType, + default = 'special_yellow_coin') + +def unregister(): + for cls in reversed(classes): + unregister_class(cls) + +enumWaterBoxType = [ + ("Water", 'Water', "Water"), + ('Toxic Haze', 'Toxic Haze', 'Toxic Haze') +] + +class CollisionTypeDefinition: + def __init__(self): + self.SURFACE_DEFAULT = 0x0000 + self.SURFACE_BURNING = 0x0001 + self.SURFACE_0004 = 0x0004 + self.SURFACE_HANGABLE = 0x0005 + self.SURFACE_SLOW = 0x0009 + self.SURFACE_DEATH_PLANE = 0x000A + self.SURFACE_CLOSE_CAMERA = 0x000B + self.SURFACE_WATER = 0x000D + self.SURFACE_FLOWING_WATER = 0x000E + self.SURFACE_INTANGIBLE = 0x0012 + self.SURFACE_VERY_SLIPPERY = 0x0013 + self.SURFACE_SLIPPERY = 0x0014 + self.SURFACE_NOT_SLIPPERY = 0x0015 + self.SURFACE_TTM_VINES = 0x0016 + self.SURFACE_MGR_MUSIC = 0x001A + self.SURFACE_INSTANT_WARP_1B = 0x001B + self.SURFACE_INSTANT_WARP_1C = 0x001C + self.SURFACE_INSTANT_WARP_1D = 0x001D + self.SURFACE_INSTANT_WARP_1E = 0x001E + self.SURFACE_SHALLOW_QUICKSAND = 0x0021 + self.SURFACE_DEEP_QUICKSAND = 0x0022 + self.SURFACE_INSTANT_QUICKSAND = 0x0023 + self.SURFACE_DEEP_MOVING_QUICKSAND = 0x0024 + self.SURFACE_SHALLOW_MOVING_QUICKSAND = 0x0025 + self.SURFACE_QUICKSAND = 0x0026 + self.SURFACE_MOVING_QUICKSAND = 0x0027 + self.SURFACE_WALL_MISC = 0x0028 + self.SURFACE_NOISE_DEFAULT = 0x0029 + self.SURFACE_NOISE_SLIPPERY = 0x002A + self.SURFACE_HORIZONTAL_WIND = 0x002C + self.SURFACE_INSTANT_MOVING_QUICKSAND = 0x002D + self.SURFACE_ICE = 0x002E + self.SURFACE_LOOK_UP_WARP = 0x002F + self.SURFACE_HARD = 0x0030 + self.SURFACE_WARP = 0x0032 + self.SURFACE_TIMER_START = 0x0033 + self.SURFACE_TIMER_END = 0x0034 + self.SURFACE_HARD_SLIPPERY = 0x0035 + self.SURFACE_HARD_VERY_SLIPPERY = 0x0036 + self.SURFACE_HARD_NOT_SLIPPERY = 0x0037 + self.SURFACE_VERTICAL_WIND = 0x0038 + self.SURFACE_BOSS_FIGHT_CAMERA = 0x0065 + self.SURFACE_CAMERA_FREE_ROAM = 0x0066 + self.SURFACE_THI3_WALLKICK = 0x0068 + self.SURFACE_CAMERA_PLATFORM = 0x0069 + self.SURFACE_CAMERA_MIDDLE = 0x006E + self.SURFACE_CAMERA_ROTATE_RIGHT = 0x006F + self.SURFACE_CAMERA_ROTATE_LEFT = 0x0070 + self.SURFACE_CAMERA_BOUNDARY = 0x0072 + self.SURFACE_NOISE_VERY_SLIPPERY_73 = 0x0073 + self.SURFACE_NOISE_VERY_SLIPPERY_74 = 0x0074 + self.SURFACE_NOISE_VERY_SLIPPERY = 0x0075 + self.SURFACE_NO_CAM_COLLISION = 0x0076 + self.SURFACE_NO_CAM_COLLISION_77 = 0x0077 + self.SURFACE_NO_CAM_COL_VERY_SLIPPERY = 0x0078 + self.SURFACE_NO_CAM_COL_SLIPPERY = 0x0079 + self.SURFACE_SWITCH = 0x007A + self.SURFACE_VANISH_CAP_WALLS = 0x007B + self.SURFACE_PAINTING_WOBBLE_A6 = 0x00A6 + self.SURFACE_PAINTING_WOBBLE_A7 = 0x00A7 + self.SURFACE_PAINTING_WOBBLE_A8 = 0x00A8 + self.SURFACE_PAINTING_WOBBLE_A9 = 0x00A9 + self.SURFACE_PAINTING_WOBBLE_AA = 0x00AA + self.SURFACE_PAINTING_WOBBLE_AB = 0x00AB + self.SURFACE_PAINTING_WOBBLE_AC = 0x00AC + self.SURFACE_PAINTING_WOBBLE_AD = 0x00AD + self.SURFACE_PAINTING_WOBBLE_AE = 0x00AE + self.SURFACE_PAINTING_WOBBLE_AF = 0x00AF + self.SURFACE_PAINTING_WOBBLE_B0 = 0x00B0 + self.SURFACE_PAINTING_WOBBLE_B1 = 0x00B1 + self.SURFACE_PAINTING_WOBBLE_B2 = 0x00B2 + self.SURFACE_PAINTING_WOBBLE_B3 = 0x00B3 + self.SURFACE_PAINTING_WOBBLE_B4 = 0x00B4 + self.SURFACE_PAINTING_WOBBLE_B5 = 0x00B5 + self.SURFACE_PAINTING_WOBBLE_B6 = 0x00B6 + self.SURFACE_PAINTING_WOBBLE_B7 = 0x00B7 + self.SURFACE_PAINTING_WOBBLE_B8 = 0x00B8 + self.SURFACE_PAINTING_WOBBLE_B9 = 0x00B9 + self.SURFACE_PAINTING_WOBBLE_BA = 0x00BA + self.SURFACE_PAINTING_WOBBLE_BB = 0x00BB + self.SURFACE_PAINTING_WOBBLE_BC = 0x00BC + self.SURFACE_PAINTING_WOBBLE_BD = 0x00BD + self.SURFACE_PAINTING_WOBBLE_BE = 0x00BE + self.SURFACE_PAINTING_WOBBLE_BF = 0x00BF + self.SURFACE_PAINTING_WOBBLE_C0 = 0x00C0 + self.SURFACE_PAINTING_WOBBLE_C1 = 0x00C1 + self.SURFACE_PAINTING_WOBBLE_C2 = 0x00C2 + self.SURFACE_PAINTING_WOBBLE_C3 = 0x00C3 + self.SURFACE_PAINTING_WOBBLE_C4 = 0x00C4 + self.SURFACE_PAINTING_WOBBLE_C5 = 0x00C5 + self.SURFACE_PAINTING_WOBBLE_C6 = 0x00C6 + self.SURFACE_PAINTING_WOBBLE_C7 = 0x00C7 + self.SURFACE_PAINTING_WOBBLE_C8 = 0x00C8 + self.SURFACE_PAINTING_WOBBLE_C9 = 0x00C9 + self.SURFACE_PAINTING_WOBBLE_CA = 0x00CA + self.SURFACE_PAINTING_WOBBLE_CB = 0x00CB + self.SURFACE_PAINTING_WOBBLE_CC = 0x00CC + self.SURFACE_PAINTING_WOBBLE_CD = 0x00CD + self.SURFACE_PAINTING_WOBBLE_CE = 0x00CE + self.SURFACE_PAINTING_WOBBLE_CF = 0x00CF + self.SURFACE_PAINTING_WOBBLE_D0 = 0x00D0 + self.SURFACE_PAINTING_WOBBLE_D1 = 0x00D1 + self.SURFACE_PAINTING_WOBBLE_D2 = 0x00D2 + self.SURFACE_PAINTING_WARP_D3 = 0x00D3 + self.SURFACE_PAINTING_WARP_D4 = 0x00D4 + self.SURFACE_PAINTING_WARP_D5 = 0x00D5 + self.SURFACE_PAINTING_WARP_D6 = 0x00D6 + self.SURFACE_PAINTING_WARP_D7 = 0x00D7 + self.SURFACE_PAINTING_WARP_D8 = 0x00D8 + self.SURFACE_PAINTING_WARP_D9 = 0x00D9 + self.SURFACE_PAINTING_WARP_DA = 0x00DA + self.SURFACE_PAINTING_WARP_DB = 0x00DB + self.SURFACE_PAINTING_WARP_DC = 0x00DC + self.SURFACE_PAINTING_WARP_DD = 0x00DD + self.SURFACE_PAINTING_WARP_DE = 0x00DE + self.SURFACE_PAINTING_WARP_DF = 0x00DF + self.SURFACE_PAINTING_WARP_E0 = 0x00E0 + self.SURFACE_PAINTING_WARP_E1 = 0x00E1 + self.SURFACE_PAINTING_WARP_E2 = 0x00E2 + self.SURFACE_PAINTING_WARP_E3 = 0x00E3 + self.SURFACE_PAINTING_WARP_E4 = 0x00E4 + self.SURFACE_PAINTING_WARP_E5 = 0x00E5 + self.SURFACE_PAINTING_WARP_E6 = 0x00E6 + self.SURFACE_PAINTING_WARP_E7 = 0x00E7 + self.SURFACE_PAINTING_WARP_E8 = 0x00E8 + self.SURFACE_PAINTING_WARP_E9 = 0x00E9 + self.SURFACE_PAINTING_WARP_EA = 0x00EA + self.SURFACE_PAINTING_WARP_EB = 0x00EB + self.SURFACE_PAINTING_WARP_EC = 0x00EC + self.SURFACE_PAINTING_WARP_ED = 0x00ED + self.SURFACE_PAINTING_WARP_EE = 0x00EE + self.SURFACE_PAINTING_WARP_EF = 0x00EF + self.SURFACE_PAINTING_WARP_F0 = 0x00F0 + self.SURFACE_PAINTING_WARP_F1 = 0x00F1 + self.SURFACE_PAINTING_WARP_F2 = 0x00F2 + self.SURFACE_PAINTING_WARP_F3 = 0x00F3 + self.SURFACE_TTC_PAINTING_1 = 0x00F4 + self.SURFACE_TTC_PAINTING_2 = 0x00F5 + self.SURFACE_TTC_PAINTING_3 = 0x00F6 + self.SURFACE_PAINTING_WARP_F7 = 0x00F7 + self.SURFACE_PAINTING_WARP_F8 = 0x00F8 + self.SURFACE_PAINTING_WARP_F9 = 0x00F9 + self.SURFACE_PAINTING_WARP_FA = 0x00FA + self.SURFACE_PAINTING_WARP_FB = 0x00FB + self.SURFACE_PAINTING_WARP_FC = 0x00FC + self.SURFACE_WOBBLING_WARP = 0x00FD + self.SURFACE_TRAPDOOR = 0x00FF + + self.SURFACE_CLASS_DEFAULT = 0x0000 + self.SURFACE_CLASS_VERY_SLIPPERY = 0x0013 + self.SURFACE_CLASS_SLIPPERY = 0x0014 + self.SURFACE_CLASS_NOT_SLIPPERY = 0x0015 + + self.SURFACE_FLAG_DYNAMIC = (1 << 0) + self.SURFACE_FLAG_NO_CAM_COLLISION = (1 << 1) + self.SURFACE_FLAG_X_PROJECTION = (1 << 3) + + self.TERRAIN_LOAD_VERTICES = 0x0040 + self.TERRAIN_LOAD_CONTINUE = 0x0041 + self.TERRAIN_LOAD_END = 0x0042 + self.TERRAIN_LOAD_OBJECTS = 0x0043 + self.TERRAIN_LOAD_ENVIRONMENT = 0x0044 + + self.TERRAIN_GRASS = 0x0000 + self.TERRAIN_STONE = 0x0001 + self.TERRAIN_SNOW = 0x0002 + self.TERRAIN_SAND = 0x0003 + self.TERRAIN_SPOOKY = 0x0004 + self.TERRAIN_WATER = 0x0005 + self.TERRAIN_SLIDE = 0x0006 + self.TERRAIN_MASK = 0x0007 + + def SURFACE_IS_QUICKSAND(self, cmd): + return (cmd >= 0x21 and cmd <= 0x28) + def SURFACE_IS_NOT_HARD(self, cmd): + return (cmd != self.SURFACE_HARD and not (cmd >= 0x35 and cmd <= 0x37)) + def SURFACE_IS_PAINTING_WARP(self, cmd): + return (cmd >= 0xD3 and cmd < 0xFD) + def TERRAIN_LOAD_IS_SURFACE_TYPE_LOW(self, cmd) : + return (cmd < 0x40) + def TERRAIN_LOAD_IS_SURFACE_TYPE_HIGH(self, cmd) : + return (cmd >= 0x65) + +enumSpecialType = [ + ('special_null_start', 'Null Start', 'Null Start'), + ('special_yellow_coin', 'Yellow Coin', 'Yellow Coin'), + ('special_yellow_coin_2', 'Yellow Coin 2', 'Yellow Coin 2'), + ('special_unknown_3', 'Unknown 3', 'Unknown 3'), + ('special_boo', 'Boo', 'Boo'), + ('special_unknown_5', 'Unknown 5', 'Unknown 5'), + ('special_lll_moving_octagonal_mesh_platform', + 'LLL Moving Octagonal Platform', 'LLL Moving Octagonal Platform'), + ('special_snow_ball', 'Snow Ball', 'Snow Ball'), + ('special_lll_drawbridge_spawner', 'LLL Drawbridge Spawner', + 'LLL Drawbridge Spawner'), + ('special_empty_9', 'Empty 9', 'Empty 9'), + ('special_lll_rotating_block_with_fire_bars', + 'LLL Rotating Block With Fire Bars', 'LLL Rotating Block With Fire Bars'), + ('special_lll_floating_wood_bridge', + 'LLL Floating Wood Bridge', 'LLL Floating Wood Bridge'), + ('special_tumbling_platform', 'Tumbling Platform', 'Tumbling Platform'), + ('special_lll_rotating_hexagonal_ring', 'LLL Rotating Hexagonal Ring', + 'LLL Rotating Hexagonal Ring'), + ('special_lll_sinking_rectangular_platform', + 'LLL Sinking Rectangular Platform', 'LLL Sinking Rectangular Platform'), + ('special_lll_sinking_square_platforms', 'LLL Sinking Square Platforms', + 'LLL Sinking Square Platforms'), + ('special_lll_tilting_square_platform', 'LLL Tilting Square Platform', + 'LLL Tilting Square Platform'), + ('special_lll_bowser_puzzle', 'LLL Bowser Puzzle', 'LLL Bowser Puzzle'), + ('special_mr_i', 'Mr. I', 'Mr. I'), + ('special_small_bully', 'Small Bully', 'Small Bully'), + ('special_big_bully', 'Big Bully', 'Big Bully'), + ('special_empty_21', 'Empty 21', 'Empty 21'), + ('special_empty_22', 'Empty 22', 'Empty 22'), + ('special_empty_23', 'Empty 23', 'Empty 23'), + ('special_empty_24', 'Empty 24', 'Empty 24'), + ('special_empty_25', 'Empty 25', 'Empty 25'), + ('special_moving_blue_coin', 'Moving Blue Coin', 'Moving Blue Coin'), + ('special_jrb_chest', 'JRB Chest', 'JRB Chest'), + ('special_water_ring', 'Water Ring', 'Water Ring'), + ('special_mine', 'Mine', 'Mine'), + ('special_empty_30', 'Empty 30', 'Empty 30'), + ('special_empty_31', 'Empty 31', 'Empty 31'), + ('special_butterfly', 'Butterfly', 'Butterfly'), + ('special_bowser', 'Bowser', 'Bowser'), + ('special_wf_rotating_wooden_platform', 'WF Rotating Wooden Platform', + 'WF Rotating Wooden Platform'), + ('special_small_bomp', 'Small Bomp', 'Small Bomp'), + ('special_wf_sliding_platform', 'WF Sliding Platform', 'WF Sliding Platform'), + ('special_tower_platform_group', 'Tower Platform Group', + 'Tower Platform Group'), + ('special_rotating_counter_clockwise', 'Rotating Counter Clockwise', + 'Rotating Counter Clockwise'), + ('special_wf_tumbling_bridge', 'WF Tumbling Bridge', 'WF Tumbling Bridge'), + ('special_large_bomp', 'Large Bomp', 'Large Bomp' ), + ('special_level_geo_03', 'Level Geo 03', 'Level Geo 03'), + ('special_level_geo_04', 'Level Geo 04', 'Level Geo 04'), + ('special_level_geo_05', 'Level Geo 05', 'Level Geo 05'), + ('special_level_geo_06', 'Level Geo 06', 'Level Geo 06'), + ('special_level_geo_07', 'Level Geo 07', 'Level Geo 07'), + ('special_level_geo_08', 'Level Geo 08', 'Level Geo 08'), + ('special_level_geo_09', 'Level Geo 09', 'Level Geo 09'), + ('special_level_geo_0A', 'Level Geo 0A', 'Level Geo 0A'), + ('special_level_geo_0B', 'Level Geo 0B', 'Level Geo 0B'), + ('special_level_geo_0C', 'Level Geo 0C', 'Level Geo 0C'), + ('special_level_geo_0D', 'Level Geo 0D', 'Level Geo 0D'), + ('special_level_geo_0E', 'Level Geo 0E', 'Level Geo 0E'), + ('special_level_geo_0F', 'Level Geo 0F', 'Level Geo 0F'), + ('special_level_geo_10', 'Level Geo 10', 'Level Geo 10'), + ('special_level_geo_11', 'Level Geo 11', 'Level Geo 11'), + ('special_level_geo_12', 'Level Geo 12', 'Level Geo 12'), + ('special_level_geo_13', 'Level Geo 13', 'Level Geo 13'), + ('special_level_geo_14', 'Level Geo 14', 'Level Geo 14'), + ('special_level_geo_15', 'Level Geo 15', 'Level Geo 15'), + ('special_level_geo_16', 'Level Geo 16', 'Level Geo 16'), + ('special_bubble_tree', 'bubble_tree', 'bubble_tree'), + ('special_spiky_tree', 'Spiky Tree', 'Spiky Tree'), + ('special_snow_tree', 'Snow Tree', 'Snow Tree'), + ('special_unknown_tree', 'Unknown Tree', 'Unknown Tree'), + ('special_palm_tree', 'Palm Tree', 'Palm Tree'), + ('special_wooden_door', 'Wooden Door', 'Wooden Door'), + ('special_haunted_door', 'Haunted Door', 'Haunted Door'), + ('special_unknown_door', 'Unknown Door', 'Unknown Door'), + ('special_metal_door', 'Metal Door', 'Metal Door'), + ('special_hmc_door', 'HMC Door', 'HMC Door'), + ('special_unknown2_door', 'Unknown 2 Door', 'Unknown 2 Door'), + ('special_wooden_door_warp', 'Wooden Door Warp', 'Wooden Door Warp'), + ('special_unknown1_door_warp', 'Unknown 1 Door Warp', 'Unknown 1 Door Warp'), + ('special_metal_door_warp', 'Metal Door Warp', 'Metal Door Warp'), + ('special_unknown2_door_warp', 'Unknown 2 Door Warp', 'Unknown 2 Door Warp'), + ('special_unknown3_door_warp', 'Unknown 3 Door Warp', 'Unknown 3 Door Warp'), + ('special_castle_door_warp', 'Castle Door Warp', 'Castle Door Warp'), + ('special_castle_door', 'Castle Door', 'Castle Door'), + ('special_0stars_door', '0 Stars Door', '0 Stars Door'), + ('special_1star_door', '1 Star Door', '1 Star Door'), + ('special_3star_door', '3 Star Door', '3 Star Door'), + ('special_key_door', 'Key Door', 'Key Door'), + ('special_null_end', 'Null End', 'Null End'), +] + +enumObjectType = [ + ('None', 'None', 'None'), + ('Special', 'Special', 'Special'), + ('Water Box', 'Water Box', 'Water Box'), +] + +enumCollisionTypeSimple = [ + ('SURFACE_DEFAULT','Default','Default'), + ('SURFACE_BURNING','Burning','Burning'), + ('SURFACE_HANGABLE','Hangable','Hangable'), + ('SURFACE_SLOW','Slow','Slow'), + ('SURFACE_DEATH_PLANE','Death Plane','Death Plane'), + ('SURFACE_WATER','Water','Water'), + ('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'), + ('SURFACE_INTANGIBLE','Intangible','Intangible'), + ('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'), + ('SURFACE_SLIPPERY','Slippery','Slippery'), + ('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'), + ('SURFACE_TTM_VINES','Vines','Vines'), + ('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'), + ('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'), + ('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'), + ('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'), + ('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'), + ('SURFACE_QUICKSAND','Quicksand','Quicksand'), + ('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'), + ('SURFACE_WALL_MISC','Wall Misc','Wall Misc'), + ('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'), + ('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'), + ('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'), + ('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'), + ('SURFACE_ICE','Ice','Ice'), + ('SURFACE_HARD','Hard','Hard'), + ('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'), + ('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'), + ('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'), + ('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'), + ('SURFACE_SWITCH','Switch','Switch'), + ('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'), +] + +enumCollisionType = [ + ('SURFACE_DEFAULT','Default','Default'), + ('SURFACE_BURNING','Burning','Burning'), + ('SURFACE_0004','0004','0004'), + ('SURFACE_HANGABLE','Hangable','Hangable'), + ('SURFACE_SLOW','Slow','Slow'), + ('SURFACE_DEATH_PLANE','Death Plane','Death Plane'), + ('SURFACE_CLOSE_CAMERA','Close Camera','Close Camera'), + ('SURFACE_WATER','Water','Water'), + ('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'), + ('SURFACE_INTANGIBLE','Intangible','Intangible'), + ('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'), + ('SURFACE_SLIPPERY','Slippery','Slippery'), + ('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'), + ('SURFACE_TTM_VINES','Vines','Vines'), + ('SURFACE_MGR_MUSIC','Music','Music'), + ('SURFACE_INSTANT_WARP_1B','Instant Warp 1B','Instant Warp 1B'), + ('SURFACE_INSTANT_WARP_1C','Instant Warp 1C','Instant Warp 1C'), + ('SURFACE_INSTANT_WARP_1D','Instant Warp 1D','Instant Warp 1D'), + ('SURFACE_INSTANT_WARP_1E','Instant Warp 1E','Instant Warp 1E'), + ('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'), + ('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'), + ('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'), + ('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'), + ('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'), + ('SURFACE_QUICKSAND','Quicksand','Quicksand'), + ('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'), + ('SURFACE_WALL_MISC','Wall Misc','Wall Misc'), + ('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'), + ('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'), + ('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'), + ('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'), + ('SURFACE_ICE','Ice','Ice'), + ('SURFACE_LOOK_UP_WARP','Look Up Warp','Look Up Warp'), + ('SURFACE_HARD','Hard','Hard'), + ('SURFACE_WARP','Warp','Warp'), + ('SURFACE_TIMER_START','Timer Start','Timer Start'), + ('SURFACE_TIMER_END','Timer End','Timer End'), + ('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'), + ('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'), + ('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'), + ('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'), + ('SURFACE_BOSS_FIGHT_CAMERA','Boss Fight Camera','Boss Fight Camera'), + ('SURFACE_CAMERA_FREE_ROAM','Camera Free Roam','Camera Free Roam'), + ('SURFACE_THI3_WALLKICK','Wallkick','Wallkick'), + ('SURFACE_CAMERA_PLATFORM','Camera Platform','Camera Platform'), + ('SURFACE_CAMERA_MIDDLE','Camera Middle','Camera Middle'), + ('SURFACE_CAMERA_ROTATE_RIGHT','Camera Rotate Right','Camera Rotate Right'), + ('SURFACE_CAMERA_ROTATE_LEFT','Camera Rotate Left','Camera Rotate Left'), + ('SURFACE_CAMERA_BOUNDARY','Camera Boundary','Camera Boundary'), + ('SURFACE_NOISE_VERY_SLIPPERY_73','Noise Very Slippery 73','Noise Very Slippery 73'), + ('SURFACE_NOISE_VERY_SLIPPERY_74','Noise Very Slippery 74','Noise Very Slippery 74'), + ('SURFACE_NOISE_VERY_SLIPPERY','Noise Very Slippery','Noise Very Slippery'), + ('SURFACE_NO_CAM_COLLISION','No Cam Collision','No Cam Collision'), + ('SURFACE_NO_CAM_COLLISION_77','No Cam Collision 77','No Cam Collision 77'), + ('SURFACE_NO_CAM_COL_VERY_SLIPPERY','No Cam Collision Very Slippery','No Cam Collision Very Slippery'), + ('SURFACE_NO_CAM_COL_SLIPPERY','No Cam Collision Slippery','No Cam Collision Slippery'), + ('SURFACE_SWITCH','Switch','Switch'), + ('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'), + ('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'), + ('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'), + ('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'), + ('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'), + ('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'), + ('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'), + ('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'), + ('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'), + ('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'), + ('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'), + ('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'), + ('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'), + ('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'), + ('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'), + ('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'), + ('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'), + ('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'), + ('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'), + ('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'), + ('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'), + ('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'), + ('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'), + ('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'), + ('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'), + ('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'), + ('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'), + ('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'), + ('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'), + ('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'), + ('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'), + ('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'), + ('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'), + ('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'), + ('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'), + ('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'), + ('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'), + ('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'), + ('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'), + ('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'), + ('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'), + ('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'), + ('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'), + ('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'), + ('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'), + ('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'), + ('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'), + ('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'), + ('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'), + ('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'), + ('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'), + ('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'), + ('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'), + ('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'), + ('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'), + ('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'), + ('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'), + ('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'), + ('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'), + ('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'), + ('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'), + ('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'), + ('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'), + ('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'), + ('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'), + ('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'), + ('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'), + ('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'), + ('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'), + ('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'), + ('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'), + ('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'), + ('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'), + ('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'), + ('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'), + ('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'), + ('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'), + ('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'), + ('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'), + ('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'), + ('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'), + ('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'), + ('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'), + ('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'), + ('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'), + ('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'), + ('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'), + ('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'), + ('SURFACE_WOBBLING_WARP','Wobbling Warp','Wobbling Warp'), + ('SURFACE_TRAPDOOR','Trapdoor','Trapdoor'), +] + +enumPaintingCollisionType = [ + ('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'), + ('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'), + ('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'), + ('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'), + ('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'), + ('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'), + ('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'), + ('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'), + ('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'), + ('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'), + ('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'), + ('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'), + ('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'), + ('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'), + ('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'), + ('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'), + ('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'), + ('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'), + ('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'), + ('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'), + ('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'), + ('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'), + ('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'), + ('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'), + ('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'), + ('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'), + ('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'), + ('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'), + ('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'), + ('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'), + ('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'), + ('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'), + ('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'), + ('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'), + ('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'), + ('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'), + ('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'), + ('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'), + ('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'), + ('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'), + ('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'), + ('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'), + ('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'), + ('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'), + ('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'), + ('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'), + ('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'), + ('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'), + ('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'), + ('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'), + ('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'), + ('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'), + ('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'), + ('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'), + ('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'), + ('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'), + ('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'), + ('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'), + ('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'), + ('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'), + ('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'), + ('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'), + ('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'), + ('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'), + ('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'), + ('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'), + ('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'), + ('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'), + ('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'), + ('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'), + ('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'), + ('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'), + ('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'), + ('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'), + ('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'), + ('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'), + ('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'), + ('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'), + ('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'), + ('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'), + ('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'), + ('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'), + ('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'), + ('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'), + ('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'), + ('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'), + ('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'), +] + +enumUnusedCollisionType = [ + ('SURFACE_0004','0004','0004'), +] \ No newline at end of file diff --git a/fast64_internal/sm64_constants.py b/fast64_internal/sm64_constants.py new file mode 100644 index 0000000..eb48f23 --- /dev/null +++ b/fast64_internal/sm64_constants.py @@ -0,0 +1,325 @@ +import mathutils + +# RAM address used in evaluating switch for hatless Mario +marioHatSwitch = 0x80277740 +marioLowPolySwitch = 0x80277150 + +marioFaceExpressionCount = 4 + +loadMarioMIO0 = 0x2ABCA0 +loadMarioGeo = 0x2ABCB8 + +# Full available rom interval for extended 0x04 bank (from SM64 Editor) +marioFullRomInterval = (0x11D8930, 0x11FFF00) +defaultExtendSegment4 = (0x11A35B8, 0x11FFF00) + +mainLevelLoadScriptSegment = { + 0x15 : (0x2ABCA0, 0x2AC6B0) +} + +bank0Segment = { + 0x00 : (0x000000, 0x7FFFFF) +} + +# Segments for common geolayouts and Mario +loadSegmentAddresses = { + 0x03 : 0x2ABCAC, + 0x04 : 0x2ABCA0, + 0x13 : 0x2ABCD0, + 0x16 : 0x2ABCC4, + 0x17 : 0x2ABCB8 +} + +geoNodeRotateOrder = 'ZXY' + +sm64ToBlenderScale = 0.0047 +marioScale = 0.25 + +sm64BoneUp = mathutils.Vector([1,0,0]) + +# Equivalent of euler(-90, -90, 0) +blenderToSM64Rotation = mathutils.Matrix( + [[0, 1, 0], + [0, 0, 1], + [1, 0, 0]] +) + +blenderToSM64RotationLevel = mathutils.Matrix( + [[1, 0, 0], + [0, 0, 1], + [0,-1, 0]] +) + +colorFormats = { + 'RGBA' : 0, + 'YUV' : 1, + 'CI' : 2, + 'IA' : 3, + 'I' : 4 +} + +pixelBitSizes = { + '4bit' : 0, + '8bit' : 1, + '16bit' : 2, + '32bit' : 3 +} + +lightIndices = [0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94] + +# Color combinations +# In SM64, environment alpha controls Mario's alpha. + +# 2 cycle with specular lighting stored in primitive +S_PHONG_TEX = ['TEXEL0', '0', 'SHADE', '0', + 'TEXEL0', '0', 'ENVIRONMENT', '0', + 'PRIMITIVE', 'COMBINED', 'TEXEL0', 'COMBINED', + '0', '0', '0', 'COMBINED'] + +# MULTIPLY TWO TEXTURES +S_MULTIPLY = [ + 'TEXEL0', '0', 'TEXEL1', '0', + '0', '0', '0', 'ENVIRONMENT', + 'TEXEL0', '0', 'TEXEL1', '0', + '0', '0', '0', 'ENVIRONMENT' +] + +S_PRIM_TRANSPARENT_SHADE = ['TEXEL0', '0', 'SHADE', '0', + 'TEXEL0', '0', 'PRIMITIVE', '0', + 'TEXEL0', '0', 'SHADE', '0', + 'TEXEL0', '0', 'PRIMITIVE', '0'] + +# REGULAR SHADED TEXTURE +S_SHADED_TEX = ['TEXEL0', '0', 'SHADE', '0', + '0', '0', '0', 'ENVIRONMENT', + 'TEXEL0', '0', 'SHADE', '0', + '0', '0', '0', 'ENVIRONMENT'] + +# VERTEX COLOR +S_VERTEX_COLORED_TEX = ['TEXEL0', '0', 'SHADE', '0', + 'ENVIRONMENT', '0', 'SHADE', '0', + 'TEXEL0', '0', 'SHADE', '0', + 'ENVIRONMENT', '0', 'SHADE', '0'] + +S_SHADED_TEX_CUTOUT = ['TEXEL0', '0', 'SHADE', '0', + 'TEXEL0', '0', 'ENVIRONMENT', '0', + 'TEXEL0', '0', 'SHADE', '0', + 'TEXEL0', '0', 'ENVIRONMENT', '0'] + +# UNLIT TEXTURE, USED FOR METAL MARIO +S_UNLIT_TEX = ['0', '0', '0', 'TEXEL0', + '0', '0', '0', 'ENVIRONMENT', + '0', '0', '0', 'TEXEL0', + '0', '0', '0', 'ENVIRONMENT'] + +# SM64 CUSTOM IMPORTER CC +S_SHADED_TEX_NOALPHA = ['TEXEL0', '0', 'SHADE', '0', + '0', '0', '0', 'SHADE', + 'TEXEL0', '0', 'SHADE', '0', + '0', '0', '0', 'SHADE'] + +# SM64 BODY CC +S_SHADED_SOLID = ['0', '0', '0', 'SHADE', + '0', '0', '0', 'ENVIRONMENT', + '0', '0', '0', 'SHADE', + '0', '0', '0', 'ENVIRONMENT'] + +# SM64 BODY TEXTURES (FACE, SIDEBURNS, ETC.) +S_UNLIT_DECAL_ON_SHADED_SOLID = ['TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', + '0', '0', '0', 'ENVIRONMENT', + 'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE', + '0', '0', '0', 'ENVIRONMENT'] + +# SHADED RANDOM NOISE +S_SHADED_NOISE = ['NOISE', '0', 'SHADE', '0', + '0', '0', '0', 'ENVIRONMENT', + 'NOISE', '0', 'SHADE', '0', + '0', '0', '0', 'ENVIRONMENT'] + +colorCombinationCommands = [ + 0x03, #load lighting data + 0xB6, #clear geometry params + 0xB7, #set geometry params + 0xBB, #set texture scaling factor + 0xF3, #set texture size + 0xF5, #set texture properties + 0xF7, #set fill color + 0xF8, #set fog color + 0xFB, #set env color + 0xFC, #set color combination + 0xFD #load texture +] + +drawCommands = [ + 0x04, #load vertex data + 0xBF #draw triangle +] + +originalMarioHeadROMInterval = ( + 0x011B4F58, + 0x011B5710 +) + +marioVanishOffsets = { + "regular" : 0xB0C, + "metal" : 0x9EC, +} + +commonGeolayoutPointers = { + 'Dorrie' : [2039136, 'HMC'], + 'Bowser' : [1809204, 'BFB'], + 'Bowser 2' : [1809328, 'BFB'], + 'Lakitu' : [1985520, 'CC'] +} + +draw_layer_enums = [ + ('1', 'Solid', '0x01'), + ('2', 'Solid Decal', '0x02'), + ('4', 'Transparent (No Blending)', '0x04'), + ('5', 'Transparent (Blending Front)', '0x05'), + ('6', 'Transparent (Blending Back)', '0x06'), +] + +level_enums = [ + ("HH" , "Haunted House" , "HH" ), + ("CCM", "Cool Cool Mountain" , "CCM"), + ("IC" , "Inside Castle" , "IC" ), + ("HMC", "Hazy Maze Cave" , "HMC"), + ("SSL", "Shifting Sand Land" , "SSL"), + ("BOB", "Bob-Omb's Battlefield" , "BOB"), + ("SML", "Snow Man's land" , "SML"), + ("WDW", "Wet Dry World" , "WDW"), + ("JRB", "Jolly Roger Bay" , "JRB"), + ("THI", "Tiny Huge Island" , "THI"), + ("TTC", "Tick Tock Clock" , "TTC"), + ("RR" , "Rainbow Ride" , "RR" ), + ("CG" , "Castle Grounds" , "CG" ), + ("BFC", "Bowser First Course" , "BFC"), + ("VC" , "Vanish Cap" , "VC" ), + ("BFS", "Bowser's Fire Sea" , "BFS"), + ("SA" , "Secret Aquarium" , "SA" ), + ("BTC", "Bowser Third Course" , "BTC"), + ("LLL", "Lethal Lava Land" , "LLL"), + ("DDD", "Dire Dire Docks" , "DDD"), + ("WF" , "Whomp's Fortress" , "WF" ), + ("PIC", "Picture at the end" , "PIC"), + ("CC" , "Castle Courtyard" , "CC" ), + ("PSS", "Peach's Secret Slide" , "PSS"), + ("MC" , "Metal Cap" , "MC" ), + ("WC" , "Wing Cap" , "WC" ), + ("BFB", "Bowser First Battle" , "BFB"), + ("RC" , "Rainbow Clouds" , "RC" ), + ("BSB", "Bowser Second Battle" , "BSB"), + ("BTB", "Bowser Third Battle" , "BTB"), + ("TTM", "Tall Tall Mountain" , "TTM"), +] + +class SM64_CharacterImportData: + def __init__(self, geoAddr, level, switchDict): + self.geoAddr = geoAddr + self.level = level + self.switchDict = switchDict + +character_enums = [ + ("Mario" , "Mario" , "Mario" ), + ("Peach" , "Peach" , "Peach" ), + #("Bowser" , "Bowser" , "Bowser" ), + #("Koopa" , "Koopa" , "Koopa" ), + #("Toad" , "Toad" , "Toad" ) +] + +# switch index starts at 1. +# switch option index starts at 0. +sm64_character_data = { + + "Mario" : SM64_CharacterImportData('12A784', 'CG', { + 1 : { # Low poly mario + 0 : 'Ignore' + }, + 2 : { + 1 : "Material", # metal mario + 2 : "Draw Layer", # vanish mario + 3 : "Material", # metal vanish mario + }, + 3 : { # cap vs no cap + }, + 4 : { # face animation (cap) + 0 : "Material", + 1 : "Material", + 2 : "Material", + 3 : "Material", + 4 : "Material", + 5 : "Material", + 6 : "Material", + 7 : "Material", + }, + 5 : { # face animation (no cap) + 0 : "Material", + 1 : "Material", + 2 : "Material", + 3 : "Material", + 4 : "Material", + 5 : "Material", + 6 : "Material", + 7 : "Material", + }, + 6 : { # right fist type + # 0 = closed, 1 = open, 2 is something, 3-5 are same as 2 + 3 : 'Ignore', + 4 : 'Ignore', + 5 : 'Ignore', + }, + 7 : { # left fist type + # 0 = closed, 1 = open, 2 = peace, 3 = hold cap, 4 = hold cap wing + }, + }), + 'Peach' : SM64_CharacterImportData('180950', 'CG', { + 1 : { # transparent peach + 1 : 'Draw Layer', + }, + 2 : { # 0-3 = face animation kissing, 4-7 = face animation normal + 0 : "Material", + 1 : "Material", + 2 : "Material", + 3 : "Material", + 4 : "Material", + 5 : "Material", + 6 : "Material", + 7 : "Material", + } + }), +} + +level_pointers = { + "HH" : 0x2AC094, + "CCM": 0x2AC0A8, + "IC" : 0x2AC0BC, + "HMC": 0x2AC0D0, + "SSL": 0x2AC0E4, + "BOB": 0x2AC0F8, + "SML": 0x2AC10C, + "WDW": 0x2AC120, + "JRB": 0x2AC134, + "THI": 0x2AC148, + "TTC": 0x2AC15C, + "RR" : 0x2AC170, + "CG" : 0x2AC184, + "BFC": 0x2AC198, + "VC" : 0x2AC1AC, + "BFS": 0x2AC1C0, + "SA" : 0x2AC1D4, + "BTC": 0x2AC1E8, + "LLL": 0x2AC1FC, + "DDD": 0x2AC210, + "WF" : 0x2AC224, + "PIC": 0x2AC238, + "CC" : 0x2AC24C, + "PSS": 0x2AC260, + "MC" : 0x2AC274, + "WC" : 0x2AC288, + "BFB": 0x2AC29C, + "RC" : 0x2AC2B0, + "BSB": 0x2AC2C4, + "BTB": 0x2AC2D8, + "TTM": 0x2AC2EC, +} \ No newline at end of file diff --git a/fast64_internal/sm64_geolayout_bone.py b/fast64_internal/sm64_geolayout_bone.py new file mode 100644 index 0000000..bae1cbe --- /dev/null +++ b/fast64_internal/sm64_geolayout_bone.py @@ -0,0 +1,406 @@ +import bpy +from bpy.utils import register_class, unregister_class +from .sm64_geolayout_utility import createBoneGroups, addBoneToGroup +from .utility import prop_split + +enumBoneType = [ + ("Switch", "Switch (0x0E)", "Switch"), + ("Start", "Start (0x0B)", "Start"), + ("TranslateRotate", "Translate Rotate (0x10)", "Translate Rotate"), + ("Translate", "Translate (0x11)", "Translate"), + ("Rotate", "Rotate (0x12)", "Rotate"), + ("Billboard", "Billboard (0x14)", "Billboard"), + ("DisplayList", "Display List (0x15)", "Display List"), + ("Shadow", "Shadow (0x16)", "Shadow"), + ("Function", "Function (0x18)", "Function"), + ("HeldObject", "Held Object (0x1C)", "Held Object"), + ("Scale", "Scale (0x1D)", "Scale"), + ("StartRenderArea", "Start Render Area (0x20)", "Start Render Area"), + ("Ignore", "Ignore", "Ignore bones when exporting."), + ("SwitchOption", "Switch Option", "Switch Option"), + ("DisplayListWithOffset", "Display List With Offset (0x13)", + "Display List With Offset"), +] + +enumDrawLayers = [ + ('0', 'Background (0x00)', 'Background'), + ('1', 'Opaque (0x01)', 'Opaque'), + ('2', 'Opaque Decal (0x02)', 'Opaque Decal'), + ('3', 'Opaque Intersecting (0x03)', 'Opaque Intersecting'), + ('4', 'Cutout (0x04)', 'Cutout'), + ('5', 'Transparent (0x05)', 'Transparent'), + ('6', 'Transparent Decal (0x06)', 'Transparent Decal'), + ('7', 'Transparent Intersecting (0x07)', 'Transparent Intersecting'), +] + +enumFieldLayout = [ + ('0', 'Translate And Rotate', 'Translate And Rotate'), + ('1', 'Translate', 'Translate'), + ('2', 'Rotate', 'Rotate'), + #('3', 'Rotate Y', 'Rotate Y'), + # Rotate Y complicates exporting code, so we treat it as Rotate. +] + +enumShadowType = [ + ('0', 'Circle Scalable (9 verts)', 'Circle Scalable (9 verts)'), + ('1', 'Circle Scalable (4 verts)', 'Circle Scalable (4 verts)'), + ('2', 'Circle Permanent (4 verts)', 'Circle Permanent (4 verts)'), + ('10', 'Square Permanent', 'Square Permanent'), + ('11', 'Square Scalable', 'Square Scalable'), + ('12', 'Square Togglable', 'Square Togglable'), +] + +enumSwitchOptions = [ + ('Mesh', 'Mesh Override', 'Switch to a different mesh hierarchy.'), + ('Material', 'Material Override', 'Use the same mesh hierarchy, but override material on ALL meshes. Optionally override draw layer.'), + ('Draw Layer', 'Draw Layer Override', 'Override draw layer only.'), +] + +enumMatOverrideOptions = [ + ("All", 'All', 'Override every material with this one.'), + ("Specific", 'Specific', 'Only override instances of give material.'), +] + +def drawGeoInfo(panel, bone): + + panel.layout.box().label(text = 'Geolayout Inspector') + if bone is None: + panel.layout.label(text = 'Edit geolayout properties in Pose mode.') + return + + col = panel.layout.column() + + prop_split(col, bone, 'geo_cmd', 'Geolayout Command') + + if bone.geo_cmd in ['TranslateRotate', 'Translate', 'Rotate', + 'Billboard', 'DisplayList', 'Scale', 'DisplayListWithOffset']: + prop_split(col, bone, 'draw_layer', 'Draw layer') + + if bone.geo_cmd == 'Scale': + prop_split(col, bone, 'geo_scale', 'Scale') + + if bone.geo_cmd == 'HeldObject': + prop_split(col, bone, 'geo_func', 'Function') + + if bone.geo_cmd == 'Switch': + prop_split(col, bone, 'geo_func', 'Function') + prop_split(col, bone, 'func_param', 'Parameter') + col.label(text = 'Switch Option 0 is always this bone\'s children.') + col.operator(AddSwitchOption.bl_idname).option = len(bone.switch_options) + for i in range(len(bone.switch_options)): + drawSwitchOptionProperty(col, bone.switch_options[i], i) + + if bone.geo_cmd == 'Function': + prop_split(col, bone, 'geo_func', 'Function') + prop_split(col, bone, 'func_param', 'Parameter') + infoBox2 = col.box() + infoBox2.label(text = 'This affects the next sibling bone in ' +\ + 'alphabetical order.') + + ''' + if bone.geo_cmd == 'Function' or \ + bone.geo_cmd == 'Switch' or \ + bone.geo_cmd == 'HeldObject': + infoBox = col.box() + infoBox.label(text = 'For binary, this is a memory address.') + infoBox.label(text = 'For C, this is a function name.') + ''' + + if bone.geo_cmd == 'TranslateRotate': + prop_split(col, bone, 'field_layout', 'Field Layout') + + if bone.geo_cmd == 'Shadow': + prop_split(col, bone, 'shadow_type', 'Type') + prop_split(col, bone, 'shadow_solidity', 'Alpha') + prop_split(col, bone, 'shadow_scale', 'Scale') + + if bone.geo_cmd == 'StartRenderArea': + prop_split(col, bone, 'culling_radius', 'Culling Radius') + + #if bone.geo_cmd == 'SwitchOption': + # prop_split(col, bone, 'switch_bone', 'Switch Bone') + + layerInfoBox = panel.layout.box() + layerInfoBox.label(text = + 'Regular bones (0x13) are on armature layer 0.') + layerInfoBox.label(text = + 'Other bones are on armature layer 1.') + layerInfoBox.label(text = + "'Ignore' bones are on any layer.") + +class MaterialPointerProperty(bpy.types.PropertyGroup): + material : bpy.props.PointerProperty(type = bpy.types.Material) + +class SwitchOptionProperty(bpy.types.PropertyGroup): + switchType : bpy.props.EnumProperty(name = 'Option Type', + items = enumSwitchOptions) + optionArmature : bpy.props.PointerProperty(name = 'Option Armature', + type = bpy.types.Object) + materialOverride : bpy.props.PointerProperty(type = bpy.types.Material, + name = 'Material Override') + materialOverrideType : bpy.props.EnumProperty(name = 'Material Override Type', + items = enumMatOverrideOptions) + specificOverrideArray : bpy.props.CollectionProperty(type = MaterialPointerProperty, + name = 'Specified Materials To Override') + specificIgnoreArray : bpy.props.CollectionProperty(type = MaterialPointerProperty, + name = 'Specified Materials To Ignore') + overrideDrawLayer : bpy.props.BoolProperty() + drawLayer : bpy.props.EnumProperty(items = enumDrawLayers, name = 'Draw Layer') + collapse : bpy.props.BoolProperty() + +def drawSwitchOptionProperty(layout, switchOption, index): + box = layout.box() + #box.box().label(text = 'Switch Option ' + str(index + 1)) + box.prop(switchOption, 'collapse', text = 'Switch Option ' + \ + str(index + 1), icon = 'TRIA_DOWN' if not switchOption.collapse else \ + 'TRIA_RIGHT') + if not switchOption.collapse: + prop_split(box, switchOption, 'switchType', 'Type') + if switchOption.switchType == 'Material': + prop_split(box, switchOption, 'materialOverride', 'Material') + prop_split(box, switchOption, 'materialOverrideType', + "Material Override Type") + if switchOption.materialOverrideType == 'Specific': + matArrayBox = box.box() + matArrayBox.label(text = "Specified Materials To Override") + drawMatArray(matArrayBox, index, switchOption, + switchOption.specificOverrideArray, True) + else: + matArrayBox = box.box() + matArrayBox.label(text = "Specified Materials To Ignore") + drawMatArray(matArrayBox, index, switchOption, + switchOption.specificIgnoreArray, False) + prop_split(box, switchOption, 'overrideDrawLayer', + "Override Draw Layer") + if switchOption.overrideDrawLayer: + prop_split(box, switchOption, 'drawLayer', 'Draw Layer') + elif switchOption.switchType == 'Draw Layer': + prop_split(box, switchOption, 'drawLayer', "Draw Layer") + else: + prop_split(box, switchOption, 'optionArmature', 'Option Armature') + buttons = box.row(align = True) + buttons.operator(RemoveSwitchOption.bl_idname, + text = 'Remove Option').option = index + buttons.operator(AddSwitchOption.bl_idname, + text = 'Add Option').option = index + 1 + + moveButtons = box.row(align = True) + moveUp = moveButtons.operator(MoveSwitchOption.bl_idname, + text = 'Move Up') + moveUp.option = index + moveUp.offset = -1 + moveDown = moveButtons.operator(MoveSwitchOption.bl_idname, + text = 'Move Down') + moveDown.option = index + moveDown.offset = 1 + +def drawMatArray(layout, option, switchOption, matArray, isSpecific): + addOp = layout.operator(AddSwitchOptionMat.bl_idname, text = 'Add Material') + addOp.option = option + addOp.isSpecific = isSpecific + + for i in range(len(matArray)): + drawMatArrayProperty(layout, matArray[i], option, i, isSpecific) + +def drawMatArrayProperty(layout, materialPointer, option, index, isSpecific): + row = layout.box().row() + row.prop(materialPointer, 'material', text = '') + removeOp = row.operator(RemoveSwitchOptionMat.bl_idname, text = 'Remove Material') + removeOp.option = option + removeOp.index = index + removeOp.isSpecific = isSpecific + +class AddSwitchOptionMat(bpy.types.Operator): + bl_idname = 'bone.add_switch_option_mat' + bl_label = 'Add Switch Option Material' + option : bpy.props.IntProperty() + isSpecific : bpy.props.BoolProperty() + def execute(self, context): + bone = context.bone + if self.isSpecific: + bone.switch_options[self.option].specificOverrideArray.add() + else: + bone.switch_options[self.option].specificIgnoreArray.add() + self.report({'INFO'}, 'Success!') + return {'FINISHED'} + +class RemoveSwitchOptionMat(bpy.types.Operator): + bl_idname = 'bone.remove_switch_option_mat' + bl_label = 'Remove Switch Option Material' + option : bpy.props.IntProperty() + index : bpy.props.IntProperty() + isSpecific : bpy.props.BoolProperty() + def execute(self, context): + if self.isSpecific: + context.bone.switch_options[self.option].specificOverrideArray.remove(self.index) + else: + context.bone.switch_options[self.option].specificIgnoreArray.remove(self.index) + self.report({'INFO'}, 'Success!') + return {'FINISHED'} + +class GeolayoutBonePanel(bpy.types.Panel): + bl_label = "Geolayout Inspector" + bl_idname = "SM64_Geolayout_Inspector" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "bone" + bl_options = {'HIDE_HEADER'} + + #@classmethod + #def poll(cls, context): + # return (context.bone is not None) + + def draw(self, context): + drawGeoInfo(self, context.bone) + +class AddSwitchOption(bpy.types.Operator): + bl_idname = 'bone.add_switch_option' + bl_label = 'Add Switch Option' + option : bpy.props.IntProperty() + def execute(self, context): + bone = context.bone + bone.switch_options.add() + bone.switch_options.move(len(bone.switch_options)-1, self.option) + self.report({'INFO'}, 'Success!') + return {'FINISHED'} + +class RemoveSwitchOption(bpy.types.Operator): + bl_idname = 'bone.remove_switch_option' + bl_label = 'Remove Switch Option' + option : bpy.props.IntProperty() + def execute(self, context): + context.bone.switch_options.remove(self.option) + self.report({'INFO'}, 'Success!') + return {'FINISHED'} + +class MoveSwitchOption(bpy.types.Operator): + bl_idname = 'bone.move_switch_option' + bl_label = 'Move Switch Option' + option : bpy.props.IntProperty() + offset : bpy.props.IntProperty() + def execute(self, context): + bone = context.bone + bone.switch_options.move(self.option, self.option + self.offset) + self.report({'INFO'}, 'Success!') + return {'FINISHED'} + +''' +class GeolayoutBoneSidePanel(bpy.types.Panel): + bl_idname = "SM64_Geolayout_Inspector_Side" + bl_label = "SM64 Geolayout Inspector" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Item' + + @classmethod + def poll(cls, context): + return context.selected_bones is not None and \ + len(context.selected_bones) > 0 + + def draw(self, context): + drawGeoInfo(self, context.selected_bones[0]) +''' + +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 len(optionBones) > 1: + raise ValueError("There should only be one switch option bone in " +\ + switchArmature.name + '.') + elif len(optionBones) < 1: + raise ValueError("Could not find a switch option bone in " +\ + switchArmature.name + ', which should be the root bone in the hierarchy.') + return optionBones[0] + +def updateBone(self, context): + if not hasattr(context, 'bone'): + print("No bone in context.") + return + armatureObj = context.object + + createBoneGroups(armatureObj) + if context.bone.geo_cmd != 'DisplayListWithOffset': + addBoneToGroup(armatureObj, context.bone.name, context.bone.geo_cmd) + bpy.ops.object.mode_set(mode="POSE") + else: + addBoneToGroup(armatureObj, context.bone.name, None) + bpy.ops.object.mode_set(mode="POSE") + +classes = ( + GeolayoutBonePanel, + #GeolayoutBoneSidePanel + AddSwitchOption, + RemoveSwitchOption, + AddSwitchOptionMat, + RemoveSwitchOptionMat, + MoveSwitchOption, + MaterialPointerProperty, + SwitchOptionProperty, +) + +def register(): + for cls in classes: + register_class(cls) + + bpy.types.Bone.geo_cmd = bpy.props.EnumProperty( + name = 'Geolayout Command', items = enumBoneType, + default = 'DisplayListWithOffset', update = updateBone) + + bpy.types.Bone.draw_layer = bpy.props.EnumProperty( + name = 'Draw Layer', items = enumDrawLayers, default = '1') + + # Scale + bpy.types.Bone.geo_scale = bpy.props.FloatProperty( + name = 'Scale', min = 2**(-16), max = 2**(16), default = 1) + + # Function, HeldObject, Switch + # 8027795C for HeldObject + bpy.types.Bone.geo_func = bpy.props.StringProperty( + name = 'Function', default = '', + description = 'Name of function for C, hex address for binary.') + + # Function + bpy.types.Bone.func_param = bpy.props.IntProperty( + name = 'Function Parameter', min = -2**(15), max = 2**(15) - 1, default = 0) + + # TranslateRotate + bpy.types.Bone.field_layout = bpy.props.EnumProperty( + name = 'Field Layout', items = enumFieldLayout, default = '0') + + # Shadow + bpy.types.Bone.shadow_type = bpy.props.EnumProperty( + name = 'Shadow Type', items = enumShadowType, default = '1') + + bpy.types.Bone.shadow_solidity = bpy.props.FloatProperty( + name = 'Shadow Alpha', min = 0, max = 1, default = 1) + + bpy.types.Bone.shadow_scale = bpy.props.IntProperty( + name = 'Shadow Scale', min = -2**(15), max = 2**(15) - 1, default = 100) + + # StartRenderArea + bpy.types.Bone.culling_radius = bpy.props.IntProperty( + name = 'Culling Radius', min=-2**(15), max=2**(15)-1, default=2000) + + #bpy.types.Bone.switch_bone = bpy.props.StringProperty( + # name = 'Switch Bone') + + bpy.types.Bone.switch_options = bpy.props.CollectionProperty( + type = SwitchOptionProperty) + +def unregister(): + for cls in reversed(classes): + unregister_class(cls) + + #del bpy.types.Bone.geo_cmd + #del bpy.types.Bone.draw_layer + #del bpy.types.Bone.geo_scale + #del bpy.types.Bone.geo_func + #del bpy.types.Bone.func_param + #del bpy.types.Bone.field_layout + #del bpy.types.Bone.shadow_type + #del bpy.types.Bone.shadow_solidity + #del bpy.types.Bone.shadow_scale + #del bpy.types.Bone.culling_radius \ No newline at end of file diff --git a/fast64_internal/sm64_geolayout_constants.py b/fast64_internal/sm64_geolayout_constants.py new file mode 100644 index 0000000..5eb2194 --- /dev/null +++ b/fast64_internal/sm64_geolayout_constants.py @@ -0,0 +1,104 @@ +drawLayers = { + 'Unused' : [0, 3], + 'Solid' : 1, + 'Decal' : 2, + 'AlphaTest' : 4, + 'Blend' : 5, + 'BlendBehind' : 6 +} + +GEO_BRANCH_STORE = 0x00 +GEO_END = 0x01 +GEO_BRANCH = 0x02 +GEO_RETURN = 0x03 +GEO_NODE_OPEN = 0x04 +GEO_NODE_CLOSE = 0x05 +GEO_SET_RENDER_AREA = 0x08 +GEO_SET_CAMERA_FRUSTRUM = 0x0A +GEO_START = 0x0B +GEO_SET_Z_BUF = 0x0C +GEO_SET_RENDER_RANGE = 0x0D +GEO_SWITCH = 0x0E +GEO_TRANSLATE_ROTATE = 0x10 +GEO_TRANSLATE = 0x11 +GEO_ROTATE = 0x12 +GEO_LOAD_DL_W_OFFSET = 0x13 +GEO_BILLBOARD = 0x14 +GEO_LOAD_DL = 0x15 +GEO_START_W_SHADOW = 0x16 +GEO_SETUP_OBJ_RENDER = 0x17 +GEO_CALL_ASM = 0x18 +GEO_SET_BG = 0x19 +GEO_HELD_OBJECT = 0x1C +GEO_NOP = [0x1A, 0x1E, 0x1F] +GEO_SCALE = 0x1D +GEO_START_W_RENDERAREA = 0x20 + +startCommands = [ + GEO_START, + GEO_START_W_SHADOW, + GEO_START_W_RENDERAREA +] + +nodeGroupCmds = [ + GEO_START, + GEO_SWITCH, + GEO_TRANSLATE_ROTATE, + GEO_TRANSLATE, + GEO_ROTATE, + GEO_LOAD_DL_W_OFFSET, + GEO_BILLBOARD, + GEO_START_W_SHADOW, + GEO_SCALE, + GEO_START_W_RENDERAREA +] + +nodeDeformCmds = [ + GEO_TRANSLATE_ROTATE, + GEO_TRANSLATE, + GEO_ROTATE, + GEO_LOAD_DL_W_OFFSET, + GEO_BILLBOARD, + GEO_LOAD_DL, + GEO_SCALE, +] + +nodeDeformCmdsBoneGroups = [ + 'TranslateRotate', + 'Translate', + 'Rotate', + 'Billboard', + 'DisplayList', + 'Scale' +] + +nodeCmds = [ + GEO_NODE_OPEN, + #GEO_START, + #GEO_START_W_SHADOW, + #GEO_START_W_RENDERAREA, + GEO_LOAD_DL, + GEO_LOAD_DL_W_OFFSET, + GEO_BRANCH, + #GEO_SWITCH, + #GEO_SCALE, + #GEO_TRANSLATE_ROTATE +] + +geoCmdStatic = { + 0x04: [0x00, 0x01, 0x03, 0x04, 0x05, 0x09, 0x0B, 0x0C, 0x17, 0x20], + 0x08: [0x02, 0x0D, 0x0E, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19], + 0x0C: [0x08, 0x13, 0x1C], + 0x14: [0x0F], +} + +def getGeoLayoutCmdLength(byte0, byte1): + for length, cmdList in geoCmdStatic.items(): + if byte0 in cmdList: + return length + + # handle variable length + if byte0 in [0x0A]: + return 0x08 if byte1 == 0 else 0x0C + else: + raise ValueError("Unhandled geolayout command: " + hex(byte0)) diff --git a/fast64_internal/sm64_geolayout_parser.py b/fast64_internal/sm64_geolayout_parser.py new file mode 100644 index 0000000..6750c86 --- /dev/null +++ b/fast64_internal/sm64_geolayout_parser.py @@ -0,0 +1,1089 @@ +import bpy +import mathutils +import math +import copy + +from math import pi + +from .sm64_level_parser import * +from .sm64_geolayout_constants import * +from .sm64_geolayout_utility import * +from .f3d_parser import * +from .utility import * +from .sm64_constants import * +from .f3d_material import createF3DMat + +blender_modes = {'OBJECT', 'BONE'} + +# This geolayout parser is designed to rip armature / model. +# It will only handle transform/mesh related commands. +# For switch cases, only the first option will be chosen. + +def parseGeoLayout(romfile, startAddress, scene, level, + convertTransformMatrix, useArmature, ignoreSwitch, shadeSmooth, + f3dType, isHWv1): + currentAddress = startAddress + romfile.seek(currentAddress) + + # Get segment data + levelParsed = parseLevelAtPointer(romfile, level_pointers[level]) + segmentData = levelParsed.segmentData + + # Create new skinned mesh + #bpy.ops.object.mode_set(mode = 'OBJECT') + mesh = bpy.data.meshes.new('skinnned-mesh') + obj = bpy.data.objects.new('skinned', mesh) + scene.collection.objects.link(obj) + material = createF3DMat(obj) + material.node_tree.nodes['Case A 1'].inA = '1' + material.set_env = False + + bMesh = bmesh.new() + bMesh.from_mesh(mesh) + + # Create new armature + if useArmature: + armature = bpy.data.armatures.new('Armature') + armatureObj = bpy.data.objects.new('ArmatureObj', armature) + armatureObj.show_in_front = True + armature.show_names = True + + bpy.context.scene.collection.objects.link(armatureObj) + bpy.context.view_layer.objects.active = armatureObj + bpy.ops.object.mode_set(mode = 'EDIT') + createBoneGroups(armatureObj) + else: + armatureObj = None + + # Parse geolayout + # Pretend that command starts with an 0x04 + currentAddress, armatureMeshGroups = parseNode( + romfile, startAddress, currentAddress - 4, [0x04, 0x00], + [currentAddress], convertTransformMatrix.to_4x4(), + bMesh, obj, armatureObj, None, ignoreSwitch, False, 0, 0, + [None] * 16 * 16, f3dType, isHWv1, + segmentData = segmentData) + + armatureMeshGroups.insert(0, (armatureObj, bMesh, obj)) + + for i in range (len(armatureMeshGroups)): + listObj = armatureMeshGroups[i][2] + listBMesh = armatureMeshGroups[i][1] + listBMesh.to_mesh(listObj.data) + listBMesh.free() + listObj.data.update() + + if shadeSmooth: + bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.select_all(action = 'DESELECT') + listObj.select_set(True) + bpy.ops.object.shade_smooth() + + # Dont remove doubles here, as importing geolayout all at once results + # in some overlapping verts from different display lists. + # bmesh.ops.remove_doubles(bMesh, verts = bMesh.verts, dist = 0.000001) + + if useArmature: + # Set bone rotation mode. + for bone in armatureObj.pose.bones: + bone.rotation_mode = 'XYZ' + + for i in range(len(armatureMeshGroups)): + obj = armatureMeshGroups[i][2] + switchArmatureObj = armatureMeshGroups[i][0] + # Apply mesh to armature. + bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.select_all(action = 'DESELECT') + obj.select_set(True) + switchArmatureObj.select_set(True) + bpy.context.view_layer.objects.active = switchArmatureObj + bpy.ops.object.parent_set(type = "ARMATURE") + switchArmatureObj.matrix_world = switchArmatureObj.matrix_world @ \ + mathutils.Matrix.Translation(mathutils.Vector((3*i, 0 ,0))) + + + # Make Mario face forward. + #bpy.ops.object.select_all(action = 'DESELECT') + #armatureObj.select_set(True) + #bpy.ops.transform.rotate(value = math.radians(-90), orient_axis = 'X') + #bpy.ops.object.transform_apply() + + + if useArmature: + armatureObj.data.layers[1] = True + + ''' + if useMetarig: + metaBones = [bone for bone in armatureObj.data.bones if \ + bone.layers[boneLayers['meta']] or bone.layers[boneLayers['visual']]] + for bone in metaBones: + addBoneToGroup(armatureObj, bone.name, 'Ignore') + ''' + return armatureMeshGroups, armatureObj + +# Every node is passed in the address of its OWN command. +# Every node returns the address AFTER the end of its processing extent. +# Make sure to NOT create blender objects if the node is being ignored. +def parseNode(romfile, geoStartAddress, currentAddress, currentCmd, jumps, + currentTransform, bMesh, obj, armatureObj, parentBoneName, ignoreSwitch, ignoreNode, switchLevel, switchCount, + vertexBuffer, f3dType, isHWv1, singleChild = False, + endCmd = GEO_NODE_CLOSE, segmentData = None): + print("NODE " + hex(currentAddress)) + currentTransform = copy.deepcopy(currentTransform) + originalTransform = copy.deepcopy(currentTransform) + currentAddress += getGeoLayoutCmdLength(*currentCmd) + romfile.seek(currentAddress) + currentCmd = romfile.read(2) + armatureMeshGroups = [] + + # True if at least one complete node processed. + # This is used for switch cases. + nodeProcessed = False + + # True when a complete node group is processed. + # Certain commands like 0x13 will be grouped with a proceeding 0x4 commmand. + # However, they will stand individually if this is not the case. + # This distinction is important when handling switch cases. + completeNodeProcessed = False + + # In the case of branches in switches we must treat the branch as one option, + # not just the first node that is being branched to. + # We cannot treat a branch as a node because some branches don't return. + singleChildStack = [singleChild] + switchActive = False + localSwitchCount = 0 + + # a 0x13 command creates a bone, but we only use it as a parent if + # a 0x4 command follows. If another 0x13 command follows, we don't use it. + nextParentBoneName = parentBoneName + nextParentTransform = copy.deepcopy(currentTransform) + nodeIndex = [0] + while currentCmd[0] is not endCmd and currentCmd[0] is not GEO_END: + isSwitchOption = singleChildStack[-1] and nodeProcessed and \ + completeNodeProcessed + + # Create a separate sub-armature for switch options. + if isSwitchOption: + switchCount += 1 + localSwitchCount += 1 + if ignoreSwitch: + ignoreNode = True + if not ignoreNode: + parentBoneName, armatureMeshTuple, currentTransform, \ + nextParentTransform = \ + createSwitchOption(armatureObj, parentBoneName, + format(nodeIndex[-1], '03') + '-switch_option', currentTransform, nextParentTransform, + switchLevel, switchCount) + armatureMeshGroups.append(armatureMeshTuple) + obj = armatureMeshTuple[2] + bMesh = armatureMeshTuple[1] + nextParentBoneName = parentBoneName + armatureObj = armatureMeshTuple[0] + switchLevel = switchCount + + if currentCmd[0] == GEO_BRANCH and not ignoreNode: # 0x02 + currentAddress = parseBranch(romfile, currentCmd, currentAddress, + jumps, segmentData = segmentData) + singleChildStack.append(False) + nodeIndex.append(0) + + elif currentCmd[0] == GEO_RETURN and not ignoreNode: # 0x03 + currentAddress = parseReturn( + currentCmd, currentAddress, jumps) + singleChildStack = singleChildStack[:-1] + nodeIndex = nodeIndex[:-1] + + elif currentCmd[0] == GEO_NODE_OPEN: # 0x04 + #print(str(switchCount) + " - " + str(localSwitchCount) + " - " + \ + # str(switchLevel)) + currentAddress, newArmatureMeshGroups = \ + parseNode(romfile, geoStartAddress, currentAddress, + currentCmd, jumps, nextParentTransform, bMesh, obj, + armatureObj, nextParentBoneName, ignoreSwitch, ignoreNode, + switchLevel, switchCount, + vertexBuffer, f3dType, isHWv1, + singleChild = switchActive, segmentData = segmentData) + armatureMeshGroups.extend(newArmatureMeshGroups) + switchActive = False + + elif currentCmd[0] == GEO_START: # 0x0B + currentAddress, nextParentBoneName, nextParentTransform = \ + parseStart(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex[-1], + segmentData) + + elif currentCmd[0] == GEO_SWITCH: # 0x0E + currentAddress, nextParentBoneName = \ + parseSwitch(romfile, currentAddress, + currentTransform, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData) + switchActive = True + + # We skip translate/rotate/scale nodes if before the first 0x13 node. + # This allows us to import model animations without having to transform keyframes. + elif currentCmd[0] == GEO_TRANSLATE_ROTATE: #0x10 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseTranslateRotate(romfile, + currentAddress, currentCmd, currentTransform, + bMesh, obj, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_TRANSLATE: #0x11 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseTranslate(romfile, + currentAddress, currentCmd, currentTransform, + bMesh, obj, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1],segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_ROTATE: # 0x12 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseRotate(romfile, + currentAddress, currentCmd, currentTransform, + bMesh, obj, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1],segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_LOAD_DL_W_OFFSET: # 0x13 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseDLWithOffset(romfile, + currentAddress, currentTransform, bMesh, obj, armatureObj, + parentBoneName, ignoreNode, nodeIndex[-1], + currentCmd, segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_BILLBOARD: # 0x14 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseBillboard(romfile, currentAddress, + currentCmd, currentTransform, bMesh, obj, + armatureObj, parentBoneName, ignoreNode, nodeIndex[-1], + segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_LOAD_DL: # 0x15 + currentAddress = parseDL(romfile, currentAddress, + currentTransform, bMesh, obj, armatureObj, + parentBoneName, ignoreNode, currentCmd, + nodeIndex[-1], segmentData, vertexBuffer, f3dType, isHWv1) + + elif currentCmd[0] == GEO_START_W_SHADOW: # 0x16 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseShadow(romfile, currentAddress, + currentTransform, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData) + + elif currentCmd[0] == GEO_CALL_ASM: # 0x18 + currentAddress = parseFunction(romfile, currentAddress, + currentTransform, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData) + + elif currentCmd[0] == GEO_HELD_OBJECT: # 0x1C + currentAddress, nextParentTransform = \ + parseHeldObject(romfile, currentAddress, + currentTransform, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData) + + elif currentCmd[0] == GEO_SCALE: # 0x1D + currentAddress, nextParentBoneName, nextParentTransform = \ + parseScale(romfile, + currentAddress, currentCmd, currentTransform, + bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex[-1], segmentData, vertexBuffer, + f3dType, isHWv1) + + elif currentCmd[0] == GEO_START_W_RENDERAREA: # 0x20 + currentAddress, nextParentBoneName, nextParentTransform = \ + parseStartWithRenderArea(romfile, currentAddress, + currentTransform, armatureObj, parentBoneName, ignoreNode, + nodeIndex[-1], segmentData) + + else: + currentAddress += getGeoLayoutCmdLength(*currentCmd) + print("Unhandled command: " + hex(currentCmd[0])) + + nodeIndex[-1] += 1 + + romfile.seek(currentAddress) + previousCmdType = currentCmd[0] + currentCmd = romfile.read(2) + + if previousCmdType not in nodeGroupCmds or \ + currentCmd[0] != GEO_NODE_OPEN: + completeNodeProcessed = True + nodeProcessed = True + else: + completeNodeProcessed = False + + if currentCmd[0] == GEO_END: + currentAddress = jumps.pop() + else: + currentAddress += getGeoLayoutCmdLength(*currentCmd) + return currentAddress, armatureMeshGroups + +def generateMetarig(armatureObj): + if 'root' not in armatureObj.data.bones: + raise ValueError('Cannot find bone named "root". The first ' + \ + 'animatable (0x13) bone in the armature must be named "root."') + createBoneGroups(armatureObj) + traverseArmatureForMetarig(armatureObj, 'root', None) + armatureObj.data.layers = createBoneLayerMask([boneLayers['visual']]) + bpy.ops.object.mode_set(mode = "OBJECT") + +def traverseArmatureForMetarig(armatureObj, boneName, parentName): + 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 + + poseBone = armatureObj.pose.bones[boneName] + 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) + +def processBoneMeta(armatureObj, boneName, parentName): + bpy.ops.object.mode_set(mode = "EDIT") + bone = armatureObj.data.edit_bones[boneName] + + # create meta bone, which the actual bone copies the rotation of + metabone = armatureObj.data.edit_bones.new("meta_" + boneName) + metabone.use_connect = False + metabone.head = bone.head + metabone.tail = bone.tail + metabone.roll = bone.roll + + # create visual bone, which visually connect parent bone to child + visualBone = armatureObj.data.edit_bones.new("vis_" + boneName) + visualBone.use_connect = False + visualBone.head = bone.head + visualBone.tail = bone.head + sm64BoneUp * 0.2 + metabone.parent = visualBone + + metaboneName = metabone.name + visualBoneName = visualBone.name + + bpy.ops.object.mode_set(mode = 'OBJECT') + poseBone = armatureObj.pose.bones[boneName] + metabonePose = armatureObj.pose.bones[metaboneName] + visualBonePose = armatureObj.pose.bones[visualBoneName] + + metabone = armatureObj.data.bones[metaboneName] + visualBone = armatureObj.data.bones[visualBoneName] + + # apply rotation constraint + constraint = poseBone.constraints.new(type = "COPY_ROTATION") + constraint.target = armatureObj + constraint.subtarget = metaboneName + + if boneName == 'root': + rootConstraint = poseBone.constraints.new(type = "COPY_LOCATION") + rootConstraint.target = armatureObj + rootConstraint.subtarget = metaboneName + + metabone.layers = createBoneLayerMask([boneLayers['meta']]) + 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] + + if parentName is not None: + parentVisualBone = \ + armatureObj.data.edit_bones["vis_" + parentName] + parentVisualBoneName = parentVisualBone.name + visualChildren = [child for child in parentVisualBone.children if\ + child.name[:5] != 'meta_'] + print(str(parentVisualBoneName) + " " + str([child.name for child in parentVisualBone.children])) + if len(visualChildren) == 0: + print("Zero children: " + str(visualBone.name)) + parentVisualBone.tail = visualBone.head + if parentVisualBone.length < 0.00001: + parentVisualBone.tail = parentVisualBone.head + sm64BoneUp * 0.1 + else: + visualBone.use_connect = True + visualBone.parent = parentVisualBone + + else: + print("Some children: " + str(visualBone.name) + " " + str([child.name for child in visualChildren])) + # If multiple children, make parent bone a "straight upward" + # bone, then add "connection" bones to connect children to parent + for child in visualChildren: + #if parentVisualBone.vector.angle(sm64BoneUp, 1) > 0.0001: + child.use_connect = False + parentVisualBone.tail = parentVisualBone.head + \ + sm64BoneUp * 0.2 + #createConnectBone(armatureObj, child.name, parentVisualBoneName) + visualBone = armatureObj.data.edit_bones[visualBoneName] + parentVisualBone = armatureObj.data.edit_bones[parentVisualBoneName] + #createConnectBone(armatureObj, visualBone.name, + # parentVisualBone.name) + visualBone.use_connect = False + visualBone.parent = parentVisualBone + else: + pass # connect to root anim bone + +def createConnectBone(armatureObj, childName, parentName): + child = armatureObj.data.edit_bones[childName] + parent = armatureObj.data.edit_bones[parentName] + + connectBone = armatureObj.data.edit_bones.new( + "connect_" + child.name) + connectBoneName = connectBone.name + connectBone.head = parent.head + connectBone.use_connect = False + connectBone.parent = parent + connectBone.tail = child.head + child.use_connect = False + connectBone.use_deform = False + child.parent = connectBone + if (connectBone.head - connectBone.tail).length < 0.0001: + connectBone.tail = connectBone.head + sm64BoneUp * 0.2 + else: + child.use_connect = True + + 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') + connectPoseBone.lock_rotation = (True, True, True) + bpy.ops.object.mode_set(mode = 'EDIT') + +def createBone(armatureObj, parentBoneName, boneName, currentTransform, + boneGroup, loadDL): + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.select_all(action = 'DESELECT') + bpy.context.view_layer.objects.active = armatureObj + bpy.ops.object.mode_set(mode="EDIT") + bone = armatureObj.data.edit_bones.new(boneName) + bone.use_connect = False + if parentBoneName is not None: + bone.parent = armatureObj.data.edit_bones[parentBoneName] + bone.head = currentTransform @ mathutils.Vector((0,0,0)) + bone.tail = bone.head + (currentTransform.to_quaternion() @ \ + mathutils.Vector((0,1,0))* \ + (0.2 if boneGroup != 'DisplayList' else 0.1)) + + # Connect bone to parent if it is possible without changing parent direction. + + if parentBoneName is not None: + nodeOffsetVector = mathutils.Vector(bone.head - bone.parent.head) + # set fallback to nonzero to avoid creating zero length bones + if(nodeOffsetVector.angle(bone.parent.tail - bone.parent.head, 1) \ + < 0.0001 and loadDL): + for child in bone.parent.children: + if child != bone: + child.use_connect = False + bone.parent.tail = bone.head + bone.use_connect = True + elif bone.head == bone.parent.head and bone.tail == bone.parent.tail: + bone.tail += currentTransform.to_quaternion() @ mathutils.Vector((0,1,0)) * 0.02 + + boneName = bone.name + addBoneToGroup(armatureObj, bone.name, boneGroup) + bone = armatureObj.data.bones[boneName] + bone.geo_cmd = boneGroup if boneGroup is not None else \ + 'DisplayListWithOffset' + + return boneName + +def createSwitchOption(armatureObj, switchBoneName, boneName, currentTransform, + nextParentTransform, switchLevel, switchCount): + bpy.ops.object.mode_set(mode="OBJECT") + bpy.ops.object.select_all(action = 'DESELECT') + #bpy.context.view_layer.objects.active = armatureObj + #bpy.ops.object.mode_set(mode="EDIT") + #bone = armatureObj.data.edit_bones.new(boneName) + #bone.use_connect = False + + # calculate transform + translation = mathutils.Matrix.Translation( + mathutils.Vector((0,3 * (switchCount - switchLevel),0))) + translation = mathutils.Matrix.Translation((0,0,0)) + finalTransform = translation @ currentTransform + finalNextParentTransform = translation @ nextParentTransform + + # create armature + armature = bpy.data.armatures.new('Armature') + switchArmature = bpy.data.objects.new('armature_' + boneName, armature) + switchArmature.show_in_front = True + armature.show_names = True + + bpy.context.scene.collection.objects.link(switchArmature) + bpy.ops.object.mode_set(mode = 'POSE') + bpy.ops.object.mode_set(mode = 'OBJECT') + createBoneGroups(switchArmature) + bpy.context.view_layer.objects.active = switchArmature + #switchArmature.matrix_world = mathutils.Matrix.Translation( + # finalTransform.to_translation()) + bpy.ops.object.mode_set(mode = 'EDIT') + + # create switch option bone + bone = switchArmature.data.edit_bones.new(boneName) + bone.use_connect = False + bone.head = finalTransform @ mathutils.Vector((0,0,0)) + bone.tail = bone.head + currentTransform.to_quaternion() @ \ + mathutils.Vector((0,1,0)) * 0.2 + + boneName = bone.name + bpy.ops.object.mode_set(mode='OBJECT') + bone = switchArmature.data.bones[boneName] + #poseBone = switchArmature.pose.bones[boneName] + switchBone = armatureObj.data.bones[switchBoneName] + + #rotConstraint = poseBone.constraints.new(type = 'COPY_ROTATION') + #rotConstraint.target = armatureObj + #rotConstraint.subtarget = switchBone.name + + bone.geo_cmd = 'SwitchOption' + + #switchOption = switchBone.switch_options.add() + #switchOption.switchType = 'Mesh' + #switchOption.optionArmature = switchArmature + + # Create new mesh as well + mesh = bpy.data.meshes.new('skinnned-mesh') + obj = bpy.data.objects.new('skinned', mesh) + bpy.context.scene.collection.objects.link(obj) + obj.matrix_world = switchArmature.matrix_world + #material = createF3DMat(obj) + #material.node_tree.nodes['Case A 1'].inA = '1' + #material.set_env = False + + bMesh = bmesh.new() + bMesh.from_mesh(mesh) + + addBoneToGroup(switchArmature, boneName, 'SwitchOption') + + return boneName, (switchArmature, bMesh, obj), finalTransform, \ + finalNextParentTransform + +def parseSwitch(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData): + print("SWITCH " + hex(currentAddress)) + + commandSize = 8 + + if not ignoreNode: + romfile.seek(currentAddress) + command = romfile.read(commandSize) + funcParam = int.from_bytes(command[2:4], 'big', signed = True) + switchFunc = bytesToHexClean(command[4:8]) + + boneName = format(nodeIndex, '03') + "-switch" + if armatureObj is not None: + boneName = createBone(armatureObj, parentBoneName, boneName, + currentTransform, 'Switch', False) + bone = armatureObj.data.bones[boneName] + bone.geo_func = switchFunc + bone.func_param = funcParam + else: + boneName = None + + currentAddress += commandSize + return currentAddress, boneName + +def parseDL(romfile, currentAddress, currentTransform, bMesh, obj, armatureObj, + parentBoneName, ignoreNode, currentCmd, nodeIndex, segmentData, vertexBuffer, f3dType, isHWv1): + + drawLayer = bitMask(currentCmd[1], 0, 4) + + romfile.seek(currentAddress) + commandSize = 8 + command = romfile.read(commandSize) + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + currentTransform, True, command, segmentData, bMesh, obj, + nodeIndex, 'DisplayList', vertexBuffer, + f3dType, isHWv1) + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + + currentAddress += commandSize + return currentAddress + +def parseDLWithOffset(romfile, currentAddress, + currentTransform, bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex, currentCmd, segmentData, vertexBuffer, + f3dType, isHWv1): + print("DL_OFFSET " + hex(currentAddress)) + romfile.seek(currentAddress) + + command = romfile.read(getGeoLayoutCmdLength(*currentCmd)) + + drawLayer = command[1] + + translationVector = readVectorFromShorts(command, 2) + translation = mathutils.Matrix.Translation( + mathutils.Vector(translationVector)) + finalTransform = currentTransform @ translation + + if armatureObj is not None and 'root' not in armatureObj.data.bones: + boneName = 'root' + else: + boneName = format(nodeIndex, '03') + '-offset' + + # Handle parent object and transformation + # Note: Since we are storing the world transform for each node, + # we must set the transforms in preorder traversal. + segmentedAddr = command[8:12] + hasMeshData = int.from_bytes(segmentedAddr, 'big') != 0 + + if not ignoreNode: + if armatureObj is not None: + # Create bone + boneName = createBone(armatureObj, parentBoneName, boneName, finalTransform, None, hasMeshData) + bpy.ops.object.mode_set(mode = 'OBJECT') + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + armatureObj.data.bones[boneName].use_deform = hasMeshData + bpy.ops.object.mode_set(mode = 'EDIT') + + # load mesh data + if hasMeshData: + displayListStartAddress = decodeSegmentedAddr(segmentedAddr, + segmentData = segmentData) + #print(displayListStartAddress) + parseF3D(romfile, displayListStartAddress, bpy.context.scene, + bMesh, obj, finalTransform, boneName, segmentData, vertexBuffer) + + # Handle child objects + # Validate that next command is 04 (open node) + currentAddress += getGeoLayoutCmdLength(*currentCmd) + romfile.seek(currentAddress) # DONT FORGET THIS, FIXES PARENTING BUG + currentCmd = romfile.read(2) + + return currentAddress, boneName, finalTransform + +def parseBranch(romfile, currentCmd, currentAddress, jumps, + segmentData = None): + print("BRANCH " + hex(currentAddress)) + romfile.seek(currentAddress) + postJumpAddr = currentAddress + getGeoLayoutCmdLength(*currentCmd) + currentCmd = romfile.read(getGeoLayoutCmdLength(*currentCmd)) + + if currentCmd[1] == 1: + jumps.append(postJumpAddr) + currentAddress = decodeSegmentedAddr(currentCmd[4:8], + segmentData = segmentData) + + return currentAddress + +def parseReturn(currentCmd, currentAddress, jumps): + print("RETURN " + hex(currentAddress)) + currentAddress = jumps.pop() + return currentAddress + +# Create bone and load geometry +def handleNodeCommon(romfile, armatureObj, parentBoneName, finalTransform, + loadDL, command, segmentData, bMesh, obj, nodeIndex, + boneGroupName, vertexBuffer, + f3dType, isHWv1): + + boneName = format(nodeIndex, '03') + "-" + boneGroupName.lower() + + if armatureObj is not None: + # Create bone + boneName = createBone(armatureObj, parentBoneName, boneName, + finalTransform, boneGroupName, loadDL) + + if loadDL: + segmentedAddr = command[-4:] + hasMeshData = int.from_bytes(segmentedAddr, 'big') != 0 + if hasMeshData: + startAddress = decodeSegmentedAddr(segmentedAddr, segmentData) + parseF3D(romfile, startAddress, bpy.context.scene, bMesh, obj, + finalTransform, boneName, segmentData, vertexBuffer) + elif armatureObj is not None: + armatureObj.data.bones[boneName].use_deform = False + return boneName + +def parseScale(romfile, currentAddress, currentCmd, currentTransform, + bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData , vertexBuffer, f3dType, isHWv1): + print("SCALE " + hex(currentAddress)) + + loadDL = bitMask(currentCmd[1], 7, 1) + drawLayer = bitMask(currentCmd[1], 0, 4) + + romfile.seek(currentAddress) + commandSize = 8 + (4 if loadDL else 0) + command = romfile.read(commandSize) + + scale = int.from_bytes(command[4:8], 'big') / 0x10000 + #finalTransform = currentTransform @ mathutils.Matrix.Scale(scale, 4) + finalTransform = currentTransform # Don't apply to armature + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + finalTransform, loadDL, command, segmentData, bMesh, obj, + nodeIndex, 'Scale', vertexBuffer, + f3dType, isHWv1) + if armatureObj is not None: + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + bone.geo_scale = scale + else: + boneName = None + + currentAddress += commandSize + return (currentAddress, boneName, finalTransform) + +def parseTranslateRotate(romfile, currentAddress, + currentCmd, currentTransform, bMesh, obj, armatureObj, + parentBoneName, ignoreNode, nodeIndex, segmentData , vertexBuffer, f3dType, isHWv1): + print("TRANSLATE_ROTATE " + hex(currentAddress)) + + loadDL = bitMask(currentCmd[1], 7, 1) + fieldLayout = bitMask(currentCmd[1], 4, 3) + drawLayer = bitMask(currentCmd[1], 0, 4) + + if fieldLayout == 0: + commandSize = 16 + elif fieldLayout == 1 or fieldLayout == 2: + commandSize = 8 + else: + commandSize = 4 + if loadDL: + commandSize += 4 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + + if fieldLayout == 0: + pos = readVectorFromShorts(command, 4) + rot = readEulerVectorFromShorts(command, 10) + + rotation = mathutils.Euler(rot, geoNodeRotateOrder).to_matrix().to_4x4() + translation = mathutils.Matrix.Translation( + mathutils.Vector(pos)) + finalTransform = currentTransform @ translation @ rotation + + elif fieldLayout == 1: + pos = readVectorFromShorts(command, 2) + translation = mathutils.Matrix.Translation( + mathutils.Vector(pos)) + finalTransform = currentTransform @ translation + + elif fieldLayout == 2: + rot = readEulerVectorFromShorts(command, 2) + rotation = mathutils.Euler(rot, geoNodeRotateOrder).to_matrix().to_4x4() + finalTransform = currentTransform @ rotation + + else: + yRot = readFloatFromShort(command, 2) + rotation = mathutils.Euler((0, yRot, 0), geoNodeRotateOrder + ).to_matrix().to_4x4() + finalTransform = currentTransform @ rotation + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + finalTransform, loadDL, command, segmentData, bMesh, obj, + nodeIndex, 'TranslateRotate', vertexBuffer, + f3dType, isHWv1) + if armatureObj is not None: + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + + # Rotate Y complicates exporting code, so we treat it as Rotate. + if fieldLayout == 3: + fieldLayout = 2 + bone.field_layout = str(fieldLayout) + else: + boneName = None + + currentAddress += commandSize + + return (currentAddress, boneName, finalTransform) + +def parseTranslate(romfile, currentAddress, + currentCmd, currentTransform, bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData, vertexBuffer, f3dType, isHWv1): + print("TRANSLATE " + hex(currentAddress)) + + loadDL = bitMask(currentCmd[1], 7, 1) + drawLayer = bitMask(currentCmd[1], 0, 4) + + if loadDL: + commandSize = 12 + else: + commandSize = 8 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + + pos = readVectorFromShorts(command, 2) + translation = mathutils.Matrix.Translation( + mathutils.Vector(pos)) + finalTransform = currentTransform @ translation + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + finalTransform, loadDL, command, segmentData, bMesh, obj, + nodeIndex, 'Translate', vertexBuffer, + f3dType, isHWv1) + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + else: + boneName = None + + currentAddress += commandSize + + return (currentAddress, boneName, finalTransform) + +def parseRotate(romfile, currentAddress, + currentCmd, currentTransform, bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData, vertexBuffer, f3dType, isHWv1): + print("ROTATE " + hex(currentAddress)) + + loadDL = bitMask(currentCmd[1], 7, 1) + drawLayer = bitMask(currentCmd[1], 0, 4) + + if loadDL: + commandSize = 12 + else: + commandSize = 8 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + + rot = readEulerVectorFromShorts(command, 2) + rotation = mathutils.Euler(rot, geoNodeRotateOrder).to_matrix().to_4x4() + finalTransform = currentTransform @ rotation + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + finalTransform, loadDL, command, segmentData, bMesh, obj, + nodeIndex, 'Rotate', vertexBuffer, + f3dType, isHWv1) + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + else: + boneName = None + + currentAddress += commandSize + return (currentAddress, boneName, finalTransform) + +def parseBillboard(romfile, currentAddress, + currentCmd, currentTransform, bMesh, obj, armatureObj, parentBoneName, ignoreNode, nodeIndex, segmentData, vertexBuffer, + f3dType, isHWv1): + print("BILLBOARD " + hex(currentAddress)) + + loadDL = bitMask(currentCmd[1], 7, 1) + drawLayer = bitMask(currentCmd[1], 0, 4) + + if loadDL: + commandSize = 12 + else: + commandSize = 8 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + + pos = readVectorFromShorts(command, 2) + translation = mathutils.Matrix.Translation( + mathutils.Vector(pos)) + finalTransform = currentTransform @ translation + + if not ignoreNode: + boneName = handleNodeCommon(romfile, armatureObj, parentBoneName, + finalTransform, loadDL, command, segmentData, bMesh, obj, + nodeIndex, 'Billboard', vertexBuffer, + f3dType, isHWv1) + bone = armatureObj.data.bones[boneName] + bone.draw_layer = str(drawLayer) + else: + boneName = None + + currentAddress += commandSize + + return (currentAddress, boneName, finalTransform) + +def parseShadow(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData): + print("SHADOW " + hex(currentAddress)) + commandSize = 8 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + shadowType = int.from_bytes(command[2:4], 'big') + shadowSolidity = int.from_bytes(command[4:6], 'big') + shadowScale = int.from_bytes(command[6:8], 'big') + + if not ignoreNode: + boneName = format(nodeIndex, '03') + "-shadow" + if armatureObj is not None: + boneName = createBone(armatureObj, parentBoneName, boneName, + currentTransform, 'Shadow', False) + bone = armatureObj.data.bones[boneName] + bone.shadow_type = str(shadowType) + bone.shadow_solidity = shadowSolidity / 0xFF + bone.shadow_scale = shadowScale + else: + boneName = None + + currentAddress += commandSize + return currentAddress, boneName, copy.deepcopy(currentTransform) + +def parseStart(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData): + print("START " + hex(currentAddress)) + + commandSize = 4 + romfile.seek(currentAddress) + + if not ignoreNode: + boneName = format(nodeIndex, '03') + "-start" + if armatureObj is not None: + boneName = createBone(armatureObj, parentBoneName, boneName, + currentTransform, 'Start', False) + else: + boneName = None + + currentAddress += commandSize + return currentAddress, boneName, copy.deepcopy(currentTransform) + +def parseStartWithRenderArea(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData): + print("START W/ RENDER AREA" + hex(currentAddress)) + + commandSize = 4 + romfile.seek(currentAddress) + command = romfile.read(commandSize) + cullingRadius = int.from_bytes(command[2:4], 'big') + + if not ignoreNode: + boneName = format(nodeIndex, '03') + "-start_render_area" + if armatureObj is not None: + boneName = createBone(armatureObj, parentBoneName, boneName, + currentTransform, 'StartRenderArea', False) + bone = armatureObj.data.bones[boneName] + bone.geo_cmd = 'StartRenderArea' + bone.culling_radius = cullingRadius + else: + boneName = None + + currentAddress += commandSize + return currentAddress, boneName, copy.deepcopy(currentTransform) + +def parseFunction(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, + segmentData): + print("Function " + hex(currentAddress)) + + commandSize = 8 + + romfile.seek(currentAddress) + command = romfile.read(commandSize) + asmParam = int.from_bytes(command[2:4], 'big', signed = True) + asmFunc = bytesToHexClean(command[4:8]) + + boneName = format(nodeIndex, '03') + "-asm" + if armatureObj is not None and not ignoreNode: + boneName = createBone(armatureObj, parentBoneName, boneName, + currentTransform, 'Function', False) + bone = armatureObj.data.bones[boneName] + bone.geo_func = asmFunc + bone.func_param = asmParam + + currentAddress += commandSize + return currentAddress + +def parseHeldObject(romfile, currentAddress, currentTransform, + armatureObj, parentBoneName, ignoreNode, nodeIndex, segmentData): + print("HELD OBJECT " + hex(currentAddress)) + commandSize = 12 + romfile.seek(currentAddress) + command = romfile.read(commandSize) + + pos = readVectorFromShorts(command, 2) + translation = mathutils.Matrix.Translation( + mathutils.Vector(pos)) + finalTransform = currentTransform @ translation + asmFunc = bytesToHexClean(command[8:12]) + + if not ignoreNode: + boneName = format(nodeIndex, '03') + "-held_object" + if armatureObj is not None: + boneName = createBone(armatureObj, parentBoneName, boneName, + finalTransform, 'HeldObject', False) + bone = armatureObj.data.bones[boneName] + bone.geo_func = asmFunc + + currentAddress += commandSize + return currentAddress, finalTransform + +def getMarioBoneName(startRelativeAddr, armatureData, default = 'sm64_mesh'): + try: + boneName = armatureData.findBoneByOffset(startRelativeAddr).name + return boneName + except Exception: + return default + +def assignMarioGeoMetadata(obj, commandAddress, geoStartAddress, + cmdType, armatureData, lastTransRotAddr = None): + + # for geo_pointer reading offsets: + # cmd = 0 + # draw layer = 1 + # translation = 2 (for 0x13) + # display lists = 4 (for 0x15) or 8 (for 0x13) + sm64_geo_meta = { + 'geo_start' : geoStartAddress, + 'geo_pointer_relative' : commandAddress - geoStartAddress, + 'geo_cmd_type' : cmdType, + 'geo_has_mesh' : len(obj.data.loops) > 0, + 'geo_top_overlap_ptr' : None, + 'geo_other_overlap_ptrs': [] + } + + # actual value doesn't matter, just a flag + if lastTransRotAddr is not None: + sm64_geo_meta['geo_trans_ptr'] = lastTransRotAddr + + obj['sm64_geo_meta'] = sm64_geo_meta + + if armatureData is not None: + obj['sm64_part_names'] = [armatureData.findBoneByOffset( + commandAddress - geoStartAddress).name] + +def handleOverlapGeoMetadata(bone): + parentBone = bone.parent + if parentBone is not None and 'sm64_geo_meta' in parentBone: + #boneMeta = copyBlenderPropDict(bone['sm64_geo_meta']) + boneMeta = bone['sm64_geo_meta'] + parentBoneMeta = parentBone['sm64_geo_meta'] + + if parentBoneMeta['geo_top_overlap_ptr'] is None: + parentBoneMeta['geo_top_overlap_ptr'] = \ + parentBoneMeta['geo_pointer_relative'] + + else: + # Done this way since we cannot deepcopy blender ID property arrays + parentBoneMeta['geo_other_overlap_ptrs'] = \ + [ptr for ptr in parentBoneMeta['geo_other_overlap_ptrs']] + \ + [parentBoneMeta['geo_pointer_relative']] + + parentBoneMeta['geo_pointer_relative'] = \ + boneMeta['geo_pointer_relative'] + parentBoneMeta['geo_cmd_type'] = \ + boneMeta['geo_cmd_type'] + parentBoneMeta['geo_has_mesh'] = \ + boneMeta['geo_has_mesh'] + + bone['sm64_geo_meta'] = parentBoneMeta + del parentBone['sm64_geo_meta'] \ No newline at end of file diff --git a/fast64_internal/sm64_geolayout_utility.py b/fast64_internal/sm64_geolayout_utility.py new file mode 100644 index 0000000..c3dea80 --- /dev/null +++ b/fast64_internal/sm64_geolayout_utility.py @@ -0,0 +1,89 @@ +import bpy + +def getBoneGroupByName(armatureObj, name): + for boneGroup in armatureObj.pose.bone_groups: + if boneGroup.name == name: + return boneGroup + return None + +def getBoneGroupIndex(armatureObj, name): + index = 0 + for boneGroup in armatureObj.pose.bone_groups: + if boneGroup.name == name: + return index + else: + index += 1 + return -1 + +class BoneNodeProperties: + def __init__(self, deform, theme): + self.deform = deform + self.theme = theme + +# Only 0x13 commands are keyframe animated. +# We want to ignore/prevent animations on these other nodes. +boneNodeProperties = { + "Switch" : BoneNodeProperties(False, 'THEME01'), #0xE + "Start" : BoneNodeProperties(True, 'THEME12'), #0x0B + "TranslateRotate" : BoneNodeProperties(True, 'THEME02'), #0x10 + "Translate" : BoneNodeProperties(True, 'THEME03'), #0x11 + "Rotate" : BoneNodeProperties(True, 'THEME04'), #0x12 + "Billboard" : BoneNodeProperties(True, 'THEME14'), #0x14 + "DisplayList" : BoneNodeProperties(True, 'THEME06'), #0x15 + "Shadow" : BoneNodeProperties(False, 'THEME07'), #0x16 + "Function" : BoneNodeProperties(False, 'THEME05'), #0x18 + "HeldObject" : BoneNodeProperties(False, 'THEME09'), #0x1C + "Scale" : BoneNodeProperties(True, 'THEME10'), #0x1D + "StartRenderArea" : BoneNodeProperties(True, 'THEME13'), #0x20 + "Ignore" : BoneNodeProperties(False, 'THEME08'), # Used for rigging + + "SwitchOption" : BoneNodeProperties(False, 'THEME11') +} + +boneLayers = { + 'anim' : 0, + 'other' : 1, + 'meta' : 2, + 'visual' : 3 +} + +def createBoneLayerMask(values): + mask = [False] * 32 + for value in values: + mask[value] = True + return mask + +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 + +def addBoneToGroup(armatureObj, boneName, groupName): + if groupName is None: + bpy.ops.object.mode_set(mode="OBJECT") + posebone = armatureObj.pose.bones[boneName] + bone = armatureObj.data.bones[boneName] + posebone.bone_group = None + bone.use_deform = True + bone.layers = createBoneLayerMask([boneLayers['anim']]) + posebone.lock_location = (False, False, False) + posebone.lock_rotation = (False, False, False) + posebone.lock_scale = (False, False, False) + return + + elif groupName not in boneNodeProperties: + raise ValueError("Bone group " + groupName + " doesn't exist.") + + 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 groupName != 'Ignore': + bone.use_deform = boneNodeProperties[groupName].deform + if groupName != "DisplayList": + bone.layers = createBoneLayerMask([boneLayers['other']]) + if groupName != "SwitchOption": + posebone.lock_location = (True, True, True) + posebone.lock_rotation = (True, True, True) + posebone.lock_scale = (True, True, True) \ No newline at end of file diff --git a/fast64_internal/sm64_geolayout_writer.py b/fast64_internal/sm64_geolayout_writer.py new file mode 100644 index 0000000..cefcc09 --- /dev/null +++ b/fast64_internal/sm64_geolayout_writer.py @@ -0,0 +1,1582 @@ +import bpy +import mathutils +import math +import copy + +from os.path import basename +from io import BytesIO + +from .sm64_geolayout_bone import getSwitchOptionBone +from .sm64_geolayout_constants import * +from .sm64_geolayout_utility import * +from .utility import * +from .sm64_constants import * +from .f3d_material import all_combiner_uses +from .f3d_writer import * + +def findStartBone(armatureObj): + noParentBones = [poseBone for poseBone in armatureObj.pose.bones if \ + poseBone.parent is None] + for poseBone in noParentBones: + # We want to ignore any switch option bones. + if poseBone.bone_group is None or \ + (poseBone.bone_group.name != "SwitchOption" and \ + poseBone.bone_group.name != "Ignore"): + return poseBone.name + raise ValueError("No non switch option start bone could be found.") + +def prepareGeolayoutExport(armatureObj, obj): + # Make object and armature space the same. + bpy.ops.object.select_all(action = "DESELECT") + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.transform_apply() + bpy.context.scene.cursor.location = armatureObj.location + bpy.ops.object.origin_set(type = 'ORIGIN_CURSOR') + + # Apply armature scale. + bpy.ops.object.select_all(action = "DESELECT") + armatureObj.select_set(True) + bpy.context.view_layer.objects.active = armatureObj + bpy.ops.object.transform_apply(location = False, rotation = False, + scale = True, properties = False) + +def getAllArmatures(armatureObj, currentArmatures): + linkedArmatures = [] + for bone in armatureObj.data.bones: + if bone.geo_cmd == 'Switch': + for switchOption in bone.switch_options: + if switchOption.switchType == 'Mesh': + if switchOption.optionArmature is None: + raise ValueError('"' + bone.name + '" in armature "' +\ + armatureObj.name + '" has a mesh switch option ' +\ + 'with no defined mesh.') + elif switchOption.optionArmature not in linkedArmatures and \ + switchOption.optionArmature not in currentArmatures: + linkedArmatures.append(switchOption.optionArmature) + + currentArmatures.extend(linkedArmatures) + for linkedArmature in linkedArmatures: + getAllArmatures(linkedArmature, currentArmatures) + +def exportGeolayoutCommon(armatureObj, obj, convertTransformMatrix, + f3dType, isHWv1): + + fModel = FModel(f3dType, isHWv1) + + if len(armatureObj.children) == 0: + raise ValueError("No mesh parented to armature.") + + obj.data.calc_loop_triangles() + obj.data.calc_normals_split() + edgeDict = getEdgeToFaceDict(obj.data) + vertDict = getVertToFaceDict(obj.data) + + # Find start bone, which is not root. Root is the start for animation. + startBoneName = findStartBone(armatureObj) + + convertTransformMatrix = convertTransformMatrix @ \ + mathutils.Matrix.Diagonal(armatureObj.scale).to_4x4() + + # Start geolayout + geolayout = Geolayout() + geolayout.rootNode = RootTransformNode(StartNode(), toAlnum(armatureObj.name)) + processBone(fModel, startBoneName, obj, armatureObj, + convertTransformMatrix, None, None, None, geolayout.rootNode, [], '', + geolayout, edgeDict, vertDict) + generateSwitchOptions(geolayout.rootNode) + return geolayout.rootNode, fModel + +def exportGeolayoutC(armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, + geoPath, dlPath, cDefPath): + rootNode, fModel = exportGeolayoutCommon(armatureObj, obj, + convertTransformMatrix, f3dType, isHWv1) + + dlData = fModel.to_c(True) + dlFile = open(dlPath, 'w') + dlFile.write(dlData) + dlFile.close() + + geoData = rootNode.to_c() + geoFile = open(geoPath, 'w') + geoFile.write(geoData) + geoFile.close() + + cDefine = rootNode.to_c_def() + fModel.to_c_def() + cDefFile = open(cDefPath, 'w') + cDefFile.write(cDefine) + cDefFile.close() + +def exportGeolayoutBinaryBank0(romfile, armatureObj, obj, exportRange, + convertTransformMatrix, levelCommandPos, modelID, textDumpFilePath, + f3dType, isHWv1, RAMAddr): + segmentData = copy.copy(bank0Segment) + rootNode, fModel = exportGeolayoutCommon(armatureObj, obj, + convertTransformMatrix, f3dType, isHWv1) + + startRAM = get64bitAlignedAddr(RAMAddr) + nonGeoStartAddr = startRAM + len(rootNode.to_binary(None)) + + addrRange = fModel.set_addr(nonGeoStartAddr) + addrEndInROM = addrRange[1] - startRAM + exportRange[0] + if addrEndInROM > exportRange[1]: + raise ValueError('Size too big: Data ends at ' + hex(addrEndInROM) +\ + ', which is larger than the specified range.') + bytesIO = BytesIO() + #actualRAMAddr = get64bitAlignedAddr(RAMAddr) + bytesIO.seek(startRAM) + bytesIO.write(rootNode.to_binary(segmentData)) + fModel.save_binary(bytesIO, segmentData) + + data = bytesIO.getvalue()[startRAM:] + bytesIO.close() + + startAddress = get64bitAlignedAddr(exportRange[0]) + romfile.seek(startAddress) + romfile.write(data) + + segPointerData = encodeSegmentedAddr(startRAM, segmentData) + geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID) + geoWriteTextDump(textDumpFilePath, rootNode, segmentData) + + return ((startAddress, startAddress + len(data)), startRAM + 0x80000000) + +def exportGeolayoutBinary(romfile, armatureObj, obj, exportRange, + convertTransformMatrix, levelData, levelCommandPos, modelID, + textDumpFilePath, f3dType, isHWv1): + + rootNode, fModel = exportGeolayoutCommon(armatureObj, obj, + convertTransformMatrix, f3dType, isHWv1) + + # Get length of data, then actually write it after relative addresses + # are found. + startAddress = get64bitAlignedAddr(exportRange[0]) + nonGeoStartAddr = startAddress + len(rootNode.to_binary(None)) + + addrRange = fModel.set_addr(nonGeoStartAddr) + if addrRange[1] > exportRange[1]: + raise ValueError('Size too big: Data ends at ' + hex(addrRange[1]) +\ + ', which is larger than the specified range.') + romfile.seek(startAddress) + romfile.write(rootNode.to_binary(levelData)) + fModel.save_binary(romfile, levelData) + + segPointerData = encodeSegmentedAddr(startAddress, levelData) + geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID) + geoWriteTextDump(textDumpFilePath, rootNode, levelData) + + return (startAddress, addrRange[1]), bytesToHex(segPointerData) + +def geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID): + if levelCommandPos is not None and modelID is not None: + romfile.seek(levelCommandPos + 3) + romfile.write(modelID.to_bytes(1, byteorder='big')) + romfile.seek(levelCommandPos + 4) + romfile.write(segPointerData) + +def geoWriteTextDump(textDumpFilePath, rootNode, levelData): + if textDumpFilePath is not None: + openfile = open(textDumpFilePath, 'w') + openfile.write(rootNode.toTextDump(levelData)) + openfile.close() + +# Switch Handling Process +# When convert armature to geolayout node hierarchy, mesh switch options +# are converted to switch node children, but material/draw layer options +# 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): + overrideNodes = [] + if isinstance(transformNode.node, SwitchNode): + i = 0 + while i < len(transformNode.children): + childNode = transformNode.children[i] + if isinstance(childNode.node, SwitchOverrideNode): + drawLayer = childNode.node.drawLayer + material = childNode.node.material + specificMat = childNode.node.specificMat + overrideType = childNode.node.overrideType + + # This should be a 0xB node + copyNode = duplicateNode(transformNode.children[0], + transformNode, transformNode.children.index(childNode)) + index = transformNode.children.index(childNode) + transformNode.children.remove(childNode) + #i -= 1 + # Assumes each switch child has only one child + for overrideChild in transformNode.children[0].children: + generateOverrideHierarchy(copyNode, overrideChild, + material, specificMat, overrideType, drawLayer, index) + if material is not None: + overrideNodes.append(copyNode) + i += 1 + for childNode in transformNode.children: + if childNode not in overrideNodes: + generateSwitchOptions(childNode) + +def generateOverrideHierarchy(parentCopyNode, transformNode, + material, specificMat, overrideType, drawLayer, index): + #print(transformNode.node) + if isinstance(transformNode.node, SwitchOverrideNode) and \ + material is not None: + return + + copyNode = TransformNode(copy.copy(transformNode.node)) + copyNode.parent = parentCopyNode + parentCopyNode.children.insert(index, copyNode) + if not isinstance(copyNode.node, SwitchOverrideNode) and\ + copyNode.node.hasDL: + if material is not None: + copyNode.node.DLmicrocode = \ + copyNode.node.fMesh.drawMatOverrides[(material, specificMat, overrideType)] + if drawLayer is not None: + copyNode.node.drawLayer = drawLayer + + for child in transformNode.children: + generateOverrideHierarchy(copyNode, child, material, specificMat, overrideType, + drawLayer, transformNode.children.index(child)) + +def addStartNode(transformNode): + optionNode = TransformNode(StartNode()) + optionNode.parent = transformNode + transformNode.children.append(optionNode) + return optionNode + +def duplicateNode(transformNode, parentNode, index): + optionNode = TransformNode(copy.copy(transformNode.node)) + optionNode.parent = parentNode + parentNode.children.insert(index, optionNode) + return optionNode + +def addPreTranslateRotateNode(armatureObj, parentTransformNode, + translate, rotate): + preNodeTransform = TransformNode( + TranslateRotateNode(armatureObj, None, translate, rotate)) + + preNodeTransform.parent = parentTransformNode + parentTransformNode.children.append(preNodeTransform) + return preNodeTransform + +class Geolayout: + def __init__(self): + self.rootNode = None + # dict of Object : geolayout + self.secondaryGeolayouts = {} + +class RootTransformNode: + def __init__(self, node, name): + self.name = name + self.node = node + self.children = [] + self.parent = None + + # dict of Object : node + self.switchArmatures = {} + + def to_binary(self, segmentData): + data = self.node.to_binary(segmentData) + if len(self.children) > 0: + if data[0] in nodeGroupCmds: + data.extend(bytearray([GEO_NODE_OPEN, 0x00, 0x00, 0x00])) + for child in self.children: + data.extend(child.to_binary(segmentData)) + if data[0] in nodeGroupCmds: + data.extend(bytearray([GEO_NODE_CLOSE, 0x00, 0x00, 0x00])) + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + data.extend(bytearray([GEO_END, 0x00, 0x00, 0x00])) + return data + + def to_c(self): + data = 'const GeoLayout ' + self.name + '[] = {\n' + data += '\t' + self.node.to_c() + '\n' + if len(self.children) > 0: + if type(self.node) in nodeGroupClasses: + data += '\t' + 'GEO_OPEN_NODE(),\n' + for child in self.children: + data += child.to_c(2) + if type(self.node) in nodeGroupClasses: + data += '\t' + 'GEO_CLOSE_NODE(),\n' + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + data += '\t' + 'GEO_RETURN(),\n' + data += '};\n' + return data + + def to_c_def(self): + return 'extern const GeoLayout ' + self.name + '[];\n' + + def toTextDump(self, segmentData): + data = '' + command = self.node.to_binary(segmentData) + + for byteVal in command: + data += (format(byteVal, '02X') + ' ') + data += '\n' + + if len(self.children) > 0: + if command[0] in nodeGroupCmds: + data += '04 00 00 00\n' + for child in self.children: + data += child.toTextDump(1, segmentData) + if command[0] in nodeGroupCmds: + data += '05 00 00 00\n' + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + data += '01 00 00 00' + return data + +class TransformNode: + def __init__(self, node): + self.node = node + self.children = [] + self.parent = None + + # Function commands usually effect the following command, so it is similar + # to a parent child relationship. + def to_binary(self, segmentData): + data = self.node.to_binary(segmentData) + if len(self.children) > 0: + if type(self.node) is DisplayListNode: + raise ValueError("A DisplayListNode cannot have children.") + elif type(self.node) is FunctionNode: + raise ValueError("An FunctionNode cannot have children.") + + if data[0] in nodeGroupCmds: + data.extend(bytearray([GEO_NODE_OPEN, 0x00, 0x00, 0x00])) + for child in self.children: + data.extend(child.to_binary(segmentData)) + if data[0] in nodeGroupCmds: + data.extend(bytearray([GEO_NODE_CLOSE, 0x00, 0x00, 0x00])) + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + return data + + def to_c(self, depth): + data = depth * '\t' + self.node.to_c() + '\n' + if len(self.children) > 0: + if type(self.node) in nodeGroupClasses: + data += depth * '\t' + 'GEO_OPEN_NODE(),\n' + for child in self.children: + data += child.to_c(depth + 1) + if type(self.node) in nodeGroupClasses: + data += depth * '\t' + 'GEO_CLOSE_NODE(),\n' + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + return data + + def toTextDump(self, nodeLevel, segmentData): + data = '' + command = self.node.to_binary(segmentData) + + data += '\t' * nodeLevel + for byteVal in command: + data += (format(byteVal, '02X') + ' ') + data += '\n' + + if len(self.children) > 0: + if command[0] in nodeGroupCmds: + data += '\t' * nodeLevel + '04 00 00 00\n' + for child in self.children: + data += child.toTextDump(nodeLevel + 1, segmentData) + if command[0] in nodeGroupCmds: + data += '\t' * nodeLevel + '05 00 00 00\n' + elif type(self.node) is SwitchNode: + raise ValueError("A switch bone must have at least one child bone.") + return data + +# need to remember last geometry holding parent bone. +# to do skinning, add the 0x15 command before any non-geometry bone groups. +# + +# transformMatrix is a constant matrix to apply to verts, +# not related to heirarchy. + +# lastTransformParentName: last parent with mesh data. +# lastDeformParentName: last parent in transform node category. +# this may or may not include mesh data. + +# If an armature is rotated, its bones' local_matrix will remember original +# rotation. Thus we don't want a bone's matrix relative to armature, but +# relative to the root bone of the armature. + +def processBone(fModel, boneName, obj, armatureObj, transformMatrix, + lastTranslateName, lastRotateName, lastDeformName, parentTransformNode, + materialOverrides, namePrefix, geolayout, edgeDict, vertDict): + bone = armatureObj.data.bones[boneName] + poseBone = armatureObj.pose.bones[boneName] + boneGroup = poseBone.bone_group + finalTransform = copy.deepcopy(transformMatrix) + materialOverrides = copy.copy(materialOverrides) + + if boneGroup is not None and boneGroup.name == 'Ignore': + return + + # Get translate + if lastTranslateName is not None: + translateParent = armatureObj.data.bones[lastTranslateName] + translate = (translateParent.matrix_local.inverted() @ \ + bone.matrix_local).decompose()[0] + else: + translateParent = None + translate = bone.matrix_local.decompose()[0] + + # Get rotate + if lastRotateName is not None: + rotateParent = armatureObj.data.bones[lastRotateName] + rotate = (rotateParent.matrix_local.inverted() @ \ + bone.matrix_local).decompose()[1] + else: + rotateParent = None + rotate = bone.matrix_local.decompose()[1] + + translation = mathutils.Matrix.Translation(translate) + rotation = rotate.to_matrix().to_4x4() + + if boneGroup is None: + rotAxis, rotAngle = rotate.to_axis_angle() + if rotAngle > 0.00001: + + node = DisplayListWithOffsetNode(armatureObj, boneName, + mathutils.Vector((0,0,0))) + + parentTransformNode = addPreTranslateRotateNode( + armatureObj, parentTransformNode, translate, rotate) + + lastTranslateName = boneName + lastRotateName = boneName + else: + node = DisplayListWithOffsetNode(armatureObj, boneName, translate) + lastTranslateName = boneName + + finalTransform = transformMatrix @ translation + + elif boneGroup.name == 'Function': + node = FunctionNode(armatureObj, boneName) + elif boneGroup.name == 'HeldObject': + node = HeldObjectNode(armatureObj, boneName, translate) + else: + if boneGroup.name == 'Switch': + # This is done so we can easily calculate transforms + # of switch options. + + + node = SwitchNode(armatureObj, boneName) + processSwitchBoneMatOverrides(materialOverrides, bone) + + elif boneGroup.name == 'Start': + node = StartNode() + elif boneGroup.name == 'TranslateRotate': + node = TranslateRotateNode(armatureObj, boneName, translate, rotate) + + if node.fieldLayout == 0: + finalTransform = transformMatrix @ translation @ rotation + lastTranslateName = boneName + lastRotateName = boneName + elif node.fieldLayout == 1: + finalTransform = transformMatrix @ translation + lastTranslateName = boneName + elif node.fieldLayout == 2: + finalTransform = transformMatrix @ rotation + lastRotateName = boneName + else: + yRot = rotate.to_euler().y + rotation = mathutils.Euler((0, yRot, 0)).to_matrix().to_4x4() + finalTransform = transformMatrix @ rotation + lastRotateName = boneName + + elif boneGroup.name == 'Translate': + node = TranslateNode(armatureObj, boneName, translate) + finalTransform = transformMatrix @ translation + lastTranslateName = boneName + elif boneGroup.name == 'Rotate': + node = RotateNode(armatureObj, boneName, rotate) + finalTransform = transformMatrix @ rotation + lastRotateName = boneName + elif boneGroup.name == 'Billboard': + node = BillboardNode(armatureObj, boneName, translate) + finalTransform = transformMatrix @ translation + lastTranslateName = boneName + elif boneGroup.name == 'DisplayList': + node = DisplayListNode(armatureObj, boneName) + if not armatureObj.data.bones[boneName].use_deform: + raise ValueError("Display List (0x15) " + boneName + ' must be a deform bone. Make sure deform is checked in bone properties.') + elif boneGroup.name == 'Shadow': + node = ShadowNode(armatureObj, boneName) + elif boneGroup.name == 'Scale': + node = ScaleNode(armatureObj, boneName) + finalTransform = transformMatrix @ \ + mathutils.Matrix.Scale(node.scaleValue, 4) + elif boneGroup.name == 'StartRenderArea': + node = StartRenderAreaNode(armatureObj, boneName) + else: + raise ValueError("Invalid bone group " + boneGroup.name) + + transformNode = TransformNode(node) + + # TODO: HERE!!! + if node.hasDL: + meshGroup = saveModelGivenVertexGroup( + fModel, obj, bone.name, lastDeformName, + finalTransform, armatureObj, materialOverrides, namePrefix, edgeDict, + vertDict) + + if meshGroup is None: + #print("No mesh data.") + if isinstance(node, DisplayListNode): + raise ValueError("Display List (0x15) " + boneName + " must have vertices assigned to it. If you have already done this, make sure there aren't any other bones that also own these vertices with greater or equal weighting.") + node.hasDL = False + bone.use_deform = False + parentTransformNode.children.append(transformNode) + transformNode.parent = parentTransformNode + else: + node.DLmicrocode = meshGroup.mesh.draw + node.fMesh = meshGroup.mesh # Used for material override switches + if lastDeformName is not None and \ + armatureObj.data.bones[lastDeformName].geo_cmd == 'SwitchOption' \ + and meshGroup.skinnedMesh is not None: + raise ValueError("Cannot skin geometry to a Switch Option " +\ + "bone. Skinning cannot occur across a switch node.") + + + transformNode = addSkinnedMeshNode(armatureObj, boneName, + meshGroup.skinnedMesh, transformNode, parentTransformNode) + + lastDeformName = boneName + #print(boneName) + else: + parentTransformNode.children.append(transformNode) + transformNode.parent = parentTransformNode + + isSwitch = isinstance(transformNode.node, SwitchNode) + if isSwitch: + switchTransformNode = transformNode + option0Node = addStartNode(transformNode) + #option0Node = addPreTranslateRotateNode(armatureObj, transformNode, + # mathutils.Vector((0,0,0)), mathutils.Quaternion()) + transformNode = option0Node + + #print(boneGroup.name if boneGroup is not None else "Offset") + if len(bone.children) > 0: + #print("\tHas Children") + if boneGroup is not None and boneGroup.name == 'Function': + raise ValueError("Function bones cannot have children. They instead affect the next sibling bone in alphabetical order.") + + # Handle child nodes + # nonDeformTransformData should be modified to be sent to children, + # otherwise it should not be modified for parent. + # This is so it can be used for siblings. + childrenNames = sorted([bone.name for bone in bone.children]) + for name in childrenNames: + processBone(fModel, name, obj, armatureObj, + finalTransform, lastTranslateName, lastRotateName, + lastDeformName, transformNode, materialOverrides, namePrefix, + geolayout, edgeDict, vertDict) + #transformNode.children.append(childNode) + #childNode.parent = transformNode + + # see generateSwitchOptions() for explanation. + if isSwitch: + bone = armatureObj.data.bones[boneName] + for switchIndex in range(len( bone.switch_options)): + switchOption = bone.switch_options[switchIndex] + if switchOption.switchType == 'Mesh': + optionArmature = switchOption.optionArmature + if optionArmature is None: + raise ValueError('Error: In switch bone ' + boneName +\ + ' for option ' + str(switchIndex) + \ + ', the switch option armature is None.') + elif not isinstance(optionArmature.data, bpy.types.Armature): + raise ValueError('Error: In switch bone ' + boneName +\ + ' for option ' + str(switchIndex) + \ + ', the object provided is not an armature.') + elif optionArmature in geolayout.secondaryGeolayouts: + raise ValueError('Error: In switch bone ' + boneName +\ + ' for option ' + str(switchIndex) + \ + ', the armature provided cannot be used more than once' + \ + ' as a switch option in the entire geolayout.') + + #optionNode = addStartNode(switchTransformNode) + + optionBoneName = getSwitchOptionBone(optionArmature) + optionBone = optionArmature.data.bones[optionBoneName] + switchOptionRotate = (bone.matrix_local.inverted() @ \ + optionBone.matrix_local).decompose()[1] + switchOptionRotate = mathutils.Quaternion() + + # Armature doesn't matter here since node is not based off bone + translateRotateNode = addPreTranslateRotateNode( + optionArmature, switchTransformNode, translate, + rotate @ switchOptionRotate) + + geolayout.secondaryGeolayouts[optionArmature] = translateRotateNode + + childrenNames = sorted( + [bone.name for bone in optionBone.children]) + for name in childrenNames: + # We can use optionBone as the last translate/rotate + # since we added a TranslateRotate node before + # the switch node. + optionObjs = [] + for childObj in optionArmature.children: + if isinstance(childObj.data, bpy.types.Mesh): + optionObjs.append(childObj) + if len(optionObjs) > 1: + raise ValueError('Error: In switch bone ' + boneName +\ + ' for option ' + str(switchIndex) + \ + ', the switch option armature has more than one mesh child.') + elif len(optionObjs) < 1: + raise ValueError('Error: In switch bone ' + boneName +\ + ' for option ' + str(switchIndex) + \ + ', the switch option armature has no mesh children.') + optionObj = optionObjs[0] + optionObj.data.calc_loop_triangles() + optionObj.data.calc_normals_split() + optionEdgeDict = getEdgeToFaceDict(optionObj.data) + optionVertDict = getVertToFaceDict(optionObj.data) + processBone(fModel, name, optionObj, + optionArmature, + finalTransform, optionBone.name, optionBone.name, + optionBone.name, translateRotateNode, materialOverrides, + optionBone.name, geolayout, optionEdgeDict, + optionVertDict) + else: + if switchOption.switchType == 'Material': + material = switchOption.materialOverride + if switchOption.overrideDrawLayer: + drawLayer = int(switchOption.drawLayer) + else: + drawLayer = None + if switchOption.materialOverrideType == 'Specific': + specificMat = tuple([matPtr.material for matPtr in \ + switchOption.specificOverrideArray]) + else: + specificMat = tuple([matPtr.material for matPtr in \ + switchOption.specificIgnoreArray]) + else: + material = None + specificMat = None + drawLayer = int(switchOption.drawLayer) + + overrideNode = TransformNode(SwitchOverrideNode( + material, specificMat, drawLayer, + switchOption.materialOverrideType)) + overrideNode.parent = switchTransformNode + switchTransformNode.children.append(overrideNode) + +def processSwitchBoneMatOverrides(materialOverrides, switchBone): + for switchOption in switchBone.switch_options: + if switchOption.switchType == 'Material': + if switchOption.materialOverride is None: + raise ValueError("Error: On switch bone " + \ + switchBone.name + ', a switch option' + \ + ' is a Material Override, but no material is provided.') + if switchOption.materialOverrideType == 'Specific': + for mat in switchOption.specificOverrideArray: + if mat is None: + raise ValueError("Error: On switch bone " + \ + switchBone.name + ', a switch option' + \ + ' has a material override field that is None.') + specificMat = tuple([matPtr.material for matPtr in \ + switchOption.specificOverrideArray]) + else: + for mat in switchOption.specificIgnoreArray: + if mat is None: + raise ValueError("Error: On switch bone " + \ + switchBone.name + ', a switch option' + \ + ' has a material ignore field that is None.') + specificMat = tuple([matPtr.material for matPtr in \ + switchOption.specificIgnoreArray]) + + materialOverrides.append((switchOption.materialOverride, specificMat, + switchOption.materialOverrideType)) + +class SwitchOverrideNode: + def __init__(self, material, specificMat, drawLayer, overrideType): + self.material = material + self.specificMat = specificMat + self.drawLayer = drawLayer + self.overrideType = overrideType + +# We add Function commands to nonDeformTransformData because any skinned +# 0x15 commands should go before them, as they are usually preceding +# an empty transform command (of which they modify?) +class FunctionNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + if bone.geo_func == '': + raise ValueError('Function bone ' + boneName + ' function value is empty.') + self.geo_func = bone.geo_func + self.func_param = bone.func_param + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_CALL_ASM, 0x00]) + command.extend(self.func_param.to_bytes(2, 'big', signed = True)) + command.extend(bytes.fromhex(self.geo_func)) + return command + + def to_c(self): + return "GEO_ASM(" + str(self.func_param) + ', ' + \ + toAlnum(self.geo_func) + '),' + +class HeldObjectNode: + def __init__(self, armatureObj, boneName, translate): + bone = armatureObj.data.bones[boneName] + if bone.geo_func == '': + raise ValueError('Held object bone ' + boneName + ' function value is empty.') + self.geo_func = bone.geo_func + self.translate = translate + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_HELD_OBJECT, 0x00]) + command.extend(bytearray([0x00] * 6)) + writeVectorToShorts(command, 2, self.translate) + command.extend(bytes.fromhex(self.geo_func)) + return command + + def to_c(self): + return "GEO_HELD_OBJECT(0, " + \ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) + ', ' +\ + toAlnum(self.geo_func) + '),' + +class StartNode: + def __init__(self): + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_START, 0x00, 0x00, 0x00]) + return command + + def to_c(self): + return "GEO_NODE_START()," + +class EndNode: + def __init__(self): + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_END, 0x00, 0x00, 0x00]) + return command + + def to_c(self): + return 'GEO_END(),' + +# Geolayout node hierarchy is first generated without material/draw layer +# override options, but with material override DL's being generated. +# Afterward, for each switch node the node hierarchy is duplicated and +# the correct diplsay lists are added. +class SwitchNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + if bone.geo_func == '': + raise ValueError('Switch bone ' + boneName + ' function value is empty.') + self.switchFunc = bone.geo_func + self.defaultCase = bone.func_param + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_SWITCH, 0x00]) + command.extend(self.defaultCase.to_bytes(2, 'big', signed = True)) + command.extend(bytes.fromhex(self.switchFunc)) + return command + + def to_c(self): + return "GEO_SWITCH_CASE(" + \ + str(self.defaultCase) + ', ' +\ + toAlnum(self.switchFunc) + '),' + +class TranslateRotateNode: + def __init__(self, armatureObj, boneName, translate, rotate): + if boneName is not None: + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + self.fieldLayout = int(bone.field_layout) + self.hasDL = bone.use_deform + else: + self.drawLayer = 1 + self.fieldLayout = 0 + self.hasDL = False + + self.translate = translate + self.rotate = rotate + + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + params = ((1 if self.hasDL else 0) << 7) & \ + (self.fieldLayout << 4) | self.drawLayer + + command = bytearray([GEO_TRANSLATE_ROTATE, params]) + if self.fieldLayout == 0: + command.extend(bytearray([0x00] * 14)) + writeVectorToShorts(command, 4, self.translate) + writeEulerVectorToShorts(command, 10, + self.rotate.to_euler(geoNodeRotateOrder)) + elif self.fieldLayout == 1: + command.extend(bytearray([0x00] * 6)) + writeVectorToShorts(command, 2, self.translate) + elif self.fieldLayout == 2: + command.extend(bytearray([0x00] * 6)) + writeEulerVectorToShorts(command, 2, + self.rotate.to_euler(geoNodeRotateOrder)) + elif self.fieldLayout == 3: + command.extend(bytearray([0x00] * 2)) + writeFloatToShort(command, 2, + self.rotate.to_euler(geoNodeRotateOrder).y) + if self.hasDL: + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + if self.fieldLayout == 0: + return ("GEO_TRANSLATE_ROTATE_WITH_DL" if self.hasDL else \ + "GEO_TRANSLATE_ROTATE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[0])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[1])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[2])) + \ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + elif self.fieldLayout == 1: + return ("GEO_TRANSLATE_WITH_DL" if self.hasDL else \ + "GEO_TRANSLATE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) +\ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + elif self.fieldLayout == 2: + return ("GEO_ROTATE_WITH_DL" if self.hasDL else \ + "GEO_ROTATE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[0])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[1])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[2])) + \ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + elif self.fieldLayout == 3: + return ("GEO_ROTATE_Y_WITH_DL" if self.hasDL else \ + "GEO_ROTATE_Y") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[1])) +\ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + +class TranslateNode: + def __init__(self, armatureObj, boneName, translate): + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + + self.hasDL = bone.use_deform + self.translate = translate + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + params = ((1 if self.hasDL else 0) << 7) | self.drawLayer + command = bytearray([GEO_TRANSLATE, params]) + command.extend(bytearray([0x00] * 6)) + writeVectorToShorts(command, 2, self.translate) + if self.hasDL: + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return ("GEO_TRANSLATE_NODE_WITH_DL" if self.hasDL else \ + "GEO_TRANSLATE_NODE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) +\ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + +class RotateNode: + def __init__(self, armatureObj, boneName, rotate): + # In the case for automatically inserting rotate nodes between + # 0x13 bones. + if boneName is not None: + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + + self.hasDL = bone.use_deform + self.rotate = rotate + self.fMesh = None + self.DLmicrocode = None + else: + self.drawLayer = 1 + self.hasDL = False + self.rotate = rotate + self.fMesh = None + self.DLmicrocode = None + + + def to_binary(self, segmentData): + params = ((1 if self.hasDL else 0) << 7) | self.drawLayer + command = bytearray([GEO_ROTATE, params]) + command.extend(bytearray([0x00] * 6)) + writeEulerVectorToShorts(command, 2, + self.rotate.to_euler(geoNodeRotateOrder)) + if self.hasDL: + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return ("GEO_ROTATION_NODE_WITH_DL" if self.hasDL else \ + "GEO_ROTATION_NODE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[0])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[1])) + ', ' +\ + str(convertEulerFloatToShort(self.rotate.to_euler( + geoNodeRotateOrder)[2])) + \ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + +class BillboardNode: + def __init__(self, armatureObj, boneName, translate): + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + + self.hasDL = bone.use_deform + self.translate = translate + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + params = ((1 if self.hasDL else 0) << 7) | self.drawLayer + command = bytearray([GEO_BILLBOARD, params]) + command.extend(bytearray([0x00] * 6)) + writeVectorToShorts(command, 2, self.translate) + if self.hasDL: + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return ("GEO_BILLBOARD_WITH_PARAMS_AND_DL" if self.hasDL else \ + "GEO_BILLBOARD_WITH_PARAMS") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) +\ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + +class DisplayListNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + self.hasDL = True + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + if self.DLmicrocode is None: + raise ValueError("No mesh data associated with this 0x15 command. Make sure you have assigned vertices to this node.") + command = bytearray([GEO_LOAD_DL, self.drawLayer, 0x00, 0x00]) + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return "GEO_DISPLAY_LIST(" + \ + str(self.drawLayer) + ', ' +\ + self.DLmicrocode.name + '),' + +class ShadowNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + self.shadowType = int(bone.shadow_type) + self.shadowSolidity = int(round(bone.shadow_solidity * 0xFF)) + self.shadowScale = bone.shadow_scale + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_START_W_SHADOW, 0x00]) + command.extend(self.shadowType.to_bytes(2, 'big')) + command.extend(self.shadowSolidity.to_bytes(2, 'big')) + command.extend(self.shadowScale.to_bytes(2, 'big')) + return command + + def to_c(self): + return "GEO_SHADOW(" + \ + str(self.shadowType) + ', ' +\ + str(self.shadowSolidity) + ', ' +\ + str(self.shadowScale) + '),' + +class ScaleNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + + self.scaleValue = bone.geo_scale + self.hasDL = bone.use_deform + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + params = ((1 if self.hasDL else 0) << 7) | self.drawLayer + command = bytearray([GEO_SCALE, params, 0x00, 0x00]) + command.extend(int(self.scaleValue * 0x10000).to_bytes(4, 'big')) + if self.hasDL: + if segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return ("GEO_SCALE_WITH_DL" if self.hasDL else "GEO_SCALE") + "(" + \ + str(self.drawLayer) + ', ' +\ + str(int(round(self.scaleValue * 0x10000))) +\ + ((', ' + self.DLmicrocode.name + '),') if self.hasDL else '),') + +class StartRenderAreaNode: + def __init__(self, armatureObj, boneName): + bone = armatureObj.data.bones[boneName] + self.cullingRadius = bone.culling_radius + self.hasDL = False + + def to_binary(self, segmentData): + command = bytearray([GEO_START_W_RENDERAREA, 0x00]) + command.extend(self.cullingRadius.to_bytes(2, 'big')) + return command + + def to_c(self): + return 'GEO_CULLING_RADIUS(' + str(self.cullingRadius) + '),' + +class DisplayListWithOffsetNode: + def __init__(self, armatureObj, boneName, translate): + bone = armatureObj.data.bones[boneName] + self.drawLayer = int(bone.draw_layer) + + self.hasDL = bone.use_deform + self.translate = translate + self.fMesh = None + self.DLmicrocode = None + + def to_binary(self, segmentData): + command = bytearray([GEO_LOAD_DL_W_OFFSET, self.drawLayer]) + command.extend(bytearray([0x00] * 6)) + writeVectorToShorts(command, 2, self.translate) + if self.hasDL and self.DLmicrocode is not None and segmentData is not None: + command.extend(encodeSegmentedAddr(self.DLmicrocode.startAddress,segmentData)) + else: + command.extend(bytearray([0x00] * 4)) + return command + + def to_c(self): + return "GEO_ANIMATED_PART(" + \ + str(self.drawLayer) + ', ' +\ + str(convertFloatToShort(self.translate[0])) + ', ' +\ + str(convertFloatToShort(self.translate[1])) + ', ' +\ + str(convertFloatToShort(self.translate[2])) + ', ' +\ + (self.DLmicrocode.name if self.hasDL else 'NULL') + '),' + +def getGroupIndexFromname(obj, name): + for group in obj.vertex_groups: + if group.name == name: + return group.index + return None + +def getGroupNameFromIndex(obj, index): + for group in obj.vertex_groups: + if group.index == index: + return group.name + return None + +def getGroupIndex(vert, armatureObj, obj): + actualGroups = [] + for group in vert.groups: + groupName = getGroupNameFromIndex(obj, group.group) + if groupName is not None and groupName in armatureObj.data.bones: + actualGroups.append(group) + + if len(actualGroups) == 0: + raise ValueError("All vertices must be part of a vertex group (weight painted), and the vertex group must correspond to a bone in the armature.") + vertGroup = actualGroups[0] + for group in actualGroups: + if group.weight > vertGroup.weight: + vertGroup = group + + return vertGroup.group + +class SkinnedFace(): + def __init__(self, bFace, loopsInGroup, loopsNotInGroup): + self.bFace= bFace + self.loopsInGroup = loopsInGroup + self.loopsNotInGroup = loopsNotInGroup + +def checkIfFirstNonASMNode(childNode): + index = childNode.parent.children.index(childNode) + if index == 0: + return True + while index > 0 and \ + isinstance(childNode.parent.children[index - 1].node, FunctionNode): + index -= 1 + return index == 0 + +# 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. +# Sibling mesh nodes thus cannot share the same transform nodes before it +# If they are both deform. +# Additionally, ASM nodes should count as modifiers for other nodes if +# they precede them +def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parentNode): + # Add node to its immediate parent + #print(str(type(parentNode.node)) + str(type(transformNode.node))) + parentNode.children.append(transformNode) + transformNode.parent = parentNode + + if skinnedMesh is None: + return transformNode + #else: + # print("Skinned mesh exists.") + + # Get skinned node + skinnedNode = DisplayListNode(armatureObj, boneName) + skinnedNode.fMesh = skinnedMesh + skinnedNode.DLmicrocode = skinnedMesh.draw + skinnedTransformNode = TransformNode(skinnedNode) + + # Ascend heirarchy until reaching first node before a deform parent. + # We duplicate the hierarchy along the way to possibly use later. + highestChildNode = transformNode + transformNodeCopy = TransformNode(copy.copy(transformNode.node)) + transformNodeCopy.parent = parentNode + highestChildCopy = transformNodeCopy + isFirstChild = True + hasNonDeform0x13Command = False + acrossSwitchNode = False + while highestChildNode.parent is not None and\ + not (highestChildNode.parent.node.hasDL): # empty 0x13 command? + isFirstChild &= checkIfFirstNonASMNode(highestChildNode) + hasNonDeform0x13Command &= highestChildNode.parent.node is \ + DisplayListWithOffsetNode + + acrossSwitchNode |= isinstance(highestChildNode.parent.node, SwitchNode) + + highestChildNode = highestChildNode.parent + highestChildCopyParent = TransformNode(copy.copy(highestChildNode.node)) + highestChildCopyParent.children = [highestChildCopy] + highestChildCopy.parent = highestChildCopyParent + #print(str(highestChildCopy.node) + " " + str(isFirstChild)) + highestChildCopy = highestChildCopyParent + if highestChildNode.parent is None: + raise ValueError("There shouldn't be a skinned mesh section if there is no deform parent. This error may have ocurred if a switch option node is trying to skin to a parent but no deform parent exists.") + + # Otherwise, remove the transformNode from the parent and + # duplicate the node heirarchy up to the last deform parent. + # Add the skinned node first to the last deform parent, + # then add the duplicated node hierarchy afterward. + if highestChildNode != transformNode: + if not isFirstChild: + #print("Hierarchy but not first child.") + if hasNonDeform0x13Command: + raise ValueError("Error with " + boneName + ': You cannot have more that one child skinned mesh connected to a parent skinned mesh with a non deform 0x13 bone in between. Try removing any unnecessary non-deform bones.') + + if acrossSwitchNode: + raise ValueError("Error with " + boneName + ': You can not' +\ + ' skin across a switch node with more than one child.') + + # Remove transformNode + parentNode.children.remove(transformNode) + transformNode.parent = None + + # copy hierarchy, along with any preceding Function commands + highestChildIndex = \ + highestChildNode.parent.children.index(highestChildNode) + precedingFunctionCmds = [] + while highestChildIndex > 0 and \ + type(highestChildNode.parent.children[\ + highestChildIndex - 1].node) is FunctionNode: + + precedingFunctionCmds.insert(0, copy.deepcopy( + highestChildNode.parent.children[highestChildIndex - 1])) + highestChildIndex -= 1 + #_____________ + # add skinned mesh node + highestChildCopy.parent = highestChildNode.parent + highestChildCopy.parent.children.append(skinnedTransformNode) + skinnedTransformNode.parent = highestChildCopy.parent + + # add Function cmd nodes + for asmCmdNode in precedingFunctionCmds: + highestChildCopy.parent.children.append(asmCmdNode) + + # add heirarchy to parent + highestChildCopy.parent.children.append(highestChildCopy) + + transformNode = transformNodeCopy + else: + #print("Hierarchy with first child.") + nodeIndex = highestChildNode.parent.children.index(highestChildNode) + while nodeIndex > 0 and \ + type(highestChildNode.parent.children[\ + nodeIndex - 1].node) is FunctionNode: + nodeIndex -= 1 + highestChildNode.parent.children.insert( + nodeIndex, skinnedTransformNode) + skinnedTransformNode.parent = highestChildNode.parent + else: + #print("Immediate child.") + nodeIndex = parentNode.children.index(transformNode) + parentNode.children.insert(nodeIndex, skinnedTransformNode) + skinnedTransformNode.parent = parentNode + + return transformNode + +def getAncestorGroups(parentGroup, armatureObj, obj): + if parentGroup is None: + return [] + processingBones = \ + [bone for bone in armatureObj.data.bones if bone.parent is None] + ancestorBones = [] + while len(processingBones) > 0: + currentBone = processingBones[0] + processingBones = processingBones[1:] + if currentBone.name != parentGroup: + ancestorBones.append(getGroupIndexFromname(obj, currentBone.name)) + processingBones.extend(currentBone.children) + return ancestorBones + +def checkUniqueBoneNames(fModel, name, vertexGroup): + if name in fModel.meshGroups: + raise ValueError(vertexGroup + " has already been processed. Make " +\ + "sure this bone name is unique, even across all switch option " +\ + "armatures.") + +def saveModelGivenVertexGroup(fModel, obj, vertexGroup, + parentGroup, transformMatrix, armatureObj, materialOverrides, namePrefix, + edgeDict, vertDict): + checkForF3DMaterial(obj) + + mesh = obj.data + currentGroupIndex = getGroupIndexFromname(obj, vertexGroup) + vertIndices = [vert.index for vert in obj.data.vertices if\ + getGroupIndex(vert, armatureObj, obj) == currentGroupIndex] + parentGroupIndex = getGroupIndexFromname(obj, parentGroup) \ + if parentGroup is not None else -1 + + ancestorGroups = getAncestorGroups(parentGroup, armatureObj, obj) + + if len(vertIndices) == 0: + return None + + bone = armatureObj.data.bones[vertexGroup] + + currentMatrix = mathutils.Matrix.Scale(1 / sm64ToBlenderScale, 4) @ \ + bone.matrix_local.inverted() + + if parentGroup is None: + parentMatrix = mathutils.Matrix.Scale(1 / sm64ToBlenderScale, 4) + else: + parentBone = armatureObj.data.bones[parentGroup] + parentMatrix = mathutils.Matrix.Scale(1 / sm64ToBlenderScale, 4) @ \ + parentBone.matrix_local.inverted() + + # dict of material_index keys to face array values + groupFaces = {} + + # dict of material_index keys to SkinnedFace objects + skinnedFaces = {} + + handledFaces = [] + for vertIndex in vertIndices: + if vertIndex not in vertDict: + continue + for face in vertDict[vertIndex]: + # generate a vertDict + + # Ignore repeat faces + if face in handledFaces: + continue + else: + handledFaces.append(face) + + loopsInGroup = [] + loopsNotInGroup = [] + isChildSkinnedFace = False + + # loop is interpreted as face + loop index + for i in range(3): + vertGroupIndex = \ + getGroupIndex(mesh.vertices[face.vertices[i]], + armatureObj, obj) + if vertGroupIndex == currentGroupIndex: + loopsInGroup.append((face, mesh.loops[face.loops[i]])) + elif vertGroupIndex == parentGroupIndex: + loopsNotInGroup.append((face, mesh.loops[face.loops[i]])) + elif vertGroupIndex not in ancestorGroups: + # Only want to handle skinned faces connected to parent + isChildSkinnedFace = True + break + else: + raise ValueError("Error with " + vertexGroup + ": Verts attached to one bone can not be attached to any of its ancestor bones besides its first immediate deformable parent bone. For example, a foot vertex can be connected to a leg vertex, but a foot vertex cannot be connected to a thigh vertex.") + if isChildSkinnedFace: + continue + + if len(loopsNotInGroup) == 0: + if face.material_index not in groupFaces: + groupFaces[face.material_index] = [] + groupFaces[face.material_index].append(face) + else: + if face.material_index not in skinnedFaces: + skinnedFaces[face.material_index] = [] + skinnedFaces[face.material_index].append( + SkinnedFace(face, loopsInGroup, loopsNotInGroup)) + + # Save skinned mesh + if len(skinnedFaces) > 0: + #print("Skinned") + fMeshGroup = saveSkinnedMeshByMaterial(skinnedFaces, fModel, + vertexGroup, obj, currentMatrix, parentMatrix, namePrefix, edgeDict) + else: + fMeshGroup = FMeshGroup(toAlnum(namePrefix + vertexGroup), + FMesh(toAlnum(namePrefix + vertexGroup) + '_mesh'), None) + + # Save mesh group + checkUniqueBoneNames(fModel, toAlnum(namePrefix + vertexGroup), vertexGroup) + fModel.meshGroups[toAlnum(namePrefix + vertexGroup)] = fMeshGroup + + # Save unskinned mesh + for material_index, bFaces in groupFaces.items(): + material = obj.data.materials[material_index] + saveMeshByFaces(material, bFaces, + fModel, fMeshGroup.mesh, obj, currentMatrix, edgeDict) + + # End mesh drawing + # Reset settings to prevent issues with other models + revertMatAndEndDraw(fMeshGroup.mesh.draw) + + # Must be done after all geometry saved + for (material, specificMat, overrideType) in materialOverrides: + if fMeshGroup.mesh is not None: + saveOverrideDraw(obj, fModel, material, specificMat, overrideType, + fMeshGroup.mesh) + if fMeshGroup.skinnedMesh is not None: + saveOverrideDraw(obj, fModel, material, specificMat, overrideType, + fMeshGroup.skinnedMesh) + + return fMeshGroup + +def saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh): + fOverrideMat, texDimensions = \ + saveOrGetF3DMaterial(material, fModel, obj) + meshMatOverride = GfxList( + fMesh.name + '_mat_override_' + toAlnum(material.name)) + #print('fdddddddddddddddd ' + str(fMesh.name) + " " + str(material) + " " + str(specificMat) + " " + str(overrideType)) + fMesh.drawMatOverrides[(material, specificMat, overrideType)] = meshMatOverride + removeReverts = [] + for command in fMesh.draw.commands: + meshMatOverride.commands.append(copy.copy(command)) + for command in meshMatOverride.commands: + if isinstance(command, SPDisplayList): + for modelMaterial, (fMaterial, texDimensions) in \ + fModel.materials.items(): + shouldModify = \ + (overrideType == 'Specific' and modelMaterial in specificMat) or \ + (overrideType == 'All' and modelMaterial not in specificMat) + if command.displayList == fMaterial.material and shouldModify: + #print(fOverrideMat.material.name) + command.displayList = fOverrideMat.material + if command.displayList == fMaterial.revert and shouldModify: + if fOverrideMat.revert is not None: + command.displayList = fOverrideMat.revert + else: + removeReverts.append(command) + for command in removeReverts: + meshMatOverride.commands.remove(command) + + #else: + # meshMatOverride.commands.append(SPDisplayList(fOverrideMat.material)) + # for triList in fMesh.triangleLists: + # meshMatOverride.commands.append(SPDisplayList(triList)) + # if fOverrideMat.revert is not None: + # meshMatOverride.commands.append(SPDisplayList(fOverrideMat.revert)) + # meshMatOverride.commands.append(SPEndDisplayList()) + +def findVertIndexInBuffer(loop, buffer, loopDict): + i = 0 + for material_index, vertData in buffer: + for f3dVert in vertData: + if f3dVert == loopDict[loop]: + return i + i += 1 + #print("Can't find " + str(loop)) + return -1 + +def convertVertDictToArray(vertDict): + data = [] + matRegions = {} + for material_index, vertData in vertDict: + start = len(data) + data.extend(vertData) + end = len(data) + matRegions[material_index] = (start, end) + return data, matRegions + +# This collapses similar loops together IF they are in the same material. +def splitSkinnedFacesIntoTwoGroups(skinnedFaces, fModel, obj, uv_layer): + inGroupVertArray = [] + notInGroupVertArray = [] + loopDict = {} + for material_index, skinnedFaceArray in skinnedFaces.items(): + # These MUST be arrays (not dicts) as order is important + inGroupVerts = [] + inGroupVertArray.append([material_index, inGroupVerts]) + + notInGroupVerts = [] + notInGroupVertArray.append([material_index, notInGroupVerts]) + + material = obj.data.materials[material_index] + fMaterial, texDimensions = \ + saveOrGetF3DMaterial(material, fModel, obj) + + exportVertexColors = isLightingDisabled(material) + convertInfo = LoopConvertInfo(uv_layer, obj, exportVertexColors) + for skinnedFace in skinnedFaceArray: + for (face, loop) in skinnedFace.loopsInGroup: + f3dVert = getF3DVert(loop, face, convertInfo, obj.data) + if f3dVert not in inGroupVerts: + inGroupVerts.append(f3dVert) + loopDict[loop] = f3dVert + for (face, loop) in skinnedFace.loopsNotInGroup: + f3dVert = getF3DVert(loop, face, convertInfo, obj.data) + if f3dVert not in notInGroupVerts: + notInGroupVerts.append(f3dVert) + loopDict[loop] = f3dVert + + return inGroupVertArray, notInGroupVertArray, loopDict + +def getGroupVertCount(group): + count = 0 + for material_index, vertData in group: + count += len(vertData) + return count + +def saveSkinnedMeshByMaterial(skinnedFaces, fModel, name, obj, + currentMatrix, parentMatrix, namePrefix, edgeDict): + # We choose one or more loops per vert to represent a material from which + # texDimensions can be found, since it is required for UVs. + uv_layer = obj.data.uv_layers.active.data + inGroupVertArray, notInGroupVertArray, loopDict = \ + splitSkinnedFacesIntoTwoGroups(skinnedFaces, fModel, obj, uv_layer) + + notInGroupCount = getGroupVertCount(notInGroupVertArray) + if notInGroupCount > fModel.f3d.vert_load_size - 2: + raise ValueError("Too many connecting vertices in skinned " +\ + "triangles, max is " + str(fModel.f3d.vert_load_size - 2) + \ + " on parent bone, currently at " + str(notInGroupCount) +\ + ". Note that a vertex with different UVs/normals/materials in " +\ + "connected faces will count more than once. Try " +\ + "keeping UVs contiguous, minimize edge creasing, and don't " +\ + "disable 'Smooth Shading'. " +\ + "Note that Blender's 'Shade Flat/Smooth' does not actually " +\ + "modify normal data.") + + # Load parent group vertices + fSkinnedMesh = FMesh(toAlnum(namePrefix + name) + '_skinned') + + # Load verts into buffer by material. + # It seems like material setup must be done BEFORE triangles are drawn. + # Because of this we cannot share verts between materials (?) + curIndex = 0 + for material_index, vertData in notInGroupVertArray: + material = obj.data.materials[material_index] + fMaterial, texDimensions = fModel.materials[material] + isPointSampled = isTexturePointSampled(material) + exportVertexColors = isLightingDisabled(material) + + skinnedTriList = fSkinnedMesh.tri_list_new() + fSkinnedMesh.draw.commands.append(SPDisplayList(fMaterial.material)) + fSkinnedMesh.draw.commands.append(SPDisplayList(skinnedTriList)) + skinnedTriList.commands.append( + SPVertex(fSkinnedMesh.vertexList, + len(fSkinnedMesh.vertexList.vertices), + len(vertData), curIndex)) + curIndex += len(vertData) + + for f3dVert in vertData: + fSkinnedMesh.vertexList.vertices.append(convertVertexData(obj.data, + f3dVert[0], f3dVert[1], f3dVert[2], texDimensions, + parentMatrix, isPointSampled, exportVertexColors)) + + skinnedTriList.commands.append(SPEndDisplayList()) + + # End skinned mesh vertices. + fSkinnedMesh.draw.commands.append(SPEndDisplayList()) + + fMesh = FMesh(toAlnum(namePrefix + name) + '_mesh') + + # Load current group vertices, then draw commands by material + existingVertData, matRegionDict = \ + convertVertDictToArray(notInGroupVertArray) + for material_index, skinnedFaceArray in skinnedFaces.items(): + + # We've already saved all materials, this just returns the existing ones. + material = obj.data.materials[material_index] + fMaterial, texDimensions = \ + saveOrGetF3DMaterial(material, fModel, obj) + isPointSampled = isTexturePointSampled(material) + exportVertexColors = isLightingDisabled(material) + + triList = fMesh.tri_list_new() + fMesh.draw.commands.append(SPDisplayList(fMaterial.material)) + fMesh.draw.commands.append(SPDisplayList(triList)) + if fMaterial.revert is not None: + fMesh.draw.commands.append(SPDisplayList(fMaterial.revert)) + + convertInfo = LoopConvertInfo(uv_layer, obj, exportVertexColors) + saveTriangleStrip( + [skinnedFace.bFace for skinnedFace in skinnedFaceArray], + convertInfo, triList, fMesh.vertexList, fModel.f3d, + texDimensions, currentMatrix, isPointSampled, exportVertexColors, + copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict), + edgeDict, obj.data) + + return FMeshGroup(toAlnum(namePrefix + name), fMesh, fSkinnedMesh) + +nodeGroupClasses = [ + StartNode, + SwitchNode, + TranslateRotateNode, + TranslateNode, + RotateNode, + DisplayListWithOffsetNode, + BillboardNode, + ShadowNode, + ScaleNode, + StartRenderAreaNode +] \ No newline at end of file diff --git a/fast64_internal/sm64_level_constants.py b/fast64_internal/sm64_level_constants.py new file mode 100644 index 0000000..ff51ad6 --- /dev/null +++ b/fast64_internal/sm64_level_constants.py @@ -0,0 +1,81 @@ +terrain_types = { + 'Normal A' : 0x00, + 'Normal B' : 0x01, + 'Snow' : 0x02, + 'Sand' : 0x03, + 'Haunted House' : 0x04, + 'Water Level' : 0x05, + 'Slippery Slide': 0x06, +} + +default_dialog_ID = { + 'BOB' : 0x00, + 'WF' : 0x1E, + 'BFC' : 0x5A, + 'VC' : 0x81, + 'MC' : 0x82, + 'WC' : 0x83, +} + +L_LOAD_RAW_JUMP0 = 0x00 +L_LOAD_RAW_JUMP1 = 0x01 +L_END = 0x02 +L_DELAY1 = 0x03 +L_DELAY2 = 0x04 +L_JUMP = 0x05 +L_PUSH = 0x06 +L_POP = 0x07 +L_PUSH_16 = 0x08 +L_POP_16 = 0x09 +L_PUSH_00 = 0x0A +L_COND_POP = 0x0B +L_COND_JUMP = 0x0C +L_COND_PUSH = 0x0D +L_COND_SKIP = 0x0E +L_SKIP = 0x0F +L_NOOP = 0x10 +L_SET_ACC_ASM = 0x11 +L_ACTIVE_SET_ACC = 0x12 +L_SET_ACC = 0x13 +L_POOL_PUSH = 0x14 +L_POOL_POP = 0x15 +L_LOAD_ROM_RAM = 0x16 +L_LOAD_ROM_SEG = 0x17 +L_LOAD_MIO0_SEG = 0x18 +L_MARIO_DEMO = 0x19 +L_LOAD_MIO0_TEX = 0x1A +L_LOAD_START = 0x1B +L_MEM_CLEANUP = 0x1C +L_LOAD_END = 0x1D +L_POOL_ALLOC = 0x1E +L_AREA_START = 0x1F +L_AREA_END = 0x20 +L_LOAD_POLY_WO_GEO = 0x21 +L_LOAD_POLY_W_GEO = 0x22 +L_23 = 0x23 +L_PLACE_OBJECT = 0x24 +L_LOAD_MARIO = 0x25 +L_WARP_CONNECT = 0x26 +L_WARP_PAINTING = 0x27 +L_WARP_AREA = 0x28 +L_29 = 0x29 +L_2A = 0x2A +L_SET_DEFAULT_MARIO_POS = 0x2B +L_2C = 0x2C +L_2D = 0x2D +L_LOAD_COLLISION = 0x2E +L_RENDER_AREA = 0x2F +L_SHOW_DIALOG = 0x30 +L_SET_DEFAULT_TERRAIN = 0x31 +L_NOOP2 = 0x32 +L_FADE_OVERLAY = 0x33 +L_BLACKOUT_SCREEN = 0x34 +L_35 = 0x35 +L_SET_MUSIC_SCREEN = 0x36 +L_SET_MUSIC_LEVEL = 0x37 +L_38 = 0x38 +L_PLACE_MACRO_OBJECT = 0x39 +L_3A = 0x3A +L_JET_STREAM = 0x3B +L_3C = 0x3C + diff --git a/fast64_internal/sm64_level_parser.py b/fast64_internal/sm64_level_parser.py new file mode 100644 index 0000000..5717a67 --- /dev/null +++ b/fast64_internal/sm64_level_parser.py @@ -0,0 +1,435 @@ +import bpy +import mathutils +import copy + +from math import pi + +from .sm64_level_constants import * +from .sm64_geolayout_constants import * +from .f3d_parser import * +from .utility import * + +def parseLevelAtPointer(romfile, pointerAddress): + segmentData = parseCommonSegmentLoad(romfile) + + romfile.seek(pointerAddress) + command = romfile.read(16) + segment = command[3] + segmentStart = int.from_bytes(command[4:8], 'big') + segmentEnd = int.from_bytes(command[8:12], 'big') + + segmentData[segment] = (segmentStart, segmentEnd) + + startAddress = decodeSegmentedAddr( + command[12:16], segmentData) + + parsedLevel = parseLevel(romfile, startAddress, segmentData) + for segment, interval in parsedLevel.segmentData.items(): + print("Segment " + format(segment, '#04x') + ': ' + hex(interval[0])\ + + " - " + hex(interval[1])) + + return parsedLevel + +def parseCommonSegmentLoad(romfile): + segmentData = copy.deepcopy(mainLevelLoadScriptSegment) + for segment, pointer in loadSegmentAddresses.items(): + romfile.seek(pointer) + command = romfile.read(12) + + segment = command[3] + segmentStart = int.from_bytes(command[4:8], 'big') + segmentEnd = int.from_bytes(command[8:12], 'big') + + segmentData[segment] = (segmentStart, segmentEnd) + + return segmentData + +# second byte = command length +def parseLevel(romfile, startAddress, segmentData): + currentAddress = startAddress + + romfile.seek(currentAddress) + currentCmd = romfile.read(2) + romfile.seek(currentAddress) # second seek is because reading moves read pointer forward + currentCmd = romfile.read(currentCmd[1]) + #currentAddress += currentCmd[1] + + scriptStack = [currentAddress] + currentLevel = SM64_Level() + currentLevel.segmentData = segmentData + currentArea = currentLevel.nonArea + + # Note that this is only applicable for specific levels, + # accessed through a jump table in the main level script. + while len(scriptStack) > 0 and currentCmd[0] is not L_END: + #print(bytesToHex(currentCmd) + " at " + hex(currentAddress)) + + if currentCmd[0] == L_JUMP: + currentAddress = decodeSegmentedAddr( + currentCmd[4:8], segmentData) + + elif currentCmd[0] == L_PUSH: + scriptStack.append(currentAddress) + #print([hex(value) for value in scriptStack]) + currentAddress = decodeSegmentedAddr( + currentCmd[4:8], segmentData) + + elif currentCmd[0] == L_POP: + currentAddress = scriptStack.pop() + romfile.seek(currentAddress) + currentCmd = romfile.read(2) + romfile.seek(currentAddress) + currentCmd = romfile.read(currentCmd[1]) + currentAddress += currentCmd[1] + #print([hex(value) for value in scriptStack]) + + elif currentCmd[0] == L_NOOP or \ + currentCmd[0] == L_NOOP2: + pass + + elif currentCmd[0] == L_LOAD_ROM_SEG or\ + currentCmd[0] == L_LOAD_MIO0_SEG or\ + currentCmd[0] == L_LOAD_MIO0_TEX: + segmentData[currentCmd[3]] = [ + int.from_bytes(currentCmd[4:8], 'big'), + int.from_bytes(currentCmd[8:12], 'big') + ] + + elif currentCmd[0] == L_AREA_START: + if currentArea is not currentLevel.nonArea: + raise ValueError("Nested areas not supported.") + else: + currentArea = SM64_Area(currentAddress, currentCmd) + + elif currentCmd[0] == L_AREA_END: + currentLevel.areas.append(currentArea) + currentArea = currentLevel.nonArea + + elif currentCmd[0] == L_LOAD_POLY_W_GEO or\ + currentCmd[0] == L_LOAD_POLY_WO_GEO: + polygonData = SM64_Geometry(currentCmd, currentAddress) + currentLevel.geometry.append(polygonData) + + elif currentCmd[0] == L_PLACE_OBJECT: + newObj = SM64_Object(currentCmd, currentAddress) + currentArea.objects.append(newObj) + + elif currentCmd[0] == L_WARP_CONNECT or\ + currentCmd[0] == L_WARP_PAINTING: + warp = SM64_Warp(currentCmd, currentAddress) + currentArea.warps.append(warp) + + elif currentCmd[0] == L_WARP_AREA: + areaWarp = SM64_Area_Warp(currentCmd, currentAddress) + currentArea.areaWarps.append(areaWarp) + + elif currentCmd[0] == L_SET_DEFAULT_MARIO_POS: + marioPos = SM64_MarioStart(currentCmd, currentAddress) + currentLevel.marioStartPosition = marioPos + + elif currentCmd[0] == L_LOAD_COLLISION: + col = SM64_Collider(currentCmd, currentAddress) + currentArea.collider = col + + elif currentCmd[0] == L_SHOW_DIALOG: + dialog = SM64_Dialog_ID(currentCmd, currentAddress) + currentArea.startDialogID = dialog + + elif currentCmd[0] == L_SET_DEFAULT_TERRAIN: + terrain = SM64_Default_Terrain(currentCmd, currentAddress) + currentArea.defaultTerrainType = terrain + + elif currentCmd[0] == L_SET_MUSIC_LEVEL: + music = SM64_Music_Level(currentCmd, currentAddress) + currentArea.music = music + + elif currentCmd[0] == L_PLACE_MACRO_OBJECT: + macroObj = SM64_Macro_Object(currentCmd, currentAddress) + currentArea.macroObjects.append(macroObj) + + elif currentCmd[0] == L_JET_STREAM: + jetStream = SM64_Jet_Stream(currentCmd, currentAddress) + currentArea.jetStreams.append(jetStream) + + else: + print("Unhandled command: " + hex(currentCmd[0])) + + if currentCmd[0] != L_PUSH and currentCmd[0] != L_JUMP and currentCmd[0] != L_POP: + currentAddress += currentCmd[1] + romfile.seek(currentAddress) + currentCmd = romfile.read(2) + romfile.seek(currentAddress) + currentCmd = romfile.read(currentCmd[1]) + #currentAddress += currentCmd[1] + + return currentLevel + +class SM64_Level: + def __init__(self): + self.segmentData = {} + self.geometry = [] + self.areas = [] + self.marioStartPosition = None + self.nonArea = SM64_Area(0, [0x00, 0x00, 0x00, 0x00]) + +class SM64_Area: + def __init__(self, startAddress, command): + self.objects = [] + self.warps = [] + self.areaWarps = [] + self.collider = None + self.macroObjects = [] + self.startDialogID = None + self.music = None + self.defaultTerrainType = None + self.marioStart = None + self.startAddress = startAddress + self.areaNum = command[2] + self.geoAddress = command[4:8] + self.jetStreams = [] + +class SM64_Music_Screen: + def __init__(self, command = None, address = None): + if command is None: + self.seqNum = None + self.params = None + else: + self.seqNum = command[5] + self.params = command[2:5] + self.address = address + + def to_microcode(self): + command = bytearray(8) + command[0] = L_SET_MUSIC_SCREEN + command[1] = 8 + command[2:5] = self.params + command[5] = seqNum + + return command + +class SM64_Music_Level: + def __init__(self, command = None, address = None): + if command is None: + self.seqNum = None + else: + self.seqNum = command[3] + self.address = address + + def to_microcode(self): + command = bytearray(8) + command[0] = L_SET_MUSIC_LEVEL + command[1] = 4 + command[3] = seqNum + + return command + +class SM64_Geometry: + def __init__(self, command = None, address = None): + if command is None: + self.geoCmd = None + self.drawLayer = None + self.modelID = None + self.geoAddress = None + else: + self.geoCmd = command[0] + self.drawLayer = command[2] >> 4 + self.modelID = command[3] + self.geoAddress = command[4:8] + self.address = address + + #print('Level geo: ' + hex(int.from_bytes(self.geoAddress, 'big'))) + def to_microcode(self): + command = bytearray(8) + command[0] = self.geoCmd + command[1] = 8 + command[2] = self.drawLayer << 4 + command[3] = self.modelID + command[4:8] = self.collisionAddress + + return command + +class SM64_Collider: + def __init__(self, command = None, address = None): + if command is None: + self.collisionAddress = None + self.address = None + else: + self.collisionAddress = command[4:8] + self.address = address + def to_microcode(self): + command = bytearray(8) + command[0] = L_LOAD_COLLISION + command[1] = 8 + command[4:8] = self.collisionAddress + + return command + +class SM64_Dialog_ID: + def __init__(self, command = None, address = None): + if command is None: + self.ID = None + self.address = None + else: + self.ID = command[3] + self.address = address + def to_microcode(self): + command = bytearray(8) + command[0] = L_SHOW_DIALOG + command[1] = 4 + command[3] = self.ID + + return command + +class SM64_Default_Terrain: + def __init__(self, command = None, address = None): + if command is None: + self.terrainType = None + self.address = None + else: + self.terrainType = command[3] + self.address = address + def to_microcode(self): + command = bytearray(8) + command[0] = L_SET_DEFAULT_TERRAIN + command[1] = 4 + command[3] = self.terrainType + + return command + +class SM64_Object: + def __init__(self, command = None, address = None): + if command is None: + self.mask = None + self.modelID = None + self.position = [0,0,0] + self.rotation = [0,0,0] + self.behaviourParams = None + self.behaviourAddress = None + else: + self.mask = command[2] + self.modelID = command[3] + self.position = readVectorFromShorts(command, 4) + self.rotation = readEulerVectorFromShorts(command, 10) + self.behaviourParams = command[16:20] + self.behaviourAddress = command[20:24] + self.address = address + + def to_microcode(self): + command = bytearray(24) + command[0] = L_PLACE_OBJECT + command[1] = 0x18 + command[2] = self.mask + command[3] = self.modelID + writeVectorToShorts(command, 4, self.position) + writeEulerVectorToShorts(command, 10, self.rotation) + command[16:20] = self.behaviourParams + command[20:24] = self.behaviourAddress + + return command + +class SM64_Macro_Object: + def __init__(self, command = None, address = None): + if command is None: + self.objPlacementListPointer = None + #self.objectID = None + #self.position = [0,0,0] + else: + self.objPlacementListPointer = command[4:8] + self.address = address + + def to_microcode(self): + command = bytearray(8) + command[0] = L_PLACE_MACRO_OBJECT + command[1] = 8 + command[4:8] = self.objPlacementListPointer + + +class SM64_Jet_Stream: + def __init__(self, command = None, address = None): + if command is None: + self.position = None + self.intensity = 0 + else: + self.position = readVectorFromShorts(command, 4) + self.intensity = readFloatFromShort(command, 10) + self.address = address + + def to_microcode(self, command = None, address = None): + command = bytearray(12) + command[0] = L_JET_STREAM + command[1] = 12 + writeVectorToShorts(command, 4, self.position) + writeFloatToShort(command, 10, self.intensity) + self.address = address + + return command + +class SM64_Warp: + def __init__(self, command = None, address = None): + if command is None: + self.warpCmd = None + self.curWarpID = None + self.destCourseID = None + self.destCourseArea = None + self.destWarpID = None + else: + self.warpCmd = command[0] + self.curWarpID = command[2] + self.destCourseID = command[3] + self.destCourseArea = command[4] + self.destWarpID = command[5] + self.address = address + + def to_microcode(self): + command = bytearray(8) + command[0] = self.warpCmd + command[1] = 8 + command[2] = self.curWarpID + command[3] = self.destCourseID + command[4] = self.destCourseArea + command[5] = self.destWarpID + + return command + +class SM64_Area_Warp: + def __init__(self, command = None, address = None): + if command is None: + self.collisionType = None + self.courseAreaID = None + self.teleportPosition = [0,0,0] + else: + self.collisionType = command[2] + self.courseAreaID = command[3] + self.teleportPosition = readVectorFromShorts(command, 4) + self.address = address + + def to_microcode(self): + command = bytearray(12) + command[0] = L_WARP_AREA + command[1] = 12 + command[2] = self.collisionType + command[3] = self.courseAreaID + writeVectorToShorts(command, 4, self.teleportPosition) + + return command + +class SM64_MarioStart: + def __init__(self, command = None, address = None): + if command is None: + self.areaID = None + self.position = (0,0,0) + self.yRotation = 0 + else: + self.areaID = command[2] + self.yRotation = readEulerFloatFromShort(command, 4) + self.position = readVectorFromShorts(command, 6) + self.address = address + + def to_microcode(self): + command = bytearray(12) + command[0] = L_SET_DEFAULT_MARIO_POS + command[1] = 12 + writeEulerFloatToShort(command, 4, self.yRotation) + writeVectorToShorts(command, 6, self.position) + + return command \ No newline at end of file diff --git a/fast64_internal/sm64_rom_tweaks.py b/fast64_internal/sm64_rom_tweaks.py new file mode 100644 index 0000000..e3c2071 --- /dev/null +++ b/fast64_internal/sm64_rom_tweaks.py @@ -0,0 +1,39 @@ +from .sm64_constants import loadSegmentAddresses + +def ExtendBank0x04(romfile, segmentData, segment4): + + # Extend bank 0x04 + romfile.seek(loadSegmentAddresses[0x04] + 4) + oldStart = int.from_bytes(romfile.read(4), 'big') + romfile.seek(loadSegmentAddresses[0x04] + 4) + romfile.write(int.to_bytes(segment4[0], 4, 'big')) + + romfile.seek(loadSegmentAddresses[0x04] + 8) + oldEnd = int.from_bytes(romfile.read(4), 'big') + romfile.seek(loadSegmentAddresses[0x04] + 8) + romfile.write(int.to_bytes(segment4[1], 4, 'big')) + + romfile.seek(oldStart) + oldData = romfile.read(oldEnd - oldStart) + + if oldEnd - oldStart > segment4[1] - segment4[0]: + print("Not enough space to copy old data.") + raise ValueError("Not enough space to copy old data.") + + romfile.seek(segment4[0]) + romfile.write(oldData) + + segmentData[0x04] = (segment4[0], segment4[1]) + +def DisableLowPolyMario(romfile, geoStartAddress): + # disable low poly mario + romfile.seek(geoStartAddress + 0x42) + romfile.write(bytearray.fromhex('2E 18')) + +def readSegment4(romfile, segmentData): + romfile.seek(loadSegmentAddresses[0x04] + 0x4) + seg4start = int.from_bytes(romfile.read(4), 'big') + romfile.seek(loadSegmentAddresses[0x04] + 0x8) + seg4end = int.from_bytes(romfile.read(4), 'big') + segmentData[0x04] = (seg4start, seg4end) + diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py new file mode 100644 index 0000000..7945e6b --- /dev/null +++ b/fast64_internal/utility.py @@ -0,0 +1,262 @@ +import bpy +from math import pi, ceil, degrees, radians +from mathutils import * +from .sm64_constants import * +from .sm64_geolayout_constants import * +import random +import string + +def getAddressFromRAMAddress(RAMAddress): + addr = RAMAddress - 0x80000000 + if addr < 0: + raise ValueError("Invalid RAM address.") + return addr + +def getObjectQuaternion(obj): + if obj.rotation_mode == 'QUATERNION': + rotation = mathutils.Quaternion(obj.rotation_quaternion) + elif obj.rotation_mode == 'AXIS_ANGLE': + rotation = mathutils.Quaternion(obj.rotation_axis_angle) + else: + rotation = mathutils.Euler( + obj.rotation_euler, obj.rotation_mode).to_quaternion() + return rotation + +def tempName(name): + letters = string.digits + return name + '_temp' + "".join(random.choice(letters) for i in range(10)) + +def prop_split(layout, data, field, name): + split = layout.split(factor = 0.5) + split.label(text = name) + split.prop(data, field, text = '') + +def toAlnum(name): + if name == '': + return None + for i in range(len(name)): + if not name[i].isalnum(): + name = name[:i] + '_' + name[i+1:] + if name[0].isdigit(): + name = '_' + name + return name + +def get64bitAlignedAddr(address): + endNibble = hex(address)[-1] + if endNibble != '0' and endNibble != '8': + address = ceil(address / 8) * 8 + return address + +def getNameFromPath(path): + index = len(path) - 1 + n = path[index] + while n != '/' and n != '\\' and index > 0: + index -= 1 + n = path[index] + if index == 0 and n != '/' and n != '\\': + return toAlnum(path) + else: + return toAlnum(path[index + 1:]) + +def gammaCorrect(color): + return [ + gammaCorrectValue(color[0]), + gammaCorrectValue(color[1]), + gammaCorrectValue(color[2])] + +def gammaCorrectValue(u): + if u < 0.0031308: + y = u * 12.92 + else: + y = 1.055 * pow(u, (1/2.4)) - 0.055 + + return min(max(y, 0), 1) + +def gammaInverse(color): + return [ + gammaInverseValue(color[0]), + gammaInverseValue(color[1]), + gammaInverseValue(color[2])] + +def gammaInverseValue(u): + if u < 0.04045: + y = u / 12.92 + else: + y = ((u + 0.055) / 1.055) ** 2.4 + + return min(max(y, 0), 1) + +def printBlenderMessage(msgSet, message, blenderOp): + if blenderOp is not None: + blenderOp.report(msgSet, message) + else: + print(message) + +def bytesToInt(value): + return int.from_bytes(value, 'big') + +def bytesToHex(value, byteSize = 4): + return format(bytesToInt(value), '#0' + str(byteSize * 2 + 2) + 'x') + +def bytesToHexClean(value, byteSize = 4): + return format(bytesToInt(value), '0' + str(byteSize * 2) + 'x') + +def intToHex(value, byteSize = 4): + return format(value, '#0' + str(byteSize * 2 + 2) + 'x') + +def intToBytes(value, byteSize): + return bytes.fromhex(intToHex(value, byteSize)[2:]) + +# byte input +# returns an integer, usually used for file seeking positions +def decodeSegmentedAddr(address, segmentData): + #print(bytesAsHex(address)) + segmentStart = segmentData[address[0]][0] + return segmentStart + bytesToInt(address[1:4]) + +#int input +# returns bytes, usually used for writing new segmented addresses +def encodeSegmentedAddr(address, segmentData): + segment = getSegment(address, segmentData) + segmentStart = segmentData[segment][0] + + segmentedAddr = address - segmentStart + return intToBytes(segment, 1) + intToBytes(segmentedAddr, 3) + +def getSegment(address, segmentData): + for segment, interval in segmentData.items(): + if address in range(*interval): + return segment + + raise ValueError("Address " + hex(address) + \ + " is not found in any of the provided segments.") + +# Position +def readVectorFromShorts(command, offset): + return [readFloatFromShort(command, valueOffset) for valueOffset + in range(offset, offset + 6, 2)] + +def readFloatFromShort(command, offset): + return int.from_bytes(command[offset: offset + 2], + 'big', signed = True) * sm64ToBlenderScale + +def writeVectorToShorts(command, offset, values): + for i in range(3): + valueOffset = offset + i * 2 + writeFloatToShort(command, valueOffset, values[i]) + +def writeFloatToShort(command, offset, value): + command[offset : offset + 2] = \ + int(round(value / sm64ToBlenderScale)).to_bytes( + 2, 'big', signed = True) + +def convertFloatToShort(value): + return int(round((value / sm64ToBlenderScale))) + +def convertEulerFloatToShort(value): + return int(round(degrees(value))) + +# Rotation + +# Rotation is stored as a short. +# Zero rotation starts at Z+ on an XZ plane and goes counterclockwise. +# 2**16 - 1 is the last value before looping around again. +def readEulerVectorFromShorts(command, offset): + return [readEulerFloatFromShort(command, valueOffset) for valueOffset + in range(offset, offset + 6, 2)] + +def readEulerFloatFromShort(command, offset): + return radians(int.from_bytes(command[offset: offset + 2], + 'big', signed = True)) + +def writeEulerVectorToShorts(command, offset, values): + for i in range(3): + valueOffset = offset + i * 2 + writeEulerFloatToShort(command, valueOffset, values[i]) + +def writeEulerFloatToShort(command, offset, value): + command[offset : offset + 2] = int(round(degrees(value))).to_bytes( + 2, 'big', signed = True) + +# convert 32 bit (8888) to 16 bit (5551) color +def convert32to16bitRGBA(oldPixel): + if oldPixel[3] > 127: + alpha = 1 + else: + alpha = 0 + newPixel = (oldPixel[0] >> 3) << 11 |\ + (oldPixel[1] >> 3) << 6 |\ + (oldPixel[2] >> 3) << 1 |\ + alpha + return newPixel.to_bytes(2, 'big') + +# convert normalized RGB values to bytes (0-255) +def convertRGB(normalizedRGB): + return bytearray([ + int(normalizedRGB[0] * 255), + int(normalizedRGB[1] * 255), + int(normalizedRGB[2] * 255) + ]) +# convert normalized RGB values to bytes (0-255) +def convertRGBA(normalizedRGBA): + return bytearray([ + int(normalizedRGBA[0] * 255), + int(normalizedRGBA[1] * 255), + int(normalizedRGBA[2] * 255), + int(normalizedRGBA[3] * 255) + ]) + +def vector3ComponentMultiply(a, b): + return mathutils.Vector( + (a.x * b.x, a.y * b.y, a.z * b.z) + ) + +# Position values are signed shorts. +def convertPosition(position): + positionShorts = [int(floatValue) for floatValue in position] + F3DPosition = bytearray(0) + for shortData in [shortValue.to_bytes(2, 'big', signed=True) for shortValue in positionShorts]: + F3DPosition.extend(shortData) + return F3DPosition + +# UVs in F3D are a fixed point short: s10.5 (hence the 2**5) +# fixed point is NOT exponent+mantissa, it is integer+fraction +def convertUV(normalizedUVs, textureWidth, textureHeight): + #print(str(normalizedUVs[0]) + " - " + str(normalizedUVs[1])) + F3DUVs = convertFloatToFixed16Bytes(normalizedUVs[0] * textureWidth) +\ + convertFloatToFixed16Bytes(normalizedUVs[1] * textureHeight) + return F3DUVs + +def convertFloatToFixed16Bytes(value): + value *= 2**5 + value = min(max(value, -2**15), 2**15 - 1) + + return int(round(value)).to_bytes(2, 'big', signed = True) + +def convertFloatToFixed16(value): + value *= 2**5 + value = min(max(value, -2**15), 2**15 - 1) + return int.from_bytes( + int(round(value)).to_bytes(2, 'big', signed = True), 'big') + + +# 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 byteMask(data, offset, amount): + return bitMask(data, offset * 8, amount * 8) +def bitMask(data, offset, amount): + return (~(-1 << amount) << offset & data) >> offset + +def read16bitRGBA(data): + r = bitMask(data, 11, 5) / ((2**5) - 1) + g = bitMask(data, 6, 5) / ((2**5) - 1) + b = bitMask(data, 1, 5) / ((2**5) - 1) + a = bitMask(data, 0, 1) / ((2**1) - 1) + + return [r,g,b,a] \ No newline at end of file diff --git a/images/bone_inspector.png b/images/bone_inspector.png new file mode 100644 index 0000000..8cf5399 Binary files /dev/null and b/images/bone_inspector.png differ diff --git a/images/mario.png b/images/mario.png new file mode 100644 index 0000000..ca0f3d6 Binary files /dev/null and b/images/mario.png differ diff --git a/images/mario_running.gif b/images/mario_running.gif new file mode 100644 index 0000000..3cc272a Binary files /dev/null and b/images/mario_running.gif differ diff --git a/images/mat_inspector.png b/images/mat_inspector.png new file mode 100644 index 0000000..39fbeb8 Binary files /dev/null and b/images/mat_inspector.png differ diff --git a/mario.blend b/mario.blend new file mode 100644 index 0000000..2693ce6 Binary files /dev/null and b/mario.blend differ