diff --git a/__init__.py b/__init__.py index a22f345fa..0d482259d 100644 --- a/__init__.py +++ b/__init__.py @@ -22,6 +22,8 @@ from .fast64_internal.oot.props_panel_main import OOT_ObjectProperties from .fast64_internal.utility_anim import utility_anim_register, utility_anim_unregister, ArmatureApplyWithMeshOperator +from .fast64_internal.mk64 import MK64_Properties, mk64_register, mk64_unregister + from .fast64_internal.f3d.f3d_material import ( F3D_MAT_CUR_VERSION, mat_register, @@ -61,9 +63,10 @@ } gameEditorEnum = ( - ("SM64", "SM64", "Super Mario 64"), - ("OOT", "OOT", "Ocarina Of Time"), - ("Homebrew", "Homebrew", "Homebrew"), + ("SM64", "SM64", "Super Mario 64", 0), + ("OOT", "OOT", "Ocarina Of Time", 1), + ("MK64", "MK64", "Mario Kart 64", 3), + ("Homebrew", "Homebrew", "Homebrew", 2), ) @@ -213,6 +216,7 @@ class Fast64_Properties(bpy.types.PropertyGroup): sm64: bpy.props.PointerProperty(type=SM64_Properties, name="SM64 Properties") oot: bpy.props.PointerProperty(type=OOT_Properties, name="OOT Properties") + mk64: bpy.props.PointerProperty(type=MK64_Properties, name="MK64 Properties") settings: bpy.props.PointerProperty(type=Fast64Settings_Properties, name="Fast64 Settings") renderSettings: bpy.props.PointerProperty(type=Fast64RenderSettings_Properties, name="Fast64 Render Settings") @@ -315,6 +319,7 @@ def draw(self, context): def upgrade_changed_props(): """Set scene properties after a scene loads, used for migrating old properties""" SM64_Properties.upgrade_changed_props() + MK64_Properties.upgrade_changed_props() SM64_ObjectProperties.upgrade_changed_props() OOT_ObjectProperties.upgrade_changed_props() for scene in bpy.data.scenes: @@ -352,6 +357,8 @@ def gameEditorUpdate(self, context): self.f3d_type = "F3D" elif self.gameEditorMode == "OOT": self.f3d_type = "F3DEX2/LX2" + elif self.gameEditorMode == "MK64": + self.f3d_type = "F3DEX/LX" # called on add-on enabling @@ -381,6 +388,7 @@ def register(): bsdf_conv_register() sm64_register(True) oot_register(True) + mk64_register(True) repo_settings_operators_register() @@ -427,6 +435,7 @@ def unregister(): f3d_parser_unregister() sm64_unregister(True) oot_unregister(True) + mk64_unregister(True) mat_unregister() bsdf_conv_unregister() bsdf_conv_panel_unregsiter() diff --git a/fast64_internal/f3d/f3d_parser.py b/fast64_internal/f3d/f3d_parser.py index fd74f52f0..56ab40ae7 100644 --- a/fast64_internal/f3d/f3d_parser.py +++ b/fast64_internal/f3d/f3d_parser.py @@ -1527,6 +1527,19 @@ def applyTLUT(self, image, tlut): if invalidIndicesDetected: print("Invalid LUT Indices detected.") + def getVertexDataStart(self, vertexDataParam: str, f3d: F3D): + matchResult = re.search(r"\&?([A-Za-z0-9\_]*)\s*(\[([^\]]*)\])?\s*(\+(.*))?", vertexDataParam) + if matchResult is None: + raise PluginError("SPVertex param " + vertexDataParam + " is malformed.") + + offset = 0 + if matchResult.group(3): + offset += math_eval(matchResult.group(3), f3d) + if matchResult.group(5): + offset += math_eval(matchResult.group(5), f3d) + + return matchResult.group(1), offset + def processCommands(self, dlData: str, dlName: str, dlCommands: "list[ParsedMacro]"): callStack = [F3DParsedCommands(dlName, dlCommands, 0)] while len(callStack) > 0: @@ -1540,7 +1553,7 @@ def processCommands(self, dlData: str, dlName: str, dlCommands: "list[ParsedMacr # print(command.name + " " + str(command.params)) if command.name == "gsSPVertex": - vertexDataName, vertexDataOffset = getVertexDataStart(command.params[0], self.f3d) + vertexDataName, vertexDataOffset = self.getVertexDataStart(command.params[0], self.f3d) parseVertexData(dlData, vertexDataName, self) self.addVertices(command.params[1], command.params[2], vertexDataName, vertexDataOffset) elif command.name == "gsSPMatrix": @@ -1951,20 +1964,6 @@ def parseDLData(dlData: str, dlName: str): return dlCommands -def getVertexDataStart(vertexDataParam: str, f3d: F3D): - matchResult = re.search(r"\&?([A-Za-z0-9\_]*)\s*(\[([^\]]*)\])?\s*(\+(.*))?", vertexDataParam) - if matchResult is None: - raise PluginError("SPVertex param " + vertexDataParam + " is malformed.") - - offset = 0 - if matchResult.group(3): - offset += math_eval(matchResult.group(3), f3d) - if matchResult.group(5): - offset += math_eval(matchResult.group(5), f3d) - - return matchResult.group(1), offset - - def parseVertexData(dlData: str, vertexDataName: str, f3dContext: F3DContext): if vertexDataName in f3dContext.vertexData: return f3dContext.vertexData[vertexDataName] diff --git a/fast64_internal/mk64/README.md b/fast64_internal/mk64/README.md new file mode 100644 index 000000000..4c0b99647 --- /dev/null +++ b/fast64_internal/mk64/README.md @@ -0,0 +1,9 @@ +# Mario Kart 64 + +## Importing Course Display Lists +It's similar to the F3D Importer, you select the file where you have the display list, set the name of the display list you want to import and the path to the decomp. (by default courses are split in segments so it's better to use the cinematic version, usually the last display list in course_data.inc.c). + +Example of configuration: +- d_course_wario_stadium_dl_CA78 +- path/to/mk64/courses/wario_stadium/course_data.inc.c +- path/to/mk64 \ No newline at end of file diff --git a/fast64_internal/mk64/__init__.py b/fast64_internal/mk64/__init__.py new file mode 100644 index 000000000..1f550761d --- /dev/null +++ b/fast64_internal/mk64/__init__.py @@ -0,0 +1,54 @@ +import bpy +from bpy.props import FloatProperty +from bpy.types import PropertyGroup +from bpy.utils import register_class, unregister_class +from .f3d.properties import MK64CourseDLImportSettings, f3d_props_register, f3d_props_unregister +from .f3d.operators import MK64_ImportCourseDL +from .f3d.panels import MK64_ImportCourseDLPanel +from ..render_settings import on_update_render_settings + + +class MK64_Properties(PropertyGroup): + """Global MK64 Scene Properties found under scene.fast64.mk64""" + + # Import Course DL + course_DL_import_settings: bpy.props.PointerProperty(type=MK64CourseDLImportSettings) + scale: FloatProperty(name="F3D Blender Scale", default=100, update=on_update_render_settings) + + @staticmethod + def upgrade_changed_props(): + pass + + +mk64_classes = (MK64_Properties,) + +mk64_panel_classes = ( + MK64_ImportCourseDL, + MK64_ImportCourseDLPanel, +) + + +def mk64_panel_register(): + for cls in mk64_panel_classes: + register_class(cls) + + +def mk64_panel_unregister(): + for cls in mk64_panel_classes: + unregister_class(cls) + + +def mk64_register(registerPanels): + f3d_props_register() + for cls in mk64_classes: + register_class(cls) + if registerPanels: + mk64_panel_register() + + +def mk64_unregister(registerPanel): + for cls in reversed(mk64_classes): + unregister_class(cls) + if registerPanel: + mk64_panel_unregister() + f3d_props_unregister() diff --git a/fast64_internal/mk64/f3d/operators.py b/fast64_internal/mk64/f3d/operators.py new file mode 100644 index 000000000..913eac3ef --- /dev/null +++ b/fast64_internal/mk64/f3d/operators.py @@ -0,0 +1,94 @@ +import bpy +from bpy.types import Operator +from ..mk64_model_classes import MK64F3DContext, parse_course_vtx +from ...f3d.f3d_material import createF3DMat +from ...f3d.f3d_gbi import get_F3D_GBI +from ...f3d.f3d_parser import getImportData, importMeshC +from ...utility import raisePluginError +from .properties import MK64CourseDLImportSettings + + +class MK64_ImportCourseDL(Operator): + # set bl_ properties + bl_idname = "scene.fast64_mk64_course_import_dl" + bl_label = "Import Course DL" + 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 = None + if context.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + + try: + import_settings: MK64CourseDLImportSettings = context.scene.fast64.mk64.course_DL_import_settings + name = import_settings.name + import_path = bpy.path.abspath(import_settings.path) + base_path = bpy.path.abspath(import_settings.base_path) + scale_value = context.scene.fast64.mk64.scale + + remove_doubles = import_settings.remove_doubles + import_normals = import_settings.import_normals + draw_layer = "Opaque" + + paths = [import_path] + + if "course_data" in import_path: + paths += [import_path.replace("course_data", "course_displaylists.inc")] + + paths += [ + import_path.replace("course_data", "course_textures.linkonly").replace( + "course_displaylists.inc", "course_textures.linkonly" + ) + ] + + data = getImportData(paths) + + material = createF3DMat(None) + f3d_mat = material.f3d_mat + f3d_mat.rdp_settings.set_rendermode = import_settings.enable_render_Mode_Default + f3d_mat.combiner1.A = "TEXEL0" + f3d_mat.combiner1.B = "0" + f3d_mat.combiner1.C = "SHADE" + f3d_mat.combiner1.D = "0" + f3d_mat.combiner1.A_alpha = "TEXEL0" + f3d_mat.combiner1.B_alpha = "0" + f3d_mat.combiner1.C_alpha = "SHADE" + f3d_mat.combiner1.D_alpha = "0" + f3d_mat.combiner2.name = "" + f3d_mat.combiner2.A = "TEXEL0" + f3d_mat.combiner2.B = "0" + f3d_mat.combiner2.C = "SHADE" + f3d_mat.combiner2.D = "0" + f3d_mat.combiner2.A_alpha = "TEXEL0" + f3d_mat.combiner2.B_alpha = "0" + f3d_mat.combiner2.C_alpha = "SHADE" + f3d_mat.combiner2.D_alpha = "0" + + f3d_context = MK64F3DContext(get_F3D_GBI(), base_path, material) + if "course_displaylists" in import_path or "course_data" in import_path: + vertex_path = import_path.replace("course_displaylists.inc", "course_vertices.inc").replace( + "course_data", "course_vertices.inc" + ) + print(vertex_path) + f3d_context.vertexData["0x4000000"] = parse_course_vtx(vertex_path, f3d_context.f3d) + + importMeshC( + data, + name, + scale_value, + remove_doubles, + import_normals, + draw_layer, + f3d_context, + ) + + self.report({"INFO"}, "Success!") + return {"FINISHED"} + + except Exception as e: + if context.mode != "OBJECT": + bpy.ops.object.mode_set(mode="OBJECT") + raisePluginError(self, e) + return {"CANCELLED"} # must return a set diff --git a/fast64_internal/mk64/f3d/panels.py b/fast64_internal/mk64/f3d/panels.py new file mode 100644 index 000000000..de8bfabe4 --- /dev/null +++ b/fast64_internal/mk64/f3d/panels.py @@ -0,0 +1,25 @@ +from .properties import MK64CourseDLImportSettings +from .operators import MK64_ImportCourseDL +from ...panels import MK64_Panel +from ...utility import prop_split + + +class MK64_ImportCourseDLPanel(MK64_Panel): + bl_idname = "MK64_PT_import_course_DL" + bl_label = "MK64 Import Course DL" + bl_options = set() # default to open + bl_order = 0 # force to front + + # called every frame + def draw(self, context): + col = self.layout.column() + col.scale_y = 1.1 # extra padding + + col.operator(MK64_ImportCourseDL.bl_idname) + course_DL_import_settings: MK64CourseDLImportSettings = context.scene.fast64.mk64.course_DL_import_settings + course_DL_import_settings.draw_props(col) + prop_split(col, context.scene.fast64.mk64, "scale", "Scale") + + box = col.box().column() + box.label(text="All data must be contained within file.") + box.label(text="The only exception are pngs converted to inc.c.") diff --git a/fast64_internal/mk64/f3d/properties.py b/fast64_internal/mk64/f3d/properties.py new file mode 100644 index 000000000..c01700fd8 --- /dev/null +++ b/fast64_internal/mk64/f3d/properties.py @@ -0,0 +1,38 @@ +from bpy.props import StringProperty, BoolProperty +from bpy.types import PropertyGroup, UILayout +from bpy.utils import register_class, unregister_class +from ...utility import prop_split +from ...f3d.f3d_material import ootEnumDrawLayers + + +class MK64CourseDLImportSettings(PropertyGroup): + name: StringProperty(name="Name") + path: StringProperty(name="Directory", subtype="FILE_PATH") + base_path: StringProperty(name="Directory", subtype="FILE_PATH") + remove_doubles: BoolProperty(name="Remove Doubles", default=True) + import_normals: BoolProperty(name="Import Normals", default=True) + enable_render_Mode_Default: BoolProperty(name="Set Render Mode by Default", default=True) + + def draw_props(self, layout: UILayout): + prop_split(layout, self, "name", "Name") + prop_split(layout, self, "path", "File") + prop_split(layout, self, "base_path", "Base Path") + layout.prop(self, "remove_doubles") + layout.prop(self, "import_normals") + + layout.prop(self, "enable_render_Mode_Default") + + +mk64_dl_writer_classes = [ + MK64CourseDLImportSettings, +] + + +def f3d_props_register(): + for cls in mk64_dl_writer_classes: + register_class(cls) + + +def f3d_props_unregister(): + for cls in reversed(mk64_dl_writer_classes): + unregister_class(cls) diff --git a/fast64_internal/mk64/mk64_model_classes.py b/fast64_internal/mk64/mk64_model_classes.py new file mode 100644 index 000000000..721616cf4 --- /dev/null +++ b/fast64_internal/mk64/mk64_model_classes.py @@ -0,0 +1,56 @@ +import re +from mathutils import Vector +from ..f3d.f3d_gbi import F3D +from ..f3d.f3d_parser import F3DContext, math_eval +from ..f3d.f3d_writer import F3DVert +from ..utility import PluginError, readFile, unpackNormal + + +def course_vertex_format_patterns(): + # position, uv, color/normal + return ( + # decomp format + r"\{\s*" + r"\{+([^,\}]*),([^,\}]*),([^,\}]*)\}\s*,\s*" + r"\{([^,\}]*),([^,\}]*)\}\s*,\s*" + r"\{MACRO_COLOR_FLAG\(([^,\}]*),([^,\}]*),([^,\}]*),([^,\}])*\),([^,\}]*)\}\s*" + r"\}" + ) + + +def parse_course_vtx(path: str, f3d): + data = readFile(path) + pattern = course_vertex_format_patterns() + vertexData = [] + for values in re.findall(pattern, data, re.DOTALL): + values = [math_eval(g, f3d) for g in values] + vertexData.append( + F3DVert( + Vector(values[0:3]), + Vector(values[3:5]), + Vector(values[5:8]), + unpackNormal(values[8]), + values[9], + ) + ) + return vertexData + + +class MK64F3DContext(F3DContext): + def getVertexDataStart(self, vertexDataParam: str, f3d: F3D): + matchResult = re.search(r"\&?([A-Za-z0-9\_]*)\s*(\[([^\]]*)\])?\s*(\+(.*))?", vertexDataParam) + if matchResult is None: + raise PluginError(f"SPVertex param {vertexDataParam} is malformed.") + + offset = 0 + if matchResult.group(3): + offset += math_eval(matchResult.group(3), f3d) + if matchResult.group(5): + offset += math_eval(matchResult.group(5), f3d) + + name = matchResult.group(1) + + if matchResult.group(1).startswith("0x04"): + offset = (int(matchResult.group(1), 16) - 0x04000000) // 16 + name = hex(0x04000000) + return name, offset diff --git a/fast64_internal/panels.py b/fast64_internal/panels.py index 1fe5d1220..a7c986dac 100644 --- a/fast64_internal/panels.py +++ b/fast64_internal/panels.py @@ -47,3 +47,14 @@ class OOT_Panel(bpy.types.Panel): @classmethod def poll(cls, context): return context.scene.gameEditorMode == "OOT" + + +class MK64_Panel(bpy.types.Panel): + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "MK64" + bl_options = {"DEFAULT_CLOSED"} + + @classmethod + def poll(cls, context): + return context.scene.gameEditorMode == "MK64"