From 9c6354d86e6f03c8c1dfcbcd5d545add90129624 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:16:35 +0200 Subject: [PATCH 01/98] new exporter base --- fast64_internal/oot/new_exporter/__init__.py | 1 + fast64_internal/oot/new_exporter/classes.py | 145 +++ fast64_internal/oot/new_exporter/commands.py | 208 ++++ .../oot/new_exporter/io_classes.py | 1053 +++++++++++++++++ fast64_internal/oot/new_exporter/ne_main.py | 42 + fast64_internal/oot/oot_object.py | 28 + fast64_internal/oot/oot_utility.py | 3 +- fast64_internal/oot/scene/operators.py | 24 +- 8 files changed, 1492 insertions(+), 12 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/__init__.py create mode 100644 fast64_internal/oot/new_exporter/classes.py create mode 100644 fast64_internal/oot/new_exporter/commands.py create mode 100644 fast64_internal/oot/new_exporter/io_classes.py create mode 100644 fast64_internal/oot/new_exporter/ne_main.py diff --git a/fast64_internal/oot/new_exporter/__init__.py b/fast64_internal/oot/new_exporter/__init__.py new file mode 100644 index 000000000..13f5e265e --- /dev/null +++ b/fast64_internal/oot/new_exporter/__init__.py @@ -0,0 +1 @@ +from .ne_main import * diff --git a/fast64_internal/oot/new_exporter/classes.py b/fast64_internal/oot/new_exporter/classes.py new file mode 100644 index 000000000..1b8b8497b --- /dev/null +++ b/fast64_internal/oot/new_exporter/classes.py @@ -0,0 +1,145 @@ +import bpy, os + +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from ...f3d.f3d_gbi import DLFormat +from ...utility import PluginError, checkObjectReference, unhideAllAndGetHiddenState, restoreHiddenState, writeCDataSourceOnly +from ..oot_utility import ExportInfo, OOTObjectCategorizer, ootDuplicateHierarchy, ootCleanupScene, getSceneDirFromLevelName, ootGetPath +from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty +from ..room.properties import OOTRoomHeaderProperty +from ..oot_constants import ootData +from ..oot_object import addMissingObjectsToAllRoomHeadersNew + +from .io_classes import ( + OOTRoomAlternate, + OOTRoom, + OOTSceneAlternate, + OOTScene, + OOTExporter, +) + +@dataclass +class OOTSceneExport: + exportInfo: ExportInfo + sceneObj: Object + sceneName: str + ootBlenderScale: float + transform: Matrix + f3dType: str + saveTexturesAsPNG: bool + hackerootBootOption: OOTBootupSceneOptions + dlFormat: DLFormat = DLFormat.Static + + altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) + + def getRoomData(self, scene: OOTScene): + processedRooms = [] + roomList: list[OOTRoom] = [] + roomObjs: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" + ] + + if len(roomObjs) == 0: + raise PluginError("ERROR: The scene has no child empties with the 'Room' empty type.") + + for roomObj in roomObjs: + altProp = roomObj.ootAlternateRoomHeaders + roomIndex = roomObj.ootRoomHeader.roomIndex + roomData = OOTRoom(self.sceneObj, roomObj, self.transform, roomIndex, self.sceneName) + altHeaderData = OOTRoomAlternate() + roomData.header = roomData.getSingleRoomHeader(roomObj.ootRoomHeader) + + for i, header in enumerate(self.altHeaderList, 1): + altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + setattr(altHeaderData, header, roomData.getSingleRoomHeader(altP, i)) + + altHeaderData.cutscene = [ + roomData.getSingleRoomHeader(csHeader, i) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + roomData.alternate = altHeaderData + + if roomIndex in processedRooms: + raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") + + addMissingObjectsToAllRoomHeadersNew(roomObj, roomData, ootData) + processedRooms.append(roomIndex) + roomList.append(roomData) + + return roomList + + def getSceneData(self): + altProp = self.sceneObj.ootAlternateSceneHeaders + sceneData = OOTScene(self.sceneObj, None, self.transform, None, self.sceneName) + altHeaderData = OOTSceneAlternate() + sceneData.header = sceneData.getSingleSceneHeader(self.sceneObj.ootSceneHeader) + + for i, header in enumerate(self.altHeaderList, 1): + altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + setattr(altHeaderData, header, sceneData.getSingleSceneHeader(altP, i)) + + altHeaderData.cutscene = [ + sceneData.getSingleSceneHeader(csHeader, i) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + sceneData.alternate = altHeaderData + sceneData.roomList = self.getRoomData(sceneData) + + sceneData.validateScene() + return sceneData + + def processSceneObj(self): + """Returns the default scene header and adds the alternate/cutscene ones""" + + # init + originalSceneObj = self.sceneObj + if self.sceneObj.type != "EMPTY" or self.sceneObj.ootEmptyType != "Scene": + raise PluginError(f'{self.sceneObj.name} is not an empty with the "Scene" empty type.') + + if bpy.context.scene.exportHiddenGeometry: + hiddenState = unhideAllAndGetHiddenState(bpy.context.scene) + + # Don't remove ignore_render, as we want to reuse this for collision + self.sceneObj, allObjs = ootDuplicateHierarchy(self.sceneObj, None, True, OOTObjectCategorizer()) + + if bpy.context.scene.exportHiddenGeometry: + restoreHiddenState(hiddenState) + + # convert scene + sceneData = None + try: + sceneData = self.getSceneData() + + ootCleanupScene(originalSceneObj, allObjs) + except Exception as e: + ootCleanupScene(originalSceneObj, allObjs) + raise Exception(str(e)) + + if sceneData is None: + raise PluginError("ERROR: 'sceneData' is None!") + + return sceneData + + def exportScene(self): + checkObjectReference(self.sceneObj, "Scene object") + isCustomExport = self.exportInfo.isCustomExportPath + exportPath = self.exportInfo.exportPath + + exportSubdir = "" + if self.exportInfo.customSubPath is not None: + exportSubdir = self.exportInfo.customSubPath + if not isCustomExport and self.exportInfo.customSubPath is None: + exportSubdir = os.path.dirname(getSceneDirFromLevelName(self.sceneName)) + + sceneInclude = exportSubdir + "/" + self.sceneName + "/" + levelPath = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) + + exporter = OOTExporter(self.processSceneObj(), levelPath) + exporter.setSceneData() + exporter.setRoomListData() + exporter.writeScene() diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py new file mode 100644 index 000000000..b30cff19c --- /dev/null +++ b/fast64_internal/oot/new_exporter/commands.py @@ -0,0 +1,208 @@ +from ...utility import CData, indent + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .io_classes import ( + OOTScene, + OOTSceneGeneral, + OOTSceneHeader, + OOTSceneLighting, + OOTSceneCutscene, + OOTRoom, + OOTRoomHeader, + OOTRoomGeneral, + OOTRoomObjects, + OOTRoomActors, + OOTSceneActors, + ) + + +class OOTRoomCommands: + def getEchoSettingsCmd(self, infos: "OOTRoomGeneral"): + return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" + + def getRoomBehaviourCmd(self, infos: "OOTRoomGeneral"): + showInvisibleActors = "true" if infos.showInvisActors else "false" + disableWarpSongs = "true" if infos.disableWarpSongs else "false" + + return ( + (indent + "SCENE_CMD_ROOM_BEHAVIOR(") + + ", ".join([infos.roomBehavior, infos.playerIdleType, showInvisibleActors, disableWarpSongs]) + + ")" + ) + + def getSkyboxDisablesCmd(self, infos: "OOTRoomGeneral"): + disableSkybox = "true" if infos.disableSky else "false" + disableSunMoon = "true" if infos.disableSunMoon else "false" + + return indent + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})" + + def getTimeSettingsCmd(self, infos: "OOTRoomGeneral"): + return indent + f"SCENE_CMD_TIME_SETTINGS({infos.hour}, {infos.minute}, {infos.timeSpeed})" + + def getWindSettingsCmd(self, infos: "OOTRoomGeneral"): + return ( + indent + + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" + ) + + # def getRoomShapeCmd(self, infos: "OOTRoom"): + # return indent + f"SCENE_CMD_ROOM_SHAPE(&{infos.mesh.headerName()})" + + def getObjectListCmd(self, objects: "OOTRoomObjects", headerIndex: int): + return ( + indent + "SCENE_CMD_OBJECT_LIST(" + ) + f"{objects.getObjectLengthDefineName(headerIndex)}, {objects.objectListName(headerIndex)}),\n" + + def getActorListCmd(self, actors: "OOTRoomActors", headerIndex: int): + return ( + indent + "SCENE_CMD_ACTOR_LIST(" + ) + f"{actors.getActorLengthDefineName(headerIndex)}, {actors.actorListName(headerIndex)}),\n" + + def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): + cmdListData = CData() + curHeader = room.getRoomHeader(headerIndex) + listName = f"SceneCmd {curHeader.roomName}_header{headerIndex:02}" + + getCmdFuncList = [ + self.getEchoSettingsCmd, + self.getRoomBehaviourCmd, + self.getSkyboxDisablesCmd, + self.getTimeSettingsCmd, + # self.getRoomShapeCmd, + ] + + roomCmdData = ( + (room.getAltHeaderListCmd(room.alternateHeadersName()) if room.hasAlternateHeaders() else "") + + (",\n".join(getCmd(curHeader.general) for getCmd in getCmdFuncList) + ",\n") + + (self.getWindSettingsCmd(curHeader.general) if curHeader.general.setWind else "") + + (self.getObjectListCmd(curHeader.objects, headerIndex) if len(curHeader.objects.objectList) > 0 else "") + + (self.getActorListCmd(curHeader.actors, headerIndex) if len(curHeader.actors.actorList) > 0 else "") + + room.getEndCmd() + ) + + # .h + cmdListData.header = f"extern {listName}[];\n" + + # .c + cmdListData.source = f"{listName}[]" + " = {\n" + roomCmdData + "};\n\n" + + return cmdListData + + +class OOTSceneCommands: + def getSoundSettingsCmd(self, infos: "OOTSceneGeneral"): + return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" + + def getRoomListCmd(self, scene: "OOTScene"): + return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName()}),\n" + + def getTransActorListCmd(self, scene: "OOTScene", actors: "OOTSceneActors", headerIndex: int): + headerName = scene.getHeaderName(headerIndex) + return ( + indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" + ) + f"{len(actors.transitionActorList)}, {actors.transitionActorListName(headerName)})" + + def getMiscSettingsCmd(self, infos: "OOTSceneGeneral"): + return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" + + # def getColHeaderCmd(self, outScene: OOTScene): + # return indent + f"SCENE_CMD_COL_HEADER(&{outScene.collision.headerName()})" + + def getSpawnListCmd(self, scene: "OOTScene", actors: "OOTSceneActors", headerIndex: int): + name = scene.getHeaderName(headerIndex) + return ( + indent + "SCENE_CMD_ENTRANCE_LIST(" + ) + f"{actors.entranceListName(name) if len(actors.entranceActorList) > 0 else 'NULL'})" + + def getSpecialFilesCmd(self, infos: "OOTSceneGeneral"): + return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" + + # def getPathListCmd(self, outScene: "OOTScene", headerIndex: int): + # return indent + f"SCENE_CMD_PATH_LIST({outScene.pathListName(headerIndex)})" + + def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): + curHeader = scene.getSceneHeader(headerIndex) + headerName = scene.getHeaderName(headerIndex) + startPosName = curHeader.actors.startPositionsName(headerName) + return ( + (indent + "SCENE_CMD_SPAWN_LIST(") + + f"{len(curHeader.actors.entranceActorList)}, " + + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'}),\n" + ) + + def getSkyboxSettingsCmd(self, infos: "OOTSceneGeneral", lights: "OOTSceneLighting"): + return ( + indent + + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" + ) + + def getExitListCmd(self, scene: "OOTScene", headerIndex: int): + curHeader = scene.getSceneHeader(headerIndex) + return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.exitListName(scene.getHeaderName(headerIndex))}),\n" + + def getLightSettingsCmd(self, scene: "OOTScene", lights: "OOTSceneLighting", headerIndex: int): + name = scene.getHeaderName(headerIndex) + return ( + indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" + ) + f"{len(lights.settings)}, {lights.lightListName(name) if len(lights.settings) > 0 else 'NULL'}),\n" + + def getCutsceneDataCmd(self, cs: "OOTSceneCutscene"): + match cs.writeType: + case "Object": + csDataName = cs.csObj.name + case _: + csDataName = cs.csWriteCustom + + return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" + + def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", headerIndex: int): + cmdListData = CData() + # curHeader = scene.getSceneHeader(headerIndex) + listName = f"SceneCmd {scene.getHeaderName(headerIndex)}" + + getCmdFunc1ArgList = [ + # self.getColHeaderCmd, + ] + + getCmdGeneralList = [ + self.getSoundSettingsCmd, + self.getMiscSettingsCmd, + self.getSpecialFilesCmd, + ] + + getCmdActorList = [ + self.getSpawnListCmd, + ] + + if len(curHeader.actors.transitionActorList) > 0: + getCmdActorList.append(self.getTransActorListCmd) + + # if len(outScene.pathList) > 0: + # getCmdFunc2ArgList.append(self.getPathListCmd) + + # if scene.writeCutscene: + # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) + + sceneCmdData = ( + (scene.getAltHeaderListCmd(scene.alternateHeadersName()) if scene.hasAlternateHeaders() else "") + + self.getRoomListCmd(scene) + + self.getSkyboxSettingsCmd(curHeader.general, curHeader.lighting) + # + (",\n".join(getCmd(scene) for getCmd in getCmdFunc1ArgList) + ",\n") + + (",\n".join(getCmd(curHeader.general) for getCmd in getCmdGeneralList) + ",\n") + + (self.getExitListCmd(scene, headerIndex) if len(curHeader.exits.exitList) > 0 else "") + + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + + self.getSpawnActorListCmd(scene, headerIndex) + + self.getLightSettingsCmd(scene, curHeader.lighting, headerIndex) + + (",\n".join(getCmd(scene, curHeader.actors, headerIndex) for getCmd in getCmdActorList) + ",\n") + + scene.getEndCmd() + ) + + # .h + cmdListData.header = f"extern {listName}[]" + ";\n" + + # .c + cmdListData.source = f"{listName}[]" + " = {\n" + sceneCmdData + "};\n\n" + + return cmdListData diff --git a/fast64_internal/oot/new_exporter/io_classes.py b/fast64_internal/oot/new_exporter/io_classes.py new file mode 100644 index 000000000..3a7e1fdee --- /dev/null +++ b/fast64_internal/oot/new_exporter/io_classes.py @@ -0,0 +1,1053 @@ +import os + +from dataclasses import dataclass, field +from math import radians +from mathutils import Quaternion, Matrix +from bpy.types import Object +from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, toAlnum, writeFile, indent +from ..oot_utility import ootConvertTranslation, ootConvertRotation +from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty +from ..room.properties import OOTRoomHeaderProperty +from ..actor.properties import OOTActorProperty +from ..oot_constants import ootData +from .commands import OOTRoomCommands, OOTSceneCommands + + +@dataclass +class Common: + sceneObj: Object + roomObj: Object + transform: Matrix + roomIndex: int + sceneName: str + + def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): + preset = actorProp.headerSettings.sceneSetupPreset + + if preset == "All Scene Setups" or (preset == "All Non-Cutscene Scene Setups" and headerIndex < 4): + return True + + if preset == "Custom": + if actorProp.headerSettings.childDayHeader and headerIndex == 0: + return True + if actorProp.headerSettings.childNightHeader and headerIndex == 1: + return True + if actorProp.headerSettings.adultDayHeader and headerIndex == 2: + return True + if actorProp.headerSettings.adultNightHeader and headerIndex == 3: + return True + + return False + + def getPropValue(self, data, propName: str): + """Returns ``data.propName`` or ``data.propNameCustom``""" + + value = getattr(data, propName) + return value if value != "Custom" else getattr(data, f"{propName}Custom") + + def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, orientation): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + blenderTranslation, blenderRotation, scale = relativeTransform.decompose() + rotation = blenderRotation @ orientation + convertedTranslation = ootConvertTranslation(blenderTranslation) + convertedRotation = ootConvertRotation(rotation) + + return convertedTranslation, convertedRotation, scale, rotation + + def getConvertedTransform(self, transformMatrix, sceneObj, obj, handleOrientation): + # Hacky solution to handle Z-up to Y-up conversion + # We cannot apply rotation to empty, as that modifies scale + if handleOrientation: + orientation = Quaternion((1, 0, 0), radians(90.0)) + else: + orientation = Matrix.Identity(4) + return self.getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) + + def getAltHeaderListCmd(self, altName): + return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" + + def getEndCmd(self): + return indent + "SCENE_CMD_END(),\n" + + +@dataclass +class Actor: + name: str = None + id: str = None + pos: list[int] = field(default_factory=list) + rot: str = None + params: str = None + + def getActorEntry(self): + """Returns a single actor entry""" + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" + rotData = "{ " + self.rot + " }" + + actorInfos = [self.id, posData, rotData, self.params] + infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] + + return ( + indent + + (f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class TransitionActor(Actor): + dontTransition: bool = None + roomFrom: int = None + roomTo: int = None + cameraFront: str = None + cameraBack: str = None + + def getTransitionActorEntry(self): + """Returns a single transition actor entry""" + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] + roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" + posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" + + actorInfos = [roomData, self.id, posData, self.rot, self.params] + infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] + + return ( + (indent + f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class EntranceActor(Actor): + roomIndex: int = None + spawnIndex: int = None + + def getSpawnEntry(self): + """Returns a single spawn entry""" + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" + + +### EXPORT UTILITY ### + +@dataclass +class OOTRoomData: + name: str + roomMain: str = None + roomModel: str = None + roomModelInfo: str = None + + +@dataclass +class OOTSceneData: + sceneMain: str = None + sceneCollision: str = None + + +@dataclass +class OOTExporter: + scene: "OOTScene" + path: str + + header: str = "" + sceneData: OOTSceneData = None + roomList: dict[int, OOTRoomData] = field(default_factory=dict) + csList: dict[int, str] = field(default_factory=dict) + + def setRoomListData(self): + for room in self.scene.roomList: + roomData = OOTRoomData(room.getRoomName()) + roomMainData = room.getRoomData() + + roomData.roomMain = roomMainData.source + self.header += roomMainData.header + + self.roomList[room.roomIndex] = roomData + + def setSceneData(self): + sceneData = OOTSceneData() + sceneMainData = self.scene.getSceneData() + + sceneData.sceneMain = sceneMainData.source + self.header += sceneMainData.header + self.sceneData = sceneData + + def writeScene(self): + scenePath = os.path.join(self.path, self.scene.getSceneName() + ".c") + writeFile(scenePath, self.sceneData.sceneMain) + + for room in self.roomList.values(): + roomPath = os.path.join(self.path, room.name + ".c") + writeFile(roomPath, room.roomMain) + + +### ROOM ### + +@dataclass +class RoomCommon: + roomName: str + +@dataclass +class OOTRoomGeneral: + ### General ### + + index: int + roomShape: str + + ### Behavior ### + + roomBehavior: str + playerIdleType: str + disableWarpSongs: bool + showInvisActors: bool + + ### Skybox And Time ### + + disableSky: bool + disableSunMoon: bool + hour: int + minute: int + timeSpeed: float + echo: str + + ### Wind ### + + setWind: bool + direction: tuple[int, int, int] + strength: int + + +@dataclass +class OOTRoomObjects(RoomCommon): + objectList: list[str] + + def objectListName(self, headerIndex: int): + return f"{self.roomName}_header{headerIndex:02}_objectList" + + def getObjectLengthDefineName(self, headerIndex: int): + return f"LENGTH_{self.objectListName(headerIndex).upper()}" + + def getObjectList(self, headerIndex: int): + objectList = CData() + + listName = f"s16 {self.objectListName(headerIndex)}" + + # .h + objectList.header = f"extern {listName}[];\n" + + # .c + objectList.source = ( + (f"{listName}[{self.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + + ",\n".join(indent + objectID for objectID in self.objectList) + + ",\n};\n\n" + ) + + return objectList + + +@dataclass +class OOTRoomActors(RoomCommon): + actorList: list[Actor] + + def actorListName(self, headerIndex: int): + return f"{self.roomName}_header{headerIndex:02}_actorList" + + def getActorLengthDefineName(self, headerIndex: int): + return f"LENGTH_{self.actorListName(headerIndex).upper()}" + + def getActorListData(self, headerIndex: int): + """Returns the actor list for the current header""" + actorList = CData() + listName = f"ActorEntry {self.actorListName(headerIndex)}" + + # .h + actorList.header = f"extern {listName}[];\n" + + # .c + actorList.source = ( + (f"{listName}[{self.getActorLengthDefineName(headerIndex)}]" + " = {\n") + + "\n".join(actor.getActorEntry() for actor in self.actorList) + + "};\n\n" + ) + + return actorList + + +@dataclass +class OOTRoomAlternate: + childNight: "OOTRoomHeader" = None + adultDay: "OOTRoomHeader" = None + adultNight: "OOTRoomHeader" = None + cutscene: list["OOTRoomHeader"] = field(default_factory=list) + + +@dataclass +class OOTRoomHeader(RoomCommon): + general: OOTRoomGeneral + objects: OOTRoomObjects + actors: OOTRoomActors + + def getHeaderDefines(self, headerIndex: int): + """Returns a string containing defines for actor and object lists lengths""" + headerDefines = "" + + if len(self.objects.objectList) > 0: + name = self.objects.getObjectLengthDefineName(headerIndex) + headerDefines += f"#define {name} {len(self.objects.objectList)}\n" + + if len(self.actors.actorList) > 0: + name = self.actors.getActorLengthDefineName(headerIndex) + headerDefines += f"#define {name} {len(self.actors.actorList)}\n" + + return headerDefines + + +@dataclass +class OOTRoom(Common): + header: OOTRoomHeader = None + alternate: OOTRoomAlternate = None + + def hasAlternateHeaders(self): + return ( + self.alternate is not None and + self.alternate.childNight is not None and + self.alternate.adultDay is not None and + self.alternate.adultNight is not None and + len(self.alternate.cutscene) > 0 + ) + + def getRoomHeader(self, headerIndex: int) -> OOTRoomHeader | None: + if headerIndex == 0: + return self.header + + for i, header in enumerate(self.altHeaderList, 1): + if headerIndex == i: + return getattr(self.alternate, header) + + for i, csHeader in enumerate(self.alternate.cutscene, 4): + if headerIndex == i: + return csHeader + + return None + + def getActorList(self, headerIndex: int): + actorList: list[Actor] = [] + actorObjList: list[Object] = [ + obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" + ] + for obj in actorObjList: + actorProp = obj.ootActorProperty + if not self.isCurrentHeaderValid(actorProp, headerIndex): + continue + + # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for + # the total number of actors defined in the XML. If the user deletes one, this will prevent + # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty + # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if + # the current Actor has the ID `None` to avoid export issues. + if actorProp.actorID != "None": + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + actor = Actor() + + if actorProp.actorID == "Custom": + actor.id = actorProp.actorIDCustom + else: + actor.id = actorProp.actorID + + if actorProp.rotOverride: + actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) + else: + actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + + actor.name = ( + ootData.actorData.actorsByID[actorProp.actorID].name.replace( + f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" + ) + if actorProp.actorID != "Custom" + else "Custom Actor" + ) + + actor.pos = pos + actor.params = actorProp.actorParam + actorList.append(actor) + return actorList + + def getSingleRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0): + """Returns a single room header with the informations from the scene empty object""" + + objIDList = [] + for objProp in headerProp.objectList: + if objProp.objectKey == "Custom": + objIDList.append(objProp.objectIDCustom) + else: + objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) + + return OOTRoomHeader( + self.getRoomName(), + OOTRoomGeneral( + headerProp.roomIndex, + headerProp.roomShape, + self.getPropValue(headerProp, "roomBehaviour"), + self.getPropValue(headerProp, "linkIdleMode"), + headerProp.disableWarpSongs, + headerProp.showInvisibleActors, + headerProp.disableSkybox, + headerProp.disableSunMoon, + 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeHours, + 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeMinutes, + max(-128, min(127, round(headerProp.timeSpeed * 0xA))), + headerProp.echo, + headerProp.setWind, + [d for d in headerProp.windVector] if headerProp.setWind else None, + headerProp.windStrength if headerProp.setWind else None + ), + OOTRoomObjects(self.getRoomName(), objIDList), + OOTRoomActors( + self.getRoomName(), + self.getActorList(headerIndex), + ) + ) + + # Export + + def getRoomName(self): + return f"{toAlnum(self.sceneName)}_room_{self.roomIndex}" + + def alternateHeadersName(self): + return f"{self.getRoomName()}_alternateHeaders" + + def getRoomData(self): + cmdExport = OOTRoomCommands() + roomC = CData() + + roomHeaders: list[tuple[OOTRoomHeader, str]] = [ + (self.alternate.childNight, "Child Night"), + (self.alternate.adultDay, "Adult Day"), + (self.alternate.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.alternate.cutscene): + roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrListName = f"SceneCmd* {self.alternateHeadersName()}" + + # .h + roomC.header = f"extern {altHeaderPtrListName}[];\n" + + # .c + altHeaderPtrList = ( + f"{altHeaderPtrListName}[]" + + " = {\n" + + "\n".join( + indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," + for i, (curHeader, headerDesc) in enumerate(roomHeaders, 1) + ) + + "\n};\n\n" + ) + + roomHeaders.insert(0, (self.header, "Child Day (Default)")) + for i, (curHeader, headerDesc) in enumerate(roomHeaders): + if curHeader is not None: + roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" + roomC.source += curHeader.getHeaderDefines(i) + roomC.append(cmdExport.getRoomCommandList(self, i)) + + if i == 0 and self.hasAlternateHeaders(): + roomC.source += altHeaderPtrList + + if len(curHeader.objects.objectList) > 0: + roomC.append(curHeader.objects.getObjectList(i)) + + if len(curHeader.actors.actorList) > 0: + roomC.append(curHeader.actors.getActorListData(i)) + + return roomC + + +### SCENE ### + +@dataclass +class SceneCommon: + sceneName: str + + +@dataclass +class EnvLightSettings: + envLightMode: str + ambientColor: tuple[int, int, int] + light1Color: tuple[int, int, int] + light1Dir: tuple[int, int, int] + light2Color: tuple[int, int, int] + light2Dir: tuple[int, int, int] + fogColor: tuple[int, int, int] + fogNear: int + zFar: int + blendRate: int + + def getBlendFogNear(self): + return f"(({self.blendRate} << 10) | {self.fogNear})" + + def getColorValues(self, vector: tuple[int, int, int]): + return ", ".join(f"{v:5}" for v in vector) + + def getDirectionValues(self, vector: tuple[int, int, int]): + return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) + + def getLightSettingsEntry(self, index: int): + isLightingCustom = self.envLightMode == "Custom" + + vectors = [ + (self.ambientColor, "Ambient Color", self.getColorValues), + (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), + (self.light1Color, "Diffuse0 Color", self.getColorValues), + (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), + (self.light2Color, "Diffuse1 Color", self.getColorValues), + (self.fogColor, "Fog Color", self.getColorValues), + ] + + fogData = [ + (self.getBlendFogNear(), "Blend Rate & Fog Near"), + (f"{self.zFar}", "Fog Far"), + ] + + lightDescs = ["Dawn", "Day", "Dusk", "Night"] + + if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": + # @TODO: Improve the lighting system. + # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. + # This is not accurate and more complicated, + # for now we are doing ``index % 4`` to avoid having an OoB read in the list + # but this will need to be changed the day the lighting system is updated. + lightDesc = f"// {lightDescs[index % 4]} Lighting\n" + else: + isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" + lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" + + lightData = ( + (indent + lightDesc) + + (indent + "{\n") + + "".join(indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors) + + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) + + (indent + "},\n") + ) + + return lightData + + +@dataclass +class OOTSceneGeneral: + ### General ### + + keepObjectID: str + naviHintType: str + drawConfig: str + appendNullEntrance: bool + useDummyRoomList: bool + + ### Skybox And Sound ### + + # Skybox + skyboxID: str + skyboxConfig: str + + # Sound + sequenceID: str + ambienceID: str + specID: str + + ### Camera And World Map ### + + # World Map + worldMapLocation: str + + # Camera + sceneCamType: str + + +@dataclass +class OOTSceneLighting: + envLightMode: str + settings: list[EnvLightSettings] + + def lightListName(self, headerName: str): + return f"{headerName}_lightSettings" + + def getLightSettings(self, headerName: str): + lightSettingsData = CData() + lightName = f"EnvLightSettings {self.lightListName(headerName)}[{len(self.settings)}]" + + # .h + lightSettingsData.header = f"extern {lightName};\n" + + # .c + lightSettingsData.source = ( + (lightName + " = {\n") + + "".join( + light.getLightSettingsEntry(i) + for i, light in enumerate(self.settings) + ) + + "};\n\n" + ) + + return lightSettingsData + + +@dataclass +class OOTSceneCutscene: + writeType: str + writeCutscene: bool + csObj: Object + csWriteCustom: str + extraCutscenes: list[Object] + + +@dataclass +class OOTSceneExits: + exitList: list[tuple[int, str]] + + def exitListName(self, headerName: str): + return f"{headerName}_exitList" + + def getExitListData(self, headerName: str): + exitList = CData() + listName = f"u16 {self.exitListName(headerName)}[{len(self.exitList)}]" + + # .h + exitList.header = f"extern {listName};\n" + + # .c + exitList.source = ( + (listName + " = {\n") + # @TODO: use the enum name instead of the raw index + + "\n".join(indent + f"{value}," for (_, value) in self.exitList) + + "\n};\n\n" + ) + + return exitList + + +@dataclass +class OOTSceneActors: + transitionActorList: list[TransitionActor] + entranceActorList: list[EntranceActor] + + def entranceListName(self, headerName: str): + return f"{headerName}_entranceList" + + def startPositionsName(self, headerName: str): + return f"{headerName}_playerEntryList" + + def transitionActorListName(self, headerName: str): + return f"{headerName}_transitionActors" + + def getSpawnActorList(self, headerName: str): + """Returns the spawn actor list for the current header""" + spawnActorList = CData() + listName = f"ActorEntry {self.startPositionsName(headerName)}" + + # .h + spawnActorList.header = f"extern {listName}[];\n" + + # .c + spawnActorList.source = ( + (f"{listName}[]" + " = {\n") + + "".join(entrance.getActorEntry() for entrance in self.entranceActorList) + + "};\n\n" + ) + + return spawnActorList + + def getSpawnList(self, headerName: str): + """Returns the spawn list for the current header""" + spawnList = CData() + listName = f"Spawn {self.entranceListName(headerName)}" + + # .h + spawnList.header = f"extern {listName}[];\n" + + # .c + spawnList.source = ( + (f"{listName}[]" + " = {\n") + + (indent + "// { Spawn Actor List Index, Room Index }\n") + + "".join(entrance.getSpawnEntry() for entrance in self.entranceActorList) + + "};\n\n" + ) + + return spawnList + + def getTransitionActorListData(self, headerName: str): + """Returns the transition actor list for the current header""" + transActorList = CData() + listName = f"TransitionActorEntry {self.transitionActorListName(headerName)}" + + # .h + transActorList.header = f"extern {listName}[];\n" + + # .c + transActorList.source = ( + (f"{listName}[]" + " = {\n") + + "\n".join(transActor.getTransitionActorEntry() for transActor in self.transitionActorList) + + "};\n\n" + ) + + return transActorList + + +@dataclass +class OOTSceneAlternate: + childNight: "OOTSceneHeader" = None + adultDay: "OOTSceneHeader" = None + adultNight: "OOTSceneHeader" = None + cutscene: list["OOTSceneHeader"] = field(default_factory=list) + + +@dataclass +class OOTSceneHeader: + general: OOTSceneGeneral + lighting: OOTSceneLighting + cutscene: OOTSceneCutscene + exits: OOTSceneExits + actors: OOTSceneActors + + def getHeaderData(self, headerName: str): + headerData = CData() + + # Write the spawn position list data and the entrance list + if len(self.actors.entranceActorList) > 0: + headerData.append(self.actors.getSpawnActorList(headerName)) + headerData.append(self.actors.getSpawnList(headerName)) + + # Write the transition actor list data + if len(self.actors.transitionActorList) > 0: + headerData.append(self.actors.getTransitionActorListData(headerName)) + + # Write the exit list + if len(self.exits.exitList) > 0: + headerData.append(self.exits.getExitListData(headerName)) + + # Write the light data + if len(self.lighting.settings) > 0: + headerData.append(self.lighting.getLightSettings(headerName)) + + # Write the path data, if used + # if len(self.pathList) > 0: + # headerData.append(getPathData(header, headerIndex)) + + return headerData + + +@dataclass +class OOTScene(Common): + header: OOTSceneHeader = None + alternate: OOTSceneAlternate = None + roomList: list[OOTRoom] = field(default_factory=list) + + altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) + + def validateRoomIndices(self): + for i, room in enumerate(self.roomList): + if i != room.roomIndex: + return False + + return True + + def validateScene(self): + if not len(self.roomList) > 0: + raise PluginError("ERROR: This scene does not have any rooms!") + + if not self.validateRoomIndices(): + raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") + + def hasAlternateHeaders(self): + return ( + self.alternate is not None and + self.alternate.childNight is not None and + self.alternate.adultDay is not None and + self.alternate.adultNight is not None and + len(self.alternate.cutscene) > 0 + ) + + def getSceneHeader(self, headerIndex: int) -> OOTSceneHeader | None: + if headerIndex == 0: + return self.header + + for i, header in enumerate(self.altHeaderList, 1): + if headerIndex == i: + return getattr(self.alternate, header) + + for i, csHeader in enumerate(self.alternate.cutscene, 4): + if headerIndex == i: + return csHeader + + return None + + def getExitList(self, headerProp: OOTSceneHeaderProperty): + """Returns the exit list and performs safety checks""" + + exitList: list[tuple[int, str]] = [] + + for i, exitProp in enumerate(headerProp.exitList): + if exitProp.exitIndex != "Custom": + raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") + + exitList.append((i, exitProp.exitIndexCustom)) + + return exitList + + def getRoomObject(self, object: Object) -> Object | None: + # Note: temporary solution until PRs #243 & #255 are merged + for obj in self.sceneObj.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": + for o in obj.children_recursive: + if o == object: + return obj + return None + + def getTransitionActorList(self, headerIndex: int): + actorList: list[TransitionActor] = [] + actorObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" + ] + for obj in actorObjList: + roomObj = self.getRoomObject(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + self.roomIndex = roomObj.ootRoomHeader.roomIndex + + transActorProp = obj.ootTransitionActorProperty + + if not self.isCurrentHeaderValid(transActorProp.actor, headerIndex): + continue + + if transActorProp.actor.actorID != "None": + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + transActor = TransitionActor() + + if transActorProp.dontTransition: + front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) + back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + else: + front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + + if transActorProp.actor.actorID == "Custom": + transActor.id = transActorProp.actor.actorIDCustom + else: + transActor.id = transActorProp.actor.actorID + + transActor.name = ( + ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( + f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if transActorProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + transActor.pos = pos + transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? + transActor.params = transActorProp.actor.actorParam + transActor.roomFrom, transActor.cameraFront = front + transActor.roomTo, transActor.cameraBack = back + actorList.append(transActor) + return actorList + + def getEntranceActorList(self, headerIndex: int): + actorList: list[EntranceActor] = [] + actorObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" + ] + for obj in actorObjList: + roomObj = self.getRoomObject(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + + entranceProp = obj.ootEntranceProperty + if not self.isCurrentHeaderValid(entranceProp.actor, headerIndex): + continue + + if entranceProp.actor.actorID != "None": + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + entranceActor = EntranceActor() + + entranceActor.name = ( + ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( + f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if entranceProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom + entranceActor.pos = pos + entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + entranceActor.params = entranceProp.actor.actorParam + entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex + entranceActor.spawnIndex = entranceProp.spawnIndex + actorList.append(entranceActor) + return actorList + + def getSingleSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int=0): + """Returns a single scene header with the informations from the scene empty object""" + + if headerProp.csWriteType == "Embedded": + raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") + + lightMode = self.getPropValue(headerProp, "skyboxLighting") + lightList: list[OOTLightProperty] = [] + lightSettings: list[EnvLightSettings] = [] + + if lightMode == "LIGHT_MODE_TIME": + todLights = headerProp.timeOfDayLights + lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] + else: + lightList = headerProp.lightList + + for lightProp in lightList: + light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) + light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) + lightSettings.append( + EnvLightSettings( + lightMode, + exportColor(lightProp.ambient), + light1[0], + light1[1], + light2[0], + light2[1], + exportColor(lightProp.fogColor), + lightProp.fogNear, + lightProp.fogFar, + lightProp.transitionSpeed + ) + ) + + return OOTSceneHeader( + OOTSceneGeneral( + self.getPropValue(headerProp, "globalObject"), + self.getPropValue(headerProp, "naviCup"), + self.getPropValue(headerProp.sceneTableEntry, "drawConfig"), + headerProp.appendNullEntrance, + self.sceneObj.fast64.oot.scene.write_dummy_room_list, + self.getPropValue(headerProp, "skyboxID"), + self.getPropValue(headerProp, "skyboxCloudiness"), + self.getPropValue(headerProp, "musicSeq"), + self.getPropValue(headerProp, "nightSeq"), + self.getPropValue(headerProp, "audioSessionPreset"), + self.getPropValue(headerProp, "mapLocation"), + self.getPropValue(headerProp, "cameraMode") + ), + OOTSceneLighting( + lightMode, + lightSettings, + ), + OOTSceneCutscene( + headerProp.csWriteType, + headerProp.writeCutscene, + headerProp.csWriteObject, + headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, + [csObj for csObj in headerProp.extraCutscenes] + ), + OOTSceneExits(self.getExitList(headerProp)), + OOTSceneActors( + self.getTransitionActorList(headerIndex), + self.getEntranceActorList(headerIndex) + ) + ) + + # Export + + def getSceneName(self): + return f"{toAlnum(self.sceneName)}_scene" + + def getHeaderName(self, headerIndex: int): + return f"{self.getSceneName()}_header{headerIndex:02}" + + def alternateHeadersName(self): + return f"{self.getSceneName()}_alternateHeaders" + + def roomListName(self): + return f"{self.getSceneName()}_roomList" + + def getRoomList(self): + roomList = CData() + listName = f"RomFile {self.roomListName()}[]" + + # generating segment rom names for every room + segNames = [] + for i in range(len(self.roomList)): + roomName = self.roomList[i].getRoomName() + segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) + + # .h + roomList.header += f"extern {listName};\n" + + if not self.header.general.useDummyRoomList: + # Write externs for rom segments + roomList.header += "".join( + f"extern u8 {startName}[];\n" + f"extern u8 {stopName}[];\n" for startName, stopName in segNames + ) + + # .c + roomList.source = listName + " = {\n" + + if self.header.general.useDummyRoomList: + roomList.source = ( + "// Dummy room list\n" + roomList.source + ((indent + "{ NULL, NULL },\n") * len(self.roomList)) + ) + else: + roomList.source += ( + " },\n".join( + indent + "{ " + f"(uintptr_t){startName}, (uintptr_t){stopName}" + for startName, stopName in segNames + ) + + " },\n" + ) + + roomList.source += "};\n\n" + return roomList + + def getSceneData(self): + cmdExport = OOTSceneCommands() + sceneC = CData() + + headers: list[tuple[OOTSceneHeader, str]] = [ + (self.alternate.childNight, "Child Night"), + (self.alternate.adultDay, "Adult Day"), + (self.alternate.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.alternate.cutscene): + headers.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrs = "\n".join( + indent + self.getHeaderName(i) + "," + if curHeader is not None + else indent + "NULL," + if i < 4 + else "" + for i, (curHeader, _) in enumerate(headers, 1) + ) + + headers.insert(0, (self.header, "Child Day (Default)")) + for i, (curHeader, headerDesc) in enumerate(headers): + if curHeader is not None: + sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" + sceneC.append(cmdExport.getSceneCommandList(self, curHeader, i)) + + if i == 0: + if self.hasAlternateHeaders(): + altHeaderListName = f"SceneCmd* {self.alternateHeadersName()}[]" + sceneC.header += f"extern {altHeaderListName};\n" + sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" + + # Write the room segment list + sceneC.append(self.getRoomList()) + + sceneC.append(curHeader.getHeaderData(self.getHeaderName(i))) + + return sceneC diff --git a/fast64_internal/oot/new_exporter/ne_main.py b/fast64_internal/oot/new_exporter/ne_main.py new file mode 100644 index 000000000..d5c9d47ec --- /dev/null +++ b/fast64_internal/oot/new_exporter/ne_main.py @@ -0,0 +1,42 @@ +import bpy + +from mathutils import Matrix, Vector +from bpy.ops import object +from bpy.types import Object +from ...utility import PluginError +from ..oot_utility import ExportInfo, sceneNameFromID +from .classes import OOTSceneExport + + +def getSceneObject() -> Object: + """Returns the selected OoT scene empty object to export""" + if bpy.context.mode != "OBJECT": + object.mode_set(mode="OBJECT") + + sceneObj = bpy.context.scene.ootSceneExportObj + + if sceneObj is None: + raise PluginError("Scene object input not set.") + elif sceneObj.type != "EMPTY" or sceneObj.ootEmptyType != "Scene": + raise PluginError("The input object is not an empty with the Scene type.") + + return sceneObj + + +def exportScene(exportInfo: ExportInfo): + """Returns the initialised scene exporter""" + + ootBlenderScale = bpy.context.scene.ootBlenderScale + bootOptions = bpy.context.scene.fast64.oot.bootupSceneOptions + hackerFeaturesEnabled = bpy.context.scene.fast64.oot.hackerFeaturesEnabled + + return OOTSceneExport( + exportInfo, + getSceneObject(), + exportInfo.name, + ootBlenderScale, + Matrix.Diagonal(Vector((ootBlenderScale, ootBlenderScale, ootBlenderScale))).to_4x4(), + bpy.context.scene.f3d_type, + bpy.context.scene.saveTextures, + bootOptions if hackerFeaturesEnabled else None + ) diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 4417d26a4..1a3b57e63 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -39,3 +39,31 @@ def addMissingObjectsToAllRoomHeaders(roomObj: Object, room: OOTRoom, ootData: O addMissingObjectsToRoomHeader(roomObj, layer, ootData, i) for i in range(len(room.cutsceneHeaders)): addMissingObjectsToRoomHeader(roomObj, room.cutsceneHeaders[i], ootData, i + 4) + + +def addMissingObjectsToRoomHeaderNew(roomObj: Object, room, ootData: OoT_Data, headerIndex: int): + """Adds missing objects to the object list""" + curHeader = room.getRoomHeader(headerIndex) + if len(curHeader.actors.actorList) > 0: + for roomActor in curHeader.actors.actorList: + actor = ootData.actorData.actorsByID.get(roomActor.actorID) + if actor is not None and actor.key != "player" and len(actor.tiedObjects) > 0: + for objKey in actor.tiedObjects: + if objKey not in ["obj_gameplay_keep", "obj_gameplay_field_keep", "obj_gameplay_dangeon_keep"]: + objID = ootData.objectData.objectsByKey[objKey].id + if not (objID in curHeader.objects.objectList): + curHeader.objects.objectList.append(objID) + addMissingObjectToProp(roomObj, headerIndex, objKey) + + +def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room, ootData: OoT_Data): + """ + Adds missing objects (required by actors) to all headers of a room, + both to the roomObj empty and the exported room + """ + sceneLayers = [room, room.alternate.childNight, room.alternate.adultDay, room.alternate.adultNight] + for i, layer in enumerate(sceneLayers): + if layer is not None: + addMissingObjectsToRoomHeaderNew(roomObj, layer, ootData, i) + for i in range(len(room.alternate.cutscene)): + addMissingObjectsToRoomHeaderNew(roomObj, room.cutsceneHeaders[i], ootData, i + 4) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 1d7ca39d4..815f4d4f6 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -1,6 +1,7 @@ import bpy, math, os, re from ast import parse, Expression, Num, UnaryOp, USub, Invert, BinOp from bpy.utils import register_class, unregister_class +from bpy.types import Object from typing import Callable from .oot_constants import ootSceneIDToName @@ -240,7 +241,7 @@ def sortObjects(self, allObjs): # This also sets all origins relative to the scene object. -def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer): +def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer) -> tuple[Object, list[Object]]: # Duplicate objects to apply scale / modifiers / linked data bpy.ops.object.select_all(action="DESELECT") ootSelectMeshChildrenOnly(obj, includeEmpties) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 0c28b72f3..331e5b8bb 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -12,6 +12,7 @@ from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles +from ..new_exporter import exportScene def ootRemoveSceneC(exportInfo): @@ -175,17 +176,18 @@ def execute(self, context): bootOptions = context.scene.fast64.oot.bootupSceneOptions hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled - ootExportSceneToC( - obj, - finalTransform, - context.scene.f3d_type, - context.scene.isHWv1, - levelName, - DLFormat.Static, - context.scene.saveTextures, - exportInfo, - bootOptions if hackerFeaturesEnabled else None, - ) + exportScene(exportInfo).exportScene() + # ootExportSceneToC( + # obj, + # finalTransform, + # context.scene.f3d_type, + # context.scene.isHWv1, + # levelName, + # DLFormat.Static, + # context.scene.saveTextures, + # exportInfo, + # bootOptions if hackerFeaturesEnabled else None, + # ) self.report({"INFO"}, "Success!") From 017f0915933eff9efc6b8a697dbdec157d4bb6ec Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sat, 23 Sep 2023 23:42:05 +0200 Subject: [PATCH 02/98] cleanup --- fast64_internal/oot/new_exporter/classes.py | 19 +- fast64_internal/oot/new_exporter/commands.py | 41 +-- .../oot/new_exporter/io_classes.py | 333 +++++++++--------- fast64_internal/oot/oot_object.py | 2 +- 4 files changed, 198 insertions(+), 197 deletions(-) diff --git a/fast64_internal/oot/new_exporter/classes.py b/fast64_internal/oot/new_exporter/classes.py index 1b8b8497b..ec439311e 100644 --- a/fast64_internal/oot/new_exporter/classes.py +++ b/fast64_internal/oot/new_exporter/classes.py @@ -4,7 +4,7 @@ from mathutils import Matrix from bpy.types import Object from ...f3d.f3d_gbi import DLFormat -from ...utility import PluginError, checkObjectReference, unhideAllAndGetHiddenState, restoreHiddenState, writeCDataSourceOnly +from ...utility import PluginError, checkObjectReference, unhideAllAndGetHiddenState, restoreHiddenState, toAlnum from ..oot_utility import ExportInfo, OOTObjectCategorizer, ootDuplicateHierarchy, ootCleanupScene, getSceneDirFromLevelName, ootGetPath from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty from ..room.properties import OOTRoomHeaderProperty @@ -46,17 +46,18 @@ def getRoomData(self, scene: OOTScene): for roomObj in roomObjs: altProp = roomObj.ootAlternateRoomHeaders roomIndex = roomObj.ootRoomHeader.roomIndex - roomData = OOTRoom(self.sceneObj, roomObj, self.transform, roomIndex, self.sceneName) + roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" + roomData = OOTRoom(self.sceneObj, roomObj, self.transform, roomIndex, self.sceneName, name=roomName) altHeaderData = OOTRoomAlternate() - roomData.header = roomData.getSingleRoomHeader(roomObj.ootRoomHeader) + roomData.header = roomData.getNewRoomHeader(roomObj.ootRoomHeader) for i, header in enumerate(self.altHeaderList, 1): altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: - setattr(altHeaderData, header, roomData.getSingleRoomHeader(altP, i)) + setattr(altHeaderData, header, roomData.getNewRoomHeader(altP, i)) altHeaderData.cutscene = [ - roomData.getSingleRoomHeader(csHeader, i) + roomData.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] @@ -73,17 +74,17 @@ def getRoomData(self, scene: OOTScene): def getSceneData(self): altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = OOTScene(self.sceneObj, None, self.transform, None, self.sceneName) + sceneData = OOTScene(self.sceneObj, None, self.transform, None, f"{toAlnum(self.sceneName)}_scene") altHeaderData = OOTSceneAlternate() - sceneData.header = sceneData.getSingleSceneHeader(self.sceneObj.ootSceneHeader) + sceneData.header = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) for i, header in enumerate(self.altHeaderList, 1): altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: - setattr(altHeaderData, header, sceneData.getSingleSceneHeader(altP, i)) + setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) altHeaderData.cutscene = [ - sceneData.getSingleSceneHeader(csHeader, i) + sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index b30cff19c..1484f994c 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -58,11 +58,11 @@ def getObjectListCmd(self, objects: "OOTRoomObjects", headerIndex: int): def getActorListCmd(self, actors: "OOTRoomActors", headerIndex: int): return ( indent + "SCENE_CMD_ACTOR_LIST(" - ) + f"{actors.getActorLengthDefineName(headerIndex)}, {actors.actorListName(headerIndex)}),\n" + ) + f"{actors.getActorLengthDefineName()}, {actors.actorListName()}),\n" def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): cmdListData = CData() - curHeader = room.getRoomHeader(headerIndex) + curHeader = room.getRoomHeaderFromIndex(headerIndex) listName = f"SceneCmd {curHeader.roomName}_header{headerIndex:02}" getCmdFuncList = [ @@ -74,7 +74,7 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): ] roomCmdData = ( - (room.getAltHeaderListCmd(room.alternateHeadersName()) if room.hasAlternateHeaders() else "") + (room.getAltHeaderListCmd(room.altHeadersName) if room.hasAlternateHeaders() else "") + (",\n".join(getCmd(curHeader.general) for getCmd in getCmdFuncList) + ",\n") + (self.getWindSettingsCmd(curHeader.general) if curHeader.general.setWind else "") + (self.getObjectListCmd(curHeader.objects, headerIndex) if len(curHeader.objects.objectList) > 0 else "") @@ -96,13 +96,12 @@ def getSoundSettingsCmd(self, infos: "OOTSceneGeneral"): return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" def getRoomListCmd(self, scene: "OOTScene"): - return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName()}),\n" + return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName}),\n" - def getTransActorListCmd(self, scene: "OOTScene", actors: "OOTSceneActors", headerIndex: int): - headerName = scene.getHeaderName(headerIndex) + def getTransActorListCmd(self, actors: "OOTSceneActors"): return ( indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" - ) + f"{len(actors.transitionActorList)}, {actors.transitionActorListName(headerName)})" + ) + f"{len(actors.transitionActorList)}, {actors.transActorListName})" def getMiscSettingsCmd(self, infos: "OOTSceneGeneral"): return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" @@ -110,11 +109,10 @@ def getMiscSettingsCmd(self, infos: "OOTSceneGeneral"): # def getColHeaderCmd(self, outScene: OOTScene): # return indent + f"SCENE_CMD_COL_HEADER(&{outScene.collision.headerName()})" - def getSpawnListCmd(self, scene: "OOTScene", actors: "OOTSceneActors", headerIndex: int): - name = scene.getHeaderName(headerIndex) + def getSpawnListCmd(self, actors: "OOTSceneActors"): return ( indent + "SCENE_CMD_ENTRANCE_LIST(" - ) + f"{actors.entranceListName(name) if len(actors.entranceActorList) > 0 else 'NULL'})" + ) + f"{actors.entranceListName if len(actors.entranceActorList) > 0 else 'NULL'})" def getSpecialFilesCmd(self, infos: "OOTSceneGeneral"): return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" @@ -123,9 +121,8 @@ def getSpecialFilesCmd(self, infos: "OOTSceneGeneral"): # return indent + f"SCENE_CMD_PATH_LIST({outScene.pathListName(headerIndex)})" def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): - curHeader = scene.getSceneHeader(headerIndex) - headerName = scene.getHeaderName(headerIndex) - startPosName = curHeader.actors.startPositionsName(headerName) + curHeader = scene.getSceneHeaderFromIndex(headerIndex) + startPosName = curHeader.actors.startPositionsName return ( (indent + "SCENE_CMD_SPAWN_LIST(") + f"{len(curHeader.actors.entranceActorList)}, " @@ -139,14 +136,13 @@ def getSkyboxSettingsCmd(self, infos: "OOTSceneGeneral", lights: "OOTSceneLighti ) def getExitListCmd(self, scene: "OOTScene", headerIndex: int): - curHeader = scene.getSceneHeader(headerIndex) - return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.exitListName(scene.getHeaderName(headerIndex))}),\n" + curHeader = scene.getSceneHeaderFromIndex(headerIndex) + return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name}),\n" - def getLightSettingsCmd(self, scene: "OOTScene", lights: "OOTSceneLighting", headerIndex: int): - name = scene.getHeaderName(headerIndex) + def getLightSettingsCmd(self, lights: "OOTSceneLighting"): return ( indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" - ) + f"{len(lights.settings)}, {lights.lightListName(name) if len(lights.settings) > 0 else 'NULL'}),\n" + ) + f"{len(lights.settings)}, {lights.name if len(lights.settings) > 0 else 'NULL'}),\n" def getCutsceneDataCmd(self, cs: "OOTSceneCutscene"): match cs.writeType: @@ -159,8 +155,7 @@ def getCutsceneDataCmd(self, cs: "OOTSceneCutscene"): def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", headerIndex: int): cmdListData = CData() - # curHeader = scene.getSceneHeader(headerIndex) - listName = f"SceneCmd {scene.getHeaderName(headerIndex)}" + listName = f"SceneCmd {scene.headerName}" getCmdFunc1ArgList = [ # self.getColHeaderCmd, @@ -186,7 +181,7 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) sceneCmdData = ( - (scene.getAltHeaderListCmd(scene.alternateHeadersName()) if scene.hasAlternateHeaders() else "") + (scene.getAltHeaderListCmd(scene.altName) if scene.hasAlternateHeaders() else "") + self.getRoomListCmd(scene) + self.getSkyboxSettingsCmd(curHeader.general, curHeader.lighting) # + (",\n".join(getCmd(scene) for getCmd in getCmdFunc1ArgList) + ",\n") @@ -194,8 +189,8 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he + (self.getExitListCmd(scene, headerIndex) if len(curHeader.exits.exitList) > 0 else "") + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + self.getSpawnActorListCmd(scene, headerIndex) - + self.getLightSettingsCmd(scene, curHeader.lighting, headerIndex) - + (",\n".join(getCmd(scene, curHeader.actors, headerIndex) for getCmd in getCmdActorList) + ",\n") + + self.getLightSettingsCmd(curHeader.lighting) + + (",\n".join(getCmd(curHeader.actors) for getCmd in getCmdActorList) + ",\n") + scene.getEndCmd() ) diff --git a/fast64_internal/oot/new_exporter/io_classes.py b/fast64_internal/oot/new_exporter/io_classes.py index 3a7e1fdee..9df302e84 100644 --- a/fast64_internal/oot/new_exporter/io_classes.py +++ b/fast64_internal/oot/new_exporter/io_classes.py @@ -20,6 +20,7 @@ class Common: transform: Matrix roomIndex: int sceneName: str + altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): preset = actorProp.headerSettings.sceneSetupPreset @@ -158,8 +159,8 @@ class OOTExporter: def setRoomListData(self): for room in self.scene.roomList: - roomData = OOTRoomData(room.getRoomName()) - roomMainData = room.getRoomData() + roomData = OOTRoomData(room.name) + roomMainData = room.getRoomMainC() roomData.roomMain = roomMainData.source self.header += roomMainData.header @@ -168,14 +169,14 @@ def setRoomListData(self): def setSceneData(self): sceneData = OOTSceneData() - sceneMainData = self.scene.getSceneData() + sceneMainData = self.scene.getSceneMainC() sceneData.sceneMain = sceneMainData.source self.header += sceneMainData.header self.sceneData = sceneData def writeScene(self): - scenePath = os.path.join(self.path, self.scene.getSceneName() + ".c") + scenePath = os.path.join(self.path, self.scene.sceneName + ".c") writeFile(scenePath, self.sceneData.sceneMain) for room in self.roomList.values(): @@ -189,6 +190,7 @@ def writeScene(self): class RoomCommon: roomName: str + @dataclass class OOTRoomGeneral: ### General ### @@ -249,25 +251,71 @@ def getObjectList(self, headerIndex: int): @dataclass class OOTRoomActors(RoomCommon): - actorList: list[Actor] + sceneObj: Object + roomObj: Object + transform: Matrix + headerIndex: int + actorList: list[Actor] = field(default_factory=list) + + def __post_init__(self): + actorObjList: list[Object] = [ + obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" + ] + for obj in actorObjList: + actorProp = obj.ootActorProperty + if not Common.isCurrentHeaderValid(actorProp, self.headerIndex): + continue + + # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for + # the total number of actors defined in the XML. If the user deletes one, this will prevent + # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty + # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if + # the current Actor has the ID `None` to avoid export issues. + if actorProp.actorID != "None": + pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) + actor = Actor() + + if actorProp.actorID == "Custom": + actor.id = actorProp.actorIDCustom + else: + actor.id = actorProp.actorID + + if actorProp.rotOverride: + actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) + else: + actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + + actor.name = ( + ootData.actorData.actorsByID[actorProp.actorID].name.replace( + f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" + ) + if actorProp.actorID != "Custom" + else "Custom Actor" + ) + + actor.pos = pos + actor.params = actorProp.actorParam + self.actorList.append(actor) - def actorListName(self, headerIndex: int): - return f"{self.roomName}_header{headerIndex:02}_actorList" + # Exporter - def getActorLengthDefineName(self, headerIndex: int): - return f"LENGTH_{self.actorListName(headerIndex).upper()}" + def actorListName(self): + return f"{self.roomName}_header{self.headerIndex:02}_actorList" + + def getActorLengthDefineName(self): + return f"LENGTH_{self.actorListName().upper()}" - def getActorListData(self, headerIndex: int): + def getActorListData(self): """Returns the actor list for the current header""" actorList = CData() - listName = f"ActorEntry {self.actorListName(headerIndex)}" + listName = f"ActorEntry {self.actorListName()}" # .h actorList.header = f"extern {listName}[];\n" # .c actorList.source = ( - (f"{listName}[{self.getActorLengthDefineName(headerIndex)}]" + " = {\n") + (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") + "\n".join(actor.getActorEntry() for actor in self.actorList) + "};\n\n" ) @@ -305,10 +353,15 @@ def getHeaderDefines(self, headerIndex: int): @dataclass -class OOTRoom(Common): +class OOTRoom(Common, OOTRoomCommands): + name: str = None + altName: str = None header: OOTRoomHeader = None alternate: OOTRoomAlternate = None + def __post_init__(self): + self.altHeadersName = f"{self.name}_alternateHeaders" + def hasAlternateHeaders(self): return ( self.alternate is not None and @@ -318,10 +371,10 @@ def hasAlternateHeaders(self): len(self.alternate.cutscene) > 0 ) - def getRoomHeader(self, headerIndex: int) -> OOTRoomHeader | None: + def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: if headerIndex == 0: return self.header - + for i, header in enumerate(self.altHeaderList, 1): if headerIndex == i: return getattr(self.alternate, header) @@ -332,50 +385,8 @@ def getRoomHeader(self, headerIndex: int) -> OOTRoomHeader | None: return None - def getActorList(self, headerIndex: int): - actorList: list[Actor] = [] - actorObjList: list[Object] = [ - obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" - ] - for obj in actorObjList: - actorProp = obj.ootActorProperty - if not self.isCurrentHeaderValid(actorProp, headerIndex): - continue - - # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for - # the total number of actors defined in the XML. If the user deletes one, this will prevent - # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty - # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if - # the current Actor has the ID `None` to avoid export issues. - if actorProp.actorID != "None": - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - actor = Actor() - - if actorProp.actorID == "Custom": - actor.id = actorProp.actorIDCustom - else: - actor.id = actorProp.actorID - - if actorProp.rotOverride: - actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) - else: - actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - - actor.name = ( - ootData.actorData.actorsByID[actorProp.actorID].name.replace( - f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" - ) - if actorProp.actorID != "Custom" - else "Custom Actor" - ) - - actor.pos = pos - actor.params = actorProp.actorParam - actorList.append(actor) - return actorList - - def getSingleRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0): - """Returns a single room header with the informations from the scene empty object""" + def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0): + """Returns a new room header with the informations from the scene empty object""" objIDList = [] for objProp in headerProp.objectList: @@ -385,7 +396,7 @@ def getSingleRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: in objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) return OOTRoomHeader( - self.getRoomName(), + self.name, OOTRoomGeneral( headerProp.roomIndex, headerProp.roomShape, @@ -403,23 +414,17 @@ def getSingleRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: in [d for d in headerProp.windVector] if headerProp.setWind else None, headerProp.windStrength if headerProp.setWind else None ), - OOTRoomObjects(self.getRoomName(), objIDList), + OOTRoomObjects(self.name, objIDList), OOTRoomActors( - self.getRoomName(), - self.getActorList(headerIndex), + self.name, + self.sceneObj, + self.roomObj, + self.transform, + headerIndex, ) ) - - # Export - - def getRoomName(self): - return f"{toAlnum(self.sceneName)}_room_{self.roomIndex}" - - def alternateHeadersName(self): - return f"{self.getRoomName()}_alternateHeaders" - def getRoomData(self): - cmdExport = OOTRoomCommands() + def getRoomMainC(self): roomC = CData() roomHeaders: list[tuple[OOTRoomHeader, str]] = [ @@ -431,7 +436,7 @@ def getRoomData(self): for i, csHeader in enumerate(self.alternate.cutscene): roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - altHeaderPtrListName = f"SceneCmd* {self.alternateHeadersName()}" + altHeaderPtrListName = f"SceneCmd* {self.altHeadersName}" # .h roomC.header = f"extern {altHeaderPtrListName}[];\n" @@ -442,7 +447,7 @@ def getRoomData(self): + " = {\n" + "\n".join( indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," - for i, (curHeader, headerDesc) in enumerate(roomHeaders, 1) + for i, (curHeader, _) in enumerate(roomHeaders, 1) ) + "\n};\n\n" ) @@ -452,7 +457,7 @@ def getRoomData(self): if curHeader is not None: roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" roomC.source += curHeader.getHeaderDefines(i) - roomC.append(cmdExport.getRoomCommandList(self, i)) + roomC.append(self.getRoomCommandList(self, i)) if i == 0 and self.hasAlternateHeaders(): roomC.source += altHeaderPtrList @@ -461,7 +466,7 @@ def getRoomData(self): roomC.append(curHeader.objects.getObjectList(i)) if len(curHeader.actors.actorList) > 0: - roomC.append(curHeader.actors.getActorListData(i)) + roomC.append(curHeader.actors.getActorListData()) return roomC @@ -470,7 +475,8 @@ def getRoomData(self): @dataclass class SceneCommon: - sceneName: str + headerName: str + name: str = None @dataclass @@ -567,22 +573,22 @@ class OOTSceneGeneral: @dataclass -class OOTSceneLighting: - envLightMode: str - settings: list[EnvLightSettings] +class OOTSceneLighting(SceneCommon): + envLightMode: str = None + settings: list[EnvLightSettings] = field(default_factory=list) - def lightListName(self, headerName: str): - return f"{headerName}_lightSettings" + def __post_init__(self): + self.name = f"{self.headerName}_lightSettings" - def getLightSettings(self, headerName: str): - lightSettingsData = CData() - lightName = f"EnvLightSettings {self.lightListName(headerName)}[{len(self.settings)}]" + def getEnvLightSettingsC(self): + lightSettingsC = CData() + lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" # .h - lightSettingsData.header = f"extern {lightName};\n" + lightSettingsC.header = f"extern {lightName};\n" # .c - lightSettingsData.source = ( + lightSettingsC.source = ( (lightName + " = {\n") + "".join( light.getLightSettingsEntry(i) @@ -591,7 +597,7 @@ def getLightSettings(self, headerName: str): + "};\n\n" ) - return lightSettingsData + return lightSettingsC @dataclass @@ -604,48 +610,48 @@ class OOTSceneCutscene: @dataclass -class OOTSceneExits: - exitList: list[tuple[int, str]] +class OOTSceneExits(SceneCommon): + exitList: list[tuple[int, str]] = field(default_factory=list) - def exitListName(self, headerName: str): - return f"{headerName}_exitList" + def __post_init__(self): + self.name = f"{self.headerName}_exitList" - def getExitListData(self, headerName: str): - exitList = CData() - listName = f"u16 {self.exitListName(headerName)}[{len(self.exitList)}]" + def getExitListC(self): + exitListC = CData() + listName = f"u16 {self.name}[{len(self.exitList)}]" # .h - exitList.header = f"extern {listName};\n" + exitListC.header = f"extern {listName};\n" # .c - exitList.source = ( + exitListC.source = ( (listName + " = {\n") # @TODO: use the enum name instead of the raw index + "\n".join(indent + f"{value}," for (_, value) in self.exitList) + "\n};\n\n" ) - return exitList + return exitListC @dataclass -class OOTSceneActors: - transitionActorList: list[TransitionActor] - entranceActorList: list[EntranceActor] - - def entranceListName(self, headerName: str): - return f"{headerName}_entranceList" - - def startPositionsName(self, headerName: str): - return f"{headerName}_playerEntryList" - - def transitionActorListName(self, headerName: str): - return f"{headerName}_transitionActors" +class OOTSceneActors(SceneCommon): + transitionActorList: list[TransitionActor] = field(default_factory=list) + entranceActorList: list[EntranceActor] = field(default_factory=list) + + entranceListName: str = None + startPositionsName: str = None + transActorListName: str = None + + def __post_init__(self): + self.entranceListName = f"{self.headerName}_entranceList" + self.startPositionsName = f"{self.headerName}_playerEntryList" + self.transActorListName = f"{self.headerName}_transitionActors" - def getSpawnActorList(self, headerName: str): + def getSpawnActorListC(self): """Returns the spawn actor list for the current header""" spawnActorList = CData() - listName = f"ActorEntry {self.startPositionsName(headerName)}" + listName = f"ActorEntry {self.startPositionsName}" # .h spawnActorList.header = f"extern {listName}[];\n" @@ -659,10 +665,10 @@ def getSpawnActorList(self, headerName: str): return spawnActorList - def getSpawnList(self, headerName: str): + def getSpawnListC(self): """Returns the spawn list for the current header""" spawnList = CData() - listName = f"Spawn {self.entranceListName(headerName)}" + listName = f"Spawn {self.entranceListName}" # .h spawnList.header = f"extern {listName}[];\n" @@ -677,10 +683,10 @@ def getSpawnList(self, headerName: str): return spawnList - def getTransitionActorListData(self, headerName: str): + def getTransActorListC(self): """Returns the transition actor list for the current header""" transActorList = CData() - listName = f"TransitionActorEntry {self.transitionActorListName(headerName)}" + listName = f"TransitionActorEntry {self.transActorListName}" # .h transActorList.header = f"extern {listName}[];\n" @@ -711,25 +717,25 @@ class OOTSceneHeader: exits: OOTSceneExits actors: OOTSceneActors - def getHeaderData(self, headerName: str): + def getHeaderC(self): headerData = CData() # Write the spawn position list data and the entrance list if len(self.actors.entranceActorList) > 0: - headerData.append(self.actors.getSpawnActorList(headerName)) - headerData.append(self.actors.getSpawnList(headerName)) + headerData.append(self.actors.getSpawnActorListC()) + headerData.append(self.actors.getSpawnListC()) # Write the transition actor list data if len(self.actors.transitionActorList) > 0: - headerData.append(self.actors.getTransitionActorListData(headerName)) + headerData.append(self.actors.getTransActorListC()) # Write the exit list if len(self.exits.exitList) > 0: - headerData.append(self.exits.getExitListData(headerName)) + headerData.append(self.exits.getExitListC()) # Write the light data if len(self.lighting.settings) > 0: - headerData.append(self.lighting.getLightSettings(headerName)) + headerData.append(self.lighting.getEnvLightSettingsC()) # Write the path data, if used # if len(self.pathList) > 0: @@ -739,12 +745,19 @@ def getHeaderData(self, headerName: str): @dataclass -class OOTScene(Common): +class OOTScene(Common, OOTSceneCommands): + headerIndex: int = None + headerName: str = None header: OOTSceneHeader = None alternate: OOTSceneAlternate = None roomList: list[OOTRoom] = field(default_factory=list) - altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) + altName: str = None + roomListName: str = None + + def __post_init__(self): + self.altName = f"{self.sceneName}_alternateHeaders" + self.roomListName = f"{self.sceneName}_roomList" def validateRoomIndices(self): for i, room in enumerate(self.roomList): @@ -769,7 +782,7 @@ def hasAlternateHeaders(self): len(self.alternate.cutscene) > 0 ) - def getSceneHeader(self, headerIndex: int) -> OOTSceneHeader | None: + def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: if headerIndex == 0: return self.header @@ -783,7 +796,7 @@ def getSceneHeader(self, headerIndex: int) -> OOTSceneHeader | None: return None - def getExitList(self, headerProp: OOTSceneHeaderProperty): + def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): """Returns the exit list and performs safety checks""" exitList: list[tuple[int, str]] = [] @@ -796,16 +809,16 @@ def getExitList(self, headerProp: OOTSceneHeaderProperty): return exitList - def getRoomObject(self, object: Object) -> Object | None: + def getRoomObjectFromChild(self, childObj: Object) -> Object | None: # Note: temporary solution until PRs #243 & #255 are merged for obj in self.sceneObj.children_recursive: if obj.type == "EMPTY" and obj.ootEmptyType == "Room": for o in obj.children_recursive: - if o == object: + if o == childObj: return obj return None - def getTransitionActorList(self, headerIndex: int): + def getTransActorListFromProps(self): actorList: list[TransitionActor] = [] actorObjList: list[Object] = [ obj @@ -813,14 +826,14 @@ def getTransitionActorList(self, headerIndex: int): if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" ] for obj in actorObjList: - roomObj = self.getRoomObject(obj) + roomObj = self.getRoomObjectFromChild(obj) if roomObj is None: raise PluginError("ERROR: Room Object not found!") self.roomIndex = roomObj.ootRoomHeader.roomIndex transActorProp = obj.ootTransitionActorProperty - if not self.isCurrentHeaderValid(transActorProp.actor, headerIndex): + if not self.isCurrentHeaderValid(transActorProp.actor, self.headerIndex): continue if transActorProp.actor.actorID != "None": @@ -855,7 +868,7 @@ def getTransitionActorList(self, headerIndex: int): actorList.append(transActor) return actorList - def getEntranceActorList(self, headerIndex: int): + def getEntranceActorListFromProps(self): actorList: list[EntranceActor] = [] actorObjList: list[Object] = [ obj @@ -863,12 +876,12 @@ def getEntranceActorList(self, headerIndex: int): if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" ] for obj in actorObjList: - roomObj = self.getRoomObject(obj) + roomObj = self.getRoomObjectFromChild(obj) if roomObj is None: raise PluginError("ERROR: Room Object not found!") entranceProp = obj.ootEntranceProperty - if not self.isCurrentHeaderValid(entranceProp.actor, headerIndex): + if not self.isCurrentHeaderValid(entranceProp.actor, self.headerIndex): continue if entranceProp.actor.actorID != "None": @@ -892,12 +905,15 @@ def getEntranceActorList(self, headerIndex: int): actorList.append(entranceActor) return actorList - def getSingleSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int=0): + def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int=0): """Returns a single scene header with the informations from the scene empty object""" + self.headerIndex = headerIndex + self.headerName = f"{self.sceneName}_header{self.headerIndex:02}" + if headerProp.csWriteType == "Embedded": raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") - + lightMode = self.getPropValue(headerProp, "skyboxLighting") lightList: list[OOTLightProperty] = [] lightSettings: list[EnvLightSettings] = [] @@ -942,8 +958,9 @@ def getSingleSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: self.getPropValue(headerProp, "cameraMode") ), OOTSceneLighting( - lightMode, - lightSettings, + self.headerName, + envLightMode=lightMode, + settings=lightSettings, ), OOTSceneCutscene( headerProp.csWriteType, @@ -952,35 +969,24 @@ def getSingleSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, [csObj for csObj in headerProp.extraCutscenes] ), - OOTSceneExits(self.getExitList(headerProp)), + OOTSceneExits(self.headerName, exitList=self.getExitListFromProps(headerProp)), OOTSceneActors( - self.getTransitionActorList(headerIndex), - self.getEntranceActorList(headerIndex) + self.headerName, + transitionActorList=self.getTransActorListFromProps(), + entranceActorList=self.getEntranceActorListFromProps() ) ) # Export - def getSceneName(self): - return f"{toAlnum(self.sceneName)}_scene" - - def getHeaderName(self, headerIndex: int): - return f"{self.getSceneName()}_header{headerIndex:02}" - - def alternateHeadersName(self): - return f"{self.getSceneName()}_alternateHeaders" - - def roomListName(self): - return f"{self.getSceneName()}_roomList" - - def getRoomList(self): + def getRoomListC(self): roomList = CData() - listName = f"RomFile {self.roomListName()}[]" + listName = f"RomFile {self.roomListName}[]" # generating segment rom names for every room segNames = [] for i in range(len(self.roomList)): - roomName = self.roomList[i].getRoomName() + roomName = self.roomList[i].name segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) # .h @@ -1011,8 +1017,7 @@ def getRoomList(self): roomList.source += "};\n\n" return roomList - def getSceneData(self): - cmdExport = OOTSceneCommands() + def getSceneMainC(self): sceneC = CData() headers: list[tuple[OOTSceneHeader, str]] = [ @@ -1025,7 +1030,7 @@ def getSceneData(self): headers.append((csHeader, f"Cutscene No. {i + 1}")) altHeaderPtrs = "\n".join( - indent + self.getHeaderName(i) + "," + indent + self.headerName + "," if curHeader is not None else indent + "NULL," if i < 4 @@ -1037,17 +1042,17 @@ def getSceneData(self): for i, (curHeader, headerDesc) in enumerate(headers): if curHeader is not None: sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - sceneC.append(cmdExport.getSceneCommandList(self, curHeader, i)) + sceneC.append(self.getSceneCommandList(self, curHeader, i)) if i == 0: if self.hasAlternateHeaders(): - altHeaderListName = f"SceneCmd* {self.alternateHeadersName()}[]" + altHeaderListName = f"SceneCmd* {self.altName}[]" sceneC.header += f"extern {altHeaderListName};\n" sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" # Write the room segment list - sceneC.append(self.getRoomList()) + sceneC.append(self.getRoomListC()) - sceneC.append(curHeader.getHeaderData(self.getHeaderName(i))) + sceneC.append(curHeader.getHeaderC()) return sceneC diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 1a3b57e63..779e0e033 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -43,7 +43,7 @@ def addMissingObjectsToAllRoomHeaders(roomObj: Object, room: OOTRoom, ootData: O def addMissingObjectsToRoomHeaderNew(roomObj: Object, room, ootData: OoT_Data, headerIndex: int): """Adds missing objects to the object list""" - curHeader = room.getRoomHeader(headerIndex) + curHeader = room.getRoomHeaderFromIndex(headerIndex) if len(curHeader.actors.actorList) > 0: for roomActor in curHeader.actors.actorList: actor = ootData.actorData.actorsByID.get(roomActor.actorID) From c7c33140dc5dea7f4935628fb0480c6eefb97666 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sat, 23 Sep 2023 23:42:20 +0200 Subject: [PATCH 03/98] format --- fast64_internal/oot/new_exporter/classes.py | 22 ++-- fast64_internal/oot/new_exporter/commands.py | 12 +- .../oot/new_exporter/io_classes.py | 107 +++++++++--------- fast64_internal/oot/new_exporter/ne_main.py | 2 +- 4 files changed, 69 insertions(+), 74 deletions(-) diff --git a/fast64_internal/oot/new_exporter/classes.py b/fast64_internal/oot/new_exporter/classes.py index ec439311e..5440082da 100644 --- a/fast64_internal/oot/new_exporter/classes.py +++ b/fast64_internal/oot/new_exporter/classes.py @@ -5,7 +5,14 @@ from bpy.types import Object from ...f3d.f3d_gbi import DLFormat from ...utility import PluginError, checkObjectReference, unhideAllAndGetHiddenState, restoreHiddenState, toAlnum -from ..oot_utility import ExportInfo, OOTObjectCategorizer, ootDuplicateHierarchy, ootCleanupScene, getSceneDirFromLevelName, ootGetPath +from ..oot_utility import ( + ExportInfo, + OOTObjectCategorizer, + ootDuplicateHierarchy, + ootCleanupScene, + getSceneDirFromLevelName, + ootGetPath, +) from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData @@ -19,6 +26,7 @@ OOTExporter, ) + @dataclass class OOTSceneExport: exportInfo: ExportInfo @@ -57,8 +65,7 @@ def getRoomData(self, scene: OOTScene): setattr(altHeaderData, header, roomData.getNewRoomHeader(altP, i)) altHeaderData.cutscene = [ - roomData.getNewRoomHeader(csHeader, i) - for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + roomData.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] roomData.alternate = altHeaderData @@ -69,7 +76,7 @@ def getRoomData(self, scene: OOTScene): addMissingObjectsToAllRoomHeadersNew(roomObj, roomData, ootData) processedRooms.append(roomIndex) roomList.append(roomData) - + return roomList def getSceneData(self): @@ -84,8 +91,7 @@ def getSceneData(self): setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) altHeaderData.cutscene = [ - sceneData.getNewSceneHeader(csHeader, i) - for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] sceneData.alternate = altHeaderData @@ -120,10 +126,10 @@ def processSceneObj(self): except Exception as e: ootCleanupScene(originalSceneObj, allObjs) raise Exception(str(e)) - + if sceneData is None: raise PluginError("ERROR: 'sceneData' is None!") - + return sceneData def exportScene(self): diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 1484f994c..d6f09bfd3 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -43,8 +43,7 @@ def getTimeSettingsCmd(self, infos: "OOTRoomGeneral"): def getWindSettingsCmd(self, infos: "OOTRoomGeneral"): return ( - indent - + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" + indent + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" ) # def getRoomShapeCmd(self, infos: "OOTRoom"): @@ -56,9 +55,7 @@ def getObjectListCmd(self, objects: "OOTRoomObjects", headerIndex: int): ) + f"{objects.getObjectLengthDefineName(headerIndex)}, {objects.objectListName(headerIndex)}),\n" def getActorListCmd(self, actors: "OOTRoomActors", headerIndex: int): - return ( - indent + "SCENE_CMD_ACTOR_LIST(" - ) + f"{actors.getActorLengthDefineName()}, {actors.actorListName()}),\n" + return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.actorListName()}),\n" def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): cmdListData = CData() @@ -130,10 +127,7 @@ def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): ) def getSkyboxSettingsCmd(self, infos: "OOTSceneGeneral", lights: "OOTSceneLighting"): - return ( - indent - + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" - ) + return indent + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" def getExitListCmd(self, scene: "OOTScene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) diff --git a/fast64_internal/oot/new_exporter/io_classes.py b/fast64_internal/oot/new_exporter/io_classes.py index 9df302e84..be7fb71cc 100644 --- a/fast64_internal/oot/new_exporter/io_classes.py +++ b/fast64_internal/oot/new_exporter/io_classes.py @@ -12,7 +12,7 @@ from ..oot_constants import ootData from .commands import OOTRoomCommands, OOTSceneCommands - + @dataclass class Common: sceneObj: Object @@ -31,13 +31,13 @@ def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): if preset == "Custom": if actorProp.headerSettings.childDayHeader and headerIndex == 0: return True - if actorProp.headerSettings.childNightHeader and headerIndex == 1: + if actorProp.headerSettings.childNightHeader and headerIndex == 1: return True if actorProp.headerSettings.adultDayHeader and headerIndex == 2: return True - if actorProp.headerSettings.adultNightHeader and headerIndex == 3: + if actorProp.headerSettings.adultNightHeader and headerIndex == 3: return True - + return False def getPropValue(self, data, propName: str): @@ -45,7 +45,7 @@ def getPropValue(self, data, propName: str): value = getattr(data, propName) return value if value != "Custom" else getattr(data, f"{propName}Custom") - + def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, orientation): relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world blenderTranslation, blenderRotation, scale = relativeTransform.decompose() @@ -63,7 +63,7 @@ def getConvertedTransform(self, transformMatrix, sceneObj, obj, handleOrientatio else: orientation = Matrix.Identity(4) return self.getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) - + def getAltHeaderListCmd(self, altName): return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" @@ -133,6 +133,7 @@ def getSpawnEntry(self): ### EXPORT UTILITY ### + @dataclass class OOTRoomData: name: str @@ -166,7 +167,7 @@ def setRoomListData(self): self.header += roomMainData.header self.roomList[room.roomIndex] = roomData - + def setSceneData(self): sceneData = OOTSceneData() sceneMainData = self.scene.getSceneMainC() @@ -186,6 +187,7 @@ def writeScene(self): ### ROOM ### + @dataclass class RoomCommon: roomName: str @@ -230,7 +232,7 @@ def objectListName(self, headerIndex: int): def getObjectLengthDefineName(self, headerIndex: int): return f"LENGTH_{self.objectListName(headerIndex).upper()}" - + def getObjectList(self, headerIndex: int): objectList = CData() @@ -304,7 +306,7 @@ def actorListName(self): def getActorLengthDefineName(self): return f"LENGTH_{self.actorListName().upper()}" - + def getActorListData(self): """Returns the actor list for the current header""" actorList = CData() @@ -364,13 +366,13 @@ def __post_init__(self): def hasAlternateHeaders(self): return ( - self.alternate is not None and - self.alternate.childNight is not None and - self.alternate.adultDay is not None and - self.alternate.adultNight is not None and - len(self.alternate.cutscene) > 0 + self.alternate is not None + and self.alternate.childNight is not None + and self.alternate.adultDay is not None + and self.alternate.adultNight is not None + and len(self.alternate.cutscene) > 0 ) - + def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: if headerIndex == 0: return self.header @@ -378,14 +380,14 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: for i, header in enumerate(self.altHeaderList, 1): if headerIndex == i: return getattr(self.alternate, header) - + for i, csHeader in enumerate(self.alternate.cutscene, 4): if headerIndex == i: return csHeader return None - def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0): + def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): """Returns a new room header with the informations from the scene empty object""" objIDList = [] @@ -412,7 +414,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0 headerProp.echo, headerProp.setWind, [d for d in headerProp.windVector] if headerProp.setWind else None, - headerProp.windStrength if headerProp.setWind else None + headerProp.windStrength if headerProp.setWind else None, ), OOTRoomObjects(self.name, objIDList), OOTRoomActors( @@ -421,7 +423,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int=0 self.roomObj, self.transform, headerIndex, - ) + ), ) def getRoomMainC(self): @@ -473,6 +475,7 @@ def getRoomMainC(self): ### SCENE ### + @dataclass class SceneCommon: headerName: str @@ -494,13 +497,13 @@ class EnvLightSettings: def getBlendFogNear(self): return f"(({self.blendRate} << 10) | {self.fogNear})" - + def getColorValues(self, vector: tuple[int, int, int]): return ", ".join(f"{v:5}" for v in vector) def getDirectionValues(self, vector: tuple[int, int, int]): return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - + def getLightSettingsEntry(self, index: int): isLightingCustom = self.envLightMode == "Custom" @@ -534,7 +537,9 @@ def getLightSettingsEntry(self, index: int): lightData = ( (indent + lightDesc) + (indent + "{\n") - + "".join(indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors) + + "".join( + indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors + ) + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) + (indent + "},\n") ) @@ -590,10 +595,7 @@ def getEnvLightSettingsC(self): # .c lightSettingsC.source = ( (lightName + " = {\n") - + "".join( - light.getLightSettingsEntry(i) - for i, light in enumerate(self.settings) - ) + + "".join(light.getLightSettingsEntry(i) for i, light in enumerate(self.settings)) + "};\n\n" ) @@ -647,7 +649,7 @@ def __post_init__(self): self.entranceListName = f"{self.headerName}_entranceList" self.startPositionsName = f"{self.headerName}_playerEntryList" self.transActorListName = f"{self.headerName}_transitionActors" - + def getSpawnActorListC(self): """Returns the spawn actor list for the current header""" spawnActorList = CData() @@ -682,7 +684,7 @@ def getSpawnListC(self): ) return spawnList - + def getTransActorListC(self): """Returns the transition actor list for the current header""" transActorList = CData() @@ -763,7 +765,7 @@ def validateRoomIndices(self): for i, room in enumerate(self.roomList): if i != room.roomIndex: return False - + return True def validateScene(self): @@ -775,27 +777,27 @@ def validateScene(self): def hasAlternateHeaders(self): return ( - self.alternate is not None and - self.alternate.childNight is not None and - self.alternate.adultDay is not None and - self.alternate.adultNight is not None and - len(self.alternate.cutscene) > 0 + self.alternate is not None + and self.alternate.childNight is not None + and self.alternate.adultDay is not None + and self.alternate.adultNight is not None + and len(self.alternate.cutscene) > 0 ) - + def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: if headerIndex == 0: return self.header - + for i, header in enumerate(self.altHeaderList, 1): if headerIndex == i: return getattr(self.alternate, header) - + for i, csHeader in enumerate(self.alternate.cutscene, 4): if headerIndex == i: return csHeader return None - + def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): """Returns the exit list and performs safety checks""" @@ -806,7 +808,7 @@ def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") exitList.append((i, exitProp.exitIndexCustom)) - + return exitList def getRoomObjectFromChild(self, childObj: Object) -> Object | None: @@ -871,9 +873,7 @@ def getTransActorListFromProps(self): def getEntranceActorListFromProps(self): actorList: list[EntranceActor] = [] actorObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" ] for obj in actorObjList: roomObj = self.getRoomObjectFromChild(obj) @@ -905,7 +905,7 @@ def getEntranceActorListFromProps(self): actorList.append(entranceActor) return actorList - def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int=0): + def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): """Returns a single scene header with the informations from the scene empty object""" self.headerIndex = headerIndex @@ -938,7 +938,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int exportColor(lightProp.fogColor), lightProp.fogNear, lightProp.fogFar, - lightProp.transitionSpeed + lightProp.transitionSpeed, ) ) @@ -955,7 +955,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getPropValue(headerProp, "nightSeq"), self.getPropValue(headerProp, "audioSessionPreset"), self.getPropValue(headerProp, "mapLocation"), - self.getPropValue(headerProp, "cameraMode") + self.getPropValue(headerProp, "cameraMode"), ), OOTSceneLighting( self.headerName, @@ -967,16 +967,16 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int headerProp.writeCutscene, headerProp.csWriteObject, headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, - [csObj for csObj in headerProp.extraCutscenes] + [csObj for csObj in headerProp.extraCutscenes], ), OOTSceneExits(self.headerName, exitList=self.getExitListFromProps(headerProp)), OOTSceneActors( self.headerName, transitionActorList=self.getTransActorListFromProps(), - entranceActorList=self.getEntranceActorListFromProps() - ) + entranceActorList=self.getEntranceActorListFromProps(), + ), ) - + # Export def getRoomListC(self): @@ -1008,8 +1008,7 @@ def getRoomListC(self): else: roomList.source += ( " },\n".join( - indent + "{ " + f"(uintptr_t){startName}, (uintptr_t){stopName}" - for startName, stopName in segNames + indent + "{ " + f"(uintptr_t){startName}, (uintptr_t){stopName}" for startName, stopName in segNames ) + " },\n" ) @@ -1030,11 +1029,7 @@ def getSceneMainC(self): headers.append((csHeader, f"Cutscene No. {i + 1}")) altHeaderPtrs = "\n".join( - indent + self.headerName + "," - if curHeader is not None - else indent + "NULL," - if i < 4 - else "" + indent + self.headerName + "," if curHeader is not None else indent + "NULL," if i < 4 else "" for i, (curHeader, _) in enumerate(headers, 1) ) diff --git a/fast64_internal/oot/new_exporter/ne_main.py b/fast64_internal/oot/new_exporter/ne_main.py index d5c9d47ec..2ffd4121f 100644 --- a/fast64_internal/oot/new_exporter/ne_main.py +++ b/fast64_internal/oot/new_exporter/ne_main.py @@ -38,5 +38,5 @@ def exportScene(exportInfo: ExportInfo): Matrix.Diagonal(Vector((ootBlenderScale, ootBlenderScale, ootBlenderScale))).to_4x4(), bpy.context.scene.f3d_type, bpy.context.scene.saveTextures, - bootOptions if hackerFeaturesEnabled else None + bootOptions if hackerFeaturesEnabled else None, ) From 8236329b8b0bb8e62963b98114c5b82f8d55a470 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 00:15:55 +0200 Subject: [PATCH 04/98] more cleanup + split classes --- fast64_internal/oot/new_exporter/__init__.py | 2 +- fast64_internal/oot/new_exporter/commands.py | 15 +- fast64_internal/oot/new_exporter/common.py | 125 +++++ .../new_exporter/{classes.py => exporter.py} | 99 +++- fast64_internal/oot/new_exporter/ne_main.py | 42 -- fast64_internal/oot/new_exporter/room.py | 293 +++++++++++ .../new_exporter/{io_classes.py => scene.py} | 476 +----------------- fast64_internal/oot/scene/operators.py | 14 +- 8 files changed, 512 insertions(+), 554 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/common.py rename fast64_internal/oot/new_exporter/{classes.py => exporter.py} (72%) delete mode 100644 fast64_internal/oot/new_exporter/ne_main.py create mode 100644 fast64_internal/oot/new_exporter/room.py rename fast64_internal/oot/new_exporter/{io_classes.py => scene.py} (57%) diff --git a/fast64_internal/oot/new_exporter/__init__.py b/fast64_internal/oot/new_exporter/__init__.py index 13f5e265e..6668eb826 100644 --- a/fast64_internal/oot/new_exporter/__init__.py +++ b/fast64_internal/oot/new_exporter/__init__.py @@ -1 +1 @@ -from .ne_main import * +from .exporter import OOTSceneExport diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index d6f09bfd3..9fa2ae2fe 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -3,19 +3,8 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from .io_classes import ( - OOTScene, - OOTSceneGeneral, - OOTSceneHeader, - OOTSceneLighting, - OOTSceneCutscene, - OOTRoom, - OOTRoomHeader, - OOTRoomGeneral, - OOTRoomObjects, - OOTRoomActors, - OOTSceneActors, - ) + from .scene import OOTScene, OOTSceneGeneral, OOTSceneHeader, OOTSceneLighting, OOTSceneCutscene, OOTSceneActors + from .room import OOTRoom, OOTRoomHeader, OOTRoomGeneral, OOTRoomObjects, OOTRoomActors class OOTRoomCommands: diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/common.py new file mode 100644 index 000000000..ab51cabf8 --- /dev/null +++ b/fast64_internal/oot/new_exporter/common.py @@ -0,0 +1,125 @@ +from dataclasses import dataclass, field +from math import radians +from mathutils import Quaternion, Matrix +from bpy.types import Object +from ...utility import indent +from ..oot_utility import ootConvertTranslation, ootConvertRotation +from ..actor.properties import OOTActorProperty + + +@dataclass +class Common: + sceneObj: Object + roomObj: Object + transform: Matrix + roomIndex: int + sceneName: str + altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) + + def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): + preset = actorProp.headerSettings.sceneSetupPreset + + if preset == "All Scene Setups" or (preset == "All Non-Cutscene Scene Setups" and headerIndex < 4): + return True + + if preset == "Custom": + if actorProp.headerSettings.childDayHeader and headerIndex == 0: + return True + if actorProp.headerSettings.childNightHeader and headerIndex == 1: + return True + if actorProp.headerSettings.adultDayHeader and headerIndex == 2: + return True + if actorProp.headerSettings.adultNightHeader and headerIndex == 3: + return True + + return False + + def getPropValue(self, data, propName: str): + """Returns ``data.propName`` or ``data.propNameCustom``""" + + value = getattr(data, propName) + return value if value != "Custom" else getattr(data, f"{propName}Custom") + + def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, orientation): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + blenderTranslation, blenderRotation, scale = relativeTransform.decompose() + rotation = blenderRotation @ orientation + convertedTranslation = ootConvertTranslation(blenderTranslation) + convertedRotation = ootConvertRotation(rotation) + + return convertedTranslation, convertedRotation, scale, rotation + + def getConvertedTransform(self, transformMatrix, sceneObj, obj, handleOrientation): + # Hacky solution to handle Z-up to Y-up conversion + # We cannot apply rotation to empty, as that modifies scale + if handleOrientation: + orientation = Quaternion((1, 0, 0), radians(90.0)) + else: + orientation = Matrix.Identity(4) + return self.getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) + + def getAltHeaderListCmd(self, altName): + return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" + + def getEndCmd(self): + return indent + "SCENE_CMD_END(),\n" + + +@dataclass +class Actor: + name: str = None + id: str = None + pos: list[int] = field(default_factory=list) + rot: str = None + params: str = None + + def getActorEntry(self): + """Returns a single actor entry""" + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" + rotData = "{ " + self.rot + " }" + + actorInfos = [self.id, posData, rotData, self.params] + infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] + + return ( + indent + + (f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class TransitionActor(Actor): + dontTransition: bool = None + roomFrom: int = None + roomTo: int = None + cameraFront: str = None + cameraBack: str = None + + def getTransitionActorEntry(self): + """Returns a single transition actor entry""" + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] + roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" + posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" + + actorInfos = [roomData, self.id, posData, self.rot, self.params] + infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] + + return ( + (indent + f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class EntranceActor(Actor): + roomIndex: int = None + spawnIndex: int = None + + def getSpawnEntry(self): + """Returns a single spawn entry""" + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/classes.py b/fast64_internal/oot/new_exporter/exporter.py similarity index 72% rename from fast64_internal/oot/new_exporter/classes.py rename to fast64_internal/oot/new_exporter/exporter.py index 5440082da..77268af07 100644 --- a/fast64_internal/oot/new_exporter/classes.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -1,10 +1,25 @@ -import bpy, os +import bpy +import os from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object +from ...utility import ( + PluginError, + checkObjectReference, + unhideAllAndGetHiddenState, + restoreHiddenState, + toAlnum, + writeFile, +) from ...f3d.f3d_gbi import DLFormat -from ...utility import PluginError, checkObjectReference, unhideAllAndGetHiddenState, restoreHiddenState, toAlnum +from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty +from ..room.properties import OOTRoomHeaderProperty +from ..oot_constants import ootData +from ..oot_object import addMissingObjectsToAllRoomHeadersNew +from .scene import OOTScene, OOTSceneAlternate +from .room import OOTRoom, OOTRoomAlternate + from ..oot_utility import ( ExportInfo, OOTObjectCategorizer, @@ -13,18 +28,20 @@ getSceneDirFromLevelName, ootGetPath, ) -from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty -from ..room.properties import OOTRoomHeaderProperty -from ..oot_constants import ootData -from ..oot_object import addMissingObjectsToAllRoomHeadersNew -from .io_classes import ( - OOTRoomAlternate, - OOTRoom, - OOTSceneAlternate, - OOTScene, - OOTExporter, -) + +@dataclass +class OOTRoomData: + name: str + roomMain: str = None + roomModel: str = None + roomModelInfo: str = None + + +@dataclass +class OOTSceneData: + sceneMain: str = None + sceneCollision: str = None @dataclass @@ -38,10 +55,16 @@ class OOTSceneExport: saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions dlFormat: DLFormat = DLFormat.Static - altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) - def getRoomData(self, scene: OOTScene): + scene: OOTScene = None + path: str = None + header: str = "" + sceneData: OOTSceneData = None + roomList: dict[int, OOTRoomData] = field(default_factory=dict) + csList: dict[int, str] = field(default_factory=dict) + + def getNewRoomList(self): processedRooms = [] roomList: list[OOTRoom] = [] roomObjs: list[Object] = [ @@ -79,7 +102,7 @@ def getRoomData(self, scene: OOTScene): return roomList - def getSceneData(self): + def getNewScene(self): altProp = self.sceneObj.ootAlternateSceneHeaders sceneData = OOTScene(self.sceneObj, None, self.transform, None, f"{toAlnum(self.sceneName)}_scene") altHeaderData = OOTSceneAlternate() @@ -95,12 +118,12 @@ def getSceneData(self): ] sceneData.alternate = altHeaderData - sceneData.roomList = self.getRoomData(sceneData) + sceneData.roomList = self.getNewRoomList() sceneData.validateScene() return sceneData - def processSceneObj(self): + def getNewSceneFromEmptyObject(self): """Returns the default scene header and adds the alternate/cutscene ones""" # init @@ -120,7 +143,7 @@ def processSceneObj(self): # convert scene sceneData = None try: - sceneData = self.getSceneData() + sceneData = self.getNewScene() ootCleanupScene(originalSceneObj, allObjs) except Exception as e: @@ -132,7 +155,33 @@ def processSceneObj(self): return sceneData - def exportScene(self): + def setRoomListData(self): + for room in self.scene.roomList: + roomData = OOTRoomData(room.name) + roomMainData = room.getRoomMainC() + + roomData.roomMain = roomMainData.source + self.header += roomMainData.header + + self.roomList[room.roomIndex] = roomData + + def setSceneData(self): + sceneData = OOTSceneData() + sceneMainData = self.scene.getSceneMainC() + + sceneData.sceneMain = sceneMainData.source + self.header += sceneMainData.header + self.sceneData = sceneData + + def writeScene(self): + scenePath = os.path.join(self.path, self.scene.sceneName + ".c") + writeFile(scenePath, self.sceneData.sceneMain) + + for room in self.roomList.values(): + roomPath = os.path.join(self.path, room.name + ".c") + writeFile(roomPath, room.roomMain) + + def export(self): checkObjectReference(self.sceneObj, "Scene object") isCustomExport = self.exportInfo.isCustomExportPath exportPath = self.exportInfo.exportPath @@ -146,7 +195,9 @@ def exportScene(self): sceneInclude = exportSubdir + "/" + self.sceneName + "/" levelPath = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) - exporter = OOTExporter(self.processSceneObj(), levelPath) - exporter.setSceneData() - exporter.setRoomListData() - exporter.writeScene() + self.scene = self.getNewSceneFromEmptyObject() + self.path = levelPath + self.setSceneData() + self.setRoomListData() + + self.writeScene() diff --git a/fast64_internal/oot/new_exporter/ne_main.py b/fast64_internal/oot/new_exporter/ne_main.py deleted file mode 100644 index 2ffd4121f..000000000 --- a/fast64_internal/oot/new_exporter/ne_main.py +++ /dev/null @@ -1,42 +0,0 @@ -import bpy - -from mathutils import Matrix, Vector -from bpy.ops import object -from bpy.types import Object -from ...utility import PluginError -from ..oot_utility import ExportInfo, sceneNameFromID -from .classes import OOTSceneExport - - -def getSceneObject() -> Object: - """Returns the selected OoT scene empty object to export""" - if bpy.context.mode != "OBJECT": - object.mode_set(mode="OBJECT") - - sceneObj = bpy.context.scene.ootSceneExportObj - - if sceneObj is None: - raise PluginError("Scene object input not set.") - elif sceneObj.type != "EMPTY" or sceneObj.ootEmptyType != "Scene": - raise PluginError("The input object is not an empty with the Scene type.") - - return sceneObj - - -def exportScene(exportInfo: ExportInfo): - """Returns the initialised scene exporter""" - - ootBlenderScale = bpy.context.scene.ootBlenderScale - bootOptions = bpy.context.scene.fast64.oot.bootupSceneOptions - hackerFeaturesEnabled = bpy.context.scene.fast64.oot.hackerFeaturesEnabled - - return OOTSceneExport( - exportInfo, - getSceneObject(), - exportInfo.name, - ootBlenderScale, - Matrix.Diagonal(Vector((ootBlenderScale, ootBlenderScale, ootBlenderScale))).to_4x4(), - bpy.context.scene.f3d_type, - bpy.context.scene.saveTextures, - bootOptions if hackerFeaturesEnabled else None, - ) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py new file mode 100644 index 000000000..1dd3f8174 --- /dev/null +++ b/fast64_internal/oot/new_exporter/room.py @@ -0,0 +1,293 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from ...utility import CData, indent +from ..room.properties import OOTRoomHeaderProperty +from ..oot_constants import ootData +from .commands import OOTRoomCommands +from .common import Common, Actor + + +@dataclass +class RoomCommon: + roomName: str + + +@dataclass +class OOTRoomGeneral: + ### General ### + + index: int + roomShape: str + + ### Behavior ### + + roomBehavior: str + playerIdleType: str + disableWarpSongs: bool + showInvisActors: bool + + ### Skybox And Time ### + + disableSky: bool + disableSunMoon: bool + hour: int + minute: int + timeSpeed: float + echo: str + + ### Wind ### + + setWind: bool + direction: tuple[int, int, int] + strength: int + + +@dataclass +class OOTRoomObjects(RoomCommon): + objectList: list[str] + + def objectListName(self, headerIndex: int): + return f"{self.roomName}_header{headerIndex:02}_objectList" + + def getObjectLengthDefineName(self, headerIndex: int): + return f"LENGTH_{self.objectListName(headerIndex).upper()}" + + def getObjectList(self, headerIndex: int): + objectList = CData() + + listName = f"s16 {self.objectListName(headerIndex)}" + + # .h + objectList.header = f"extern {listName}[];\n" + + # .c + objectList.source = ( + (f"{listName}[{self.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + + ",\n".join(indent + objectID for objectID in self.objectList) + + ",\n};\n\n" + ) + + return objectList + + +@dataclass +class OOTRoomActors(RoomCommon): + sceneObj: Object + roomObj: Object + transform: Matrix + headerIndex: int + actorList: list[Actor] = field(default_factory=list) + + def __post_init__(self): + actorObjList: list[Object] = [ + obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" + ] + for obj in actorObjList: + actorProp = obj.ootActorProperty + if not Common.isCurrentHeaderValid(actorProp, self.headerIndex): + continue + + # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for + # the total number of actors defined in the XML. If the user deletes one, this will prevent + # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty + # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if + # the current Actor has the ID `None` to avoid export issues. + if actorProp.actorID != "None": + pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) + actor = Actor() + + if actorProp.actorID == "Custom": + actor.id = actorProp.actorIDCustom + else: + actor.id = actorProp.actorID + + if actorProp.rotOverride: + actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) + else: + actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + + actor.name = ( + ootData.actorData.actorsByID[actorProp.actorID].name.replace( + f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" + ) + if actorProp.actorID != "Custom" + else "Custom Actor" + ) + + actor.pos = pos + actor.params = actorProp.actorParam + self.actorList.append(actor) + + # Exporter + + def actorListName(self): + return f"{self.roomName}_header{self.headerIndex:02}_actorList" + + def getActorLengthDefineName(self): + return f"LENGTH_{self.actorListName().upper()}" + + def getActorListData(self): + """Returns the actor list for the current header""" + actorList = CData() + listName = f"ActorEntry {self.actorListName()}" + + # .h + actorList.header = f"extern {listName}[];\n" + + # .c + actorList.source = ( + (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") + + "\n".join(actor.getActorEntry() for actor in self.actorList) + + "};\n\n" + ) + + return actorList + + +@dataclass +class OOTRoomAlternate: + childNight: "OOTRoomHeader" = None + adultDay: "OOTRoomHeader" = None + adultNight: "OOTRoomHeader" = None + cutscene: list["OOTRoomHeader"] = field(default_factory=list) + + +@dataclass +class OOTRoomHeader(RoomCommon): + general: OOTRoomGeneral + objects: OOTRoomObjects + actors: OOTRoomActors + + def getHeaderDefines(self, headerIndex: int): + """Returns a string containing defines for actor and object lists lengths""" + headerDefines = "" + + if len(self.objects.objectList) > 0: + name = self.objects.getObjectLengthDefineName(headerIndex) + headerDefines += f"#define {name} {len(self.objects.objectList)}\n" + + if len(self.actors.actorList) > 0: + name = self.actors.getActorLengthDefineName(headerIndex) + headerDefines += f"#define {name} {len(self.actors.actorList)}\n" + + return headerDefines + + +@dataclass +class OOTRoom(Common, OOTRoomCommands): + name: str = None + altName: str = None + header: OOTRoomHeader = None + alternate: OOTRoomAlternate = None + + def __post_init__(self): + self.altHeadersName = f"{self.name}_alternateHeaders" + + def hasAlternateHeaders(self): + return ( + self.alternate is not None + and self.alternate.childNight is not None + and self.alternate.adultDay is not None + and self.alternate.adultNight is not None + and len(self.alternate.cutscene) > 0 + ) + + def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: + if headerIndex == 0: + return self.header + + for i, header in enumerate(self.altHeaderList, 1): + if headerIndex == i: + return getattr(self.alternate, header) + + for i, csHeader in enumerate(self.alternate.cutscene, 4): + if headerIndex == i: + return csHeader + + return None + + def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): + """Returns a new room header with the informations from the scene empty object""" + + objIDList = [] + for objProp in headerProp.objectList: + if objProp.objectKey == "Custom": + objIDList.append(objProp.objectIDCustom) + else: + objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) + + return OOTRoomHeader( + self.name, + OOTRoomGeneral( + headerProp.roomIndex, + headerProp.roomShape, + self.getPropValue(headerProp, "roomBehaviour"), + self.getPropValue(headerProp, "linkIdleMode"), + headerProp.disableWarpSongs, + headerProp.showInvisibleActors, + headerProp.disableSkybox, + headerProp.disableSunMoon, + 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeHours, + 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeMinutes, + max(-128, min(127, round(headerProp.timeSpeed * 0xA))), + headerProp.echo, + headerProp.setWind, + [d for d in headerProp.windVector] if headerProp.setWind else None, + headerProp.windStrength if headerProp.setWind else None, + ), + OOTRoomObjects(self.name, objIDList), + OOTRoomActors( + self.name, + self.sceneObj, + self.roomObj, + self.transform, + headerIndex, + ), + ) + + def getRoomMainC(self): + roomC = CData() + + roomHeaders: list[tuple[OOTRoomHeader, str]] = [ + (self.alternate.childNight, "Child Night"), + (self.alternate.adultDay, "Adult Day"), + (self.alternate.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.alternate.cutscene): + roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrListName = f"SceneCmd* {self.altHeadersName}" + + # .h + roomC.header = f"extern {altHeaderPtrListName}[];\n" + + # .c + altHeaderPtrList = ( + f"{altHeaderPtrListName}[]" + + " = {\n" + + "\n".join( + indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," + for i, (curHeader, _) in enumerate(roomHeaders, 1) + ) + + "\n};\n\n" + ) + + roomHeaders.insert(0, (self.header, "Child Day (Default)")) + for i, (curHeader, headerDesc) in enumerate(roomHeaders): + if curHeader is not None: + roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" + roomC.source += curHeader.getHeaderDefines(i) + roomC.append(self.getRoomCommandList(self, i)) + + if i == 0 and self.hasAlternateHeaders(): + roomC.source += altHeaderPtrList + + if len(curHeader.objects.objectList) > 0: + roomC.append(curHeader.objects.getObjectList(i)) + + if len(curHeader.actors.actorList) > 0: + roomC.append(curHeader.actors.getActorListData()) + + return roomC diff --git a/fast64_internal/oot/new_exporter/io_classes.py b/fast64_internal/oot/new_exporter/scene.py similarity index 57% rename from fast64_internal/oot/new_exporter/io_classes.py rename to fast64_internal/oot/new_exporter/scene.py index be7fb71cc..1e4c002c7 100644 --- a/fast64_internal/oot/new_exporter/io_classes.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -1,479 +1,11 @@ -import os - from dataclasses import dataclass, field -from math import radians -from mathutils import Quaternion, Matrix from bpy.types import Object -from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, toAlnum, writeFile, indent -from ..oot_utility import ootConvertTranslation, ootConvertRotation +from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ..room.properties import OOTRoomHeaderProperty -from ..actor.properties import OOTActorProperty from ..oot_constants import ootData -from .commands import OOTRoomCommands, OOTSceneCommands - - -@dataclass -class Common: - sceneObj: Object - roomObj: Object - transform: Matrix - roomIndex: int - sceneName: str - altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) - - def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): - preset = actorProp.headerSettings.sceneSetupPreset - - if preset == "All Scene Setups" or (preset == "All Non-Cutscene Scene Setups" and headerIndex < 4): - return True - - if preset == "Custom": - if actorProp.headerSettings.childDayHeader and headerIndex == 0: - return True - if actorProp.headerSettings.childNightHeader and headerIndex == 1: - return True - if actorProp.headerSettings.adultDayHeader and headerIndex == 2: - return True - if actorProp.headerSettings.adultNightHeader and headerIndex == 3: - return True - - return False - - def getPropValue(self, data, propName: str): - """Returns ``data.propName`` or ``data.propNameCustom``""" - - value = getattr(data, propName) - return value if value != "Custom" else getattr(data, f"{propName}Custom") - - def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, orientation): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - blenderTranslation, blenderRotation, scale = relativeTransform.decompose() - rotation = blenderRotation @ orientation - convertedTranslation = ootConvertTranslation(blenderTranslation) - convertedRotation = ootConvertRotation(rotation) - - return convertedTranslation, convertedRotation, scale, rotation - - def getConvertedTransform(self, transformMatrix, sceneObj, obj, handleOrientation): - # Hacky solution to handle Z-up to Y-up conversion - # We cannot apply rotation to empty, as that modifies scale - if handleOrientation: - orientation = Quaternion((1, 0, 0), radians(90.0)) - else: - orientation = Matrix.Identity(4) - return self.getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) - - def getAltHeaderListCmd(self, altName): - return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" - - def getEndCmd(self): - return indent + "SCENE_CMD_END(),\n" - - -@dataclass -class Actor: - name: str = None - id: str = None - pos: list[int] = field(default_factory=list) - rot: str = None - params: str = None - - def getActorEntry(self): - """Returns a single actor entry""" - posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" - rotData = "{ " + self.rot + " }" - - actorInfos = [self.id, posData, rotData, self.params] - infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] - - return ( - indent - + (f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class TransitionActor(Actor): - dontTransition: bool = None - roomFrom: int = None - roomTo: int = None - cameraFront: str = None - cameraBack: str = None - - def getTransitionActorEntry(self): - """Returns a single transition actor entry""" - sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] - roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" - - actorInfos = [roomData, self.id, posData, self.rot, self.params] - infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] - - return ( - (indent + f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class EntranceActor(Actor): - roomIndex: int = None - spawnIndex: int = None - - def getSpawnEntry(self): - """Returns a single spawn entry""" - return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" - - -### EXPORT UTILITY ### - - -@dataclass -class OOTRoomData: - name: str - roomMain: str = None - roomModel: str = None - roomModelInfo: str = None - - -@dataclass -class OOTSceneData: - sceneMain: str = None - sceneCollision: str = None - - -@dataclass -class OOTExporter: - scene: "OOTScene" - path: str - - header: str = "" - sceneData: OOTSceneData = None - roomList: dict[int, OOTRoomData] = field(default_factory=dict) - csList: dict[int, str] = field(default_factory=dict) - - def setRoomListData(self): - for room in self.scene.roomList: - roomData = OOTRoomData(room.name) - roomMainData = room.getRoomMainC() - - roomData.roomMain = roomMainData.source - self.header += roomMainData.header - - self.roomList[room.roomIndex] = roomData - - def setSceneData(self): - sceneData = OOTSceneData() - sceneMainData = self.scene.getSceneMainC() - - sceneData.sceneMain = sceneMainData.source - self.header += sceneMainData.header - self.sceneData = sceneData - - def writeScene(self): - scenePath = os.path.join(self.path, self.scene.sceneName + ".c") - writeFile(scenePath, self.sceneData.sceneMain) - - for room in self.roomList.values(): - roomPath = os.path.join(self.path, room.name + ".c") - writeFile(roomPath, room.roomMain) - - -### ROOM ### - - -@dataclass -class RoomCommon: - roomName: str - - -@dataclass -class OOTRoomGeneral: - ### General ### - - index: int - roomShape: str - - ### Behavior ### - - roomBehavior: str - playerIdleType: str - disableWarpSongs: bool - showInvisActors: bool - - ### Skybox And Time ### - - disableSky: bool - disableSunMoon: bool - hour: int - minute: int - timeSpeed: float - echo: str - - ### Wind ### - - setWind: bool - direction: tuple[int, int, int] - strength: int - - -@dataclass -class OOTRoomObjects(RoomCommon): - objectList: list[str] - - def objectListName(self, headerIndex: int): - return f"{self.roomName}_header{headerIndex:02}_objectList" - - def getObjectLengthDefineName(self, headerIndex: int): - return f"LENGTH_{self.objectListName(headerIndex).upper()}" - - def getObjectList(self, headerIndex: int): - objectList = CData() - - listName = f"s16 {self.objectListName(headerIndex)}" - - # .h - objectList.header = f"extern {listName}[];\n" - - # .c - objectList.source = ( - (f"{listName}[{self.getObjectLengthDefineName(headerIndex)}]" + " = {\n") - + ",\n".join(indent + objectID for objectID in self.objectList) - + ",\n};\n\n" - ) - - return objectList - - -@dataclass -class OOTRoomActors(RoomCommon): - sceneObj: Object - roomObj: Object - transform: Matrix - headerIndex: int - actorList: list[Actor] = field(default_factory=list) - - def __post_init__(self): - actorObjList: list[Object] = [ - obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" - ] - for obj in actorObjList: - actorProp = obj.ootActorProperty - if not Common.isCurrentHeaderValid(actorProp, self.headerIndex): - continue - - # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for - # the total number of actors defined in the XML. If the user deletes one, this will prevent - # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty - # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if - # the current Actor has the ID `None` to avoid export issues. - if actorProp.actorID != "None": - pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) - actor = Actor() - - if actorProp.actorID == "Custom": - actor.id = actorProp.actorIDCustom - else: - actor.id = actorProp.actorID - - if actorProp.rotOverride: - actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) - else: - actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - - actor.name = ( - ootData.actorData.actorsByID[actorProp.actorID].name.replace( - f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" - ) - if actorProp.actorID != "Custom" - else "Custom Actor" - ) - - actor.pos = pos - actor.params = actorProp.actorParam - self.actorList.append(actor) - - # Exporter - - def actorListName(self): - return f"{self.roomName}_header{self.headerIndex:02}_actorList" - - def getActorLengthDefineName(self): - return f"LENGTH_{self.actorListName().upper()}" - - def getActorListData(self): - """Returns the actor list for the current header""" - actorList = CData() - listName = f"ActorEntry {self.actorListName()}" - - # .h - actorList.header = f"extern {listName}[];\n" - - # .c - actorList.source = ( - (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") - + "\n".join(actor.getActorEntry() for actor in self.actorList) - + "};\n\n" - ) - - return actorList - - -@dataclass -class OOTRoomAlternate: - childNight: "OOTRoomHeader" = None - adultDay: "OOTRoomHeader" = None - adultNight: "OOTRoomHeader" = None - cutscene: list["OOTRoomHeader"] = field(default_factory=list) - - -@dataclass -class OOTRoomHeader(RoomCommon): - general: OOTRoomGeneral - objects: OOTRoomObjects - actors: OOTRoomActors - - def getHeaderDefines(self, headerIndex: int): - """Returns a string containing defines for actor and object lists lengths""" - headerDefines = "" - - if len(self.objects.objectList) > 0: - name = self.objects.getObjectLengthDefineName(headerIndex) - headerDefines += f"#define {name} {len(self.objects.objectList)}\n" - - if len(self.actors.actorList) > 0: - name = self.actors.getActorLengthDefineName(headerIndex) - headerDefines += f"#define {name} {len(self.actors.actorList)}\n" - - return headerDefines - - -@dataclass -class OOTRoom(Common, OOTRoomCommands): - name: str = None - altName: str = None - header: OOTRoomHeader = None - alternate: OOTRoomAlternate = None - - def __post_init__(self): - self.altHeadersName = f"{self.name}_alternateHeaders" - - def hasAlternateHeaders(self): - return ( - self.alternate is not None - and self.alternate.childNight is not None - and self.alternate.adultDay is not None - and self.alternate.adultNight is not None - and len(self.alternate.cutscene) > 0 - ) - - def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: - if headerIndex == 0: - return self.header - - for i, header in enumerate(self.altHeaderList, 1): - if headerIndex == i: - return getattr(self.alternate, header) - - for i, csHeader in enumerate(self.alternate.cutscene, 4): - if headerIndex == i: - return csHeader - - return None - - def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): - """Returns a new room header with the informations from the scene empty object""" - - objIDList = [] - for objProp in headerProp.objectList: - if objProp.objectKey == "Custom": - objIDList.append(objProp.objectIDCustom) - else: - objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) - - return OOTRoomHeader( - self.name, - OOTRoomGeneral( - headerProp.roomIndex, - headerProp.roomShape, - self.getPropValue(headerProp, "roomBehaviour"), - self.getPropValue(headerProp, "linkIdleMode"), - headerProp.disableWarpSongs, - headerProp.showInvisibleActors, - headerProp.disableSkybox, - headerProp.disableSunMoon, - 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeHours, - 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeMinutes, - max(-128, min(127, round(headerProp.timeSpeed * 0xA))), - headerProp.echo, - headerProp.setWind, - [d for d in headerProp.windVector] if headerProp.setWind else None, - headerProp.windStrength if headerProp.setWind else None, - ), - OOTRoomObjects(self.name, objIDList), - OOTRoomActors( - self.name, - self.sceneObj, - self.roomObj, - self.transform, - headerIndex, - ), - ) - - def getRoomMainC(self): - roomC = CData() - - roomHeaders: list[tuple[OOTRoomHeader, str]] = [ - (self.alternate.childNight, "Child Night"), - (self.alternate.adultDay, "Adult Day"), - (self.alternate.adultNight, "Adult Night"), - ] - - for i, csHeader in enumerate(self.alternate.cutscene): - roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrListName = f"SceneCmd* {self.altHeadersName}" - - # .h - roomC.header = f"extern {altHeaderPtrListName}[];\n" - - # .c - altHeaderPtrList = ( - f"{altHeaderPtrListName}[]" - + " = {\n" - + "\n".join( - indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," - for i, (curHeader, _) in enumerate(roomHeaders, 1) - ) - + "\n};\n\n" - ) - - roomHeaders.insert(0, (self.header, "Child Day (Default)")) - for i, (curHeader, headerDesc) in enumerate(roomHeaders): - if curHeader is not None: - roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - roomC.source += curHeader.getHeaderDefines(i) - roomC.append(self.getRoomCommandList(self, i)) - - if i == 0 and self.hasAlternateHeaders(): - roomC.source += altHeaderPtrList - - if len(curHeader.objects.objectList) > 0: - roomC.append(curHeader.objects.getObjectList(i)) - - if len(curHeader.actors.actorList) > 0: - roomC.append(curHeader.actors.getActorListData()) - - return roomC - - -### SCENE ### +from .commands import OOTSceneCommands +from .common import Common, TransitionActor, EntranceActor +from .room import OOTRoom @dataclass diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 331e5b8bb..b929d7567 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -12,7 +12,7 @@ from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles -from ..new_exporter import exportScene +from ..new_exporter import OOTSceneExport def ootRemoveSceneC(exportInfo): @@ -176,7 +176,17 @@ def execute(self, context): bootOptions = context.scene.fast64.oot.bootupSceneOptions hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled - exportScene(exportInfo).exportScene() + OOTSceneExport( + exportInfo, + obj, + exportInfo.name, + scaleValue, + Matrix.Diagonal(Vector((scaleValue, scaleValue, scaleValue))).to_4x4(), + bpy.context.scene.f3d_type, + bpy.context.scene.saveTextures, + bootOptions if hackerFeaturesEnabled else None, + ).export() + # ootExportSceneToC( # obj, # finalTransform, From 8efbad48ea4c801f320fb3f881f2adf358aade11 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 01:01:46 +0200 Subject: [PATCH 05/98] moar cleanup --- fast64_internal/oot/new_exporter/commands.py | 53 ++++++----- fast64_internal/oot/new_exporter/common.py | 8 +- fast64_internal/oot/new_exporter/exporter.py | 32 +++---- fast64_internal/oot/new_exporter/room.py | 64 +++++++------ fast64_internal/oot/new_exporter/scene.py | 97 ++++++++++---------- fast64_internal/oot/oot_object.py | 4 +- 6 files changed, 131 insertions(+), 127 deletions(-) diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 9fa2ae2fe..c384971b1 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -3,15 +3,22 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from .scene import OOTScene, OOTSceneGeneral, OOTSceneHeader, OOTSceneLighting, OOTSceneCutscene, OOTSceneActors - from .room import OOTRoom, OOTRoomHeader, OOTRoomGeneral, OOTRoomObjects, OOTRoomActors + from .scene import ( + OOTScene, + OOTSceneHeaderInfos, + OOTSceneHeader, + OOTSceneHeaderLighting, + OOTSceneHeaderCutscene, + OOTSceneHeaderActors, + ) + from .room import OOTRoom, OOTRoomHeader, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors class OOTRoomCommands: - def getEchoSettingsCmd(self, infos: "OOTRoomGeneral"): + def getEchoSettingsCmd(self, infos: "OOTRoomHeaderInfos"): return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" - def getRoomBehaviourCmd(self, infos: "OOTRoomGeneral"): + def getRoomBehaviourCmd(self, infos: "OOTRoomHeaderInfos"): showInvisibleActors = "true" if infos.showInvisActors else "false" disableWarpSongs = "true" if infos.disableWarpSongs else "false" @@ -21,16 +28,16 @@ def getRoomBehaviourCmd(self, infos: "OOTRoomGeneral"): + ")" ) - def getSkyboxDisablesCmd(self, infos: "OOTRoomGeneral"): + def getSkyboxDisablesCmd(self, infos: "OOTRoomHeaderInfos"): disableSkybox = "true" if infos.disableSky else "false" disableSunMoon = "true" if infos.disableSunMoon else "false" return indent + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})" - def getTimeSettingsCmd(self, infos: "OOTRoomGeneral"): + def getTimeSettingsCmd(self, infos: "OOTRoomHeaderInfos"): return indent + f"SCENE_CMD_TIME_SETTINGS({infos.hour}, {infos.minute}, {infos.timeSpeed})" - def getWindSettingsCmd(self, infos: "OOTRoomGeneral"): + def getWindSettingsCmd(self, infos: "OOTRoomHeaderInfos"): return ( indent + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" ) @@ -38,12 +45,12 @@ def getWindSettingsCmd(self, infos: "OOTRoomGeneral"): # def getRoomShapeCmd(self, infos: "OOTRoom"): # return indent + f"SCENE_CMD_ROOM_SHAPE(&{infos.mesh.headerName()})" - def getObjectListCmd(self, objects: "OOTRoomObjects", headerIndex: int): + def getObjectListCmd(self, objects: "OOTRoomHeaderObjects", headerIndex: int): return ( indent + "SCENE_CMD_OBJECT_LIST(" ) + f"{objects.getObjectLengthDefineName(headerIndex)}, {objects.objectListName(headerIndex)}),\n" - def getActorListCmd(self, actors: "OOTRoomActors", headerIndex: int): + def getActorListCmd(self, actors: "OOTRoomHeaderActors", headerIndex: int): return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.actorListName()}),\n" def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): @@ -60,9 +67,9 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): ] roomCmdData = ( - (room.getAltHeaderListCmd(room.altHeadersName) if room.hasAlternateHeaders() else "") - + (",\n".join(getCmd(curHeader.general) for getCmd in getCmdFuncList) + ",\n") - + (self.getWindSettingsCmd(curHeader.general) if curHeader.general.setWind else "") + (room.getAltHeaderListCmd(room.altHeader.name) if room.hasAlternateHeaders() else "") + + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdFuncList) + ",\n") + + (self.getWindSettingsCmd(curHeader.infos) if curHeader.infos.setWind else "") + (self.getObjectListCmd(curHeader.objects, headerIndex) if len(curHeader.objects.objectList) > 0 else "") + (self.getActorListCmd(curHeader.actors, headerIndex) if len(curHeader.actors.actorList) > 0 else "") + room.getEndCmd() @@ -78,29 +85,29 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): class OOTSceneCommands: - def getSoundSettingsCmd(self, infos: "OOTSceneGeneral"): + def getSoundSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" def getRoomListCmd(self, scene: "OOTScene"): return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName}),\n" - def getTransActorListCmd(self, actors: "OOTSceneActors"): + def getTransActorListCmd(self, actors: "OOTSceneHeaderActors"): return ( indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" ) + f"{len(actors.transitionActorList)}, {actors.transActorListName})" - def getMiscSettingsCmd(self, infos: "OOTSceneGeneral"): + def getMiscSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" # def getColHeaderCmd(self, outScene: OOTScene): # return indent + f"SCENE_CMD_COL_HEADER(&{outScene.collision.headerName()})" - def getSpawnListCmd(self, actors: "OOTSceneActors"): + def getSpawnListCmd(self, actors: "OOTSceneHeaderActors"): return ( indent + "SCENE_CMD_ENTRANCE_LIST(" ) + f"{actors.entranceListName if len(actors.entranceActorList) > 0 else 'NULL'})" - def getSpecialFilesCmd(self, infos: "OOTSceneGeneral"): + def getSpecialFilesCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" # def getPathListCmd(self, outScene: "OOTScene", headerIndex: int): @@ -115,19 +122,19 @@ def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'}),\n" ) - def getSkyboxSettingsCmd(self, infos: "OOTSceneGeneral", lights: "OOTSceneLighting"): + def getSkyboxSettingsCmd(self, infos: "OOTSceneHeaderInfos", lights: "OOTSceneHeaderLighting"): return indent + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" def getExitListCmd(self, scene: "OOTScene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name}),\n" - def getLightSettingsCmd(self, lights: "OOTSceneLighting"): + def getLightSettingsCmd(self, lights: "OOTSceneHeaderLighting"): return ( indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" ) + f"{len(lights.settings)}, {lights.name if len(lights.settings) > 0 else 'NULL'}),\n" - def getCutsceneDataCmd(self, cs: "OOTSceneCutscene"): + def getCutsceneDataCmd(self, cs: "OOTSceneHeaderCutscene"): match cs.writeType: case "Object": csDataName = cs.csObj.name @@ -164,11 +171,11 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) sceneCmdData = ( - (scene.getAltHeaderListCmd(scene.altName) if scene.hasAlternateHeaders() else "") + (scene.getAltHeaderListCmd(scene.altHeader.name) if scene.hasAlternateHeaders() else "") + self.getRoomListCmd(scene) - + self.getSkyboxSettingsCmd(curHeader.general, curHeader.lighting) + + self.getSkyboxSettingsCmd(curHeader.infos, curHeader.lighting) # + (",\n".join(getCmd(scene) for getCmd in getCmdFunc1ArgList) + ",\n") - + (",\n".join(getCmd(curHeader.general) for getCmd in getCmdGeneralList) + ",\n") + + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") + (self.getExitListCmd(scene, headerIndex) if len(curHeader.exits.exitList) > 0 else "") + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + self.getSpawnActorListCmd(scene, headerIndex) diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/common.py index ab51cabf8..fe383e69a 100644 --- a/fast64_internal/oot/new_exporter/common.py +++ b/fast64_internal/oot/new_exporter/common.py @@ -7,14 +7,14 @@ from ..actor.properties import OOTActorProperty +altHeaderList = ["childNight", "adultDay", "adultNight"] + + @dataclass class Common: sceneObj: Object - roomObj: Object transform: Matrix - roomIndex: int - sceneName: str - altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) + roomIndex: int = None def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): preset = actorProp.headerSettings.sceneSetupPreset diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 77268af07..dbd8ab3f3 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -17,8 +17,9 @@ from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData from ..oot_object import addMissingObjectsToAllRoomHeadersNew -from .scene import OOTScene, OOTSceneAlternate -from .room import OOTRoom, OOTRoomAlternate +from .common import altHeaderList +from .scene import OOTScene, OOTSceneAlternateHeader +from .room import OOTRoom, OOTRoomAlternateHeader from ..oot_utility import ( ExportInfo, @@ -55,7 +56,6 @@ class OOTSceneExport: saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions dlFormat: DLFormat = DLFormat.Static - altHeaderList: list[str] = field(default_factory=lambda: ["childNight", "adultDay", "adultNight"]) scene: OOTScene = None path: str = None @@ -78,20 +78,20 @@ def getNewRoomList(self): altProp = roomObj.ootAlternateRoomHeaders roomIndex = roomObj.ootRoomHeader.roomIndex roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" - roomData = OOTRoom(self.sceneObj, roomObj, self.transform, roomIndex, self.sceneName, name=roomName) - altHeaderData = OOTRoomAlternate() - roomData.header = roomData.getNewRoomHeader(roomObj.ootRoomHeader) + roomData = OOTRoom(self.sceneObj, self.transform, roomIndex, roomName, roomObj) + altHeaderData = OOTRoomAlternateHeader(f"{roomData.name}_alternateHeaders") + roomData.mainHeader = roomData.getNewRoomHeader(roomObj.ootRoomHeader) - for i, header in enumerate(self.altHeaderList, 1): + for i, header in enumerate(altHeaderList, 1): altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: setattr(altHeaderData, header, roomData.getNewRoomHeader(altP, i)) - altHeaderData.cutscene = [ + altHeaderData.cutscenes = [ roomData.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - roomData.alternate = altHeaderData + roomData.altHeader = altHeaderData if roomIndex in processedRooms: raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") @@ -104,20 +104,20 @@ def getNewRoomList(self): def getNewScene(self): altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = OOTScene(self.sceneObj, None, self.transform, None, f"{toAlnum(self.sceneName)}_scene") - altHeaderData = OOTSceneAlternate() - sceneData.header = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) + sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") + altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") + sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) - for i, header in enumerate(self.altHeaderList, 1): + for i, header in enumerate(altHeaderList, 1): altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) - altHeaderData.cutscene = [ + altHeaderData.cutscenes = [ sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - sceneData.alternate = altHeaderData + sceneData.altHeader = altHeaderData sceneData.roomList = self.getNewRoomList() sceneData.validateScene() @@ -174,7 +174,7 @@ def setSceneData(self): self.sceneData = sceneData def writeScene(self): - scenePath = os.path.join(self.path, self.scene.sceneName + ".c") + scenePath = os.path.join(self.path, self.scene.name + ".c") writeFile(scenePath, self.sceneData.sceneMain) for room in self.roomList.values(): diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 1dd3f8174..755678857 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -5,7 +5,7 @@ from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData from .commands import OOTRoomCommands -from .common import Common, Actor +from .common import Common, Actor, altHeaderList @dataclass @@ -14,7 +14,7 @@ class RoomCommon: @dataclass -class OOTRoomGeneral: +class OOTRoomHeaderInfos: ### General ### index: int @@ -44,7 +44,7 @@ class OOTRoomGeneral: @dataclass -class OOTRoomObjects(RoomCommon): +class OOTRoomHeaderObjects(RoomCommon): objectList: list[str] def objectListName(self, headerIndex: int): @@ -72,7 +72,7 @@ def getObjectList(self, headerIndex: int): @dataclass -class OOTRoomActors(RoomCommon): +class OOTRoomHeaderActors(RoomCommon): sceneObj: Object roomObj: Object transform: Matrix @@ -146,18 +146,19 @@ def getActorListData(self): @dataclass -class OOTRoomAlternate: +class OOTRoomAlternateHeader: + name: str childNight: "OOTRoomHeader" = None adultDay: "OOTRoomHeader" = None adultNight: "OOTRoomHeader" = None - cutscene: list["OOTRoomHeader"] = field(default_factory=list) + cutscenes: list["OOTRoomHeader"] = field(default_factory=list) @dataclass class OOTRoomHeader(RoomCommon): - general: OOTRoomGeneral - objects: OOTRoomObjects - actors: OOTRoomActors + infos: OOTRoomHeaderInfos + objects: OOTRoomHeaderObjects + actors: OOTRoomHeaderActors def getHeaderDefines(self, headerIndex: int): """Returns a string containing defines for actor and object lists lengths""" @@ -177,31 +178,28 @@ def getHeaderDefines(self, headerIndex: int): @dataclass class OOTRoom(Common, OOTRoomCommands): name: str = None - altName: str = None - header: OOTRoomHeader = None - alternate: OOTRoomAlternate = None - - def __post_init__(self): - self.altHeadersName = f"{self.name}_alternateHeaders" + roomObj: Object = None + mainHeader: OOTRoomHeader = None + altHeader: OOTRoomAlternateHeader = None def hasAlternateHeaders(self): return ( - self.alternate is not None - and self.alternate.childNight is not None - and self.alternate.adultDay is not None - and self.alternate.adultNight is not None - and len(self.alternate.cutscene) > 0 + self.altHeader is not None + and self.altHeader.childNight is not None + and self.altHeader.adultDay is not None + and self.altHeader.adultNight is not None + and len(self.altHeader.cutscenes) > 0 ) def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: if headerIndex == 0: - return self.header + return self.mainHeader - for i, header in enumerate(self.altHeaderList, 1): + for i, header in enumerate(altHeaderList, 1): if headerIndex == i: - return getattr(self.alternate, header) + return getattr(self.altHeader, header) - for i, csHeader in enumerate(self.alternate.cutscene, 4): + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): if headerIndex == i: return csHeader @@ -219,7 +217,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = return OOTRoomHeader( self.name, - OOTRoomGeneral( + OOTRoomHeaderInfos( headerProp.roomIndex, headerProp.roomShape, self.getPropValue(headerProp, "roomBehaviour"), @@ -236,8 +234,8 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = [d for d in headerProp.windVector] if headerProp.setWind else None, headerProp.windStrength if headerProp.setWind else None, ), - OOTRoomObjects(self.name, objIDList), - OOTRoomActors( + OOTRoomHeaderObjects(self.name, objIDList), + OOTRoomHeaderActors( self.name, self.sceneObj, self.roomObj, @@ -250,15 +248,15 @@ def getRoomMainC(self): roomC = CData() roomHeaders: list[tuple[OOTRoomHeader, str]] = [ - (self.alternate.childNight, "Child Night"), - (self.alternate.adultDay, "Adult Day"), - (self.alternate.adultNight, "Adult Night"), + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), ] - for i, csHeader in enumerate(self.alternate.cutscene): + for i, csHeader in enumerate(self.altHeader.cutscenes): roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - altHeaderPtrListName = f"SceneCmd* {self.altHeadersName}" + altHeaderPtrListName = f"SceneCmd* {self.altHeader.name}" # .h roomC.header = f"extern {altHeaderPtrListName}[];\n" @@ -274,7 +272,7 @@ def getRoomMainC(self): + "\n};\n\n" ) - roomHeaders.insert(0, (self.header, "Child Day (Default)")) + roomHeaders.insert(0, (self.mainHeader, "Child Day (Default)")) for i, (curHeader, headerDesc) in enumerate(roomHeaders): if curHeader is not None: roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 1e4c002c7..2b2871b42 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -4,14 +4,13 @@ from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ..oot_constants import ootData from .commands import OOTSceneCommands -from .common import Common, TransitionActor, EntranceActor +from .common import Common, TransitionActor, EntranceActor, altHeaderList from .room import OOTRoom @dataclass class SceneCommon: headerName: str - name: str = None @dataclass @@ -80,7 +79,7 @@ def getLightSettingsEntry(self, index: int): @dataclass -class OOTSceneGeneral: +class OOTSceneHeaderInfos: ### General ### keepObjectID: str @@ -110,9 +109,10 @@ class OOTSceneGeneral: @dataclass -class OOTSceneLighting(SceneCommon): +class OOTSceneHeaderLighting(SceneCommon): envLightMode: str = None settings: list[EnvLightSettings] = field(default_factory=list) + name: str = None def __post_init__(self): self.name = f"{self.headerName}_lightSettings" @@ -135,17 +135,19 @@ def getEnvLightSettingsC(self): @dataclass -class OOTSceneCutscene: +class OOTSceneHeaderCutscene: writeType: str writeCutscene: bool csObj: Object csWriteCustom: str extraCutscenes: list[Object] + name: str = None @dataclass -class OOTSceneExits(SceneCommon): +class OOTSceneHeaderExits(SceneCommon): exitList: list[tuple[int, str]] = field(default_factory=list) + name: str = None def __post_init__(self): self.name = f"{self.headerName}_exitList" @@ -169,7 +171,7 @@ def getExitListC(self): @dataclass -class OOTSceneActors(SceneCommon): +class OOTSceneHeaderActors(SceneCommon): transitionActorList: list[TransitionActor] = field(default_factory=list) entranceActorList: list[EntranceActor] = field(default_factory=list) @@ -236,20 +238,21 @@ def getTransActorListC(self): @dataclass -class OOTSceneAlternate: +class OOTSceneAlternateHeader: + name: str childNight: "OOTSceneHeader" = None adultDay: "OOTSceneHeader" = None adultNight: "OOTSceneHeader" = None - cutscene: list["OOTSceneHeader"] = field(default_factory=list) + cutscenes: list["OOTSceneHeader"] = field(default_factory=list) @dataclass class OOTSceneHeader: - general: OOTSceneGeneral - lighting: OOTSceneLighting - cutscene: OOTSceneCutscene - exits: OOTSceneExits - actors: OOTSceneActors + infos: OOTSceneHeaderInfos + lighting: OOTSceneHeaderLighting + cutscene: OOTSceneHeaderCutscene + exits: OOTSceneHeaderExits + actors: OOTSceneHeaderActors def getHeaderC(self): headerData = CData() @@ -280,18 +283,16 @@ def getHeaderC(self): @dataclass class OOTScene(Common, OOTSceneCommands): + name: str = None headerIndex: int = None headerName: str = None - header: OOTSceneHeader = None - alternate: OOTSceneAlternate = None + mainHeader: OOTSceneHeader = None + altHeader: OOTSceneAlternateHeader = None roomList: list[OOTRoom] = field(default_factory=list) - - altName: str = None roomListName: str = None def __post_init__(self): - self.altName = f"{self.sceneName}_alternateHeaders" - self.roomListName = f"{self.sceneName}_roomList" + self.roomListName = f"{self.name}_roomList" def validateRoomIndices(self): for i, room in enumerate(self.roomList): @@ -309,22 +310,22 @@ def validateScene(self): def hasAlternateHeaders(self): return ( - self.alternate is not None - and self.alternate.childNight is not None - and self.alternate.adultDay is not None - and self.alternate.adultNight is not None - and len(self.alternate.cutscene) > 0 + self.altHeader is not None + and self.altHeader.childNight is not None + and self.altHeader.adultDay is not None + and self.altHeader.adultNight is not None + and len(self.altHeader.cutscenes) > 0 ) def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: if headerIndex == 0: - return self.header + return self.mainHeader - for i, header in enumerate(self.altHeaderList, 1): + for i, header in enumerate(altHeaderList, 1): if headerIndex == i: - return getattr(self.alternate, header) + return getattr(self.altHeader, header) - for i, csHeader in enumerate(self.alternate.cutscene, 4): + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): if headerIndex == i: return csHeader @@ -441,7 +442,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int """Returns a single scene header with the informations from the scene empty object""" self.headerIndex = headerIndex - self.headerName = f"{self.sceneName}_header{self.headerIndex:02}" + self.headerName = f"{self.name}_header{self.headerIndex:02}" if headerProp.csWriteType == "Embedded": raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") @@ -475,7 +476,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int ) return OOTSceneHeader( - OOTSceneGeneral( + OOTSceneHeaderInfos( self.getPropValue(headerProp, "globalObject"), self.getPropValue(headerProp, "naviCup"), self.getPropValue(headerProp.sceneTableEntry, "drawConfig"), @@ -489,28 +490,26 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getPropValue(headerProp, "mapLocation"), self.getPropValue(headerProp, "cameraMode"), ), - OOTSceneLighting( + OOTSceneHeaderLighting( self.headerName, - envLightMode=lightMode, - settings=lightSettings, + lightMode, + lightSettings, ), - OOTSceneCutscene( + OOTSceneHeaderCutscene( headerProp.csWriteType, headerProp.writeCutscene, headerProp.csWriteObject, headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, [csObj for csObj in headerProp.extraCutscenes], ), - OOTSceneExits(self.headerName, exitList=self.getExitListFromProps(headerProp)), - OOTSceneActors( + OOTSceneHeaderExits(self.headerName, self.getExitListFromProps(headerProp)), + OOTSceneHeaderActors( self.headerName, - transitionActorList=self.getTransActorListFromProps(), - entranceActorList=self.getEntranceActorListFromProps(), + self.getTransActorListFromProps(), + self.getEntranceActorListFromProps(), ), ) - # Export - def getRoomListC(self): roomList = CData() listName = f"RomFile {self.roomListName}[]" @@ -524,7 +523,7 @@ def getRoomListC(self): # .h roomList.header += f"extern {listName};\n" - if not self.header.general.useDummyRoomList: + if not self.mainHeader.infos.useDummyRoomList: # Write externs for rom segments roomList.header += "".join( f"extern u8 {startName}[];\n" + f"extern u8 {stopName}[];\n" for startName, stopName in segNames @@ -533,7 +532,7 @@ def getRoomListC(self): # .c roomList.source = listName + " = {\n" - if self.header.general.useDummyRoomList: + if self.mainHeader.infos.useDummyRoomList: roomList.source = ( "// Dummy room list\n" + roomList.source + ((indent + "{ NULL, NULL },\n") * len(self.roomList)) ) @@ -552,12 +551,12 @@ def getSceneMainC(self): sceneC = CData() headers: list[tuple[OOTSceneHeader, str]] = [ - (self.alternate.childNight, "Child Night"), - (self.alternate.adultDay, "Adult Day"), - (self.alternate.adultNight, "Adult Night"), + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), ] - for i, csHeader in enumerate(self.alternate.cutscene): + for i, csHeader in enumerate(self.altHeader.cutscenes): headers.append((csHeader, f"Cutscene No. {i + 1}")) altHeaderPtrs = "\n".join( @@ -565,7 +564,7 @@ def getSceneMainC(self): for i, (curHeader, _) in enumerate(headers, 1) ) - headers.insert(0, (self.header, "Child Day (Default)")) + headers.insert(0, (self.mainHeader, "Child Day (Default)")) for i, (curHeader, headerDesc) in enumerate(headers): if curHeader is not None: sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" @@ -573,7 +572,7 @@ def getSceneMainC(self): if i == 0: if self.hasAlternateHeaders(): - altHeaderListName = f"SceneCmd* {self.altName}[]" + altHeaderListName = f"SceneCmd* {self.altHeader.name}[]" sceneC.header += f"extern {altHeaderListName};\n" sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 779e0e033..4446453a7 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -61,9 +61,9 @@ def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room, ootData: OoT_Dat Adds missing objects (required by actors) to all headers of a room, both to the roomObj empty and the exported room """ - sceneLayers = [room, room.alternate.childNight, room.alternate.adultDay, room.alternate.adultNight] + sceneLayers = [room, room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight] for i, layer in enumerate(sceneLayers): if layer is not None: addMissingObjectsToRoomHeaderNew(roomObj, layer, ootData, i) - for i in range(len(room.alternate.cutscene)): + for i in range(len(room.altHeader.cutscenes)): addMissingObjectsToRoomHeaderNew(roomObj, room.cutsceneHeaders[i], ootData, i + 4) From 9a4b0290c22ff961aab05eb98d2ffb1859604a3b Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 02:54:59 +0200 Subject: [PATCH 06/98] improvements --- fast64_internal/oot/new_exporter/commands.py | 50 ++++---- fast64_internal/oot/new_exporter/exporter.py | 58 +++++++-- fast64_internal/oot/new_exporter/room.py | 125 +++++++++---------- fast64_internal/oot/new_exporter/scene.py | 93 ++++++-------- fast64_internal/oot/oot_object.py | 16 +-- 5 files changed, 182 insertions(+), 160 deletions(-) diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index c384971b1..fd0f991e4 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from .room import OOTRoom, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors from .scene import ( OOTScene, OOTSceneHeaderInfos, @@ -11,7 +12,6 @@ OOTSceneHeaderCutscene, OOTSceneHeaderActors, ) - from .room import OOTRoom, OOTRoomHeader, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors class OOTRoomCommands: @@ -45,20 +45,18 @@ def getWindSettingsCmd(self, infos: "OOTRoomHeaderInfos"): # def getRoomShapeCmd(self, infos: "OOTRoom"): # return indent + f"SCENE_CMD_ROOM_SHAPE(&{infos.mesh.headerName()})" - def getObjectListCmd(self, objects: "OOTRoomHeaderObjects", headerIndex: int): - return ( - indent + "SCENE_CMD_OBJECT_LIST(" - ) + f"{objects.getObjectLengthDefineName(headerIndex)}, {objects.objectListName(headerIndex)}),\n" + def getObjectListCmd(self, objects: "OOTRoomHeaderObjects"): + return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getObjectLengthDefineName()}, {objects.name}),\n" - def getActorListCmd(self, actors: "OOTRoomHeaderActors", headerIndex: int): - return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.actorListName()}),\n" + def getActorListCmd(self, actors: "OOTRoomHeaderActors"): + return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.name}),\n" def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): cmdListData = CData() curHeader = room.getRoomHeaderFromIndex(headerIndex) - listName = f"SceneCmd {curHeader.roomName}_header{headerIndex:02}" + listName = f"SceneCmd {curHeader.name}" - getCmdFuncList = [ + getCmdFuncInfosList = [ self.getEchoSettingsCmd, self.getRoomBehaviourCmd, self.getSkyboxDisablesCmd, @@ -66,12 +64,15 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): # self.getRoomShapeCmd, ] + if curHeader.infos.setWind: + getCmdFuncInfosList.append(self.getWindSettingsCmd) + + hasAltHeaders = headerIndex == 0 and room.hasAlternateHeaders() roomCmdData = ( - (room.getAltHeaderListCmd(room.altHeader.name) if room.hasAlternateHeaders() else "") - + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdFuncList) + ",\n") - + (self.getWindSettingsCmd(curHeader.infos) if curHeader.infos.setWind else "") - + (self.getObjectListCmd(curHeader.objects, headerIndex) if len(curHeader.objects.objectList) > 0 else "") - + (self.getActorListCmd(curHeader.actors, headerIndex) if len(curHeader.actors.actorList) > 0 else "") + (room.getAltHeaderListCmd(room.altHeader.name) if hasAltHeaders else "") + + (self.getObjectListCmd(curHeader.objects) if len(curHeader.objects.objectList) > 0 else "") + + (self.getActorListCmd(curHeader.actors) if len(curHeader.actors.actorList) > 0 else "") + + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdFuncInfosList) + ",\n") + room.getEndCmd() ) @@ -119,7 +120,7 @@ def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): return ( (indent + "SCENE_CMD_SPAWN_LIST(") + f"{len(curHeader.actors.entranceActorList)}, " - + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'}),\n" + + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'})" ) def getSkyboxSettingsCmd(self, infos: "OOTSceneHeaderInfos", lights: "OOTSceneHeaderLighting"): @@ -127,7 +128,7 @@ def getSkyboxSettingsCmd(self, infos: "OOTSceneHeaderInfos", lights: "OOTSceneHe def getExitListCmd(self, scene: "OOTScene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) - return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name}),\n" + return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name})" def getLightSettingsCmd(self, lights: "OOTSceneHeaderLighting"): return ( @@ -145,9 +146,11 @@ def getCutsceneDataCmd(self, cs: "OOTSceneHeaderCutscene"): def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", headerIndex: int): cmdListData = CData() - listName = f"SceneCmd {scene.headerName}" + listName = f"SceneCmd {curHeader.name}" - getCmdFunc1ArgList = [ + getCmdFunc1List = [ + self.getExitListCmd, + self.getSpawnActorListCmd, # self.getColHeaderCmd, ] @@ -170,16 +173,15 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he # if scene.writeCutscene: # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) + hasAltHeaders = headerIndex == 0 and scene.hasAlternateHeaders() sceneCmdData = ( - (scene.getAltHeaderListCmd(scene.altHeader.name) if scene.hasAlternateHeaders() else "") + (scene.getAltHeaderListCmd(scene.altHeader.name) if hasAltHeaders else "") + self.getRoomListCmd(scene) + self.getSkyboxSettingsCmd(curHeader.infos, curHeader.lighting) - # + (",\n".join(getCmd(scene) for getCmd in getCmdFunc1ArgList) + ",\n") - + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") - + (self.getExitListCmd(scene, headerIndex) if len(curHeader.exits.exitList) > 0 else "") - + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") - + self.getSpawnActorListCmd(scene, headerIndex) + self.getLightSettingsCmd(curHeader.lighting) + # + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") + + (",\n".join(getCmd(scene, headerIndex) for getCmd in getCmdFunc1List) + ",\n") + (",\n".join(getCmd(curHeader.actors) for getCmd in getCmdActorList) + ",\n") + scene.getEndCmd() ) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index dbd8ab3f3..7fa9f2b15 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -43,6 +43,7 @@ class OOTRoomData: class OOTSceneData: sceneMain: str = None sceneCollision: str = None + sceneCutscenes: list[str] = field(default_factory=list) @dataclass @@ -62,7 +63,7 @@ class OOTSceneExport: header: str = "" sceneData: OOTSceneData = None roomList: dict[int, OOTRoomData] = field(default_factory=dict) - csList: dict[int, str] = field(default_factory=dict) + hasCutscenes: bool = False def getNewRoomList(self): processedRooms = [] @@ -81,17 +82,22 @@ def getNewRoomList(self): roomData = OOTRoom(self.sceneObj, self.transform, roomIndex, roomName, roomObj) altHeaderData = OOTRoomAlternateHeader(f"{roomData.name}_alternateHeaders") roomData.mainHeader = roomData.getNewRoomHeader(roomObj.ootRoomHeader) + hasAltHeader = False for i, header in enumerate(altHeaderList, 1): altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: + hasAltHeader = True setattr(altHeaderData, header, roomData.getNewRoomHeader(altP, i)) altHeaderData.cutscenes = [ roomData.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - roomData.altHeader = altHeaderData + if len(altHeaderData.cutscenes) > 0: + hasAltHeader = True + + roomData.altHeader = altHeaderData if hasAltHeader else None if roomIndex in processedRooms: raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") @@ -107,17 +113,22 @@ def getNewScene(self): sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) + hasAltHeader = False for i, header in enumerate(altHeaderList, 1): altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) + hasAltHeader = True altHeaderData.cutscenes = [ sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - sceneData.altHeader = altHeaderData + if len(altHeaderData.cutscenes) > 0: + hasAltHeader = True + + sceneData.altHeader = altHeaderData if hasAltHeader else None sceneData.roomList = self.getNewRoomList() sceneData.validateScene() @@ -144,6 +155,13 @@ def getNewSceneFromEmptyObject(self): sceneData = None try: sceneData = self.getNewScene() + self.hasCutscenes = sceneData.mainHeader.cutscene.writeCutscene + + if not self.hasCutscenes: + for cs in sceneData.altHeader.cutscenes: + if cs.cutscene.writeCutscene: + self.hasCutscenes = True + break ootCleanupScene(originalSceneObj, allObjs) except Exception as e: @@ -173,13 +191,38 @@ def setSceneData(self): self.header += sceneMainData.header self.sceneData = sceneData + def setIncludeData(self): + suffix = "\n\n" + common = "\n".join( + elem + for elem in [ + '#include "ultra64.h"', + '#include "macros.h"', + '#include "z64.h"', + f'#include "{self.scene.name}.h"', + ] + ) + self.sceneData.sceneMain = common + suffix + self.sceneData.sceneMain + + if self.hasCutscenes: + for cs in self.sceneData.sceneCutscenes: + cs = '#include "z64cutscene.h\n' + '#include "z64cutscene_commands.h\n\n' + cs + + for room in self.roomList.values(): + room.roomMain = common + suffix + room.roomMain + def writeScene(self): - scenePath = os.path.join(self.path, self.scene.name + ".c") - writeFile(scenePath, self.sceneData.sceneMain) + sceneBasePath = os.path.join(self.path, self.scene.name) for room in self.roomList.values(): - roomPath = os.path.join(self.path, room.name + ".c") - writeFile(roomPath, room.roomMain) + writeFile(os.path.join(self.path, room.name + ".c"), room.roomMain) + + if self.hasCutscenes: + for i, cs in enumerate(self.sceneData.sceneCutscenes): + writeFile(f"{sceneBasePath}_cs_{i}.c", cs) + + writeFile(sceneBasePath + ".c", self.sceneData.sceneMain) + writeFile(sceneBasePath + ".h", self.header) def export(self): checkObjectReference(self.sceneObj, "Scene object") @@ -199,5 +242,6 @@ def export(self): self.path = levelPath self.setSceneData() self.setRoomListData() + self.setIncludeData() self.writeScene() diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 755678857..dee62b696 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -8,11 +8,6 @@ from .common import Common, Actor, altHeaderList -@dataclass -class RoomCommon: - roomName: str - - @dataclass class OOTRoomHeaderInfos: ### General ### @@ -44,26 +39,24 @@ class OOTRoomHeaderInfos: @dataclass -class OOTRoomHeaderObjects(RoomCommon): +class OOTRoomHeaderObjects: + name: str objectList: list[str] - def objectListName(self, headerIndex: int): - return f"{self.roomName}_header{headerIndex:02}_objectList" - - def getObjectLengthDefineName(self, headerIndex: int): - return f"LENGTH_{self.objectListName(headerIndex).upper()}" + def getObjectLengthDefineName(self): + return f"LENGTH_{self.name.upper()}" - def getObjectList(self, headerIndex: int): + def getObjectListC(self): objectList = CData() - listName = f"s16 {self.objectListName(headerIndex)}" + listName = f"s16 {self.name}" # .h objectList.header = f"extern {listName}[];\n" # .c objectList.source = ( - (f"{listName}[{self.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + (f"{listName}[{self.getObjectLengthDefineName()}]" + " = {\n") + ",\n".join(indent + objectID for objectID in self.objectList) + ",\n};\n\n" ) @@ -72,7 +65,8 @@ def getObjectList(self, headerIndex: int): @dataclass -class OOTRoomHeaderActors(RoomCommon): +class OOTRoomHeaderActors: + name: str sceneObj: Object roomObj: Object transform: Matrix @@ -119,18 +113,13 @@ def __post_init__(self): actor.params = actorProp.actorParam self.actorList.append(actor) - # Exporter - - def actorListName(self): - return f"{self.roomName}_header{self.headerIndex:02}_actorList" - def getActorLengthDefineName(self): - return f"LENGTH_{self.actorListName().upper()}" + return f"LENGTH_{self.name.upper()}" - def getActorListData(self): + def getActorListC(self): """Returns the actor list for the current header""" actorList = CData() - listName = f"ActorEntry {self.actorListName()}" + listName = f"ActorEntry {self.name}" # .h actorList.header = f"extern {listName}[];\n" @@ -155,22 +144,23 @@ class OOTRoomAlternateHeader: @dataclass -class OOTRoomHeader(RoomCommon): +class OOTRoomHeader: + name: str infos: OOTRoomHeaderInfos objects: OOTRoomHeaderObjects actors: OOTRoomHeaderActors - def getHeaderDefines(self, headerIndex: int): + def getHeaderDefines(self): """Returns a string containing defines for actor and object lists lengths""" headerDefines = "" if len(self.objects.objectList) > 0: - name = self.objects.getObjectLengthDefineName(headerIndex) - headerDefines += f"#define {name} {len(self.objects.objectList)}\n" + defineName = self.objects.getObjectLengthDefineName() + headerDefines += f"#define {defineName} {len(self.objects.objectList)}\n" if len(self.actors.actorList) > 0: - name = self.actors.getActorLengthDefineName(headerIndex) - headerDefines += f"#define {name} {len(self.actors.actorList)}\n" + defineName = self.actors.getActorLengthDefineName() + headerDefines += f"#define {defineName} {len(self.actors.actorList)}\n" return headerDefines @@ -179,17 +169,12 @@ def getHeaderDefines(self, headerIndex: int): class OOTRoom(Common, OOTRoomCommands): name: str = None roomObj: Object = None + headerIndex: int = None mainHeader: OOTRoomHeader = None altHeader: OOTRoomAlternateHeader = None def hasAlternateHeaders(self): - return ( - self.altHeader is not None - and self.altHeader.childNight is not None - and self.altHeader.adultDay is not None - and self.altHeader.adultNight is not None - and len(self.altHeader.cutscenes) > 0 - ) + return self.altHeader is not None def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: if headerIndex == 0: @@ -208,6 +193,9 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): """Returns a new room header with the informations from the scene empty object""" + self.headerIndex = headerIndex + headerName = f"{self.name}_header{self.headerIndex:02}" + objIDList = [] for objProp in headerProp.objectList: if objProp.objectKey == "Custom": @@ -216,7 +204,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) return OOTRoomHeader( - self.name, + headerName, OOTRoomHeaderInfos( headerProp.roomIndex, headerProp.roomShape, @@ -234,9 +222,9 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = [d for d in headerProp.windVector] if headerProp.setWind else None, headerProp.windStrength if headerProp.setWind else None, ), - OOTRoomHeaderObjects(self.name, objIDList), + OOTRoomHeaderObjects(f"{headerName}_objectList", objIDList), OOTRoomHeaderActors( - self.name, + f"{headerName}_actorList", self.sceneObj, self.roomObj, self.transform, @@ -246,46 +234,49 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = def getRoomMainC(self): roomC = CData() - - roomHeaders: list[tuple[OOTRoomHeader, str]] = [ - (self.altHeader.childNight, "Child Night"), - (self.altHeader.adultDay, "Adult Day"), - (self.altHeader.adultNight, "Adult Night"), - ] - - for i, csHeader in enumerate(self.altHeader.cutscenes): - roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrListName = f"SceneCmd* {self.altHeader.name}" - - # .h - roomC.header = f"extern {altHeaderPtrListName}[];\n" - - # .c - altHeaderPtrList = ( - f"{altHeaderPtrListName}[]" - + " = {\n" - + "\n".join( - indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," - for i, (curHeader, _) in enumerate(roomHeaders, 1) + roomHeaders: list[tuple[OOTRoomHeader, str]] = [] + altHeaderPtrList = None + + if self.hasAlternateHeaders(): + roomHeaders: list[tuple[OOTRoomHeader, str]] = [ + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.altHeader.cutscenes): + roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrListName = f"SceneCmd* {self.altHeader.name}" + + # .h + roomC.header = f"extern {altHeaderPtrListName}[];\n" + + # .c + altHeaderPtrList = ( + f"{altHeaderPtrListName}[]" + + " = {\n" + + "\n".join( + indent + f"{curHeader.name}," if curHeader is not None else indent + "NULL," + for (curHeader, _) in roomHeaders + ) + + "\n};\n\n" ) - + "\n};\n\n" - ) roomHeaders.insert(0, (self.mainHeader, "Child Day (Default)")) for i, (curHeader, headerDesc) in enumerate(roomHeaders): if curHeader is not None: roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - roomC.source += curHeader.getHeaderDefines(i) + roomC.source += curHeader.getHeaderDefines() roomC.append(self.getRoomCommandList(self, i)) - if i == 0 and self.hasAlternateHeaders(): + if i == 0 and self.hasAlternateHeaders() and altHeaderPtrList is not None: roomC.source += altHeaderPtrList if len(curHeader.objects.objectList) > 0: - roomC.append(curHeader.objects.getObjectList(i)) + roomC.append(curHeader.objects.getObjectListC()) if len(curHeader.actors.actorList) > 0: - roomC.append(curHeader.actors.getActorListData()) + roomC.append(curHeader.actors.getActorListC()) return roomC diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 2b2871b42..a5361ee7d 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -8,11 +8,6 @@ from .room import OOTRoom -@dataclass -class SceneCommon: - headerName: str - - @dataclass class EnvLightSettings: envLightMode: str @@ -109,13 +104,10 @@ class OOTSceneHeaderInfos: @dataclass -class OOTSceneHeaderLighting(SceneCommon): +class OOTSceneHeaderLighting: + name: str envLightMode: str = None settings: list[EnvLightSettings] = field(default_factory=list) - name: str = None - - def __post_init__(self): - self.name = f"{self.headerName}_lightSettings" def getEnvLightSettingsC(self): lightSettingsC = CData() @@ -136,21 +128,18 @@ def getEnvLightSettingsC(self): @dataclass class OOTSceneHeaderCutscene: + name: str writeType: str writeCutscene: bool csObj: Object csWriteCustom: str extraCutscenes: list[Object] - name: str = None @dataclass -class OOTSceneHeaderExits(SceneCommon): - exitList: list[tuple[int, str]] = field(default_factory=list) +class OOTSceneHeaderExits: name: str = None - - def __post_init__(self): - self.name = f"{self.headerName}_exitList" + exitList: list[tuple[int, str]] = field(default_factory=list) def getExitListC(self): exitListC = CData() @@ -171,19 +160,14 @@ def getExitListC(self): @dataclass -class OOTSceneHeaderActors(SceneCommon): +class OOTSceneHeaderActors: + entranceListName: str + startPositionsName: str + transActorListName: str + transitionActorList: list[TransitionActor] = field(default_factory=list) entranceActorList: list[EntranceActor] = field(default_factory=list) - entranceListName: str = None - startPositionsName: str = None - transActorListName: str = None - - def __post_init__(self): - self.entranceListName = f"{self.headerName}_entranceList" - self.startPositionsName = f"{self.headerName}_playerEntryList" - self.transActorListName = f"{self.headerName}_transitionActors" - def getSpawnActorListC(self): """Returns the spawn actor list for the current header""" spawnActorList = CData() @@ -248,6 +232,7 @@ class OOTSceneAlternateHeader: @dataclass class OOTSceneHeader: + name: str infos: OOTSceneHeaderInfos lighting: OOTSceneHeaderLighting cutscene: OOTSceneHeaderCutscene @@ -285,7 +270,6 @@ def getHeaderC(self): class OOTScene(Common, OOTSceneCommands): name: str = None headerIndex: int = None - headerName: str = None mainHeader: OOTSceneHeader = None altHeader: OOTSceneAlternateHeader = None roomList: list[OOTRoom] = field(default_factory=list) @@ -309,13 +293,7 @@ def validateScene(self): raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") def hasAlternateHeaders(self): - return ( - self.altHeader is not None - and self.altHeader.childNight is not None - and self.altHeader.adultDay is not None - and self.altHeader.adultNight is not None - and len(self.altHeader.cutscenes) > 0 - ) + return self.altHeader is not None def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: if headerIndex == 0: @@ -442,9 +420,9 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int """Returns a single scene header with the informations from the scene empty object""" self.headerIndex = headerIndex - self.headerName = f"{self.name}_header{self.headerIndex:02}" + headerName = f"{self.name}_header{self.headerIndex:02}" - if headerProp.csWriteType == "Embedded": + if headerProp.writeCutscene and headerProp.csWriteType == "Embedded": raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") lightMode = self.getPropValue(headerProp, "skyboxLighting") @@ -476,6 +454,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int ) return OOTSceneHeader( + headerName, OOTSceneHeaderInfos( self.getPropValue(headerProp, "globalObject"), self.getPropValue(headerProp, "naviCup"), @@ -491,20 +470,23 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getPropValue(headerProp, "cameraMode"), ), OOTSceneHeaderLighting( - self.headerName, + f"{headerName}_lightSettings", lightMode, lightSettings, ), OOTSceneHeaderCutscene( + f"{headerName}_cutscene", headerProp.csWriteType, headerProp.writeCutscene, headerProp.csWriteObject, headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, [csObj for csObj in headerProp.extraCutscenes], ), - OOTSceneHeaderExits(self.headerName, self.getExitListFromProps(headerProp)), + OOTSceneHeaderExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), OOTSceneHeaderActors( - self.headerName, + f"{headerName}_entranceList", + f"{headerName}_playerEntryList", + f"{headerName}_transitionActors", self.getTransActorListFromProps(), self.getEntranceActorListFromProps(), ), @@ -549,20 +531,23 @@ def getRoomListC(self): def getSceneMainC(self): sceneC = CData() - - headers: list[tuple[OOTSceneHeader, str]] = [ - (self.altHeader.childNight, "Child Night"), - (self.altHeader.adultDay, "Adult Day"), - (self.altHeader.adultNight, "Adult Night"), - ] - - for i, csHeader in enumerate(self.altHeader.cutscenes): - headers.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrs = "\n".join( - indent + self.headerName + "," if curHeader is not None else indent + "NULL," if i < 4 else "" - for i, (curHeader, _) in enumerate(headers, 1) - ) + headers: list[tuple[OOTSceneHeader, str]] = [] + altHeaderPtrs = None + + if self.hasAlternateHeaders(): + headers: list[tuple[OOTSceneHeader, str]] = [ + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.altHeader.cutscenes): + headers.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrs = "\n".join( + indent + curHeader.name + "," if curHeader is not None else indent + "NULL," if i < 4 else "" + for i, (curHeader, _) in enumerate(headers, 1) + ) headers.insert(0, (self.mainHeader, "Child Day (Default)")) for i, (curHeader, headerDesc) in enumerate(headers): @@ -571,7 +556,7 @@ def getSceneMainC(self): sceneC.append(self.getSceneCommandList(self, curHeader, i)) if i == 0: - if self.hasAlternateHeaders(): + if self.hasAlternateHeaders() and altHeaderPtrs is not None: altHeaderListName = f"SceneCmd* {self.altHeader.name}[]" sceneC.header += f"extern {altHeaderListName};\n" sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 4446453a7..218890f33 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -41,9 +41,8 @@ def addMissingObjectsToAllRoomHeaders(roomObj: Object, room: OOTRoom, ootData: O addMissingObjectsToRoomHeader(roomObj, room.cutsceneHeaders[i], ootData, i + 4) -def addMissingObjectsToRoomHeaderNew(roomObj: Object, room, ootData: OoT_Data, headerIndex: int): +def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, ootData: OoT_Data, headerIndex: int): """Adds missing objects to the object list""" - curHeader = room.getRoomHeaderFromIndex(headerIndex) if len(curHeader.actors.actorList) > 0: for roomActor in curHeader.actors.actorList: actor = ootData.actorData.actorsByID.get(roomActor.actorID) @@ -61,9 +60,10 @@ def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room, ootData: OoT_Dat Adds missing objects (required by actors) to all headers of a room, both to the roomObj empty and the exported room """ - sceneLayers = [room, room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight] - for i, layer in enumerate(sceneLayers): - if layer is not None: - addMissingObjectsToRoomHeaderNew(roomObj, layer, ootData, i) - for i in range(len(room.altHeader.cutscenes)): - addMissingObjectsToRoomHeaderNew(roomObj, room.cutsceneHeaders[i], ootData, i + 4) + if room.altHeader is not None: + sceneLayers = [room.mainHeader, room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight] + if len(room.altHeader.cutscenes) > 0: + sceneLayers.extend(room.altHeader.cutscenes) + for i, layer in enumerate(sceneLayers): + if layer is not None: + addMissingObjectsToRoomHeaderNew(roomObj, layer, ootData, i) From 7616a11018389e341fc3c2483db765618234fcb1 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 13:49:58 +0200 Subject: [PATCH 07/98] implement paths --- fast64_internal/oot/new_exporter/commands.py | 9 +- fast64_internal/oot/new_exporter/common.py | 14 +-- fast64_internal/oot/new_exporter/room.py | 2 +- fast64_internal/oot/new_exporter/scene.py | 99 +++++++++++++++++++- 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index fd0f991e4..602b5634a 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -11,6 +11,7 @@ OOTSceneHeaderLighting, OOTSceneHeaderCutscene, OOTSceneHeaderActors, + OOTSceneHeaderPath, ) @@ -111,8 +112,8 @@ def getSpawnListCmd(self, actors: "OOTSceneHeaderActors"): def getSpecialFilesCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" - # def getPathListCmd(self, outScene: "OOTScene", headerIndex: int): - # return indent + f"SCENE_CMD_PATH_LIST({outScene.pathListName(headerIndex)})" + def getPathListCmd(self, path: "OOTSceneHeaderPath"): + return indent + f"SCENE_CMD_PATH_LIST({path.name}),\n" if len(path.pathList) > 0 else "" def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) @@ -167,9 +168,6 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he if len(curHeader.actors.transitionActorList) > 0: getCmdActorList.append(self.getTransActorListCmd) - # if len(outScene.pathList) > 0: - # getCmdFunc2ArgList.append(self.getPathListCmd) - # if scene.writeCutscene: # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) @@ -179,6 +177,7 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he + self.getRoomListCmd(scene) + self.getSkyboxSettingsCmd(curHeader.infos, curHeader.lighting) + self.getLightSettingsCmd(curHeader.lighting) + + self.getPathListCmd(curHeader.path) # + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") + (",\n".join(getCmd(scene, headerIndex) for getCmd in getCmdFunc1List) + ",\n") diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/common.py index fe383e69a..01611ef76 100644 --- a/fast64_internal/oot/new_exporter/common.py +++ b/fast64_internal/oot/new_exporter/common.py @@ -4,7 +4,7 @@ from bpy.types import Object from ...utility import indent from ..oot_utility import ootConvertTranslation, ootConvertRotation -from ..actor.properties import OOTActorProperty +from ..actor.properties import OOTActorHeaderProperty altHeaderList = ["childNight", "adultDay", "adultNight"] @@ -16,20 +16,20 @@ class Common: transform: Matrix roomIndex: int = None - def isCurrentHeaderValid(self, actorProp: OOTActorProperty, headerIndex: int): - preset = actorProp.headerSettings.sceneSetupPreset + def isCurrentHeaderValid(self, headerSettings: OOTActorHeaderProperty, headerIndex: int): + preset = headerSettings.sceneSetupPreset if preset == "All Scene Setups" or (preset == "All Non-Cutscene Scene Setups" and headerIndex < 4): return True if preset == "Custom": - if actorProp.headerSettings.childDayHeader and headerIndex == 0: + if headerSettings.childDayHeader and headerIndex == 0: return True - if actorProp.headerSettings.childNightHeader and headerIndex == 1: + if headerSettings.childNightHeader and headerIndex == 1: return True - if actorProp.headerSettings.adultDayHeader and headerIndex == 2: + if headerSettings.adultDayHeader and headerIndex == 2: return True - if actorProp.headerSettings.adultNightHeader and headerIndex == 3: + if headerSettings.adultNightHeader and headerIndex == 3: return True return False diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index dee62b696..4eaa36276 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -79,7 +79,7 @@ def __post_init__(self): ] for obj in actorObjList: actorProp = obj.ootActorProperty - if not Common.isCurrentHeaderValid(actorProp, self.headerIndex): + if not Common.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index a5361ee7d..aa7e9bd06 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -73,6 +73,31 @@ def getLightSettingsEntry(self, index: int): return lightData +@dataclass +class Path: + name: str + points: list[tuple[int, int, int]] = field(default_factory=list) + + def getPathPointListC(self): + pathData = CData() + pathName = f"Vec3s {self.name}" + + # .h + pathData.header = f"extern {pathName}[];\n" + + # .c + pathData.source = ( + f"{pathName}[]" + + " = {\n" + + "\n".join( + indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points + ) + + "\n};\n\n" + ) + + return pathData + + @dataclass class OOTSceneHeaderInfos: ### General ### @@ -221,6 +246,37 @@ def getTransActorListC(self): return transActorList +@dataclass +class OOTSceneHeaderPath: + name: str + pathList: list[Path] + + def getPathC(self): + pathData = CData() + pathListData = CData() + listName = f"Path {self.name}[{len(self.pathList)}]" + + # .h + pathListData.header = f"extern {listName};\n" + + # .c + pathListData.source = listName + " = {\n" + + for path in self.pathList: + pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" + pathData.append(path.getPathPointListC()) + + pathListData.source += "};\n\n" + pathData.append(pathListData) + + return pathData + + +@dataclass +class OOTSceneHeaderCrawlspace: + name: str + + @dataclass class OOTSceneAlternateHeader: name: str @@ -238,6 +294,8 @@ class OOTSceneHeader: cutscene: OOTSceneHeaderCutscene exits: OOTSceneHeaderExits actors: OOTSceneHeaderActors + path: OOTSceneHeaderPath + crawlspace: OOTSceneHeaderCrawlspace def getHeaderC(self): headerData = CData() @@ -260,8 +318,8 @@ def getHeaderC(self): headerData.append(self.lighting.getEnvLightSettingsC()) # Write the path data, if used - # if len(self.pathList) > 0: - # headerData.append(getPathData(header, headerIndex)) + if len(self.path.pathList) > 0: + headerData.append(self.path.getPathC()) return headerData @@ -278,6 +336,18 @@ class OOTScene(Common, OOTSceneCommands): def __post_init__(self): self.roomListName = f"{self.name}_roomList" + def validateCurveData(self, curveObj: Object): + curveData = curveObj.data + if curveObj.type != "CURVE" or curveData.splines[0].type != "NURBS": + # Curve was likely not intended to be exported + return False + + if len(curveData.splines) != 1: + # Curve was intended to be exported but has multiple disconnected segments + raise PluginError(f"Exported curves should have only one single segment, found {len(curveData.splines)}") + + return True + def validateRoomIndices(self): for i, room in enumerate(self.roomList): if i != room.roomIndex: @@ -346,7 +416,7 @@ def getTransActorListFromProps(self): transActorProp = obj.ootTransitionActorProperty - if not self.isCurrentHeaderValid(transActorProp.actor, self.headerIndex): + if not self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex): continue if transActorProp.actor.actorID != "None": @@ -392,7 +462,7 @@ def getEntranceActorListFromProps(self): raise PluginError("ERROR: Room Object not found!") entranceProp = obj.ootEntranceProperty - if not self.isCurrentHeaderValid(entranceProp.actor, self.headerIndex): + if not self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex): continue if entranceProp.actor.actorID != "None": @@ -416,6 +486,25 @@ def getEntranceActorListFromProps(self): actorList.append(entranceActor) return actorList + def getPathListFromProps(self, listNameBase: str): + pathList: list[Path] = [] + pathObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" + ] + + for i, obj in enumerate(pathObjList): + isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) + if isHeaderValid and self.validateCurveData(obj): + pathList.append( + Path( + f"{listNameBase}{i:02}", [self.transform @ point.co.xyz for point in obj.data.splines[0].points] + ) + ) + + return pathList + def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): """Returns a single scene header with the informations from the scene empty object""" @@ -490,6 +579,8 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getTransActorListFromProps(), self.getEntranceActorListFromProps(), ), + OOTSceneHeaderPath(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), + OOTSceneHeaderCrawlspace(None), ) def getRoomListC(self): From b5a6eef0802c896e7298949e81ceba3145c2f33e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 13:52:50 +0200 Subject: [PATCH 08/98] moved lightsettings out of getNewSceneHeader --- fast64_internal/oot/new_exporter/scene.py | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index aa7e9bd06..8c7eeda7b 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -505,16 +505,7 @@ def getPathListFromProps(self, listNameBase: str): return pathList - def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): - """Returns a single scene header with the informations from the scene empty object""" - - self.headerIndex = headerIndex - headerName = f"{self.name}_header{self.headerIndex:02}" - - if headerProp.writeCutscene and headerProp.csWriteType == "Embedded": - raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") - - lightMode = self.getPropValue(headerProp, "skyboxLighting") + def getEnvLightSettingsListFromProps(self, headerProp: OOTSceneHeaderProperty, lightMode: str): lightList: list[OOTLightProperty] = [] lightSettings: list[EnvLightSettings] = [] @@ -542,6 +533,18 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int ) ) + return lightSettings + + def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): + """Returns a single scene header with the informations from the scene empty object""" + + self.headerIndex = headerIndex + headerName = f"{self.name}_header{self.headerIndex:02}" + lightMode = self.getPropValue(headerProp, "skyboxLighting") + + if headerProp.writeCutscene and headerProp.csWriteType == "Embedded": + raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") + return OOTSceneHeader( headerName, OOTSceneHeaderInfos( @@ -561,7 +564,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int OOTSceneHeaderLighting( f"{headerName}_lightSettings", lightMode, - lightSettings, + self.getEnvLightSettingsListFromProps(headerProp, lightMode), ), OOTSceneHeaderCutscene( f"{headerName}_cutscene", From 3d317564015e3673619dc9b249166940c2526ab7 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 14:30:03 +0200 Subject: [PATCH 09/98] cs stuff --- fast64_internal/oot/new_exporter/exporter.py | 4 +++- fast64_internal/oot/new_exporter/scene.py | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 7fa9f2b15..9923dadfc 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -186,9 +186,11 @@ def setRoomListData(self): def setSceneData(self): sceneData = OOTSceneData() sceneMainData = self.scene.getSceneMainC() + sceneCutsceneData = self.scene.getSceneCutscenesC() sceneData.sceneMain = sceneMainData.source - self.header += sceneMainData.header + sceneData.sceneCutscenes = [cs.source for cs in sceneCutsceneData] + self.header += sceneMainData.header + "".join(cs.header for cs in sceneCutsceneData) self.sceneData = sceneData def setIncludeData(self): diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 8c7eeda7b..d2c6f71bc 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -3,6 +3,7 @@ from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ..oot_constants import ootData +from ..cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC from .commands import OOTSceneCommands from .common import Common, TransitionActor, EntranceActor, altHeaderList from .room import OOTRoom @@ -160,6 +161,11 @@ class OOTSceneHeaderCutscene: csWriteCustom: str extraCutscenes: list[Object] + def getCutsceneC(self): + # will be implemented when PR #208 is merged + cutsceneData = CData() + return cutsceneData + @dataclass class OOTSceneHeaderExits: @@ -567,7 +573,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getEnvLightSettingsListFromProps(headerProp, lightMode), ), OOTSceneHeaderCutscene( - f"{headerName}_cutscene", + headerProp.csWriteObject.name.removeprefix("Cutscene."), headerProp.csWriteType, headerProp.writeCutscene, headerProp.csWriteObject, @@ -583,7 +589,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getEntranceActorListFromProps(), ), OOTSceneHeaderPath(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), - OOTSceneHeaderCrawlspace(None), + OOTSceneHeaderCrawlspace(None), # not implemented yet ) def getRoomListC(self): @@ -629,7 +635,7 @@ def getSceneMainC(self): altHeaderPtrs = None if self.hasAlternateHeaders(): - headers: list[tuple[OOTSceneHeader, str]] = [ + headers = [ (self.altHeader.childNight, "Child Night"), (self.altHeader.adultDay, "Adult Day"), (self.altHeader.adultNight, "Adult Night"), @@ -661,3 +667,8 @@ def getSceneMainC(self): sceneC.append(curHeader.getHeaderC()) return sceneC + + def getSceneCutscenesC(self): + # will be implemented when PR #208 is merged + csDataList: list[CData] = [] + return csDataList From f28b31363b9901b563324daf45d4abdf9f4df745 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 14:36:06 +0200 Subject: [PATCH 10/98] split header data --- fast64_internal/oot/new_exporter/room.py | 170 +-------- .../oot/new_exporter/room_header.py | 163 +++++++++ fast64_internal/oot/new_exporter/scene.py | 334 +----------------- .../oot/new_exporter/scene_header.py | 325 +++++++++++++++++ 4 files changed, 511 insertions(+), 481 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/room_header.py create mode 100644 fast64_internal/oot/new_exporter/scene_header.py diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 4eaa36276..9fc42eb83 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -1,168 +1,18 @@ -from dataclasses import dataclass, field -from mathutils import Matrix +from dataclasses import dataclass from bpy.types import Object from ...utility import CData, indent from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData from .commands import OOTRoomCommands -from .common import Common, Actor, altHeaderList - - -@dataclass -class OOTRoomHeaderInfos: - ### General ### - - index: int - roomShape: str - - ### Behavior ### - - roomBehavior: str - playerIdleType: str - disableWarpSongs: bool - showInvisActors: bool - - ### Skybox And Time ### - - disableSky: bool - disableSunMoon: bool - hour: int - minute: int - timeSpeed: float - echo: str - - ### Wind ### - - setWind: bool - direction: tuple[int, int, int] - strength: int - - -@dataclass -class OOTRoomHeaderObjects: - name: str - objectList: list[str] - - def getObjectLengthDefineName(self): - return f"LENGTH_{self.name.upper()}" - - def getObjectListC(self): - objectList = CData() - - listName = f"s16 {self.name}" - - # .h - objectList.header = f"extern {listName}[];\n" - - # .c - objectList.source = ( - (f"{listName}[{self.getObjectLengthDefineName()}]" + " = {\n") - + ",\n".join(indent + objectID for objectID in self.objectList) - + ",\n};\n\n" - ) - - return objectList - - -@dataclass -class OOTRoomHeaderActors: - name: str - sceneObj: Object - roomObj: Object - transform: Matrix - headerIndex: int - actorList: list[Actor] = field(default_factory=list) - - def __post_init__(self): - actorObjList: list[Object] = [ - obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" - ] - for obj in actorObjList: - actorProp = obj.ootActorProperty - if not Common.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): - continue - - # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for - # the total number of actors defined in the XML. If the user deletes one, this will prevent - # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty - # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if - # the current Actor has the ID `None` to avoid export issues. - if actorProp.actorID != "None": - pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) - actor = Actor() - - if actorProp.actorID == "Custom": - actor.id = actorProp.actorIDCustom - else: - actor.id = actorProp.actorID - - if actorProp.rotOverride: - actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) - else: - actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - - actor.name = ( - ootData.actorData.actorsByID[actorProp.actorID].name.replace( - f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" - ) - if actorProp.actorID != "Custom" - else "Custom Actor" - ) - - actor.pos = pos - actor.params = actorProp.actorParam - self.actorList.append(actor) - - def getActorLengthDefineName(self): - return f"LENGTH_{self.name.upper()}" - - def getActorListC(self): - """Returns the actor list for the current header""" - actorList = CData() - listName = f"ActorEntry {self.name}" - - # .h - actorList.header = f"extern {listName}[];\n" - - # .c - actorList.source = ( - (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") - + "\n".join(actor.getActorEntry() for actor in self.actorList) - + "};\n\n" - ) - - return actorList - - -@dataclass -class OOTRoomAlternateHeader: - name: str - childNight: "OOTRoomHeader" = None - adultDay: "OOTRoomHeader" = None - adultNight: "OOTRoomHeader" = None - cutscenes: list["OOTRoomHeader"] = field(default_factory=list) - - -@dataclass -class OOTRoomHeader: - name: str - infos: OOTRoomHeaderInfos - objects: OOTRoomHeaderObjects - actors: OOTRoomHeaderActors - - def getHeaderDefines(self): - """Returns a string containing defines for actor and object lists lengths""" - headerDefines = "" - - if len(self.objects.objectList) > 0: - defineName = self.objects.getObjectLengthDefineName() - headerDefines += f"#define {defineName} {len(self.objects.objectList)}\n" - - if len(self.actors.actorList) > 0: - defineName = self.actors.getActorLengthDefineName() - headerDefines += f"#define {defineName} {len(self.actors.actorList)}\n" - - return headerDefines +from .common import Common, altHeaderList + +from .room_header import ( + OOTRoomHeader, + OOTRoomAlternateHeader, + OOTRoomHeaderInfos, + OOTRoomHeaderObjects, + OOTRoomHeaderActors, +) @dataclass diff --git a/fast64_internal/oot/new_exporter/room_header.py b/fast64_internal/oot/new_exporter/room_header.py new file mode 100644 index 000000000..e5a97ec0e --- /dev/null +++ b/fast64_internal/oot/new_exporter/room_header.py @@ -0,0 +1,163 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from ...utility import CData, indent +from ..oot_constants import ootData +from .common import Common, Actor + + +@dataclass +class OOTRoomHeaderInfos: + ### General ### + + index: int + roomShape: str + + ### Behavior ### + + roomBehavior: str + playerIdleType: str + disableWarpSongs: bool + showInvisActors: bool + + ### Skybox And Time ### + + disableSky: bool + disableSunMoon: bool + hour: int + minute: int + timeSpeed: float + echo: str + + ### Wind ### + + setWind: bool + direction: tuple[int, int, int] + strength: int + + +@dataclass +class OOTRoomHeaderObjects: + name: str + objectList: list[str] + + def getObjectLengthDefineName(self): + return f"LENGTH_{self.name.upper()}" + + def getObjectListC(self): + objectList = CData() + + listName = f"s16 {self.name}" + + # .h + objectList.header = f"extern {listName}[];\n" + + # .c + objectList.source = ( + (f"{listName}[{self.getObjectLengthDefineName()}]" + " = {\n") + + ",\n".join(indent + objectID for objectID in self.objectList) + + ",\n};\n\n" + ) + + return objectList + + +@dataclass +class OOTRoomHeaderActors: + name: str + sceneObj: Object + roomObj: Object + transform: Matrix + headerIndex: int + actorList: list[Actor] = field(default_factory=list) + + def __post_init__(self): + actorObjList: list[Object] = [ + obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" + ] + for obj in actorObjList: + actorProp = obj.ootActorProperty + if not Common.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): + continue + + # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for + # the total number of actors defined in the XML. If the user deletes one, this will prevent + # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty + # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if + # the current Actor has the ID `None` to avoid export issues. + if actorProp.actorID != "None": + pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) + actor = Actor() + + if actorProp.actorID == "Custom": + actor.id = actorProp.actorIDCustom + else: + actor.id = actorProp.actorID + + if actorProp.rotOverride: + actor.rot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) + else: + actor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + + actor.name = ( + ootData.actorData.actorsByID[actorProp.actorID].name.replace( + f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" + ) + if actorProp.actorID != "Custom" + else "Custom Actor" + ) + + actor.pos = pos + actor.params = actorProp.actorParam + self.actorList.append(actor) + + def getActorLengthDefineName(self): + return f"LENGTH_{self.name.upper()}" + + def getActorListC(self): + """Returns the actor list for the current header""" + actorList = CData() + listName = f"ActorEntry {self.name}" + + # .h + actorList.header = f"extern {listName}[];\n" + + # .c + actorList.source = ( + (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") + + "\n".join(actor.getActorEntry() for actor in self.actorList) + + "};\n\n" + ) + + return actorList + + +@dataclass +class OOTRoomAlternateHeader: + name: str + childNight: "OOTRoomHeader" = None + adultDay: "OOTRoomHeader" = None + adultNight: "OOTRoomHeader" = None + cutscenes: list["OOTRoomHeader"] = field(default_factory=list) + + +@dataclass +class OOTRoomHeader: + name: str + infos: OOTRoomHeaderInfos + objects: OOTRoomHeaderObjects + actors: OOTRoomHeaderActors + + def getHeaderDefines(self): + """Returns a string containing defines for actor and object lists lengths""" + headerDefines = "" + + if len(self.objects.objectList) > 0: + defineName = self.objects.getObjectLengthDefineName() + headerDefines += f"#define {defineName} {len(self.objects.objectList)}\n" + + if len(self.actors.actorList) > 0: + defineName = self.actors.getActorLengthDefineName() + headerDefines += f"#define {defineName} {len(self.actors.actorList)}\n" + + return headerDefines diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index d2c6f71bc..5b3aa55ca 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -3,331 +3,23 @@ from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ..oot_constants import ootData -from ..cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC from .commands import OOTSceneCommands from .common import Common, TransitionActor, EntranceActor, altHeaderList from .room import OOTRoom - -@dataclass -class EnvLightSettings: - envLightMode: str - ambientColor: tuple[int, int, int] - light1Color: tuple[int, int, int] - light1Dir: tuple[int, int, int] - light2Color: tuple[int, int, int] - light2Dir: tuple[int, int, int] - fogColor: tuple[int, int, int] - fogNear: int - zFar: int - blendRate: int - - def getBlendFogNear(self): - return f"(({self.blendRate} << 10) | {self.fogNear})" - - def getColorValues(self, vector: tuple[int, int, int]): - return ", ".join(f"{v:5}" for v in vector) - - def getDirectionValues(self, vector: tuple[int, int, int]): - return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - - def getLightSettingsEntry(self, index: int): - isLightingCustom = self.envLightMode == "Custom" - - vectors = [ - (self.ambientColor, "Ambient Color", self.getColorValues), - (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), - (self.light1Color, "Diffuse0 Color", self.getColorValues), - (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), - (self.light2Color, "Diffuse1 Color", self.getColorValues), - (self.fogColor, "Fog Color", self.getColorValues), - ] - - fogData = [ - (self.getBlendFogNear(), "Blend Rate & Fog Near"), - (f"{self.zFar}", "Fog Far"), - ] - - lightDescs = ["Dawn", "Day", "Dusk", "Night"] - - if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": - # @TODO: Improve the lighting system. - # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. - # This is not accurate and more complicated, - # for now we are doing ``index % 4`` to avoid having an OoB read in the list - # but this will need to be changed the day the lighting system is updated. - lightDesc = f"// {lightDescs[index % 4]} Lighting\n" - else: - isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" - lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" - - lightData = ( - (indent + lightDesc) - + (indent + "{\n") - + "".join( - indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors - ) - + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) - + (indent + "},\n") - ) - - return lightData - - -@dataclass -class Path: - name: str - points: list[tuple[int, int, int]] = field(default_factory=list) - - def getPathPointListC(self): - pathData = CData() - pathName = f"Vec3s {self.name}" - - # .h - pathData.header = f"extern {pathName}[];\n" - - # .c - pathData.source = ( - f"{pathName}[]" - + " = {\n" - + "\n".join( - indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points - ) - + "\n};\n\n" - ) - - return pathData - - -@dataclass -class OOTSceneHeaderInfos: - ### General ### - - keepObjectID: str - naviHintType: str - drawConfig: str - appendNullEntrance: bool - useDummyRoomList: bool - - ### Skybox And Sound ### - - # Skybox - skyboxID: str - skyboxConfig: str - - # Sound - sequenceID: str - ambienceID: str - specID: str - - ### Camera And World Map ### - - # World Map - worldMapLocation: str - - # Camera - sceneCamType: str - - -@dataclass -class OOTSceneHeaderLighting: - name: str - envLightMode: str = None - settings: list[EnvLightSettings] = field(default_factory=list) - - def getEnvLightSettingsC(self): - lightSettingsC = CData() - lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" - - # .h - lightSettingsC.header = f"extern {lightName};\n" - - # .c - lightSettingsC.source = ( - (lightName + " = {\n") - + "".join(light.getLightSettingsEntry(i) for i, light in enumerate(self.settings)) - + "};\n\n" - ) - - return lightSettingsC - - -@dataclass -class OOTSceneHeaderCutscene: - name: str - writeType: str - writeCutscene: bool - csObj: Object - csWriteCustom: str - extraCutscenes: list[Object] - - def getCutsceneC(self): - # will be implemented when PR #208 is merged - cutsceneData = CData() - return cutsceneData - - -@dataclass -class OOTSceneHeaderExits: - name: str = None - exitList: list[tuple[int, str]] = field(default_factory=list) - - def getExitListC(self): - exitListC = CData() - listName = f"u16 {self.name}[{len(self.exitList)}]" - - # .h - exitListC.header = f"extern {listName};\n" - - # .c - exitListC.source = ( - (listName + " = {\n") - # @TODO: use the enum name instead of the raw index - + "\n".join(indent + f"{value}," for (_, value) in self.exitList) - + "\n};\n\n" - ) - - return exitListC - - -@dataclass -class OOTSceneHeaderActors: - entranceListName: str - startPositionsName: str - transActorListName: str - - transitionActorList: list[TransitionActor] = field(default_factory=list) - entranceActorList: list[EntranceActor] = field(default_factory=list) - - def getSpawnActorListC(self): - """Returns the spawn actor list for the current header""" - spawnActorList = CData() - listName = f"ActorEntry {self.startPositionsName}" - - # .h - spawnActorList.header = f"extern {listName}[];\n" - - # .c - spawnActorList.source = ( - (f"{listName}[]" + " = {\n") - + "".join(entrance.getActorEntry() for entrance in self.entranceActorList) - + "};\n\n" - ) - - return spawnActorList - - def getSpawnListC(self): - """Returns the spawn list for the current header""" - spawnList = CData() - listName = f"Spawn {self.entranceListName}" - - # .h - spawnList.header = f"extern {listName}[];\n" - - # .c - spawnList.source = ( - (f"{listName}[]" + " = {\n") - + (indent + "// { Spawn Actor List Index, Room Index }\n") - + "".join(entrance.getSpawnEntry() for entrance in self.entranceActorList) - + "};\n\n" - ) - - return spawnList - - def getTransActorListC(self): - """Returns the transition actor list for the current header""" - transActorList = CData() - listName = f"TransitionActorEntry {self.transActorListName}" - - # .h - transActorList.header = f"extern {listName}[];\n" - - # .c - transActorList.source = ( - (f"{listName}[]" + " = {\n") - + "\n".join(transActor.getTransitionActorEntry() for transActor in self.transitionActorList) - + "};\n\n" - ) - - return transActorList - - -@dataclass -class OOTSceneHeaderPath: - name: str - pathList: list[Path] - - def getPathC(self): - pathData = CData() - pathListData = CData() - listName = f"Path {self.name}[{len(self.pathList)}]" - - # .h - pathListData.header = f"extern {listName};\n" - - # .c - pathListData.source = listName + " = {\n" - - for path in self.pathList: - pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" - pathData.append(path.getPathPointListC()) - - pathListData.source += "};\n\n" - pathData.append(pathListData) - - return pathData - - -@dataclass -class OOTSceneHeaderCrawlspace: - name: str - - -@dataclass -class OOTSceneAlternateHeader: - name: str - childNight: "OOTSceneHeader" = None - adultDay: "OOTSceneHeader" = None - adultNight: "OOTSceneHeader" = None - cutscenes: list["OOTSceneHeader"] = field(default_factory=list) - - -@dataclass -class OOTSceneHeader: - name: str - infos: OOTSceneHeaderInfos - lighting: OOTSceneHeaderLighting - cutscene: OOTSceneHeaderCutscene - exits: OOTSceneHeaderExits - actors: OOTSceneHeaderActors - path: OOTSceneHeaderPath - crawlspace: OOTSceneHeaderCrawlspace - - def getHeaderC(self): - headerData = CData() - - # Write the spawn position list data and the entrance list - if len(self.actors.entranceActorList) > 0: - headerData.append(self.actors.getSpawnActorListC()) - headerData.append(self.actors.getSpawnListC()) - - # Write the transition actor list data - if len(self.actors.transitionActorList) > 0: - headerData.append(self.actors.getTransActorListC()) - - # Write the exit list - if len(self.exits.exitList) > 0: - headerData.append(self.exits.getExitListC()) - - # Write the light data - if len(self.lighting.settings) > 0: - headerData.append(self.lighting.getEnvLightSettingsC()) - - # Write the path data, if used - if len(self.path.pathList) > 0: - headerData.append(self.path.getPathC()) - - return headerData +from .scene_header import ( + OOTSceneHeader, + OOTSceneAlternateHeader, + Path, + EnvLightSettings, + OOTSceneHeaderInfos, + OOTSceneHeaderLighting, + OOTSceneHeaderCutscene, + OOTSceneHeaderExits, + OOTSceneHeaderActors, + OOTSceneHeaderPath, + OOTSceneHeaderCrawlspace, +) @dataclass diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py new file mode 100644 index 000000000..4552da86d --- /dev/null +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -0,0 +1,325 @@ +from dataclasses import dataclass, field +from bpy.types import Object +from ...utility import CData, indent +from .common import TransitionActor, EntranceActor + + +@dataclass +class EnvLightSettings: + envLightMode: str + ambientColor: tuple[int, int, int] + light1Color: tuple[int, int, int] + light1Dir: tuple[int, int, int] + light2Color: tuple[int, int, int] + light2Dir: tuple[int, int, int] + fogColor: tuple[int, int, int] + fogNear: int + zFar: int + blendRate: int + + def getBlendFogNear(self): + return f"(({self.blendRate} << 10) | {self.fogNear})" + + def getColorValues(self, vector: tuple[int, int, int]): + return ", ".join(f"{v:5}" for v in vector) + + def getDirectionValues(self, vector: tuple[int, int, int]): + return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) + + def getLightSettingsEntry(self, index: int): + isLightingCustom = self.envLightMode == "Custom" + + vectors = [ + (self.ambientColor, "Ambient Color", self.getColorValues), + (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), + (self.light1Color, "Diffuse0 Color", self.getColorValues), + (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), + (self.light2Color, "Diffuse1 Color", self.getColorValues), + (self.fogColor, "Fog Color", self.getColorValues), + ] + + fogData = [ + (self.getBlendFogNear(), "Blend Rate & Fog Near"), + (f"{self.zFar}", "Fog Far"), + ] + + lightDescs = ["Dawn", "Day", "Dusk", "Night"] + + if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": + # @TODO: Improve the lighting system. + # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. + # This is not accurate and more complicated, + # for now we are doing ``index % 4`` to avoid having an OoB read in the list + # but this will need to be changed the day the lighting system is updated. + lightDesc = f"// {lightDescs[index % 4]} Lighting\n" + else: + isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" + lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" + + lightData = ( + (indent + lightDesc) + + (indent + "{\n") + + "".join( + indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors + ) + + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) + + (indent + "},\n") + ) + + return lightData + + +@dataclass +class Path: + name: str + points: list[tuple[int, int, int]] = field(default_factory=list) + + def getPathPointListC(self): + pathData = CData() + pathName = f"Vec3s {self.name}" + + # .h + pathData.header = f"extern {pathName}[];\n" + + # .c + pathData.source = ( + f"{pathName}[]" + + " = {\n" + + "\n".join( + indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points + ) + + "\n};\n\n" + ) + + return pathData + + +@dataclass +class OOTSceneHeaderInfos: + ### General ### + + keepObjectID: str + naviHintType: str + drawConfig: str + appendNullEntrance: bool + useDummyRoomList: bool + + ### Skybox And Sound ### + + # Skybox + skyboxID: str + skyboxConfig: str + + # Sound + sequenceID: str + ambienceID: str + specID: str + + ### Camera And World Map ### + + # World Map + worldMapLocation: str + + # Camera + sceneCamType: str + + +@dataclass +class OOTSceneHeaderLighting: + name: str + envLightMode: str = None + settings: list[EnvLightSettings] = field(default_factory=list) + + def getEnvLightSettingsC(self): + lightSettingsC = CData() + lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" + + # .h + lightSettingsC.header = f"extern {lightName};\n" + + # .c + lightSettingsC.source = ( + (lightName + " = {\n") + + "".join(light.getLightSettingsEntry(i) for i, light in enumerate(self.settings)) + + "};\n\n" + ) + + return lightSettingsC + + +@dataclass +class OOTSceneHeaderCutscene: + name: str + writeType: str + writeCutscene: bool + csObj: Object + csWriteCustom: str + extraCutscenes: list[Object] + + def getCutsceneC(self): + # will be implemented when PR #208 is merged + cutsceneData = CData() + return cutsceneData + + +@dataclass +class OOTSceneHeaderExits: + name: str = None + exitList: list[tuple[int, str]] = field(default_factory=list) + + def getExitListC(self): + exitListC = CData() + listName = f"u16 {self.name}[{len(self.exitList)}]" + + # .h + exitListC.header = f"extern {listName};\n" + + # .c + exitListC.source = ( + (listName + " = {\n") + # @TODO: use the enum name instead of the raw index + + "\n".join(indent + f"{value}," for (_, value) in self.exitList) + + "\n};\n\n" + ) + + return exitListC + + +@dataclass +class OOTSceneHeaderActors: + entranceListName: str + startPositionsName: str + transActorListName: str + + transitionActorList: list[TransitionActor] = field(default_factory=list) + entranceActorList: list[EntranceActor] = field(default_factory=list) + + def getSpawnActorListC(self): + """Returns the spawn actor list for the current header""" + spawnActorList = CData() + listName = f"ActorEntry {self.startPositionsName}" + + # .h + spawnActorList.header = f"extern {listName}[];\n" + + # .c + spawnActorList.source = ( + (f"{listName}[]" + " = {\n") + + "".join(entrance.getActorEntry() for entrance in self.entranceActorList) + + "};\n\n" + ) + + return spawnActorList + + def getSpawnListC(self): + """Returns the spawn list for the current header""" + spawnList = CData() + listName = f"Spawn {self.entranceListName}" + + # .h + spawnList.header = f"extern {listName}[];\n" + + # .c + spawnList.source = ( + (f"{listName}[]" + " = {\n") + + (indent + "// { Spawn Actor List Index, Room Index }\n") + + "".join(entrance.getSpawnEntry() for entrance in self.entranceActorList) + + "};\n\n" + ) + + return spawnList + + def getTransActorListC(self): + """Returns the transition actor list for the current header""" + transActorList = CData() + listName = f"TransitionActorEntry {self.transActorListName}" + + # .h + transActorList.header = f"extern {listName}[];\n" + + # .c + transActorList.source = ( + (f"{listName}[]" + " = {\n") + + "\n".join(transActor.getTransitionActorEntry() for transActor in self.transitionActorList) + + "};\n\n" + ) + + return transActorList + + +@dataclass +class OOTSceneHeaderPath: + name: str + pathList: list[Path] + + def getPathC(self): + pathData = CData() + pathListData = CData() + listName = f"Path {self.name}[{len(self.pathList)}]" + + # .h + pathListData.header = f"extern {listName};\n" + + # .c + pathListData.source = listName + " = {\n" + + for path in self.pathList: + pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" + pathData.append(path.getPathPointListC()) + + pathListData.source += "};\n\n" + pathData.append(pathListData) + + return pathData + + +@dataclass +class OOTSceneHeaderCrawlspace: + name: str + + +@dataclass +class OOTSceneAlternateHeader: + name: str + childNight: "OOTSceneHeader" = None + adultDay: "OOTSceneHeader" = None + adultNight: "OOTSceneHeader" = None + cutscenes: list["OOTSceneHeader"] = field(default_factory=list) + + +@dataclass +class OOTSceneHeader: + name: str + infos: OOTSceneHeaderInfos + lighting: OOTSceneHeaderLighting + cutscene: OOTSceneHeaderCutscene + exits: OOTSceneHeaderExits + actors: OOTSceneHeaderActors + path: OOTSceneHeaderPath + crawlspace: OOTSceneHeaderCrawlspace + + def getHeaderC(self): + headerData = CData() + + # Write the spawn position list data and the entrance list + if len(self.actors.entranceActorList) > 0: + headerData.append(self.actors.getSpawnActorListC()) + headerData.append(self.actors.getSpawnListC()) + + # Write the transition actor list data + if len(self.actors.transitionActorList) > 0: + headerData.append(self.actors.getTransActorListC()) + + # Write the exit list + if len(self.exits.exitList) > 0: + headerData.append(self.exits.getExitListC()) + + # Write the light data + if len(self.lighting.settings) > 0: + headerData.append(self.lighting.getEnvLightSettingsC()) + + # Write the path data, if used + if len(self.path.pathList) > 0: + headerData.append(self.path.getPathC()) + + return headerData From 2d7a09e3df95f2661a7d758db7e5dd5bcfcf5ea7 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:31:32 +0200 Subject: [PATCH 11/98] collision start --- fast64_internal/oot/new_exporter/collision.py | 308 ++++++++++++++++++ fast64_internal/oot/new_exporter/common.py | 3 + fast64_internal/oot/new_exporter/exporter.py | 12 +- fast64_internal/oot/new_exporter/scene.py | 167 +++++++++- 4 files changed, 485 insertions(+), 5 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/collision.py diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py new file mode 100644 index 000000000..2ee357c3f --- /dev/null +++ b/fast64_internal/oot/new_exporter/collision.py @@ -0,0 +1,308 @@ +from dataclasses import dataclass +from ...utility import PluginError, CData, indent + + +@dataclass +class CollisionPoly: + indices: list[int] + ignoreCamera: bool + ignoreActor: bool + ignoreProjectile: bool + enableConveyor: bool + normal: tuple[int, int, int] + dist: int + type: int = None + + def getFlags_vIA(self): + vertPart = self.indices[0] & 0x1FFF + colPart = (1 if self.ignoreCamera else 0) + (2 if self.ignoreActor else 0) + (4 if self.ignoreProjectile else 0) + return vertPart | (colPart << 13) + + def getFlags_vIB(self): + vertPart = self.indices[1] & 0x1FFF + conveyorPart = 1 if self.enableConveyor else 0 + return vertPart | (conveyorPart << 13) + + def getVIC(self): + return self.indices[2] & 0x1FFF + + def getCollisionPolyEntryC(self): + if self.type is None: + raise PluginError("ERROR: Type unset!") + return ( + (indent + "{ ") + + ", ".join( + ( + f"0x{self.type:04X}", + f"0x{self.getFlags_vIA():04X}", + f"0x{self.getFlags_vIB():04X}", + f"0x{self.getVIC():04X}", + ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), + f"0x{self.dist:04X}", + ) + ) + + " }," + ) + + +@dataclass +class SurfaceType: + bgCamIndex: int + exitIndex: int + floorType: int + unk18: int # unused? + wallType: int + floorProperty: int + isSoft: bool + isHorseBlocked: bool + + material: int + floorEffect: int + lightSetting: int + echo: int + canHookshot: bool + conveyorSpeed: int + conveyorDirection: int + isWallDamage: bool # unk27 + + conveyorKeepMomentum: bool + useMacros: bool = True + isSoftC: str = None + isHorseBlockedC: str = None + canHookshotC: str = None + isWallDamageC: str = None + + def __post_init__(self): + if self.conveyorKeepMomentum: + self.conveyorSpeed += 4 + + self.isSoftC = "1" if self.isSoft else "0" + self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" + self.canHookshotC = "1" if self.canHookshot else "0" + self.isWallDamageC = "1" if self.isWallDamage else "0" + + def getSurfaceType0(self): + if self.useMacros: + return ( + ("SURFACETYPE0(") + + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " + + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isHorseBlockedC} & 1) << 31)", + f"(({self.isSoftC} & 1) << 30)", + f"(({self.floorProperty} & 0x0F) << 26)", + f"(({self.wallType} & 0x1F) << 21)", + f"(({self.unk18} & 0x07) << 18)", + f"(({self.floorType} & 0x1F) << 13)", + f"(({self.exitIndex} & 0x1F) << 8)", + f"({self.bgCamIndex} & 0xFF)", + ] + ) + + ")" + ) + + def getSurfaceType1(self): + if self.useMacros: + return ( + ("SURFACETYPE1(") + + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " + + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isWallDamageC} & 1) << 27)", + f"(({self.conveyorDirection} & 0x3F) << 21)", + f"(({self.conveyorSpeed} & 0x07) << 18)", + f"(({self.canHookshotC} & 1) << 17)", + f"(({self.echo} & 0x3F) << 11)", + f"(({self.lightSetting} & 0x1F) << 6)", + f"(({self.floorEffect} & 0x03) << 4)", + f"({self.material} & 0x0F)", + ] + ) + + ")" + ) + + def getSurfaceTypeEntryC(self): + if self.useMacros: + return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," + else: + return (indent + "{\n") + self.getSurfaceType0() + ",\n" + self.getSurfaceType1() + ("\n" + indent + "},") + + +@dataclass +class BgCamFuncData: # CameraPosData + name: str + pos: tuple[int, int, int] + rot: tuple[int, int, int] + fov: int + roomImageOverrideBgCamIndex: int + timer: int + flags: int + unk_10: int = 0 # unused + + +@dataclass +class CrawlspaceData: + name: str + points: list[tuple[int, int, int]] + + +@dataclass +class BgCamInfo: + name: str + setting: int + count: int + + # Export one of these but never both, see BgCamInfo in z64bgcheck.h + bgCamFuncDataList: list[BgCamFuncData] + crawlspaceList: list[CrawlspaceData] + + +@dataclass +class WaterBox: + name: str + xMin: int + ySurface: int + zMin: int + xLength: int + zLength: int + + # Properties + bgCamIndex: int + lightIndex: int + roomIndex: int + setFlag19: bool + + def getWaterboxProperties(self): + return ( + "(" + + " | ".join( + prop + for prop in [ + f"(({'1' if self.setFlag19 else '0'} & 1) << 19)", + f"(({self.roomIndex} & 0x3F) << 13)", + f"(({self.lightIndex} & 0x1F) << 8)", + f"(({self.bgCamIndex}) & 0xFF)", + ] + ) + + ")" + ) + + +@dataclass +class Vertex: + pos: tuple[int, int, int] + + def getVertexEntryC(self): + return indent + "{ " + ", ".join(f"{p}" for p in self.pos) + " }," + + +@dataclass +class CollisionHeaderVertices: + name: str + vertexList: list[Vertex] + + def getVertexListC(self): + vertData = CData() + listName = f"Vec3s {self.name}[{len(self.vertexList)}]" + + # .h + vertData.header = f"extern {listName};\n" + + # .c + vertData.source = ( + (listName + " = {\n") + "\n".join(vertex.getVertexEntryC() for vertex in self.vertexList) + "\n};\n\n" + ) + + return vertData + + +@dataclass +class CollisionHeaderCollisionPoly: + name: str + polyList: list[CollisionPoly] + + def getCollisionPolyDataC(self): + colPolyData = CData() + listName = f"CollisionPoly {self.name}[{len(self.polyList)}]" + + # .h + colPolyData.header = f"extern {listName};\n" + + # .c + colPolyData.source = ( + (listName + " = {\n") + "\n".join(poly.getCollisionPolyEntryC() for poly in self.polyList) + "\n};\n\n" + ) + + return colPolyData + + +@dataclass +class CollisionHeaderSurfaceType: + name: str + surfaceTypeList: list[SurfaceType] + + def getSurfaceTypeDataC(self): + surfaceData = CData() + listName = f"SurfaceType {self.name}[{len(self.surfaceTypeList)}]" + + # .h + surfaceData.header = f"extern {listName};\n" + + # .c + surfaceData.source = ( + (listName + " = {\n") + "\n".join(poly.getSurfaceTypeEntryC() for poly in self.surfaceTypeList) + "\n};\n\n" + ) + + return surfaceData + + +@dataclass +class CollisionHeaderBgCamInfo: + name: str + bgCamInfoList: list[BgCamInfo] + + +@dataclass +class CollisionHeaderWaterBox: + name: str + waterboxList: list[WaterBox] + + +@dataclass +class OOTSceneCollisionHeader: + name: str + minBounds: tuple[int, int, int] = None + maxBounds: tuple[int, int, int] = None + vertices: CollisionHeaderVertices = None + collisionPoly: CollisionHeaderCollisionPoly = None + surfaceType: CollisionHeaderSurfaceType = None + bgCamInfo: CollisionHeaderBgCamInfo = None + waterbox: CollisionHeaderWaterBox = None + + def getCollisionDataC(self): + colData = CData() + + if len(self.vertices.vertexList) > 0: + colData.append(self.vertices.getVertexListC()) + + if len(self.collisionPoly.polyList) > 0: + colData.append(self.collisionPoly.getCollisionPolyDataC()) + + if len(self.surfaceType.surfaceTypeList) > 0: + colData.append(self.surfaceType.getSurfaceTypeDataC()) + + return colData diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/common.py index 01611ef76..3e1919b00 100644 --- a/fast64_internal/oot/new_exporter/common.py +++ b/fast64_internal/oot/new_exporter/common.py @@ -16,6 +16,9 @@ class Common: transform: Matrix roomIndex: int = None + def roundPosition(self, position) -> tuple[int, int, int]: + return (round(position[0]), round(position[1]), round(position[2])) + def isCurrentHeaderValid(self, headerSettings: OOTActorHeaderProperty, headerIndex: int): preset = headerSettings.sceneSetupPreset diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 9923dadfc..e33600b03 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -20,6 +20,7 @@ from .common import altHeaderList from .scene import OOTScene, OOTSceneAlternateHeader from .room import OOTRoom, OOTRoomAlternateHeader +from .collision import OOTSceneCollisionHeader from ..oot_utility import ( ExportInfo, @@ -64,6 +65,7 @@ class OOTSceneExport: sceneData: OOTSceneData = None roomList: dict[int, OOTRoomData] = field(default_factory=dict) hasCutscenes: bool = False + singleFileExport: bool = True def getNewRoomList(self): processedRooms = [] @@ -130,6 +132,7 @@ def getNewScene(self): sceneData.altHeader = altHeaderData if hasAltHeader else None sceneData.roomList = self.getNewRoomList() + sceneData.colHeader = sceneData.getNewCollisionHeader() sceneData.validateScene() return sceneData @@ -186,11 +189,13 @@ def setRoomListData(self): def setSceneData(self): sceneData = OOTSceneData() sceneMainData = self.scene.getSceneMainC() + sceneCollisionData = self.scene.colHeader.getCollisionDataC() sceneCutsceneData = self.scene.getSceneCutscenesC() sceneData.sceneMain = sceneMainData.source + sceneData.sceneCollision = sceneCollisionData.source sceneData.sceneCutscenes = [cs.source for cs in sceneCutsceneData] - self.header += sceneMainData.header + "".join(cs.header for cs in sceneCutsceneData) + self.header += sceneMainData.header + "".join(cs.header for cs in sceneCutsceneData) + sceneCollisionData.header self.sceneData = sceneData def setIncludeData(self): @@ -223,6 +228,11 @@ def writeScene(self): for i, cs in enumerate(self.sceneData.sceneCutscenes): writeFile(f"{sceneBasePath}_cs_{i}.c", cs) + if self.singleFileExport: + self.sceneData.sceneMain += self.sceneData.sceneCollision + else: + writeFile(f"{sceneBasePath}_col.c", self.sceneData.sceneCollision) + writeFile(sceneBasePath + ".c", self.sceneData.sceneMain) writeFile(sceneBasePath + ".h", self.header) diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 5b3aa55ca..becbb854c 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -1,17 +1,34 @@ +import math + from dataclasses import dataclass, field -from bpy.types import Object +from mathutils import Vector, Matrix +from bpy.types import Object, Mesh +from bpy.ops import object from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent +from ..oot_utility import convertIntTo2sComplement from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ..oot_constants import ootData from .commands import OOTSceneCommands from .common import Common, TransitionActor, EntranceActor, altHeaderList from .room import OOTRoom +from .collision import ( + OOTSceneCollisionHeader, + CollisionHeaderVertices, + CollisionHeaderCollisionPoly, + CollisionHeaderSurfaceType, + CollisionHeaderBgCamInfo, + CollisionHeaderWaterBox, + SurfaceType, + CollisionPoly, + Vertex, +) + from .scene_header import ( + EnvLightSettings, + Path, OOTSceneHeader, OOTSceneAlternateHeader, - Path, - EnvLightSettings, OOTSceneHeaderInfos, OOTSceneHeaderLighting, OOTSceneHeaderCutscene, @@ -31,8 +48,12 @@ class OOTScene(Common, OOTSceneCommands): roomList: list[OOTRoom] = field(default_factory=list) roomListName: str = None + colHeader: OOTSceneCollisionHeader = None + meshObjList: list[Object] = field(default_factory=list) + def __post_init__(self): self.roomListName = f"{self.name}_roomList" + self.meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] def validateCurveData(self, curveObj: Object): curveData = curveObj.data @@ -281,7 +302,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getEntranceActorListFromProps(), ), OOTSceneHeaderPath(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), - OOTSceneHeaderCrawlspace(None), # not implemented yet + OOTSceneHeaderCrawlspace(None), # not implemented yet ) def getRoomListC(self): @@ -321,6 +342,144 @@ def getRoomListC(self): roomList.source += "};\n\n" return roomList + def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): + if len(bounds) == 0: + bounds.append([position[0], position[1], position[2]]) + bounds.append([position[0], position[1], position[2]]) + return + + minBounds = bounds[0] + maxBounds = bounds[1] + for i in range(3): + if position[i] < minBounds[i]: + minBounds[i] = position[i] + if position[i] > maxBounds[i]: + maxBounds[i] = position[i] + + def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): + for i in range(len(vertArray)): + colVert = vertArray[i].pos + if colVert == vert: + return i + return None + + def getNewCollisionHeader(self): + object.select_all(action="DESELECT") + self.sceneObj.select_set(True) + + surfaceTypeList: list[SurfaceType] = [] + polyList: list[CollisionPoly] = [] + vertexList: list[Vertex] = [] + bounds = [] + + i = 0 + for meshObj in self.meshObjList: + if not meshObj.ignore_collision: + if len(meshObj.data.materials) == 0: + raise PluginError(f"'{meshObj.name}' must have a material associated with it.") + + meshObj.data.calc_loop_triangles() + for face in meshObj.data.loop_triangles: + colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty + + planePoint = self.transform @ meshObj.data.vertices[face.vertices[0]].co + (x1, y1, z1) = self.roundPosition(planePoint) + (x2, y2, z2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, y3, z3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) + + self.updateBounds((x1, y1, z1), bounds) + self.updateBounds((x2, y2, z2), bounds) + self.updateBounds((x3, y3, z3), bounds) + + normal = (self.transform.inverted().transposed() @ face.normal).normalized() + distance = int( + round(-1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2])) + ) + distance = convertIntTo2sComplement(distance, 2, True) + + indices: list[int] = [] + for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: + index = self.getVertIndex(vertex, vertexList) + if index is None: + vertexList.append(Vertex(vertex)) + indices.append(len(vertexList) - 1) + else: + indices.append(index) + assert len(indices) == 3 + + # We need to ensure two things about the order in which the vertex indices are: + # + # 1) The vertex with the minimum y coordinate should be first. + # This prevents a bug due to an optimization in OoT's CollisionPoly_GetMinY. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L202 + # + # 2) The vertices should wrap around the polygon normal **counter-clockwise**. + # This is needed for OoT's dynapoly, which is collision that can move. + # When it moves, the vertex coordinates and normals are recomputed. + # The normal is computed based on the vertex coordinates, which makes the order of vertices matter. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L2976 + + # Address 1): sort by ascending y coordinate + indices.sort(key=lambda index: vertexList[index].pos[1]) + + # Address 2): + # swap indices[1] and indices[2], + # if the normal computed from the vertices in the current order is the wrong way. + v0 = Vector(vertexList[indices[0]].pos) + v1 = Vector(vertexList[indices[1]].pos) + v2 = Vector(vertexList[indices[2]].pos) + if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: + indices[1], indices[2] = indices[2], indices[1] + + useConveyor = colProp.conveyorOption != "None" + surfaceType = SurfaceType( + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorSetting"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorProperty"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + ) + + if surfaceType not in surfaceTypeList: + surfaceTypeList.append(surfaceType) + + polyList.append( + CollisionPoly( + indices, + colProp.ignoreCameraCollision, + colProp.ignoreActorCollision, + colProp.ignoreProjectileCollision, + useConveyor, + tuple(normal), + distance, + i, + ) + ) + i += 1 + + return OOTSceneCollisionHeader( + f"{self.name}_collisionHeader", + bounds[0], + bounds[1], + CollisionHeaderVertices(f"{self.name}_vertices", vertexList), + CollisionHeaderCollisionPoly(f"{self.name}_polygons", polyList), + CollisionHeaderSurfaceType(f"{self.name}_polygonTypes", surfaceTypeList), + None, + None, + ) + def getSceneMainC(self): sceneC = CData() headers: list[tuple[OOTSceneHeader, str]] = [] From 35dd43030b657616af9d4e29dfd250d6e2c504e5 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 01:53:25 +0200 Subject: [PATCH 12/98] collision part 2 --- fast64_internal/oot/new_exporter/collision.py | 210 +++++++++++++++--- fast64_internal/oot/new_exporter/commands.py | 7 +- fast64_internal/oot/new_exporter/scene.py | 133 +++++++++-- 3 files changed, 302 insertions(+), 48 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 2ee357c3f..464f95ae8 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from ...utility import PluginError, CData, indent @@ -144,41 +144,49 @@ def getSurfaceTypeEntryC(self): @dataclass class BgCamFuncData: # CameraPosData - name: str pos: tuple[int, int, int] rot: tuple[int, int, int] fov: int roomImageOverrideBgCamIndex: int - timer: int - flags: int - unk_10: int = 0 # unused @dataclass class CrawlspaceData: - name: str - points: list[tuple[int, int, int]] + points: list[tuple[int, int, int]] = field(default_factory=list) + + def getCrawlspacePointEntriesC(self): + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) @dataclass class BgCamInfo: - name: str - setting: int + setting: str count: int - - # Export one of these but never both, see BgCamInfo in z64bgcheck.h + arrayIndex: int bgCamFuncDataList: list[BgCamFuncData] - crawlspaceList: list[CrawlspaceData] + + def getCamPosEntriesC(self): + source = "" + + for camData in self.bgCamFuncDataList: + source += ( + (indent + "{ " + ", ".join(f"{p:6}" for p in camData.pos) + " },\n") + + (indent + "{ " + ", ".join(f"{r:6}" for r in camData.rot) + " },\n") + + (indent + "{ " + f"{camData.fov:6}, {camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + ) + + return source + + def getBgCamInfoEntryC(self, posDataName: str): + ptr = f"&{posDataName}[{self.arrayIndex}]" if len(self.bgCamFuncDataList) > 0 else "NULL" + return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" @dataclass class WaterBox: - name: str - xMin: int - ySurface: int - zMin: int - xLength: int - zLength: int + position: tuple[int, int, int] + scale: float + emptyDisplaySize: float # Properties bgCamIndex: int @@ -186,19 +194,55 @@ class WaterBox: roomIndex: int setFlag19: bool + xMin: int = None + ySurface: int = None + zMin: int = None + xLength: int = None + zLength: int = None + + useMacros: bool = True + setFlag19C: str = None + roomIndexC: str = None + + def __post_init__(self): + self.setFlag19C = "1" if self.setFlag19 else "0" + self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" + + # The scale ordering is due to the fact that scaling happens AFTER rotation. + # Thus the translation uses Y-up, while the scale uses Z-up. + xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) + zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) + + self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) + self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) + self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) + self.xLength = xMax - self.xMin + self.zLength = zMax - self.zMin + def getWaterboxProperties(self): - return ( - "(" - + " | ".join( - prop - for prop in [ - f"(({'1' if self.setFlag19 else '0'} & 1) << 19)", - f"(({self.roomIndex} & 0x3F) << 13)", - f"(({self.lightIndex} & 0x1F) << 8)", - f"(({self.bgCamIndex}) & 0xFF)", - ] + if self.useMacros: + return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" + else: + return ( + "(" + + " | ".join( + prop + for prop in [ + f"(({self.setFlag19C} & 1) << 19)", + f"(({self.roomIndexC} & 0x3F) << 13)", + f"(({self.lightIndex} & 0x1F) << 8)", + f"(({self.bgCamIndex}) & 0xFF)", + ] + ) + + ")" ) - + ")" + + def getWaterboxEntryC(self): + return ( + (indent + "{ ") + + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " + + self.getWaterboxProperties() + + " }," ) @@ -207,7 +251,7 @@ class Vertex: pos: tuple[int, int, int] def getVertexEntryC(self): - return indent + "{ " + ", ".join(f"{p}" for p in self.pos) + " }," + return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," @dataclass @@ -273,7 +317,59 @@ def getSurfaceTypeDataC(self): @dataclass class CollisionHeaderBgCamInfo: name: str + posDataName: str bgCamInfoList: list[BgCamInfo] + crawlspacePosList: list[CrawlspaceData] + + crawlspaceCount: int = 6 + arrayIdx: int = 0 + + def __post_init__(self): + if len(self.bgCamInfoList) > 0: + self.arrayIdx = self.bgCamInfoList[-1].arrayIndex + self.crawlspaceCount + + def getCamPosListC(self): + posData = CData() + listName = f"Vec3s {self.posDataName}[]" + + # .h + posData.header = f"extern {listName};" + + # .c + posData.source = ( + (listName + " = {\n") + + "".join(cam.getCamPosEntriesC() for cam in self.bgCamInfoList) + + "".join(crawlspace.getCrawlspacePointEntriesC() for crawlspace in self.crawlspacePosList) + + "};\n\n" + ) + + return posData + + def getCrawlspaceInfoEntries(self): + source = "" + + for _ in self.crawlspacePosList: + source += indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{self.posDataName}[{self.arrayIdx}]" + " },\n" + self.arrayIdx += self.crawlspaceCount + + return source + + def getBgCamInfoListC(self): + bgCamInfoData = CData() + listName = f"BgCamInfo {self.name}[]" + + # .h + bgCamInfoData.header = f"extern {listName};" + + # .c + bgCamInfoData.source = ( + (listName + " = {\n") + + "".join(cam.getBgCamInfoEntryC(self.posDataName) for cam in self.bgCamInfoList) + + self.getCrawlspaceInfoEntries() + + "};\n\n" + ) + + return bgCamInfoData @dataclass @@ -281,6 +377,20 @@ class CollisionHeaderWaterBox: name: str waterboxList: list[WaterBox] + def getWaterboxListC(self): + wboxData = CData() + listName = f"WaterBox {self.name}[{len(self.waterboxList)}]" + + # .h + wboxData.header = f"extern {listName};\n" + + # .c + wboxData.source = ( + (listName + " = {\n") + "\n".join(wBox.getWaterboxEntryC() for wBox in self.waterboxList) + "\n};\n\n" + ) + + return wboxData + @dataclass class OOTSceneCollisionHeader: @@ -295,14 +405,54 @@ class OOTSceneCollisionHeader: def getCollisionDataC(self): colData = CData() + varName = f"CollisionHeader {self.name}" + + vtxPtrLine = "0, NULL" + colPolyPtrLine = "0, NULL" + surfacePtrLine = "NULL" + camPtrLine = "NULL" + wBoxPtrLine = "0, NULL" if len(self.vertices.vertexList) > 0: colData.append(self.vertices.getVertexListC()) + vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" if len(self.collisionPoly.polyList) > 0: colData.append(self.collisionPoly.getCollisionPolyDataC()) + colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" if len(self.surfaceType.surfaceTypeList) > 0: colData.append(self.surfaceType.getSurfaceTypeDataC()) + surfacePtrLine = f"{self.surfaceType.name}" + + if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: + colData.append(self.bgCamInfo.getCamPosListC()) + colData.append(self.bgCamInfo.getBgCamInfoListC()) + camPtrLine = f"{self.bgCamInfo.name}" + + if len(self.waterbox.waterboxList) > 0: + colData.append(self.waterbox.getWaterboxListC()) + wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" + + # .h + colData.header = f"extern {varName};\n" + + # .c + colData.source += ( + (varName + " = {\n") + + ",\n".join( + indent + val + for val in [ + ("{ " + ", ".join(f"{val}" for val in self.minBounds) + " }"), + ("{ " + ", ".join(f"{val}" for val in self.maxBounds) + " }"), + vtxPtrLine, + colPolyPtrLine, + surfacePtrLine, + camPtrLine, + wBoxPtrLine, + ] + ) + + "\n};\n\n" + ) return colData diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 602b5634a..be47c74b5 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from .collision import OOTSceneCollisionHeader from .room import OOTRoom, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors from .scene import ( OOTScene, @@ -101,8 +102,8 @@ def getTransActorListCmd(self, actors: "OOTSceneHeaderActors"): def getMiscSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" - # def getColHeaderCmd(self, outScene: OOTScene): - # return indent + f"SCENE_CMD_COL_HEADER(&{outScene.collision.headerName()})" + def getColHeaderCmd(self, colHeader: "OOTSceneCollisionHeader"): + return indent + f"SCENE_CMD_COL_HEADER(&{colHeader.name})" def getSpawnListCmd(self, actors: "OOTSceneHeaderActors"): return ( @@ -152,7 +153,6 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he getCmdFunc1List = [ self.getExitListCmd, self.getSpawnActorListCmd, - # self.getColHeaderCmd, ] getCmdGeneralList = [ @@ -174,6 +174,7 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he hasAltHeaders = headerIndex == 0 and scene.hasAlternateHeaders() sceneCmdData = ( (scene.getAltHeaderListCmd(scene.altHeader.name) if hasAltHeaders else "") + + self.getColHeaderCmd(scene.colHeader) + self.getRoomListCmd(scene) + self.getSkyboxSettingsCmd(curHeader.infos, curHeader.lighting) + self.getLightSettingsCmd(curHeader.lighting) diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index becbb854c..4f26d7c6e 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -1,13 +1,14 @@ import math from dataclasses import dataclass, field -from mathutils import Vector, Matrix +from mathutils import Vector, Quaternion from bpy.types import Object, Mesh from bpy.ops import object -from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent +from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, checkIdentityRotation, indent from ..oot_utility import convertIntTo2sComplement from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ..oot_constants import ootData +from ..oot_collision_classes import decomp_compat_map_CameraSType from .commands import OOTSceneCommands from .common import Common, TransitionActor, EntranceActor, altHeaderList from .room import OOTRoom @@ -22,6 +23,10 @@ SurfaceType, CollisionPoly, Vertex, + WaterBox, + BgCamInfo, + BgCamFuncData, + CrawlspaceData, ) from .scene_header import ( @@ -49,11 +54,9 @@ class OOTScene(Common, OOTSceneCommands): roomListName: str = None colHeader: OOTSceneCollisionHeader = None - meshObjList: list[Object] = field(default_factory=list) def __post_init__(self): self.roomListName = f"{self.name}_roomList" - self.meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] def validateCurveData(self, curveObj: Object): curveData = curveObj.data @@ -363,17 +366,18 @@ def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): return i return None - def getNewCollisionHeader(self): + def getColSurfaceVtxDataFromMeshObj(self): + meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] object.select_all(action="DESELECT") self.sceneObj.select_set(True) - surfaceTypeList: list[SurfaceType] = [] + surfaceTypeData: dict[int, SurfaceType] = {} polyList: list[CollisionPoly] = [] vertexList: list[Vertex] = [] bounds = [] i = 0 - for meshObj in self.meshObjList: + for meshObj in meshObjList: if not meshObj.ignore_collision: if len(meshObj.data.materials) == 0: raise PluginError(f"'{meshObj.name}' must have a material associated with it.") @@ -432,7 +436,7 @@ def getNewCollisionHeader(self): indices[1], indices[2] = indices[2], indices[1] useConveyor = colProp.conveyorOption != "None" - surfaceType = SurfaceType( + surfaceTypeData[i] = SurfaceType( colProp.cameraID, colProp.exitID, int(self.getPropValue(colProp, "floorSetting"), base=16), @@ -452,9 +456,6 @@ def getNewCollisionHeader(self): colProp.conveyorKeepMomentum if useConveyor else False, ) - if surfaceType not in surfaceTypeList: - surfaceTypeList.append(surfaceType) - polyList.append( CollisionPoly( indices, @@ -468,16 +469,118 @@ def getNewCollisionHeader(self): ) ) i += 1 + return bounds, vertexList, polyList, [surfaceTypeData[i] for i in range(len(surfaceTypeData))] + + def getBgCamFuncDataFromObjects(self, camObj: Object): + camProp = camObj.ootCameraPositionProperty + + # Camera faces opposite direction + pos, rot, _, _ = self.getConvertedTransformWithOrientation( + self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + ) + + fov = math.degrees(camObj.data.angle) + if fov > 3.6: + fov *= 100 # see CAM_DATA_SCALED() macro + + return BgCamFuncData( + pos, + rot, + round(fov), + camProp.bgImageOverrideIndex, + ) + + def getCrawlspaceDataFromObjects(self): + crawlspaceList: list[CrawlspaceData] = [] + crawlspaceObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" + ] + + for obj in crawlspaceObjList: + if self.validateCurveData(obj): + crawlspaceList.append( + CrawlspaceData( + [ + [round(value) for value in self.transform @ obj.matrix_world @ point.co] + for point in obj.data.splines[0].points + ] + ) + ) + return crawlspaceList + + def getBgCamInfoDataFromObjects(self): + camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] + camPosData: dict[int, BgCamFuncData] = {} + bgCamList: list[BgCamInfo] = [] + + index = 0 + for camObj in camObjList: + camProp = camObj.ootCameraPositionProperty + + if camProp.camSType == "Custom": + setting = camProp.camSTypeCustom + else: + setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) + + if camProp.hasPositionData: + count = 3 + index = camProp.index + if index in camPosData: + raise PluginError(f"Error: Repeated camera position index: {index} for {camObj.name}") + camPosData[index] = self.getBgCamFuncDataFromObjects(camObj) + else: + count = 0 + + bgCamList.append(BgCamInfo(setting, count, index, [camPosData[i] for i in range(len(camPosData))])) + index += count + return bgCamList + + def getWaterBoxDataFromObjects(self): + waterboxObjList = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" + ] + waterboxList: list[WaterBox] = [] + + for waterboxObj in waterboxObjList: + emptyScale = waterboxObj.empty_display_size + pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) + checkIdentityRotation(waterboxObj, orientedRot, False) + + wboxProp = waterboxObj.ootWaterBoxProperty + roomObj = self.getRoomObjectFromChild(waterboxObj) + waterboxList.append( + WaterBox( + pos, + scale, + emptyScale, + wboxProp.camera, + wboxProp.lighting, + roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, + wboxProp.flag19, + ) + ) + + return waterboxList + + def getNewCollisionHeader(self): + colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() return OOTSceneCollisionHeader( f"{self.name}_collisionHeader", - bounds[0], - bounds[1], + colBounds[0], + colBounds[1], CollisionHeaderVertices(f"{self.name}_vertices", vertexList), CollisionHeaderCollisionPoly(f"{self.name}_polygons", polyList), CollisionHeaderSurfaceType(f"{self.name}_polygonTypes", surfaceTypeList), - None, - None, + CollisionHeaderBgCamInfo( + f"{self.name}_bgCamInfo", + f"{self.name}_camPosData", + self.getBgCamInfoDataFromObjects(), + self.getCrawlspaceDataFromObjects(), + ), + CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), ) def getSceneMainC(self): From a2692603ca5a397b519d1a9af44d03e20dd5650e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 02:32:22 +0200 Subject: [PATCH 13/98] export stuff --- fast64_internal/oot/new_exporter/collision.py | 34 +++++---- fast64_internal/oot/new_exporter/commands.py | 2 +- fast64_internal/oot/new_exporter/exporter.py | 74 +++++++++++++++---- fast64_internal/oot/scene/operators.py | 1 + 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 464f95ae8..ca2a0638e 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -404,6 +404,7 @@ class OOTSceneCollisionHeader: waterbox: CollisionHeaderWaterBox = None def getCollisionDataC(self): + headerData = CData() colData = CData() varName = f"CollisionHeader {self.name}" @@ -413,6 +414,19 @@ def getCollisionDataC(self): camPtrLine = "NULL" wBoxPtrLine = "0, NULL" + if len(self.waterbox.waterboxList) > 0: + colData.append(self.waterbox.getWaterboxListC()) + wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" + + if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: + colData.append(self.bgCamInfo.getCamPosListC()) + colData.append(self.bgCamInfo.getBgCamInfoListC()) + camPtrLine = f"{self.bgCamInfo.name}" + + if len(self.surfaceType.surfaceTypeList) > 0: + colData.append(self.surfaceType.getSurfaceTypeDataC()) + surfacePtrLine = f"{self.surfaceType.name}" + if len(self.vertices.vertexList) > 0: colData.append(self.vertices.getVertexListC()) vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" @@ -421,24 +435,11 @@ def getCollisionDataC(self): colData.append(self.collisionPoly.getCollisionPolyDataC()) colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" - if len(self.surfaceType.surfaceTypeList) > 0: - colData.append(self.surfaceType.getSurfaceTypeDataC()) - surfacePtrLine = f"{self.surfaceType.name}" - - if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: - colData.append(self.bgCamInfo.getCamPosListC()) - colData.append(self.bgCamInfo.getBgCamInfoListC()) - camPtrLine = f"{self.bgCamInfo.name}" - - if len(self.waterbox.waterboxList) > 0: - colData.append(self.waterbox.getWaterboxListC()) - wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" - # .h - colData.header = f"extern {varName};\n" + headerData.header = f"extern {varName};\n" # .c - colData.source += ( + headerData.source += ( (varName + " = {\n") + ",\n".join( indent + val @@ -455,4 +456,5 @@ def getCollisionDataC(self): + "\n};\n\n" ) - return colData + headerData.append(colData) + return headerData diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index be47c74b5..ffd3ceb1a 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -103,7 +103,7 @@ def getMiscSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" def getColHeaderCmd(self, colHeader: "OOTSceneCollisionHeader"): - return indent + f"SCENE_CMD_COL_HEADER(&{colHeader.name})" + return indent + f"SCENE_CMD_COL_HEADER(&{colHeader.name}),\n" def getSpawnListCmd(self, actors: "OOTSceneHeaderActors"): return ( diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index e33600b03..80140c364 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -57,6 +57,7 @@ class OOTSceneExport: f3dType: str saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions + singleFileExport: bool dlFormat: DLFormat = DLFormat.Static scene: OOTScene = None @@ -65,7 +66,6 @@ class OOTSceneExport: sceneData: OOTSceneData = None roomList: dict[int, OOTRoomData] = field(default_factory=dict) hasCutscenes: bool = False - singleFileExport: bool = True def getNewRoomList(self): processedRooms = [] @@ -200,23 +200,65 @@ def setSceneData(self): def setIncludeData(self): suffix = "\n\n" + sceneInclude = f'\n#include "{self.scene.name}.h"\n' common = "\n".join( elem for elem in [ - '#include "ultra64.h"', + '#include "ultra64/ultratypes.h"', + '#include "libc/stdint.h"', + '#include "z64math.h"', + ] + ) + "\n" + + room = "\n".join( + elem + for elem in [ + '#include "z64object.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + ] + ) + "\n" + + scene = "\n".join( + elem + for elem in [ + '#include "z64dma.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + '#include "z64environment.h"', + ] + ) + "\n" + + collision = "\n".join( + elem + for elem in [ '#include "macros.h"', - '#include "z64.h"', - f'#include "{self.scene.name}.h"', + '#include "z64camera.h"', + '#include "z64bgcheck.h"', ] - ) - self.sceneData.sceneMain = common + suffix + self.sceneData.sceneMain + ) + "\n" - if self.hasCutscenes: - for cs in self.sceneData.sceneCutscenes: - cs = '#include "z64cutscene.h\n' + '#include "z64cutscene_commands.h\n\n' + cs + cutscene = "\n".join( + elem + for elem in [ + '#include "z64cutscene.h"', + '#include "z64cutscene_commands.h"', + ] + ) + "\n" - for room in self.roomList.values(): - room.roomMain = common + suffix + room.roomMain + for roomData in self.roomList.values(): + roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain + + if self.singleFileExport: + common += scene + collision + cutscene + sceneInclude + self.sceneData.sceneMain = common + suffix + self.sceneData.sceneMain + else: + self.sceneData.sceneMain = common + scene + sceneInclude + suffix + self.sceneData.sceneMain + self.sceneData.sceneCollision = common + collision + sceneInclude + suffix + self.sceneData.sceneCollision + + if self.hasCutscenes: + for cs in self.sceneData.sceneCutscenes: + cs = cutscene + sceneInclude + suffix + cs def writeScene(self): sceneBasePath = os.path.join(self.path, self.scene.name) @@ -224,14 +266,16 @@ def writeScene(self): for room in self.roomList.values(): writeFile(os.path.join(self.path, room.name + ".c"), room.roomMain) - if self.hasCutscenes: - for i, cs in enumerate(self.sceneData.sceneCutscenes): - writeFile(f"{sceneBasePath}_cs_{i}.c", cs) - if self.singleFileExport: self.sceneData.sceneMain += self.sceneData.sceneCollision + if self.hasCutscenes: + for i, cs in enumerate(self.sceneData.sceneCutscenes): + self.sceneData.sceneMain += cs else: writeFile(f"{sceneBasePath}_col.c", self.sceneData.sceneCollision) + if self.hasCutscenes: + for i, cs in enumerate(self.sceneData.sceneCutscenes): + writeFile(f"{sceneBasePath}_cs_{i}.c", cs) writeFile(sceneBasePath + ".c", self.sceneData.sceneMain) writeFile(sceneBasePath + ".h", self.header) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index b929d7567..b5ee03f82 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -185,6 +185,7 @@ def execute(self, context): bpy.context.scene.f3d_type, bpy.context.scene.saveTextures, bootOptions if hackerFeaturesEnabled else None, + settings.singleFile, ).export() # ootExportSceneToC( From 90dd75b1519ac2aa4bc470ba3f430a06457eef72 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:48:42 +0200 Subject: [PATCH 14/98] collision cleanup/fixes --- fast64_internal/oot/new_exporter/collision.py | 324 ++---------------- .../oot/new_exporter/collision_common.py | 260 ++++++++++++++ fast64_internal/oot/new_exporter/exporter.py | 100 +++--- fast64_internal/oot/new_exporter/scene.py | 48 ++- 4 files changed, 385 insertions(+), 347 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/collision_common.py diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index ca2a0638e..699979639 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -1,257 +1,14 @@ -from dataclasses import dataclass, field -from ...utility import PluginError, CData, indent +from dataclasses import dataclass +from ...utility import CData, indent - -@dataclass -class CollisionPoly: - indices: list[int] - ignoreCamera: bool - ignoreActor: bool - ignoreProjectile: bool - enableConveyor: bool - normal: tuple[int, int, int] - dist: int - type: int = None - - def getFlags_vIA(self): - vertPart = self.indices[0] & 0x1FFF - colPart = (1 if self.ignoreCamera else 0) + (2 if self.ignoreActor else 0) + (4 if self.ignoreProjectile else 0) - return vertPart | (colPart << 13) - - def getFlags_vIB(self): - vertPart = self.indices[1] & 0x1FFF - conveyorPart = 1 if self.enableConveyor else 0 - return vertPart | (conveyorPart << 13) - - def getVIC(self): - return self.indices[2] & 0x1FFF - - def getCollisionPolyEntryC(self): - if self.type is None: - raise PluginError("ERROR: Type unset!") - return ( - (indent + "{ ") - + ", ".join( - ( - f"0x{self.type:04X}", - f"0x{self.getFlags_vIA():04X}", - f"0x{self.getFlags_vIB():04X}", - f"0x{self.getVIC():04X}", - ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), - f"0x{self.dist:04X}", - ) - ) - + " }," - ) - - -@dataclass -class SurfaceType: - bgCamIndex: int - exitIndex: int - floorType: int - unk18: int # unused? - wallType: int - floorProperty: int - isSoft: bool - isHorseBlocked: bool - - material: int - floorEffect: int - lightSetting: int - echo: int - canHookshot: bool - conveyorSpeed: int - conveyorDirection: int - isWallDamage: bool # unk27 - - conveyorKeepMomentum: bool - useMacros: bool = True - isSoftC: str = None - isHorseBlockedC: str = None - canHookshotC: str = None - isWallDamageC: str = None - - def __post_init__(self): - if self.conveyorKeepMomentum: - self.conveyorSpeed += 4 - - self.isSoftC = "1" if self.isSoft else "0" - self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" - self.canHookshotC = "1" if self.canHookshot else "0" - self.isWallDamageC = "1" if self.isWallDamage else "0" - - def getSurfaceType0(self): - if self.useMacros: - return ( - ("SURFACETYPE0(") - + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " - + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" - + ")" - ) - else: - return ( - (indent * 2 + "(") - + " | ".join( - prop - for prop in [ - f"(({self.isHorseBlockedC} & 1) << 31)", - f"(({self.isSoftC} & 1) << 30)", - f"(({self.floorProperty} & 0x0F) << 26)", - f"(({self.wallType} & 0x1F) << 21)", - f"(({self.unk18} & 0x07) << 18)", - f"(({self.floorType} & 0x1F) << 13)", - f"(({self.exitIndex} & 0x1F) << 8)", - f"({self.bgCamIndex} & 0xFF)", - ] - ) - + ")" - ) - - def getSurfaceType1(self): - if self.useMacros: - return ( - ("SURFACETYPE1(") - + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " - + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" - + ")" - ) - else: - return ( - (indent * 2 + "(") - + " | ".join( - prop - for prop in [ - f"(({self.isWallDamageC} & 1) << 27)", - f"(({self.conveyorDirection} & 0x3F) << 21)", - f"(({self.conveyorSpeed} & 0x07) << 18)", - f"(({self.canHookshotC} & 1) << 17)", - f"(({self.echo} & 0x3F) << 11)", - f"(({self.lightSetting} & 0x1F) << 6)", - f"(({self.floorEffect} & 0x03) << 4)", - f"({self.material} & 0x0F)", - ] - ) - + ")" - ) - - def getSurfaceTypeEntryC(self): - if self.useMacros: - return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," - else: - return (indent + "{\n") + self.getSurfaceType0() + ",\n" + self.getSurfaceType1() + ("\n" + indent + "},") - - -@dataclass -class BgCamFuncData: # CameraPosData - pos: tuple[int, int, int] - rot: tuple[int, int, int] - fov: int - roomImageOverrideBgCamIndex: int - - -@dataclass -class CrawlspaceData: - points: list[tuple[int, int, int]] = field(default_factory=list) - - def getCrawlspacePointEntriesC(self): - return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) - - -@dataclass -class BgCamInfo: - setting: str - count: int - arrayIndex: int - bgCamFuncDataList: list[BgCamFuncData] - - def getCamPosEntriesC(self): - source = "" - - for camData in self.bgCamFuncDataList: - source += ( - (indent + "{ " + ", ".join(f"{p:6}" for p in camData.pos) + " },\n") - + (indent + "{ " + ", ".join(f"{r:6}" for r in camData.rot) + " },\n") - + (indent + "{ " + f"{camData.fov:6}, {camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") - ) - - return source - - def getBgCamInfoEntryC(self, posDataName: str): - ptr = f"&{posDataName}[{self.arrayIndex}]" if len(self.bgCamFuncDataList) > 0 else "NULL" - return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" - - -@dataclass -class WaterBox: - position: tuple[int, int, int] - scale: float - emptyDisplaySize: float - - # Properties - bgCamIndex: int - lightIndex: int - roomIndex: int - setFlag19: bool - - xMin: int = None - ySurface: int = None - zMin: int = None - xLength: int = None - zLength: int = None - - useMacros: bool = True - setFlag19C: str = None - roomIndexC: str = None - - def __post_init__(self): - self.setFlag19C = "1" if self.setFlag19 else "0" - self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" - - # The scale ordering is due to the fact that scaling happens AFTER rotation. - # Thus the translation uses Y-up, while the scale uses Z-up. - xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) - zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) - - self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) - self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) - self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) - self.xLength = xMax - self.xMin - self.zLength = zMax - self.zMin - - def getWaterboxProperties(self): - if self.useMacros: - return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" - else: - return ( - "(" - + " | ".join( - prop - for prop in [ - f"(({self.setFlag19C} & 1) << 19)", - f"(({self.roomIndexC} & 0x3F) << 13)", - f"(({self.lightIndex} & 0x1F) << 8)", - f"(({self.bgCamIndex}) & 0xFF)", - ] - ) - + ")" - ) - - def getWaterboxEntryC(self): - return ( - (indent + "{ ") - + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " - + self.getWaterboxProperties() - + " }," - ) - - -@dataclass -class Vertex: - pos: tuple[int, int, int] - - def getVertexEntryC(self): - return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," +from .collision_common import ( + SurfaceType, + CollisionPoly, + Vertex, + WaterBox, + BgCamInfo, + CrawlspaceData, +) @dataclass @@ -259,7 +16,7 @@ class CollisionHeaderVertices: name: str vertexList: list[Vertex] - def getVertexListC(self): + def getC(self): vertData = CData() listName = f"Vec3s {self.name}[{len(self.vertexList)}]" @@ -268,7 +25,7 @@ def getVertexListC(self): # .c vertData.source = ( - (listName + " = {\n") + "\n".join(vertex.getVertexEntryC() for vertex in self.vertexList) + "\n};\n\n" + (listName + " = {\n") + "\n".join(vertex.getEntryC() for vertex in self.vertexList) + "\n};\n\n" ) return vertData @@ -279,7 +36,7 @@ class CollisionHeaderCollisionPoly: name: str polyList: list[CollisionPoly] - def getCollisionPolyDataC(self): + def getC(self): colPolyData = CData() listName = f"CollisionPoly {self.name}[{len(self.polyList)}]" @@ -287,9 +44,7 @@ def getCollisionPolyDataC(self): colPolyData.header = f"extern {listName};\n" # .c - colPolyData.source = ( - (listName + " = {\n") + "\n".join(poly.getCollisionPolyEntryC() for poly in self.polyList) + "\n};\n\n" - ) + colPolyData.source = (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.polyList) + "\n};\n\n" return colPolyData @@ -299,7 +54,7 @@ class CollisionHeaderSurfaceType: name: str surfaceTypeList: list[SurfaceType] - def getSurfaceTypeDataC(self): + def getC(self): surfaceData = CData() listName = f"SurfaceType {self.name}[{len(self.surfaceTypeList)}]" @@ -308,7 +63,7 @@ def getSurfaceTypeDataC(self): # .c surfaceData.source = ( - (listName + " = {\n") + "\n".join(poly.getSurfaceTypeEntryC() for poly in self.surfaceTypeList) + "\n};\n\n" + (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.surfaceTypeList) + "\n};\n\n" ) return surfaceData @@ -321,14 +76,14 @@ class CollisionHeaderBgCamInfo: bgCamInfoList: list[BgCamInfo] crawlspacePosList: list[CrawlspaceData] - crawlspaceCount: int = 6 arrayIdx: int = 0 + crawlspaceCount: int = 6 def __post_init__(self): if len(self.bgCamInfoList) > 0: self.arrayIdx = self.bgCamInfoList[-1].arrayIndex + self.crawlspaceCount - def getCamPosListC(self): + def getDataArrayC(self): posData = CData() listName = f"Vec3s {self.posDataName}[]" @@ -338,23 +93,14 @@ def getCamPosListC(self): # .c posData.source = ( (listName + " = {\n") - + "".join(cam.getCamPosEntriesC() for cam in self.bgCamInfoList) - + "".join(crawlspace.getCrawlspacePointEntriesC() for crawlspace in self.crawlspacePosList) + + "".join(cam.getDataEntriesC() for cam in self.bgCamInfoList) + + "".join(crawlspace.getDataEntriesC() for crawlspace in self.crawlspacePosList) + "};\n\n" ) return posData - def getCrawlspaceInfoEntries(self): - source = "" - - for _ in self.crawlspacePosList: - source += indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{self.posDataName}[{self.arrayIdx}]" + " },\n" - self.arrayIdx += self.crawlspaceCount - - return source - - def getBgCamInfoListC(self): + def getInfoArrayC(self): bgCamInfoData = CData() listName = f"BgCamInfo {self.name}[]" @@ -364,8 +110,8 @@ def getBgCamInfoListC(self): # .c bgCamInfoData.source = ( (listName + " = {\n") - + "".join(cam.getBgCamInfoEntryC(self.posDataName) for cam in self.bgCamInfoList) - + self.getCrawlspaceInfoEntries() + + "".join(cam.getInfoEntryC(self.posDataName) for cam in self.bgCamInfoList) + + "".join(crawlspace.getInfoEntryC(self.posDataName) for crawlspace in self.crawlspacePosList) + "};\n\n" ) @@ -377,7 +123,7 @@ class CollisionHeaderWaterBox: name: str waterboxList: list[WaterBox] - def getWaterboxListC(self): + def getC(self): wboxData = CData() listName = f"WaterBox {self.name}[{len(self.waterboxList)}]" @@ -385,9 +131,7 @@ def getWaterboxListC(self): wboxData.header = f"extern {listName};\n" # .c - wboxData.source = ( - (listName + " = {\n") + "\n".join(wBox.getWaterboxEntryC() for wBox in self.waterboxList) + "\n};\n\n" - ) + wboxData.source = (listName + " = {\n") + "\n".join(wBox.getEntryC() for wBox in self.waterboxList) + "\n};\n\n" return wboxData @@ -403,7 +147,7 @@ class OOTSceneCollisionHeader: bgCamInfo: CollisionHeaderBgCamInfo = None waterbox: CollisionHeaderWaterBox = None - def getCollisionDataC(self): + def getSceneCollisionC(self): headerData = CData() colData = CData() varName = f"CollisionHeader {self.name}" @@ -415,24 +159,24 @@ def getCollisionDataC(self): wBoxPtrLine = "0, NULL" if len(self.waterbox.waterboxList) > 0: - colData.append(self.waterbox.getWaterboxListC()) + colData.append(self.waterbox.getC()) wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" - + if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: - colData.append(self.bgCamInfo.getCamPosListC()) - colData.append(self.bgCamInfo.getBgCamInfoListC()) + colData.append(self.bgCamInfo.getDataArrayC()) + colData.append(self.bgCamInfo.getInfoArrayC()) camPtrLine = f"{self.bgCamInfo.name}" - + if len(self.surfaceType.surfaceTypeList) > 0: - colData.append(self.surfaceType.getSurfaceTypeDataC()) + colData.append(self.surfaceType.getC()) surfacePtrLine = f"{self.surfaceType.name}" if len(self.vertices.vertexList) > 0: - colData.append(self.vertices.getVertexListC()) + colData.append(self.vertices.getC()) vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" if len(self.collisionPoly.polyList) > 0: - colData.append(self.collisionPoly.getCollisionPolyDataC()) + colData.append(self.collisionPoly.getC()) colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" # .h diff --git a/fast64_internal/oot/new_exporter/collision_common.py b/fast64_internal/oot/new_exporter/collision_common.py new file mode 100644 index 000000000..d322c05aa --- /dev/null +++ b/fast64_internal/oot/new_exporter/collision_common.py @@ -0,0 +1,260 @@ +from dataclasses import dataclass, field +from ...utility import PluginError, indent + + +@dataclass +class CollisionPoly: + indices: list[int] + ignoreCamera: bool + ignoreActor: bool + ignoreProjectile: bool + enableConveyor: bool + normal: tuple[int, int, int] + dist: int + type: int = None + + def getFlags_vIA(self): + vertPart = self.indices[0] & 0x1FFF + colPart = (1 if self.ignoreCamera else 0) + (2 if self.ignoreActor else 0) + (4 if self.ignoreProjectile else 0) + return vertPart | (colPart << 13) + + def getFlags_vIB(self): + vertPart = self.indices[1] & 0x1FFF + conveyorPart = 1 if self.enableConveyor else 0 + return vertPart | (conveyorPart << 13) + + def getVIC(self): + return self.indices[2] & 0x1FFF + + def getEntryC(self): + if self.type is None: + raise PluginError("ERROR: Type unset!") + return ( + (indent + "{ ") + + ", ".join( + ( + f"0x{self.type:04X}", + f"0x{self.getFlags_vIA():04X}", + f"0x{self.getFlags_vIB():04X}", + f"0x{self.getVIC():04X}", + ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), + f"0x{self.dist:04X}", + ) + ) + + " }," + ) + + +@dataclass +class SurfaceType: + bgCamIndex: int + exitIndex: int + floorType: int + unk18: int # unused? + wallType: int + floorProperty: int + isSoft: bool + isHorseBlocked: bool + + material: int + floorEffect: int + lightSetting: int + echo: int + canHookshot: bool + conveyorSpeed: int + conveyorDirection: int + isWallDamage: bool # unk27 + + conveyorKeepMomentum: bool + useMacros: bool = True + isSoftC: str = None + isHorseBlockedC: str = None + canHookshotC: str = None + isWallDamageC: str = None + + def __post_init__(self): + if self.conveyorKeepMomentum: + self.conveyorSpeed += 4 + + self.isSoftC = "1" if self.isSoft else "0" + self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" + self.canHookshotC = "1" if self.canHookshot else "0" + self.isWallDamageC = "1" if self.isWallDamage else "0" + + def getSurfaceType0(self): + if self.useMacros: + return ( + ("SURFACETYPE0(") + + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " + + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isHorseBlockedC} & 1) << 31)", + f"(({self.isSoftC} & 1) << 30)", + f"(({self.floorProperty} & 0x0F) << 26)", + f"(({self.wallType} & 0x1F) << 21)", + f"(({self.unk18} & 0x07) << 18)", + f"(({self.floorType} & 0x1F) << 13)", + f"(({self.exitIndex} & 0x1F) << 8)", + f"({self.bgCamIndex} & 0xFF)", + ] + ) + + ")" + ) + + def getSurfaceType1(self): + if self.useMacros: + return ( + ("SURFACETYPE1(") + + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " + + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isWallDamageC} & 1) << 27)", + f"(({self.conveyorDirection} & 0x3F) << 21)", + f"(({self.conveyorSpeed} & 0x07) << 18)", + f"(({self.canHookshotC} & 1) << 17)", + f"(({self.echo} & 0x3F) << 11)", + f"(({self.lightSetting} & 0x1F) << 6)", + f"(({self.floorEffect} & 0x03) << 4)", + f"({self.material} & 0x0F)", + ] + ) + + ")" + ) + + def getEntryC(self): + if self.useMacros: + return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," + else: + return (indent + "{\n") + self.getSurfaceType0() + ",\n" + self.getSurfaceType1() + ("\n" + indent + "},") + + +@dataclass +class BgCamFuncData: # CameraPosData + pos: tuple[int, int, int] + rot: tuple[int, int, int] + fov: int + roomImageOverrideBgCamIndex: int + + +@dataclass +class CrawlspaceData: + points: list[tuple[int, int, int]] = field(default_factory=list) + arrayIndex: int = None + + def getDataEntriesC(self): + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) + + def getInfoEntryC(self, posDataName: str): + return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" + + +@dataclass +class BgCamInfo: + setting: str + count: int + arrayIndex: int + hasPosData: bool + bgCamFuncDataList: list[BgCamFuncData] + + def getDataEntriesC(self): + source = "" + + if self.hasPosData: + for camData in self.bgCamFuncDataList: + source += ( + (indent + "{ " + ", ".join(f"{p:6}" for p in camData.pos) + " },\n") + + (indent + "{ " + ", ".join(f"{r:6}" for r in camData.rot) + " },\n") + + (indent + "{ " + f"{camData.fov:6}, {camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + ) + + return source + + def getInfoEntryC(self, posDataName: str): + ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" + return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" + + +@dataclass +class WaterBox: + position: tuple[int, int, int] + scale: float + emptyDisplaySize: float + + # Properties + bgCamIndex: int + lightIndex: int + roomIndex: int + setFlag19: bool + + xMin: int = None + ySurface: int = None + zMin: int = None + xLength: int = None + zLength: int = None + + useMacros: bool = True + setFlag19C: str = None + roomIndexC: str = None + + def __post_init__(self): + self.setFlag19C = "1" if self.setFlag19 else "0" + self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" + + # The scale ordering is due to the fact that scaling happens AFTER rotation. + # Thus the translation uses Y-up, while the scale uses Z-up. + xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) + zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) + + self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) + self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) + self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) + self.xLength = xMax - self.xMin + self.zLength = zMax - self.zMin + + def getProperties(self): + if self.useMacros: + return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" + else: + return ( + "(" + + " | ".join( + prop + for prop in [ + f"(({self.setFlag19C} & 1) << 19)", + f"(({self.roomIndexC} & 0x3F) << 13)", + f"(({self.lightIndex} & 0x1F) << 8)", + f"(({self.bgCamIndex}) & 0xFF)", + ] + ) + + ")" + ) + + def getEntryC(self): + return ( + (indent + "{ ") + + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " + + self.getProperties() + + " }," + ) + + +@dataclass +class Vertex: + pos: tuple[int, int, int] + + def getEntryC(self): + return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 80140c364..77779df72 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -189,7 +189,7 @@ def setRoomListData(self): def setSceneData(self): sceneData = OOTSceneData() sceneMainData = self.scene.getSceneMainC() - sceneCollisionData = self.scene.colHeader.getCollisionDataC() + sceneCollisionData = self.scene.colHeader.getSceneCollisionC() sceneCutsceneData = self.scene.getSceneCutscenesC() sceneData.sceneMain = sceneMainData.source @@ -201,50 +201,60 @@ def setSceneData(self): def setIncludeData(self): suffix = "\n\n" sceneInclude = f'\n#include "{self.scene.name}.h"\n' - common = "\n".join( - elem - for elem in [ - '#include "ultra64/ultratypes.h"', - '#include "libc/stdint.h"', - '#include "z64math.h"', - ] - ) + "\n" - - room = "\n".join( - elem - for elem in [ - '#include "z64object.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - ] - ) + "\n" - - scene = "\n".join( - elem - for elem in [ - '#include "z64dma.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - '#include "z64environment.h"', - ] - ) + "\n" - - collision = "\n".join( - elem - for elem in [ - '#include "macros.h"', - '#include "z64camera.h"', - '#include "z64bgcheck.h"', - ] - ) + "\n" - - cutscene = "\n".join( - elem - for elem in [ - '#include "z64cutscene.h"', - '#include "z64cutscene_commands.h"', - ] - ) + "\n" + common = ( + "\n".join( + [ + '#include "ultra64/ultratypes.h"', + '#include "libc/stdint.h"', + '#include "z64math.h"', + ] + ) + + "\n" + ) + + room = ( + "\n".join( + [ + '#include "z64object.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + ] + ) + + "\n" + ) + + scene = ( + "\n".join( + [ + '#include "z64dma.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + '#include "z64environment.h"', + ] + ) + + "\n" + ) + + collision = ( + "\n".join( + [ + '#include "macros.h"', + '#include "z64camera.h"', + '#include "z64bgcheck.h"', + ] + ) + + "\n" + ) + + cutscene = ( + "\n".join( + [ + '#include "z64cutscene.h"', + '#include "z64cutscene_commands.h"', + ] + ) + + "\n" + ) for roomData in self.roomList.values(): roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 4f26d7c6e..ae7085481 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -20,6 +20,9 @@ CollisionHeaderSurfaceType, CollisionHeaderBgCamInfo, CollisionHeaderWaterBox, +) + +from .collision_common import ( SurfaceType, CollisionPoly, Vertex, @@ -490,7 +493,7 @@ def getBgCamFuncDataFromObjects(self, camObj: Object): camProp.bgImageOverrideIndex, ) - def getCrawlspaceDataFromObjects(self): + def getCrawlspaceDataFromObjects(self, startIndex: int): crawlspaceList: list[CrawlspaceData] = [] crawlspaceObjList: list[Object] = [ obj @@ -498,6 +501,7 @@ def getCrawlspaceDataFromObjects(self): if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" ] + index = startIndex for obj in crawlspaceObjList: if self.validateCurveData(obj): crawlspaceList.append( @@ -505,16 +509,17 @@ def getCrawlspaceDataFromObjects(self): [ [round(value) for value in self.transform @ obj.matrix_world @ point.co] for point in obj.data.splines[0].points - ] + ], + index, ) ) - + index += 6 return crawlspaceList def getBgCamInfoDataFromObjects(self): camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] camPosData: dict[int, BgCamFuncData] = {} - bgCamList: list[BgCamInfo] = [] + camInfoData: dict[int, BgCamInfo] = {} index = 0 for camObj in camObjList: @@ -527,16 +532,26 @@ def getBgCamInfoDataFromObjects(self): if camProp.hasPositionData: count = 3 - index = camProp.index - if index in camPosData: - raise PluginError(f"Error: Repeated camera position index: {index} for {camObj.name}") - camPosData[index] = self.getBgCamFuncDataFromObjects(camObj) + if camProp.index in camPosData: + raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") + camPosData[camProp.index] = self.getBgCamFuncDataFromObjects(camObj) else: count = 0 - bgCamList.append(BgCamInfo(setting, count, index, [camPosData[i] for i in range(len(camPosData))])) + if camProp.index in camInfoData: + raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") + camInfoData[camProp.index] = BgCamInfo( + setting, + count, + index, + camProp.hasPositionData, + [camPosData[i] for i in range(min(camPosData.keys()), len(camPosData))] if len(camPosData) > 0 else [], + ) + index += count - return bgCamList + return ( + [camInfoData[i] for i in range(min(camInfoData.keys()), len(camInfoData))] if len(camInfoData) > 0 else [] + ) def getWaterBoxDataFromObjects(self): waterboxObjList = [ @@ -565,8 +580,17 @@ def getWaterBoxDataFromObjects(self): return waterboxList + def getCount(self, bgCamInfoList: list[BgCamInfo]): + count = 0 + for elem in bgCamInfoList: + if elem.count != 0: # 0 means no pos data + count += elem.count + return count + def getNewCollisionHeader(self): colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() + bgCamInfoList = self.getBgCamInfoDataFromObjects() + return OOTSceneCollisionHeader( f"{self.name}_collisionHeader", colBounds[0], @@ -577,8 +601,8 @@ def getNewCollisionHeader(self): CollisionHeaderBgCamInfo( f"{self.name}_bgCamInfo", f"{self.name}_camPosData", - self.getBgCamInfoDataFromObjects(), - self.getCrawlspaceDataFromObjects(), + bgCamInfoList, + self.getCrawlspaceDataFromObjects(self.getCount(bgCamInfoList)), ), CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), ) From f34b03f06a7cc186cb506feadccd1fed6df5aeb2 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:34:25 +0200 Subject: [PATCH 15/98] more cleanup --- fast64_internal/oot/new_exporter/collision.py | 2 +- ...llision_common.py => collision_classes.py} | 0 fast64_internal/oot/new_exporter/commands.py | 12 +- .../oot/new_exporter/common/__init__.py | 3 + .../{common.py => common/classes.py} | 27 +- .../oot/new_exporter/common/collision.py | 262 +++++++++ .../oot/new_exporter/common/scene.py | 197 +++++++ fast64_internal/oot/new_exporter/exporter.py | 19 +- fast64_internal/oot/new_exporter/room.py | 4 +- fast64_internal/oot/new_exporter/scene.py | 515 +----------------- .../oot/new_exporter/scene_header.py | 6 - 11 files changed, 528 insertions(+), 519 deletions(-) rename fast64_internal/oot/new_exporter/{collision_common.py => collision_classes.py} (100%) create mode 100644 fast64_internal/oot/new_exporter/common/__init__.py rename fast64_internal/oot/new_exporter/{common.py => common/classes.py} (80%) create mode 100644 fast64_internal/oot/new_exporter/common/collision.py create mode 100644 fast64_internal/oot/new_exporter/common/scene.py diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 699979639..d21f4a7bc 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from ...utility import CData, indent -from .collision_common import ( +from .collision_classes import ( SurfaceType, CollisionPoly, Vertex, diff --git a/fast64_internal/oot/new_exporter/collision_common.py b/fast64_internal/oot/new_exporter/collision_classes.py similarity index 100% rename from fast64_internal/oot/new_exporter/collision_common.py rename to fast64_internal/oot/new_exporter/collision_classes.py diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index ffd3ceb1a..75d248b64 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -1,12 +1,12 @@ from ...utility import CData, indent - from typing import TYPE_CHECKING if TYPE_CHECKING: from .collision import OOTSceneCollisionHeader - from .room import OOTRoom, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors - from .scene import ( - OOTScene, + from .room import OOTRoom + from .room_header import OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors + from .scene import OOTScene + from .scene_header import ( OOTSceneHeaderInfos, OOTSceneHeader, OOTSceneHeaderLighting, @@ -16,7 +16,7 @@ ) -class OOTRoomCommands: +class RoomCommands: def getEchoSettingsCmd(self, infos: "OOTRoomHeaderInfos"): return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" @@ -87,7 +87,7 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): return cmdListData -class OOTSceneCommands: +class SceneCommands: def getSoundSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" diff --git a/fast64_internal/oot/new_exporter/common/__init__.py b/fast64_internal/oot/new_exporter/common/__init__.py new file mode 100644 index 000000000..4fe1fe63b --- /dev/null +++ b/fast64_internal/oot/new_exporter/common/__init__.py @@ -0,0 +1,3 @@ +from .classes import Common, Actor, TransitionActor, EntranceActor, altHeaderList +from .collision import CollisionCommon +from .scene import SceneCommon diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/common/classes.py similarity index 80% rename from fast64_internal/oot/new_exporter/common.py rename to fast64_internal/oot/new_exporter/common/classes.py index 3e1919b00..a3e7c1c2c 100644 --- a/fast64_internal/oot/new_exporter/common.py +++ b/fast64_internal/oot/new_exporter/common/classes.py @@ -2,9 +2,9 @@ from math import radians from mathutils import Quaternion, Matrix from bpy.types import Object -from ...utility import indent -from ..oot_utility import ootConvertTranslation, ootConvertRotation -from ..actor.properties import OOTActorHeaderProperty +from ....utility import PluginError, indent +from ...oot_utility import ootConvertTranslation, ootConvertRotation +from ...actor.properties import OOTActorHeaderProperty altHeaderList = ["childNight", "adultDay", "adultNight"] @@ -16,6 +16,27 @@ class Common: transform: Matrix roomIndex: int = None + def getRoomObjectFromChild(self, childObj: Object) -> Object | None: + # Note: temporary solution until PRs #243 & #255 are merged + for obj in self.sceneObj.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": + for o in obj.children_recursive: + if o == childObj: + return obj + return None + + def validateCurveData(self, curveObj: Object): + curveData = curveObj.data + if curveObj.type != "CURVE" or curveData.splines[0].type != "NURBS": + # Curve was likely not intended to be exported + return False + + if len(curveData.splines) != 1: + # Curve was intended to be exported but has multiple disconnected segments + raise PluginError(f"Exported curves should have only one single segment, found {len(curveData.splines)}") + + return True + def roundPosition(self, position) -> tuple[int, int, int]: return (round(position[0]), round(position[1]), round(position[2])) diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py new file mode 100644 index 000000000..c071f90e1 --- /dev/null +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -0,0 +1,262 @@ +import math + +from dataclasses import dataclass +from mathutils import Vector, Quaternion +from bpy.types import Object +from bpy.ops import object +from ....utility import PluginError, checkIdentityRotation +from ...oot_utility import convertIntTo2sComplement +from ...oot_collision_classes import decomp_compat_map_CameraSType +from .classes import Common + +from ..collision_classes import ( + SurfaceType, + CollisionPoly, + Vertex, + WaterBox, + BgCamInfo, + BgCamFuncData, + CrawlspaceData, +) + + +@dataclass +class CollisionCommon(Common): + def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): + if len(bounds) == 0: + bounds.append([position[0], position[1], position[2]]) + bounds.append([position[0], position[1], position[2]]) + return + + minBounds = bounds[0] + maxBounds = bounds[1] + for i in range(3): + if position[i] < minBounds[i]: + minBounds[i] = position[i] + if position[i] > maxBounds[i]: + maxBounds[i] = position[i] + + def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): + for i in range(len(vertArray)): + colVert = vertArray[i].pos + if colVert == vert: + return i + return None + + def getColSurfaceVtxDataFromMeshObj(self): + meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] + object.select_all(action="DESELECT") + self.sceneObj.select_set(True) + + surfaceTypeData: dict[int, SurfaceType] = {} + polyList: list[CollisionPoly] = [] + vertexList: list[Vertex] = [] + bounds = [] + + i = 0 + for meshObj in meshObjList: + if not meshObj.ignore_collision: + if len(meshObj.data.materials) == 0: + raise PluginError(f"'{meshObj.name}' must have a material associated with it.") + + meshObj.data.calc_loop_triangles() + for face in meshObj.data.loop_triangles: + colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty + + planePoint = self.transform @ meshObj.data.vertices[face.vertices[0]].co + (x1, y1, z1) = self.roundPosition(planePoint) + (x2, y2, z2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, y3, z3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) + + self.updateBounds((x1, y1, z1), bounds) + self.updateBounds((x2, y2, z2), bounds) + self.updateBounds((x3, y3, z3), bounds) + + normal = (self.transform.inverted().transposed() @ face.normal).normalized() + distance = int( + round(-1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2])) + ) + distance = convertIntTo2sComplement(distance, 2, True) + + indices: list[int] = [] + for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: + index = self.getVertIndex(vertex, vertexList) + if index is None: + vertexList.append(Vertex(vertex)) + indices.append(len(vertexList) - 1) + else: + indices.append(index) + assert len(indices) == 3 + + # We need to ensure two things about the order in which the vertex indices are: + # + # 1) The vertex with the minimum y coordinate should be first. + # This prevents a bug due to an optimization in OoT's CollisionPoly_GetMinY. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L202 + # + # 2) The vertices should wrap around the polygon normal **counter-clockwise**. + # This is needed for OoT's dynapoly, which is collision that can move. + # When it moves, the vertex coordinates and normals are recomputed. + # The normal is computed based on the vertex coordinates, which makes the order of vertices matter. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L2976 + + # Address 1): sort by ascending y coordinate + indices.sort(key=lambda index: vertexList[index].pos[1]) + + # Address 2): + # swap indices[1] and indices[2], + # if the normal computed from the vertices in the current order is the wrong way. + v0 = Vector(vertexList[indices[0]].pos) + v1 = Vector(vertexList[indices[1]].pos) + v2 = Vector(vertexList[indices[2]].pos) + if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: + indices[1], indices[2] = indices[2], indices[1] + + useConveyor = colProp.conveyorOption != "None" + surfaceTypeData[i] = SurfaceType( + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorSetting"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorProperty"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + ) + + polyList.append( + CollisionPoly( + indices, + colProp.ignoreCameraCollision, + colProp.ignoreActorCollision, + colProp.ignoreProjectileCollision, + useConveyor, + tuple(normal), + distance, + i, + ) + ) + i += 1 + return bounds, vertexList, polyList, [surfaceTypeData[i] for i in range(len(surfaceTypeData))] + + def getBgCamFuncDataFromObjects(self, camObj: Object): + camProp = camObj.ootCameraPositionProperty + + # Camera faces opposite direction + pos, rot, _, _ = self.getConvertedTransformWithOrientation( + self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + ) + + fov = math.degrees(camObj.data.angle) + if fov > 3.6: + fov *= 100 # see CAM_DATA_SCALED() macro + + return BgCamFuncData( + pos, + rot, + round(fov), + camProp.bgImageOverrideIndex, + ) + + def getCrawlspaceDataFromObjects(self, startIndex: int): + crawlspaceList: list[CrawlspaceData] = [] + crawlspaceObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" + ] + + index = startIndex + for obj in crawlspaceObjList: + if self.validateCurveData(obj): + crawlspaceList.append( + CrawlspaceData( + [ + [round(value) for value in self.transform @ obj.matrix_world @ point.co] + for point in obj.data.splines[0].points + ], + index, + ) + ) + index += 6 + return crawlspaceList + + def getBgCamInfoDataFromObjects(self): + camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] + camPosData: dict[int, BgCamFuncData] = {} + camInfoData: dict[int, BgCamInfo] = {} + + index = 0 + for camObj in camObjList: + camProp = camObj.ootCameraPositionProperty + + if camProp.camSType == "Custom": + setting = camProp.camSTypeCustom + else: + setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) + + if camProp.hasPositionData: + count = 3 + if camProp.index in camPosData: + raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") + camPosData[camProp.index] = self.getBgCamFuncDataFromObjects(camObj) + else: + count = 0 + + if camProp.index in camInfoData: + raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") + camInfoData[camProp.index] = BgCamInfo( + setting, + count, + index, + camProp.hasPositionData, + [camPosData[i] for i in range(min(camPosData.keys()), len(camPosData))] if len(camPosData) > 0 else [], + ) + + index += count + return ( + [camInfoData[i] for i in range(min(camInfoData.keys()), len(camInfoData))] if len(camInfoData) > 0 else [] + ) + + def getWaterBoxDataFromObjects(self): + waterboxObjList = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" + ] + waterboxList: list[WaterBox] = [] + + for waterboxObj in waterboxObjList: + emptyScale = waterboxObj.empty_display_size + pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) + checkIdentityRotation(waterboxObj, orientedRot, False) + + wboxProp = waterboxObj.ootWaterBoxProperty + roomObj = self.getRoomObjectFromChild(waterboxObj) + waterboxList.append( + WaterBox( + pos, + scale, + emptyScale, + wboxProp.camera, + wboxProp.lighting, + roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, + wboxProp.flag19, + ) + ) + + return waterboxList + + def getCount(self, bgCamInfoList: list[BgCamInfo]): + count = 0 + for elem in bgCamInfoList: + if elem.count != 0: # 0 means no pos data + count += elem.count + return count diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py new file mode 100644 index 000000000..8340693b4 --- /dev/null +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -0,0 +1,197 @@ +from dataclasses import dataclass, field +from bpy.types import Object +from ....utility import PluginError, exportColor, ootGetBaseOrCustomLight +from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty +from ...oot_constants import ootData +from ..commands import SceneCommands +from ..scene_header import EnvLightSettings, Path, OOTSceneHeader, OOTSceneAlternateHeader +from ..room import OOTRoom +from .classes import TransitionActor, EntranceActor, altHeaderList +from .collision import CollisionCommon + + +@dataclass +class SceneCommon(CollisionCommon, SceneCommands): + name: str = None + headerIndex: int = None + mainHeader: OOTSceneHeader = None + altHeader: OOTSceneAlternateHeader = None + roomList: list[OOTRoom] = field(default_factory=list) + + def validateRoomIndices(self): + for i, room in enumerate(self.roomList): + if i != room.roomIndex: + return False + + return True + + def validateScene(self): + if not len(self.roomList) > 0: + raise PluginError("ERROR: This scene does not have any rooms!") + + if not self.validateRoomIndices(): + raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") + + def hasAlternateHeaders(self): + return self.altHeader is not None + + def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: + if headerIndex == 0: + return self.mainHeader + + for i, header in enumerate(altHeaderList, 1): + if headerIndex == i: + return getattr(self.altHeader, header) + + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): + if headerIndex == i: + return csHeader + + return None + + def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): + """Returns the exit list and performs safety checks""" + + exitList: list[tuple[int, str]] = [] + + for i, exitProp in enumerate(headerProp.exitList): + if exitProp.exitIndex != "Custom": + raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") + + exitList.append((i, exitProp.exitIndexCustom)) + + return exitList + + def getTransActorListFromProps(self): + actorList: list[TransitionActor] = [] + actorObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + self.roomIndex = roomObj.ootRoomHeader.roomIndex + + transActorProp = obj.ootTransitionActorProperty + + if not self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex): + continue + + if transActorProp.actor.actorID != "None": + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + transActor = TransitionActor() + + if transActorProp.dontTransition: + front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) + back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + else: + front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + + if transActorProp.actor.actorID == "Custom": + transActor.id = transActorProp.actor.actorIDCustom + else: + transActor.id = transActorProp.actor.actorID + + transActor.name = ( + ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( + f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if transActorProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + transActor.pos = pos + transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? + transActor.params = transActorProp.actor.actorParam + transActor.roomFrom, transActor.cameraFront = front + transActor.roomTo, transActor.cameraBack = back + actorList.append(transActor) + return actorList + + def getEntranceActorListFromProps(self): + actorList: list[EntranceActor] = [] + actorObjList: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + + entranceProp = obj.ootEntranceProperty + if not self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex): + continue + + if entranceProp.actor.actorID != "None": + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + entranceActor = EntranceActor() + + entranceActor.name = ( + ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( + f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if entranceProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom + entranceActor.pos = pos + entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + entranceActor.params = entranceProp.actor.actorParam + entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex + entranceActor.spawnIndex = entranceProp.spawnIndex + actorList.append(entranceActor) + return actorList + + def getPathListFromProps(self, listNameBase: str): + pathList: list[Path] = [] + pathObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" + ] + + for i, obj in enumerate(pathObjList): + isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) + if isHeaderValid and self.validateCurveData(obj): + pathList.append( + Path( + f"{listNameBase}{i:02}", [self.transform @ point.co.xyz for point in obj.data.splines[0].points] + ) + ) + + return pathList + + def getEnvLightSettingsListFromProps(self, headerProp: OOTSceneHeaderProperty, lightMode: str): + lightList: list[OOTLightProperty] = [] + lightSettings: list[EnvLightSettings] = [] + + if lightMode == "LIGHT_MODE_TIME": + todLights = headerProp.timeOfDayLights + lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] + else: + lightList = headerProp.lightList + + for lightProp in lightList: + light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) + light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) + lightSettings.append( + EnvLightSettings( + lightMode, + exportColor(lightProp.ambient), + light1[0], + light1[1], + light2[0], + light2[1], + exportColor(lightProp.fogColor), + lightProp.fogNear, + lightProp.fogFar, + lightProp.transitionSpeed, + ) + ) + + return lightSettings diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 77779df72..5d73de6b8 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -4,6 +4,16 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object +from ...f3d.f3d_gbi import DLFormat +from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty +from ..room.properties import OOTRoomHeaderProperty +from ..oot_constants import ootData +from ..oot_object import addMissingObjectsToAllRoomHeadersNew +from .common import altHeaderList +from .scene import OOTScene +from .scene_header import OOTSceneAlternateHeader +from .room import OOTRoom, OOTRoomAlternateHeader + from ...utility import ( PluginError, checkObjectReference, @@ -12,15 +22,6 @@ toAlnum, writeFile, ) -from ...f3d.f3d_gbi import DLFormat -from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty -from ..room.properties import OOTRoomHeaderProperty -from ..oot_constants import ootData -from ..oot_object import addMissingObjectsToAllRoomHeadersNew -from .common import altHeaderList -from .scene import OOTScene, OOTSceneAlternateHeader -from .room import OOTRoom, OOTRoomAlternateHeader -from .collision import OOTSceneCollisionHeader from ..oot_utility import ( ExportInfo, diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 9fc42eb83..6c87458b3 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -3,7 +3,7 @@ from ...utility import CData, indent from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData -from .commands import OOTRoomCommands +from .commands import RoomCommands from .common import Common, altHeaderList from .room_header import ( @@ -16,7 +16,7 @@ @dataclass -class OOTRoom(Common, OOTRoomCommands): +class OOTRoom(Common, RoomCommands): name: str = None roomObj: Object = None headerIndex: int = None diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index ae7085481..fb638f4dc 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -1,17 +1,7 @@ -import math - -from dataclasses import dataclass, field -from mathutils import Vector, Quaternion -from bpy.types import Object, Mesh -from bpy.ops import object -from ...utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, checkIdentityRotation, indent -from ..oot_utility import convertIntTo2sComplement -from ..scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ..oot_constants import ootData -from ..oot_collision_classes import decomp_compat_map_CameraSType -from .commands import OOTSceneCommands -from .common import Common, TransitionActor, EntranceActor, altHeaderList -from .room import OOTRoom +from dataclasses import dataclass +from ...utility import PluginError, CData, indent +from ..scene.properties import OOTSceneHeaderProperty +from .common import SceneCommon from .collision import ( OOTSceneCollisionHeader, @@ -22,243 +12,44 @@ CollisionHeaderWaterBox, ) -from .collision_common import ( - SurfaceType, - CollisionPoly, - Vertex, - WaterBox, - BgCamInfo, - BgCamFuncData, - CrawlspaceData, -) - from .scene_header import ( - EnvLightSettings, - Path, OOTSceneHeader, - OOTSceneAlternateHeader, OOTSceneHeaderInfos, OOTSceneHeaderLighting, OOTSceneHeaderCutscene, OOTSceneHeaderExits, OOTSceneHeaderActors, OOTSceneHeaderPath, - OOTSceneHeaderCrawlspace, ) @dataclass -class OOTScene(Common, OOTSceneCommands): - name: str = None - headerIndex: int = None - mainHeader: OOTSceneHeader = None - altHeader: OOTSceneAlternateHeader = None - roomList: list[OOTRoom] = field(default_factory=list) +class OOTScene(SceneCommon): roomListName: str = None - colHeader: OOTSceneCollisionHeader = None def __post_init__(self): self.roomListName = f"{self.name}_roomList" - def validateCurveData(self, curveObj: Object): - curveData = curveObj.data - if curveObj.type != "CURVE" or curveData.splines[0].type != "NURBS": - # Curve was likely not intended to be exported - return False - - if len(curveData.splines) != 1: - # Curve was intended to be exported but has multiple disconnected segments - raise PluginError(f"Exported curves should have only one single segment, found {len(curveData.splines)}") - - return True - - def validateRoomIndices(self): - for i, room in enumerate(self.roomList): - if i != room.roomIndex: - return False - - return True - - def validateScene(self): - if not len(self.roomList) > 0: - raise PluginError("ERROR: This scene does not have any rooms!") - - if not self.validateRoomIndices(): - raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") - - def hasAlternateHeaders(self): - return self.altHeader is not None - - def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: - if headerIndex == 0: - return self.mainHeader - - for i, header in enumerate(altHeaderList, 1): - if headerIndex == i: - return getattr(self.altHeader, header) - - for i, csHeader in enumerate(self.altHeader.cutscenes, 4): - if headerIndex == i: - return csHeader - - return None - - def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): - """Returns the exit list and performs safety checks""" - - exitList: list[tuple[int, str]] = [] - - for i, exitProp in enumerate(headerProp.exitList): - if exitProp.exitIndex != "Custom": - raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") - - exitList.append((i, exitProp.exitIndexCustom)) - - return exitList - - def getRoomObjectFromChild(self, childObj: Object) -> Object | None: - # Note: temporary solution until PRs #243 & #255 are merged - for obj in self.sceneObj.children_recursive: - if obj.type == "EMPTY" and obj.ootEmptyType == "Room": - for o in obj.children_recursive: - if o == childObj: - return obj - return None - - def getTransActorListFromProps(self): - actorList: list[TransitionActor] = [] - actorObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - self.roomIndex = roomObj.ootRoomHeader.roomIndex - - transActorProp = obj.ootTransitionActorProperty - - if not self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex): - continue - - if transActorProp.actor.actorID != "None": - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - transActor = TransitionActor() - - if transActorProp.dontTransition: - front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) - back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - else: - front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) - - if transActorProp.actor.actorID == "Custom": - transActor.id = transActorProp.actor.actorIDCustom - else: - transActor.id = transActorProp.actor.actorID - - transActor.name = ( - ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( - f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if transActorProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - transActor.pos = pos - transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? - transActor.params = transActorProp.actor.actorParam - transActor.roomFrom, transActor.cameraFront = front - transActor.roomTo, transActor.cameraBack = back - actorList.append(transActor) - return actorList - - def getEntranceActorListFromProps(self): - actorList: list[EntranceActor] = [] - actorObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - - entranceProp = obj.ootEntranceProperty - if not self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex): - continue - - if entranceProp.actor.actorID != "None": - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - entranceActor = EntranceActor() - - entranceActor.name = ( - ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( - f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if entranceProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom - entranceActor.pos = pos - entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - entranceActor.params = entranceProp.actor.actorParam - entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex - entranceActor.spawnIndex = entranceProp.spawnIndex - actorList.append(entranceActor) - return actorList - - def getPathListFromProps(self, listNameBase: str): - pathList: list[Path] = [] - pathObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" - ] - - for i, obj in enumerate(pathObjList): - isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) - if isHeaderValid and self.validateCurveData(obj): - pathList.append( - Path( - f"{listNameBase}{i:02}", [self.transform @ point.co.xyz for point in obj.data.splines[0].points] - ) - ) - - return pathList - - def getEnvLightSettingsListFromProps(self, headerProp: OOTSceneHeaderProperty, lightMode: str): - lightList: list[OOTLightProperty] = [] - lightSettings: list[EnvLightSettings] = [] - - if lightMode == "LIGHT_MODE_TIME": - todLights = headerProp.timeOfDayLights - lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] - else: - lightList = headerProp.lightList - - for lightProp in lightList: - light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) - light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) - lightSettings.append( - EnvLightSettings( - lightMode, - exportColor(lightProp.ambient), - light1[0], - light1[1], - light2[0], - light2[1], - exportColor(lightProp.fogColor), - lightProp.fogNear, - lightProp.fogFar, - lightProp.transitionSpeed, - ) - ) + def getNewCollisionHeader(self): + colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() + bgCamInfoList = self.getBgCamInfoDataFromObjects() - return lightSettings + return OOTSceneCollisionHeader( + f"{self.name}_collisionHeader", + colBounds[0], + colBounds[1], + CollisionHeaderVertices(f"{self.name}_vertices", vertexList), + CollisionHeaderCollisionPoly(f"{self.name}_polygons", polyList), + CollisionHeaderSurfaceType(f"{self.name}_polygonTypes", surfaceTypeList), + CollisionHeaderBgCamInfo( + f"{self.name}_bgCamInfo", + f"{self.name}_camPosData", + bgCamInfoList, + self.getCrawlspaceDataFromObjects(self.getCount(bgCamInfoList)), + ), + CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), + ) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): """Returns a single scene header with the informations from the scene empty object""" @@ -308,7 +99,6 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getEntranceActorListFromProps(), ), OOTSceneHeaderPath(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), - OOTSceneHeaderCrawlspace(None), # not implemented yet ) def getRoomListC(self): @@ -348,265 +138,6 @@ def getRoomListC(self): roomList.source += "};\n\n" return roomList - def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): - if len(bounds) == 0: - bounds.append([position[0], position[1], position[2]]) - bounds.append([position[0], position[1], position[2]]) - return - - minBounds = bounds[0] - maxBounds = bounds[1] - for i in range(3): - if position[i] < minBounds[i]: - minBounds[i] = position[i] - if position[i] > maxBounds[i]: - maxBounds[i] = position[i] - - def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): - for i in range(len(vertArray)): - colVert = vertArray[i].pos - if colVert == vert: - return i - return None - - def getColSurfaceVtxDataFromMeshObj(self): - meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] - object.select_all(action="DESELECT") - self.sceneObj.select_set(True) - - surfaceTypeData: dict[int, SurfaceType] = {} - polyList: list[CollisionPoly] = [] - vertexList: list[Vertex] = [] - bounds = [] - - i = 0 - for meshObj in meshObjList: - if not meshObj.ignore_collision: - if len(meshObj.data.materials) == 0: - raise PluginError(f"'{meshObj.name}' must have a material associated with it.") - - meshObj.data.calc_loop_triangles() - for face in meshObj.data.loop_triangles: - colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty - - planePoint = self.transform @ meshObj.data.vertices[face.vertices[0]].co - (x1, y1, z1) = self.roundPosition(planePoint) - (x2, y2, z2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) - (x3, y3, z3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) - - self.updateBounds((x1, y1, z1), bounds) - self.updateBounds((x2, y2, z2), bounds) - self.updateBounds((x3, y3, z3), bounds) - - normal = (self.transform.inverted().transposed() @ face.normal).normalized() - distance = int( - round(-1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2])) - ) - distance = convertIntTo2sComplement(distance, 2, True) - - indices: list[int] = [] - for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: - index = self.getVertIndex(vertex, vertexList) - if index is None: - vertexList.append(Vertex(vertex)) - indices.append(len(vertexList) - 1) - else: - indices.append(index) - assert len(indices) == 3 - - # We need to ensure two things about the order in which the vertex indices are: - # - # 1) The vertex with the minimum y coordinate should be first. - # This prevents a bug due to an optimization in OoT's CollisionPoly_GetMinY. - # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L202 - # - # 2) The vertices should wrap around the polygon normal **counter-clockwise**. - # This is needed for OoT's dynapoly, which is collision that can move. - # When it moves, the vertex coordinates and normals are recomputed. - # The normal is computed based on the vertex coordinates, which makes the order of vertices matter. - # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L2976 - - # Address 1): sort by ascending y coordinate - indices.sort(key=lambda index: vertexList[index].pos[1]) - - # Address 2): - # swap indices[1] and indices[2], - # if the normal computed from the vertices in the current order is the wrong way. - v0 = Vector(vertexList[indices[0]].pos) - v1 = Vector(vertexList[indices[1]].pos) - v2 = Vector(vertexList[indices[2]].pos) - if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: - indices[1], indices[2] = indices[2], indices[1] - - useConveyor = colProp.conveyorOption != "None" - surfaceTypeData[i] = SurfaceType( - colProp.cameraID, - colProp.exitID, - int(self.getPropValue(colProp, "floorSetting"), base=16), - 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorProperty"), base=16), - colProp.decreaseHeight, - colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), - colProp.lightingSetting, - int(colProp.echo, base=16), - colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, - int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, - colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, - ) - - polyList.append( - CollisionPoly( - indices, - colProp.ignoreCameraCollision, - colProp.ignoreActorCollision, - colProp.ignoreProjectileCollision, - useConveyor, - tuple(normal), - distance, - i, - ) - ) - i += 1 - return bounds, vertexList, polyList, [surfaceTypeData[i] for i in range(len(surfaceTypeData))] - - def getBgCamFuncDataFromObjects(self, camObj: Object): - camProp = camObj.ootCameraPositionProperty - - # Camera faces opposite direction - pos, rot, _, _ = self.getConvertedTransformWithOrientation( - self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) - ) - - fov = math.degrees(camObj.data.angle) - if fov > 3.6: - fov *= 100 # see CAM_DATA_SCALED() macro - - return BgCamFuncData( - pos, - rot, - round(fov), - camProp.bgImageOverrideIndex, - ) - - def getCrawlspaceDataFromObjects(self, startIndex: int): - crawlspaceList: list[CrawlspaceData] = [] - crawlspaceObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" - ] - - index = startIndex - for obj in crawlspaceObjList: - if self.validateCurveData(obj): - crawlspaceList.append( - CrawlspaceData( - [ - [round(value) for value in self.transform @ obj.matrix_world @ point.co] - for point in obj.data.splines[0].points - ], - index, - ) - ) - index += 6 - return crawlspaceList - - def getBgCamInfoDataFromObjects(self): - camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] - camPosData: dict[int, BgCamFuncData] = {} - camInfoData: dict[int, BgCamInfo] = {} - - index = 0 - for camObj in camObjList: - camProp = camObj.ootCameraPositionProperty - - if camProp.camSType == "Custom": - setting = camProp.camSTypeCustom - else: - setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) - - if camProp.hasPositionData: - count = 3 - if camProp.index in camPosData: - raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") - camPosData[camProp.index] = self.getBgCamFuncDataFromObjects(camObj) - else: - count = 0 - - if camProp.index in camInfoData: - raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") - camInfoData[camProp.index] = BgCamInfo( - setting, - count, - index, - camProp.hasPositionData, - [camPosData[i] for i in range(min(camPosData.keys()), len(camPosData))] if len(camPosData) > 0 else [], - ) - - index += count - return ( - [camInfoData[i] for i in range(min(camInfoData.keys()), len(camInfoData))] if len(camInfoData) > 0 else [] - ) - - def getWaterBoxDataFromObjects(self): - waterboxObjList = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" - ] - waterboxList: list[WaterBox] = [] - - for waterboxObj in waterboxObjList: - emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) - checkIdentityRotation(waterboxObj, orientedRot, False) - - wboxProp = waterboxObj.ootWaterBoxProperty - roomObj = self.getRoomObjectFromChild(waterboxObj) - waterboxList.append( - WaterBox( - pos, - scale, - emptyScale, - wboxProp.camera, - wboxProp.lighting, - roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, - wboxProp.flag19, - ) - ) - - return waterboxList - - def getCount(self, bgCamInfoList: list[BgCamInfo]): - count = 0 - for elem in bgCamInfoList: - if elem.count != 0: # 0 means no pos data - count += elem.count - return count - - def getNewCollisionHeader(self): - colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() - bgCamInfoList = self.getBgCamInfoDataFromObjects() - - return OOTSceneCollisionHeader( - f"{self.name}_collisionHeader", - colBounds[0], - colBounds[1], - CollisionHeaderVertices(f"{self.name}_vertices", vertexList), - CollisionHeaderCollisionPoly(f"{self.name}_polygons", polyList), - CollisionHeaderSurfaceType(f"{self.name}_polygonTypes", surfaceTypeList), - CollisionHeaderBgCamInfo( - f"{self.name}_bgCamInfo", - f"{self.name}_camPosData", - bgCamInfoList, - self.getCrawlspaceDataFromObjects(self.getCount(bgCamInfoList)), - ), - CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), - ) - def getSceneMainC(self): sceneC = CData() headers: list[tuple[OOTSceneHeader, str]] = [] diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py index 4552da86d..521beeb2b 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -273,11 +273,6 @@ def getPathC(self): return pathData -@dataclass -class OOTSceneHeaderCrawlspace: - name: str - - @dataclass class OOTSceneAlternateHeader: name: str @@ -296,7 +291,6 @@ class OOTSceneHeader: exits: OOTSceneHeaderExits actors: OOTSceneHeaderActors path: OOTSceneHeaderPath - crawlspace: OOTSceneHeaderCrawlspace def getHeaderC(self): headerData = CData() From 86966aedfed0f4dc4eb06ff8f0e041effac6872a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:51:47 +0200 Subject: [PATCH 16/98] textures & room shape --- fast64_internal/oot/new_exporter/commands.py | 6 +- .../oot/new_exporter/common/scene.py | 2 + fast64_internal/oot/new_exporter/exporter.py | 155 ++++++++--- fast64_internal/oot/new_exporter/room.py | 84 +++++- .../oot/new_exporter/room_shape.py | 244 ++++++++++++++++++ fast64_internal/oot/new_exporter/scene.py | 6 + fast64_internal/oot/oot_level_classes.py | 4 +- fast64_internal/oot/scene/operators.py | 4 +- 8 files changed, 469 insertions(+), 36 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/room_shape.py diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 75d248b64..62369000f 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -44,8 +44,8 @@ def getWindSettingsCmd(self, infos: "OOTRoomHeaderInfos"): indent + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" ) - # def getRoomShapeCmd(self, infos: "OOTRoom"): - # return indent + f"SCENE_CMD_ROOM_SHAPE(&{infos.mesh.headerName()})" + def getRoomShapeCmd(self, room: "OOTRoom"): + return indent + f"SCENE_CMD_ROOM_SHAPE(&{room.roomShape.getName()}),\n" def getObjectListCmd(self, objects: "OOTRoomHeaderObjects"): return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getObjectLengthDefineName()}, {objects.name}),\n" @@ -63,7 +63,6 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): self.getRoomBehaviourCmd, self.getSkyboxDisablesCmd, self.getTimeSettingsCmd, - # self.getRoomShapeCmd, ] if curHeader.infos.setWind: @@ -72,6 +71,7 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): hasAltHeaders = headerIndex == 0 and room.hasAlternateHeaders() roomCmdData = ( (room.getAltHeaderListCmd(room.altHeader.name) if hasAltHeaders else "") + + self.getRoomShapeCmd(room) + (self.getObjectListCmd(curHeader.objects) if len(curHeader.objects.objectList) > 0 else "") + (self.getActorListCmd(curHeader.actors) if len(curHeader.actors.actorList) > 0 else "") + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdFuncInfosList) + ",\n") diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py index 8340693b4..d5f7a5a01 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -3,6 +3,7 @@ from ....utility import PluginError, exportColor, ootGetBaseOrCustomLight from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ...oot_constants import ootData +from ...oot_model_classes import OOTModel from ..commands import SceneCommands from ..scene_header import EnvLightSettings, Path, OOTSceneHeader, OOTSceneAlternateHeader from ..room import OOTRoom @@ -13,6 +14,7 @@ @dataclass class SceneCommon(CollisionCommon, SceneCommands): name: str = None + model: OOTModel = None headerIndex: int = None mainHeader: OOTSceneHeader = None altHeader: OOTSceneAlternateHeader = None diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 5d73de6b8..9d3b2e7d1 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -4,12 +4,16 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from ...f3d.f3d_gbi import DLFormat +from ...f3d.f3d_gbi import DLFormat, TextureExportSettings from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData from ..oot_object import addMissingObjectsToAllRoomHeadersNew -from .common import altHeaderList +from ..oot_model_classes import OOTModel +from ..oot_f3d_writer import writeTextureArraysNew +from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh +from ..oot_utility import CullGroup +from .common import Common, altHeaderList from .scene import OOTScene from .scene_header import OOTSceneAlternateHeader from .room import OOTRoom, OOTRoomAlternateHeader @@ -46,6 +50,7 @@ class OOTSceneData: sceneMain: str = None sceneCollision: str = None sceneCutscenes: list[str] = field(default_factory=list) + sceneTextures: str = None @dataclass @@ -59,16 +64,19 @@ class OOTSceneExport: saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions singleFileExport: bool + isHWv1: bool + textureExportSettings: TextureExportSettings dlFormat: DLFormat = DLFormat.Static scene: OOTScene = None path: str = None + sceneBasePath: str = None header: str = "" sceneData: OOTSceneData = None roomList: dict[int, OOTRoomData] = field(default_factory=dict) hasCutscenes: bool = False - def getNewRoomList(self): + def getNewRoomList(self, scene: OOTScene): processedRooms = [] roomList: list[OOTRoom] = [] roomObjs: list[Object] = [ @@ -80,11 +88,57 @@ def getNewRoomList(self): for roomObj in roomObjs: altProp = roomObj.ootAlternateRoomHeaders - roomIndex = roomObj.ootRoomHeader.roomIndex + roomHeader = roomObj.ootRoomHeader + roomIndex = roomHeader.roomIndex roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" - roomData = OOTRoom(self.sceneObj, self.transform, roomIndex, roomName, roomObj) + roomData = OOTRoom( + self.sceneObj, + self.transform, + roomIndex, + roomName, + roomObj, + roomHeader.roomShape, + scene.model.addSubModel( + OOTModel( + scene.model.f3d.F3D_VER, + scene.model.f3d._HW_VERSION_1, + roomName + "_dl", + scene.model.DLFormat, + None, + ) + ), + ) + + # Mesh stuff + c = Common(self.sceneObj, self.transform) + pos, _, scale, _ = c.getConvertedTransform(self.transform, self.sceneObj, roomObj, True) + cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) + DLGroup = roomData.mesh.addMeshGroup(cullGroup).DLGroup + boundingBox = BoundingBox() + ootProcessMesh( + roomData.mesh, + DLGroup, + self.sceneObj, + roomObj, + self.transform, + not self.saveTexturesAsPNG, + None, + boundingBox, + ) + + roomData.mesh.terminateDLs() + roomData.mesh.removeUnusedEntries() + + # Other + if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomHeader.bgImageList) < 1: + raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') + + if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(processedRooms) > 1: + raise PluginError(f'Room shape "Image" can only have one room in the scene.') + + roomData.roomShape = roomData.getNewRoomShape() altHeaderData = OOTRoomAlternateHeader(f"{roomData.name}_alternateHeaders") - roomData.mainHeader = roomData.getNewRoomHeader(roomObj.ootRoomHeader) + roomData.mainHeader = roomData.getNewRoomHeader(roomHeader) hasAltHeader = False for i, header in enumerate(altHeaderList, 1): @@ -114,6 +168,7 @@ def getNewRoomList(self): def getNewScene(self): altProp = self.sceneObj.ootAlternateSceneHeaders sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") + sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) hasAltHeader = False @@ -132,7 +187,7 @@ def getNewScene(self): hasAltHeader = True sceneData.altHeader = altHeaderData if hasAltHeader else None - sceneData.roomList = self.getNewRoomList() + sceneData.roomList = self.getNewRoomList(sceneData) sceneData.colHeader = sceneData.getNewCollisionHeader() sceneData.validateScene() @@ -179,25 +234,34 @@ def getNewSceneFromEmptyObject(self): def setRoomListData(self): for room in self.scene.roomList: - roomData = OOTRoomData(room.name) roomMainData = room.getRoomMainC() + roomModelData = room.roomShape.getRoomShapeDListC(room.mesh, self.textureExportSettings) + roomModelInfoData = room.roomShape.getRoomShapeC() - roomData.roomMain = roomMainData.source - self.header += roomMainData.header - - self.roomList[room.roomIndex] = roomData + self.header += roomMainData.header + roomModelData.header + roomModelInfoData.header + self.roomList[room.roomIndex] = OOTRoomData( + room.name, roomMainData.source, roomModelData.source, roomModelInfoData.source + ) def setSceneData(self): - sceneData = OOTSceneData() sceneMainData = self.scene.getSceneMainC() sceneCollisionData = self.scene.colHeader.getSceneCollisionC() sceneCutsceneData = self.scene.getSceneCutscenesC() + sceneTexturesData = self.scene.getSceneTexturesC(self.textureExportSettings) + + self.header += ( + sceneMainData.header + + "".join(cs.header for cs in sceneCutsceneData) + + sceneCollisionData.header + + sceneTexturesData.header + ) - sceneData.sceneMain = sceneMainData.source - sceneData.sceneCollision = sceneCollisionData.source - sceneData.sceneCutscenes = [cs.source for cs in sceneCutsceneData] - self.header += sceneMainData.header + "".join(cs.header for cs in sceneCutsceneData) + sceneCollisionData.header - self.sceneData = sceneData + self.sceneData = OOTSceneData( + sceneMainData.source, + sceneCollisionData.source, + [cs.source for cs in sceneCutsceneData], + sceneTexturesData.source, + ) def setIncludeData(self): suffix = "\n\n" @@ -206,6 +270,8 @@ def setIncludeData(self): "\n".join( [ '#include "ultra64/ultratypes.h"', + '#include "ultra64/gbi.h"', + '#include "libc/stddef.h"', '#include "libc/stdint.h"', '#include "z64math.h"', ] @@ -224,6 +290,16 @@ def setIncludeData(self): + "\n" ) + roomShapeInfo = ( + "\n".join( + [ + '#include "macros.h"', + '#include "z64scene.h"', + ] + ) + + "\n" + ) + scene = ( "\n".join( [ @@ -258,7 +334,13 @@ def setIncludeData(self): ) for roomData in self.roomList.values(): - roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain + if self.singleFileExport: + common += room + roomShapeInfo + sceneInclude + roomData.roomMain = common + suffix + roomData.roomMain + else: + roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain + roomData.roomModelInfo = common + roomShapeInfo + sceneInclude + suffix + roomData.roomModelInfo + roomData.roomModel = common + sceneInclude + suffix + roomData.roomModel if self.singleFileExport: common += scene + collision + cutscene + sceneInclude @@ -272,9 +354,13 @@ def setIncludeData(self): cs = cutscene + sceneInclude + suffix + cs def writeScene(self): - sceneBasePath = os.path.join(self.path, self.scene.name) - for room in self.roomList.values(): + if self.singleFileExport: + room.roomMain += room.roomModelInfo + room.roomModel + else: + writeFile(os.path.join(self.path, f"{room.name}_model_info.c"), room.roomModelInfo) + writeFile(os.path.join(self.path, f"{room.name}_model.c"), room.roomModel) + writeFile(os.path.join(self.path, room.name + ".c"), room.roomMain) if self.singleFileExport: @@ -283,13 +369,16 @@ def writeScene(self): for i, cs in enumerate(self.sceneData.sceneCutscenes): self.sceneData.sceneMain += cs else: - writeFile(f"{sceneBasePath}_col.c", self.sceneData.sceneCollision) + writeFile(f"{self.sceneBasePath}_col.c", self.sceneData.sceneCollision) if self.hasCutscenes: for i, cs in enumerate(self.sceneData.sceneCutscenes): - writeFile(f"{sceneBasePath}_cs_{i}.c", cs) + writeFile(f"{self.sceneBasePath}_cs_{i}.c", cs) - writeFile(sceneBasePath + ".c", self.sceneData.sceneMain) - writeFile(sceneBasePath + ".h", self.header) + writeFile(self.sceneBasePath + ".c", self.sceneData.sceneMain) + writeFile(self.sceneBasePath + ".h", self.header) + + for room in self.scene.roomList: + room.mesh.copyBgImages(self.path) def export(self): checkObjectReference(self.sceneObj, "Scene object") @@ -303,12 +392,20 @@ def export(self): exportSubdir = os.path.dirname(getSceneDirFromLevelName(self.sceneName)) sceneInclude = exportSubdir + "/" + self.sceneName + "/" - levelPath = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) - self.scene = self.getNewSceneFromEmptyObject() - self.path = levelPath + self.path = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) + self.sceneBasePath = os.path.join(self.path, self.scene.name) + self.textureExportSettings.includeDir = sceneInclude + self.textureExportSettings.exportPath = self.path self.setSceneData() self.setRoomListData() - self.setIncludeData() + if not isCustomExport: + writeTextureArraysExistingScene(self.scene.model, exportPath, sceneInclude + self.sceneName + "_scene.h") + else: + textureArrayData = writeTextureArraysNew(self.scene.model, None) + self.sceneData.sceneTextures += textureArrayData.source + self.header += textureArrayData.header + + self.setIncludeData() self.writeScene() diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 6c87458b3..c6e1524ac 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -1,11 +1,25 @@ from dataclasses import dataclass from bpy.types import Object -from ...utility import CData, indent +from ...utility import PluginError, CData, indent from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData +from ..oot_level_classes import OOTRoomMesh +from ..oot_model_classes import OOTModel from .commands import RoomCommands from .common import Common, altHeaderList +from .room_shape import ( + RoomShape, + RoomShapeDLists, + RoomShapeDListsEntry, + RoomShapeImageBase, + RoomShapeImageMulti, + RoomShapeImageMultiBg, + RoomShapeImageMultiBgEntry, + RoomShapeImageSingle, + RoomShapeNormal, +) + from .room_header import ( OOTRoomHeader, OOTRoomAlternateHeader, @@ -19,9 +33,16 @@ class OOTRoom(Common, RoomCommands): name: str = None roomObj: Object = None + roomShapeType: str = None + model: OOTModel = None headerIndex: int = None mainHeader: OOTRoomHeader = None altHeader: OOTRoomAlternateHeader = None + mesh: OOTRoomMesh = None + roomShape: RoomShape = None + + def __post_init__(self): + self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) def hasAlternateHeaders(self): return self.altHeader is not None @@ -40,6 +61,31 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: return None + def getMultiBgEntries(self): + entries: list[RoomShapeImageMultiBgEntry] = [] + + for i, bgImg in enumerate(self.mesh.bgImages): + entries.append( + RoomShapeImageMultiBgEntry( + i, bgImg.name, bgImg.image.size[0], bgImg.image.size[1], bgImg.otherModeFlags + ) + ) + + return entries + + def getDListsEntries(self): + entries: list[RoomShapeDListsEntry] = [] + + for meshGrp in self.mesh.meshEntries: + entries.append( + RoomShapeDListsEntry( + meshGrp.DLGroup.opaque.name if meshGrp.DLGroup.opaque is not None else "NULL", + meshGrp.DLGroup.transparent.name if meshGrp.DLGroup.transparent is not None else "NULL", + ) + ) + + return entries + def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): """Returns a new room header with the informations from the scene empty object""" @@ -82,6 +128,42 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = ), ) + def getNewRoomShape(self): + normal = None + single = None + multiImg = None + multi = None + name = f"{self.name}_shapeHeader" + dlName = f"{self.name}_shapeDListEntry" + + match self.roomShapeType: + case "ROOM_SHAPE_TYPE_NORMAL": + normal = RoomShapeNormal(name, self.roomShapeType, dlName) + case "ROOM_SHAPE_TYPE_IMAGE": + if len(self.mesh.bgImages) > 1: + multiImg = RoomShapeImageMultiBg(f"{self.name}_shapeMultiBg", self.getMultiBgEntries()) + multi = RoomShapeImageMulti( + name, self.roomShapeType, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name + ) + else: + bgImg = self.mesh.bgImages[0] + single = RoomShapeImageSingle( + name, + self.roomShapeType, + "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE", + dlName, + bgImg.name, + bgImg.image.size[0], + bgImg.image.size[1], + bgImg.otherModeFlags, + ) + case _: + raise PluginError(f"ERROR: Room Shape not supported: {self.roomShapeType}") + + return RoomShape( + RoomShapeDLists(dlName, normal is not None, self.getDListsEntries()), normal, single, multiImg, multi + ) + def getRoomMainC(self): roomC = CData() roomHeaders: list[tuple[OOTRoomHeader, str]] = [] diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room_shape.py new file mode 100644 index 000000000..2d3fad7f2 --- /dev/null +++ b/fast64_internal/oot/new_exporter/room_shape.py @@ -0,0 +1,244 @@ +from dataclasses import dataclass +from ...utility import CData, indent +from ...f3d.f3d_gbi import TextureExportSettings +from ..oot_level_classes import OOTRoomMesh + + +@dataclass +class RoomShapeImageBase: + name: str + + type: str + amountType: str # ROOM_SHAPE_IMAGE_AMOUNT_SINGLE/_MULTI + entryArrayName: str + + +@dataclass +class RoomShapeDListsEntry: + opaPtr: str + xluPtr: str + + def getEntryC(self): + return f"{self.opaPtr}, {self.xluPtr}" + + +@dataclass +class RoomShapeImageMultiBgEntry: + bgCamIndex: int + imgName: str + width: int + height: int + otherModeFlags: str + + unk_00: int = 130 + unk_0C: int = 0 + tlut: str = "NULL" + format: str = "G_IM_FMT_RGBA" + size: str = "G_IM_SIZ_16b" + tlutCount: int = 0 + + def getEntryC(self): + return ( + indent + + "{\n" + + f",\n{indent * 2}".join( + [ + f"0x{self.unk_00:04X}, {self.bgCamIndex}", + f"{self.imgName}", + f"0x{self.unk_0C:08X}", + f"{self.tlut}", + f"{self.width}, {self.height}", + f"{self.format}, {self.size}", + f"{self.otherModeFlags}, 0x{self.tlutCount:04X},", + ] + ) + + indent + + " },\n" + ) + + +@dataclass +class RoomShapeImageMultiBg: + name: str + entries: list[RoomShapeImageMultiBgEntry] + + def getC(self): + infoData = CData() + listName = f"RoomShapeImageMultiBgEntry {self.name}[{len(self.entries)}]" + + # .h + infoData.header = f"extern {listName};\n" + + # .c + infoData.source = listName + " = {\n" + f"".join(elem.getEntryC() for elem in self.entries) + "};\n\n" + + return infoData + + +@dataclass +class RoomShapeDLists: + name: str + isArray: bool + entries: list[RoomShapeDListsEntry] + + def getC(self): + infoData = CData() + listName = f"RoomShapeDListsEntry {self.name}" + f"[{len(self.entries)}]" if self.isArray else "" + + # .h + infoData.header = f"extern {listName};\n" + + # .c + infoData.source = ( + (listName + " = {\n") + + ( + indent + f",\n{indent}".join("{ " + elem.getEntryC() + " }" for elem in self.entries) + if self.isArray + else indent + self.entries[0].getEntryC() + ) + + "\n};\n\n" + ) + + return infoData + + +@dataclass +class RoomShapeImageSingle(RoomShapeImageBase): + imgName: str + width: int + height: int + otherModeFlags: str + + unk_0C: int = 0 + tlut: str = "NULL" + format: str = "G_IM_FMT_RGBA" + size: str = "G_IM_SIZ_16b" + tlutCount: int = 0 + + def getC(self): + infoData = CData() + listName = f"RoomShapeImageSingle {self.name}" + + # .h + infoData.header = f"extern {listName};\n" + + # .c + infoData.source = (listName + " = {\n") + f",\n{indent}".join( + [ + "{ " + f"{self.type}, {self.amountType}, &{self.entryArrayName}" + " }", + f"{self.imgName}", + f"0x{self.unk_0C:08X}", + f"{self.tlut}", + f"{self.width}, {self.height}", + f"{self.format}, {self.size}", + f"{self.otherModeFlags}, 0x{self.tlutCount:04X}", + ] + ) + + return infoData + + +@dataclass +class RoomShapeImageMulti(RoomShapeImageBase): + bgEntryArrayName: str + + def getC(self): + infoData = CData() + listName = f"RoomShapeImageSingle {self.name}" + + # .h + infoData.header = f"extern {listName};\n" + + # .c + infoData.source = (listName + " = {\n") + f",\n{indent}".join( + [ + "{ " + f"{self.type}, {self.amountType}, &{self.entryArrayName}" + " }", + f"ARRAY_COUNT({self.bgEntryArrayName})", + f"{self.bgEntryArrayName}", + ] + ) + + return infoData + + +@dataclass +class RoomShapeNormal: + name: str + type: str + entryArrayName: str + + def getC(self): + infoData = CData() + listName = f"RoomShapeNormal {self.name}" + + # .h + infoData.header = f"extern {listName};\n" + + # .c + numEntries = f"ARRAY_COUNT({self.entryArrayName})" + infoData.source = ( + (listName + " = {\n" + indent) + + f",\n{indent}".join( + [f"{self.type}", numEntries, f"{self.entryArrayName}", f"{self.entryArrayName} + {numEntries}"] + ) + + "\n};\n\n" + ) + + return infoData + + +@dataclass +class RoomShape: + dl: RoomShapeDLists + normal: RoomShapeNormal + single: RoomShapeImageSingle + multiImg: RoomShapeImageMultiBg + multi: RoomShapeImageMulti + + def getName(self): + if self.normal is not None: + return self.normal.name + + if self.single is not None: + return self.single.name + + if self.multi is not None and self.multiImg is not None: + return self.multi.name + + + def getRoomShapeDListC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): + dlData = CData() + + bitsPerValue = 64 + for bgImage in roomMesh.bgImages: + # .h + dlData.header += f"extern u{bitsPerValue} {bgImage.name}[];\n" + + # .c + dlData.source += ( + # This is to force 8 byte alignment + (f"Gfx {bgImage.name}_aligner[] = " + "{ gsSPEndDisplayList() };\n" if bitsPerValue != 64 else "") + + (f"u{bitsPerValue} {bgImage.name}[SCREEN_WIDTH * SCREEN_HEIGHT / 4]" + " = {\n") + + f'#include "{textureSettings.includeDir + bgImage.getFilename()}.inc.c"' + + "\n};\n\n" + ) + + print("sauce: \n", dlData.source) + + return dlData + + def getRoomShapeC(self): + shapeData = CData() + + if self.normal is not None: + shapeData.append(self.normal.getC()) + + if self.single is not None: + shapeData.append(self.single.getC()) + + if self.multi is not None and self.multiImg is not None: + shapeData.append(self.multi.getC()) + shapeData.append(self.multiImg.getC()) + + shapeData.append(self.dl.getC()) + return shapeData diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index fb638f4dc..ef223e64a 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -1,5 +1,7 @@ from dataclasses import dataclass from ...utility import PluginError, CData, indent +from ...f3d.f3d_gbi import TextureExportSettings, ScrollMethod +from ..oot_model_classes import OOTGfxFormatter from ..scene.properties import OOTSceneHeaderProperty from .common import SceneCommon @@ -181,3 +183,7 @@ def getSceneCutscenesC(self): # will be implemented when PR #208 is merged csDataList: list[CData] = [] return csDataList + + # Writes the textures and material setup displaylists that are shared between multiple rooms (is written to the scene) + def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): + return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() diff --git a/fast64_internal/oot/oot_level_classes.py b/fast64_internal/oot/oot_level_classes.py index 185d03fb5..b5d238bdb 100644 --- a/fast64_internal/oot/oot_level_classes.py +++ b/fast64_internal/oot/oot_level_classes.py @@ -267,9 +267,9 @@ class OOTRoomMesh: def __init__(self, roomName, roomShape, model): self.roomName = roomName self.roomShape = roomShape - self.meshEntries = [] + self.meshEntries: list[OOTRoomMeshGroup] = [] self.model = model - self.bgImages = [] + self.bgImages: list[OOTBGImage] = [] def terminateDLs(self): for entry in self.meshEntries: diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index b5ee03f82..5fb9e0dfc 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -5,7 +5,7 @@ from bpy.utils import register_class, unregister_class from bpy.ops import object from mathutils import Matrix, Vector -from ...f3d.f3d_gbi import DLFormat +from ...f3d.f3d_gbi import TextureExportSettings from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader from ..oot_utility import ExportInfo, sceneNameFromID, getEnumName from ..oot_level_writer import ootExportSceneToC @@ -186,6 +186,8 @@ def execute(self, context): bpy.context.scene.saveTextures, bootOptions if hackerFeaturesEnabled else None, settings.singleFile, + context.scene.isHWv1, + TextureExportSettings(False, context.scene.saveTextures, None, None), ).export() # ootExportSceneToC( From 0a5347e4b09ccd5a5385ccfe00a9d1cbe2a88a14 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:22:06 +0200 Subject: [PATCH 17/98] fix missing things --- fast64_internal/oot/new_exporter/exporter.py | 9 +++- fast64_internal/oot/new_exporter/room.py | 44 +++++++++++++++++-- .../oot/new_exporter/room_shape.py | 6 +-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 9d3b2e7d1..2f29c9db8 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -126,6 +126,10 @@ def getNewRoomList(self, scene: OOTScene): boundingBox, ) + centroid, radius = boundingBox.getEnclosingSphere() + cullGroup.position = centroid + cullGroup.cullDepth = radius + roomData.mesh.terminateDLs() roomData.mesh.removeUnusedEntries() @@ -136,7 +140,7 @@ def getNewRoomList(self, scene: OOTScene): if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(processedRooms) > 1: raise PluginError(f'Room shape "Image" can only have one room in the scene.') - roomData.roomShape = roomData.getNewRoomShape() + roomData.roomShape = roomData.getNewRoomShape(roomHeader, self.sceneName) altHeaderData = OOTRoomAlternateHeader(f"{roomData.name}_alternateHeaders") roomData.mainHeader = roomData.getNewRoomHeader(roomHeader) hasAltHeader = False @@ -235,7 +239,7 @@ def getNewSceneFromEmptyObject(self): def setRoomListData(self): for room in self.scene.roomList: roomMainData = room.getRoomMainC() - roomModelData = room.roomShape.getRoomShapeDListC(room.mesh, self.textureExportSettings) + roomModelData = room.getRoomShapeModelC(self.textureExportSettings) roomModelInfoData = room.roomShape.getRoomShapeC() self.header += roomMainData.header + roomModelData.header + roomModelInfoData.header @@ -374,6 +378,7 @@ def writeScene(self): for i, cs in enumerate(self.sceneData.sceneCutscenes): writeFile(f"{self.sceneBasePath}_cs_{i}.c", cs) + writeFile(f"{self.sceneBasePath}_tex.c", self.sceneData.sceneTextures) writeFile(self.sceneBasePath + ".c", self.sceneData.sceneMain) writeFile(self.sceneBasePath + ".h", self.header) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index c6e1524ac..84bb1481c 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -1,10 +1,11 @@ from dataclasses import dataclass from bpy.types import Object -from ...utility import PluginError, CData, indent +from ...utility import PluginError, CData, toAlnum, indent +from ...f3d.f3d_gbi import TextureExportSettings, ScrollMethod from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData -from ..oot_level_classes import OOTRoomMesh -from ..oot_model_classes import OOTModel +from ..oot_level_classes import OOTRoomMesh, OOTBGImage +from ..oot_model_classes import OOTModel, OOTGfxFormatter from .commands import RoomCommands from .common import Common, altHeaderList @@ -128,7 +129,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = ), ) - def getNewRoomShape(self): + def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): normal = None single = None multiImg = None @@ -140,6 +141,19 @@ def getNewRoomShape(self): case "ROOM_SHAPE_TYPE_NORMAL": normal = RoomShapeNormal(name, self.roomShapeType, dlName) case "ROOM_SHAPE_TYPE_IMAGE": + for bgImage in headerProp.bgImageList: + if bgImage.image is None: + raise PluginError( + 'A room is has room shape "Image" but does not have an image set in one of its BG images.' + ) + self.mesh.bgImages.append( + OOTBGImage( + toAlnum(sceneName + "_bg_" + bgImage.image.name), + bgImage.image, + bgImage.otherModeFlags, + ) + ) + if len(self.mesh.bgImages) > 1: multiImg = RoomShapeImageMultiBg(f"{self.name}_shapeMultiBg", self.getMultiBgEntries()) multi = RoomShapeImageMulti( @@ -212,3 +226,25 @@ def getRoomMainC(self): roomC.append(curHeader.actors.getActorListC()) return roomC + + def getRoomShapeModelC(self, textureSettings: TextureExportSettings): + roomModel = CData() + + for i, entry in enumerate(self.mesh.meshEntries): + if entry.DLGroup.opaque is not None: + roomModel.append(entry.DLGroup.opaque.to_c(self.mesh.model.f3d)) + + if entry.DLGroup.transparent is not None: + roomModel.append(entry.DLGroup.transparent.to_c(self.mesh.model.f3d)) + + # type ``ROOM_SHAPE_TYPE_IMAGE`` only allows 1 room + if i == 0 and self.mesh.roomShape == "ROOM_SHAPE_TYPE_IMAGE": + break + + roomModel.append(self.mesh.model.to_c(textureSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all()) + + if self.roomShape.multiImg is not None: + roomModel.append(self.roomShape.multiImg.getC()) + roomModel.append(self.roomShape.getRoomShapeBgImgDataC(self.mesh, textureSettings)) + + return roomModel diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room_shape.py index 2d3fad7f2..56f35a46b 100644 --- a/fast64_internal/oot/new_exporter/room_shape.py +++ b/fast64_internal/oot/new_exporter/room_shape.py @@ -206,7 +206,7 @@ def getName(self): return self.multi.name - def getRoomShapeDListC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): + def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): dlData = CData() bitsPerValue = 64 @@ -223,10 +223,8 @@ def getRoomShapeDListC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExpo + "\n};\n\n" ) - print("sauce: \n", dlData.source) - return dlData - + def getRoomShapeC(self): shapeData = CData() From 1a2e9828a6252a05fe2c3e4fcdbbe672c0067523 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:05:00 +0200 Subject: [PATCH 18/98] fixes and additional things --- fast64_internal/oot/new_exporter/collision.py | 4 +- fast64_internal/oot/new_exporter/commands.py | 4 +- .../oot/new_exporter/common/__init__.py | 65 ++++ .../oot/new_exporter/common/scene.py | 7 +- fast64_internal/oot/new_exporter/exporter.py | 113 +++---- fast64_internal/oot/new_exporter/file.py | 35 +++ fast64_internal/oot/new_exporter/room.py | 2 +- .../oot/new_exporter/room_shape.py | 3 +- .../oot/new_exporter/scene_table.py | 288 ++++++++++++++++++ fast64_internal/oot/new_exporter/spec.py | 139 +++++++++ fast64_internal/oot/oot_utility.py | 2 +- .../oot/scene/exporter/to_c/__init__.py | 2 +- 12 files changed, 585 insertions(+), 79 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/file.py create mode 100644 fast64_internal/oot/new_exporter/scene_table.py create mode 100644 fast64_internal/oot/new_exporter/spec.py diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index d21f4a7bc..d1c9f1673 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -88,7 +88,7 @@ def getDataArrayC(self): listName = f"Vec3s {self.posDataName}[]" # .h - posData.header = f"extern {listName};" + posData.header = f"extern {listName};\n" # .c posData.source = ( @@ -105,7 +105,7 @@ def getInfoArrayC(self): listName = f"BgCamInfo {self.name}[]" # .h - bgCamInfoData.header = f"extern {listName};" + bgCamInfoData.header = f"extern {listName};\n" # .c bgCamInfoData.source = ( diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 62369000f..f21f06b25 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -166,7 +166,7 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he ] if len(curHeader.actors.transitionActorList) > 0: - getCmdActorList.append(self.getTransActorListCmd) + getCmdActorList.insert(0, self.getTransActorListCmd) # if scene.writeCutscene: # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) @@ -181,8 +181,8 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he + self.getPathListCmd(curHeader.path) # + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") - + (",\n".join(getCmd(scene, headerIndex) for getCmd in getCmdFunc1List) + ",\n") + (",\n".join(getCmd(curHeader.actors) for getCmd in getCmdActorList) + ",\n") + + (",\n".join(getCmd(scene, headerIndex) for getCmd in getCmdFunc1List) + ",\n") + scene.getEndCmd() ) diff --git a/fast64_internal/oot/new_exporter/common/__init__.py b/fast64_internal/oot/new_exporter/common/__init__.py index 4fe1fe63b..85f4eff55 100644 --- a/fast64_internal/oot/new_exporter/common/__init__.py +++ b/fast64_internal/oot/new_exporter/common/__init__.py @@ -1,3 +1,68 @@ from .classes import Common, Actor, TransitionActor, EntranceActor, altHeaderList from .collision import CollisionCommon from .scene import SceneCommon + + +includeData = { + "common": ( + "\n".join( + [ + '#include "ultra64/ultratypes.h"', + '#include "ultra64/gbi.h"', + '#include "libc/stddef.h"', + '#include "libc/stdint.h"', + '#include "z64math.h"', + ] + ) + + "\n" + ), + "roomMain": ( + "\n".join( + [ + '#include "z64object.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + ] + ) + + "\n" + ), + "roomShapeInfo": ( + "\n".join( + [ + '#include "macros.h"', + '#include "z64scene.h"', + ] + ) + + "\n" + ), + "sceneMain": ( + "\n".join( + [ + '#include "z64dma.h"', + '#include "z64actor.h"', + '#include "z64scene.h"', + '#include "z64environment.h"', + ] + ) + + "\n" + ), + "collision": ( + "\n".join( + [ + '#include "macros.h"', + '#include "z64camera.h"', + '#include "z64bgcheck.h"', + ] + ) + + "\n" + ), + "cutscene": ( + "\n".join( + [ + '#include "z64cutscene.h"', + '#include "z64cutscene_commands.h"', + ] + ) + + "\n" + ), +} diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py index d5f7a5a01..c16b9730f 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -1,15 +1,18 @@ from dataclasses import dataclass, field from bpy.types import Object +from typing import TYPE_CHECKING from ....utility import PluginError, exportColor, ootGetBaseOrCustomLight from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty from ...oot_constants import ootData from ...oot_model_classes import OOTModel from ..commands import SceneCommands from ..scene_header import EnvLightSettings, Path, OOTSceneHeader, OOTSceneAlternateHeader -from ..room import OOTRoom from .classes import TransitionActor, EntranceActor, altHeaderList from .collision import CollisionCommon +if TYPE_CHECKING: + from ..room import OOTRoom + @dataclass class SceneCommon(CollisionCommon, SceneCommands): @@ -18,7 +21,7 @@ class SceneCommon(CollisionCommon, SceneCommands): headerIndex: int = None mainHeader: OOTSceneHeader = None altHeader: OOTSceneAlternateHeader = None - roomList: list[OOTRoom] = field(default_factory=list) + roomList: list["OOTRoom"] = field(default_factory=list) def validateRoomIndices(self): for i, room in enumerate(self.roomList): diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 2f29c9db8..3921b5dc9 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -6,6 +6,7 @@ from bpy.types import Object from ...f3d.f3d_gbi import DLFormat, TextureExportSettings from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty +from ..scene.exporter.to_c import setBootupScene from ..room.properties import OOTRoomHeaderProperty from ..oot_constants import ootData from ..oot_object import addMissingObjectsToAllRoomHeadersNew @@ -13,10 +14,11 @@ from ..oot_f3d_writer import writeTextureArraysNew from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh from ..oot_utility import CullGroup -from .common import Common, altHeaderList +from .common import Common, altHeaderList, includeData from .scene import OOTScene from .scene_header import OOTSceneAlternateHeader from .room import OOTRoom, OOTRoomAlternateHeader +from .file import Files from ...utility import ( PluginError, @@ -75,6 +77,7 @@ class OOTSceneExport: sceneData: OOTSceneData = None roomList: dict[int, OOTRoomData] = field(default_factory=dict) hasCutscenes: bool = False + hasSceneTextures: bool = False def getNewRoomList(self, scene: OOTScene): processedRooms = [] @@ -270,71 +273,26 @@ def setSceneData(self): def setIncludeData(self): suffix = "\n\n" sceneInclude = f'\n#include "{self.scene.name}.h"\n' - common = ( - "\n".join( - [ - '#include "ultra64/ultratypes.h"', - '#include "ultra64/gbi.h"', - '#include "libc/stddef.h"', - '#include "libc/stdint.h"', - '#include "z64math.h"', - ] - ) - + "\n" - ) - - room = ( - "\n".join( - [ - '#include "z64object.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - ] - ) - + "\n" - ) + common = includeData["common"] + # room = includeData["roomMain"] + # roomShapeInfo = includeData["roomShapeInfo"] + # scene = includeData["sceneMain"] + # collision = includeData["collision"] + # cutscene = includeData["cutscene"] + room = "" + roomShapeInfo = "" + scene = "" + collision = "" + cutscene = "" - roomShapeInfo = ( - "\n".join( - [ - '#include "macros.h"', - '#include "z64scene.h"', - ] - ) - + "\n" - ) - - scene = ( - "\n".join( - [ - '#include "z64dma.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - '#include "z64environment.h"', - ] - ) - + "\n" - ) - - collision = ( - "\n".join( - [ - '#include "macros.h"', - '#include "z64camera.h"', - '#include "z64bgcheck.h"', - ] - ) - + "\n" - ) - - cutscene = ( - "\n".join( - [ - '#include "z64cutscene.h"', - '#include "z64cutscene_commands.h"', - ] - ) - + "\n" + common = ( + '#include "ultra64.h"\n' + + '#include "z64.h"\n' + + '#include "macros.h"\n' + + '#include "segment_symbols.h"\n' + + '#include "command_macros_base.h"\n' + + '#include "z64cutscene_commands.h"\n' + + '#include "variables.h"\n' ) for roomData in self.roomList.values(): @@ -360,26 +318,32 @@ def setIncludeData(self): def writeScene(self): for room in self.roomList.values(): if self.singleFileExport: + roomMainPath = f"{room.name}.c" room.roomMain += room.roomModelInfo + room.roomModel else: + roomMainPath = f"{room.name}_main.c" writeFile(os.path.join(self.path, f"{room.name}_model_info.c"), room.roomModelInfo) writeFile(os.path.join(self.path, f"{room.name}_model.c"), room.roomModel) - writeFile(os.path.join(self.path, room.name + ".c"), room.roomMain) + writeFile(os.path.join(self.path, roomMainPath), room.roomMain) if self.singleFileExport: + sceneMainPath = f"{self.sceneBasePath}.c" self.sceneData.sceneMain += self.sceneData.sceneCollision if self.hasCutscenes: for i, cs in enumerate(self.sceneData.sceneCutscenes): self.sceneData.sceneMain += cs else: + sceneMainPath = f"{self.sceneBasePath}_main.c" writeFile(f"{self.sceneBasePath}_col.c", self.sceneData.sceneCollision) if self.hasCutscenes: for i, cs in enumerate(self.sceneData.sceneCutscenes): writeFile(f"{self.sceneBasePath}_cs_{i}.c", cs) - writeFile(f"{self.sceneBasePath}_tex.c", self.sceneData.sceneTextures) - writeFile(self.sceneBasePath + ".c", self.sceneData.sceneMain) + if self.hasSceneTextures: + writeFile(f"{self.sceneBasePath}_tex.c", self.sceneData.sceneTextures) + + writeFile(sceneMainPath, self.sceneData.sceneMain) writeFile(self.sceneBasePath + ".h", self.header) for room in self.scene.roomList: @@ -404,6 +368,7 @@ def export(self): self.textureExportSettings.exportPath = self.path self.setSceneData() self.setRoomListData() + self.hasSceneTextures = len(self.sceneData.sceneTextures) > 0 if not isCustomExport: writeTextureArraysExistingScene(self.scene.model, exportPath, sceneInclude + self.sceneName + "_scene.h") @@ -414,3 +379,15 @@ def export(self): self.setIncludeData() self.writeScene() + + if not isCustomExport: + Files(self).editFiles() + + if self.hackerootBootOption is not None and self.hackerootBootOption.bootToScene: + setBootupScene( + os.path.join(exportPath, "include/config/config_debug.h") + if not isCustomExport + else os.path.join(self.path, "config_bootup.h"), + "ENTR_" + self.sceneName.upper() + "_" + str(self.hackerootBootOption.spawnIndex), + self.hackerootBootOption, + ) diff --git a/fast64_internal/oot/new_exporter/file.py b/fast64_internal/oot/new_exporter/file.py new file mode 100644 index 000000000..2d19f9937 --- /dev/null +++ b/fast64_internal/oot/new_exporter/file.py @@ -0,0 +1,35 @@ +import os +import re + +from dataclasses import dataclass +from typing import TYPE_CHECKING +from ..oot_utility import ExportInfo, getSceneDirFromLevelName +from .spec import Spec +from .scene_table import SceneTable + +if TYPE_CHECKING: + from .exporter import OOTSceneExport + + +@dataclass +class Files: + exporter: "OOTSceneExport" + + def modifySceneFiles(self): + if self.exporter.exportInfo.customSubPath is not None: + sceneDir = self.exporter.exportInfo.customSubPath + self.exporter.exportInfo.name + else: + sceneDir = getSceneDirFromLevelName(self.exporter.sceneName) + + scenePath = os.path.join(self.exporter.exportInfo.exportPath, sceneDir) + for filename in os.listdir(scenePath): + filepath = os.path.join(scenePath, filename) + if os.path.isfile(filepath): + match = re.match(self.exporter.scene.name + "\_room\_(\d+)\.[ch]", filename) + if match is not None and int(match.group(1)) >= len(self.exporter.scene.roomList): + os.remove(filepath) + + def editFiles(self): + self.modifySceneFiles() + Spec().editSpec(self.exporter) + SceneTable().editSceneTable(self.exporter) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 84bb1481c..63c10b132 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -226,7 +226,7 @@ def getRoomMainC(self): roomC.append(curHeader.actors.getActorListC()) return roomC - + def getRoomShapeModelC(self, textureSettings: TextureExportSettings): roomModel = CData() diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room_shape.py index 56f35a46b..fed658348 100644 --- a/fast64_internal/oot/new_exporter/room_shape.py +++ b/fast64_internal/oot/new_exporter/room_shape.py @@ -205,7 +205,6 @@ def getName(self): if self.multi is not None and self.multiImg is not None: return self.multi.name - def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): dlData = CData() @@ -224,7 +223,7 @@ def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: Texture ) return dlData - + def getRoomShapeC(self): shapeData = CData() diff --git a/fast64_internal/oot/new_exporter/scene_table.py b/fast64_internal/oot/new_exporter/scene_table.py new file mode 100644 index 000000000..fa9500e5a --- /dev/null +++ b/fast64_internal/oot/new_exporter/scene_table.py @@ -0,0 +1,288 @@ +import os +import bpy + +from typing import TYPE_CHECKING +from ...utility import PluginError, writeFile +from ..oot_constants import ootEnumSceneID, ootSceneNameToID +from ..oot_utility import ExportInfo + +if TYPE_CHECKING: + from .exporter import OOTSceneExport + + +class SceneTable: + def getSceneNameSettings(self, isExport: bool): + if isExport: + return bpy.context.scene.ootSceneExportSettings.option + else: + return bpy.context.scene.ootSceneRemoveSettings.option + + def getHackerOoTCheck(self, line: str): + return ( + line != "\n" + and '#include "config.h"\n' not in line + and "#ifdef INCLUDE_TEST_SCENES" not in line + and "#endif" not in line + and not line.startswith("// ") + ) + + def getSceneTable(self, exportPath: str): + """Read and remove unwanted stuff from ``scene_table.h``""" + dataList = [] + sceneNames = [] + fileHeader = "" + + # read the scene table + try: + with open(os.path.join(exportPath, "include/tables/scene_table.h")) as fileData: + # keep the relevant data and do some formatting + for i, line in enumerate(fileData): + # remove empty lines from the file + if not line.strip(): + continue + + if not bpy.context.scene.fast64.oot.hackerFeaturesEnabled or self.getHackerOoTCheck(line): + if not ( + # Detects the multiline comment at the top of the file: + (line.startswith("/**") or line.startswith(" *")) + # Detects single line comments: + # (meant to detect the built-in single-line comments + # "// Debug-only scenes" and "// Added scenes") + or line.startswith("//") + ): + dataList.append(line[(line.find("(") + 1) :].rstrip(")\n").replace(" ", "").split(",")) + else: + # Only keep comments before the data (as indicated by dataList being empty). + # This prevents duplicating the built-in single-line comments to the header. + # It also means other handwritten single-line comments are removed from the file. + if not dataList: + fileHeader += line + if line.startswith("/* 0x"): + startIndex = line.find("SCENE_") + sceneNames.append(line[startIndex : line.find(",", startIndex)]) + except FileNotFoundError: + raise PluginError("ERROR: Can't find scene_table.h!") + + # return the parsed data, the header comment and the comment mentionning debug scenes + return dataList, fileHeader, sceneNames + + def getSceneIndex(self, sceneNameList: list[str], sceneName: str): + """Returns the index (int) of the chosen scene, returns None if ``Custom`` is chosen""" + if sceneName == "Custom": + return None + + if sceneNameList is not None: + for i in range(len(sceneNameList)): + if sceneNameList[i] == sceneName: + return i + + # intended return value to check if the chosen scene was removed + return None + + def getOriginalIndex(self, sceneName): + """ + Returns the index of a specific scene defined by which one the user chose + or by the ``sceneName`` parameter if it's not set to ``None`` + """ + i = 0 + + if sceneName != "Custom": + for elem in ootEnumSceneID: + if elem[0] == sceneName: + # returns i - 1 because the first entry is the ``Custom`` option + return i - 1 + i += 1 + + raise PluginError("ERROR: Scene Index not found!") + + def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: str, index: int, mode: str): + """Returns the index to know where to insert data""" + # special case where the scene is "Inside the Great Deku Tree" + # since it's the first scene simply return 0 + if sceneName == "SCENE_DEKU_TREE": + return 0 + + # if index is None this means this is looking for ``original_scene_index - 1`` + # else, this means the table is shifted + if index is None: + currentIndex = self.getOriginalIndex(sceneName) + else: + currentIndex = index + + for i in range(len(sceneNames)): + if sceneNames[i] == ootEnumSceneID[currentIndex][0]: + # return an index to insert new data + if mode == "INSERT": + return i + 1 + # return an index to insert a comment + elif mode == "EXPORT": + return ( + i if not sceneName in sceneNames and sceneName != self.getSceneNameSettings(isExport) else i + 1 + ) + # same but don't check for chosen scene + elif mode == "REMOVE": + return i if not sceneName in sceneNames else i + 1 + else: + raise NotImplementedError + + # if the index hasn't been found yet, do it again but decrement the index + return self.getInsertionIndex(isExport, sceneNames, sceneName, currentIndex - 1, mode) + + def getSceneParams(self, exporter: "OOTSceneExport", exportInfo: ExportInfo, sceneNames: list[str]): + """Returns the parameters that needs to be set in ``DEFINE_SCENE()``""" + # in order to replace the values of ``unk10``, ``unk12`` and basically every parameters from ``DEFINE_SCENE``, + # you just have to make it return something other than None, not necessarily a string + sceneIndex = self.getSceneIndex(sceneNames, self.getSceneNameSettings(exporter is not None)) + sceneName = sceneTitle = sceneID = sceneUnk10 = sceneUnk12 = None + name = exporter.sceneName if exporter is not None else exportInfo.name + + # if the index is None then this is a custom scene + if sceneIndex is None and exporter is not None: + sceneName = name.lower() + "_scene" + sceneTitle = "none" + sceneID = ootSceneNameToID.get(name, f"SCENE_{name.upper()}") + sceneUnk10 = sceneUnk12 = 0 + + return sceneName, sceneTitle, sceneID, sceneUnk10, sceneUnk12, sceneIndex + + def sceneTableToC(self, data, header: str, sceneNames: list[str], isExport: bool): + """Converts the Scene Table to C code""" + # start the data with the header comment explaining the format of the file + fileData = header + + # determine if this function is called by 'Remove Scene' or 'Export Scene' + mode = "EXPORT" if isExport else "REMOVE" + + # get the index of the last non-debug scene + lastNonDebugSceneIdx = self.getInsertionIndex(isExport, sceneNames, "SCENE_OUTSIDE_GANONS_CASTLE", None, mode) + lastSceneIdx = self.getInsertionIndex(isExport, sceneNames, "SCENE_TESTROOM", None, mode) + + # add the actual lines with the same formatting + for i in range(len(data)): + # adds the "// Debug-only scenes" + # if both lastScene indexes are the same values this means there's no debug scene + if ((i - 1) == lastNonDebugSceneIdx) and (lastSceneIdx != lastNonDebugSceneIdx): + fileData += "// Debug-only scenes\n" + + # add a comment to show when it's new scenes + if (i - 1) == lastSceneIdx: + fileData += "// Added scenes\n" + + fileData += f"/* 0x{i:02X} */ DEFINE_SCENE(" + fileData += ", ".join(str(d) for d in data[i]) + + fileData += ")\n" + + # return the string containing the file data to write + return fileData + + def getDrawConfig(self, sceneName: str): + """Read draw config from scene table""" + fileData, header, sceneNames = self.getSceneTable(bpy.path.abspath(bpy.context.scene.ootDecompPath)) + + for sceneEntry in fileData: + if sceneEntry[0] == f"{sceneName}_scene": + return sceneEntry[3] + + raise PluginError(f"Scene name {sceneName} not found in scene table.") + + def addHackerOoTData(self, fileData: str): + """Reads the file and adds HackerOoT's modifications to the scene table file""" + newFileData = ['#include "config.h"\n\n'] + + for line in fileData.splitlines(): + if "// Debug-only scenes" in line: + newFileData.append("\n#ifdef INCLUDE_TEST_SCENES\n") + + if "// Added scenes" in line: + newFileData.append("#endif\n\n") + + newFileData.append(f"{line}\n") + + if not "// Added scenes" in fileData: + newFileData.append("#endif\n") + + return "".join(newFileData) + + def editSceneTable(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): + """Edit the scene table with the new data""" + isExport = exporter is not None + if exportInfo is None: + exportInfo = exporter.exportInfo + + exportPath = exportInfo.exportPath + # the list ``sceneNames`` needs to be synced with ``fileData`` + fileData, header, sceneNames = self.getSceneTable(exportPath) + sceneName, sceneTitle, sceneID, sceneUnk10, sceneUnk12, sceneIndex = self.getSceneParams( + exporter, exportInfo, sceneNames + ) + + if isExport: + sceneDrawConfig = exporter.scene.mainHeader.infos.drawConfig + else: + sceneDrawConfig = None + + # ``DEFINE_SCENE()`` parameters + sceneParams = [sceneName, sceneTitle, sceneID, sceneDrawConfig, sceneUnk10, sceneUnk12] + + # check if it's a custom scene name + # sceneIndex can be None and ootSceneOption not "Custom", + # that means the selected scene has been removed from the table + # however if the scene variable is not None + # set it to "INSERT" because we need to insert the scene in the right place + if sceneIndex is None and self.getSceneNameSettings(isExport) == "Custom": + mode = "CUSTOM" + elif sceneIndex is None and isExport: + mode = "INSERT" + elif sceneIndex is not None: + mode = "NORMAL" + else: + mode = None + + if mode is not None: + # if so, check if the custom scene already exists in the data + # if it already exists set mode to NORMAL to consider it like a normal scene + if mode == "CUSTOM": + exportName = exportInfo.name.lower() + for i in range(len(fileData)): + if fileData[i][0] == exportName + "_scene": + sceneIndex = i + mode = "NORMAL" + break + else: + exportName = exportInfo.name + + # edit the current data or append new one if we are in a ``Custom`` context + if mode == "NORMAL": + for i in range(6): + if sceneParams[i] is not None and fileData[sceneIndex][i] != sceneParams[i]: + fileData[sceneIndex][i] = sceneParams[i] + elif mode == "CUSTOM": + sceneNames.append(sceneParams[2]) + fileData.append(sceneParams) + sceneIndex = len(fileData) - 1 + elif mode == "INSERT": + # if this the user chose a vanilla scene, removed it and want to export + # insert the data in the normal location + # shifted index = vanilla index - (vanilla last scene index - new last scene index) + index = self.getInsertionIndex(isExport, sceneNames, sceneID, None, mode) + sceneNames.insert(index, sceneParams[2]) + fileData.insert(index, sceneParams) + + # remove the scene data if scene is None (`Remove Scene` button) + if not isExport: + if sceneIndex is not None: + sceneNames.pop(sceneIndex) + fileData.pop(sceneIndex) + else: + raise PluginError("ERROR: Scene not found in ``scene_table.h``!") + + # get the new file data + newFileData = self.sceneTableToC(fileData, header, sceneNames, isExport) + + # apply HackerOoT changes if needed + if bpy.context.scene.fast64.oot.hackerFeaturesEnabled: + newFileData = self.addHackerOoTData(newFileData) + + # write the file with the final data + writeFile(os.path.join(exportPath, "include/tables/scene_table.h"), newFileData) diff --git a/fast64_internal/oot/new_exporter/spec.py b/fast64_internal/oot/new_exporter/spec.py new file mode 100644 index 000000000..98788b0a9 --- /dev/null +++ b/fast64_internal/oot/new_exporter/spec.py @@ -0,0 +1,139 @@ +import os +import re + +from typing import TYPE_CHECKING +from ...utility import readFile, writeFile, indent +from ..oot_utility import ExportInfo, getSceneDirFromLevelName + +if TYPE_CHECKING: + from .exporter import OOTSceneExport + + +class Spec: + def getSceneSpecEntries(self, segmentDefinition: list[str], sceneName: str): + """Returns the existing spec entries for the selected scene""" + entries = [] + matchText = rf'\s*name\s*"{sceneName}\_' + + for entry in segmentDefinition: + if re.match(matchText + 'scene"', entry) or re.match(matchText + 'room\_\d+"', entry): + entries.append(entry) + + return entries + + def getSpecEntries(self, fileData: str): + """Returns the existing spec entries for the whole file""" + entries = [] + compressFlag = "" + + for match in re.finditer("beginseg(((?!endseg).)*)endseg", fileData, re.DOTALL): + segData = match.group(1) + entries.append(segData) + + # avoid deleting compress flag if the user is using it + # (defined by whether it is present at least once in spec or not) + if "compress" in segData: + compressFlag = indent + "compress\n" + + includes = [] + for match in re.finditer("(#include.*)", fileData): + includes.append(match.group(0)) + + return entries, compressFlag, includes + + def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): + """Adds or removes entries for the selected scene""" + isExport = exporter is not None + if exportInfo is None: + exportInfo = exporter.exportInfo + + exportPath = exportInfo.exportPath + sceneName = exporter.sceneName if isExport else exportInfo.name + fileData = readFile(os.path.join(exportPath, "spec")) + + specEntries, compressFlag, includes = self.getSpecEntries(fileData) + sceneSpecEntries = self.getSceneSpecEntries(specEntries, sceneName) + + if len(sceneSpecEntries) > 0: + firstIndex = specEntries.index(sceneSpecEntries[0]) + + # remove the entries of the selected scene + for entry in sceneSpecEntries: + specEntries.remove(entry) + else: + firstIndex = len(specEntries) + + # Add the spec data for the exported scene + if isExport: + if exportInfo.customSubPath is not None: + includeDir = f"build/{exportInfo.customSubPath + sceneName}" + else: + includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" + + sceneName = exporter.scene.name + if exporter.singleFileExport: + specEntries.insert( + firstIndex, + ("\n" + indent + f'name "{sceneName}"\n') + + compressFlag + + (indent + "romalign 0x1000\n") + + (indent + f'include "{includeDir}/{sceneName}.o"\n') + + (indent + "number 2\n"), + ) + + firstIndex += 1 + + for room in exporter.roomList.values(): + specEntries.insert( + firstIndex, + ("\n" + indent + f'name "{room.name}"\n') + + compressFlag + + (indent + "romalign 0x1000\n") + + (indent + f'include "{includeDir}/{room.name}.o"\n') + + (indent + "number 3\n"), + ) + + firstIndex += 1 + else: + sceneSegInclude = ( + ("\n" + indent + f'name "{sceneName}"\n') + + compressFlag + + (indent + "romalign 0x1000\n") + + (indent + f'include "{includeDir}/{sceneName}_main.o"\n') + + (indent + f'include "{includeDir}/{sceneName}_col.o"\n') + ) + + if exporter.hasSceneTextures: + sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_tex.o"\n' + + if exporter.hasCutscenes: + for i in range(len(exporter.sceneData.sceneCutscenes)): + sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_cs_{i}.o"\n' + + sceneSegInclude += indent + "number 2\n" + specEntries.insert(firstIndex, sceneSegInclude) + firstIndex += 1 + + for room in exporter.roomList.values(): + specEntries.insert( + firstIndex, + ("\n" + indent + f'name "{room.name}"\n') + + compressFlag + + (indent + "romalign 0x1000\n") + + (indent + f'include "{includeDir}/{room.name}_main.o"\n') + + (indent + f'include "{includeDir}/{room.name}_model_info.o"\n') + + (indent + f'include "{includeDir}/{room.name}_model.o"\n') + + (indent + "number 3\n"), + ) + + firstIndex += 1 + + # Write the file data + newFileData = ( + "/*\n * ROM spec file\n */\n\n" + + ("\n".join(includes) + "\n\n" if len(includes) > 0 else "") + + "\n".join("beginseg" + entry + "endseg\n" for entry in specEntries) + ) + + if newFileData != fileData: + writeFile(os.path.join(exportPath, "spec"), newFileData) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 815f4d4f6..ce52cc21b 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -208,7 +208,7 @@ def __init__(self, isCustomExport, exportPath, customSubPath, name): self.isCustomExportPath = isCustomExport self.exportPath = exportPath self.customSubPath = customSubPath - self.name = name + self.name: str = name class OOTObjectCategorizer: diff --git a/fast64_internal/oot/scene/exporter/to_c/__init__.py b/fast64_internal/oot/scene/exporter/to_c/__init__.py index e9f7106ad..f0bb656a9 100644 --- a/fast64_internal/oot/scene/exporter/to_c/__init__.py +++ b/fast64_internal/oot/scene/exporter/to_c/__init__.py @@ -1,5 +1,5 @@ from .scene import getIncludes, getSceneC -from .scene_table_c import modifySceneTable, getDrawConfig +from .scene_table_c import modifySceneTable, getDrawConfig, getSceneTable from .spec import editSpecFile from .scene_folder import modifySceneFiles, deleteSceneFiles from .scene_bootup import setBootupScene, clearBootupScene From 87a0c08a5eb9083498d3c4430698da76394323d1 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:37:29 +0200 Subject: [PATCH 19/98] final "fixes" (?) --- .../oot/new_exporter/collision_classes.py | 5 +++-- fast64_internal/oot/new_exporter/commands.py | 4 +++- .../oot/new_exporter/common/collision.py | 10 +++++----- fast64_internal/oot/new_exporter/exporter.py | 14 ++++++++------ fast64_internal/oot/new_exporter/scene.py | 2 +- fast64_internal/oot/scene/operators.py | 2 +- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index d322c05aa..3df5f4ae3 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from mathutils import Vector from ...utility import PluginError, indent @@ -9,7 +10,7 @@ class CollisionPoly: ignoreActor: bool ignoreProjectile: bool enableConveyor: bool - normal: tuple[int, int, int] + normal: Vector dist: int type: int = None @@ -38,7 +39,7 @@ def getEntryC(self): f"0x{self.getFlags_vIB():04X}", f"0x{self.getVIC():04X}", ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), - f"0x{self.dist:04X}", + f"{self.dist:6}", ) ) + " }," diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index f21f06b25..6fcb68fab 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -151,7 +151,6 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he listName = f"SceneCmd {curHeader.name}" getCmdFunc1List = [ - self.getExitListCmd, self.getSpawnActorListCmd, ] @@ -165,6 +164,9 @@ def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", he self.getSpawnListCmd, ] + if len(curHeader.exits.exitList) > 0: + getCmdFunc1List.append(self.getExitListCmd) + if len(curHeader.actors.transitionActorList) > 0: getCmdActorList.insert(0, self.getTransActorListCmd) diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index c071f90e1..a995b9886 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -64,9 +64,9 @@ def getColSurfaceVtxDataFromMeshObj(self): colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty planePoint = self.transform @ meshObj.data.vertices[face.vertices[0]].co - (x1, y1, z1) = self.roundPosition(planePoint) - (x2, y2, z2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) - (x3, y3, z3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) + (x1, z1, y1) = self.roundPosition(planePoint) + (x2, z2, y2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, z3, y3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) self.updateBounds((x1, y1, z1), bounds) self.updateBounds((x2, y2, z2), bounds) @@ -79,7 +79,7 @@ def getColSurfaceVtxDataFromMeshObj(self): distance = convertIntTo2sComplement(distance, 2, True) indices: list[int] = [] - for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: + for vertex in [(x1, y1, -z1), (x2, y2, -z2), (x3, y3, -z3)]: index = self.getVertIndex(vertex, vertexList) if index is None: vertexList.append(Vertex(vertex)) @@ -140,7 +140,7 @@ def getColSurfaceVtxDataFromMeshObj(self): colProp.ignoreActorCollision, colProp.ignoreProjectileCollision, useConveyor, - tuple(normal), + Vector((normal[0], normal[2], normal[1])), distance, i, ) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 3921b5dc9..1b6aaeed7 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -221,13 +221,15 @@ def getNewSceneFromEmptyObject(self): sceneData = None try: sceneData = self.getNewScene() - self.hasCutscenes = sceneData.mainHeader.cutscene.writeCutscene - if not self.hasCutscenes: - for cs in sceneData.altHeader.cutscenes: - if cs.cutscene.writeCutscene: - self.hasCutscenes = True - break + if sceneData.mainHeader.cutscene is not None: + self.hasCutscenes = sceneData.mainHeader.cutscene.writeCutscene + + if not self.hasCutscenes: + for cs in sceneData.altHeader.cutscenes: + if cs.cutscene.writeCutscene: + self.hasCutscenes = True + break ootCleanupScene(originalSceneObj, allObjs) except Exception as e: diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index ef223e64a..1aa46e82c 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -91,7 +91,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int headerProp.csWriteObject, headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, [csObj for csObj in headerProp.extraCutscenes], - ), + ) if headerProp.writeCutscene else None, OOTSceneHeaderExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), OOTSceneHeaderActors( f"{headerName}_entranceList", diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 5fb9e0dfc..f3b72540e 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -5,7 +5,7 @@ from bpy.utils import register_class, unregister_class from bpy.ops import object from mathutils import Matrix, Vector -from ...f3d.f3d_gbi import TextureExportSettings +from ...f3d.f3d_gbi import TextureExportSettings, DLFormat from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader from ..oot_utility import ExportInfo, sceneNameFromID, getEnumName from ..oot_level_writer import ootExportSceneToC From 3d28d0fca020497c059747ac8446c7525246029e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 26 Sep 2023 20:28:19 +0200 Subject: [PATCH 20/98] improvements --- fast64_internal/oot/new_exporter/exporter.py | 41 +++++++-------- fast64_internal/oot/new_exporter/room.py | 1 - fast64_internal/oot/new_exporter/scene.py | 2 +- .../oot/new_exporter/scene_header.py | 20 ++++++- fast64_internal/oot/scene/operators.py | 52 ++++++++++--------- fast64_internal/oot/scene/properties.py | 3 ++ 6 files changed, 68 insertions(+), 51 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 1b6aaeed7..46fda3e65 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -80,8 +80,7 @@ class OOTSceneExport: hasSceneTextures: bool = False def getNewRoomList(self, scene: OOTScene): - processedRooms = [] - roomList: list[OOTRoom] = [] + roomDict: dict[int, OOTRoom] = {} roomObjs: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" ] @@ -93,8 +92,12 @@ def getNewRoomList(self, scene: OOTScene): altProp = roomObj.ootAlternateRoomHeaders roomHeader = roomObj.ootRoomHeader roomIndex = roomHeader.roomIndex + + if roomIndex in roomDict: + raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") + roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" - roomData = OOTRoom( + roomDict[roomIndex] = OOTRoom( self.sceneObj, self.transform, roomIndex, @@ -116,10 +119,10 @@ def getNewRoomList(self, scene: OOTScene): c = Common(self.sceneObj, self.transform) pos, _, scale, _ = c.getConvertedTransform(self.transform, self.sceneObj, roomObj, True) cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) - DLGroup = roomData.mesh.addMeshGroup(cullGroup).DLGroup + DLGroup = roomDict[roomIndex].mesh.addMeshGroup(cullGroup).DLGroup boundingBox = BoundingBox() ootProcessMesh( - roomData.mesh, + roomDict[roomIndex].mesh, DLGroup, self.sceneObj, roomObj, @@ -133,44 +136,38 @@ def getNewRoomList(self, scene: OOTScene): cullGroup.position = centroid cullGroup.cullDepth = radius - roomData.mesh.terminateDLs() - roomData.mesh.removeUnusedEntries() + roomDict[roomIndex].mesh.terminateDLs() + roomDict[roomIndex].mesh.removeUnusedEntries() # Other if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomHeader.bgImageList) < 1: raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(processedRooms) > 1: + if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomDict) > 1: raise PluginError(f'Room shape "Image" can only have one room in the scene.') - roomData.roomShape = roomData.getNewRoomShape(roomHeader, self.sceneName) - altHeaderData = OOTRoomAlternateHeader(f"{roomData.name}_alternateHeaders") - roomData.mainHeader = roomData.getNewRoomHeader(roomHeader) + roomDict[roomIndex].roomShape = roomDict[roomIndex].getNewRoomShape(roomHeader, self.sceneName) + altHeaderData = OOTRoomAlternateHeader(f"{roomDict[roomIndex].name}_alternateHeaders") + roomDict[roomIndex].mainHeader = roomDict[roomIndex].getNewRoomHeader(roomHeader) hasAltHeader = False for i, header in enumerate(altHeaderList, 1): altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: hasAltHeader = True - setattr(altHeaderData, header, roomData.getNewRoomHeader(altP, i)) + setattr(altHeaderData, header, roomDict[roomIndex].getNewRoomHeader(altP, i)) altHeaderData.cutscenes = [ - roomData.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + roomDict[roomIndex].getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] if len(altHeaderData.cutscenes) > 0: hasAltHeader = True - roomData.altHeader = altHeaderData if hasAltHeader else None - - if roomIndex in processedRooms: - raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") - - addMissingObjectsToAllRoomHeadersNew(roomObj, roomData, ootData) - processedRooms.append(roomIndex) - roomList.append(roomData) + roomDict[roomIndex].altHeader = altHeaderData if hasAltHeader else None + addMissingObjectsToAllRoomHeadersNew(roomObj, roomDict[roomIndex], ootData) - return roomList + return [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] def getNewScene(self): altProp = self.sceneObj.ootAlternateSceneHeaders diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 63c10b132..a571a9d40 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -13,7 +13,6 @@ RoomShape, RoomShapeDLists, RoomShapeDListsEntry, - RoomShapeImageBase, RoomShapeImageMulti, RoomShapeImageMultiBg, RoomShapeImageMultiBgEntry, diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 1aa46e82c..40449a031 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -85,7 +85,7 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getEnvLightSettingsListFromProps(headerProp, lightMode), ), OOTSceneHeaderCutscene( - headerProp.csWriteObject.name.removeprefix("Cutscene."), + headerIndex, headerProp.csWriteType, headerProp.writeCutscene, headerProp.csWriteObject, diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py index 521beeb2b..756cc3616 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from bpy.types import Object -from ...utility import CData, indent +from ...utility import PluginError, CData, indent from .common import TransitionActor, EntranceActor @@ -149,12 +149,28 @@ def getEnvLightSettingsC(self): @dataclass class OOTSceneHeaderCutscene: - name: str + headerIndex: int writeType: str writeCutscene: bool csObj: Object csWriteCustom: str extraCutscenes: list[Object] + name: str = None + + def __post_init__(self): + if self.headerIndex > 0 and len(self.extraCutscenes) > 0: + raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") + + if self.csObj is not None: + self.name = self.csObj.name.removeprefix("Cutscene.") + + if self.csObj.ootEmptyType != "Cutscene": + raise PluginError("ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type") + elif self.csObj.parent is not None: + raise PluginError("ERROR: Cutscene empty object should not be parented to anything") + else: + raise PluginError("ERROR: No object selected for cutscene reference") + def getCutsceneC(self): # will be implemented when PR #208 is merged diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index f3b72540e..483927957 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -176,31 +176,33 @@ def execute(self, context): bootOptions = context.scene.fast64.oot.bootupSceneOptions hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled - OOTSceneExport( - exportInfo, - obj, - exportInfo.name, - scaleValue, - Matrix.Diagonal(Vector((scaleValue, scaleValue, scaleValue))).to_4x4(), - bpy.context.scene.f3d_type, - bpy.context.scene.saveTextures, - bootOptions if hackerFeaturesEnabled else None, - settings.singleFile, - context.scene.isHWv1, - TextureExportSettings(False, context.scene.saveTextures, None, None), - ).export() - - # ootExportSceneToC( - # obj, - # finalTransform, - # context.scene.f3d_type, - # context.scene.isHWv1, - # levelName, - # DLFormat.Static, - # context.scene.saveTextures, - # exportInfo, - # bootOptions if hackerFeaturesEnabled else None, - # ) + + if settings.useNewExporter: + OOTSceneExport( + exportInfo, + obj, + exportInfo.name, + scaleValue, + Matrix.Diagonal(Vector((scaleValue, scaleValue, scaleValue))).to_4x4(), + bpy.context.scene.f3d_type, + bpy.context.scene.saveTextures, + bootOptions if hackerFeaturesEnabled else None, + settings.singleFile, + context.scene.isHWv1, + TextureExportSettings(False, context.scene.saveTextures, None, None), + ).export() + else: + ootExportSceneToC( + obj, + finalTransform, + context.scene.f3d_type, + context.scene.isHWv1, + levelName, + DLFormat.Static, + context.scene.saveTextures, + exportInfo, + bootOptions if hackerFeaturesEnabled else None, + ) self.report({"INFO"}, "Success!") diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index b81af4cc9..d195fec01 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -479,6 +479,8 @@ class OOTExportSceneSettingsProperty(PropertyGroup): ) option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE") + useNewExporter: BoolProperty(name="Use Experimental Exporter", default=True) + def draw_props(self, layout: UILayout): if self.customExport: prop_split(layout, self, "exportPath", "Directory") @@ -493,6 +495,7 @@ def draw_props(self, layout: UILayout): layout.prop(self, "singleFile") layout.prop(self, "customExport") + layout.prop(self, "useNewExporter") class OOTImportSceneSettingsProperty(PropertyGroup): From 85af15face6751cd569a8ab0dedee0fae8cec028 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 26 Sep 2023 20:39:13 +0200 Subject: [PATCH 21/98] format --- fast64_internal/oot/new_exporter/exporter.py | 3 ++- fast64_internal/oot/new_exporter/scene.py | 4 +++- fast64_internal/oot/new_exporter/scene_header.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 46fda3e65..de79041d5 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -158,7 +158,8 @@ def getNewRoomList(self, scene: OOTScene): setattr(altHeaderData, header, roomDict[roomIndex].getNewRoomHeader(altP, i)) altHeaderData.cutscenes = [ - roomDict[roomIndex].getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + roomDict[roomIndex].getNewRoomHeader(csHeader, i) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] if len(altHeaderData.cutscenes) > 0: diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 40449a031..feb67b909 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -91,7 +91,9 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int headerProp.csWriteObject, headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, [csObj for csObj in headerProp.extraCutscenes], - ) if headerProp.writeCutscene else None, + ) + if headerProp.writeCutscene + else None, OOTSceneHeaderExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), OOTSceneHeaderActors( f"{headerName}_entranceList", diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py index 756cc3616..c53f91095 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -170,7 +170,6 @@ def __post_init__(self): raise PluginError("ERROR: Cutscene empty object should not be parented to anything") else: raise PluginError("ERROR: No object selected for cutscene reference") - def getCutsceneC(self): # will be implemented when PR #208 is merged From c657e1833f9c36fec486df31e93fb679ca176650 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:02:56 +0200 Subject: [PATCH 22/98] fixed collision issues --- .../oot/new_exporter/common/collision.py | 35 ++++++++++++------- fast64_internal/oot/new_exporter/exporter.py | 16 ++++----- fast64_internal/oot/scene/operators.py | 4 +-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index a995b9886..f906674b2 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -1,8 +1,8 @@ import math from dataclasses import dataclass -from mathutils import Vector, Quaternion -from bpy.types import Object +from mathutils import Vector, Quaternion, Matrix +from bpy.types import Object, Mesh from bpy.ops import object from ....utility import PluginError, checkIdentityRotation from ...oot_utility import convertIntTo2sComplement @@ -43,19 +43,31 @@ def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): return i return None + def getMeshObjects( + self, parentObj: Object, transform: Matrix, matrixTable: dict[Object, Matrix] + ) -> dict[Object, Matrix]: + for obj in parentObj.children: + newTransform = transform @ obj.matrix_local + if obj.type == "MESH" and not obj.ignore_collision: + matrixTable[obj] = newTransform + self.getMeshObjects(obj, newTransform, matrixTable) + return matrixTable + def getColSurfaceVtxDataFromMeshObj(self): - meshObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "MESH"] object.select_all(action="DESELECT") self.sceneObj.select_set(True) + matrixTable: dict[Object, Matrix] = {} surfaceTypeData: dict[int, SurfaceType] = {} polyList: list[CollisionPoly] = [] vertexList: list[Vertex] = [] bounds = [] i = 0 - for meshObj in meshObjList: - if not meshObj.ignore_collision: + matrixTable = self.getMeshObjects(self.sceneObj, self.transform, matrixTable) + for meshObj, transform in matrixTable.items(): + # Note: ``isinstance``only used to get the proper type hints + if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): if len(meshObj.data.materials) == 0: raise PluginError(f"'{meshObj.name}' must have a material associated with it.") @@ -63,16 +75,15 @@ def getColSurfaceVtxDataFromMeshObj(self): for face in meshObj.data.loop_triangles: colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty - planePoint = self.transform @ meshObj.data.vertices[face.vertices[0]].co - (x1, z1, y1) = self.roundPosition(planePoint) - (x2, z2, y2) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[1]].co) - (x3, z3, y3) = self.roundPosition(self.transform @ meshObj.data.vertices[face.vertices[2]].co) - + planePoint = transform @ meshObj.data.vertices[face.vertices[0]].co + (x1, y1, z1) = self.roundPosition(planePoint) + (x2, y2, z2) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, y3, z3) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) self.updateBounds((x1, y1, z1), bounds) self.updateBounds((x2, y2, z2), bounds) self.updateBounds((x3, y3, z3), bounds) - normal = (self.transform.inverted().transposed() @ face.normal).normalized() + normal = (transform.inverted().transposed() @ face.normal).normalized() distance = int( round(-1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2])) ) @@ -140,7 +151,7 @@ def getColSurfaceVtxDataFromMeshObj(self): colProp.ignoreActorCollision, colProp.ignoreProjectileCollision, useConveyor, - Vector((normal[0], normal[2], normal[1])), + normal, distance, i, ) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index de79041d5..809e31810 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -58,7 +58,7 @@ class OOTSceneData: @dataclass class OOTSceneExport: exportInfo: ExportInfo - sceneObj: Object + originalSceneObj: Object sceneName: str ootBlenderScale: float transform: Matrix @@ -70,6 +70,7 @@ class OOTSceneExport: textureExportSettings: TextureExportSettings dlFormat: DLFormat = DLFormat.Static + sceneObj: Object = None scene: OOTScene = None path: str = None sceneBasePath: str = None @@ -202,15 +203,14 @@ def getNewSceneFromEmptyObject(self): """Returns the default scene header and adds the alternate/cutscene ones""" # init - originalSceneObj = self.sceneObj - if self.sceneObj.type != "EMPTY" or self.sceneObj.ootEmptyType != "Scene": - raise PluginError(f'{self.sceneObj.name} is not an empty with the "Scene" empty type.') + if self.originalSceneObj.type != "EMPTY" or self.originalSceneObj.ootEmptyType != "Scene": + raise PluginError(f'{self.originalSceneObj.name} is not an empty with the "Scene" empty type.') if bpy.context.scene.exportHiddenGeometry: hiddenState = unhideAllAndGetHiddenState(bpy.context.scene) # Don't remove ignore_render, as we want to reuse this for collision - self.sceneObj, allObjs = ootDuplicateHierarchy(self.sceneObj, None, True, OOTObjectCategorizer()) + self.sceneObj, allObjs = ootDuplicateHierarchy(self.originalSceneObj, None, True, OOTObjectCategorizer()) if bpy.context.scene.exportHiddenGeometry: restoreHiddenState(hiddenState) @@ -229,9 +229,9 @@ def getNewSceneFromEmptyObject(self): self.hasCutscenes = True break - ootCleanupScene(originalSceneObj, allObjs) + ootCleanupScene(self.originalSceneObj, allObjs) except Exception as e: - ootCleanupScene(originalSceneObj, allObjs) + ootCleanupScene(self.originalSceneObj, allObjs) raise Exception(str(e)) if sceneData is None: @@ -350,7 +350,7 @@ def writeScene(self): room.mesh.copyBgImages(self.path) def export(self): - checkObjectReference(self.sceneObj, "Scene object") + checkObjectReference(self.originalSceneObj, "Scene object") isCustomExport = self.exportInfo.isCustomExportPath exportPath = self.exportInfo.exportPath diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 483927957..ba88aad10 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -182,8 +182,8 @@ def execute(self, context): exportInfo, obj, exportInfo.name, - scaleValue, - Matrix.Diagonal(Vector((scaleValue, scaleValue, scaleValue))).to_4x4(), + context.scene.ootBlenderScale, + finalTransform, bpy.context.scene.f3d_type, bpy.context.scene.saveTextures, bootOptions if hackerFeaturesEnabled else None, From 0d662647974b430d2a043da07e542f11acef3c67 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:00:24 +0200 Subject: [PATCH 23/98] cam pos data fixes --- fast64_internal/oot/new_exporter/collision.py | 10 ++++--- .../oot/new_exporter/collision_classes.py | 29 ++++++++++++------- .../oot/new_exporter/common/collision.py | 3 +- .../oot/new_exporter/room_header.py | 5 ++-- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index d1c9f1673..ed0ecf6c4 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -93,8 +93,8 @@ def getDataArrayC(self): # .c posData.source = ( (listName + " = {\n") - + "".join(cam.getDataEntriesC() for cam in self.bgCamInfoList) - + "".join(crawlspace.getDataEntriesC() for crawlspace in self.crawlspacePosList) + + "".join(cam.getDataEntryC() for cam in self.bgCamInfoList) + + "".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) + "};\n\n" ) @@ -163,8 +163,10 @@ def getSceneCollisionC(self): wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: - colData.append(self.bgCamInfo.getDataArrayC()) - colData.append(self.bgCamInfo.getInfoArrayC()) + infoData = self.bgCamInfo.getInfoArrayC() + if "&" in infoData.source: + colData.append(self.bgCamInfo.getDataArrayC()) + colData.append(infoData) camPtrLine = f"{self.bgCamInfo.name}" if len(self.surfaceType.surfaceTypeList) > 0: diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index 3df5f4ae3..fb3af268f 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -39,7 +39,7 @@ def getEntryC(self): f"0x{self.getFlags_vIB():04X}", f"0x{self.getVIC():04X}", ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), - f"{self.dist:6}", + f"0x{self.dist:04X}", ) ) + " }," @@ -156,8 +156,8 @@ class CrawlspaceData: points: list[tuple[int, int, int]] = field(default_factory=list) arrayIndex: int = None - def getDataEntriesC(self): - return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) + def getDataEntryC(self): + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n\n" for point in self.points) def getInfoEntryC(self, posDataName: str): return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" @@ -168,19 +168,26 @@ class BgCamInfo: setting: str count: int arrayIndex: int - hasPosData: bool - bgCamFuncDataList: list[BgCamFuncData] + camData: BgCamFuncData + hasPosData: bool = False - def getDataEntriesC(self): + def __post_init__(self): + self.hasPosData = self.camData is not None + + def getDataEntryC(self): source = "" if self.hasPosData: - for camData in self.bgCamFuncDataList: - source += ( - (indent + "{ " + ", ".join(f"{p:6}" for p in camData.pos) + " },\n") - + (indent + "{ " + ", ".join(f"{r:6}" for r in camData.rot) + " },\n") - + (indent + "{ " + f"{camData.fov:6}, {camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + source += ( + (indent + "{ " + ", ".join(f"{p:6}" for p in self.camData.pos) + " },\n") + + (indent + "{ " + ", ".join(f"{r:6}" for r in self.camData.rot) + " },\n") + + ( + indent + + "{ " + + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + + " },\n\n" ) + ) return source diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index f906674b2..744b7d6d2 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -229,8 +229,7 @@ def getBgCamInfoDataFromObjects(self): setting, count, index, - camProp.hasPositionData, - [camPosData[i] for i in range(min(camPosData.keys()), len(camPosData))] if len(camPosData) > 0 else [], + camPosData[camProp.index] if camProp.hasPositionData else None, ) index += count diff --git a/fast64_internal/oot/new_exporter/room_header.py b/fast64_internal/oot/new_exporter/room_header.py index e5a97ec0e..3835ed0cd 100644 --- a/fast64_internal/oot/new_exporter/room_header.py +++ b/fast64_internal/oot/new_exporter/room_header.py @@ -77,7 +77,8 @@ def __post_init__(self): ] for obj in actorObjList: actorProp = obj.ootActorProperty - if not Common.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): + c = Common(self.sceneObj, self.transform) + if not c.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for @@ -86,7 +87,7 @@ def __post_init__(self): # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if # the current Actor has the ID `None` to avoid export issues. if actorProp.actorID != "None": - pos, rot, _, _ = Common.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = c.getConvertedTransform(self.transform, self.sceneObj, obj, True) actor = Actor() if actorProp.actorID == "Custom": From 7edf7921848684d9944d840533e25207f2ff9915 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:13:03 +0200 Subject: [PATCH 24/98] campos output improvements --- fast64_internal/oot/new_exporter/collision.py | 4 +-- .../oot/new_exporter/collision_classes.py | 25 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index ed0ecf6c4..914e65ed8 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -93,8 +93,8 @@ def getDataArrayC(self): # .c posData.source = ( (listName + " = {\n") - + "".join(cam.getDataEntryC() for cam in self.bgCamInfoList) - + "".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) + + "\n".join(cam.getDataEntryC() for cam in self.bgCamInfoList if cam.hasPosData) + + "\n".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) + "};\n\n" ) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index fb3af268f..adf22da31 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -157,7 +157,7 @@ class CrawlspaceData: arrayIndex: int = None def getDataEntryC(self): - return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n\n" for point in self.points) + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) def getInfoEntryC(self, posDataName: str): return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" @@ -175,21 +175,16 @@ def __post_init__(self): self.hasPosData = self.camData is not None def getDataEntryC(self): - source = "" - - if self.hasPosData: - source += ( - (indent + "{ " + ", ".join(f"{p:6}" for p in self.camData.pos) + " },\n") - + (indent + "{ " + ", ".join(f"{r:6}" for r in self.camData.rot) + " },\n") - + ( - indent - + "{ " - + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" - + " },\n\n" - ) + return ( + (indent + "{ " + ", ".join(f"{p:6}" for p in self.camData.pos) + " },\n") + + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.camData.rot) + " },\n") + + ( + indent + + "{ " + + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + + " },\n" ) - - return source + ) def getInfoEntryC(self, posDataName: str): ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" From c7d5803b5da5aaa07147fa8f33d940c9ed221265 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:53:05 +0200 Subject: [PATCH 25/98] fixed extra collision issues --- .../oot/new_exporter/common/collision.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index 744b7d6d2..8e91d01b8 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -90,7 +90,7 @@ def getColSurfaceVtxDataFromMeshObj(self): distance = convertIntTo2sComplement(distance, 2, True) indices: list[int] = [] - for vertex in [(x1, y1, -z1), (x2, y2, -z2), (x3, y3, -z3)]: + for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: index = self.getVertIndex(vertex, vertexList) if index is None: vertexList.append(Vertex(vertex)) @@ -123,26 +123,28 @@ def getColSurfaceVtxDataFromMeshObj(self): if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: indices[1], indices[2] = indices[2], indices[1] + surfaceIndex = i + face.material_index useConveyor = colProp.conveyorOption != "None" - surfaceTypeData[i] = SurfaceType( - colProp.cameraID, - colProp.exitID, - int(self.getPropValue(colProp, "floorSetting"), base=16), - 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorProperty"), base=16), - colProp.decreaseHeight, - colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), - colProp.lightingSetting, - int(colProp.echo, base=16), - colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, - int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, - colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, - ) + if not surfaceIndex in surfaceTypeData: + surfaceTypeData[surfaceIndex] = SurfaceType( + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorSetting"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorProperty"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + ) polyList.append( CollisionPoly( @@ -153,11 +155,12 @@ def getColSurfaceVtxDataFromMeshObj(self): useConveyor, normal, distance, - i, + surfaceIndex, ) ) i += 1 - return bounds, vertexList, polyList, [surfaceTypeData[i] for i in range(len(surfaceTypeData))] + surfaceList = [surfaceTypeData[i] for i in range(min(surfaceTypeData.keys()), len(surfaceTypeData))] + return bounds, vertexList, polyList, surfaceList def getBgCamFuncDataFromObjects(self, camObj: Object): camProp = camObj.ootCameraPositionProperty From dfed9425364320255da3b518f54b6f7d7290c92a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 17:11:25 +0200 Subject: [PATCH 26/98] format + docs part 1 --- .../oot/new_exporter/collision_classes.py | 7 +- .../oot/new_exporter/common/classes.py | 52 ++++++++++---- .../oot/new_exporter/common/collision.py | 68 +++++++++++-------- .../oot/new_exporter/common/scene.py | 38 ++++++++--- fast64_internal/oot/new_exporter/scene.py | 7 +- 5 files changed, 111 insertions(+), 61 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index adf22da31..e809a8a94 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -178,12 +178,7 @@ def getDataEntryC(self): return ( (indent + "{ " + ", ".join(f"{p:6}" for p in self.camData.pos) + " },\n") + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.camData.rot) + " },\n") - + ( - indent - + "{ " - + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" - + " },\n" - ) + + (indent + "{ " + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") ) def getInfoEntryC(self, posDataName: str): diff --git a/fast64_internal/oot/new_exporter/common/classes.py b/fast64_internal/oot/new_exporter/common/classes.py index a3e7c1c2c..bb734c92f 100644 --- a/fast64_internal/oot/new_exporter/common/classes.py +++ b/fast64_internal/oot/new_exporter/common/classes.py @@ -12,11 +12,15 @@ @dataclass class Common: + """This class hosts common data used across different sub-systems of this exporter""" + sceneObj: Object transform: Matrix roomIndex: int = None def getRoomObjectFromChild(self, childObj: Object) -> Object | None: + """Returns the room empty object from one of its child""" + # Note: temporary solution until PRs #243 & #255 are merged for obj in self.sceneObj.children_recursive: if obj.type == "EMPTY" and obj.ootEmptyType == "Room": @@ -26,6 +30,8 @@ def getRoomObjectFromChild(self, childObj: Object) -> Object | None: return None def validateCurveData(self, curveObj: Object): + """Performs safety checks related to curve objects""" + curveData = curveObj.data if curveObj.type != "CURVE" or curveData.splines[0].type != "NURBS": # Curve was likely not intended to be exported @@ -38,34 +44,39 @@ def validateCurveData(self, curveObj: Object): return True def roundPosition(self, position) -> tuple[int, int, int]: + """Returns the rounded position values""" + return (round(position[0]), round(position[1]), round(position[2])) def isCurrentHeaderValid(self, headerSettings: OOTActorHeaderProperty, headerIndex: int): + """Checks if the an alternate header can be used""" + preset = headerSettings.sceneSetupPreset if preset == "All Scene Setups" or (preset == "All Non-Cutscene Scene Setups" and headerIndex < 4): return True if preset == "Custom": - if headerSettings.childDayHeader and headerIndex == 0: - return True - if headerSettings.childNightHeader and headerIndex == 1: - return True - if headerSettings.adultDayHeader and headerIndex == 2: - return True - if headerSettings.adultNightHeader and headerIndex == 3: - return True + for i, header in enumerate(["childDay"] + altHeaderList): + if getattr(headerSettings, f"{header}Header") and i == headerIndex: + return True + + for csHeader in headerSettings.cutsceneHeaders: + if csHeader.headerIndex == headerIndex: + return True return False def getPropValue(self, data, propName: str): - """Returns ``data.propName`` or ``data.propNameCustom``""" + """Returns a property's value based on if the value is 'Custom'""" value = getattr(data, propName) return value if value != "Custom" else getattr(data, f"{propName}Custom") - def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, orientation): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + def getConvertedTransformWithOrientation( + self, transform: Matrix, sceneObj: Object, obj: Object, orientation: Quaternion | Matrix + ): + relativeTransform = transform @ sceneObj.matrix_world.inverted() @ obj.matrix_world blenderTranslation, blenderRotation, scale = relativeTransform.decompose() rotation = blenderRotation @ orientation convertedTranslation = ootConvertTranslation(blenderTranslation) @@ -73,24 +84,30 @@ def getConvertedTransformWithOrientation(self, transformMatrix, sceneObj, obj, o return convertedTranslation, convertedRotation, scale, rotation - def getConvertedTransform(self, transformMatrix, sceneObj, obj, handleOrientation): + def getConvertedTransform(self, transform: Matrix, sceneObj: Object, obj: Object, handleOrientation: bool): # Hacky solution to handle Z-up to Y-up conversion # We cannot apply rotation to empty, as that modifies scale if handleOrientation: orientation = Quaternion((1, 0, 0), radians(90.0)) else: orientation = Matrix.Identity(4) - return self.getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) + return self.getConvertedTransformWithOrientation(transform, sceneObj, obj, orientation) + + def getAltHeaderListCmd(self, altName: str): + """Returns the scene alternate header list command""" - def getAltHeaderListCmd(self, altName): return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" def getEndCmd(self): + """Returns the scene end command""" + return indent + "SCENE_CMD_END(),\n" @dataclass class Actor: + """Defines an Actor""" + name: str = None id: str = None pos: list[int] = field(default_factory=list) @@ -99,6 +116,7 @@ class Actor: def getActorEntry(self): """Returns a single actor entry""" + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" rotData = "{ " + self.rot + " }" @@ -116,6 +134,8 @@ def getActorEntry(self): @dataclass class TransitionActor(Actor): + """Defines a Transition Actor""" + dontTransition: bool = None roomFrom: int = None roomTo: int = None @@ -124,6 +144,7 @@ class TransitionActor(Actor): def getTransitionActorEntry(self): """Returns a single transition actor entry""" + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" @@ -141,9 +162,12 @@ def getTransitionActorEntry(self): @dataclass class EntranceActor(Actor): + """Defines an Entrance Actor""" + roomIndex: int = None spawnIndex: int = None def getSpawnEntry(self): """Returns a single spawn entry""" + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index 8e91d01b8..7f22357e9 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -22,7 +22,11 @@ @dataclass class CollisionCommon(Common): + """This class hosts different functions used to convert mesh data""" + def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): + """This is used to update the scene's boundaries""" + if len(bounds) == 0: bounds.append([position[0], position[1], position[2]]) bounds.append([position[0], position[1], position[2]]) @@ -37,6 +41,8 @@ def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, i maxBounds[i] = position[i] def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): + """Returns the index of a Vertex based on position data, returns None if no match found""" + for i in range(len(vertArray)): colVert = vertArray[i].pos if colVert == vert: @@ -46,6 +52,8 @@ def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): def getMeshObjects( self, parentObj: Object, transform: Matrix, matrixTable: dict[Object, Matrix] ) -> dict[Object, Matrix]: + """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" + for obj in parentObj.children: newTransform = transform @ obj.matrix_local if obj.type == "MESH" and not obj.ignore_collision: @@ -54,6 +62,9 @@ def getMeshObjects( return matrixTable def getColSurfaceVtxDataFromMeshObj(self): + """Returns collision data, surface types and vertex positions from mesh objects""" + # Ideally everything would be separated but this is complicated since it's all tied together + object.select_all(action="DESELECT") self.sceneObj.select_set(True) @@ -127,24 +138,24 @@ def getColSurfaceVtxDataFromMeshObj(self): useConveyor = colProp.conveyorOption != "None" if not surfaceIndex in surfaceTypeData: surfaceTypeData[surfaceIndex] = SurfaceType( - colProp.cameraID, - colProp.exitID, - int(self.getPropValue(colProp, "floorSetting"), base=16), - 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorProperty"), base=16), - colProp.decreaseHeight, - colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), - colProp.lightingSetting, - int(colProp.echo, base=16), - colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, - int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, - colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, - ) + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorSetting"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorProperty"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + ) polyList.append( CollisionPoly( @@ -163,7 +174,7 @@ def getColSurfaceVtxDataFromMeshObj(self): return bounds, vertexList, polyList, surfaceList def getBgCamFuncDataFromObjects(self, camObj: Object): - camProp = camObj.ootCameraPositionProperty + """Returns Camera data from a single camera object""" # Camera faces opposite direction pos, rot, _, _ = self.getConvertedTransformWithOrientation( @@ -178,10 +189,12 @@ def getBgCamFuncDataFromObjects(self, camObj: Object): pos, rot, round(fov), - camProp.bgImageOverrideIndex, + camObj.ootCameraPositionProperty.bgImageOverrideIndex, ) def getCrawlspaceDataFromObjects(self, startIndex: int): + """Returns a list of rawlspace data from every splines objects with the type 'Crawlspace'""" + crawlspaceList: list[CrawlspaceData] = [] crawlspaceObjList: list[Object] = [ obj @@ -201,10 +214,12 @@ def getCrawlspaceDataFromObjects(self, startIndex: int): index, ) ) - index += 6 + index += 6 # crawlspaces are using 6 entries in the data array return crawlspaceList def getBgCamInfoDataFromObjects(self): + """Returns a list of camera informations from camera objects""" + camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] camPosData: dict[int, BgCamFuncData] = {} camInfoData: dict[int, BgCamInfo] = {} @@ -219,7 +234,7 @@ def getBgCamInfoDataFromObjects(self): setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) if camProp.hasPositionData: - count = 3 + count = 3 # cameras are using 3 entries in the data array if camProp.index in camPosData: raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") camPosData[camProp.index] = self.getBgCamFuncDataFromObjects(camObj) @@ -241,6 +256,8 @@ def getBgCamInfoDataFromObjects(self): ) def getWaterBoxDataFromObjects(self): + """Returns a list of waterbox data from waterbox empty objects""" + waterboxObjList = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" ] @@ -266,10 +283,3 @@ def getWaterBoxDataFromObjects(self): ) return waterboxList - - def getCount(self, bgCamInfoList: list[BgCamInfo]): - count = 0 - for elem in bgCamInfoList: - if elem.count != 0: # 0 means no pos data - count += elem.count - return count diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py index c16b9730f..36ee3ef21 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -16,6 +16,8 @@ @dataclass class SceneCommon(CollisionCommon, SceneCommands): + """This class hosts various data and functions related to a scene file""" + name: str = None model: OOTModel = None headerIndex: int = None @@ -24,13 +26,16 @@ class SceneCommon(CollisionCommon, SceneCommands): roomList: list["OOTRoom"] = field(default_factory=list) def validateRoomIndices(self): + """Checks if there are multiple rooms with the same room index""" + for i, room in enumerate(self.roomList): if i != room.roomIndex: return False - return True def validateScene(self): + """Performs safety checks related to the scene data""" + if not len(self.roomList) > 0: raise PluginError("ERROR: This scene does not have any rooms!") @@ -38,9 +43,13 @@ def validateScene(self): raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") def hasAlternateHeaders(self): + """Checks if this scene is using alternate headers""" + return self.altHeader is not None def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: + """Returns the scene header based on the header index""" + if headerIndex == 0: return self.mainHeader @@ -55,10 +64,9 @@ def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: return None def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): - """Returns the exit list and performs safety checks""" + """Returns the exit list from the current scene header""" exitList: list[tuple[int, str]] = [] - for i, exitProp in enumerate(headerProp.exitList): if exitProp.exitIndex != "Custom": raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") @@ -68,6 +76,8 @@ def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): return exitList def getTransActorListFromProps(self): + """Returns the transition actor list based on empty objects with the type 'Transition Actor'""" + actorList: list[TransitionActor] = [] actorObjList: list[Object] = [ obj @@ -82,10 +92,10 @@ def getTransActorListFromProps(self): transActorProp = obj.ootTransitionActorProperty - if not self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex): - continue - - if transActorProp.actor.actorID != "None": + if ( + self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) + and transActorProp.actor.actorID != "None" + ): pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) transActor = TransitionActor() @@ -118,6 +128,8 @@ def getTransActorListFromProps(self): return actorList def getEntranceActorListFromProps(self): + """Returns the entrance actor list based on empty objects with the type 'Entrance'""" + actorList: list[EntranceActor] = [] actorObjList: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" @@ -128,10 +140,10 @@ def getEntranceActorListFromProps(self): raise PluginError("ERROR: Room Object not found!") entranceProp = obj.ootEntranceProperty - if not self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex): - continue - - if entranceProp.actor.actorID != "None": + if ( + self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) + and entranceProp.actor.actorID != "None" + ): pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) entranceActor = EntranceActor() @@ -153,6 +165,8 @@ def getEntranceActorListFromProps(self): return actorList def getPathListFromProps(self, listNameBase: str): + """Returns the pathway list from spline objects with the type 'Path'""" + pathList: list[Path] = [] pathObjList: list[Object] = [ obj @@ -172,6 +186,8 @@ def getPathListFromProps(self, listNameBase: str): return pathList def getEnvLightSettingsListFromProps(self, headerProp: OOTSceneHeaderProperty, lightMode: str): + """Returns the environment light settings list from the current scene header""" + lightList: list[OOTLightProperty] = [] lightSettings: list[EnvLightSettings] = [] diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index feb67b909..2920cfbf2 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -37,6 +37,11 @@ def getNewCollisionHeader(self): colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() bgCamInfoList = self.getBgCamInfoDataFromObjects() + startIndex = 0 + for elem in bgCamInfoList: + if elem.count != 0: # 0 means no pos data + startIndex += elem.count + return OOTSceneCollisionHeader( f"{self.name}_collisionHeader", colBounds[0], @@ -48,7 +53,7 @@ def getNewCollisionHeader(self): f"{self.name}_bgCamInfo", f"{self.name}_camPosData", bgCamInfoList, - self.getCrawlspaceDataFromObjects(self.getCount(bgCamInfoList)), + self.getCrawlspaceDataFromObjects(startIndex), ), CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), ) From 478c2ce97e32bab8982b2b6af22409d5ce337379 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 19:55:21 +0200 Subject: [PATCH 27/98] docs part 2 --- fast64_internal/oot/new_exporter/collision.py | 27 +++++- .../oot/new_exporter/collision_classes.py | 87 +++++++++++++++---- fast64_internal/oot/new_exporter/commands.py | 4 + 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 914e65ed8..268e3de98 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -13,6 +13,8 @@ @dataclass class CollisionHeaderVertices: + """This class defines the array of vertices""" + name: str vertexList: list[Vertex] @@ -33,6 +35,8 @@ def getC(self): @dataclass class CollisionHeaderCollisionPoly: + """This class defines the array of collision polys""" + name: str polyList: list[CollisionPoly] @@ -51,6 +55,8 @@ def getC(self): @dataclass class CollisionHeaderSurfaceType: + """This class defines the array of surface types""" + name: str surfaceTypeList: list[SurfaceType] @@ -71,6 +77,8 @@ def getC(self): @dataclass class CollisionHeaderBgCamInfo: + """This class defines the array of camera informations and the array of the associated data""" + name: str posDataName: str bgCamInfoList: list[BgCamInfo] @@ -84,6 +92,8 @@ def __post_init__(self): self.arrayIdx = self.bgCamInfoList[-1].arrayIndex + self.crawlspaceCount def getDataArrayC(self): + """Returns the camera data/crawlspace positions array""" + posData = CData() listName = f"Vec3s {self.posDataName}[]" @@ -93,7 +103,7 @@ def getDataArrayC(self): # .c posData.source = ( (listName + " = {\n") - + "\n".join(cam.getDataEntryC() for cam in self.bgCamInfoList if cam.hasPosData) + + "\n".join(cam.camData.getEntryC() for cam in self.bgCamInfoList if cam.hasPosData) + "\n".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) + "};\n\n" ) @@ -101,6 +111,8 @@ def getDataArrayC(self): return posData def getInfoArrayC(self): + """Returns the array containing the informations of each cameras""" + bgCamInfoData = CData() listName = f"BgCamInfo {self.name}[]" @@ -120,6 +132,8 @@ def getInfoArrayC(self): @dataclass class CollisionHeaderWaterBox: + """This class defines the array of waterboxes""" + name: str waterboxList: list[WaterBox] @@ -138,6 +152,8 @@ def getC(self): @dataclass class OOTSceneCollisionHeader: + """This class defines the collision header used by the scene""" + name: str minBounds: tuple[int, int, int] = None maxBounds: tuple[int, int, int] = None @@ -148,6 +164,8 @@ class OOTSceneCollisionHeader: waterbox: CollisionHeaderWaterBox = None def getSceneCollisionC(self): + """Returns the collision header for the selected scene""" + headerData = CData() colData = CData() varName = f"CollisionHeader {self.name}" @@ -158,10 +176,12 @@ def getSceneCollisionC(self): camPtrLine = "NULL" wBoxPtrLine = "0, NULL" + # Add waterbox data if necessary if len(self.waterbox.waterboxList) > 0: colData.append(self.waterbox.getC()) wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" + # Add camera data if necessary if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: infoData = self.bgCamInfo.getInfoArrayC() if "&" in infoData.source: @@ -169,18 +189,23 @@ def getSceneCollisionC(self): colData.append(infoData) camPtrLine = f"{self.bgCamInfo.name}" + # Add surface types if len(self.surfaceType.surfaceTypeList) > 0: colData.append(self.surfaceType.getC()) surfacePtrLine = f"{self.surfaceType.name}" + # Add vertex data if len(self.vertices.vertexList) > 0: colData.append(self.vertices.getC()) vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" + # Add collision poly data if len(self.collisionPoly.polyList) > 0: colData.append(self.collisionPoly.getC()) colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" + # build the C data of the collision header + # .h headerData.header = f"extern {varName};\n" diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index e809a8a94..ce995b61b 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -5,6 +5,8 @@ @dataclass class CollisionPoly: + """This class defines a single collision poly""" + indices: list[int] ignoreCamera: bool ignoreActor: bool @@ -13,23 +15,43 @@ class CollisionPoly: normal: Vector dist: int type: int = None + useMacros: bool = True def getFlags_vIA(self): - vertPart = self.indices[0] & 0x1FFF - colPart = (1 if self.ignoreCamera else 0) + (2 if self.ignoreActor else 0) + (4 if self.ignoreProjectile else 0) - return vertPart | (colPart << 13) + """Returns the value of ``flags_vIA``""" + + vtxId = self.indices[0] & 0x1FFF + if not (self.ignoreProjectile and self.ignoreActor and self.ignoreCamera): + flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" + else: + flag1 = ("COLPOLY_IGNORE_PROJECTILES" if self.useMacros else "(1 << 2)") if self.ignoreProjectile else "" + flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreProjectile else "" + flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreProjectile else "" + flags = "(" + " | ".join([flag1, flag2, flag3]) + ")" + + return f"COLPOLY_VTX({vtxId}, ({flags}))" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" def getFlags_vIB(self): - vertPart = self.indices[1] & 0x1FFF - conveyorPart = 1 if self.enableConveyor else 0 - return vertPart | (conveyorPart << 13) + """Returns the value of ``flags_vIB``""" + + vtxId = self.indices[1] & 0x1FFF + if self.enableConveyor: + flags = "COLPOLY_IS_FLOOR_CONVEYOR" if self.useMacros else "(1 << 0)" + else: + flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" + return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" def getVIC(self): - return self.indices[2] & 0x1FFF + """Returns the value of ``vIC``""" + + vtxId = self.indices[2] & 0x1FFF + return f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF" def getEntryC(self): + """Returns an entry for the collision poly array""" + if self.type is None: - raise PluginError("ERROR: Type unset!") + raise PluginError("ERROR: Surface Type missing!") return ( (indent + "{ ") + ", ".join( @@ -48,6 +70,8 @@ def getEntryC(self): @dataclass class SurfaceType: + """This class defines a single surface type""" + bgCamIndex: int exitIndex: int floorType: int @@ -83,6 +107,8 @@ def __post_init__(self): self.isWallDamageC = "1" if self.isWallDamage else "0" def getSurfaceType0(self): + """Returns surface type properties for the first element of the data array""" + if self.useMacros: return ( ("SURFACETYPE0(") @@ -110,6 +136,8 @@ def getSurfaceType0(self): ) def getSurfaceType1(self): + """Returns surface type properties for the second element of the data array""" + if self.useMacros: return ( ("SURFACETYPE1(") @@ -137,6 +165,8 @@ def getSurfaceType1(self): ) def getEntryC(self): + """Returns an entry for the surface type array""" + if self.useMacros: return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," else: @@ -144,27 +174,46 @@ def getEntryC(self): @dataclass -class BgCamFuncData: # CameraPosData +class BgCamFuncData: + """This class defines camera data, if used""" + pos: tuple[int, int, int] rot: tuple[int, int, int] fov: int roomImageOverrideBgCamIndex: int + def getEntryC(self): + """Returns an entry for the camera data array""" + + return ( + (indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " },\n") + + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.rot) + " },\n") + + (indent + "{ " + f"{self.fov:6}, {self.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + ) + @dataclass class CrawlspaceData: + """This class defines camera data for crawlspaces, if used""" + points: list[tuple[int, int, int]] = field(default_factory=list) arrayIndex: int = None def getDataEntryC(self): + """Returns an entry for the camera data array""" + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) def getInfoEntryC(self, posDataName: str): + """Returns a crawlspace entry for the camera informations array""" + return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" @dataclass class BgCamInfo: + """This class defines camera information data""" + setting: str count: int arrayIndex: int @@ -174,20 +223,16 @@ class BgCamInfo: def __post_init__(self): self.hasPosData = self.camData is not None - def getDataEntryC(self): - return ( - (indent + "{ " + ", ".join(f"{p:6}" for p in self.camData.pos) + " },\n") - + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.camData.rot) + " },\n") - + (indent + "{ " + f"{self.camData.fov:6}, {self.camData.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") - ) - - def getInfoEntryC(self, posDataName: str): + def getEntryC(self, posDataName: str): + """Returns an entry for the camera information array""" ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" @dataclass class WaterBox: + """This class defines waterbox data""" + position: tuple[int, int, int] scale: float emptyDisplaySize: float @@ -224,6 +269,8 @@ def __post_init__(self): self.zLength = zMax - self.zMin def getProperties(self): + """Returns the waterbox properties""" + if self.useMacros: return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" else: @@ -242,6 +289,8 @@ def getProperties(self): ) def getEntryC(self): + """Returns a waterbox entry""" + return ( (indent + "{ ") + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " @@ -252,7 +301,11 @@ def getEntryC(self): @dataclass class Vertex: + """This class defines a vertex data""" + pos: tuple[int, int, int] def getEntryC(self): + """Returns a vertex entry""" + return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 6fcb68fab..5a0b61680 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -17,6 +17,8 @@ class RoomCommands: + """This class defines the command list for rooms""" + def getEchoSettingsCmd(self, infos: "OOTRoomHeaderInfos"): return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" @@ -88,6 +90,8 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): class SceneCommands: + """This class defines the command list for scenes""" + def getSoundSettingsCmd(self, infos: "OOTSceneHeaderInfos"): return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" From 858b269e595df7c9aa1c367cc73bc5718a656516 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 27 Sep 2023 22:03:31 +0200 Subject: [PATCH 28/98] docs part 3 --- fast64_internal/oot/new_exporter/exporter.py | 81 ++++++++++--------- fast64_internal/oot/new_exporter/file.py | 5 +- .../oot/new_exporter/room_header.py | 20 ++++- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 809e31810..b815c42f4 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -41,6 +41,8 @@ @dataclass class OOTRoomData: + """This class hosts the C data for every room files""" + name: str roomMain: str = None roomModel: str = None @@ -49,6 +51,8 @@ class OOTRoomData: @dataclass class OOTSceneData: + """This class hosts the C data for every scene files""" + sceneMain: str = None sceneCollision: str = None sceneCutscenes: list[str] = field(default_factory=list) @@ -57,6 +61,8 @@ class OOTSceneData: @dataclass class OOTSceneExport: + """This class is the main exporter class, it handles generating the C data and writing the files""" + exportInfo: ExportInfo originalSceneObj: Object sceneName: str @@ -81,6 +87,8 @@ class OOTSceneExport: hasSceneTextures: bool = False def getNewRoomList(self, scene: OOTScene): + """Returns the room list from empty objects with the type 'Room'""" + roomDict: dict[int, OOTRoom] = {} roomObjs: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" @@ -172,36 +180,7 @@ def getNewRoomList(self, scene: OOTScene): return [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] def getNewScene(self): - altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") - sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) - altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") - sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) - hasAltHeader = False - - for i, header in enumerate(altHeaderList, 1): - altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") - if not altP.usePreviousHeader: - setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) - hasAltHeader = True - - altHeaderData.cutscenes = [ - sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) - ] - - if len(altHeaderData.cutscenes) > 0: - hasAltHeader = True - - sceneData.altHeader = altHeaderData if hasAltHeader else None - sceneData.roomList = self.getNewRoomList(sceneData) - sceneData.colHeader = sceneData.getNewCollisionHeader() - - sceneData.validateScene() - return sceneData - - def getNewSceneFromEmptyObject(self): - """Returns the default scene header and adds the alternate/cutscene ones""" - + """Returns and creates scene data""" # init if self.originalSceneObj.type != "EMPTY" or self.originalSceneObj.ootEmptyType != "Scene": raise PluginError(f'{self.originalSceneObj.name} is not an empty with the "Scene" empty type.') @@ -215,10 +194,31 @@ def getNewSceneFromEmptyObject(self): if bpy.context.scene.exportHiddenGeometry: restoreHiddenState(hiddenState) - # convert scene - sceneData = None try: - sceneData = self.getNewScene() + altProp = self.sceneObj.ootAlternateSceneHeaders + sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") + sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) + altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") + sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) + hasAltHeader = False + + for i, header in enumerate(altHeaderList, 1): + altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) + hasAltHeader = True + + altHeaderData.cutscenes = [ + sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + if len(altHeaderData.cutscenes) > 0: + hasAltHeader = True + + sceneData.altHeader = altHeaderData if hasAltHeader else None + sceneData.roomList = self.getNewRoomList(sceneData) + sceneData.colHeader = sceneData.getNewCollisionHeader() + sceneData.validateScene() if sceneData.mainHeader.cutscene is not None: self.hasCutscenes = sceneData.mainHeader.cutscene.writeCutscene @@ -234,12 +234,11 @@ def getNewSceneFromEmptyObject(self): ootCleanupScene(self.originalSceneObj, allObjs) raise Exception(str(e)) - if sceneData is None: - raise PluginError("ERROR: 'sceneData' is None!") - return sceneData def setRoomListData(self): + """Gets and sets C data for every room elements""" + for room in self.scene.roomList: roomMainData = room.getRoomMainC() roomModelData = room.getRoomShapeModelC(self.textureExportSettings) @@ -251,6 +250,8 @@ def setRoomListData(self): ) def setSceneData(self): + """Gets and sets C data for every scene elements""" + sceneMainData = self.scene.getSceneMainC() sceneCollisionData = self.scene.colHeader.getSceneCollisionC() sceneCutsceneData = self.scene.getSceneCutscenesC() @@ -271,6 +272,8 @@ def setSceneData(self): ) def setIncludeData(self): + """Adds includes at the beginning of each file to write""" + suffix = "\n\n" sceneInclude = f'\n#include "{self.scene.name}.h"\n' common = includeData["common"] @@ -316,6 +319,8 @@ def setIncludeData(self): cs = cutscene + sceneInclude + suffix + cs def writeScene(self): + """Write the scene to the chosen location""" + for room in self.roomList.values(): if self.singleFileExport: roomMainPath = f"{room.name}.c" @@ -350,6 +355,8 @@ def writeScene(self): room.mesh.copyBgImages(self.path) def export(self): + """Main function""" + checkObjectReference(self.originalSceneObj, "Scene object") isCustomExport = self.exportInfo.isCustomExportPath exportPath = self.exportInfo.exportPath @@ -361,7 +368,7 @@ def export(self): exportSubdir = os.path.dirname(getSceneDirFromLevelName(self.sceneName)) sceneInclude = exportSubdir + "/" + self.sceneName + "/" - self.scene = self.getNewSceneFromEmptyObject() + self.scene = self.getNewScene() self.path = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) self.sceneBasePath = os.path.join(self.path, self.scene.name) self.textureExportSettings.includeDir = sceneInclude diff --git a/fast64_internal/oot/new_exporter/file.py b/fast64_internal/oot/new_exporter/file.py index 2d19f9937..03b47ed55 100644 --- a/fast64_internal/oot/new_exporter/file.py +++ b/fast64_internal/oot/new_exporter/file.py @@ -12,7 +12,9 @@ @dataclass -class Files: +class Files: # TODO: find a better name + """This class handles editing decomp files""" + exporter: "OOTSceneExport" def modifySceneFiles(self): @@ -30,6 +32,7 @@ def modifySceneFiles(self): os.remove(filepath) def editFiles(self): + """Edits decomp files""" self.modifySceneFiles() Spec().editSpec(self.exporter) SceneTable().editSceneTable(self.exporter) diff --git a/fast64_internal/oot/new_exporter/room_header.py b/fast64_internal/oot/new_exporter/room_header.py index 3835ed0cd..a8f3df493 100644 --- a/fast64_internal/oot/new_exporter/room_header.py +++ b/fast64_internal/oot/new_exporter/room_header.py @@ -8,6 +8,8 @@ @dataclass class OOTRoomHeaderInfos: + """This class stores various room header informations""" + ### General ### index: int @@ -38,13 +40,19 @@ class OOTRoomHeaderInfos: @dataclass class OOTRoomHeaderObjects: + """This class defines an OoT object array""" + name: str objectList: list[str] def getObjectLengthDefineName(self): + """Returns the name of the define for the total of entries in the object list""" + return f"LENGTH_{self.name.upper()}" def getObjectListC(self): + """Returns the array with the objects the room uses""" + objectList = CData() listName = f"s16 {self.name}" @@ -64,6 +72,8 @@ def getObjectListC(self): @dataclass class OOTRoomHeaderActors: + """This class defines an OoT actor array""" + name: str sceneObj: Object roomObj: Object @@ -113,10 +123,13 @@ def __post_init__(self): self.actorList.append(actor) def getActorLengthDefineName(self): + """Returns the name of the define for the total of entries in the actor list""" + return f"LENGTH_{self.name.upper()}" def getActorListC(self): - """Returns the actor list for the current header""" + """Returns the array with the actors the room uses""" + actorList = CData() listName = f"ActorEntry {self.name}" @@ -135,6 +148,8 @@ def getActorListC(self): @dataclass class OOTRoomAlternateHeader: + """This class stores alternate header data""" + name: str childNight: "OOTRoomHeader" = None adultDay: "OOTRoomHeader" = None @@ -144,6 +159,8 @@ class OOTRoomAlternateHeader: @dataclass class OOTRoomHeader: + """This class defines a room header""" + name: str infos: OOTRoomHeaderInfos objects: OOTRoomHeaderObjects @@ -151,6 +168,7 @@ class OOTRoomHeader: def getHeaderDefines(self): """Returns a string containing defines for actor and object lists lengths""" + headerDefines = "" if len(self.objects.objectList) > 0: From fa5734c272fbd357aba55c060b791164e596ad6c Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 00:46:56 +0200 Subject: [PATCH 29/98] docs part 4 --- fast64_internal/oot/new_exporter/room.py | 15 ++++++ .../oot/new_exporter/room_shape.py | 28 +++++++++++ .../oot/new_exporter/scene_header.py | 47 +++++++++++++++++-- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index a571a9d40..4d5b053ef 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -31,6 +31,8 @@ @dataclass class OOTRoom(Common, RoomCommands): + """This class defines a room""" + name: str = None roomObj: Object = None roomShapeType: str = None @@ -45,9 +47,13 @@ def __post_init__(self): self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) def hasAlternateHeaders(self): + """Returns ``True`` if there's alternate headers data""" + return self.altHeader is not None def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: + """Returns the current room header based on the header index""" + if headerIndex == 0: return self.mainHeader @@ -62,6 +68,8 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: return None def getMultiBgEntries(self): + """Returns a list of ``RoomShapeImageMultiBgEntry`` based on mesh data""" + entries: list[RoomShapeImageMultiBgEntry] = [] for i, bgImg in enumerate(self.mesh.bgImages): @@ -74,6 +82,8 @@ def getMultiBgEntries(self): return entries def getDListsEntries(self): + """Returns a list of ``RoomShapeDListsEntry`` based on mesh data""" + entries: list[RoomShapeDListsEntry] = [] for meshGrp in self.mesh.meshEntries: @@ -129,6 +139,8 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = ) def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): + """Returns a new room shape""" + normal = None single = None multiImg = None @@ -178,6 +190,8 @@ def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): ) def getRoomMainC(self): + """Returns the C data of the main informations of a room""" + roomC = CData() roomHeaders: list[tuple[OOTRoomHeader, str]] = [] altHeaderPtrList = None @@ -227,6 +241,7 @@ def getRoomMainC(self): return roomC def getRoomShapeModelC(self, textureSettings: TextureExportSettings): + """Returns the C data of the room model""" roomModel = CData() for i, entry in enumerate(self.mesh.meshEntries): diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room_shape.py index fed658348..5da04d570 100644 --- a/fast64_internal/oot/new_exporter/room_shape.py +++ b/fast64_internal/oot/new_exporter/room_shape.py @@ -6,6 +6,7 @@ @dataclass class RoomShapeImageBase: + """This class defines the basic informations shared by other image classes""" name: str type: str @@ -15,6 +16,7 @@ class RoomShapeImageBase: @dataclass class RoomShapeDListsEntry: + """This class defines a display list pointer entry""" opaPtr: str xluPtr: str @@ -24,6 +26,8 @@ def getEntryC(self): @dataclass class RoomShapeImageMultiBgEntry: + """This class defines an image entry for the multiple image mode""" + bgCamIndex: int imgName: str width: int @@ -59,6 +63,8 @@ def getEntryC(self): @dataclass class RoomShapeImageMultiBg: + """This class defines the multiple background image array""" + name: str entries: list[RoomShapeImageMultiBgEntry] @@ -77,6 +83,8 @@ def getC(self): @dataclass class RoomShapeDLists: + """This class defines the display list pointer array (or variable)""" + name: str isArray: bool entries: list[RoomShapeDListsEntry] @@ -104,6 +112,8 @@ def getC(self): @dataclass class RoomShapeImageSingle(RoomShapeImageBase): + """This class defines a room shape using only one image""" + imgName: str width: int height: int @@ -116,6 +126,8 @@ class RoomShapeImageSingle(RoomShapeImageBase): tlutCount: int = 0 def getC(self): + """Returns the single background image mode variable""" + infoData = CData() listName = f"RoomShapeImageSingle {self.name}" @@ -140,9 +152,13 @@ def getC(self): @dataclass class RoomShapeImageMulti(RoomShapeImageBase): + """This class defines a room shape using multiple images""" + bgEntryArrayName: str def getC(self): + """Returns the multiple background image mode variable""" + infoData = CData() listName = f"RoomShapeImageSingle {self.name}" @@ -163,11 +179,15 @@ def getC(self): @dataclass class RoomShapeNormal: + """This class defines a normal room shape""" + name: str type: str entryArrayName: str def getC(self): + """Returns the C data for the room shape""" + infoData = CData() listName = f"RoomShapeNormal {self.name}" @@ -189,6 +209,8 @@ def getC(self): @dataclass class RoomShape: + """This class hosts every type of room shape""" + dl: RoomShapeDLists normal: RoomShapeNormal single: RoomShapeImageSingle @@ -196,6 +218,8 @@ class RoomShape: multi: RoomShapeImageMulti def getName(self): + """Returns the correct room shape name based on the type used""" + if self.normal is not None: return self.normal.name @@ -206,6 +230,8 @@ def getName(self): return self.multi.name def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): + """Returns the image data for image room shapes""" + dlData = CData() bitsPerValue = 64 @@ -225,6 +251,8 @@ def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: Texture return dlData def getRoomShapeC(self): + """Returns the C data for the room shape""" + shapeData = CData() if self.normal is not None: diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py index c53f91095..780676724 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -6,6 +6,8 @@ @dataclass class EnvLightSettings: + """This class defines the information of one environment light setting""" + envLightMode: str ambientColor: tuple[int, int, int] light1Color: tuple[int, int, int] @@ -18,15 +20,23 @@ class EnvLightSettings: blendRate: int def getBlendFogNear(self): + """Returns the packed blend rate and fog near values""" + return f"(({self.blendRate} << 10) | {self.fogNear})" def getColorValues(self, vector: tuple[int, int, int]): + """Returns and formats color values""" + return ", ".join(f"{v:5}" for v in vector) def getDirectionValues(self, vector: tuple[int, int, int]): + """Returns and formats direction values""" + return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) def getLightSettingsEntry(self, index: int): + """Returns an environment light entry""" + isLightingCustom = self.envLightMode == "Custom" vectors = [ @@ -71,10 +81,14 @@ def getLightSettingsEntry(self, index: int): @dataclass class Path: + """This class defines a pathway""" + name: str points: list[tuple[int, int, int]] = field(default_factory=list) def getPathPointListC(self): + """Returns the pathway position array""" + pathData = CData() pathName = f"Vec3s {self.name}" @@ -96,6 +110,8 @@ def getPathPointListC(self): @dataclass class OOTSceneHeaderInfos: + """This class stores various scene header informations""" + ### General ### keepObjectID: str @@ -126,11 +142,15 @@ class OOTSceneHeaderInfos: @dataclass class OOTSceneHeaderLighting: + """This class hosts lighting data""" + name: str envLightMode: str = None settings: list[EnvLightSettings] = field(default_factory=list) def getEnvLightSettingsC(self): + """Returns a ``CData`` containing the C data of env. light settings""" + lightSettingsC = CData() lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" @@ -149,6 +169,8 @@ def getEnvLightSettingsC(self): @dataclass class OOTSceneHeaderCutscene: + """This class hosts cutscene data (unfinished)""" + headerIndex: int writeType: str writeCutscene: bool @@ -179,10 +201,14 @@ def getCutsceneC(self): @dataclass class OOTSceneHeaderExits: + """This class hosts exit data""" + name: str = None exitList: list[tuple[int, str]] = field(default_factory=list) def getExitListC(self): + """Returns a ``CData`` containing the C data of the exit array""" + exitListC = CData() listName = f"u16 {self.name}[{len(self.exitList)}]" @@ -202,6 +228,8 @@ def getExitListC(self): @dataclass class OOTSceneHeaderActors: + """This class handles scene actors (transition actors and entrance actors)""" + entranceListName: str startPositionsName: str transActorListName: str @@ -210,7 +238,8 @@ class OOTSceneHeaderActors: entranceActorList: list[EntranceActor] = field(default_factory=list) def getSpawnActorListC(self): - """Returns the spawn actor list for the current header""" + """Returns the spawn actor array""" + spawnActorList = CData() listName = f"ActorEntry {self.startPositionsName}" @@ -227,7 +256,8 @@ def getSpawnActorListC(self): return spawnActorList def getSpawnListC(self): - """Returns the spawn list for the current header""" + """Returns the spawn array""" + spawnList = CData() listName = f"Spawn {self.entranceListName}" @@ -245,7 +275,8 @@ def getSpawnListC(self): return spawnList def getTransActorListC(self): - """Returns the transition actor list for the current header""" + """Returns the transition actor array""" + transActorList = CData() listName = f"TransitionActorEntry {self.transActorListName}" @@ -264,10 +295,14 @@ def getTransActorListC(self): @dataclass class OOTSceneHeaderPath: + """This class hosts pathways array data""" + name: str pathList: list[Path] def getPathC(self): + """Returns a ``CData`` containing the C data of the pathway array""" + pathData = CData() pathListData = CData() listName = f"Path {self.name}[{len(self.pathList)}]" @@ -290,6 +325,8 @@ def getPathC(self): @dataclass class OOTSceneAlternateHeader: + """This class stores alternate header data for the scene""" + name: str childNight: "OOTSceneHeader" = None adultDay: "OOTSceneHeader" = None @@ -299,6 +336,8 @@ class OOTSceneAlternateHeader: @dataclass class OOTSceneHeader: + """This class defines a scene header""" + name: str infos: OOTSceneHeaderInfos lighting: OOTSceneHeaderLighting @@ -308,6 +347,8 @@ class OOTSceneHeader: path: OOTSceneHeaderPath def getHeaderC(self): + """Returns the ``CData`` containing the header's data""" + headerData = CData() # Write the spawn position list data and the entrance list From 15990e17160b3455e9451c19f14eee0e0a84fc21 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 00:55:57 +0200 Subject: [PATCH 30/98] format + docs part 5 --- .../oot/new_exporter/room_shape.py | 2 ++ fast64_internal/oot/new_exporter/scene.py | 19 +++++++++++++++-- .../oot/new_exporter/scene_table.py | 21 ++++++++++++++++--- fast64_internal/oot/new_exporter/spec.py | 7 ++++++- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room_shape.py index 5da04d570..7d2d05848 100644 --- a/fast64_internal/oot/new_exporter/room_shape.py +++ b/fast64_internal/oot/new_exporter/room_shape.py @@ -7,6 +7,7 @@ @dataclass class RoomShapeImageBase: """This class defines the basic informations shared by other image classes""" + name: str type: str @@ -17,6 +18,7 @@ class RoomShapeImageBase: @dataclass class RoomShapeDListsEntry: """This class defines a display list pointer entry""" + opaPtr: str xluPtr: str diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 2920cfbf2..80b3b666b 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -27,6 +27,8 @@ @dataclass class OOTScene(SceneCommon): + """This class defines a scene""" + roomListName: str = None colHeader: OOTSceneCollisionHeader = None @@ -34,6 +36,8 @@ def __post_init__(self): self.roomListName = f"{self.name}_roomList" def getNewCollisionHeader(self): + """Returns and creates collision data""" + colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() bgCamInfoList = self.getBgCamInfoDataFromObjects() @@ -59,7 +63,7 @@ def getNewCollisionHeader(self): ) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): - """Returns a single scene header with the informations from the scene empty object""" + """Returns the scene header""" self.headerIndex = headerIndex headerName = f"{self.name}_header{self.headerIndex:02}" @@ -111,6 +115,8 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int ) def getRoomListC(self): + """Returns the ``CData`` containing the room list array""" + roomList = CData() listName = f"RomFile {self.roomListName}[]" @@ -148,6 +154,8 @@ def getRoomListC(self): return roomList def getSceneMainC(self): + """Returns the main informations of the scene as ``CData``""" + sceneC = CData() headers: list[tuple[OOTSceneHeader, str]] = [] altHeaderPtrs = None @@ -187,10 +195,17 @@ def getSceneMainC(self): return sceneC def getSceneCutscenesC(self): + """Returns the cutscene informations of the scene as ``CData`` (unfinished)""" + # will be implemented when PR #208 is merged csDataList: list[CData] = [] return csDataList - # Writes the textures and material setup displaylists that are shared between multiple rooms (is written to the scene) + # def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): + """ + Writes the textures and material setup displaylists that are shared between multiple rooms + (is written to the scene) + """ + return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() diff --git a/fast64_internal/oot/new_exporter/scene_table.py b/fast64_internal/oot/new_exporter/scene_table.py index fa9500e5a..4ff96354f 100644 --- a/fast64_internal/oot/new_exporter/scene_table.py +++ b/fast64_internal/oot/new_exporter/scene_table.py @@ -11,13 +11,19 @@ class SceneTable: + """This class hosts different function to edit the scene table""" + def getSceneNameSettings(self, isExport: bool): + """Returns the scene name""" + if isExport: return bpy.context.scene.ootSceneExportSettings.option else: return bpy.context.scene.ootSceneRemoveSettings.option - def getHackerOoTCheck(self, line: str): + def isHackerOoT(self, line: str): + """Returns ``True`` if HackerOoT-related data has been found on the current line""" + return ( line != "\n" and '#include "config.h"\n' not in line @@ -28,6 +34,7 @@ def getHackerOoTCheck(self, line: str): def getSceneTable(self, exportPath: str): """Read and remove unwanted stuff from ``scene_table.h``""" + dataList = [] sceneNames = [] fileHeader = "" @@ -41,7 +48,7 @@ def getSceneTable(self, exportPath: str): if not line.strip(): continue - if not bpy.context.scene.fast64.oot.hackerFeaturesEnabled or self.getHackerOoTCheck(line): + if not bpy.context.scene.fast64.oot.hackerFeaturesEnabled or self.isHackerOoT(line): if not ( # Detects the multiline comment at the top of the file: (line.startswith("/**") or line.startswith(" *")) @@ -68,6 +75,7 @@ def getSceneTable(self, exportPath: str): def getSceneIndex(self, sceneNameList: list[str], sceneName: str): """Returns the index (int) of the chosen scene, returns None if ``Custom`` is chosen""" + if sceneName == "Custom": return None @@ -84,6 +92,7 @@ def getOriginalIndex(self, sceneName): Returns the index of a specific scene defined by which one the user chose or by the ``sceneName`` parameter if it's not set to ``None`` """ + i = 0 if sceneName != "Custom": @@ -97,6 +106,7 @@ def getOriginalIndex(self, sceneName): def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: str, index: int, mode: str): """Returns the index to know where to insert data""" + # special case where the scene is "Inside the Great Deku Tree" # since it's the first scene simply return 0 if sceneName == "SCENE_DEKU_TREE": @@ -130,6 +140,7 @@ def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: st def getSceneParams(self, exporter: "OOTSceneExport", exportInfo: ExportInfo, sceneNames: list[str]): """Returns the parameters that needs to be set in ``DEFINE_SCENE()``""" + # in order to replace the values of ``unk10``, ``unk12`` and basically every parameters from ``DEFINE_SCENE``, # you just have to make it return something other than None, not necessarily a string sceneIndex = self.getSceneIndex(sceneNames, self.getSceneNameSettings(exporter is not None)) @@ -147,6 +158,7 @@ def getSceneParams(self, exporter: "OOTSceneExport", exportInfo: ExportInfo, sce def sceneTableToC(self, data, header: str, sceneNames: list[str], isExport: bool): """Converts the Scene Table to C code""" + # start the data with the header comment explaining the format of the file fileData = header @@ -178,7 +190,8 @@ def sceneTableToC(self, data, header: str, sceneNames: list[str], isExport: bool def getDrawConfig(self, sceneName: str): """Read draw config from scene table""" - fileData, header, sceneNames = self.getSceneTable(bpy.path.abspath(bpy.context.scene.ootDecompPath)) + + fileData, _, _ = self.getSceneTable(bpy.path.abspath(bpy.context.scene.ootDecompPath)) for sceneEntry in fileData: if sceneEntry[0] == f"{sceneName}_scene": @@ -188,6 +201,7 @@ def getDrawConfig(self, sceneName: str): def addHackerOoTData(self, fileData: str): """Reads the file and adds HackerOoT's modifications to the scene table file""" + newFileData = ['#include "config.h"\n\n'] for line in fileData.splitlines(): @@ -206,6 +220,7 @@ def addHackerOoTData(self, fileData: str): def editSceneTable(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): """Edit the scene table with the new data""" + isExport = exporter is not None if exportInfo is None: exportInfo = exporter.exportInfo diff --git a/fast64_internal/oot/new_exporter/spec.py b/fast64_internal/oot/new_exporter/spec.py index 98788b0a9..f717143f7 100644 --- a/fast64_internal/oot/new_exporter/spec.py +++ b/fast64_internal/oot/new_exporter/spec.py @@ -10,8 +10,11 @@ class Spec: + """This class hosts different functions to edit the spec file""" + def getSceneSpecEntries(self, segmentDefinition: list[str], sceneName: str): """Returns the existing spec entries for the selected scene""" + entries = [] matchText = rf'\s*name\s*"{sceneName}\_' @@ -23,6 +26,7 @@ def getSceneSpecEntries(self, segmentDefinition: list[str], sceneName: str): def getSpecEntries(self, fileData: str): """Returns the existing spec entries for the whole file""" + entries = [] compressFlag = "" @@ -42,7 +46,8 @@ def getSpecEntries(self, fileData: str): return entries, compressFlag, includes def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): - """Adds or removes entries for the selected scene""" + """Adds or removes entries for the selected scene in the spec file""" + isExport = exporter is not None if exportInfo is None: exportInfo = exporter.exportInfo From 98fa3c036fce4eadf627f55e14fe874cde3caf38 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:15:30 +0200 Subject: [PATCH 31/98] minor reorg --- fast64_internal/oot/new_exporter/actors.py | 71 ++++++++++++ fast64_internal/oot/new_exporter/commands.py | 2 +- .../oot/new_exporter/common/classes.py | 71 +----------- .../oot/new_exporter/common/scene.py | 3 +- fast64_internal/oot/new_exporter/room.py | 2 +- .../{room_header.py => room_classes.py} | 3 +- fast64_internal/oot/new_exporter/scene.py | 1 - .../oot/new_exporter/scene_classes.py | 106 +++++++++++++++++ .../oot/new_exporter/scene_header.py | 107 +----------------- 9 files changed, 186 insertions(+), 180 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/actors.py rename fast64_internal/oot/new_exporter/{room_header.py => room_classes.py} (99%) create mode 100644 fast64_internal/oot/new_exporter/scene_classes.py diff --git a/fast64_internal/oot/new_exporter/actors.py b/fast64_internal/oot/new_exporter/actors.py new file mode 100644 index 000000000..06f7fbc56 --- /dev/null +++ b/fast64_internal/oot/new_exporter/actors.py @@ -0,0 +1,71 @@ +from dataclasses import dataclass, field +from ...utility import indent + + +@dataclass +class Actor: + """Defines an Actor""" + + name: str = None + id: str = None + pos: list[int] = field(default_factory=list) + rot: str = None + params: str = None + + def getActorEntry(self): + """Returns a single actor entry""" + + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" + rotData = "{ " + self.rot + " }" + + actorInfos = [self.id, posData, rotData, self.params] + infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] + + return ( + indent + + (f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class TransitionActor(Actor): + """Defines a Transition Actor""" + + dontTransition: bool = None + roomFrom: int = None + roomTo: int = None + cameraFront: str = None + cameraBack: str = None + + def getTransitionActorEntry(self): + """Returns a single transition actor entry""" + + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] + roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" + posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" + + actorInfos = [roomData, self.id, posData, self.rot, self.params] + infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] + + return ( + (indent + f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class EntranceActor(Actor): + """Defines an Entrance Actor""" + + roomIndex: int = None + spawnIndex: int = None + + def getSpawnEntry(self): + """Returns a single spawn entry""" + + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 5a0b61680..0118180ce 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -4,7 +4,7 @@ if TYPE_CHECKING: from .collision import OOTSceneCollisionHeader from .room import OOTRoom - from .room_header import OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors + from .room_classes import OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors from .scene import OOTScene from .scene_header import ( OOTSceneHeaderInfos, diff --git a/fast64_internal/oot/new_exporter/common/classes.py b/fast64_internal/oot/new_exporter/common/classes.py index bb734c92f..30cca5419 100644 --- a/fast64_internal/oot/new_exporter/common/classes.py +++ b/fast64_internal/oot/new_exporter/common/classes.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from math import radians from mathutils import Quaternion, Matrix from bpy.types import Object @@ -102,72 +102,3 @@ def getEndCmd(self): """Returns the scene end command""" return indent + "SCENE_CMD_END(),\n" - - -@dataclass -class Actor: - """Defines an Actor""" - - name: str = None - id: str = None - pos: list[int] = field(default_factory=list) - rot: str = None - params: str = None - - def getActorEntry(self): - """Returns a single actor entry""" - - posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" - rotData = "{ " + self.rot + " }" - - actorInfos = [self.id, posData, rotData, self.params] - infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] - - return ( - indent - + (f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class TransitionActor(Actor): - """Defines a Transition Actor""" - - dontTransition: bool = None - roomFrom: int = None - roomTo: int = None - cameraFront: str = None - cameraBack: str = None - - def getTransitionActorEntry(self): - """Returns a single transition actor entry""" - - sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] - roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" - - actorInfos = [roomData, self.id, posData, self.rot, self.params] - infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] - - return ( - (indent + f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class EntranceActor(Actor): - """Defines an Entrance Actor""" - - roomIndex: int = None - spawnIndex: int = None - - def getSpawnEntry(self): - """Returns a single spawn entry""" - - return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py index 36ee3ef21..b88fc9d8c 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -7,7 +7,8 @@ from ...oot_model_classes import OOTModel from ..commands import SceneCommands from ..scene_header import EnvLightSettings, Path, OOTSceneHeader, OOTSceneAlternateHeader -from .classes import TransitionActor, EntranceActor, altHeaderList +from ..actors import TransitionActor, EntranceActor +from .classes import altHeaderList from .collision import CollisionCommon if TYPE_CHECKING: diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 4d5b053ef..9aef3cfc5 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -20,7 +20,7 @@ RoomShapeNormal, ) -from .room_header import ( +from .room_classes import ( OOTRoomHeader, OOTRoomAlternateHeader, OOTRoomHeaderInfos, diff --git a/fast64_internal/oot/new_exporter/room_header.py b/fast64_internal/oot/new_exporter/room_classes.py similarity index 99% rename from fast64_internal/oot/new_exporter/room_header.py rename to fast64_internal/oot/new_exporter/room_classes.py index a8f3df493..1cea0290a 100644 --- a/fast64_internal/oot/new_exporter/room_header.py +++ b/fast64_internal/oot/new_exporter/room_classes.py @@ -3,7 +3,8 @@ from bpy.types import Object from ...utility import CData, indent from ..oot_constants import ootData -from .common import Common, Actor +from .common import Common +from .actors import Actor @dataclass diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 80b3b666b..a82f472ed 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -201,7 +201,6 @@ def getSceneCutscenesC(self): csDataList: list[CData] = [] return csDataList - # def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): """ Writes the textures and material setup displaylists that are shared between multiple rooms diff --git a/fast64_internal/oot/new_exporter/scene_classes.py b/fast64_internal/oot/new_exporter/scene_classes.py new file mode 100644 index 000000000..913954ed1 --- /dev/null +++ b/fast64_internal/oot/new_exporter/scene_classes.py @@ -0,0 +1,106 @@ +from dataclasses import dataclass, field +from ...utility import CData, indent + + +@dataclass +class EnvLightSettings: + """This class defines the information of one environment light setting""" + + envLightMode: str + ambientColor: tuple[int, int, int] + light1Color: tuple[int, int, int] + light1Dir: tuple[int, int, int] + light2Color: tuple[int, int, int] + light2Dir: tuple[int, int, int] + fogColor: tuple[int, int, int] + fogNear: int + zFar: int + blendRate: int + + def getBlendFogNear(self): + """Returns the packed blend rate and fog near values""" + + return f"(({self.blendRate} << 10) | {self.fogNear})" + + def getColorValues(self, vector: tuple[int, int, int]): + """Returns and formats color values""" + + return ", ".join(f"{v:5}" for v in vector) + + def getDirectionValues(self, vector: tuple[int, int, int]): + """Returns and formats direction values""" + + return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) + + def getLightSettingsEntry(self, index: int): + """Returns an environment light entry""" + + isLightingCustom = self.envLightMode == "Custom" + + vectors = [ + (self.ambientColor, "Ambient Color", self.getColorValues), + (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), + (self.light1Color, "Diffuse0 Color", self.getColorValues), + (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), + (self.light2Color, "Diffuse1 Color", self.getColorValues), + (self.fogColor, "Fog Color", self.getColorValues), + ] + + fogData = [ + (self.getBlendFogNear(), "Blend Rate & Fog Near"), + (f"{self.zFar}", "Fog Far"), + ] + + lightDescs = ["Dawn", "Day", "Dusk", "Night"] + + if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": + # @TODO: Improve the lighting system. + # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. + # This is not accurate and more complicated, + # for now we are doing ``index % 4`` to avoid having an OoB read in the list + # but this will need to be changed the day the lighting system is updated. + lightDesc = f"// {lightDescs[index % 4]} Lighting\n" + else: + isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" + lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" + + lightData = ( + (indent + lightDesc) + + (indent + "{\n") + + "".join( + indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors + ) + + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) + + (indent + "},\n") + ) + + return lightData + + +@dataclass +class Path: + """This class defines a pathway""" + + name: str + points: list[tuple[int, int, int]] = field(default_factory=list) + + def getPathPointListC(self): + """Returns the pathway position array""" + + pathData = CData() + pathName = f"Vec3s {self.name}" + + # .h + pathData.header = f"extern {pathName}[];\n" + + # .c + pathData.source = ( + f"{pathName}[]" + + " = {\n" + + "\n".join( + indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points + ) + + "\n};\n\n" + ) + + return pathData diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene_header.py index 780676724..cdfe53c28 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene_header.py @@ -1,111 +1,8 @@ from dataclasses import dataclass, field from bpy.types import Object from ...utility import PluginError, CData, indent -from .common import TransitionActor, EntranceActor - - -@dataclass -class EnvLightSettings: - """This class defines the information of one environment light setting""" - - envLightMode: str - ambientColor: tuple[int, int, int] - light1Color: tuple[int, int, int] - light1Dir: tuple[int, int, int] - light2Color: tuple[int, int, int] - light2Dir: tuple[int, int, int] - fogColor: tuple[int, int, int] - fogNear: int - zFar: int - blendRate: int - - def getBlendFogNear(self): - """Returns the packed blend rate and fog near values""" - - return f"(({self.blendRate} << 10) | {self.fogNear})" - - def getColorValues(self, vector: tuple[int, int, int]): - """Returns and formats color values""" - - return ", ".join(f"{v:5}" for v in vector) - - def getDirectionValues(self, vector: tuple[int, int, int]): - """Returns and formats direction values""" - - return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - - def getLightSettingsEntry(self, index: int): - """Returns an environment light entry""" - - isLightingCustom = self.envLightMode == "Custom" - - vectors = [ - (self.ambientColor, "Ambient Color", self.getColorValues), - (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), - (self.light1Color, "Diffuse0 Color", self.getColorValues), - (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), - (self.light2Color, "Diffuse1 Color", self.getColorValues), - (self.fogColor, "Fog Color", self.getColorValues), - ] - - fogData = [ - (self.getBlendFogNear(), "Blend Rate & Fog Near"), - (f"{self.zFar}", "Fog Far"), - ] - - lightDescs = ["Dawn", "Day", "Dusk", "Night"] - - if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": - # @TODO: Improve the lighting system. - # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. - # This is not accurate and more complicated, - # for now we are doing ``index % 4`` to avoid having an OoB read in the list - # but this will need to be changed the day the lighting system is updated. - lightDesc = f"// {lightDescs[index % 4]} Lighting\n" - else: - isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" - lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" - - lightData = ( - (indent + lightDesc) - + (indent + "{\n") - + "".join( - indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors - ) - + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) - + (indent + "},\n") - ) - - return lightData - - -@dataclass -class Path: - """This class defines a pathway""" - - name: str - points: list[tuple[int, int, int]] = field(default_factory=list) - - def getPathPointListC(self): - """Returns the pathway position array""" - - pathData = CData() - pathName = f"Vec3s {self.name}" - - # .h - pathData.header = f"extern {pathName}[];\n" - - # .c - pathData.source = ( - f"{pathName}[]" - + " = {\n" - + "\n".join( - indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points - ) - + "\n};\n\n" - ) - - return pathData +from .actors import TransitionActor, EntranceActor +from .scene_classes import EnvLightSettings, Path @dataclass From 92cbe0eb22ce61e28369e909af5fd02865450adc Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:37:48 +0200 Subject: [PATCH 32/98] fixed issues related to recent collision export changes --- fast64_internal/oot/new_exporter/collision.py | 2 +- .../oot/new_exporter/collision_classes.py | 24 +++++++++---------- .../oot/new_exporter/common/__init__.py | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 268e3de98..5657a7b92 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -122,7 +122,7 @@ def getInfoArrayC(self): # .c bgCamInfoData.source = ( (listName + " = {\n") - + "".join(cam.getInfoEntryC(self.posDataName) for cam in self.bgCamInfoList) + + "".join(cam.getEntryC(self.posDataName) for cam in self.bgCamInfoList) + "".join(crawlspace.getInfoEntryC(self.posDataName) for crawlspace in self.crawlspacePosList) + "};\n\n" ) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index ce995b61b..70622c7df 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -21,15 +21,15 @@ def getFlags_vIA(self): """Returns the value of ``flags_vIA``""" vtxId = self.indices[0] & 0x1FFF - if not (self.ignoreProjectile and self.ignoreActor and self.ignoreCamera): - flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" - else: + if self.ignoreProjectile or self.ignoreActor or self.ignoreCamera: flag1 = ("COLPOLY_IGNORE_PROJECTILES" if self.useMacros else "(1 << 2)") if self.ignoreProjectile else "" - flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreProjectile else "" - flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreProjectile else "" - flags = "(" + " | ".join([flag1, flag2, flag3]) + ")" + flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreActor else "" + flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreCamera else "" + flags = "(" + " | ".join(flag for flag in [flag1, flag2, flag3] if len(flag) > 0) + ")" + else: + flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" - return f"COLPOLY_VTX({vtxId}, ({flags}))" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" + return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" def getFlags_vIB(self): """Returns the value of ``flags_vIB``""" @@ -56,12 +56,12 @@ def getEntryC(self): (indent + "{ ") + ", ".join( ( - f"0x{self.type:04X}", - f"0x{self.getFlags_vIA():04X}", - f"0x{self.getFlags_vIB():04X}", - f"0x{self.getVIC():04X}", + f"{self.type}", + self.getFlags_vIA(), + self.getFlags_vIB(), + self.getVIC(), ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), - f"0x{self.dist:04X}", + f"{self.dist}", ) ) + " }," diff --git a/fast64_internal/oot/new_exporter/common/__init__.py b/fast64_internal/oot/new_exporter/common/__init__.py index 85f4eff55..8b9cbdc37 100644 --- a/fast64_internal/oot/new_exporter/common/__init__.py +++ b/fast64_internal/oot/new_exporter/common/__init__.py @@ -1,4 +1,4 @@ -from .classes import Common, Actor, TransitionActor, EntranceActor, altHeaderList +from .classes import Common, altHeaderList from .collision import CollisionCommon from .scene import SceneCommon From df4a4adcf5bbc568f95c3cc7e57ec5474492cab8 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 01:48:38 +0200 Subject: [PATCH 33/98] add safeguard in header --- fast64_internal/oot/new_exporter/exporter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index b815c42f4..afe8a380f 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -86,6 +86,12 @@ class OOTSceneExport: hasCutscenes: bool = False hasSceneTextures: bool = False + def __post_init__(self): + self.header = ( + f"#ifndef {self.sceneName.upper()}_SCENE_H\n" + + f"#define {self.sceneName.upper()}_SCENE_H\n\n" + ) + def getNewRoomList(self, scene: OOTScene): """Returns the room list from empty objects with the type 'Room'""" @@ -348,6 +354,7 @@ def writeScene(self): if self.hasSceneTextures: writeFile(f"{self.sceneBasePath}_tex.c", self.sceneData.sceneTextures) + self.header += "\n#endif\n" writeFile(sceneMainPath, self.sceneData.sceneMain) writeFile(self.sceneBasePath + ".h", self.header) From 7c2b4724200d4c4e8eaa7c7114187ebd18aac622 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:21:35 +0200 Subject: [PATCH 34/98] exporter.py cleanup --- fast64_internal/oot/new_exporter/exporter.py | 172 ++---------------- .../oot/new_exporter/exporter_classes.py | 127 +++++++++++++ fast64_internal/oot/new_exporter/room.py | 18 ++ fast64_internal/oot/new_exporter/scene.py | 28 +++ fast64_internal/oot/new_exporter/spec.py | 6 +- 5 files changed, 188 insertions(+), 163 deletions(-) create mode 100644 fast64_internal/oot/new_exporter/exporter_classes.py diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index afe8a380f..989005ae0 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -1,7 +1,7 @@ import bpy import os -from dataclasses import dataclass, field +from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object from ...f3d.f3d_gbi import DLFormat, TextureExportSettings @@ -14,11 +14,12 @@ from ..oot_f3d_writer import writeTextureArraysNew from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh from ..oot_utility import CullGroup -from .common import Common, altHeaderList, includeData +from .common import Common, altHeaderList from .scene import OOTScene from .scene_header import OOTSceneAlternateHeader from .room import OOTRoom, OOTRoomAlternateHeader from .file import Files +from .exporter_classes import SceneFile from ...utility import ( PluginError, @@ -39,26 +40,6 @@ ) -@dataclass -class OOTRoomData: - """This class hosts the C data for every room files""" - - name: str - roomMain: str = None - roomModel: str = None - roomModelInfo: str = None - - -@dataclass -class OOTSceneData: - """This class hosts the C data for every scene files""" - - sceneMain: str = None - sceneCollision: str = None - sceneCutscenes: list[str] = field(default_factory=list) - sceneTextures: str = None - - @dataclass class OOTSceneExport: """This class is the main exporter class, it handles generating the C data and writing the files""" @@ -71,7 +52,7 @@ class OOTSceneExport: f3dType: str saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions - singleFileExport: bool + isSingleFile: bool isHWv1: bool textureExportSettings: TextureExportSettings dlFormat: DLFormat = DLFormat.Static @@ -79,19 +60,10 @@ class OOTSceneExport: sceneObj: Object = None scene: OOTScene = None path: str = None - sceneBasePath: str = None - header: str = "" - sceneData: OOTSceneData = None - roomList: dict[int, OOTRoomData] = field(default_factory=dict) + sceneFile: SceneFile = None hasCutscenes: bool = False hasSceneTextures: bool = False - def __post_init__(self): - self.header = ( - f"#ifndef {self.sceneName.upper()}_SCENE_H\n" - + f"#define {self.sceneName.upper()}_SCENE_H\n\n" - ) - def getNewRoomList(self, scene: OOTScene): """Returns the room list from empty objects with the type 'Room'""" @@ -242,125 +214,6 @@ def getNewScene(self): return sceneData - def setRoomListData(self): - """Gets and sets C data for every room elements""" - - for room in self.scene.roomList: - roomMainData = room.getRoomMainC() - roomModelData = room.getRoomShapeModelC(self.textureExportSettings) - roomModelInfoData = room.roomShape.getRoomShapeC() - - self.header += roomMainData.header + roomModelData.header + roomModelInfoData.header - self.roomList[room.roomIndex] = OOTRoomData( - room.name, roomMainData.source, roomModelData.source, roomModelInfoData.source - ) - - def setSceneData(self): - """Gets and sets C data for every scene elements""" - - sceneMainData = self.scene.getSceneMainC() - sceneCollisionData = self.scene.colHeader.getSceneCollisionC() - sceneCutsceneData = self.scene.getSceneCutscenesC() - sceneTexturesData = self.scene.getSceneTexturesC(self.textureExportSettings) - - self.header += ( - sceneMainData.header - + "".join(cs.header for cs in sceneCutsceneData) - + sceneCollisionData.header - + sceneTexturesData.header - ) - - self.sceneData = OOTSceneData( - sceneMainData.source, - sceneCollisionData.source, - [cs.source for cs in sceneCutsceneData], - sceneTexturesData.source, - ) - - def setIncludeData(self): - """Adds includes at the beginning of each file to write""" - - suffix = "\n\n" - sceneInclude = f'\n#include "{self.scene.name}.h"\n' - common = includeData["common"] - # room = includeData["roomMain"] - # roomShapeInfo = includeData["roomShapeInfo"] - # scene = includeData["sceneMain"] - # collision = includeData["collision"] - # cutscene = includeData["cutscene"] - room = "" - roomShapeInfo = "" - scene = "" - collision = "" - cutscene = "" - - common = ( - '#include "ultra64.h"\n' - + '#include "z64.h"\n' - + '#include "macros.h"\n' - + '#include "segment_symbols.h"\n' - + '#include "command_macros_base.h"\n' - + '#include "z64cutscene_commands.h"\n' - + '#include "variables.h"\n' - ) - - for roomData in self.roomList.values(): - if self.singleFileExport: - common += room + roomShapeInfo + sceneInclude - roomData.roomMain = common + suffix + roomData.roomMain - else: - roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain - roomData.roomModelInfo = common + roomShapeInfo + sceneInclude + suffix + roomData.roomModelInfo - roomData.roomModel = common + sceneInclude + suffix + roomData.roomModel - - if self.singleFileExport: - common += scene + collision + cutscene + sceneInclude - self.sceneData.sceneMain = common + suffix + self.sceneData.sceneMain - else: - self.sceneData.sceneMain = common + scene + sceneInclude + suffix + self.sceneData.sceneMain - self.sceneData.sceneCollision = common + collision + sceneInclude + suffix + self.sceneData.sceneCollision - - if self.hasCutscenes: - for cs in self.sceneData.sceneCutscenes: - cs = cutscene + sceneInclude + suffix + cs - - def writeScene(self): - """Write the scene to the chosen location""" - - for room in self.roomList.values(): - if self.singleFileExport: - roomMainPath = f"{room.name}.c" - room.roomMain += room.roomModelInfo + room.roomModel - else: - roomMainPath = f"{room.name}_main.c" - writeFile(os.path.join(self.path, f"{room.name}_model_info.c"), room.roomModelInfo) - writeFile(os.path.join(self.path, f"{room.name}_model.c"), room.roomModel) - - writeFile(os.path.join(self.path, roomMainPath), room.roomMain) - - if self.singleFileExport: - sceneMainPath = f"{self.sceneBasePath}.c" - self.sceneData.sceneMain += self.sceneData.sceneCollision - if self.hasCutscenes: - for i, cs in enumerate(self.sceneData.sceneCutscenes): - self.sceneData.sceneMain += cs - else: - sceneMainPath = f"{self.sceneBasePath}_main.c" - writeFile(f"{self.sceneBasePath}_col.c", self.sceneData.sceneCollision) - if self.hasCutscenes: - for i, cs in enumerate(self.sceneData.sceneCutscenes): - writeFile(f"{self.sceneBasePath}_cs_{i}.c", cs) - - if self.hasSceneTextures: - writeFile(f"{self.sceneBasePath}_tex.c", self.sceneData.sceneTextures) - - self.header += "\n#endif\n" - writeFile(sceneMainPath, self.sceneData.sceneMain) - writeFile(self.sceneBasePath + ".h", self.header) - - for room in self.scene.roomList: - room.mesh.copyBgImages(self.path) - def export(self): """Main function""" @@ -377,22 +230,21 @@ def export(self): sceneInclude = exportSubdir + "/" + self.sceneName + "/" self.scene = self.getNewScene() self.path = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) - self.sceneBasePath = os.path.join(self.path, self.scene.name) self.textureExportSettings.includeDir = sceneInclude self.textureExportSettings.exportPath = self.path - self.setSceneData() - self.setRoomListData() - self.hasSceneTextures = len(self.sceneData.sceneTextures) > 0 + self.sceneFile = self.scene.getNewSceneFile(self.path, self.isSingleFile, self.textureExportSettings) + self.hasSceneTextures = len(self.sceneFile.sceneTextures) > 0 if not isCustomExport: writeTextureArraysExistingScene(self.scene.model, exportPath, sceneInclude + self.sceneName + "_scene.h") else: textureArrayData = writeTextureArraysNew(self.scene.model, None) - self.sceneData.sceneTextures += textureArrayData.source - self.header += textureArrayData.header + self.sceneFile.sceneTextures += textureArrayData.source + self.sceneFile.header += textureArrayData.header - self.setIncludeData() - self.writeScene() + self.sceneFile.write() + for room in self.scene.roomList: + room.mesh.copyBgImages(self.path) if not isCustomExport: Files(self).editFiles() diff --git a/fast64_internal/oot/new_exporter/exporter_classes.py b/fast64_internal/oot/new_exporter/exporter_classes.py new file mode 100644 index 000000000..f8d685bba --- /dev/null +++ b/fast64_internal/oot/new_exporter/exporter_classes.py @@ -0,0 +1,127 @@ +import os + +from dataclasses import dataclass, field +from ...utility import writeFile +from .common import includeData + + +@dataclass +class RoomFile: + """This class hosts the C data for every room files""" + + name: str + roomMain: str = None + roomModel: str = None + roomModelInfo: str = None + singleFileExport: bool = False + path: str = None + + header: str = "" + + def write(self): + if self.singleFileExport: + roomMainPath = f"{self.name}.c" + self.roomMain += self.roomModelInfo + self.roomModel + else: + roomMainPath = f"{self.name}_main.c" + writeFile(os.path.join(self.path, f"{self.name}_model_info.c"), self.roomModelInfo) + writeFile(os.path.join(self.path, f"{self.name}_model.c"), self.roomModel) + + writeFile(os.path.join(self.path, roomMainPath), self.roomMain) + + +@dataclass +class SceneFile: + """This class hosts the C data for every scene files""" + + name: str + sceneMain: str = None + sceneCollision: str = None + sceneCutscenes: list[str] = field(default_factory=list) + sceneTextures: str = None + roomList: dict[int, RoomFile] = field(default_factory=dict) + singleFileExport: bool = False + path: str = None + header: str = "" + + hasCutscenes: bool = False + hasSceneTextures: bool = False + + def __post_init__(self): + self.hasCutscenes = len(self.sceneCutscenes) > 0 + self.hasSceneTextures = len(self.sceneTextures) > 0 + + def setIncludeData(self): + """Adds includes at the beginning of each file to write""" + + suffix = "\n\n" + sceneInclude = f'\n#include "{self.name}.h"\n' + common = includeData["common"] + # room = includeData["roomMain"] + # roomShapeInfo = includeData["roomShapeInfo"] + # scene = includeData["sceneMain"] + # collision = includeData["collision"] + # cutscene = includeData["cutscene"] + room = "" + roomShapeInfo = "" + scene = "" + collision = "" + cutscene = "" + + common = ( + '#include "ultra64.h"\n' + + '#include "z64.h"\n' + + '#include "macros.h"\n' + + '#include "segment_symbols.h"\n' + + '#include "command_macros_base.h"\n' + + '#include "z64cutscene_commands.h"\n' + + '#include "variables.h"\n' + ) + + for roomData in self.roomList.values(): + if self.singleFileExport: + common += room + roomShapeInfo + sceneInclude + roomData.roomMain = common + suffix + roomData.roomMain + else: + roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain + roomData.roomModelInfo = common + roomShapeInfo + sceneInclude + suffix + roomData.roomModelInfo + roomData.roomModel = common + sceneInclude + suffix + roomData.roomModel + + if self.singleFileExport: + common += scene + collision + cutscene + sceneInclude + self.sceneMain = common + suffix + self.sceneMain + else: + self.sceneMain = common + scene + sceneInclude + suffix + self.sceneMain + self.sceneCollision = common + collision + sceneInclude + suffix + self.sceneCollision + + if self.hasCutscenes: + for cs in self.sceneCutscenes: + cs = cutscene + sceneInclude + suffix + cs + + def write(self): + self.setIncludeData() + + for room in self.roomList.values(): + self.header += room.header + room.write() + + if self.singleFileExport: + sceneMainPath = f"{self.name}.c" + self.sceneMain += self.sceneCollision + if self.hasCutscenes: + for i, cs in enumerate(self.sceneCutscenes): + self.sceneMain += cs + else: + sceneMainPath = f"{self.name}_main.c" + writeFile(os.path.join(self.path, f"{self.name}_col.c"), self.sceneCollision) + if self.hasCutscenes: + for i, cs in enumerate(self.sceneCutscenes): + writeFile(os.path.join(self.path, f"{self.name}_cs_{i}.c"), cs) + + if self.hasSceneTextures: + writeFile(os.path.join(self.path, f"{self.name}_tex.c"), self.sceneTextures) + + writeFile(os.path.join(self.path, sceneMainPath), self.sceneMain) + + self.header += "\n#endif\n" + writeFile(os.path.join(self.path, f"{self.name}.h"), self.header) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 9aef3cfc5..727159fed 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -8,6 +8,7 @@ from ..oot_model_classes import OOTModel, OOTGfxFormatter from .commands import RoomCommands from .common import Common, altHeaderList +from .exporter_classes import RoomFile from .room_shape import ( RoomShape, @@ -262,3 +263,20 @@ def getRoomShapeModelC(self, textureSettings: TextureExportSettings): roomModel.append(self.roomShape.getRoomShapeBgImgDataC(self.mesh, textureSettings)) return roomModel + + def getNewRoomFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): + """Returns a new ``RoomFile`` element""" + + roomMainData = self.getRoomMainC() + roomModelData = self.getRoomShapeModelC(textureExportSettings) + roomModelInfoData = self.roomShape.getRoomShapeC() + + return RoomFile( + self.name, + roomMainData.source, + roomModelData.source, + roomModelInfoData.source, + isSingleFile, + path, + roomMainData.header + roomModelData.header + roomModelInfoData.header, + ) diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index a82f472ed..32cd1bf2d 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -4,6 +4,7 @@ from ..oot_model_classes import OOTGfxFormatter from ..scene.properties import OOTSceneHeaderProperty from .common import SceneCommon +from .exporter_classes import SceneFile from .collision import ( OOTSceneCollisionHeader, @@ -208,3 +209,30 @@ def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): """ return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() + + def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): + """Gets and sets C data for every scene elements""" + + sceneMainData = self.getSceneMainC() + sceneCollisionData = self.colHeader.getSceneCollisionC() + sceneCutsceneData = self.getSceneCutscenesC() + sceneTexturesData = self.getSceneTexturesC(textureExportSettings) + + return SceneFile( + self.name, + sceneMainData.source, + sceneCollisionData.source, + [cs.source for cs in sceneCutsceneData], + sceneTexturesData.source, + {room.roomIndex: room.getNewRoomFile(path, isSingleFile, textureExportSettings) for room in self.roomList}, + isSingleFile, + path, + ( + f"#ifndef {self.name.upper()}_H\n" + + f"#define {self.name.upper()}_H\n\n" + + sceneMainData.header + + "".join(cs.header for cs in sceneCutsceneData) + + sceneCollisionData.header + + sceneTexturesData.header + ), + ) diff --git a/fast64_internal/oot/new_exporter/spec.py b/fast64_internal/oot/new_exporter/spec.py index f717143f7..3a8e77468 100644 --- a/fast64_internal/oot/new_exporter/spec.py +++ b/fast64_internal/oot/new_exporter/spec.py @@ -88,7 +88,7 @@ def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): firstIndex += 1 - for room in exporter.roomList.values(): + for room in exporter.sceneFile.roomList.values(): specEntries.insert( firstIndex, ("\n" + indent + f'name "{room.name}"\n') @@ -112,14 +112,14 @@ def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_tex.o"\n' if exporter.hasCutscenes: - for i in range(len(exporter.sceneData.sceneCutscenes)): + for i in range(len(exporter.sceneFile.sceneCutscenes)): sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_cs_{i}.o"\n' sceneSegInclude += indent + "number 2\n" specEntries.insert(firstIndex, sceneSegInclude) firstIndex += 1 - for room in exporter.roomList.values(): + for room in exporter.sceneFile.roomList.values(): specEntries.insert( firstIndex, ("\n" + indent + f'name "{room.name}"\n') From fb1b2c5fa1b9301c1ce2926d4c183249b1c7904e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:23:36 +0200 Subject: [PATCH 35/98] fix part 1 --- fast64_internal/oot/new_exporter/collision.py | 1 + fast64_internal/oot/new_exporter/spec.py | 2 +- fast64_internal/oot/oot_object.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 5657a7b92..1d1f8175a 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -104,6 +104,7 @@ def getDataArrayC(self): posData.source = ( (listName + " = {\n") + "\n".join(cam.camData.getEntryC() for cam in self.bgCamInfoList if cam.hasPosData) + + ("\n" if len(self.bgCamInfoList) > 0 else "") + "\n".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) + "};\n\n" ) diff --git a/fast64_internal/oot/new_exporter/spec.py b/fast64_internal/oot/new_exporter/spec.py index 3a8e77468..5d1a8c3ed 100644 --- a/fast64_internal/oot/new_exporter/spec.py +++ b/fast64_internal/oot/new_exporter/spec.py @@ -76,7 +76,7 @@ def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" sceneName = exporter.scene.name - if exporter.singleFileExport: + if exporter.isSingleFile: specEntries.insert( firstIndex, ("\n" + indent + f'name "{sceneName}"\n') diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 218890f33..9490530b5 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -45,7 +45,7 @@ def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, ootData: OoT_Da """Adds missing objects to the object list""" if len(curHeader.actors.actorList) > 0: for roomActor in curHeader.actors.actorList: - actor = ootData.actorData.actorsByID.get(roomActor.actorID) + actor = ootData.actorData.actorsByID.get(roomActor.id) if actor is not None and actor.key != "player" and len(actor.tiedObjects) > 0: for objKey in actor.tiedObjects: if objKey not in ["obj_gameplay_keep", "obj_gameplay_field_keep", "obj_gameplay_dangeon_keep"]: From 693139e094c2b48fdfa559b6004a67fef7dcbfd0 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:34:25 +0200 Subject: [PATCH 36/98] fixes part 2 --- .../oot/new_exporter/collision_classes.py | 28 ++++++++- .../oot/new_exporter/common/collision.py | 63 ++++++++++--------- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index 70622c7df..ec27a6cb9 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -68,7 +68,7 @@ def getEntryC(self): ) -@dataclass +@dataclass(eq=True) class SurfaceType: """This class defines a single surface type""" @@ -97,6 +97,29 @@ class SurfaceType: canHookshotC: str = None isWallDamageC: str = None + def __hash__(self): + return hash( + ( + self.bgCamIndex, + self.exitIndex, + self.floorType, + self.unk18, + self.wallType, + self.floorProperty, + self.isSoft, + self.isHorseBlocked, + self.material, + self.floorEffect, + self.lightSetting, + self.echo, + self.canHookshot, + self.conveyorSpeed, + self.conveyorDirection, + self.isWallDamage, + self.conveyorKeepMomentum, + ) + ) + def __post_init__(self): if self.conveyorKeepMomentum: self.conveyorSpeed += 4 @@ -199,6 +222,9 @@ class CrawlspaceData: points: list[tuple[int, int, int]] = field(default_factory=list) arrayIndex: int = None + def __post_init__(self): + self.points = [self.points[0], self.points[0], self.points[0], self.points[1], self.points[1], self.points[1]] + def getDataEntryC(self): """Returns an entry for the camera data array""" diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index 7f22357e9..12fc446dd 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -69,12 +69,12 @@ def getColSurfaceVtxDataFromMeshObj(self): self.sceneObj.select_set(True) matrixTable: dict[Object, Matrix] = {} - surfaceTypeData: dict[int, SurfaceType] = {} + colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} + surfaceList: list[SurfaceType] = [] polyList: list[CollisionPoly] = [] vertexList: list[Vertex] = [] bounds = [] - i = 0 matrixTable = self.getMeshObjects(self.sceneObj, self.transform, matrixTable) for meshObj, transform in matrixTable.items(): # Note: ``isinstance``only used to get the proper type hints @@ -134,30 +134,31 @@ def getColSurfaceVtxDataFromMeshObj(self): if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: indices[1], indices[2] = indices[2], indices[1] - surfaceIndex = i + face.material_index useConveyor = colProp.conveyorOption != "None" - if not surfaceIndex in surfaceTypeData: - surfaceTypeData[surfaceIndex] = SurfaceType( - colProp.cameraID, - colProp.exitID, - int(self.getPropValue(colProp, "floorSetting"), base=16), - 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorProperty"), base=16), - colProp.decreaseHeight, - colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), - colProp.lightingSetting, - int(colProp.echo, base=16), - colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, - int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, - colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, - ) + surfaceType = SurfaceType( + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorProperty"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorSetting"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + ) + + if not surfaceType in colPolyFromSurfaceType: + colPolyFromSurfaceType[surfaceType] = [] - polyList.append( + colPolyFromSurfaceType[surfaceType].append( CollisionPoly( indices, colProp.ignoreCameraCollision, @@ -165,12 +166,18 @@ def getColSurfaceVtxDataFromMeshObj(self): colProp.ignoreProjectileCollision, useConveyor, normal, - distance, - surfaceIndex, + distance ) ) - i += 1 - surfaceList = [surfaceTypeData[i] for i in range(min(surfaceTypeData.keys()), len(surfaceTypeData))] + + count = 0 + for surface, colPolyList in colPolyFromSurfaceType.items(): + for colPoly in colPolyList: + colPoly.type = count + polyList.append(colPoly) + surfaceList.append(surface) + count += 1 + return bounds, vertexList, polyList, surfaceList def getBgCamFuncDataFromObjects(self, camObj: Object): From 969102d00d2b25fcba1d5b72e63ed713ee749ef1 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 3 Oct 2023 21:50:09 +0200 Subject: [PATCH 37/98] camera improvements --- fast64_internal/oot/new_exporter/collision.py | 49 ++++++++++++++----- .../oot/new_exporter/collision_classes.py | 12 +++-- .../oot/new_exporter/common/collision.py | 14 ++---- fast64_internal/oot/new_exporter/scene.py | 7 +-- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision.py index 1d1f8175a..bbb3353ca 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass -from ...utility import CData, indent +from dataclasses import dataclass, field +from ...utility import PluginError, CData, indent from .collision_classes import ( SurfaceType, @@ -86,10 +86,33 @@ class CollisionHeaderBgCamInfo: arrayIdx: int = 0 crawlspaceCount: int = 6 + camFromIndex: dict[int, BgCamInfo | CrawlspaceData] = field(default_factory=dict) def __post_init__(self): - if len(self.bgCamInfoList) > 0: - self.arrayIdx = self.bgCamInfoList[-1].arrayIndex + self.crawlspaceCount + for bgCam in self.bgCamInfoList: + if not bgCam.camIndex in self.camFromIndex: + self.camFromIndex[bgCam.camIndex] = bgCam + else: + raise PluginError(f"ERROR (BgCamInfo): Camera index already used: {bgCam.camIndex}") + + for crawlCam in self.crawlspacePosList: + if not crawlCam.camIndex in self.camFromIndex: + self.camFromIndex[crawlCam.camIndex] = crawlCam + else: + raise PluginError(f"ERROR (Crawlspace): Camera index already used: {crawlCam.camIndex}") + + self.camFromIndex = dict(sorted(self.camFromIndex.items())) + if list(self.camFromIndex.keys()) != list(range(len(self.camFromIndex))): + raise PluginError("ERROR: The camera indices are not consecutive!") + + i = 0 + for val in self.camFromIndex.values(): + if isinstance(val, CrawlspaceData): + val.arrayIndex = i + i += 6 # crawlspaces are using 6 entries in the data array + elif val.hasPosData: + val.arrayIndex = i + i += 3 def getDataArrayC(self): """Returns the camera data/crawlspace positions array""" @@ -101,13 +124,14 @@ def getDataArrayC(self): posData.header = f"extern {listName};\n" # .c - posData.source = ( - (listName + " = {\n") - + "\n".join(cam.camData.getEntryC() for cam in self.bgCamInfoList if cam.hasPosData) - + ("\n" if len(self.bgCamInfoList) > 0 else "") - + "\n".join(crawlspace.getDataEntryC() for crawlspace in self.crawlspacePosList) - + "};\n\n" - ) + posData.source = listName + " = {\n" + for val in self.camFromIndex.values(): + if isinstance(val, CrawlspaceData): + posData.source += val.getDataEntryC() + "\n" + elif val.hasPosData: + posData.source += val.camData.getEntryC() + "\n" + posData.source = posData.source[:-1] # remove extra newline + posData.source += "};\n\n" return posData @@ -123,8 +147,7 @@ def getInfoArrayC(self): # .c bgCamInfoData.source = ( (listName + " = {\n") - + "".join(cam.getEntryC(self.posDataName) for cam in self.bgCamInfoList) - + "".join(crawlspace.getInfoEntryC(self.posDataName) for crawlspace in self.crawlspacePosList) + + "".join(val.getInfoEntryC(self.posDataName) for val in self.camFromIndex.values()) + "};\n\n" ) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index ec27a6cb9..cc3c593fa 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -68,7 +68,7 @@ def getEntryC(self): ) -@dataclass(eq=True) +@dataclass class SurfaceType: """This class defines a single surface type""" @@ -219,8 +219,9 @@ def getEntryC(self): class CrawlspaceData: """This class defines camera data for crawlspaces, if used""" - points: list[tuple[int, int, int]] = field(default_factory=list) - arrayIndex: int = None + points: list[tuple[int, int, int]] + camIndex: int + arrayIndex: int = 0 def __post_init__(self): self.points = [self.points[0], self.points[0], self.points[0], self.points[1], self.points[1], self.points[1]] @@ -242,14 +243,15 @@ class BgCamInfo: setting: str count: int - arrayIndex: int camData: BgCamFuncData + camIndex: int + arrayIndex: int = 0 hasPosData: bool = False def __post_init__(self): self.hasPosData = self.camData is not None - def getEntryC(self, posDataName: str): + def getInfoEntryC(self, posDataName: str): """Returns an entry for the camera information array""" ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index 12fc446dd..4836e4583 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -166,7 +166,7 @@ def getColSurfaceVtxDataFromMeshObj(self): colProp.ignoreProjectileCollision, useConveyor, normal, - distance + distance, ) ) @@ -199,7 +199,7 @@ def getBgCamFuncDataFromObjects(self, camObj: Object): camObj.ootCameraPositionProperty.bgImageOverrideIndex, ) - def getCrawlspaceDataFromObjects(self, startIndex: int): + def getCrawlspaceDataFromObjects(self): """Returns a list of rawlspace data from every splines objects with the type 'Crawlspace'""" crawlspaceList: list[CrawlspaceData] = [] @@ -209,7 +209,6 @@ def getCrawlspaceDataFromObjects(self, startIndex: int): if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" ] - index = startIndex for obj in crawlspaceObjList: if self.validateCurveData(obj): crawlspaceList.append( @@ -218,10 +217,9 @@ def getCrawlspaceDataFromObjects(self, startIndex: int): [round(value) for value in self.transform @ obj.matrix_world @ point.co] for point in obj.data.splines[0].points ], - index, + obj.ootSplineProperty.index, ) ) - index += 6 # crawlspaces are using 6 entries in the data array return crawlspaceList def getBgCamInfoDataFromObjects(self): @@ -253,14 +251,12 @@ def getBgCamInfoDataFromObjects(self): camInfoData[camProp.index] = BgCamInfo( setting, count, - index, camPosData[camProp.index] if camProp.hasPositionData else None, + camProp.index, ) index += count - return ( - [camInfoData[i] for i in range(min(camInfoData.keys()), len(camInfoData))] if len(camInfoData) > 0 else [] - ) + return list(camInfoData.values()) def getWaterBoxDataFromObjects(self): """Returns a list of waterbox data from waterbox empty objects""" diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene.py index 32cd1bf2d..074d59589 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene.py @@ -42,11 +42,6 @@ def getNewCollisionHeader(self): colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() bgCamInfoList = self.getBgCamInfoDataFromObjects() - startIndex = 0 - for elem in bgCamInfoList: - if elem.count != 0: # 0 means no pos data - startIndex += elem.count - return OOTSceneCollisionHeader( f"{self.name}_collisionHeader", colBounds[0], @@ -58,7 +53,7 @@ def getNewCollisionHeader(self): f"{self.name}_bgCamInfo", f"{self.name}_camPosData", bgCamInfoList, - self.getCrawlspaceDataFromObjects(startIndex), + self.getCrawlspaceDataFromObjects(), ), CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), ) From 5df9652c1beec9320bf729c55876369f858a8633 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:11:10 +0200 Subject: [PATCH 38/98] fixed entrance actors order --- fast64_internal/oot/new_exporter/common/scene.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/common/scene.py index b88fc9d8c..35c399cdd 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/common/scene.py @@ -131,7 +131,7 @@ def getTransActorListFromProps(self): def getEntranceActorListFromProps(self): """Returns the entrance actor list based on empty objects with the type 'Entrance'""" - actorList: list[EntranceActor] = [] + entranceActorFromIndex: dict[int, EntranceActor] = {} actorObjList: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" ] @@ -162,8 +162,17 @@ def getEntranceActorListFromProps(self): entranceActor.params = entranceProp.actor.actorParam entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex entranceActor.spawnIndex = entranceProp.spawnIndex - actorList.append(entranceActor) - return actorList + + if not entranceProp.spawnIndex in entranceActorFromIndex: + entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor + else: + raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") + + entranceActorFromIndex = dict(sorted(entranceActorFromIndex.items())) + if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): + raise PluginError("ERROR: The spawn indices are not consecutive!") + + return list(entranceActorFromIndex.values()) def getPathListFromProps(self, listNameBase: str): """Returns the pathway list from spline objects with the type 'Path'""" From 2a1cb5b2986c74921711c4bc3cfe23057451fd60 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:41:08 +0200 Subject: [PATCH 39/98] macro toggle --- fast64_internal/oot/new_exporter/collision_classes.py | 6 +++--- fast64_internal/oot/new_exporter/common/classes.py | 1 + fast64_internal/oot/new_exporter/common/collision.py | 3 +++ fast64_internal/oot/new_exporter/exporter.py | 6 ++++-- fast64_internal/oot/new_exporter/room.py | 1 + fast64_internal/oot/new_exporter/room_classes.py | 3 ++- fast64_internal/oot/scene/operators.py | 1 + fast64_internal/oot/scene/properties.py | 3 +++ 8 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision_classes.py index cc3c593fa..ef5236c22 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision_classes.py @@ -14,8 +14,8 @@ class CollisionPoly: enableConveyor: bool normal: Vector dist: int + useMacros: bool type: int = None - useMacros: bool = True def getFlags_vIA(self): """Returns the value of ``flags_vIA``""" @@ -91,7 +91,7 @@ class SurfaceType: isWallDamage: bool # unk27 conveyorKeepMomentum: bool - useMacros: bool = True + useMacros: bool isSoftC: str = None isHorseBlockedC: str = None canHookshotC: str = None @@ -271,13 +271,13 @@ class WaterBox: roomIndex: int setFlag19: bool + useMacros: bool xMin: int = None ySurface: int = None zMin: int = None xLength: int = None zLength: int = None - useMacros: bool = True setFlag19C: str = None roomIndexC: str = None diff --git a/fast64_internal/oot/new_exporter/common/classes.py b/fast64_internal/oot/new_exporter/common/classes.py index 30cca5419..2a5a8bd1c 100644 --- a/fast64_internal/oot/new_exporter/common/classes.py +++ b/fast64_internal/oot/new_exporter/common/classes.py @@ -16,6 +16,7 @@ class Common: sceneObj: Object transform: Matrix + useMacros: bool roomIndex: int = None def getRoomObjectFromChild(self, childObj: Object) -> Object | None: diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/common/collision.py index 4836e4583..41e69c033 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/common/collision.py @@ -153,6 +153,7 @@ def getColSurfaceVtxDataFromMeshObj(self): int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, colProp.isWallDamage, colProp.conveyorKeepMomentum if useConveyor else False, + self.useMacros, ) if not surfaceType in colPolyFromSurfaceType: @@ -167,6 +168,7 @@ def getColSurfaceVtxDataFromMeshObj(self): useConveyor, normal, distance, + self.useMacros, ) ) @@ -282,6 +284,7 @@ def getWaterBoxDataFromObjects(self): wboxProp.lighting, roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, wboxProp.flag19, + self.useMacros, ) ) diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 989005ae0..b037f4fb1 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -55,6 +55,7 @@ class OOTSceneExport: isSingleFile: bool isHWv1: bool textureExportSettings: TextureExportSettings + useMacros: bool dlFormat: DLFormat = DLFormat.Static sceneObj: Object = None @@ -87,6 +88,7 @@ def getNewRoomList(self, scene: OOTScene): roomDict[roomIndex] = OOTRoom( self.sceneObj, self.transform, + self.useMacros, roomIndex, roomName, roomObj, @@ -103,7 +105,7 @@ def getNewRoomList(self, scene: OOTScene): ) # Mesh stuff - c = Common(self.sceneObj, self.transform) + c = Common(self.sceneObj, self.transform, self.useMacros) pos, _, scale, _ = c.getConvertedTransform(self.transform, self.sceneObj, roomObj, True) cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) DLGroup = roomDict[roomIndex].mesh.addMeshGroup(cullGroup).DLGroup @@ -174,7 +176,7 @@ def getNewScene(self): try: altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = OOTScene(self.sceneObj, self.transform, name=f"{toAlnum(self.sceneName)}_scene") + sceneData = OOTScene(self.sceneObj, self.transform, self.useMacros, name=f"{toAlnum(self.sceneName)}_scene") sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room.py index 727159fed..d7e0271f8 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room.py @@ -136,6 +136,7 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = self.roomObj, self.transform, headerIndex, + self.useMacros, ), ) diff --git a/fast64_internal/oot/new_exporter/room_classes.py b/fast64_internal/oot/new_exporter/room_classes.py index 1cea0290a..774eabe99 100644 --- a/fast64_internal/oot/new_exporter/room_classes.py +++ b/fast64_internal/oot/new_exporter/room_classes.py @@ -80,6 +80,7 @@ class OOTRoomHeaderActors: roomObj: Object transform: Matrix headerIndex: int + useMacros: bool actorList: list[Actor] = field(default_factory=list) def __post_init__(self): @@ -88,7 +89,7 @@ def __post_init__(self): ] for obj in actorObjList: actorProp = obj.ootActorProperty - c = Common(self.sceneObj, self.transform) + c = Common(self.sceneObj, self.transform, self.useMacros) if not c.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index ba88aad10..62b7058c7 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -190,6 +190,7 @@ def execute(self, context): settings.singleFile, context.scene.isHWv1, TextureExportSettings(False, context.scene.saveTextures, None, None), + settings.useMacros, ).export() else: ootExportSceneToC( diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index d195fec01..738773c19 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -480,6 +480,7 @@ class OOTExportSceneSettingsProperty(PropertyGroup): option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE") useNewExporter: BoolProperty(name="Use Experimental Exporter", default=True) + useMacros: BoolProperty(name="Use Decomp Macros", default=True) def draw_props(self, layout: UILayout): if self.customExport: @@ -496,6 +497,8 @@ def draw_props(self, layout: UILayout): layout.prop(self, "singleFile") layout.prop(self, "customExport") layout.prop(self, "useNewExporter") + if self.useNewExporter: + layout.prop(self, "useMacros") class OOTImportSceneSettingsProperty(PropertyGroup): From 155df9d9ffa32d8d50eb72ae1258096f8d962fd5 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:50:42 +0200 Subject: [PATCH 40/98] fixed include duplicate --- .../oot/new_exporter/exporter_classes.py | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/fast64_internal/oot/new_exporter/exporter_classes.py b/fast64_internal/oot/new_exporter/exporter_classes.py index f8d685bba..cb4f4cd7e 100644 --- a/fast64_internal/oot/new_exporter/exporter_classes.py +++ b/fast64_internal/oot/new_exporter/exporter_classes.py @@ -51,11 +51,15 @@ def __post_init__(self): self.hasCutscenes = len(self.sceneCutscenes) > 0 self.hasSceneTextures = len(self.sceneTextures) > 0 + def getSourceWithSceneInclude(self, sceneInclude: str, source: str, includeSrc: str): + if not sceneInclude in source: + includeSrc += sceneInclude + return includeSrc + source + def setIncludeData(self): """Adds includes at the beginning of each file to write""" - suffix = "\n\n" - sceneInclude = f'\n#include "{self.name}.h"\n' + sceneInclude = f'\n#include "{self.name}.h"\n\n\n' common = includeData["common"] # room = includeData["roomMain"] # roomShapeInfo = includeData["roomShapeInfo"] @@ -79,24 +83,33 @@ def setIncludeData(self): ) for roomData in self.roomList.values(): + roomMain = common + room + if self.singleFileExport: - common += room + roomShapeInfo + sceneInclude - roomData.roomMain = common + suffix + roomData.roomMain + roomMain += roomShapeInfo else: - roomData.roomMain = common + room + sceneInclude + suffix + roomData.roomMain - roomData.roomModelInfo = common + roomShapeInfo + sceneInclude + suffix + roomData.roomModelInfo - roomData.roomModel = common + sceneInclude + suffix + roomData.roomModel - + roomModelInfo = common + room + roomModel = common + room + roomData.roomModelInfo = self.getSourceWithSceneInclude( + sceneInclude, roomData.roomModelInfo, roomModelInfo + ) + roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel, roomModel) + roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain, roomMain) + + sceneMain = common + scene if self.singleFileExport: - common += scene + collision + cutscene + sceneInclude - self.sceneMain = common + suffix + self.sceneMain + sceneMain += collision + cutscene else: - self.sceneMain = common + scene + sceneInclude + suffix + self.sceneMain - self.sceneCollision = common + collision + sceneInclude + suffix + self.sceneCollision + sceneCollision = common + collision + sceneTextures = common + self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision, sceneCollision) + self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures, sceneTextures) if self.hasCutscenes: for cs in self.sceneCutscenes: - cs = cutscene + sceneInclude + suffix + cs + csInclude = cutscene + cs = self.getSourceWithSceneInclude(sceneInclude, cs, csInclude) + self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain, sceneMain) def write(self): self.setIncludeData() @@ -107,10 +120,9 @@ def write(self): if self.singleFileExport: sceneMainPath = f"{self.name}.c" - self.sceneMain += self.sceneCollision if self.hasCutscenes: - for i, cs in enumerate(self.sceneCutscenes): - self.sceneMain += cs + self.sceneMain += "".join(cs for cs in self.sceneCutscenes) + self.sceneMain += self.sceneCollision else: sceneMainPath = f"{self.name}_main.c" writeFile(os.path.join(self.path, f"{self.name}_col.c"), self.sceneCollision) From aece358b44d46ce7605062f86ba630095400d395 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 5 Oct 2023 00:42:37 +0200 Subject: [PATCH 41/98] "fix" includes (using z64.h for now) --- .../oot/new_exporter/common/__init__.py | 65 ------------------ .../oot/new_exporter/exporter_classes.py | 67 ++++++------------- 2 files changed, 21 insertions(+), 111 deletions(-) diff --git a/fast64_internal/oot/new_exporter/common/__init__.py b/fast64_internal/oot/new_exporter/common/__init__.py index 8b9cbdc37..4a961b9d4 100644 --- a/fast64_internal/oot/new_exporter/common/__init__.py +++ b/fast64_internal/oot/new_exporter/common/__init__.py @@ -1,68 +1,3 @@ from .classes import Common, altHeaderList from .collision import CollisionCommon from .scene import SceneCommon - - -includeData = { - "common": ( - "\n".join( - [ - '#include "ultra64/ultratypes.h"', - '#include "ultra64/gbi.h"', - '#include "libc/stddef.h"', - '#include "libc/stdint.h"', - '#include "z64math.h"', - ] - ) - + "\n" - ), - "roomMain": ( - "\n".join( - [ - '#include "z64object.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - ] - ) - + "\n" - ), - "roomShapeInfo": ( - "\n".join( - [ - '#include "macros.h"', - '#include "z64scene.h"', - ] - ) - + "\n" - ), - "sceneMain": ( - "\n".join( - [ - '#include "z64dma.h"', - '#include "z64actor.h"', - '#include "z64scene.h"', - '#include "z64environment.h"', - ] - ) - + "\n" - ), - "collision": ( - "\n".join( - [ - '#include "macros.h"', - '#include "z64camera.h"', - '#include "z64bgcheck.h"', - ] - ) - + "\n" - ), - "cutscene": ( - "\n".join( - [ - '#include "z64cutscene.h"', - '#include "z64cutscene_commands.h"', - ] - ) - + "\n" - ), -} diff --git a/fast64_internal/oot/new_exporter/exporter_classes.py b/fast64_internal/oot/new_exporter/exporter_classes.py index cb4f4cd7e..b344e0080 100644 --- a/fast64_internal/oot/new_exporter/exporter_classes.py +++ b/fast64_internal/oot/new_exporter/exporter_classes.py @@ -2,7 +2,6 @@ from dataclasses import dataclass, field from ...utility import writeFile -from .common import includeData @dataclass @@ -60,56 +59,32 @@ def setIncludeData(self): """Adds includes at the beginning of each file to write""" sceneInclude = f'\n#include "{self.name}.h"\n\n\n' - common = includeData["common"] - # room = includeData["roomMain"] - # roomShapeInfo = includeData["roomShapeInfo"] - # scene = includeData["sceneMain"] - # collision = includeData["collision"] - # cutscene = includeData["cutscene"] - room = "" - roomShapeInfo = "" - scene = "" - collision = "" - cutscene = "" - - common = ( - '#include "ultra64.h"\n' - + '#include "z64.h"\n' - + '#include "macros.h"\n' - + '#include "segment_symbols.h"\n' - + '#include "command_macros_base.h"\n' - + '#include "z64cutscene_commands.h"\n' - + '#include "variables.h"\n' - ) + includes = "\n".join( + [ + '#include "ultra64/ultratypes.h"', + '#include "ultra64/gbi.h"', + '#include "libc/stddef.h"', + '#include "libc/stdint.h"', + '#include "macros.h"', + '#include "z64.h"', + ] + ) + "\n" for roomData in self.roomList.values(): - roomMain = common + room - - if self.singleFileExport: - roomMain += roomShapeInfo - else: - roomModelInfo = common + room - roomModel = common + room - roomData.roomModelInfo = self.getSourceWithSceneInclude( - sceneInclude, roomData.roomModelInfo, roomModelInfo - ) - roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel, roomModel) - roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain, roomMain) - - sceneMain = common + scene - if self.singleFileExport: - sceneMain += collision + cutscene - else: - sceneCollision = common + collision - sceneTextures = common - self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision, sceneCollision) - self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures, sceneTextures) + roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain, includes) + + if not self.singleFileExport: + roomData.roomModelInfo = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModelInfo, includes) + roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel, includes) + + self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain, includes) + if not self.singleFileExport: + self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision, includes) + self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures, includes) if self.hasCutscenes: for cs in self.sceneCutscenes: - csInclude = cutscene - cs = self.getSourceWithSceneInclude(sceneInclude, cs, csInclude) - self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain, sceneMain) + cs = self.getSourceWithSceneInclude(sceneInclude, cs, includes) def write(self): self.setIncludeData() From 6e5bb0b3fe93b74b896d62558de82201ace40ae7 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:33:09 +0200 Subject: [PATCH 42/98] re-organised the files, fixed imports --- fast64_internal/oot/new_exporter/actors.py | 71 ------------------- .../oot/new_exporter/collision/__init__.py | 10 +++ .../collision.py => collision/base.py} | 18 ++--- .../classes.py} | 4 +- .../{collision.py => collision/header.py} | 12 ++-- fast64_internal/oot/new_exporter/commands.py | 7 +- .../{common/classes.py => common.py} | 38 ++++++++-- .../oot/new_exporter/common/__init__.py | 3 - fast64_internal/oot/new_exporter/exporter.py | 10 ++- .../oot/new_exporter/exporter_classes.py | 23 +++--- .../oot/new_exporter/other/__init__.py | 1 + .../oot/new_exporter/{ => other}/file.py | 6 +- .../new_exporter/{ => other}/scene_table.py | 8 +-- .../oot/new_exporter/{ => other}/spec.py | 6 +- .../oot/new_exporter/room/__init__.py | 2 + .../{room_classes.py => room/header.py} | 9 ++- .../new_exporter/{room.py => room/main.py} | 38 +++++----- .../{room_shape.py => room/shape.py} | 6 +- .../oot/new_exporter/scene/__init__.py | 11 +++ .../{common/scene.py => scene/base.py} | 10 +-- .../{scene_classes.py => scene/classes.py} | 44 +++++++++++- .../{scene_header.py => scene/header.py} | 5 +- .../new_exporter/{scene.py => scene/main.py} | 24 +++---- 23 files changed, 192 insertions(+), 174 deletions(-) delete mode 100644 fast64_internal/oot/new_exporter/actors.py create mode 100644 fast64_internal/oot/new_exporter/collision/__init__.py rename fast64_internal/oot/new_exporter/{common/collision.py => collision/base.py} (98%) rename fast64_internal/oot/new_exporter/{collision_classes.py => collision/classes.py} (99%) rename fast64_internal/oot/new_exporter/{collision.py => collision/header.py} (98%) rename fast64_internal/oot/new_exporter/{common/classes.py => common.py} (79%) delete mode 100644 fast64_internal/oot/new_exporter/common/__init__.py create mode 100644 fast64_internal/oot/new_exporter/other/__init__.py rename fast64_internal/oot/new_exporter/{ => other}/file.py (92%) rename fast64_internal/oot/new_exporter/{ => other}/scene_table.py (98%) rename fast64_internal/oot/new_exporter/{ => other}/spec.py (97%) create mode 100644 fast64_internal/oot/new_exporter/room/__init__.py rename fast64_internal/oot/new_exporter/{room_classes.py => room/header.py} (96%) rename fast64_internal/oot/new_exporter/{room.py => room/main.py} (94%) rename fast64_internal/oot/new_exporter/{room_shape.py => room/shape.py} (98%) create mode 100644 fast64_internal/oot/new_exporter/scene/__init__.py rename fast64_internal/oot/new_exporter/{common/scene.py => scene/base.py} (97%) rename fast64_internal/oot/new_exporter/{scene_classes.py => scene/classes.py} (72%) rename fast64_internal/oot/new_exporter/{scene_header.py => scene/header.py} (97%) rename fast64_internal/oot/new_exporter/{scene.py => scene/main.py} (95%) diff --git a/fast64_internal/oot/new_exporter/actors.py b/fast64_internal/oot/new_exporter/actors.py deleted file mode 100644 index 06f7fbc56..000000000 --- a/fast64_internal/oot/new_exporter/actors.py +++ /dev/null @@ -1,71 +0,0 @@ -from dataclasses import dataclass, field -from ...utility import indent - - -@dataclass -class Actor: - """Defines an Actor""" - - name: str = None - id: str = None - pos: list[int] = field(default_factory=list) - rot: str = None - params: str = None - - def getActorEntry(self): - """Returns a single actor entry""" - - posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" - rotData = "{ " + self.rot + " }" - - actorInfos = [self.id, posData, rotData, self.params] - infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] - - return ( - indent - + (f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class TransitionActor(Actor): - """Defines a Transition Actor""" - - dontTransition: bool = None - roomFrom: int = None - roomTo: int = None - cameraFront: str = None - cameraBack: str = None - - def getTransitionActorEntry(self): - """Returns a single transition actor entry""" - - sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] - roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" - - actorInfos = [roomData, self.id, posData, self.rot, self.params] - infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] - - return ( - (indent + f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class EntranceActor(Actor): - """Defines an Entrance Actor""" - - roomIndex: int = None - spawnIndex: int = None - - def getSpawnEntry(self): - """Returns a single spawn entry""" - - return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" diff --git a/fast64_internal/oot/new_exporter/collision/__init__.py b/fast64_internal/oot/new_exporter/collision/__init__.py new file mode 100644 index 000000000..d4f38cfa7 --- /dev/null +++ b/fast64_internal/oot/new_exporter/collision/__init__.py @@ -0,0 +1,10 @@ +from .base import CollisionBase + +from .header import ( + OOTSceneCollisionHeader, + CollisionHeaderVertices, + CollisionHeaderCollisionPoly, + CollisionHeaderSurfaceType, + CollisionHeaderBgCamInfo, + CollisionHeaderWaterBox, +) diff --git a/fast64_internal/oot/new_exporter/common/collision.py b/fast64_internal/oot/new_exporter/collision/base.py similarity index 98% rename from fast64_internal/oot/new_exporter/common/collision.py rename to fast64_internal/oot/new_exporter/collision/base.py index 41e69c033..e338ac78e 100644 --- a/fast64_internal/oot/new_exporter/common/collision.py +++ b/fast64_internal/oot/new_exporter/collision/base.py @@ -1,27 +1,27 @@ import math from dataclasses import dataclass -from mathutils import Vector, Quaternion, Matrix -from bpy.types import Object, Mesh +from mathutils import Matrix, Quaternion, Vector +from bpy.types import Mesh, Object from bpy.ops import object from ....utility import PluginError, checkIdentityRotation from ...oot_utility import convertIntTo2sComplement from ...oot_collision_classes import decomp_compat_map_CameraSType -from .classes import Common +from ..common import Base -from ..collision_classes import ( - SurfaceType, +from .classes import ( CollisionPoly, - Vertex, - WaterBox, - BgCamInfo, + SurfaceType, BgCamFuncData, CrawlspaceData, + BgCamInfo, + WaterBox, + Vertex, ) @dataclass -class CollisionCommon(Common): +class CollisionBase(Base): """This class hosts different functions used to convert mesh data""" def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): diff --git a/fast64_internal/oot/new_exporter/collision_classes.py b/fast64_internal/oot/new_exporter/collision/classes.py similarity index 99% rename from fast64_internal/oot/new_exporter/collision_classes.py rename to fast64_internal/oot/new_exporter/collision/classes.py index ef5236c22..28b3c415a 100644 --- a/fast64_internal/oot/new_exporter/collision_classes.py +++ b/fast64_internal/oot/new_exporter/collision/classes.py @@ -1,6 +1,6 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from mathutils import Vector -from ...utility import PluginError, indent +from ....utility import PluginError, indent @dataclass diff --git a/fast64_internal/oot/new_exporter/collision.py b/fast64_internal/oot/new_exporter/collision/header.py similarity index 98% rename from fast64_internal/oot/new_exporter/collision.py rename to fast64_internal/oot/new_exporter/collision/header.py index bbb3353ca..29ead668e 100644 --- a/fast64_internal/oot/new_exporter/collision.py +++ b/fast64_internal/oot/new_exporter/collision/header.py @@ -1,13 +1,13 @@ from dataclasses import dataclass, field -from ...utility import PluginError, CData, indent +from ....utility import PluginError, CData, indent -from .collision_classes import ( - SurfaceType, +from .classes import ( CollisionPoly, - Vertex, - WaterBox, - BgCamInfo, + SurfaceType, CrawlspaceData, + BgCamInfo, + WaterBox, + Vertex, ) diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 0118180ce..762b1ad89 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -3,10 +3,9 @@ if TYPE_CHECKING: from .collision import OOTSceneCollisionHeader - from .room import OOTRoom - from .room_classes import OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors - from .scene import OOTScene - from .scene_header import ( + from .room import OOTRoom, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors + from .scene import ( + OOTScene, OOTSceneHeaderInfos, OOTSceneHeader, OOTSceneHeaderLighting, diff --git a/fast64_internal/oot/new_exporter/common/classes.py b/fast64_internal/oot/new_exporter/common.py similarity index 79% rename from fast64_internal/oot/new_exporter/common/classes.py rename to fast64_internal/oot/new_exporter/common.py index 2a5a8bd1c..f956bd784 100644 --- a/fast64_internal/oot/new_exporter/common/classes.py +++ b/fast64_internal/oot/new_exporter/common.py @@ -1,17 +1,17 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from math import radians from mathutils import Quaternion, Matrix from bpy.types import Object -from ....utility import PluginError, indent -from ...oot_utility import ootConvertTranslation, ootConvertRotation -from ...actor.properties import OOTActorHeaderProperty +from ...utility import PluginError, indent +from ..oot_utility import ootConvertTranslation, ootConvertRotation +from ..actor.properties import OOTActorHeaderProperty altHeaderList = ["childNight", "adultDay", "adultNight"] @dataclass -class Common: +class Base: """This class hosts common data used across different sub-systems of this exporter""" sceneObj: Object @@ -103,3 +103,31 @@ def getEndCmd(self): """Returns the scene end command""" return indent + "SCENE_CMD_END(),\n" + + +@dataclass +class Actor: + """Defines an Actor""" + + name: str = None + id: str = None + pos: list[int] = field(default_factory=list) + rot: str = None + params: str = None + + def getActorEntry(self): + """Returns a single actor entry""" + + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" + rotData = "{ " + self.rot + " }" + + actorInfos = [self.id, posData, rotData, self.params] + infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] + + return ( + indent + + (f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) diff --git a/fast64_internal/oot/new_exporter/common/__init__.py b/fast64_internal/oot/new_exporter/common/__init__.py deleted file mode 100644 index 4a961b9d4..000000000 --- a/fast64_internal/oot/new_exporter/common/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .classes import Common, altHeaderList -from .collision import CollisionCommon -from .scene import SceneCommon diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index b037f4fb1..6117166be 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -14,11 +14,10 @@ from ..oot_f3d_writer import writeTextureArraysNew from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh from ..oot_utility import CullGroup -from .common import Common, altHeaderList -from .scene import OOTScene -from .scene_header import OOTSceneAlternateHeader +from .common import Base, altHeaderList +from .scene import OOTScene, OOTSceneAlternateHeader from .room import OOTRoom, OOTRoomAlternateHeader -from .file import Files +from .other import Files from .exporter_classes import SceneFile from ...utility import ( @@ -27,7 +26,6 @@ unhideAllAndGetHiddenState, restoreHiddenState, toAlnum, - writeFile, ) from ..oot_utility import ( @@ -105,7 +103,7 @@ def getNewRoomList(self, scene: OOTScene): ) # Mesh stuff - c = Common(self.sceneObj, self.transform, self.useMacros) + c = Base(self.sceneObj, self.transform, self.useMacros) pos, _, scale, _ = c.getConvertedTransform(self.transform, self.sceneObj, roomObj, True) cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) DLGroup = roomDict[roomIndex].mesh.addMeshGroup(cullGroup).DLGroup diff --git a/fast64_internal/oot/new_exporter/exporter_classes.py b/fast64_internal/oot/new_exporter/exporter_classes.py index b344e0080..76a405aa2 100644 --- a/fast64_internal/oot/new_exporter/exporter_classes.py +++ b/fast64_internal/oot/new_exporter/exporter_classes.py @@ -59,16 +59,19 @@ def setIncludeData(self): """Adds includes at the beginning of each file to write""" sceneInclude = f'\n#include "{self.name}.h"\n\n\n' - includes = "\n".join( - [ - '#include "ultra64/ultratypes.h"', - '#include "ultra64/gbi.h"', - '#include "libc/stddef.h"', - '#include "libc/stdint.h"', - '#include "macros.h"', - '#include "z64.h"', - ] - ) + "\n" + includes = ( + "\n".join( + [ + '#include "ultra64/ultratypes.h"', + '#include "ultra64/gbi.h"', + '#include "libc/stddef.h"', + '#include "libc/stdint.h"', + '#include "macros.h"', + '#include "z64.h"', + ] + ) + + "\n" + ) for roomData in self.roomList.values(): roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain, includes) diff --git a/fast64_internal/oot/new_exporter/other/__init__.py b/fast64_internal/oot/new_exporter/other/__init__.py new file mode 100644 index 000000000..8ce89661f --- /dev/null +++ b/fast64_internal/oot/new_exporter/other/__init__.py @@ -0,0 +1 @@ +from .file import Files diff --git a/fast64_internal/oot/new_exporter/file.py b/fast64_internal/oot/new_exporter/other/file.py similarity index 92% rename from fast64_internal/oot/new_exporter/file.py rename to fast64_internal/oot/new_exporter/other/file.py index 03b47ed55..f19dbd264 100644 --- a/fast64_internal/oot/new_exporter/file.py +++ b/fast64_internal/oot/new_exporter/other/file.py @@ -3,12 +3,12 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from ..oot_utility import ExportInfo, getSceneDirFromLevelName -from .spec import Spec +from ...oot_utility import getSceneDirFromLevelName from .scene_table import SceneTable +from .spec import Spec if TYPE_CHECKING: - from .exporter import OOTSceneExport + from ..exporter import OOTSceneExport @dataclass diff --git a/fast64_internal/oot/new_exporter/scene_table.py b/fast64_internal/oot/new_exporter/other/scene_table.py similarity index 98% rename from fast64_internal/oot/new_exporter/scene_table.py rename to fast64_internal/oot/new_exporter/other/scene_table.py index 4ff96354f..1e4806d59 100644 --- a/fast64_internal/oot/new_exporter/scene_table.py +++ b/fast64_internal/oot/new_exporter/other/scene_table.py @@ -2,12 +2,12 @@ import bpy from typing import TYPE_CHECKING -from ...utility import PluginError, writeFile -from ..oot_constants import ootEnumSceneID, ootSceneNameToID -from ..oot_utility import ExportInfo +from ....utility import PluginError, writeFile +from ...oot_constants import ootEnumSceneID, ootSceneNameToID +from ...oot_utility import ExportInfo if TYPE_CHECKING: - from .exporter import OOTSceneExport + from ..exporter import OOTSceneExport class SceneTable: diff --git a/fast64_internal/oot/new_exporter/spec.py b/fast64_internal/oot/new_exporter/other/spec.py similarity index 97% rename from fast64_internal/oot/new_exporter/spec.py rename to fast64_internal/oot/new_exporter/other/spec.py index 5d1a8c3ed..018ed641c 100644 --- a/fast64_internal/oot/new_exporter/spec.py +++ b/fast64_internal/oot/new_exporter/other/spec.py @@ -2,11 +2,11 @@ import re from typing import TYPE_CHECKING -from ...utility import readFile, writeFile, indent -from ..oot_utility import ExportInfo, getSceneDirFromLevelName +from ....utility import readFile, writeFile, indent +from ...oot_utility import ExportInfo, getSceneDirFromLevelName if TYPE_CHECKING: - from .exporter import OOTSceneExport + from ..exporter import OOTSceneExport class Spec: diff --git a/fast64_internal/oot/new_exporter/room/__init__.py b/fast64_internal/oot/new_exporter/room/__init__.py new file mode 100644 index 000000000..cd75cdb06 --- /dev/null +++ b/fast64_internal/oot/new_exporter/room/__init__.py @@ -0,0 +1,2 @@ +from .header import OOTRoomAlternateHeader, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors +from .main import OOTRoom diff --git a/fast64_internal/oot/new_exporter/room_classes.py b/fast64_internal/oot/new_exporter/room/header.py similarity index 96% rename from fast64_internal/oot/new_exporter/room_classes.py rename to fast64_internal/oot/new_exporter/room/header.py index 774eabe99..5b32d4114 100644 --- a/fast64_internal/oot/new_exporter/room_classes.py +++ b/fast64_internal/oot/new_exporter/room/header.py @@ -1,10 +1,9 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from ...utility import CData, indent -from ..oot_constants import ootData -from .common import Common -from .actors import Actor +from ....utility import CData, indent +from ...oot_constants import ootData +from ..common import Base, Actor @dataclass @@ -89,7 +88,7 @@ def __post_init__(self): ] for obj in actorObjList: actorProp = obj.ootActorProperty - c = Common(self.sceneObj, self.transform, self.useMacros) + c = Base(self.sceneObj, self.transform, self.useMacros) if not c.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue diff --git a/fast64_internal/oot/new_exporter/room.py b/fast64_internal/oot/new_exporter/room/main.py similarity index 94% rename from fast64_internal/oot/new_exporter/room.py rename to fast64_internal/oot/new_exporter/room/main.py index d7e0271f8..6c8e0d733 100644 --- a/fast64_internal/oot/new_exporter/room.py +++ b/fast64_internal/oot/new_exporter/room/main.py @@ -1,37 +1,37 @@ from dataclasses import dataclass from bpy.types import Object -from ...utility import PluginError, CData, toAlnum, indent -from ...f3d.f3d_gbi import TextureExportSettings, ScrollMethod -from ..room.properties import OOTRoomHeaderProperty -from ..oot_constants import ootData -from ..oot_level_classes import OOTRoomMesh, OOTBGImage -from ..oot_model_classes import OOTModel, OOTGfxFormatter -from .commands import RoomCommands -from .common import Common, altHeaderList -from .exporter_classes import RoomFile - -from .room_shape import ( - RoomShape, - RoomShapeDLists, +from ....utility import PluginError, CData, toAlnum, indent +from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings +from ...room.properties import OOTRoomHeaderProperty +from ...oot_constants import ootData +from ...oot_level_classes import OOTBGImage, OOTRoomMesh +from ...oot_model_classes import OOTModel, OOTGfxFormatter +from ..commands import RoomCommands +from ..exporter_classes import RoomFile +from ..common import Base, altHeaderList + +from .shape import ( RoomShapeDListsEntry, - RoomShapeImageMulti, - RoomShapeImageMultiBg, RoomShapeImageMultiBgEntry, + RoomShapeImageMultiBg, + RoomShapeDLists, RoomShapeImageSingle, + RoomShapeImageMulti, RoomShapeNormal, + RoomShape, ) -from .room_classes import ( - OOTRoomHeader, - OOTRoomAlternateHeader, +from .header import ( OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors, + OOTRoomAlternateHeader, + OOTRoomHeader, ) @dataclass -class OOTRoom(Common, RoomCommands): +class OOTRoom(Base, RoomCommands): """This class defines a room""" name: str = None diff --git a/fast64_internal/oot/new_exporter/room_shape.py b/fast64_internal/oot/new_exporter/room/shape.py similarity index 98% rename from fast64_internal/oot/new_exporter/room_shape.py rename to fast64_internal/oot/new_exporter/room/shape.py index 7d2d05848..983602b97 100644 --- a/fast64_internal/oot/new_exporter/room_shape.py +++ b/fast64_internal/oot/new_exporter/room/shape.py @@ -1,7 +1,7 @@ from dataclasses import dataclass -from ...utility import CData, indent -from ...f3d.f3d_gbi import TextureExportSettings -from ..oot_level_classes import OOTRoomMesh +from ....utility import CData, indent +from ....f3d.f3d_gbi import TextureExportSettings +from ...oot_level_classes import OOTRoomMesh @dataclass diff --git a/fast64_internal/oot/new_exporter/scene/__init__.py b/fast64_internal/oot/new_exporter/scene/__init__.py new file mode 100644 index 000000000..f71025c41 --- /dev/null +++ b/fast64_internal/oot/new_exporter/scene/__init__.py @@ -0,0 +1,11 @@ +from .header import ( + OOTSceneHeaderInfos, + OOTSceneHeader, + OOTSceneHeaderLighting, + OOTSceneHeaderCutscene, + OOTSceneHeaderActors, + OOTSceneHeaderPath, + OOTSceneAlternateHeader, +) + +from .main import OOTScene diff --git a/fast64_internal/oot/new_exporter/common/scene.py b/fast64_internal/oot/new_exporter/scene/base.py similarity index 97% rename from fast64_internal/oot/new_exporter/common/scene.py rename to fast64_internal/oot/new_exporter/scene/base.py index 35c399cdd..510a88d61 100644 --- a/fast64_internal/oot/new_exporter/common/scene.py +++ b/fast64_internal/oot/new_exporter/scene/base.py @@ -6,17 +6,17 @@ from ...oot_constants import ootData from ...oot_model_classes import OOTModel from ..commands import SceneCommands -from ..scene_header import EnvLightSettings, Path, OOTSceneHeader, OOTSceneAlternateHeader -from ..actors import TransitionActor, EntranceActor -from .classes import altHeaderList -from .collision import CollisionCommon +from ..common import altHeaderList +from ..collision import CollisionBase +from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path +from .header import OOTSceneAlternateHeader, OOTSceneHeader if TYPE_CHECKING: from ..room import OOTRoom @dataclass -class SceneCommon(CollisionCommon, SceneCommands): +class SceneBase(CollisionBase, SceneCommands): """This class hosts various data and functions related to a scene file""" name: str = None diff --git a/fast64_internal/oot/new_exporter/scene_classes.py b/fast64_internal/oot/new_exporter/scene/classes.py similarity index 72% rename from fast64_internal/oot/new_exporter/scene_classes.py rename to fast64_internal/oot/new_exporter/scene/classes.py index 913954ed1..cbfd48ed3 100644 --- a/fast64_internal/oot/new_exporter/scene_classes.py +++ b/fast64_internal/oot/new_exporter/scene/classes.py @@ -1,5 +1,47 @@ from dataclasses import dataclass, field -from ...utility import CData, indent +from ....utility import CData, indent +from ..common import Actor + + +@dataclass +class TransitionActor(Actor): + """Defines a Transition Actor""" + + dontTransition: bool = None + roomFrom: int = None + roomTo: int = None + cameraFront: str = None + cameraBack: str = None + + def getTransitionActorEntry(self): + """Returns a single transition actor entry""" + + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] + roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" + posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" + + actorInfos = [roomData, self.id, posData, self.rot, self.params] + infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] + + return ( + (indent + f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class EntranceActor(Actor): + """Defines an Entrance Actor""" + + roomIndex: int = None + spawnIndex: int = None + + def getSpawnEntry(self): + """Returns a single spawn entry""" + + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" @dataclass diff --git a/fast64_internal/oot/new_exporter/scene_header.py b/fast64_internal/oot/new_exporter/scene/header.py similarity index 97% rename from fast64_internal/oot/new_exporter/scene_header.py rename to fast64_internal/oot/new_exporter/scene/header.py index cdfe53c28..6c831d660 100644 --- a/fast64_internal/oot/new_exporter/scene_header.py +++ b/fast64_internal/oot/new_exporter/scene/header.py @@ -1,8 +1,7 @@ from dataclasses import dataclass, field from bpy.types import Object -from ...utility import PluginError, CData, indent -from .actors import TransitionActor, EntranceActor -from .scene_classes import EnvLightSettings, Path +from ....utility import PluginError, CData, indent +from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path @dataclass diff --git a/fast64_internal/oot/new_exporter/scene.py b/fast64_internal/oot/new_exporter/scene/main.py similarity index 95% rename from fast64_internal/oot/new_exporter/scene.py rename to fast64_internal/oot/new_exporter/scene/main.py index 074d59589..05cc752a6 100644 --- a/fast64_internal/oot/new_exporter/scene.py +++ b/fast64_internal/oot/new_exporter/scene/main.py @@ -1,33 +1,33 @@ from dataclasses import dataclass -from ...utility import PluginError, CData, indent -from ...f3d.f3d_gbi import TextureExportSettings, ScrollMethod -from ..oot_model_classes import OOTGfxFormatter -from ..scene.properties import OOTSceneHeaderProperty -from .common import SceneCommon -from .exporter_classes import SceneFile - -from .collision import ( - OOTSceneCollisionHeader, +from ....utility import PluginError, CData, indent +from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod +from ...oot_model_classes import OOTGfxFormatter +from ...scene.properties import OOTSceneHeaderProperty +from ..exporter_classes import SceneFile +from .base import SceneBase + +from ..collision import ( CollisionHeaderVertices, CollisionHeaderCollisionPoly, CollisionHeaderSurfaceType, CollisionHeaderBgCamInfo, CollisionHeaderWaterBox, + OOTSceneCollisionHeader, ) -from .scene_header import ( - OOTSceneHeader, +from .header import ( OOTSceneHeaderInfos, OOTSceneHeaderLighting, OOTSceneHeaderCutscene, OOTSceneHeaderExits, OOTSceneHeaderActors, OOTSceneHeaderPath, + OOTSceneHeader, ) @dataclass -class OOTScene(SceneCommon): +class OOTScene(SceneBase): """This class defines a scene""" roomListName: str = None From ddcdce569aa48f0caee7a91effe0f8b62f8d30e6 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:43:35 +0200 Subject: [PATCH 43/98] class rename --- .../oot/new_exporter/collision/__init__.py | 12 ++-- .../oot/new_exporter/collision/header.py | 22 +++---- fast64_internal/oot/new_exporter/commands.py | 64 +++++++++---------- fast64_internal/oot/new_exporter/exporter.py | 18 +++--- .../oot/new_exporter/room/__init__.py | 4 +- .../oot/new_exporter/room/header.py | 36 +++++------ fast64_internal/oot/new_exporter/room/main.py | 30 ++++----- .../oot/new_exporter/scene/__init__.py | 16 ++--- .../oot/new_exporter/scene/base.py | 12 ++-- .../oot/new_exporter/scene/header.py | 48 +++++++------- .../oot/new_exporter/scene/main.py | 58 ++++++++--------- 11 files changed, 160 insertions(+), 160 deletions(-) diff --git a/fast64_internal/oot/new_exporter/collision/__init__.py b/fast64_internal/oot/new_exporter/collision/__init__.py index d4f38cfa7..2439e8745 100644 --- a/fast64_internal/oot/new_exporter/collision/__init__.py +++ b/fast64_internal/oot/new_exporter/collision/__init__.py @@ -1,10 +1,10 @@ from .base import CollisionBase from .header import ( - OOTSceneCollisionHeader, - CollisionHeaderVertices, - CollisionHeaderCollisionPoly, - CollisionHeaderSurfaceType, - CollisionHeaderBgCamInfo, - CollisionHeaderWaterBox, + CollisionHeader, + Vertices, + CollisionPolygons, + SurfaceTypes, + BgCamInformations, + WaterBoxes, ) diff --git a/fast64_internal/oot/new_exporter/collision/header.py b/fast64_internal/oot/new_exporter/collision/header.py index 29ead668e..ee97595e3 100644 --- a/fast64_internal/oot/new_exporter/collision/header.py +++ b/fast64_internal/oot/new_exporter/collision/header.py @@ -12,7 +12,7 @@ @dataclass -class CollisionHeaderVertices: +class Vertices: """This class defines the array of vertices""" name: str @@ -34,7 +34,7 @@ def getC(self): @dataclass -class CollisionHeaderCollisionPoly: +class CollisionPolygons: """This class defines the array of collision polys""" name: str @@ -54,7 +54,7 @@ def getC(self): @dataclass -class CollisionHeaderSurfaceType: +class SurfaceTypes: """This class defines the array of surface types""" name: str @@ -76,7 +76,7 @@ def getC(self): @dataclass -class CollisionHeaderBgCamInfo: +class BgCamInformations: """This class defines the array of camera informations and the array of the associated data""" name: str @@ -155,7 +155,7 @@ def getInfoArrayC(self): @dataclass -class CollisionHeaderWaterBox: +class WaterBoxes: """This class defines the array of waterboxes""" name: str @@ -175,17 +175,17 @@ def getC(self): @dataclass -class OOTSceneCollisionHeader: +class CollisionHeader: """This class defines the collision header used by the scene""" name: str minBounds: tuple[int, int, int] = None maxBounds: tuple[int, int, int] = None - vertices: CollisionHeaderVertices = None - collisionPoly: CollisionHeaderCollisionPoly = None - surfaceType: CollisionHeaderSurfaceType = None - bgCamInfo: CollisionHeaderBgCamInfo = None - waterbox: CollisionHeaderWaterBox = None + vertices: Vertices = None + collisionPoly: CollisionPolygons = None + surfaceType: SurfaceTypes = None + bgCamInfo: BgCamInformations = None + waterbox: WaterBoxes = None def getSceneCollisionC(self): """Returns the collision header for the selected scene""" diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/new_exporter/commands.py index 762b1ad89..bd41c1810 100644 --- a/fast64_internal/oot/new_exporter/commands.py +++ b/fast64_internal/oot/new_exporter/commands.py @@ -2,26 +2,26 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from .collision import OOTSceneCollisionHeader - from .room import OOTRoom, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors + from .collision import CollisionHeader + from .room import Room, RoomInfos, RoomObjects, RoomActors from .scene import ( - OOTScene, - OOTSceneHeaderInfos, - OOTSceneHeader, - OOTSceneHeaderLighting, - OOTSceneHeaderCutscene, - OOTSceneHeaderActors, - OOTSceneHeaderPath, + Scene, + SceneInfos, + SceneHeader, + SceneLighting, + SceneCutscene, + SceneActors, + ScenePathways, ) class RoomCommands: """This class defines the command list for rooms""" - def getEchoSettingsCmd(self, infos: "OOTRoomHeaderInfos"): + def getEchoSettingsCmd(self, infos: "RoomInfos"): return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" - def getRoomBehaviourCmd(self, infos: "OOTRoomHeaderInfos"): + def getRoomBehaviourCmd(self, infos: "RoomInfos"): showInvisibleActors = "true" if infos.showInvisActors else "false" disableWarpSongs = "true" if infos.disableWarpSongs else "false" @@ -31,30 +31,30 @@ def getRoomBehaviourCmd(self, infos: "OOTRoomHeaderInfos"): + ")" ) - def getSkyboxDisablesCmd(self, infos: "OOTRoomHeaderInfos"): + def getSkyboxDisablesCmd(self, infos: "RoomInfos"): disableSkybox = "true" if infos.disableSky else "false" disableSunMoon = "true" if infos.disableSunMoon else "false" return indent + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})" - def getTimeSettingsCmd(self, infos: "OOTRoomHeaderInfos"): + def getTimeSettingsCmd(self, infos: "RoomInfos"): return indent + f"SCENE_CMD_TIME_SETTINGS({infos.hour}, {infos.minute}, {infos.timeSpeed})" - def getWindSettingsCmd(self, infos: "OOTRoomHeaderInfos"): + def getWindSettingsCmd(self, infos: "RoomInfos"): return ( indent + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" ) - def getRoomShapeCmd(self, room: "OOTRoom"): + def getRoomShapeCmd(self, room: "Room"): return indent + f"SCENE_CMD_ROOM_SHAPE(&{room.roomShape.getName()}),\n" - def getObjectListCmd(self, objects: "OOTRoomHeaderObjects"): + def getObjectListCmd(self, objects: "RoomObjects"): return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getObjectLengthDefineName()}, {objects.name}),\n" - def getActorListCmd(self, actors: "OOTRoomHeaderActors"): + def getActorListCmd(self, actors: "RoomActors"): return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.name}),\n" - def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): + def getRoomCommandList(self, room: "Room", headerIndex: int): cmdListData = CData() curHeader = room.getRoomHeaderFromIndex(headerIndex) listName = f"SceneCmd {curHeader.name}" @@ -91,35 +91,35 @@ def getRoomCommandList(self, room: "OOTRoom", headerIndex: int): class SceneCommands: """This class defines the command list for scenes""" - def getSoundSettingsCmd(self, infos: "OOTSceneHeaderInfos"): + def getSoundSettingsCmd(self, infos: "SceneInfos"): return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" - def getRoomListCmd(self, scene: "OOTScene"): + def getRoomListCmd(self, scene: "Scene"): return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName}),\n" - def getTransActorListCmd(self, actors: "OOTSceneHeaderActors"): + def getTransActorListCmd(self, actors: "SceneActors"): return ( indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" ) + f"{len(actors.transitionActorList)}, {actors.transActorListName})" - def getMiscSettingsCmd(self, infos: "OOTSceneHeaderInfos"): + def getMiscSettingsCmd(self, infos: "SceneInfos"): return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" - def getColHeaderCmd(self, colHeader: "OOTSceneCollisionHeader"): + def getColHeaderCmd(self, colHeader: "CollisionHeader"): return indent + f"SCENE_CMD_COL_HEADER(&{colHeader.name}),\n" - def getSpawnListCmd(self, actors: "OOTSceneHeaderActors"): + def getSpawnListCmd(self, actors: "SceneActors"): return ( indent + "SCENE_CMD_ENTRANCE_LIST(" ) + f"{actors.entranceListName if len(actors.entranceActorList) > 0 else 'NULL'})" - def getSpecialFilesCmd(self, infos: "OOTSceneHeaderInfos"): + def getSpecialFilesCmd(self, infos: "SceneInfos"): return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" - def getPathListCmd(self, path: "OOTSceneHeaderPath"): + def getPathListCmd(self, path: "ScenePathways"): return indent + f"SCENE_CMD_PATH_LIST({path.name}),\n" if len(path.pathList) > 0 else "" - def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): + def getSpawnActorListCmd(self, scene: "Scene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) startPosName = curHeader.actors.startPositionsName return ( @@ -128,19 +128,19 @@ def getSpawnActorListCmd(self, scene: "OOTScene", headerIndex: int): + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'})" ) - def getSkyboxSettingsCmd(self, infos: "OOTSceneHeaderInfos", lights: "OOTSceneHeaderLighting"): + def getSkyboxSettingsCmd(self, infos: "SceneInfos", lights: "SceneLighting"): return indent + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" - def getExitListCmd(self, scene: "OOTScene", headerIndex: int): + def getExitListCmd(self, scene: "Scene", headerIndex: int): curHeader = scene.getSceneHeaderFromIndex(headerIndex) return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name})" - def getLightSettingsCmd(self, lights: "OOTSceneHeaderLighting"): + def getLightSettingsCmd(self, lights: "SceneLighting"): return ( indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" ) + f"{len(lights.settings)}, {lights.name if len(lights.settings) > 0 else 'NULL'}),\n" - def getCutsceneDataCmd(self, cs: "OOTSceneHeaderCutscene"): + def getCutsceneDataCmd(self, cs: "SceneCutscene"): match cs.writeType: case "Object": csDataName = cs.csObj.name @@ -149,7 +149,7 @@ def getCutsceneDataCmd(self, cs: "OOTSceneHeaderCutscene"): return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" - def getSceneCommandList(self, scene: "OOTScene", curHeader: "OOTSceneHeader", headerIndex: int): + def getSceneCommandList(self, scene: "Scene", curHeader: "SceneHeader", headerIndex: int): cmdListData = CData() listName = f"SceneCmd {curHeader.name}" diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/exporter.py index 6117166be..4fd6a45ad 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/exporter.py @@ -15,8 +15,8 @@ from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh from ..oot_utility import CullGroup from .common import Base, altHeaderList -from .scene import OOTScene, OOTSceneAlternateHeader -from .room import OOTRoom, OOTRoomAlternateHeader +from .scene import Scene, SceneAlternateHeader +from .room import Room, RoomAlternateHeader from .other import Files from .exporter_classes import SceneFile @@ -57,16 +57,16 @@ class OOTSceneExport: dlFormat: DLFormat = DLFormat.Static sceneObj: Object = None - scene: OOTScene = None + scene: Scene = None path: str = None sceneFile: SceneFile = None hasCutscenes: bool = False hasSceneTextures: bool = False - def getNewRoomList(self, scene: OOTScene): + def getNewRoomList(self, scene: Scene): """Returns the room list from empty objects with the type 'Room'""" - roomDict: dict[int, OOTRoom] = {} + roomDict: dict[int, Room] = {} roomObjs: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" ] @@ -83,7 +83,7 @@ def getNewRoomList(self, scene: OOTScene): raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" - roomDict[roomIndex] = OOTRoom( + roomDict[roomIndex] = Room( self.sceneObj, self.transform, self.useMacros, @@ -134,7 +134,7 @@ def getNewRoomList(self, scene: OOTScene): raise PluginError(f'Room shape "Image" can only have one room in the scene.') roomDict[roomIndex].roomShape = roomDict[roomIndex].getNewRoomShape(roomHeader, self.sceneName) - altHeaderData = OOTRoomAlternateHeader(f"{roomDict[roomIndex].name}_alternateHeaders") + altHeaderData = RoomAlternateHeader(f"{roomDict[roomIndex].name}_alternateHeaders") roomDict[roomIndex].mainHeader = roomDict[roomIndex].getNewRoomHeader(roomHeader) hasAltHeader = False @@ -174,9 +174,9 @@ def getNewScene(self): try: altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = OOTScene(self.sceneObj, self.transform, self.useMacros, name=f"{toAlnum(self.sceneName)}_scene") + sceneData = Scene(self.sceneObj, self.transform, self.useMacros, name=f"{toAlnum(self.sceneName)}_scene") sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) - altHeaderData = OOTSceneAlternateHeader(f"{sceneData.name}_alternateHeaders") + altHeaderData = SceneAlternateHeader(f"{sceneData.name}_alternateHeaders") sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) hasAltHeader = False diff --git a/fast64_internal/oot/new_exporter/room/__init__.py b/fast64_internal/oot/new_exporter/room/__init__.py index cd75cdb06..19c760661 100644 --- a/fast64_internal/oot/new_exporter/room/__init__.py +++ b/fast64_internal/oot/new_exporter/room/__init__.py @@ -1,2 +1,2 @@ -from .header import OOTRoomAlternateHeader, OOTRoomHeaderInfos, OOTRoomHeaderObjects, OOTRoomHeaderActors -from .main import OOTRoom +from .header import RoomAlternateHeader, RoomInfos, RoomObjects, RoomActors +from .main import Room diff --git a/fast64_internal/oot/new_exporter/room/header.py b/fast64_internal/oot/new_exporter/room/header.py index 5b32d4114..af1e7d970 100644 --- a/fast64_internal/oot/new_exporter/room/header.py +++ b/fast64_internal/oot/new_exporter/room/header.py @@ -7,7 +7,7 @@ @dataclass -class OOTRoomHeaderInfos: +class RoomInfos: """This class stores various room header informations""" ### General ### @@ -39,7 +39,7 @@ class OOTRoomHeaderInfos: @dataclass -class OOTRoomHeaderObjects: +class RoomObjects: """This class defines an OoT object array""" name: str @@ -71,7 +71,7 @@ def getObjectListC(self): @dataclass -class OOTRoomHeaderActors: +class RoomActors: """This class defines an OoT actor array""" name: str @@ -148,24 +148,13 @@ def getActorListC(self): @dataclass -class OOTRoomAlternateHeader: - """This class stores alternate header data""" - - name: str - childNight: "OOTRoomHeader" = None - adultDay: "OOTRoomHeader" = None - adultNight: "OOTRoomHeader" = None - cutscenes: list["OOTRoomHeader"] = field(default_factory=list) - - -@dataclass -class OOTRoomHeader: +class RoomHeader: """This class defines a room header""" name: str - infos: OOTRoomHeaderInfos - objects: OOTRoomHeaderObjects - actors: OOTRoomHeaderActors + infos: RoomInfos + objects: RoomObjects + actors: RoomActors def getHeaderDefines(self): """Returns a string containing defines for actor and object lists lengths""" @@ -181,3 +170,14 @@ def getHeaderDefines(self): headerDefines += f"#define {defineName} {len(self.actors.actorList)}\n" return headerDefines + + +@dataclass +class RoomAlternateHeader: + """This class stores alternate header data""" + + name: str + childNight: RoomHeader = None + adultDay: RoomHeader = None + adultNight: RoomHeader = None + cutscenes: list[RoomHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/new_exporter/room/main.py b/fast64_internal/oot/new_exporter/room/main.py index 6c8e0d733..8108fd22c 100644 --- a/fast64_internal/oot/new_exporter/room/main.py +++ b/fast64_internal/oot/new_exporter/room/main.py @@ -22,16 +22,16 @@ ) from .header import ( - OOTRoomHeaderInfos, - OOTRoomHeaderObjects, - OOTRoomHeaderActors, - OOTRoomAlternateHeader, - OOTRoomHeader, + RoomInfos, + RoomObjects, + RoomActors, + RoomAlternateHeader, + RoomHeader, ) @dataclass -class OOTRoom(Base, RoomCommands): +class Room(Base, RoomCommands): """This class defines a room""" name: str = None @@ -39,8 +39,8 @@ class OOTRoom(Base, RoomCommands): roomShapeType: str = None model: OOTModel = None headerIndex: int = None - mainHeader: OOTRoomHeader = None - altHeader: OOTRoomAlternateHeader = None + mainHeader: RoomHeader = None + altHeader: RoomAlternateHeader = None mesh: OOTRoomMesh = None roomShape: RoomShape = None @@ -52,7 +52,7 @@ def hasAlternateHeaders(self): return self.altHeader is not None - def getRoomHeaderFromIndex(self, headerIndex: int) -> OOTRoomHeader | None: + def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: """Returns the current room header based on the header index""" if headerIndex == 0: @@ -110,9 +110,9 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = else: objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) - return OOTRoomHeader( + return RoomHeader( headerName, - OOTRoomHeaderInfos( + RoomInfos( headerProp.roomIndex, headerProp.roomShape, self.getPropValue(headerProp, "roomBehaviour"), @@ -129,8 +129,8 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = [d for d in headerProp.windVector] if headerProp.setWind else None, headerProp.windStrength if headerProp.setWind else None, ), - OOTRoomHeaderObjects(f"{headerName}_objectList", objIDList), - OOTRoomHeaderActors( + RoomObjects(f"{headerName}_objectList", objIDList), + RoomActors( f"{headerName}_actorList", self.sceneObj, self.roomObj, @@ -195,11 +195,11 @@ def getRoomMainC(self): """Returns the C data of the main informations of a room""" roomC = CData() - roomHeaders: list[tuple[OOTRoomHeader, str]] = [] + roomHeaders: list[tuple[RoomHeader, str]] = [] altHeaderPtrList = None if self.hasAlternateHeaders(): - roomHeaders: list[tuple[OOTRoomHeader, str]] = [ + roomHeaders: list[tuple[RoomHeader, str]] = [ (self.altHeader.childNight, "Child Night"), (self.altHeader.adultDay, "Adult Day"), (self.altHeader.adultNight, "Adult Night"), diff --git a/fast64_internal/oot/new_exporter/scene/__init__.py b/fast64_internal/oot/new_exporter/scene/__init__.py index f71025c41..9ba5105e3 100644 --- a/fast64_internal/oot/new_exporter/scene/__init__.py +++ b/fast64_internal/oot/new_exporter/scene/__init__.py @@ -1,11 +1,11 @@ from .header import ( - OOTSceneHeaderInfos, - OOTSceneHeader, - OOTSceneHeaderLighting, - OOTSceneHeaderCutscene, - OOTSceneHeaderActors, - OOTSceneHeaderPath, - OOTSceneAlternateHeader, + SceneInfos, + SceneHeader, + SceneLighting, + SceneCutscene, + SceneActors, + ScenePathways, + SceneAlternateHeader, ) -from .main import OOTScene +from .main import Scene diff --git a/fast64_internal/oot/new_exporter/scene/base.py b/fast64_internal/oot/new_exporter/scene/base.py index 510a88d61..48848970b 100644 --- a/fast64_internal/oot/new_exporter/scene/base.py +++ b/fast64_internal/oot/new_exporter/scene/base.py @@ -9,10 +9,10 @@ from ..common import altHeaderList from ..collision import CollisionBase from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path -from .header import OOTSceneAlternateHeader, OOTSceneHeader +from .header import SceneAlternateHeader, SceneHeader if TYPE_CHECKING: - from ..room import OOTRoom + from ..room import Room @dataclass @@ -22,9 +22,9 @@ class SceneBase(CollisionBase, SceneCommands): name: str = None model: OOTModel = None headerIndex: int = None - mainHeader: OOTSceneHeader = None - altHeader: OOTSceneAlternateHeader = None - roomList: list["OOTRoom"] = field(default_factory=list) + mainHeader: SceneHeader = None + altHeader: SceneAlternateHeader = None + roomList: list["Room"] = field(default_factory=list) def validateRoomIndices(self): """Checks if there are multiple rooms with the same room index""" @@ -48,7 +48,7 @@ def hasAlternateHeaders(self): return self.altHeader is not None - def getSceneHeaderFromIndex(self, headerIndex: int) -> OOTSceneHeader | None: + def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: """Returns the scene header based on the header index""" if headerIndex == 0: diff --git a/fast64_internal/oot/new_exporter/scene/header.py b/fast64_internal/oot/new_exporter/scene/header.py index 6c831d660..ec2afa3a5 100644 --- a/fast64_internal/oot/new_exporter/scene/header.py +++ b/fast64_internal/oot/new_exporter/scene/header.py @@ -5,7 +5,7 @@ @dataclass -class OOTSceneHeaderInfos: +class SceneInfos: """This class stores various scene header informations""" ### General ### @@ -37,7 +37,7 @@ class OOTSceneHeaderInfos: @dataclass -class OOTSceneHeaderLighting: +class SceneLighting: """This class hosts lighting data""" name: str @@ -64,7 +64,7 @@ def getEnvLightSettingsC(self): @dataclass -class OOTSceneHeaderCutscene: +class SceneCutscene: """This class hosts cutscene data (unfinished)""" headerIndex: int @@ -96,7 +96,7 @@ def getCutsceneC(self): @dataclass -class OOTSceneHeaderExits: +class SceneExits: """This class hosts exit data""" name: str = None @@ -123,7 +123,7 @@ def getExitListC(self): @dataclass -class OOTSceneHeaderActors: +class SceneActors: """This class handles scene actors (transition actors and entrance actors)""" entranceListName: str @@ -190,7 +190,7 @@ def getTransActorListC(self): @dataclass -class OOTSceneHeaderPath: +class ScenePathways: """This class hosts pathways array data""" name: str @@ -220,27 +220,16 @@ def getPathC(self): @dataclass -class OOTSceneAlternateHeader: - """This class stores alternate header data for the scene""" - - name: str - childNight: "OOTSceneHeader" = None - adultDay: "OOTSceneHeader" = None - adultNight: "OOTSceneHeader" = None - cutscenes: list["OOTSceneHeader"] = field(default_factory=list) - - -@dataclass -class OOTSceneHeader: +class SceneHeader: """This class defines a scene header""" name: str - infos: OOTSceneHeaderInfos - lighting: OOTSceneHeaderLighting - cutscene: OOTSceneHeaderCutscene - exits: OOTSceneHeaderExits - actors: OOTSceneHeaderActors - path: OOTSceneHeaderPath + infos: SceneInfos + lighting: SceneLighting + cutscene: SceneCutscene + exits: SceneExits + actors: SceneActors + path: ScenePathways def getHeaderC(self): """Returns the ``CData`` containing the header's data""" @@ -269,3 +258,14 @@ def getHeaderC(self): headerData.append(self.path.getPathC()) return headerData + + +@dataclass +class SceneAlternateHeader: + """This class stores alternate header data for the scene""" + + name: str + childNight: SceneHeader = None + adultDay: SceneHeader = None + adultNight: SceneHeader = None + cutscenes: list[SceneHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/new_exporter/scene/main.py b/fast64_internal/oot/new_exporter/scene/main.py index 05cc752a6..8dabbf663 100644 --- a/fast64_internal/oot/new_exporter/scene/main.py +++ b/fast64_internal/oot/new_exporter/scene/main.py @@ -7,31 +7,31 @@ from .base import SceneBase from ..collision import ( - CollisionHeaderVertices, - CollisionHeaderCollisionPoly, - CollisionHeaderSurfaceType, - CollisionHeaderBgCamInfo, - CollisionHeaderWaterBox, - OOTSceneCollisionHeader, + Vertices, + CollisionPolygons, + SurfaceTypes, + BgCamInformations, + WaterBoxes, + CollisionHeader, ) from .header import ( - OOTSceneHeaderInfos, - OOTSceneHeaderLighting, - OOTSceneHeaderCutscene, - OOTSceneHeaderExits, - OOTSceneHeaderActors, - OOTSceneHeaderPath, - OOTSceneHeader, + SceneInfos, + SceneLighting, + SceneCutscene, + SceneExits, + SceneActors, + ScenePathways, + SceneHeader, ) @dataclass -class OOTScene(SceneBase): +class Scene(SceneBase): """This class defines a scene""" roomListName: str = None - colHeader: OOTSceneCollisionHeader = None + colHeader: CollisionHeader = None def __post_init__(self): self.roomListName = f"{self.name}_roomList" @@ -42,20 +42,20 @@ def getNewCollisionHeader(self): colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() bgCamInfoList = self.getBgCamInfoDataFromObjects() - return OOTSceneCollisionHeader( + return CollisionHeader( f"{self.name}_collisionHeader", colBounds[0], colBounds[1], - CollisionHeaderVertices(f"{self.name}_vertices", vertexList), - CollisionHeaderCollisionPoly(f"{self.name}_polygons", polyList), - CollisionHeaderSurfaceType(f"{self.name}_polygonTypes", surfaceTypeList), - CollisionHeaderBgCamInfo( + Vertices(f"{self.name}_vertices", vertexList), + CollisionPolygons(f"{self.name}_polygons", polyList), + SurfaceTypes(f"{self.name}_polygonTypes", surfaceTypeList), + BgCamInformations( f"{self.name}_bgCamInfo", f"{self.name}_camPosData", bgCamInfoList, self.getCrawlspaceDataFromObjects(), ), - CollisionHeaderWaterBox(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), + WaterBoxes(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), ) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): @@ -68,9 +68,9 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int if headerProp.writeCutscene and headerProp.csWriteType == "Embedded": raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") - return OOTSceneHeader( + return SceneHeader( headerName, - OOTSceneHeaderInfos( + SceneInfos( self.getPropValue(headerProp, "globalObject"), self.getPropValue(headerProp, "naviCup"), self.getPropValue(headerProp.sceneTableEntry, "drawConfig"), @@ -84,12 +84,12 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int self.getPropValue(headerProp, "mapLocation"), self.getPropValue(headerProp, "cameraMode"), ), - OOTSceneHeaderLighting( + SceneLighting( f"{headerName}_lightSettings", lightMode, self.getEnvLightSettingsListFromProps(headerProp, lightMode), ), - OOTSceneHeaderCutscene( + SceneCutscene( headerIndex, headerProp.csWriteType, headerProp.writeCutscene, @@ -99,15 +99,15 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int ) if headerProp.writeCutscene else None, - OOTSceneHeaderExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), - OOTSceneHeaderActors( + SceneExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), + SceneActors( f"{headerName}_entranceList", f"{headerName}_playerEntryList", f"{headerName}_transitionActors", self.getTransActorListFromProps(), self.getEntranceActorListFromProps(), ), - OOTSceneHeaderPath(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), + ScenePathways(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), ) def getRoomListC(self): @@ -153,7 +153,7 @@ def getSceneMainC(self): """Returns the main informations of the scene as ``CData``""" sceneC = CData() - headers: list[tuple[OOTSceneHeader, str]] = [] + headers: list[tuple[SceneHeader, str]] = [] altHeaderPtrs = None if self.hasAlternateHeaders(): From 306a0e77bd4a405ed0239d4312ab8a3b48f37812 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:50:02 +0200 Subject: [PATCH 44/98] renamed more files --- fast64_internal/oot/new_exporter/__init__.py | 2 +- fast64_internal/oot/new_exporter/{common.py => base.py} | 0 .../oot/new_exporter/{exporter_classes.py => classes.py} | 0 fast64_internal/oot/new_exporter/collision/base.py | 2 +- fast64_internal/oot/new_exporter/{exporter.py => main.py} | 6 +++--- fast64_internal/oot/new_exporter/other/file.py | 4 ++-- fast64_internal/oot/new_exporter/other/scene_table.py | 6 +++--- fast64_internal/oot/new_exporter/other/spec.py | 4 ++-- fast64_internal/oot/new_exporter/room/header.py | 2 +- fast64_internal/oot/new_exporter/room/main.py | 4 ++-- fast64_internal/oot/new_exporter/scene/base.py | 2 +- fast64_internal/oot/new_exporter/scene/classes.py | 2 +- fast64_internal/oot/new_exporter/scene/main.py | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) rename fast64_internal/oot/new_exporter/{common.py => base.py} (100%) rename fast64_internal/oot/new_exporter/{exporter_classes.py => classes.py} (100%) rename fast64_internal/oot/new_exporter/{exporter.py => main.py} (99%) diff --git a/fast64_internal/oot/new_exporter/__init__.py b/fast64_internal/oot/new_exporter/__init__.py index 6668eb826..63e005639 100644 --- a/fast64_internal/oot/new_exporter/__init__.py +++ b/fast64_internal/oot/new_exporter/__init__.py @@ -1 +1 @@ -from .exporter import OOTSceneExport +from .main import SceneExporter diff --git a/fast64_internal/oot/new_exporter/common.py b/fast64_internal/oot/new_exporter/base.py similarity index 100% rename from fast64_internal/oot/new_exporter/common.py rename to fast64_internal/oot/new_exporter/base.py diff --git a/fast64_internal/oot/new_exporter/exporter_classes.py b/fast64_internal/oot/new_exporter/classes.py similarity index 100% rename from fast64_internal/oot/new_exporter/exporter_classes.py rename to fast64_internal/oot/new_exporter/classes.py diff --git a/fast64_internal/oot/new_exporter/collision/base.py b/fast64_internal/oot/new_exporter/collision/base.py index e338ac78e..ec38ab45a 100644 --- a/fast64_internal/oot/new_exporter/collision/base.py +++ b/fast64_internal/oot/new_exporter/collision/base.py @@ -7,7 +7,7 @@ from ....utility import PluginError, checkIdentityRotation from ...oot_utility import convertIntTo2sComplement from ...oot_collision_classes import decomp_compat_map_CameraSType -from ..common import Base +from ..base import Base from .classes import ( CollisionPoly, diff --git a/fast64_internal/oot/new_exporter/exporter.py b/fast64_internal/oot/new_exporter/main.py similarity index 99% rename from fast64_internal/oot/new_exporter/exporter.py rename to fast64_internal/oot/new_exporter/main.py index 4fd6a45ad..960118138 100644 --- a/fast64_internal/oot/new_exporter/exporter.py +++ b/fast64_internal/oot/new_exporter/main.py @@ -14,11 +14,11 @@ from ..oot_f3d_writer import writeTextureArraysNew from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh from ..oot_utility import CullGroup -from .common import Base, altHeaderList +from .base import Base, altHeaderList from .scene import Scene, SceneAlternateHeader from .room import Room, RoomAlternateHeader from .other import Files -from .exporter_classes import SceneFile +from .classes import SceneFile from ...utility import ( PluginError, @@ -39,7 +39,7 @@ @dataclass -class OOTSceneExport: +class SceneExporter: """This class is the main exporter class, it handles generating the C data and writing the files""" exportInfo: ExportInfo diff --git a/fast64_internal/oot/new_exporter/other/file.py b/fast64_internal/oot/new_exporter/other/file.py index f19dbd264..8336101c7 100644 --- a/fast64_internal/oot/new_exporter/other/file.py +++ b/fast64_internal/oot/new_exporter/other/file.py @@ -8,14 +8,14 @@ from .spec import Spec if TYPE_CHECKING: - from ..exporter import OOTSceneExport + from ..main import SceneExporter @dataclass class Files: # TODO: find a better name """This class handles editing decomp files""" - exporter: "OOTSceneExport" + exporter: "SceneExporter" def modifySceneFiles(self): if self.exporter.exportInfo.customSubPath is not None: diff --git a/fast64_internal/oot/new_exporter/other/scene_table.py b/fast64_internal/oot/new_exporter/other/scene_table.py index 1e4806d59..a0642e5a2 100644 --- a/fast64_internal/oot/new_exporter/other/scene_table.py +++ b/fast64_internal/oot/new_exporter/other/scene_table.py @@ -7,7 +7,7 @@ from ...oot_utility import ExportInfo if TYPE_CHECKING: - from ..exporter import OOTSceneExport + from ..main import SceneExporter class SceneTable: @@ -138,7 +138,7 @@ def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: st # if the index hasn't been found yet, do it again but decrement the index return self.getInsertionIndex(isExport, sceneNames, sceneName, currentIndex - 1, mode) - def getSceneParams(self, exporter: "OOTSceneExport", exportInfo: ExportInfo, sceneNames: list[str]): + def getSceneParams(self, exporter: "SceneExporter", exportInfo: ExportInfo, sceneNames: list[str]): """Returns the parameters that needs to be set in ``DEFINE_SCENE()``""" # in order to replace the values of ``unk10``, ``unk12`` and basically every parameters from ``DEFINE_SCENE``, @@ -218,7 +218,7 @@ def addHackerOoTData(self, fileData: str): return "".join(newFileData) - def editSceneTable(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): + def editSceneTable(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): """Edit the scene table with the new data""" isExport = exporter is not None diff --git a/fast64_internal/oot/new_exporter/other/spec.py b/fast64_internal/oot/new_exporter/other/spec.py index 018ed641c..47e47402e 100644 --- a/fast64_internal/oot/new_exporter/other/spec.py +++ b/fast64_internal/oot/new_exporter/other/spec.py @@ -6,7 +6,7 @@ from ...oot_utility import ExportInfo, getSceneDirFromLevelName if TYPE_CHECKING: - from ..exporter import OOTSceneExport + from ..main import SceneExporter class Spec: @@ -45,7 +45,7 @@ def getSpecEntries(self, fileData: str): return entries, compressFlag, includes - def editSpec(self, exporter: "OOTSceneExport", exportInfo: ExportInfo = None): + def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): """Adds or removes entries for the selected scene in the spec file""" isExport = exporter is not None diff --git a/fast64_internal/oot/new_exporter/room/header.py b/fast64_internal/oot/new_exporter/room/header.py index af1e7d970..f9387e969 100644 --- a/fast64_internal/oot/new_exporter/room/header.py +++ b/fast64_internal/oot/new_exporter/room/header.py @@ -3,7 +3,7 @@ from bpy.types import Object from ....utility import CData, indent from ...oot_constants import ootData -from ..common import Base, Actor +from ..base import Base, Actor @dataclass diff --git a/fast64_internal/oot/new_exporter/room/main.py b/fast64_internal/oot/new_exporter/room/main.py index 8108fd22c..4d4c60b0c 100644 --- a/fast64_internal/oot/new_exporter/room/main.py +++ b/fast64_internal/oot/new_exporter/room/main.py @@ -7,8 +7,8 @@ from ...oot_level_classes import OOTBGImage, OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter from ..commands import RoomCommands -from ..exporter_classes import RoomFile -from ..common import Base, altHeaderList +from ..classes import RoomFile +from ..base import Base, altHeaderList from .shape import ( RoomShapeDListsEntry, diff --git a/fast64_internal/oot/new_exporter/scene/base.py b/fast64_internal/oot/new_exporter/scene/base.py index 48848970b..fd068abfb 100644 --- a/fast64_internal/oot/new_exporter/scene/base.py +++ b/fast64_internal/oot/new_exporter/scene/base.py @@ -6,7 +6,7 @@ from ...oot_constants import ootData from ...oot_model_classes import OOTModel from ..commands import SceneCommands -from ..common import altHeaderList +from ..base import altHeaderList from ..collision import CollisionBase from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path from .header import SceneAlternateHeader, SceneHeader diff --git a/fast64_internal/oot/new_exporter/scene/classes.py b/fast64_internal/oot/new_exporter/scene/classes.py index cbfd48ed3..9c97090d0 100644 --- a/fast64_internal/oot/new_exporter/scene/classes.py +++ b/fast64_internal/oot/new_exporter/scene/classes.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from ....utility import CData, indent -from ..common import Actor +from ..base import Actor @dataclass diff --git a/fast64_internal/oot/new_exporter/scene/main.py b/fast64_internal/oot/new_exporter/scene/main.py index 8dabbf663..93a0c7b0f 100644 --- a/fast64_internal/oot/new_exporter/scene/main.py +++ b/fast64_internal/oot/new_exporter/scene/main.py @@ -3,7 +3,7 @@ from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod from ...oot_model_classes import OOTGfxFormatter from ...scene.properties import OOTSceneHeaderProperty -from ..exporter_classes import SceneFile +from ..classes import SceneFile from .base import SceneBase from ..collision import ( From 51c7e94e679c7dc4409e2df563f8cdd72f25337b Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:50:49 +0200 Subject: [PATCH 45/98] renamed exporter folder --- fast64_internal/oot/{new_exporter => exporter}/__init__.py | 0 fast64_internal/oot/{new_exporter => exporter}/base.py | 0 fast64_internal/oot/{new_exporter => exporter}/classes.py | 0 .../oot/{new_exporter => exporter}/collision/__init__.py | 0 .../oot/{new_exporter => exporter}/collision/base.py | 0 .../oot/{new_exporter => exporter}/collision/classes.py | 0 .../oot/{new_exporter => exporter}/collision/header.py | 0 fast64_internal/oot/{new_exporter => exporter}/commands.py | 0 fast64_internal/oot/{new_exporter => exporter}/main.py | 0 .../oot/{new_exporter => exporter}/other/__init__.py | 0 fast64_internal/oot/{new_exporter => exporter}/other/file.py | 0 .../oot/{new_exporter => exporter}/other/scene_table.py | 0 fast64_internal/oot/{new_exporter => exporter}/other/spec.py | 0 .../oot/{new_exporter => exporter}/room/__init__.py | 0 fast64_internal/oot/{new_exporter => exporter}/room/header.py | 0 fast64_internal/oot/{new_exporter => exporter}/room/main.py | 0 fast64_internal/oot/{new_exporter => exporter}/room/shape.py | 0 .../oot/{new_exporter => exporter}/scene/__init__.py | 0 fast64_internal/oot/{new_exporter => exporter}/scene/base.py | 0 .../oot/{new_exporter => exporter}/scene/classes.py | 0 .../oot/{new_exporter => exporter}/scene/header.py | 0 fast64_internal/oot/{new_exporter => exporter}/scene/main.py | 0 fast64_internal/oot/scene/operators.py | 4 ++-- 23 files changed, 2 insertions(+), 2 deletions(-) rename fast64_internal/oot/{new_exporter => exporter}/__init__.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/base.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/classes.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/collision/__init__.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/collision/base.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/collision/classes.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/collision/header.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/commands.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/main.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/other/__init__.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/other/file.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/other/scene_table.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/other/spec.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/room/__init__.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/room/header.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/room/main.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/room/shape.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/scene/__init__.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/scene/base.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/scene/classes.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/scene/header.py (100%) rename fast64_internal/oot/{new_exporter => exporter}/scene/main.py (100%) diff --git a/fast64_internal/oot/new_exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py similarity index 100% rename from fast64_internal/oot/new_exporter/__init__.py rename to fast64_internal/oot/exporter/__init__.py diff --git a/fast64_internal/oot/new_exporter/base.py b/fast64_internal/oot/exporter/base.py similarity index 100% rename from fast64_internal/oot/new_exporter/base.py rename to fast64_internal/oot/exporter/base.py diff --git a/fast64_internal/oot/new_exporter/classes.py b/fast64_internal/oot/exporter/classes.py similarity index 100% rename from fast64_internal/oot/new_exporter/classes.py rename to fast64_internal/oot/exporter/classes.py diff --git a/fast64_internal/oot/new_exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py similarity index 100% rename from fast64_internal/oot/new_exporter/collision/__init__.py rename to fast64_internal/oot/exporter/collision/__init__.py diff --git a/fast64_internal/oot/new_exporter/collision/base.py b/fast64_internal/oot/exporter/collision/base.py similarity index 100% rename from fast64_internal/oot/new_exporter/collision/base.py rename to fast64_internal/oot/exporter/collision/base.py diff --git a/fast64_internal/oot/new_exporter/collision/classes.py b/fast64_internal/oot/exporter/collision/classes.py similarity index 100% rename from fast64_internal/oot/new_exporter/collision/classes.py rename to fast64_internal/oot/exporter/collision/classes.py diff --git a/fast64_internal/oot/new_exporter/collision/header.py b/fast64_internal/oot/exporter/collision/header.py similarity index 100% rename from fast64_internal/oot/new_exporter/collision/header.py rename to fast64_internal/oot/exporter/collision/header.py diff --git a/fast64_internal/oot/new_exporter/commands.py b/fast64_internal/oot/exporter/commands.py similarity index 100% rename from fast64_internal/oot/new_exporter/commands.py rename to fast64_internal/oot/exporter/commands.py diff --git a/fast64_internal/oot/new_exporter/main.py b/fast64_internal/oot/exporter/main.py similarity index 100% rename from fast64_internal/oot/new_exporter/main.py rename to fast64_internal/oot/exporter/main.py diff --git a/fast64_internal/oot/new_exporter/other/__init__.py b/fast64_internal/oot/exporter/other/__init__.py similarity index 100% rename from fast64_internal/oot/new_exporter/other/__init__.py rename to fast64_internal/oot/exporter/other/__init__.py diff --git a/fast64_internal/oot/new_exporter/other/file.py b/fast64_internal/oot/exporter/other/file.py similarity index 100% rename from fast64_internal/oot/new_exporter/other/file.py rename to fast64_internal/oot/exporter/other/file.py diff --git a/fast64_internal/oot/new_exporter/other/scene_table.py b/fast64_internal/oot/exporter/other/scene_table.py similarity index 100% rename from fast64_internal/oot/new_exporter/other/scene_table.py rename to fast64_internal/oot/exporter/other/scene_table.py diff --git a/fast64_internal/oot/new_exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py similarity index 100% rename from fast64_internal/oot/new_exporter/other/spec.py rename to fast64_internal/oot/exporter/other/spec.py diff --git a/fast64_internal/oot/new_exporter/room/__init__.py b/fast64_internal/oot/exporter/room/__init__.py similarity index 100% rename from fast64_internal/oot/new_exporter/room/__init__.py rename to fast64_internal/oot/exporter/room/__init__.py diff --git a/fast64_internal/oot/new_exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py similarity index 100% rename from fast64_internal/oot/new_exporter/room/header.py rename to fast64_internal/oot/exporter/room/header.py diff --git a/fast64_internal/oot/new_exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py similarity index 100% rename from fast64_internal/oot/new_exporter/room/main.py rename to fast64_internal/oot/exporter/room/main.py diff --git a/fast64_internal/oot/new_exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py similarity index 100% rename from fast64_internal/oot/new_exporter/room/shape.py rename to fast64_internal/oot/exporter/room/shape.py diff --git a/fast64_internal/oot/new_exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py similarity index 100% rename from fast64_internal/oot/new_exporter/scene/__init__.py rename to fast64_internal/oot/exporter/scene/__init__.py diff --git a/fast64_internal/oot/new_exporter/scene/base.py b/fast64_internal/oot/exporter/scene/base.py similarity index 100% rename from fast64_internal/oot/new_exporter/scene/base.py rename to fast64_internal/oot/exporter/scene/base.py diff --git a/fast64_internal/oot/new_exporter/scene/classes.py b/fast64_internal/oot/exporter/scene/classes.py similarity index 100% rename from fast64_internal/oot/new_exporter/scene/classes.py rename to fast64_internal/oot/exporter/scene/classes.py diff --git a/fast64_internal/oot/new_exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py similarity index 100% rename from fast64_internal/oot/new_exporter/scene/header.py rename to fast64_internal/oot/exporter/scene/header.py diff --git a/fast64_internal/oot/new_exporter/scene/main.py b/fast64_internal/oot/exporter/scene/main.py similarity index 100% rename from fast64_internal/oot/new_exporter/scene/main.py rename to fast64_internal/oot/exporter/scene/main.py diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 62b7058c7..2b4e9259d 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -12,7 +12,7 @@ from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles -from ..new_exporter import OOTSceneExport +from ..exporter import SceneExporter def ootRemoveSceneC(exportInfo): @@ -178,7 +178,7 @@ def execute(self, context): hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled if settings.useNewExporter: - OOTSceneExport( + SceneExporter( exportInfo, obj, exportInfo.name, From 7ccffb2a6cdc492f40d313b87d44ae5a7528110b Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 03:36:06 +0200 Subject: [PATCH 46/98] code reorganisation part 1 --- fast64_internal/oot/exporter/base.py | 5 - .../oot/exporter/collision/base.py | 200 +++---------- .../oot/exporter/collision/classes.py | 61 ++-- .../oot/exporter/collision/header.py | 171 +++++++++-- fast64_internal/oot/exporter/commands.py | 6 +- fast64_internal/oot/exporter/main.py | 12 +- fast64_internal/oot/exporter/room/header.py | 119 +++++--- fast64_internal/oot/exporter/room/main.py | 174 ++--------- fast64_internal/oot/exporter/room/shape.py | 103 ++++++- fast64_internal/oot/exporter/scene/base.py | 208 ------------- fast64_internal/oot/exporter/scene/header.py | 281 ++++++++++++++++-- fast64_internal/oot/exporter/scene/main.py | 151 +++++----- 12 files changed, 736 insertions(+), 755 deletions(-) diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/base.py index f956bd784..bfd510ef3 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/base.py @@ -14,11 +14,6 @@ class Base: """This class hosts common data used across different sub-systems of this exporter""" - sceneObj: Object - transform: Matrix - useMacros: bool - roomIndex: int = None - def getRoomObjectFromChild(self, childObj: Object) -> Object | None: """Returns the room empty object from one of its child""" diff --git a/fast64_internal/oot/exporter/collision/base.py b/fast64_internal/oot/exporter/collision/base.py index ec38ab45a..1e81b4a8e 100644 --- a/fast64_internal/oot/exporter/collision/base.py +++ b/fast64_internal/oot/exporter/collision/base.py @@ -1,82 +1,78 @@ import math from dataclasses import dataclass -from mathutils import Matrix, Quaternion, Vector +from mathutils import Matrix, Vector from bpy.types import Mesh, Object from bpy.ops import object -from ....utility import PluginError, checkIdentityRotation +from ....utility import PluginError from ...oot_utility import convertIntTo2sComplement -from ...oot_collision_classes import decomp_compat_map_CameraSType from ..base import Base - -from .classes import ( - CollisionPoly, - SurfaceType, - BgCamFuncData, - CrawlspaceData, - BgCamInfo, - WaterBox, - Vertex, -) +from .classes import CollisionPoly, SurfaceType, Vertex @dataclass class CollisionBase(Base): """This class hosts different functions used to convert mesh data""" - def updateBounds(self, position: tuple[int, int, int], bounds: list[tuple[int, int, int]]): + sceneObj: Object + transform: Matrix + useMacros: bool + + def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): """This is used to update the scene's boundaries""" - if len(bounds) == 0: - bounds.append([position[0], position[1], position[2]]) - bounds.append([position[0], position[1], position[2]]) + if len(colBounds) == 0: + colBounds.append([position[0], position[1], position[2]]) + colBounds.append([position[0], position[1], position[2]]) return - minBounds = bounds[0] - maxBounds = bounds[1] + minBounds = colBounds[0] + maxBounds = colBounds[1] for i in range(3): if position[i] < minBounds[i]: minBounds[i] = position[i] if position[i] > maxBounds[i]: maxBounds[i] = position[i] - def getVertIndex(self, vert: tuple[int, int, int], vertArray: list[Vertex]): + def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Vertex]): """Returns the index of a Vertex based on position data, returns None if no match found""" - for i in range(len(vertArray)): - colVert = vertArray[i].pos - if colVert == vert: + for i in range(len(vertexList)): + if vertexList[i].pos == vertexPos: return i return None - def getMeshObjects( - self, parentObj: Object, transform: Matrix, matrixTable: dict[Object, Matrix] - ) -> dict[Object, Matrix]: + def getMeshObjects(self, parentObj: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" - for obj in parentObj.children: - newTransform = transform @ obj.matrix_local + objList: list[Object] = parentObj.children + for obj in objList: + newTransform = curTransform @ obj.matrix_local + if obj.type == "MESH" and not obj.ignore_collision: - matrixTable[obj] = newTransform - self.getMeshObjects(obj, newTransform, matrixTable) - return matrixTable + transformFromMeshObj[obj] = newTransform + + if len(obj.children) > 0: + self.getMeshObjects(obj, newTransform, transformFromMeshObj) - def getColSurfaceVtxDataFromMeshObj(self): + return transformFromMeshObj + + def getCollisionData(self): """Returns collision data, surface types and vertex positions from mesh objects""" # Ideally everything would be separated but this is complicated since it's all tied together object.select_all(action="DESELECT") self.sceneObj.select_set(True) - matrixTable: dict[Object, Matrix] = {} colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} surfaceList: list[SurfaceType] = [] polyList: list[CollisionPoly] = [] vertexList: list[Vertex] = [] - bounds = [] + colBounds: list[tuple[int, int, int]] = [] - matrixTable = self.getMeshObjects(self.sceneObj, self.transform, matrixTable) - for meshObj, transform in matrixTable.items(): + transformFromMeshObj: dict[Object, Matrix] = {} + transformFromMeshObj = self.getMeshObjects(self.sceneObj, self.transform, transformFromMeshObj) + for meshObj, transform in transformFromMeshObj.items(): # Note: ``isinstance``only used to get the proper type hints if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): if len(meshObj.data.materials) == 0: @@ -86,28 +82,29 @@ def getColSurfaceVtxDataFromMeshObj(self): for face in meshObj.data.loop_triangles: colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty + # get bounds and vertices data planePoint = transform @ meshObj.data.vertices[face.vertices[0]].co (x1, y1, z1) = self.roundPosition(planePoint) (x2, y2, z2) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) (x3, y3, z3) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) - self.updateBounds((x1, y1, z1), bounds) - self.updateBounds((x2, y2, z2), bounds) - self.updateBounds((x3, y3, z3), bounds) + self.updateBounds((x1, y1, z1), colBounds) + self.updateBounds((x2, y2, z2), colBounds) + self.updateBounds((x3, y3, z3), colBounds) normal = (transform.inverted().transposed() @ face.normal).normalized() - distance = int( - round(-1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2])) + distance = round( + -1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2]) ) distance = convertIntTo2sComplement(distance, 2, True) indices: list[int] = [] - for vertex in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: - index = self.getVertIndex(vertex, vertexList) - if index is None: - vertexList.append(Vertex(vertex)) + for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: + vertexIndex = self.getVertexIndex(pos, vertexList) + if vertexIndex is None: + vertexList.append(Vertex(pos)) indices.append(len(vertexList) - 1) else: - indices.append(index) + indices.append(vertexIndex) assert len(indices) == 3 # We need to ensure two things about the order in which the vertex indices are: @@ -134,6 +131,7 @@ def getColSurfaceVtxDataFromMeshObj(self): if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: indices[1], indices[2] = indices[2], indices[1] + # get surface type and collision poly data useConveyor = colProp.conveyorOption != "None" surfaceType = SurfaceType( colProp.cameraID, @@ -180,112 +178,4 @@ def getColSurfaceVtxDataFromMeshObj(self): surfaceList.append(surface) count += 1 - return bounds, vertexList, polyList, surfaceList - - def getBgCamFuncDataFromObjects(self, camObj: Object): - """Returns Camera data from a single camera object""" - - # Camera faces opposite direction - pos, rot, _, _ = self.getConvertedTransformWithOrientation( - self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) - ) - - fov = math.degrees(camObj.data.angle) - if fov > 3.6: - fov *= 100 # see CAM_DATA_SCALED() macro - - return BgCamFuncData( - pos, - rot, - round(fov), - camObj.ootCameraPositionProperty.bgImageOverrideIndex, - ) - - def getCrawlspaceDataFromObjects(self): - """Returns a list of rawlspace data from every splines objects with the type 'Crawlspace'""" - - crawlspaceList: list[CrawlspaceData] = [] - crawlspaceObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" - ] - - for obj in crawlspaceObjList: - if self.validateCurveData(obj): - crawlspaceList.append( - CrawlspaceData( - [ - [round(value) for value in self.transform @ obj.matrix_world @ point.co] - for point in obj.data.splines[0].points - ], - obj.ootSplineProperty.index, - ) - ) - return crawlspaceList - - def getBgCamInfoDataFromObjects(self): - """Returns a list of camera informations from camera objects""" - - camObjList = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] - camPosData: dict[int, BgCamFuncData] = {} - camInfoData: dict[int, BgCamInfo] = {} - - index = 0 - for camObj in camObjList: - camProp = camObj.ootCameraPositionProperty - - if camProp.camSType == "Custom": - setting = camProp.camSTypeCustom - else: - setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) - - if camProp.hasPositionData: - count = 3 # cameras are using 3 entries in the data array - if camProp.index in camPosData: - raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") - camPosData[camProp.index] = self.getBgCamFuncDataFromObjects(camObj) - else: - count = 0 - - if camProp.index in camInfoData: - raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") - camInfoData[camProp.index] = BgCamInfo( - setting, - count, - camPosData[camProp.index] if camProp.hasPositionData else None, - camProp.index, - ) - - index += count - return list(camInfoData.values()) - - def getWaterBoxDataFromObjects(self): - """Returns a list of waterbox data from waterbox empty objects""" - - waterboxObjList = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" - ] - waterboxList: list[WaterBox] = [] - - for waterboxObj in waterboxObjList: - emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) - checkIdentityRotation(waterboxObj, orientedRot, False) - - wboxProp = waterboxObj.ootWaterBoxProperty - roomObj = self.getRoomObjectFromChild(waterboxObj) - waterboxList.append( - WaterBox( - pos, - scale, - emptyScale, - wboxProp.camera, - wboxProp.lighting, - roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, - wboxProp.flag19, - self.useMacros, - ) - ) - - return waterboxList + return colBounds, vertexList, polyList, surfaceList diff --git a/fast64_internal/oot/exporter/collision/classes.py b/fast64_internal/oot/exporter/collision/classes.py index 28b3c415a..096fdbbad 100644 --- a/fast64_internal/oot/exporter/collision/classes.py +++ b/fast64_internal/oot/exporter/collision/classes.py @@ -9,7 +9,7 @@ class CollisionPoly: indices: list[int] ignoreCamera: bool - ignoreActor: bool + ignoreEntity: bool ignoreProjectile: bool enableConveyor: bool normal: Vector @@ -21,9 +21,9 @@ def getFlags_vIA(self): """Returns the value of ``flags_vIA``""" vtxId = self.indices[0] & 0x1FFF - if self.ignoreProjectile or self.ignoreActor or self.ignoreCamera: + if self.ignoreProjectile or self.ignoreEntity or self.ignoreCamera: flag1 = ("COLPOLY_IGNORE_PROJECTILES" if self.useMacros else "(1 << 2)") if self.ignoreProjectile else "" - flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreActor else "" + flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreEntity else "" flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreCamera else "" flags = "(" + " | ".join(flag for flag in [flag1, flag2, flag3] if len(flag) > 0) + ")" else: @@ -41,15 +41,10 @@ def getFlags_vIB(self): flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" - def getVIC(self): - """Returns the value of ``vIC``""" - - vtxId = self.indices[2] & 0x1FFF - return f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF" - def getEntryC(self): """Returns an entry for the collision poly array""" + vtxId = self.indices[2] & 0x1FFF if self.type is None: raise PluginError("ERROR: Surface Type missing!") return ( @@ -59,7 +54,7 @@ def getEntryC(self): f"{self.type}", self.getFlags_vIA(), self.getFlags_vIB(), - self.getVIC(), + f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF", ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), f"{self.dist}", ) @@ -197,26 +192,7 @@ def getEntryC(self): @dataclass -class BgCamFuncData: - """This class defines camera data, if used""" - - pos: tuple[int, int, int] - rot: tuple[int, int, int] - fov: int - roomImageOverrideBgCamIndex: int - - def getEntryC(self): - """Returns an entry for the camera data array""" - - return ( - (indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " },\n") - + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.rot) + " },\n") - + (indent + "{ " + f"{self.fov:6}, {self.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") - ) - - -@dataclass -class CrawlspaceData: +class CrawlspaceCamera: """This class defines camera data for crawlspaces, if used""" points: list[tuple[int, int, int]] @@ -238,18 +214,37 @@ def getInfoEntryC(self, posDataName: str): @dataclass -class BgCamInfo: +class CameraData: + """This class defines camera data, if used""" + + pos: tuple[int, int, int] + rot: tuple[int, int, int] + fov: int + roomImageOverrideBgCamIndex: int + + def getEntryC(self): + """Returns an entry for the camera data array""" + + return ( + (indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " },\n") + + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.rot) + " },\n") + + (indent + "{ " + f"{self.fov:6}, {self.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + ) + + +@dataclass +class CameraInfo: """This class defines camera information data""" setting: str count: int - camData: BgCamFuncData + data: CameraData camIndex: int arrayIndex: int = 0 hasPosData: bool = False def __post_init__(self): - self.hasPosData = self.camData is not None + self.hasPosData = self.data is not None def getInfoEntryC(self, posDataName: str): """Returns an entry for the camera information array""" diff --git a/fast64_internal/oot/exporter/collision/header.py b/fast64_internal/oot/exporter/collision/header.py index ee97595e3..6c8bbc941 100644 --- a/fast64_internal/oot/exporter/collision/header.py +++ b/fast64_internal/oot/exporter/collision/header.py @@ -1,16 +1,30 @@ +import math + +from mathutils import Quaternion, Matrix +from bpy.types import Object from dataclasses import dataclass, field -from ....utility import PluginError, CData, indent +from ....utility import PluginError, CData, checkIdentityRotation, indent +from ...oot_collision_classes import decomp_compat_map_CameraSType +from ...collision.properties import OOTCameraPositionProperty +from ..base import Base from .classes import ( CollisionPoly, SurfaceType, - CrawlspaceData, - BgCamInfo, + CameraData, + CrawlspaceCamera, + CameraInfo, WaterBox, Vertex, ) +@dataclass +class HeaderBase(Base): + sceneObj: Object + transform: Matrix + + @dataclass class Vertices: """This class defines the array of vertices""" @@ -76,24 +90,88 @@ def getC(self): @dataclass -class BgCamInformations: +class BgCamInformations(HeaderBase): """This class defines the array of camera informations and the array of the associated data""" name: str posDataName: str - bgCamInfoList: list[BgCamInfo] - crawlspacePosList: list[CrawlspaceData] + bgCamInfoList: list[CameraInfo] = field(default_factory=list) + crawlspacePosList: list[CrawlspaceCamera] = field(default_factory=list) arrayIdx: int = 0 crawlspaceCount: int = 6 - camFromIndex: dict[int, BgCamInfo | CrawlspaceData] = field(default_factory=dict) + camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(default_factory=dict) + + def initCrawlspaceList(self): + """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" + + crawlspaceObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" + ] + + for obj in crawlspaceObjList: + if self.validateCurveData(obj): + self.crawlspacePosList.append( + CrawlspaceCamera( + [ + [round(value) for value in self.transform @ obj.matrix_world @ point.co] + for point in obj.data.splines[0].points + ], + obj.ootSplineProperty.index, + ) + ) + + def initBgCamInfoList(self): + """Returns a list of camera informations from camera objects""" + + camObjList: list[Object] = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] + camPosData: dict[int, CameraData] = {} + camInfoData: dict[int, CameraInfo] = {} + + for camObj in camObjList: + camProp: OOTCameraPositionProperty = camObj.ootCameraPositionProperty + + if camProp.camSType == "Custom": + setting = camProp.camSTypeCustom + else: + setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) + + if camProp.hasPositionData: + if camProp.index in camPosData: + raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") + + # Camera faces opposite direction + pos, rot, _, _ = self.getConvertedTransformWithOrientation( + self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + ) + + fov = math.degrees(camObj.data.angle) + camPosData[camProp.index] = CameraData( + pos, + rot, + round(fov * 100 if fov > 3.6 else fov), # see CAM_DATA_SCALED() macro + camObj.ootCameraPositionProperty.bgImageOverrideIndex, + ) + + if camProp.index in camInfoData: + raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") + + camInfoData[camProp.index] = CameraInfo( + setting, + 3 if camProp.hasPositionData else 0, # cameras are using 3 entries in the data array + camPosData[camProp.index] if camProp.hasPositionData else None, + camProp.index, + ) + self.bgCamInfoList = list(camInfoData.values()) - def __post_init__(self): + def initCamTable(self): for bgCam in self.bgCamInfoList: if not bgCam.camIndex in self.camFromIndex: self.camFromIndex[bgCam.camIndex] = bgCam else: - raise PluginError(f"ERROR (BgCamInfo): Camera index already used: {bgCam.camIndex}") + raise PluginError(f"ERROR (CameraInfo): Camera index already used: {bgCam.camIndex}") for crawlCam in self.crawlspacePosList: if not crawlCam.camIndex in self.camFromIndex: @@ -107,13 +185,18 @@ def __post_init__(self): i = 0 for val in self.camFromIndex.values(): - if isinstance(val, CrawlspaceData): + if isinstance(val, CrawlspaceCamera): val.arrayIndex = i i += 6 # crawlspaces are using 6 entries in the data array elif val.hasPosData: val.arrayIndex = i i += 3 + def __post_init__(self): + self.initCrawlspaceList() + self.initBgCamInfoList() + self.initCamTable() + def getDataArrayC(self): """Returns the camera data/crawlspace positions array""" @@ -126,10 +209,10 @@ def getDataArrayC(self): # .c posData.source = listName + " = {\n" for val in self.camFromIndex.values(): - if isinstance(val, CrawlspaceData): + if isinstance(val, CrawlspaceCamera): posData.source += val.getDataEntryC() + "\n" elif val.hasPosData: - posData.source += val.camData.getEntryC() + "\n" + posData.source += val.data.getEntryC() + "\n" posData.source = posData.source[:-1] # remove extra newline posData.source += "};\n\n" @@ -139,7 +222,7 @@ def getInfoArrayC(self): """Returns the array containing the informations of each cameras""" bgCamInfoData = CData() - listName = f"BgCamInfo {self.name}[]" + listName = f"CameraInfo {self.name}[]" # .h bgCamInfoData.header = f"extern {listName};\n" @@ -155,11 +238,38 @@ def getInfoArrayC(self): @dataclass -class WaterBoxes: +class WaterBoxes(HeaderBase): """This class defines the array of waterboxes""" name: str - waterboxList: list[WaterBox] + useMacros: bool + + waterboxList: list[WaterBox] = field(default_factory=list) + + def __post_init__(self): + waterboxObjList: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" + ] + + for waterboxObj in waterboxObjList: + emptyScale = waterboxObj.empty_display_size + pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) + checkIdentityRotation(waterboxObj, orientedRot, False) + + wboxProp = waterboxObj.ootWaterBoxProperty + roomObj = self.getRoomObjectFromChild(waterboxObj) + self.waterboxList.append( + WaterBox( + pos, + scale, + emptyScale, + wboxProp.camera, + wboxProp.lighting, + roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, + wboxProp.flag19, + self.useMacros, + ) + ) def getC(self): wboxData = CData() @@ -175,10 +285,19 @@ def getC(self): @dataclass -class CollisionHeader: +class CollisionHeader(HeaderBase): """This class defines the collision header used by the scene""" name: str + sceneName: str + useMacros: bool + + # Ideally functions inside base.py would be there but the file would be really long + colBounds: list[tuple[int, int, int]] + vertexList: list[Vertex] + polyList: list[CollisionPoly] + surfaceTypeList: list[SurfaceType] + minBounds: tuple[int, int, int] = None maxBounds: tuple[int, int, int] = None vertices: Vertices = None @@ -187,18 +306,26 @@ class CollisionHeader: bgCamInfo: BgCamInformations = None waterbox: WaterBoxes = None - def getSceneCollisionC(self): + def __post_init__(self): + self.minBounds = self.colBounds[0] + self.maxBounds = self.colBounds[1] + self.vertices = Vertices(f"{self.sceneName}_vertices", self.vertexList) + self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", self.polyList) + self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", self.surfaceTypeList) + self.bgCamInfo = BgCamInformations( + self.sceneObj, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" + ) + self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) + + def getC(self): """Returns the collision header for the selected scene""" headerData = CData() colData = CData() varName = f"CollisionHeader {self.name}" - vtxPtrLine = "0, NULL" - colPolyPtrLine = "0, NULL" - surfacePtrLine = "NULL" - camPtrLine = "NULL" - wBoxPtrLine = "0, NULL" + wBoxPtrLine = colPolyPtrLine = vtxPtrLine = "0, NULL" + camPtrLine = surfacePtrLine = "NULL" # Add waterbox data if necessary if len(self.waterbox.waterboxList) > 0: diff --git a/fast64_internal/oot/exporter/commands.py b/fast64_internal/oot/exporter/commands.py index bd41c1810..bf8f65434 100644 --- a/fast64_internal/oot/exporter/commands.py +++ b/fast64_internal/oot/exporter/commands.py @@ -49,10 +49,10 @@ def getRoomShapeCmd(self, room: "Room"): return indent + f"SCENE_CMD_ROOM_SHAPE(&{room.roomShape.getName()}),\n" def getObjectListCmd(self, objects: "RoomObjects"): - return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getObjectLengthDefineName()}, {objects.name}),\n" + return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getDefineName()}, {objects.name}),\n" def getActorListCmd(self, actors: "RoomActors"): - return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getActorLengthDefineName()}, {actors.name}),\n" + return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getDefineName()}, {actors.name}),\n" def getRoomCommandList(self, room: "Room", headerIndex: int): cmdListData = CData() @@ -69,7 +69,7 @@ def getRoomCommandList(self, room: "Room", headerIndex: int): if curHeader.infos.setWind: getCmdFuncInfosList.append(self.getWindSettingsCmd) - hasAltHeaders = headerIndex == 0 and room.hasAlternateHeaders() + hasAltHeaders = headerIndex == 0 and room.hasAlternateHeaders roomCmdData = ( (room.getAltHeaderListCmd(room.altHeader.name) if hasAltHeaders else "") + self.getRoomShapeCmd(room) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 960118138..cff566de8 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -84,11 +84,9 @@ def getNewRoomList(self, scene: Scene): roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" roomDict[roomIndex] = Room( - self.sceneObj, - self.transform, - self.useMacros, - roomIndex, roomName, + self.transform, + self.sceneObj, roomObj, roomHeader.roomShape, scene.model.addSubModel( @@ -100,11 +98,11 @@ def getNewRoomList(self, scene: Scene): None, ) ), + roomIndex, ) # Mesh stuff - c = Base(self.sceneObj, self.transform, self.useMacros) - pos, _, scale, _ = c.getConvertedTransform(self.transform, self.sceneObj, roomObj, True) + pos, _, scale, _ = Base().getConvertedTransform(self.transform, self.sceneObj, roomObj, True) cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) DLGroup = roomDict[roomIndex].mesh.addMeshGroup(cullGroup).DLGroup boundingBox = BoundingBox() @@ -174,7 +172,7 @@ def getNewScene(self): try: altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = Scene(self.sceneObj, self.transform, self.useMacros, name=f"{toAlnum(self.sceneName)}_scene") + sceneData = Scene(self.sceneObj, self.transform, self.useMacros, f"{toAlnum(self.sceneName)}_scene") sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) altHeaderData = SceneAlternateHeader(f"{sceneData.name}_alternateHeaders") sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index f9387e969..e9f5f11cf 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -3,54 +3,88 @@ from bpy.types import Object from ....utility import CData, indent from ...oot_constants import ootData +from ...room.properties import OOTRoomHeaderProperty from ..base import Base, Actor @dataclass -class RoomInfos: +class HeaderBase(Base): + name: str = None + props: OOTRoomHeaderProperty = None + sceneObj: Object = None + roomObj: Object = None + transform: Matrix = None + headerIndex: int = None + + +@dataclass +class RoomInfos(HeaderBase): """This class stores various room header informations""" ### General ### - index: int - roomShape: str + index: int = None + roomShape: str = None ### Behavior ### - roomBehavior: str - playerIdleType: str - disableWarpSongs: bool - showInvisActors: bool + roomBehavior: str = None + playerIdleType: str = None + disableWarpSongs: bool = None + showInvisActors: bool = None ### Skybox And Time ### - disableSky: bool - disableSunMoon: bool - hour: int - minute: int - timeSpeed: float - echo: str + disableSky: bool = None + disableSunMoon: bool = None + hour: int = None + minute: int = None + timeSpeed: float = None + echo: str = None ### Wind ### - setWind: bool - direction: tuple[int, int, int] - strength: int + setWind: bool = None + direction: tuple[int, int, int] = None + strength: int = None + + def __post_init__(self): + self.index = self.props.roomIndex + self.roomShape = self.props.roomShape + self.roomBehavior = self.getPropValue(self.props, "roomBehaviour") + self.playerIdleType = self.getPropValue(self.props, "linkIdleMode") + self.disableWarpSongs = self.props.disableWarpSongs + self.showInvisActors = self.props.showInvisibleActors + self.disableSky = self.props.disableSkybox + self.disableSunMoon = self.props.disableSunMoon + self.hour = 0xFF if self.props.leaveTimeUnchanged else self.props.timeHours + self.minute = 0xFF if self.props.leaveTimeUnchanged else self.props.timeMinutes + self.timeSpeed = max(-128, min(127, round(self.props.timeSpeed * 0xA))) + self.echo = self.props.echo + self.setWind = self.props.setWind + self.direction = [d for d in self.props.windVector] if self.props.setWind else None + self.strength = self.props.windStrength if self.props.setWind else None @dataclass -class RoomObjects: +class RoomObjects(HeaderBase): """This class defines an OoT object array""" - name: str - objectList: list[str] + objectList: list[str] = field(default_factory=list) + + def __post_init__(self): + for objProp in self.props.objectList: + if objProp.objectKey == "Custom": + self.objectList.append(objProp.objectIDCustom) + else: + self.objectList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) - def getObjectLengthDefineName(self): + def getDefineName(self): """Returns the name of the define for the total of entries in the object list""" return f"LENGTH_{self.name.upper()}" - def getObjectListC(self): + def getC(self): """Returns the array with the objects the room uses""" objectList = CData() @@ -62,7 +96,7 @@ def getObjectListC(self): # .c objectList.source = ( - (f"{listName}[{self.getObjectLengthDefineName()}]" + " = {\n") + (f"{listName}[{self.getDefineName()}]" + " = {\n") + ",\n".join(indent + objectID for objectID in self.objectList) + ",\n};\n\n" ) @@ -71,15 +105,9 @@ def getObjectListC(self): @dataclass -class RoomActors: +class RoomActors(HeaderBase): """This class defines an OoT actor array""" - name: str - sceneObj: Object - roomObj: Object - transform: Matrix - headerIndex: int - useMacros: bool actorList: list[Actor] = field(default_factory=list) def __post_init__(self): @@ -88,8 +116,7 @@ def __post_init__(self): ] for obj in actorObjList: actorProp = obj.ootActorProperty - c = Base(self.sceneObj, self.transform, self.useMacros) - if not c.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): + if not self.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for @@ -98,7 +125,7 @@ def __post_init__(self): # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if # the current Actor has the ID `None` to avoid export issues. if actorProp.actorID != "None": - pos, rot, _, _ = c.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) actor = Actor() if actorProp.actorID == "Custom": @@ -123,12 +150,12 @@ def __post_init__(self): actor.params = actorProp.actorParam self.actorList.append(actor) - def getActorLengthDefineName(self): + def getDefineName(self): """Returns the name of the define for the total of entries in the actor list""" return f"LENGTH_{self.name.upper()}" - def getActorListC(self): + def getC(self): """Returns the array with the actors the room uses""" actorList = CData() @@ -139,7 +166,7 @@ def getActorListC(self): # .c actorList.source = ( - (f"{listName}[{self.getActorLengthDefineName()}]" + " = {\n") + (f"{listName}[{self.getDefineName()}]" + " = {\n") + "\n".join(actor.getActorEntry() for actor in self.actorList) + "};\n\n" ) @@ -148,13 +175,19 @@ def getActorListC(self): @dataclass -class RoomHeader: +class RoomHeader(HeaderBase): """This class defines a room header""" - name: str - infos: RoomInfos - objects: RoomObjects - actors: RoomActors + infos: RoomInfos = None + objects: RoomObjects = None + actors: RoomActors = None + + def __post_init__(self): + self.infos = RoomInfos(None, self.props) + self.objects = RoomObjects(f"{self.name}_objectList", self.props) + self.actors = RoomActors( + f"{self.name}_actorList", None, self.sceneObj, self.roomObj, self.transform, self.headerIndex + ) def getHeaderDefines(self): """Returns a string containing defines for actor and object lists lengths""" @@ -162,15 +195,15 @@ def getHeaderDefines(self): headerDefines = "" if len(self.objects.objectList) > 0: - defineName = self.objects.getObjectLengthDefineName() + defineName = self.objects.getDefineName() headerDefines += f"#define {defineName} {len(self.objects.objectList)}\n" if len(self.actors.actorList) > 0: - defineName = self.actors.getActorLengthDefineName() + defineName = self.actors.getDefineName() headerDefines += f"#define {defineName} {len(self.actors.actorList)}\n" return headerDefines - + @dataclass class RoomAlternateHeader: diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 4d4c60b0c..2d66c2d49 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -1,56 +1,41 @@ from dataclasses import dataclass +from mathutils import Matrix from bpy.types import Object -from ....utility import PluginError, CData, toAlnum, indent +from ....utility import CData, indent from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ...room.properties import OOTRoomHeaderProperty from ...oot_constants import ootData -from ...oot_level_classes import OOTBGImage, OOTRoomMesh +from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter -from ..commands import RoomCommands from ..classes import RoomFile from ..base import Base, altHeaderList - -from .shape import ( - RoomShapeDListsEntry, - RoomShapeImageMultiBgEntry, - RoomShapeImageMultiBg, - RoomShapeDLists, - RoomShapeImageSingle, - RoomShapeImageMulti, - RoomShapeNormal, - RoomShape, -) - -from .header import ( - RoomInfos, - RoomObjects, - RoomActors, - RoomAlternateHeader, - RoomHeader, -) +from ..commands import RoomCommands +from .header import RoomAlternateHeader, RoomHeader +from .shape import RoomShape @dataclass class Room(Base, RoomCommands): """This class defines a room""" - name: str = None - roomObj: Object = None - roomShapeType: str = None - model: OOTModel = None + name: str + transform: Matrix + sceneObj: Object + roomObj: Object + roomShapeType: str + model: OOTModel + roomIndex: int + headerIndex: int = None mainHeader: RoomHeader = None altHeader: RoomAlternateHeader = None mesh: OOTRoomMesh = None roomShape: RoomShape = None + hasAlternateHeaders: bool = False def __post_init__(self): self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) - - def hasAlternateHeaders(self): - """Returns ``True`` if there's alternate headers data""" - - return self.altHeader is not None + self.hasAlternateHeaders = self.altHeader is not None def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: """Returns the current room header based on the header index""" @@ -68,128 +53,23 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: return None - def getMultiBgEntries(self): - """Returns a list of ``RoomShapeImageMultiBgEntry`` based on mesh data""" - - entries: list[RoomShapeImageMultiBgEntry] = [] - - for i, bgImg in enumerate(self.mesh.bgImages): - entries.append( - RoomShapeImageMultiBgEntry( - i, bgImg.name, bgImg.image.size[0], bgImg.image.size[1], bgImg.otherModeFlags - ) - ) - - return entries - - def getDListsEntries(self): - """Returns a list of ``RoomShapeDListsEntry`` based on mesh data""" - - entries: list[RoomShapeDListsEntry] = [] - - for meshGrp in self.mesh.meshEntries: - entries.append( - RoomShapeDListsEntry( - meshGrp.DLGroup.opaque.name if meshGrp.DLGroup.opaque is not None else "NULL", - meshGrp.DLGroup.transparent.name if meshGrp.DLGroup.transparent is not None else "NULL", - ) - ) - - return entries - def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): """Returns a new room header with the informations from the scene empty object""" self.headerIndex = headerIndex - headerName = f"{self.name}_header{self.headerIndex:02}" - - objIDList = [] - for objProp in headerProp.objectList: - if objProp.objectKey == "Custom": - objIDList.append(objProp.objectIDCustom) - else: - objIDList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) - return RoomHeader( - headerName, - RoomInfos( - headerProp.roomIndex, - headerProp.roomShape, - self.getPropValue(headerProp, "roomBehaviour"), - self.getPropValue(headerProp, "linkIdleMode"), - headerProp.disableWarpSongs, - headerProp.showInvisibleActors, - headerProp.disableSkybox, - headerProp.disableSunMoon, - 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeHours, - 0xFF if headerProp.leaveTimeUnchanged else headerProp.timeMinutes, - max(-128, min(127, round(headerProp.timeSpeed * 0xA))), - headerProp.echo, - headerProp.setWind, - [d for d in headerProp.windVector] if headerProp.setWind else None, - headerProp.windStrength if headerProp.setWind else None, - ), - RoomObjects(f"{headerName}_objectList", objIDList), - RoomActors( - f"{headerName}_actorList", - self.sceneObj, - self.roomObj, - self.transform, - headerIndex, - self.useMacros, - ), + f"{self.name}_header{self.headerIndex:02}", + headerProp, + self.sceneObj, + self.roomObj, + self.transform, + self.headerIndex, ) def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): """Returns a new room shape""" - normal = None - single = None - multiImg = None - multi = None - name = f"{self.name}_shapeHeader" - dlName = f"{self.name}_shapeDListEntry" - - match self.roomShapeType: - case "ROOM_SHAPE_TYPE_NORMAL": - normal = RoomShapeNormal(name, self.roomShapeType, dlName) - case "ROOM_SHAPE_TYPE_IMAGE": - for bgImage in headerProp.bgImageList: - if bgImage.image is None: - raise PluginError( - 'A room is has room shape "Image" but does not have an image set in one of its BG images.' - ) - self.mesh.bgImages.append( - OOTBGImage( - toAlnum(sceneName + "_bg_" + bgImage.image.name), - bgImage.image, - bgImage.otherModeFlags, - ) - ) - - if len(self.mesh.bgImages) > 1: - multiImg = RoomShapeImageMultiBg(f"{self.name}_shapeMultiBg", self.getMultiBgEntries()) - multi = RoomShapeImageMulti( - name, self.roomShapeType, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name - ) - else: - bgImg = self.mesh.bgImages[0] - single = RoomShapeImageSingle( - name, - self.roomShapeType, - "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE", - dlName, - bgImg.name, - bgImg.image.size[0], - bgImg.image.size[1], - bgImg.otherModeFlags, - ) - case _: - raise PluginError(f"ERROR: Room Shape not supported: {self.roomShapeType}") - - return RoomShape( - RoomShapeDLists(dlName, normal is not None, self.getDListsEntries()), normal, single, multiImg, multi - ) + return RoomShape(self.roomShapeType, headerProp, self.mesh, sceneName, self.name) def getRoomMainC(self): """Returns the C data of the main informations of a room""" @@ -198,7 +78,7 @@ def getRoomMainC(self): roomHeaders: list[tuple[RoomHeader, str]] = [] altHeaderPtrList = None - if self.hasAlternateHeaders(): + if self.hasAlternateHeaders: roomHeaders: list[tuple[RoomHeader, str]] = [ (self.altHeader.childNight, "Child Night"), (self.altHeader.adultDay, "Adult Day"), @@ -231,14 +111,14 @@ def getRoomMainC(self): roomC.source += curHeader.getHeaderDefines() roomC.append(self.getRoomCommandList(self, i)) - if i == 0 and self.hasAlternateHeaders() and altHeaderPtrList is not None: + if i == 0 and self.hasAlternateHeaders and altHeaderPtrList is not None: roomC.source += altHeaderPtrList if len(curHeader.objects.objectList) > 0: - roomC.append(curHeader.objects.getObjectListC()) + roomC.append(curHeader.objects.getC()) if len(curHeader.actors.actorList) > 0: - roomC.append(curHeader.actors.getActorListC()) + roomC.append(curHeader.actors.getC()) return roomC diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 983602b97..29375e269 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -1,7 +1,16 @@ -from dataclasses import dataclass -from ....utility import CData, indent +from dataclasses import dataclass, field +from ....utility import PluginError, CData, toAlnum, indent from ....f3d.f3d_gbi import TextureExportSettings from ...oot_level_classes import OOTRoomMesh +from ...oot_level_classes import OOTBGImage +from ...room.properties import OOTRoomHeaderProperty + + +@dataclass +class RoomShapeBase: + type: str + props: OOTRoomHeaderProperty + mesh: OOTRoomMesh @dataclass @@ -9,7 +18,6 @@ class RoomShapeImageBase: """This class defines the basic informations shared by other image classes""" name: str - type: str amountType: str # ROOM_SHAPE_IMAGE_AMOUNT_SINGLE/_MULTI entryArrayName: str @@ -64,11 +72,19 @@ def getEntryC(self): @dataclass -class RoomShapeImageMultiBg: +class RoomShapeImageMultiBg(RoomShapeBase): """This class defines the multiple background image array""" name: str - entries: list[RoomShapeImageMultiBgEntry] + entries: list[RoomShapeImageMultiBgEntry] = field(default_factory=list) + + def __post_init__(self): + for i, bgImg in enumerate(self.mesh.bgImages): + self.entries.append( + RoomShapeImageMultiBgEntry( + i, bgImg.name, bgImg.image.size[0], bgImg.image.size[1], bgImg.otherModeFlags + ) + ) def getC(self): infoData = CData() @@ -89,7 +105,17 @@ class RoomShapeDLists: name: str isArray: bool - entries: list[RoomShapeDListsEntry] + mesh: OOTRoomMesh + entries: list[RoomShapeDListsEntry] = field(default_factory=list) + + def __post_init__(self): + for meshGrp in self.mesh.meshEntries: + self.entries.append( + RoomShapeDListsEntry( + meshGrp.DLGroup.opaque.name if meshGrp.DLGroup.opaque is not None else "NULL", + meshGrp.DLGroup.transparent.name if meshGrp.DLGroup.transparent is not None else "NULL", + ) + ) def getC(self): infoData = CData() @@ -210,26 +236,71 @@ def getC(self): @dataclass -class RoomShape: +class RoomShape(RoomShapeBase): """This class hosts every type of room shape""" - dl: RoomShapeDLists - normal: RoomShapeNormal - single: RoomShapeImageSingle - multiImg: RoomShapeImageMultiBg - multi: RoomShapeImageMulti + sceneName: str + roomName: str + + dl: RoomShapeDLists = None + normal: RoomShapeNormal = None + single: RoomShapeImageSingle = None + multiImg: RoomShapeImageMultiBg = None + multi: RoomShapeImageMulti = None + + def __post_init__(self): + name = f"{self.roomName}_shapeHeader" + dlName = f"{self.roomName}_shapeDListEntry" + + match self.type: + case "ROOM_SHAPE_TYPE_NORMAL": + self.normal = RoomShapeNormal(name, self.type, dlName) + case "ROOM_SHAPE_TYPE_IMAGE": + for bgImage in self.props.bgImageList: + if bgImage.image is None: + raise PluginError( + 'A room is has room shape "Image" but does not have an image set in one of its BG images.' + ) + self.mesh.bgImages.append( + OOTBGImage( + toAlnum(self.sceneName + "_bg_" + bgImage.image.name), + bgImage.image, + bgImage.otherModeFlags, + ) + ) + + if len(self.mesh.bgImages) > 1: + self.multiImg = RoomShapeImageMultiBg(None, None, self.mesh, f"{self.roomName}_shapeMultiBg") + self.multi = RoomShapeImageMulti( + name, self.type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, self.multiImg.name + ) + else: + bgImg = self.mesh.bgImages[0] + self.single = RoomShapeImageSingle( + name, + self.type, + "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE", + dlName, + bgImg.name, + bgImg.image.size[0], + bgImg.image.size[1], + bgImg.otherModeFlags, + ) + case _: + raise PluginError(f"ERROR: Room Shape not supported: {self.type}") + self.dl = RoomShapeDLists(dlName, self.normal is not None, self.mesh) def getName(self): """Returns the correct room shape name based on the type used""" if self.normal is not None: return self.normal.name - - if self.single is not None: + elif self.single is not None: return self.single.name - - if self.multi is not None and self.multiImg is not None: + elif self.multi is not None and self.multiImg is not None: return self.multi.name + else: + raise PluginError("ERROR: Name not found!") def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): """Returns the image data for image room shapes""" diff --git a/fast64_internal/oot/exporter/scene/base.py b/fast64_internal/oot/exporter/scene/base.py index fd068abfb..5efa0756b 100644 --- a/fast64_internal/oot/exporter/scene/base.py +++ b/fast64_internal/oot/exporter/scene/base.py @@ -18,211 +18,3 @@ @dataclass class SceneBase(CollisionBase, SceneCommands): """This class hosts various data and functions related to a scene file""" - - name: str = None - model: OOTModel = None - headerIndex: int = None - mainHeader: SceneHeader = None - altHeader: SceneAlternateHeader = None - roomList: list["Room"] = field(default_factory=list) - - def validateRoomIndices(self): - """Checks if there are multiple rooms with the same room index""" - - for i, room in enumerate(self.roomList): - if i != room.roomIndex: - return False - return True - - def validateScene(self): - """Performs safety checks related to the scene data""" - - if not len(self.roomList) > 0: - raise PluginError("ERROR: This scene does not have any rooms!") - - if not self.validateRoomIndices(): - raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") - - def hasAlternateHeaders(self): - """Checks if this scene is using alternate headers""" - - return self.altHeader is not None - - def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: - """Returns the scene header based on the header index""" - - if headerIndex == 0: - return self.mainHeader - - for i, header in enumerate(altHeaderList, 1): - if headerIndex == i: - return getattr(self.altHeader, header) - - for i, csHeader in enumerate(self.altHeader.cutscenes, 4): - if headerIndex == i: - return csHeader - - return None - - def getExitListFromProps(self, headerProp: OOTSceneHeaderProperty): - """Returns the exit list from the current scene header""" - - exitList: list[tuple[int, str]] = [] - for i, exitProp in enumerate(headerProp.exitList): - if exitProp.exitIndex != "Custom": - raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") - - exitList.append((i, exitProp.exitIndexCustom)) - - return exitList - - def getTransActorListFromProps(self): - """Returns the transition actor list based on empty objects with the type 'Transition Actor'""" - - actorList: list[TransitionActor] = [] - actorObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - self.roomIndex = roomObj.ootRoomHeader.roomIndex - - transActorProp = obj.ootTransitionActorProperty - - if ( - self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) - and transActorProp.actor.actorID != "None" - ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - transActor = TransitionActor() - - if transActorProp.dontTransition: - front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) - back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - else: - front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) - - if transActorProp.actor.actorID == "Custom": - transActor.id = transActorProp.actor.actorIDCustom - else: - transActor.id = transActorProp.actor.actorID - - transActor.name = ( - ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( - f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if transActorProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - transActor.pos = pos - transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? - transActor.params = transActorProp.actor.actorParam - transActor.roomFrom, transActor.cameraFront = front - transActor.roomTo, transActor.cameraBack = back - actorList.append(transActor) - return actorList - - def getEntranceActorListFromProps(self): - """Returns the entrance actor list based on empty objects with the type 'Entrance'""" - - entranceActorFromIndex: dict[int, EntranceActor] = {} - actorObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - - entranceProp = obj.ootEntranceProperty - if ( - self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) - and entranceProp.actor.actorID != "None" - ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - entranceActor = EntranceActor() - - entranceActor.name = ( - ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( - f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if entranceProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom - entranceActor.pos = pos - entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - entranceActor.params = entranceProp.actor.actorParam - entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex - entranceActor.spawnIndex = entranceProp.spawnIndex - - if not entranceProp.spawnIndex in entranceActorFromIndex: - entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor - else: - raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") - - entranceActorFromIndex = dict(sorted(entranceActorFromIndex.items())) - if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): - raise PluginError("ERROR: The spawn indices are not consecutive!") - - return list(entranceActorFromIndex.values()) - - def getPathListFromProps(self, listNameBase: str): - """Returns the pathway list from spline objects with the type 'Path'""" - - pathList: list[Path] = [] - pathObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" - ] - - for i, obj in enumerate(pathObjList): - isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) - if isHeaderValid and self.validateCurveData(obj): - pathList.append( - Path( - f"{listNameBase}{i:02}", [self.transform @ point.co.xyz for point in obj.data.splines[0].points] - ) - ) - - return pathList - - def getEnvLightSettingsListFromProps(self, headerProp: OOTSceneHeaderProperty, lightMode: str): - """Returns the environment light settings list from the current scene header""" - - lightList: list[OOTLightProperty] = [] - lightSettings: list[EnvLightSettings] = [] - - if lightMode == "LIGHT_MODE_TIME": - todLights = headerProp.timeOfDayLights - lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] - else: - lightList = headerProp.lightList - - for lightProp in lightList: - light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) - light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) - lightSettings.append( - EnvLightSettings( - lightMode, - exportColor(lightProp.ambient), - light1[0], - light1[1], - light2[0], - light2[1], - exportColor(lightProp.fogColor), - lightProp.fogNear, - lightProp.fogFar, - lightProp.transitionSpeed, - ) - ) - - return lightSettings diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index ec2afa3a5..bb9fa88ff 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -1,49 +1,103 @@ from dataclasses import dataclass, field +from mathutils import Matrix from bpy.types import Object -from ....utility import PluginError, CData, indent +from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent +from ...oot_constants import ootData +from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty +from ..base import Base from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path @dataclass -class SceneInfos: +class HeaderBase(Base): + props: OOTSceneHeaderProperty + + +@dataclass +class SceneInfos(HeaderBase): """This class stores various scene header informations""" + sceneObj: Object + ### General ### - keepObjectID: str - naviHintType: str - drawConfig: str - appendNullEntrance: bool - useDummyRoomList: bool + keepObjectID: str = None + naviHintType: str = None + drawConfig: str = None + appendNullEntrance: bool = None + useDummyRoomList: bool = None ### Skybox And Sound ### # Skybox - skyboxID: str - skyboxConfig: str + skyboxID: str = None + skyboxConfig: str = None # Sound - sequenceID: str - ambienceID: str - specID: str + sequenceID: str = None + ambienceID: str = None + specID: str = None ### Camera And World Map ### # World Map - worldMapLocation: str + worldMapLocation: str = None # Camera - sceneCamType: str + sceneCamType: str = None + + def __post_init__(self): + self.keepObjectID = self.getPropValue(self.props, "globalObject") + self.naviHintType = self.getPropValue(self.props, "naviCup") + self.drawConfig = self.getPropValue(self.props.sceneTableEntry, "drawConfig") + self.appendNullEntrance = self.props.appendNullEntrance + self.useDummyRoomList = self.sceneObj.fast64.oot.scene.write_dummy_room_list + self.skyboxID = self.getPropValue(self.props, "skyboxID") + self.skyboxConfig = self.getPropValue(self.props, "skyboxCloudiness") + self.sequenceID = self.getPropValue(self.props, "musicSeq") + self.ambienceID = self.getPropValue(self.props, "nightSeq") + self.specID = self.getPropValue(self.props, "audioSessionPreset") + self.worldMapLocation = self.getPropValue(self.props, "mapLocation") + self.sceneCamType = self.getPropValue(self.props, "cameraMode") @dataclass -class SceneLighting: +class SceneLighting(HeaderBase): """This class hosts lighting data""" name: str + envLightMode: str = None settings: list[EnvLightSettings] = field(default_factory=list) + def __post_init__(self): + self.envLightMode = self.getPropValue(self.props, "skyboxLighting") + lightList: list[OOTLightProperty] = [] + + if self.envLightMode == "LIGHT_MODE_TIME": + todLights = self.props.timeOfDayLights + lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] + else: + lightList = self.props.lightList + + for lightProp in lightList: + light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) + light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) + self.settings.append( + EnvLightSettings( + self.envLightMode, + exportColor(lightProp.ambient), + light1[0], + light1[1], + light2[0], + light2[1], + exportColor(lightProp.fogColor), + lightProp.fogNear, + lightProp.fogFar, + lightProp.transitionSpeed, + ) + ) + def getEnvLightSettingsC(self): """Returns a ``CData`` containing the C data of env. light settings""" @@ -64,18 +118,28 @@ def getEnvLightSettingsC(self): @dataclass -class SceneCutscene: +class SceneCutscene(HeaderBase): """This class hosts cutscene data (unfinished)""" headerIndex: int - writeType: str - writeCutscene: bool - csObj: Object - csWriteCustom: str - extraCutscenes: list[Object] + + writeType: str = None + writeCutscene: bool = None + csObj: Object = None + csWriteCustom: str = None + extraCutscenes: list[Object] = field(default_factory=list) name: str = None def __post_init__(self): + self.writeType = self.props.csWriteType + self.writeCutscene = self.props.writeCutscene + self.csObj = self.props.csWriteObject + self.csWriteCustom = self.props.csWriteCustom if self.props.csWriteType == "Custom" else None + self.extraCutscenes = [csObj for csObj in self.props.extraCutscenes] + + if self.writeCutscene and self.writeType == "Embedded": + raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") + if self.headerIndex > 0 and len(self.extraCutscenes) > 0: raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") @@ -96,12 +160,18 @@ def getCutsceneC(self): @dataclass -class SceneExits: +class SceneExits(HeaderBase): """This class hosts exit data""" - name: str = None + name: str exitList: list[tuple[int, str]] = field(default_factory=list) + def __post_init__(self): + for i, exitProp in enumerate(self.props.exitList): + if exitProp.exitIndex != "Custom": + raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") + self.exitList.append((i, exitProp.exitIndexCustom)) + def getExitListC(self): """Returns a ``CData`` containing the C data of the exit array""" @@ -123,9 +193,12 @@ def getExitListC(self): @dataclass -class SceneActors: +class SceneActors(HeaderBase): """This class handles scene actors (transition actors and entrance actors)""" + sceneObj: Object + transform: Matrix + headerIndex: int entranceListName: str startPositionsName: str transActorListName: str @@ -133,6 +206,106 @@ class SceneActors: transitionActorList: list[TransitionActor] = field(default_factory=list) entranceActorList: list[EntranceActor] = field(default_factory=list) + def initTransActorList(self): + """Returns the transition actor list based on empty objects with the type 'Transition Actor'""" + + actorObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + self.roomIndex = roomObj.ootRoomHeader.roomIndex + + transActorProp = obj.ootTransitionActorProperty + + if ( + self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) + and transActorProp.actor.actorID != "None" + ): + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + transActor = TransitionActor() + + if transActorProp.dontTransition: + front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) + back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + else: + front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + + if transActorProp.actor.actorID == "Custom": + transActor.id = transActorProp.actor.actorIDCustom + else: + transActor.id = transActorProp.actor.actorID + + transActor.name = ( + ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( + f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if transActorProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + transActor.pos = pos + transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? + transActor.params = transActorProp.actor.actorParam + transActor.roomFrom, transActor.cameraFront = front + transActor.roomTo, transActor.cameraBack = back + self.transitionActorList.append(transActor) + + def initEntranceActorList(self): + """Returns the entrance actor list based on empty objects with the type 'Entrance'""" + + entranceActorFromIndex: dict[int, EntranceActor] = {} + actorObjList: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + + entranceProp = obj.ootEntranceProperty + if ( + self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) + and entranceProp.actor.actorID != "None" + ): + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + entranceActor = EntranceActor() + + entranceActor.name = ( + ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( + f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if entranceProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom + entranceActor.pos = pos + entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + entranceActor.params = entranceProp.actor.actorParam + entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex + entranceActor.spawnIndex = entranceProp.spawnIndex + + if not entranceProp.spawnIndex in entranceActorFromIndex: + entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor + else: + raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") + + entranceActorFromIndex = dict(sorted(entranceActorFromIndex.items())) + if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): + raise PluginError("ERROR: The spawn indices are not consecutive!") + + self.entranceActorList = list(entranceActorFromIndex.values()) + + def __post_init__(self): + self.initTransActorList() + self.initEntranceActorList() + def getSpawnActorListC(self): """Returns the spawn actor array""" @@ -190,11 +363,32 @@ def getTransActorListC(self): @dataclass -class ScenePathways: +class ScenePathways(HeaderBase): """This class hosts pathways array data""" name: str - pathList: list[Path] + sceneObj: Object + transform: Matrix + headerIndex: int + + pathList: list[Path] = field(default_factory=list) + + def __post_init__(self): + pathObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" + ] + + for i, obj in enumerate(pathObjList): + isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) + if isHeaderValid and self.validateCurveData(obj): + self.pathList.append( + Path( + f"{self.name}List{i:02}", + [self.transform @ point.co.xyz for point in obj.data.splines[0].points], + ) + ) def getPathC(self): """Returns a ``CData`` containing the C data of the pathway array""" @@ -220,16 +414,37 @@ def getPathC(self): @dataclass -class SceneHeader: +class SceneHeader(HeaderBase): """This class defines a scene header""" name: str - infos: SceneInfos - lighting: SceneLighting - cutscene: SceneCutscene - exits: SceneExits - actors: SceneActors - path: ScenePathways + sceneObj: Object + transform: Matrix + headerIndex: int + + infos: SceneInfos = None + lighting: SceneLighting = None + cutscene: SceneCutscene = None + exits: SceneExits = None + actors: SceneActors = None + path: ScenePathways = None + + def __post_init__(self): + self.infos = SceneInfos(self.props, self.sceneObj) + self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") + if self.props.writeCutscene: + self.cutscene = SceneCutscene(self.props, self.headerIndex) + self.exits = SceneExits(self.props, f"{self.name}_exitList") + self.actors = SceneActors( + self.props, + self.sceneObj, + self.transform, + self.headerIndex, + f"{self.name}_entranceList", + f"{self.name}_playerEntryList", + f"{self.name}_transitionActors", + ) + self.path = ScenePathways(self.props, f"{self.name}_pathway", self.sceneObj, self.transform, self.headerIndex) def getHeaderC(self): """Returns the ``CData`` containing the header's data""" diff --git a/fast64_internal/oot/exporter/scene/main.py b/fast64_internal/oot/exporter/scene/main.py index 93a0c7b0f..06b707c0f 100644 --- a/fast64_internal/oot/exporter/scene/main.py +++ b/fast64_internal/oot/exporter/scene/main.py @@ -1,113 +1,98 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field +from typing import TYPE_CHECKING from ....utility import PluginError, CData, indent from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod from ...oot_model_classes import OOTGfxFormatter from ...scene.properties import OOTSceneHeaderProperty from ..classes import SceneFile -from .base import SceneBase - -from ..collision import ( - Vertices, - CollisionPolygons, - SurfaceTypes, - BgCamInformations, - WaterBoxes, - CollisionHeader, -) - -from .header import ( - SceneInfos, - SceneLighting, - SceneCutscene, - SceneExits, - SceneActors, - ScenePathways, - SceneHeader, -) +from ..collision import CollisionHeader +from .header import SceneHeader +from ...oot_model_classes import OOTModel +from ..commands import SceneCommands +from ..base import altHeaderList +from ..collision import CollisionBase +from .header import SceneAlternateHeader, SceneHeader + +if TYPE_CHECKING: + from ..room import Room @dataclass -class Scene(SceneBase): +class Scene(CollisionBase, SceneCommands): """This class defines a scene""" + name: str = None + model: OOTModel = None + headerIndex: int = None + mainHeader: SceneHeader = None + altHeader: SceneAlternateHeader = None + roomList: list["Room"] = field(default_factory=list) roomListName: str = None colHeader: CollisionHeader = None def __post_init__(self): self.roomListName = f"{self.name}_roomList" + def validateRoomIndices(self): + """Checks if there are multiple rooms with the same room index""" + + for i, room in enumerate(self.roomList): + if i != room.roomIndex: + return False + return True + + def validateScene(self): + """Performs safety checks related to the scene data""" + + if not len(self.roomList) > 0: + raise PluginError("ERROR: This scene does not have any rooms!") + + if not self.validateRoomIndices(): + raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") + + def hasAlternateHeaders(self): + """Checks if this scene is using alternate headers""" + + return self.altHeader is not None + + def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: + """Returns the scene header based on the header index""" + + if headerIndex == 0: + return self.mainHeader + + for i, header in enumerate(altHeaderList, 1): + if headerIndex == i: + return getattr(self.altHeader, header) + + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): + if headerIndex == i: + return csHeader + + return None + def getNewCollisionHeader(self): """Returns and creates collision data""" - colBounds, vertexList, polyList, surfaceTypeList = self.getColSurfaceVtxDataFromMeshObj() - bgCamInfoList = self.getBgCamInfoDataFromObjects() - + colBounds, vertexList, polyList, surfaceTypeList = self.getCollisionData() return CollisionHeader( + self.sceneObj, + self.transform, f"{self.name}_collisionHeader", - colBounds[0], - colBounds[1], - Vertices(f"{self.name}_vertices", vertexList), - CollisionPolygons(f"{self.name}_polygons", polyList), - SurfaceTypes(f"{self.name}_polygonTypes", surfaceTypeList), - BgCamInformations( - f"{self.name}_bgCamInfo", - f"{self.name}_camPosData", - bgCamInfoList, - self.getCrawlspaceDataFromObjects(), - ), - WaterBoxes(f"{self.name}_waterBoxes", self.getWaterBoxDataFromObjects()), + self.name, + self.useMacros, + colBounds, + vertexList, + polyList, + surfaceTypeList, ) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): """Returns the scene header""" self.headerIndex = headerIndex - headerName = f"{self.name}_header{self.headerIndex:02}" - lightMode = self.getPropValue(headerProp, "skyboxLighting") - - if headerProp.writeCutscene and headerProp.csWriteType == "Embedded": - raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") - return SceneHeader( - headerName, - SceneInfos( - self.getPropValue(headerProp, "globalObject"), - self.getPropValue(headerProp, "naviCup"), - self.getPropValue(headerProp.sceneTableEntry, "drawConfig"), - headerProp.appendNullEntrance, - self.sceneObj.fast64.oot.scene.write_dummy_room_list, - self.getPropValue(headerProp, "skyboxID"), - self.getPropValue(headerProp, "skyboxCloudiness"), - self.getPropValue(headerProp, "musicSeq"), - self.getPropValue(headerProp, "nightSeq"), - self.getPropValue(headerProp, "audioSessionPreset"), - self.getPropValue(headerProp, "mapLocation"), - self.getPropValue(headerProp, "cameraMode"), - ), - SceneLighting( - f"{headerName}_lightSettings", - lightMode, - self.getEnvLightSettingsListFromProps(headerProp, lightMode), - ), - SceneCutscene( - headerIndex, - headerProp.csWriteType, - headerProp.writeCutscene, - headerProp.csWriteObject, - headerProp.csWriteCustom if headerProp.csWriteType == "Custom" else None, - [csObj for csObj in headerProp.extraCutscenes], - ) - if headerProp.writeCutscene - else None, - SceneExits(f"{headerName}_exitList", self.getExitListFromProps(headerProp)), - SceneActors( - f"{headerName}_entranceList", - f"{headerName}_playerEntryList", - f"{headerName}_transitionActors", - self.getTransActorListFromProps(), - self.getEntranceActorListFromProps(), - ), - ScenePathways(f"{headerName}_pathway", self.getPathListFromProps(f"{headerName}_pathwayList")), + headerProp, f"{self.name}_header{self.headerIndex:02}", self.sceneObj, self.transform, headerIndex ) def getRoomListC(self): @@ -209,7 +194,7 @@ def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: """Gets and sets C data for every scene elements""" sceneMainData = self.getSceneMainC() - sceneCollisionData = self.colHeader.getSceneCollisionC() + sceneCollisionData = self.colHeader.getC() sceneCutsceneData = self.getSceneCutscenesC() sceneTexturesData = self.getSceneTexturesC(textureExportSettings) From a0987eb06f3f4608893c133fa665c99f06f2566a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 03:36:59 +0200 Subject: [PATCH 47/98] deleted useless file --- fast64_internal/oot/exporter/scene/base.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 fast64_internal/oot/exporter/scene/base.py diff --git a/fast64_internal/oot/exporter/scene/base.py b/fast64_internal/oot/exporter/scene/base.py deleted file mode 100644 index 5efa0756b..000000000 --- a/fast64_internal/oot/exporter/scene/base.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass, field -from bpy.types import Object -from typing import TYPE_CHECKING -from ....utility import PluginError, exportColor, ootGetBaseOrCustomLight -from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ...oot_constants import ootData -from ...oot_model_classes import OOTModel -from ..commands import SceneCommands -from ..base import altHeaderList -from ..collision import CollisionBase -from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path -from .header import SceneAlternateHeader, SceneHeader - -if TYPE_CHECKING: - from ..room import Room - - -@dataclass -class SceneBase(CollisionBase, SceneCommands): - """This class hosts various data and functions related to a scene file""" From 1863a9b276e74f4532f933fcd2319b3ec7793b50 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:58:28 +0200 Subject: [PATCH 48/98] more code reorganisation --- .../oot/exporter/collision/header.py | 5 +- fast64_internal/oot/exporter/commands.py | 200 ------------------ fast64_internal/oot/exporter/room/header.py | 25 +++ fast64_internal/oot/exporter/room/main.py | 26 ++- fast64_internal/oot/exporter/room/shape.py | 3 + .../oot/exporter/scene/__init__.py | 2 +- fast64_internal/oot/exporter/scene/classes.py | 8 +- fast64_internal/oot/exporter/scene/header.py | 183 ++++++++++------ fast64_internal/oot/exporter/scene/main.py | 48 ++++- 9 files changed, 211 insertions(+), 289 deletions(-) delete mode 100644 fast64_internal/oot/exporter/commands.py diff --git a/fast64_internal/oot/exporter/collision/header.py b/fast64_internal/oot/exporter/collision/header.py index 6c8bbc941..ce247b0f8 100644 --- a/fast64_internal/oot/exporter/collision/header.py +++ b/fast64_internal/oot/exporter/collision/header.py @@ -222,7 +222,7 @@ def getInfoArrayC(self): """Returns the array containing the informations of each cameras""" bgCamInfoData = CData() - listName = f"CameraInfo {self.name}[]" + listName = f"BgCamInfo {self.name}[]" # .h bgCamInfoData.header = f"extern {listName};\n" @@ -317,6 +317,9 @@ def __post_init__(self): ) self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) + def getCmd(self): + return indent + f"SCENE_CMD_COL_HEADER(&{self.name}),\n" + def getC(self): """Returns the collision header for the selected scene""" diff --git a/fast64_internal/oot/exporter/commands.py b/fast64_internal/oot/exporter/commands.py deleted file mode 100644 index bf8f65434..000000000 --- a/fast64_internal/oot/exporter/commands.py +++ /dev/null @@ -1,200 +0,0 @@ -from ...utility import CData, indent -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from .collision import CollisionHeader - from .room import Room, RoomInfos, RoomObjects, RoomActors - from .scene import ( - Scene, - SceneInfos, - SceneHeader, - SceneLighting, - SceneCutscene, - SceneActors, - ScenePathways, - ) - - -class RoomCommands: - """This class defines the command list for rooms""" - - def getEchoSettingsCmd(self, infos: "RoomInfos"): - return indent + f"SCENE_CMD_ECHO_SETTINGS({infos.echo})" - - def getRoomBehaviourCmd(self, infos: "RoomInfos"): - showInvisibleActors = "true" if infos.showInvisActors else "false" - disableWarpSongs = "true" if infos.disableWarpSongs else "false" - - return ( - (indent + "SCENE_CMD_ROOM_BEHAVIOR(") - + ", ".join([infos.roomBehavior, infos.playerIdleType, showInvisibleActors, disableWarpSongs]) - + ")" - ) - - def getSkyboxDisablesCmd(self, infos: "RoomInfos"): - disableSkybox = "true" if infos.disableSky else "false" - disableSunMoon = "true" if infos.disableSunMoon else "false" - - return indent + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})" - - def getTimeSettingsCmd(self, infos: "RoomInfos"): - return indent + f"SCENE_CMD_TIME_SETTINGS({infos.hour}, {infos.minute}, {infos.timeSpeed})" - - def getWindSettingsCmd(self, infos: "RoomInfos"): - return ( - indent + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in infos.direction)}, {infos.strength}),\n" - ) - - def getRoomShapeCmd(self, room: "Room"): - return indent + f"SCENE_CMD_ROOM_SHAPE(&{room.roomShape.getName()}),\n" - - def getObjectListCmd(self, objects: "RoomObjects"): - return (indent + "SCENE_CMD_OBJECT_LIST(") + f"{objects.getDefineName()}, {objects.name}),\n" - - def getActorListCmd(self, actors: "RoomActors"): - return (indent + "SCENE_CMD_ACTOR_LIST(") + f"{actors.getDefineName()}, {actors.name}),\n" - - def getRoomCommandList(self, room: "Room", headerIndex: int): - cmdListData = CData() - curHeader = room.getRoomHeaderFromIndex(headerIndex) - listName = f"SceneCmd {curHeader.name}" - - getCmdFuncInfosList = [ - self.getEchoSettingsCmd, - self.getRoomBehaviourCmd, - self.getSkyboxDisablesCmd, - self.getTimeSettingsCmd, - ] - - if curHeader.infos.setWind: - getCmdFuncInfosList.append(self.getWindSettingsCmd) - - hasAltHeaders = headerIndex == 0 and room.hasAlternateHeaders - roomCmdData = ( - (room.getAltHeaderListCmd(room.altHeader.name) if hasAltHeaders else "") - + self.getRoomShapeCmd(room) - + (self.getObjectListCmd(curHeader.objects) if len(curHeader.objects.objectList) > 0 else "") - + (self.getActorListCmd(curHeader.actors) if len(curHeader.actors.actorList) > 0 else "") - + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdFuncInfosList) + ",\n") - + room.getEndCmd() - ) - - # .h - cmdListData.header = f"extern {listName}[];\n" - - # .c - cmdListData.source = f"{listName}[]" + " = {\n" + roomCmdData + "};\n\n" - - return cmdListData - - -class SceneCommands: - """This class defines the command list for scenes""" - - def getSoundSettingsCmd(self, infos: "SceneInfos"): - return indent + f"SCENE_CMD_SOUND_SETTINGS({infos.specID}, {infos.ambienceID}, {infos.sequenceID})" - - def getRoomListCmd(self, scene: "Scene"): - return indent + f"SCENE_CMD_ROOM_LIST({len(scene.roomList)}, {scene.roomListName}),\n" - - def getTransActorListCmd(self, actors: "SceneActors"): - return ( - indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" - ) + f"{len(actors.transitionActorList)}, {actors.transActorListName})" - - def getMiscSettingsCmd(self, infos: "SceneInfos"): - return indent + f"SCENE_CMD_MISC_SETTINGS({infos.sceneCamType}, {infos.worldMapLocation})" - - def getColHeaderCmd(self, colHeader: "CollisionHeader"): - return indent + f"SCENE_CMD_COL_HEADER(&{colHeader.name}),\n" - - def getSpawnListCmd(self, actors: "SceneActors"): - return ( - indent + "SCENE_CMD_ENTRANCE_LIST(" - ) + f"{actors.entranceListName if len(actors.entranceActorList) > 0 else 'NULL'})" - - def getSpecialFilesCmd(self, infos: "SceneInfos"): - return indent + f"SCENE_CMD_SPECIAL_FILES({infos.naviHintType}, {infos.keepObjectID})" - - def getPathListCmd(self, path: "ScenePathways"): - return indent + f"SCENE_CMD_PATH_LIST({path.name}),\n" if len(path.pathList) > 0 else "" - - def getSpawnActorListCmd(self, scene: "Scene", headerIndex: int): - curHeader = scene.getSceneHeaderFromIndex(headerIndex) - startPosName = curHeader.actors.startPositionsName - return ( - (indent + "SCENE_CMD_SPAWN_LIST(") - + f"{len(curHeader.actors.entranceActorList)}, " - + f"{startPosName if len(curHeader.actors.entranceActorList) > 0 else 'NULL'})" - ) - - def getSkyboxSettingsCmd(self, infos: "SceneInfos", lights: "SceneLighting"): - return indent + f"SCENE_CMD_SKYBOX_SETTINGS({infos.skyboxID}, {infos.skyboxConfig}, {lights.envLightMode}),\n" - - def getExitListCmd(self, scene: "Scene", headerIndex: int): - curHeader = scene.getSceneHeaderFromIndex(headerIndex) - return indent + f"SCENE_CMD_EXIT_LIST({curHeader.exits.name})" - - def getLightSettingsCmd(self, lights: "SceneLighting"): - return ( - indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" - ) + f"{len(lights.settings)}, {lights.name if len(lights.settings) > 0 else 'NULL'}),\n" - - def getCutsceneDataCmd(self, cs: "SceneCutscene"): - match cs.writeType: - case "Object": - csDataName = cs.csObj.name - case _: - csDataName = cs.csWriteCustom - - return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" - - def getSceneCommandList(self, scene: "Scene", curHeader: "SceneHeader", headerIndex: int): - cmdListData = CData() - listName = f"SceneCmd {curHeader.name}" - - getCmdFunc1List = [ - self.getSpawnActorListCmd, - ] - - getCmdGeneralList = [ - self.getSoundSettingsCmd, - self.getMiscSettingsCmd, - self.getSpecialFilesCmd, - ] - - getCmdActorList = [ - self.getSpawnListCmd, - ] - - if len(curHeader.exits.exitList) > 0: - getCmdFunc1List.append(self.getExitListCmd) - - if len(curHeader.actors.transitionActorList) > 0: - getCmdActorList.insert(0, self.getTransActorListCmd) - - # if scene.writeCutscene: - # getCmdFunc2ArgList.append(self.getCutsceneDataCmd) - - hasAltHeaders = headerIndex == 0 and scene.hasAlternateHeaders() - sceneCmdData = ( - (scene.getAltHeaderListCmd(scene.altHeader.name) if hasAltHeaders else "") - + self.getColHeaderCmd(scene.colHeader) - + self.getRoomListCmd(scene) - + self.getSkyboxSettingsCmd(curHeader.infos, curHeader.lighting) - + self.getLightSettingsCmd(curHeader.lighting) - + self.getPathListCmd(curHeader.path) - # + (self.getCutsceneDataCmd(curHeader.cutscene) if curHeader.cutscene.writeCutscene else "") - + (",\n".join(getCmd(curHeader.infos) for getCmd in getCmdGeneralList) + ",\n") - + (",\n".join(getCmd(curHeader.actors) for getCmd in getCmdActorList) + ",\n") - + (",\n".join(getCmd(scene, headerIndex) for getCmd in getCmdFunc1List) + ",\n") - + scene.getEndCmd() - ) - - # .h - cmdListData.header = f"extern {listName}[]" + ";\n" - - # .c - cmdListData.source = f"{listName}[]" + " = {\n" + sceneCmdData + "};\n\n" - - return cmdListData diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index e9f5f11cf..dbb82c91a 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -65,6 +65,25 @@ def __post_init__(self): self.direction = [d for d in self.props.windVector] if self.props.setWind else None self.strength = self.props.windStrength if self.props.setWind else None + def getCmds(self): + showInvisActors = "true" if self.showInvisActors else "false" + disableWarpSongs = "true" if self.disableWarpSongs else "false" + disableSkybox = "true" if self.disableSky else "false" + disableSunMoon = "true" if self.disableSunMoon else "false" + + roomBehaviorArgs = f"{self.roomBehavior}, {self.playerIdleType}, {showInvisActors}, {disableWarpSongs}" + cmdList = [ + f"SCENE_CMD_ECHO_SETTINGS({self.echo})", + f"SCENE_CMD_ROOM_BEHAVIOR({roomBehaviorArgs})", + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})", + f"SCENE_CMD_TIME_SETTINGS({self.hour}, {self.minute}, {self.timeSpeed})", + ] + + if self.setWind: + cmdList.append(f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in self.direction)}, {self.strength})") + + return indent + f",\n{indent}".join(cmdList) + ",\n" + @dataclass class RoomObjects(HeaderBase): @@ -84,6 +103,9 @@ def getDefineName(self): return f"LENGTH_{self.name.upper()}" + def getCmd(self): + return indent + f"SCENE_CMD_OBJECT_LIST({self.getDefineName()}, {self.name}),\n" + def getC(self): """Returns the array with the objects the room uses""" @@ -155,6 +177,9 @@ def getDefineName(self): return f"LENGTH_{self.name.upper()}" + def getCmd(self): + return indent + f"SCENE_CMD_ACTOR_LIST({self.getDefineName()}, {self.name}),\n" + def getC(self): """Returns the array with the actors the room uses""" diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 2d66c2d49..fb206c612 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -9,13 +9,12 @@ from ...oot_model_classes import OOTModel, OOTGfxFormatter from ..classes import RoomFile from ..base import Base, altHeaderList -from ..commands import RoomCommands from .header import RoomAlternateHeader, RoomHeader from .shape import RoomShape @dataclass -class Room(Base, RoomCommands): +class Room(Base): """This class defines a room""" name: str @@ -71,6 +70,27 @@ def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): return RoomShape(self.roomShapeType, headerProp, self.mesh, sceneName, self.name) + def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): + cmdListData = CData() + listName = f"SceneCmd {curHeader.name}" + + # .h + cmdListData.header = f"extern {listName}[];\n" + + # .c + cmdListData.source = ( + (f"{listName}[]" + " = {\n") + + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + self.roomShape.getCmd() + + curHeader.infos.getCmds() + + (curHeader.objects.getCmd() if len(curHeader.objects.objectList) > 0 else "") + + (curHeader.actors.getCmd() if len(curHeader.actors.actorList) > 0 else "") + + self.getEndCmd() + + "};\n\n" + ) + + return cmdListData + def getRoomMainC(self): """Returns the C data of the main informations of a room""" @@ -109,7 +129,7 @@ def getRoomMainC(self): if curHeader is not None: roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" roomC.source += curHeader.getHeaderDefines() - roomC.append(self.getRoomCommandList(self, i)) + roomC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) if i == 0 and self.hasAlternateHeaders and altHeaderPtrList is not None: roomC.source += altHeaderPtrList diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 29375e269..f61723ea1 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -302,6 +302,9 @@ def getName(self): else: raise PluginError("ERROR: Name not found!") + def getCmd(self): + return indent + f"SCENE_CMD_ROOM_SHAPE(&{self.getName()}),\n" + def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): """Returns the image data for image room shapes""" diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 9ba5105e3..4b1a26411 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -3,7 +3,7 @@ SceneHeader, SceneLighting, SceneCutscene, - SceneActors, + SceneTransitionActors, ScenePathways, SceneAlternateHeader, ) diff --git a/fast64_internal/oot/exporter/scene/classes.py b/fast64_internal/oot/exporter/scene/classes.py index 9c97090d0..9ca83cabb 100644 --- a/fast64_internal/oot/exporter/scene/classes.py +++ b/fast64_internal/oot/exporter/scene/classes.py @@ -13,7 +13,7 @@ class TransitionActor(Actor): cameraFront: str = None cameraBack: str = None - def getTransitionActorEntry(self): + def getEntryC(self): """Returns a single transition actor entry""" sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] @@ -38,7 +38,7 @@ class EntranceActor(Actor): roomIndex: int = None spawnIndex: int = None - def getSpawnEntry(self): + def getEntryC(self): """Returns a single spawn entry""" return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" @@ -74,7 +74,7 @@ def getDirectionValues(self, vector: tuple[int, int, int]): return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - def getLightSettingsEntry(self, index: int): + def getEntryC(self, index: int): """Returns an environment light entry""" isLightingCustom = self.envLightMode == "Custom" @@ -126,7 +126,7 @@ class Path: name: str points: list[tuple[int, int, int]] = field(default_factory=list) - def getPathPointListC(self): + def getC(self): """Returns the pathway position array""" pathData = CData() diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index bb9fa88ff..5ea3da44b 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -60,6 +60,20 @@ def __post_init__(self): self.worldMapLocation = self.getPropValue(self.props, "mapLocation") self.sceneCamType = self.getPropValue(self.props, "cameraMode") + def getCmds(self, lights: "SceneLighting"): + return ( + indent + + f",\n{indent}".join( + [ + f"SCENE_CMD_SOUND_SETTINGS({self.specID}, {self.ambienceID}, {self.sequenceID})", + f"SCENE_CMD_MISC_SETTINGS({self.sceneCamType}, {self.worldMapLocation})", + f"SCENE_CMD_SPECIAL_FILES({self.naviHintType}, {self.keepObjectID})", + f"SCENE_CMD_SKYBOX_SETTINGS({self.skyboxID}, {self.skyboxConfig}, {lights.envLightMode})", + ] + ) + + ",\n" + ) + @dataclass class SceneLighting(HeaderBase): @@ -98,7 +112,12 @@ def __post_init__(self): ) ) - def getEnvLightSettingsC(self): + def getCmd(self): + return ( + indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" + ) + f"{len(self.settings)}, {self.name if len(self.settings) > 0 else 'NULL'}),\n" + + def getC(self): """Returns a ``CData`` containing the C data of env. light settings""" lightSettingsC = CData() @@ -109,9 +128,7 @@ def getEnvLightSettingsC(self): # .c lightSettingsC.source = ( - (lightName + " = {\n") - + "".join(light.getLightSettingsEntry(i) for i, light in enumerate(self.settings)) - + "};\n\n" + (lightName + " = {\n") + "".join(light.getEntryC(i) for i, light in enumerate(self.settings)) + "};\n\n" ) return lightSettingsC @@ -153,7 +170,11 @@ def __post_init__(self): else: raise PluginError("ERROR: No object selected for cutscene reference") - def getCutsceneC(self): + def getCmd(self): + csDataName = self.csObj.name if self.writeType == "Object" else self.csWriteCustom + return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" + + def getC(self): # will be implemented when PR #208 is merged cutsceneData = CData() return cutsceneData @@ -172,7 +193,10 @@ def __post_init__(self): raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") self.exitList.append((i, exitProp.exitIndexCustom)) - def getExitListC(self): + def getCmd(self): + return indent + f"SCENE_CMD_EXIT_LIST({self.name}),\n" + + def getC(self): """Returns a ``CData`` containing the C data of the exit array""" exitListC = CData() @@ -193,22 +217,15 @@ def getExitListC(self): @dataclass -class SceneActors(HeaderBase): - """This class handles scene actors (transition actors and entrance actors)""" - +class SceneTransitionActors(HeaderBase): + name: str sceneObj: Object transform: Matrix headerIndex: int - entranceListName: str - startPositionsName: str - transActorListName: str - - transitionActorList: list[TransitionActor] = field(default_factory=list) - entranceActorList: list[EntranceActor] = field(default_factory=list) - def initTransActorList(self): - """Returns the transition actor list based on empty objects with the type 'Transition Actor'""" + entries: list[TransitionActor] = field(default_factory=list) + def __post_init__(self): actorObjList: list[Object] = [ obj for obj in self.sceneObj.children_recursive @@ -254,9 +271,38 @@ def initTransActorList(self): transActor.params = transActorProp.actor.actorParam transActor.roomFrom, transActor.cameraFront = front transActor.roomTo, transActor.cameraBack = back - self.transitionActorList.append(transActor) + self.entries.append(transActor) + + def getCmd(self): + return indent + f"SCENE_CMD_TRANSITION_ACTOR_LIST({len(self.entries)}, {self.name}),\n" + + def getC(self): + """Returns the transition actor array""" + + transActorList = CData() + listName = f"TransitionActorEntry {self.name}" + + # .h + transActorList.header = f"extern {listName}[];\n" + + # .c + transActorList.source = ( + (f"{listName}[]" + " = {\n") + "\n".join(transActor.getEntryC() for transActor in self.entries) + "};\n\n" + ) + + return transActorList + + +@dataclass +class SceneEntranceActors(HeaderBase): + name: str + sceneObj: Object + transform: Matrix + headerIndex: int + + entries: list[EntranceActor] = field(default_factory=list) - def initEntranceActorList(self): + def __post_init__(self): """Returns the entrance actor list based on empty objects with the type 'Entrance'""" entranceActorFromIndex: dict[int, EntranceActor] = {} @@ -300,35 +346,44 @@ def initEntranceActorList(self): if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): raise PluginError("ERROR: The spawn indices are not consecutive!") - self.entranceActorList = list(entranceActorFromIndex.values()) + self.entries = list(entranceActorFromIndex.values()) - def __post_init__(self): - self.initTransActorList() - self.initEntranceActorList() + def getCmd(self): + name = self.name if len(self.entries) > 0 else "NULL" + return indent + f"SCENE_CMD_SPAWN_LIST({len(self.entries)}, {name}),\n" - def getSpawnActorListC(self): + def getC(self): """Returns the spawn actor array""" spawnActorList = CData() - listName = f"ActorEntry {self.startPositionsName}" + listName = f"ActorEntry {self.name}" # .h spawnActorList.header = f"extern {listName}[];\n" # .c spawnActorList.source = ( - (f"{listName}[]" + " = {\n") - + "".join(entrance.getActorEntry() for entrance in self.entranceActorList) - + "};\n\n" + (f"{listName}[]" + " = {\n") + "".join(entrance.getActorEntry() for entrance in self.entries) + "};\n\n" ) return spawnActorList - def getSpawnListC(self): + +@dataclass +class SceneSpawns(HeaderBase): + """This class handles scene actors (transition actors and entrance actors)""" + + name: str + entries: list[EntranceActor] + + def getCmd(self): + return indent + f"SCENE_CMD_ENTRANCE_LIST({self.name if len(self.entries) > 0 else 'NULL'}),\n" + + def getC(self): """Returns the spawn array""" spawnList = CData() - listName = f"Spawn {self.entranceListName}" + listName = f"Spawn {self.name}" # .h spawnList.header = f"extern {listName}[];\n" @@ -337,30 +392,12 @@ def getSpawnListC(self): spawnList.source = ( (f"{listName}[]" + " = {\n") + (indent + "// { Spawn Actor List Index, Room Index }\n") - + "".join(entrance.getSpawnEntry() for entrance in self.entranceActorList) + + "".join(entrance.getEntryC() for entrance in self.entries) + "};\n\n" ) return spawnList - def getTransActorListC(self): - """Returns the transition actor array""" - - transActorList = CData() - listName = f"TransitionActorEntry {self.transActorListName}" - - # .h - transActorList.header = f"extern {listName}[];\n" - - # .c - transActorList.source = ( - (f"{listName}[]" + " = {\n") - + "\n".join(transActor.getTransitionActorEntry() for transActor in self.transitionActorList) - + "};\n\n" - ) - - return transActorList - @dataclass class ScenePathways(HeaderBase): @@ -390,7 +427,10 @@ def __post_init__(self): ) ) - def getPathC(self): + def getCmd(self): + return indent + f"SCENE_CMD_PATH_LIST({self.name}),\n" if len(self.pathList) > 0 else "" + + def getC(self): """Returns a ``CData`` containing the C data of the pathway array""" pathData = CData() @@ -405,7 +445,7 @@ def getPathC(self): for path in self.pathList: pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" - pathData.append(path.getPathPointListC()) + pathData.append(path.getC()) pathListData.source += "};\n\n" pathData.append(pathListData) @@ -426,51 +466,56 @@ class SceneHeader(HeaderBase): lighting: SceneLighting = None cutscene: SceneCutscene = None exits: SceneExits = None - actors: SceneActors = None + transitionActors: SceneTransitionActors = None + entranceActors: SceneEntranceActors = None + spawns: SceneSpawns = None path: ScenePathways = None def __post_init__(self): self.infos = SceneInfos(self.props, self.sceneObj) self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") + if self.props.writeCutscene: self.cutscene = SceneCutscene(self.props, self.headerIndex) + self.exits = SceneExits(self.props, f"{self.name}_exitList") - self.actors = SceneActors( - self.props, - self.sceneObj, - self.transform, - self.headerIndex, - f"{self.name}_entranceList", - f"{self.name}_playerEntryList", - f"{self.name}_transitionActors", + + self.transitionActors = SceneTransitionActors( + None, f"{self.name}_transitionActors", self.sceneObj, self.transform, self.headerIndex + ) + + self.entranceActors = SceneEntranceActors( + None, f"{self.name}_playerEntryList", self.sceneObj, self.transform, self.headerIndex ) + + self.spawns = SceneSpawns(None, f"{self.name}_entranceList", self.entranceActors.entries) self.path = ScenePathways(self.props, f"{self.name}_pathway", self.sceneObj, self.transform, self.headerIndex) - def getHeaderC(self): + def getC(self): """Returns the ``CData`` containing the header's data""" headerData = CData() # Write the spawn position list data and the entrance list - if len(self.actors.entranceActorList) > 0: - headerData.append(self.actors.getSpawnActorListC()) - headerData.append(self.actors.getSpawnListC()) + if len(self.entranceActors.entries) > 0: + headerData.append(self.entranceActors.getC()) + headerData.append(self.spawns.getC()) # Write the transition actor list data - if len(self.actors.transitionActorList) > 0: - headerData.append(self.actors.getTransActorListC()) + if len(self.transitionActors.entries) > 0: + headerData.append(self.transitionActors.getC()) # Write the exit list if len(self.exits.exitList) > 0: - headerData.append(self.exits.getExitListC()) + headerData.append(self.exits.getC()) # Write the light data if len(self.lighting.settings) > 0: - headerData.append(self.lighting.getEnvLightSettingsC()) + headerData.append(self.lighting.getC()) # Write the path data, if used if len(self.path.pathList) > 0: - headerData.append(self.path.getPathC()) + headerData.append(self.path.getC()) return headerData diff --git a/fast64_internal/oot/exporter/scene/main.py b/fast64_internal/oot/exporter/scene/main.py index 06b707c0f..aed020134 100644 --- a/fast64_internal/oot/exporter/scene/main.py +++ b/fast64_internal/oot/exporter/scene/main.py @@ -8,7 +8,6 @@ from ..collision import CollisionHeader from .header import SceneHeader from ...oot_model_classes import OOTModel -from ..commands import SceneCommands from ..base import altHeaderList from ..collision import CollisionBase from .header import SceneAlternateHeader, SceneHeader @@ -18,7 +17,7 @@ @dataclass -class Scene(CollisionBase, SceneCommands): +class Scene(CollisionBase): """This class defines a scene""" name: str = None @@ -29,9 +28,11 @@ class Scene(CollisionBase, SceneCommands): roomList: list["Room"] = field(default_factory=list) roomListName: str = None colHeader: CollisionHeader = None + hasAlternateHeaders: bool = False def __post_init__(self): self.roomListName = f"{self.name}_roomList" + self.hasAlternateHeaders = self.altHeader is not None def validateRoomIndices(self): """Checks if there are multiple rooms with the same room index""" @@ -50,11 +51,6 @@ def validateScene(self): if not self.validateRoomIndices(): raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") - def hasAlternateHeaders(self): - """Checks if this scene is using alternate headers""" - - return self.altHeader is not None - def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: """Returns the scene header based on the header index""" @@ -95,6 +91,36 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int headerProp, f"{self.name}_header{self.headerIndex:02}", self.sceneObj, self.transform, headerIndex ) + def getRoomListCmd(self): + return indent + f"SCENE_CMD_ROOM_LIST({len(self.roomList)}, {self.roomListName}),\n" + + def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): + cmdListData = CData() + listName = f"SceneCmd {curHeader.name}" + + # .h + cmdListData.header = f"extern {listName}[]" + ";\n" + + # .c + cmdListData.source = ( + (f"{listName}[]" + " = {\n") + + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + self.colHeader.getCmd() + + self.getRoomListCmd() + + curHeader.infos.getCmds(curHeader.lighting) + + curHeader.lighting.getCmd() + + curHeader.path.getCmd() + + (curHeader.transitionActors.getCmd() if len(curHeader.transitionActors.entries) > 0 else "") + + curHeader.spawns.getCmd() + + curHeader.entranceActors.getCmd() + + (curHeader.exits.getCmd() if len(curHeader.exits.exitList) > 0 else "") + # + (curHeader.cutscene.getCmd() if curHeader.cutscene.writeCutscene else "") + + self.getEndCmd() + + "};\n\n" + ) + + return cmdListData + def getRoomListC(self): """Returns the ``CData`` containing the room list array""" @@ -141,7 +167,7 @@ def getSceneMainC(self): headers: list[tuple[SceneHeader, str]] = [] altHeaderPtrs = None - if self.hasAlternateHeaders(): + if self.hasAlternateHeaders: headers = [ (self.altHeader.childNight, "Child Night"), (self.altHeader.adultDay, "Adult Day"), @@ -160,10 +186,10 @@ def getSceneMainC(self): for i, (curHeader, headerDesc) in enumerate(headers): if curHeader is not None: sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - sceneC.append(self.getSceneCommandList(self, curHeader, i)) + sceneC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) if i == 0: - if self.hasAlternateHeaders() and altHeaderPtrs is not None: + if self.hasAlternateHeaders and altHeaderPtrs is not None: altHeaderListName = f"SceneCmd* {self.altHeader.name}[]" sceneC.header += f"extern {altHeaderListName};\n" sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" @@ -171,7 +197,7 @@ def getSceneMainC(self): # Write the room segment list sceneC.append(self.getRoomListC()) - sceneC.append(curHeader.getHeaderC()) + sceneC.append(curHeader.getC()) return sceneC From ccbd2257fa5d5233539c30875fe347c2b3171f41 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:51:05 +0200 Subject: [PATCH 49/98] improvements part 3 --- .../oot/exporter/collision/__init__.py | 291 +++++++++- .../oot/exporter/collision/base.py | 181 ------ .../oot/exporter/collision/camera.py | 220 ++++++++ .../oot/exporter/collision/classes.py | 334 ----------- .../oot/exporter/collision/header.py | 385 ------------- .../oot/exporter/collision/polygons.py | 83 +++ .../oot/exporter/collision/surface.py | 152 +++++ .../oot/exporter/collision/vertex.py | 36 ++ .../oot/exporter/collision/waterbox.py | 124 ++++ fast64_internal/oot/exporter/room/main.py | 1 - fast64_internal/oot/exporter/scene/classes.py | 148 ----- fast64_internal/oot/exporter/scene/header.py | 531 ------------------ .../oot/exporter/scene/header/__init__.py | 89 +++ .../oot/exporter/scene/header/actors.py | 234 ++++++++ .../oot/exporter/scene/header/cutscene.py | 52 ++ .../oot/exporter/scene/header/general.py | 240 ++++++++ .../oot/exporter/scene/header/pathways.py | 90 +++ fast64_internal/oot/exporter/scene/main.py | 21 +- 18 files changed, 1610 insertions(+), 1602 deletions(-) delete mode 100644 fast64_internal/oot/exporter/collision/base.py create mode 100644 fast64_internal/oot/exporter/collision/camera.py delete mode 100644 fast64_internal/oot/exporter/collision/classes.py delete mode 100644 fast64_internal/oot/exporter/collision/header.py create mode 100644 fast64_internal/oot/exporter/collision/polygons.py create mode 100644 fast64_internal/oot/exporter/collision/surface.py create mode 100644 fast64_internal/oot/exporter/collision/vertex.py create mode 100644 fast64_internal/oot/exporter/collision/waterbox.py delete mode 100644 fast64_internal/oot/exporter/scene/classes.py delete mode 100644 fast64_internal/oot/exporter/scene/header.py create mode 100644 fast64_internal/oot/exporter/scene/header/__init__.py create mode 100644 fast64_internal/oot/exporter/scene/header/actors.py create mode 100644 fast64_internal/oot/exporter/scene/header/cutscene.py create mode 100644 fast64_internal/oot/exporter/scene/header/general.py create mode 100644 fast64_internal/oot/exporter/scene/header/pathways.py diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 2439e8745..cca2f2d7d 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -1,10 +1,281 @@ -from .base import CollisionBase - -from .header import ( - CollisionHeader, - Vertices, - CollisionPolygons, - SurfaceTypes, - BgCamInformations, - WaterBoxes, -) +import math + +from dataclasses import dataclass +from mathutils import Matrix, Vector +from bpy.types import Mesh, Object +from bpy.ops import object +from ....utility import PluginError, CData, indent +from ...oot_utility import convertIntTo2sComplement +from ..base import Base +from .polygons import CollisionPoly, CollisionPolygons +from .surface import SurfaceType, SurfaceTypes +from .camera import BgCamInformations +from .waterbox import WaterBoxes +from .vertex import Vertex, Vertices + + +@dataclass +class CollisionBase(Base): + """This class hosts different functions used to convert mesh data""" + + sceneObj: Object + transform: Matrix + useMacros: bool + + def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): + """This is used to update the scene's boundaries""" + + if len(colBounds) == 0: + colBounds.append([position[0], position[1], position[2]]) + colBounds.append([position[0], position[1], position[2]]) + return + + minBounds = colBounds[0] + maxBounds = colBounds[1] + for i in range(3): + if position[i] < minBounds[i]: + minBounds[i] = position[i] + if position[i] > maxBounds[i]: + maxBounds[i] = position[i] + + def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Vertex]): + """Returns the index of a Vertex based on position data, returns None if no match found""" + + for i in range(len(vertexList)): + if vertexList[i].pos == vertexPos: + return i + return None + + def getMeshObjects(self, parentObj: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): + """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" + + objList: list[Object] = parentObj.children + for obj in objList: + newTransform = curTransform @ obj.matrix_local + + if obj.type == "MESH" and not obj.ignore_collision: + transformFromMeshObj[obj] = newTransform + + if len(obj.children) > 0: + self.getMeshObjects(obj, newTransform, transformFromMeshObj) + + return transformFromMeshObj + + def getCollisionData(self): + """Returns collision data, surface types and vertex positions from mesh objects""" + + object.select_all(action="DESELECT") + self.sceneObj.select_set(True) + + colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} + surfaceList: list[SurfaceType] = [] + polyList: list[CollisionPoly] = [] + vertexList: list[Vertex] = [] + colBounds: list[tuple[int, int, int]] = [] + + transformFromMeshObj: dict[Object, Matrix] = {} + transformFromMeshObj = self.getMeshObjects(self.sceneObj, self.transform, transformFromMeshObj) + for meshObj, transform in transformFromMeshObj.items(): + # Note: ``isinstance``only used to get the proper type hints + if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): + if len(meshObj.data.materials) == 0: + raise PluginError(f"'{meshObj.name}' must have a material associated with it.") + + meshObj.data.calc_loop_triangles() + for face in meshObj.data.loop_triangles: + colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty + + # get bounds and vertices data + planePoint = transform @ meshObj.data.vertices[face.vertices[0]].co + (x1, y1, z1) = self.roundPosition(planePoint) + (x2, y2, z2) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, y3, z3) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) + self.updateBounds((x1, y1, z1), colBounds) + self.updateBounds((x2, y2, z2), colBounds) + self.updateBounds((x3, y3, z3), colBounds) + + normal = (transform.inverted().transposed() @ face.normal).normalized() + distance = round( + -1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2]) + ) + distance = convertIntTo2sComplement(distance, 2, True) + + indices: list[int] = [] + for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: + vertexIndex = self.getVertexIndex(pos, vertexList) + if vertexIndex is None: + vertexList.append(Vertex(pos)) + indices.append(len(vertexList) - 1) + else: + indices.append(vertexIndex) + assert len(indices) == 3 + + # We need to ensure two things about the order in which the vertex indices are: + # + # 1) The vertex with the minimum y coordinate should be first. + # This prevents a bug due to an optimization in OoT's CollisionPoly_GetMinY. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L202 + # + # 2) The vertices should wrap around the polygon normal **counter-clockwise**. + # This is needed for OoT's dynapoly, which is collision that can move. + # When it moves, the vertex coordinates and normals are recomputed. + # The normal is computed based on the vertex coordinates, which makes the order of vertices matter. + # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L2976 + + # Address 1): sort by ascending y coordinate + indices.sort(key=lambda index: vertexList[index].pos[1]) + + # Address 2): + # swap indices[1] and indices[2], + # if the normal computed from the vertices in the current order is the wrong way. + v0 = Vector(vertexList[indices[0]].pos) + v1 = Vector(vertexList[indices[1]].pos) + v2 = Vector(vertexList[indices[2]].pos) + if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: + indices[1], indices[2] = indices[2], indices[1] + + # get surface type and collision poly data + useConveyor = colProp.conveyorOption != "None" + surfaceType = SurfaceType( + colProp.cameraID, + colProp.exitID, + int(self.getPropValue(colProp, "floorProperty"), base=16), + 0, # unused? + int(self.getPropValue(colProp, "wallSetting"), base=16), + int(self.getPropValue(colProp, "floorSetting"), base=16), + colProp.decreaseHeight, + colProp.eponaBlock, + int(self.getPropValue(colProp, "sound"), base=16), + int(self.getPropValue(colProp, "terrain"), base=16), + colProp.lightingSetting, + int(colProp.echo, base=16), + colProp.hookshotable, + int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, + colProp.isWallDamage, + colProp.conveyorKeepMomentum if useConveyor else False, + self.useMacros, + ) + + if not surfaceType in colPolyFromSurfaceType: + colPolyFromSurfaceType[surfaceType] = [] + + colPolyFromSurfaceType[surfaceType].append( + CollisionPoly( + indices, + colProp.ignoreCameraCollision, + colProp.ignoreActorCollision, + colProp.ignoreProjectileCollision, + useConveyor, + normal, + distance, + self.useMacros, + ) + ) + + count = 0 + for surface, colPolyList in colPolyFromSurfaceType.items(): + for colPoly in colPolyList: + colPoly.type = count + polyList.append(colPoly) + surfaceList.append(surface) + count += 1 + + return colBounds, vertexList, polyList, surfaceList + + +@dataclass +class CollisionHeader(CollisionBase): + """This class defines the collision header used by the scene""" + + name: str + sceneName: str + + minBounds: tuple[int, int, int] = None + maxBounds: tuple[int, int, int] = None + vertices: Vertices = None + collisionPoly: CollisionPolygons = None + surfaceType: SurfaceTypes = None + bgCamInfo: BgCamInformations = None + waterbox: WaterBoxes = None + + def __post_init__(self): + # Ideally everything would be separated but this is complicated since it's all tied together + colBounds, vertexList, polyList, surfaceTypeList = self.getCollisionData() + + self.minBounds = colBounds[0] + self.maxBounds = colBounds[1] + self.vertices = Vertices(f"{self.sceneName}_vertices", vertexList) + self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", polyList) + self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", surfaceTypeList) + self.bgCamInfo = BgCamInformations( + self.sceneObj, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" + ) + self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) + + def getCmd(self): + return indent + f"SCENE_CMD_COL_HEADER(&{self.name}),\n" + + def getC(self): + """Returns the collision header for the selected scene""" + + headerData = CData() + colData = CData() + varName = f"CollisionHeader {self.name}" + + wBoxPtrLine = colPolyPtrLine = vtxPtrLine = "0, NULL" + camPtrLine = surfacePtrLine = "NULL" + + # Add waterbox data if necessary + if len(self.waterbox.waterboxList) > 0: + colData.append(self.waterbox.getC()) + wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" + + # Add camera data if necessary + if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: + infoData = self.bgCamInfo.getInfoArrayC() + if "&" in infoData.source: + colData.append(self.bgCamInfo.getDataArrayC()) + colData.append(infoData) + camPtrLine = f"{self.bgCamInfo.name}" + + # Add surface types + if len(self.surfaceType.surfaceTypeList) > 0: + colData.append(self.surfaceType.getC()) + surfacePtrLine = f"{self.surfaceType.name}" + + # Add vertex data + if len(self.vertices.vertexList) > 0: + colData.append(self.vertices.getC()) + vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" + + # Add collision poly data + if len(self.collisionPoly.polyList) > 0: + colData.append(self.collisionPoly.getC()) + colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" + + # build the C data of the collision header + + # .h + headerData.header = f"extern {varName};\n" + + # .c + headerData.source += ( + (varName + " = {\n") + + ",\n".join( + indent + val + for val in [ + ("{ " + ", ".join(f"{val}" for val in self.minBounds) + " }"), + ("{ " + ", ".join(f"{val}" for val in self.maxBounds) + " }"), + vtxPtrLine, + colPolyPtrLine, + surfacePtrLine, + camPtrLine, + wBoxPtrLine, + ] + ) + + "\n};\n\n" + ) + + headerData.append(colData) + return headerData diff --git a/fast64_internal/oot/exporter/collision/base.py b/fast64_internal/oot/exporter/collision/base.py deleted file mode 100644 index 1e81b4a8e..000000000 --- a/fast64_internal/oot/exporter/collision/base.py +++ /dev/null @@ -1,181 +0,0 @@ -import math - -from dataclasses import dataclass -from mathutils import Matrix, Vector -from bpy.types import Mesh, Object -from bpy.ops import object -from ....utility import PluginError -from ...oot_utility import convertIntTo2sComplement -from ..base import Base -from .classes import CollisionPoly, SurfaceType, Vertex - - -@dataclass -class CollisionBase(Base): - """This class hosts different functions used to convert mesh data""" - - sceneObj: Object - transform: Matrix - useMacros: bool - - def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): - """This is used to update the scene's boundaries""" - - if len(colBounds) == 0: - colBounds.append([position[0], position[1], position[2]]) - colBounds.append([position[0], position[1], position[2]]) - return - - minBounds = colBounds[0] - maxBounds = colBounds[1] - for i in range(3): - if position[i] < minBounds[i]: - minBounds[i] = position[i] - if position[i] > maxBounds[i]: - maxBounds[i] = position[i] - - def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Vertex]): - """Returns the index of a Vertex based on position data, returns None if no match found""" - - for i in range(len(vertexList)): - if vertexList[i].pos == vertexPos: - return i - return None - - def getMeshObjects(self, parentObj: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): - """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" - - objList: list[Object] = parentObj.children - for obj in objList: - newTransform = curTransform @ obj.matrix_local - - if obj.type == "MESH" and not obj.ignore_collision: - transformFromMeshObj[obj] = newTransform - - if len(obj.children) > 0: - self.getMeshObjects(obj, newTransform, transformFromMeshObj) - - return transformFromMeshObj - - def getCollisionData(self): - """Returns collision data, surface types and vertex positions from mesh objects""" - # Ideally everything would be separated but this is complicated since it's all tied together - - object.select_all(action="DESELECT") - self.sceneObj.select_set(True) - - colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} - surfaceList: list[SurfaceType] = [] - polyList: list[CollisionPoly] = [] - vertexList: list[Vertex] = [] - colBounds: list[tuple[int, int, int]] = [] - - transformFromMeshObj: dict[Object, Matrix] = {} - transformFromMeshObj = self.getMeshObjects(self.sceneObj, self.transform, transformFromMeshObj) - for meshObj, transform in transformFromMeshObj.items(): - # Note: ``isinstance``only used to get the proper type hints - if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): - if len(meshObj.data.materials) == 0: - raise PluginError(f"'{meshObj.name}' must have a material associated with it.") - - meshObj.data.calc_loop_triangles() - for face in meshObj.data.loop_triangles: - colProp = meshObj.material_slots[face.material_index].material.ootCollisionProperty - - # get bounds and vertices data - planePoint = transform @ meshObj.data.vertices[face.vertices[0]].co - (x1, y1, z1) = self.roundPosition(planePoint) - (x2, y2, z2) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) - (x3, y3, z3) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) - self.updateBounds((x1, y1, z1), colBounds) - self.updateBounds((x2, y2, z2), colBounds) - self.updateBounds((x3, y3, z3), colBounds) - - normal = (transform.inverted().transposed() @ face.normal).normalized() - distance = round( - -1 * (normal[0] * planePoint[0] + normal[1] * planePoint[1] + normal[2] * planePoint[2]) - ) - distance = convertIntTo2sComplement(distance, 2, True) - - indices: list[int] = [] - for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: - vertexIndex = self.getVertexIndex(pos, vertexList) - if vertexIndex is None: - vertexList.append(Vertex(pos)) - indices.append(len(vertexList) - 1) - else: - indices.append(vertexIndex) - assert len(indices) == 3 - - # We need to ensure two things about the order in which the vertex indices are: - # - # 1) The vertex with the minimum y coordinate should be first. - # This prevents a bug due to an optimization in OoT's CollisionPoly_GetMinY. - # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L202 - # - # 2) The vertices should wrap around the polygon normal **counter-clockwise**. - # This is needed for OoT's dynapoly, which is collision that can move. - # When it moves, the vertex coordinates and normals are recomputed. - # The normal is computed based on the vertex coordinates, which makes the order of vertices matter. - # https://github.com/zeldaret/oot/blob/873c55faad48a67f7544be713cc115e2b858a4e8/src/code/z_bgcheck.c#L2976 - - # Address 1): sort by ascending y coordinate - indices.sort(key=lambda index: vertexList[index].pos[1]) - - # Address 2): - # swap indices[1] and indices[2], - # if the normal computed from the vertices in the current order is the wrong way. - v0 = Vector(vertexList[indices[0]].pos) - v1 = Vector(vertexList[indices[1]].pos) - v2 = Vector(vertexList[indices[2]].pos) - if (v1 - v0).cross(v2 - v0).dot(Vector(normal)) < 0: - indices[1], indices[2] = indices[2], indices[1] - - # get surface type and collision poly data - useConveyor = colProp.conveyorOption != "None" - surfaceType = SurfaceType( - colProp.cameraID, - colProp.exitID, - int(self.getPropValue(colProp, "floorProperty"), base=16), - 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorSetting"), base=16), - colProp.decreaseHeight, - colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), - colProp.lightingSetting, - int(colProp.echo, base=16), - colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, - int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, - colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, - self.useMacros, - ) - - if not surfaceType in colPolyFromSurfaceType: - colPolyFromSurfaceType[surfaceType] = [] - - colPolyFromSurfaceType[surfaceType].append( - CollisionPoly( - indices, - colProp.ignoreCameraCollision, - colProp.ignoreActorCollision, - colProp.ignoreProjectileCollision, - useConveyor, - normal, - distance, - self.useMacros, - ) - ) - - count = 0 - for surface, colPolyList in colPolyFromSurfaceType.items(): - for colPoly in colPolyList: - colPoly.type = count - polyList.append(colPoly) - surfaceList.append(surface) - count += 1 - - return colBounds, vertexList, polyList, surfaceList diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py new file mode 100644 index 000000000..1558eacb5 --- /dev/null +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -0,0 +1,220 @@ +import math + +from dataclasses import dataclass, field +from mathutils import Quaternion, Matrix +from bpy.types import Object +from ....utility import PluginError, CData, indent +from ...oot_collision_classes import decomp_compat_map_CameraSType +from ...collision.properties import OOTCameraPositionProperty +from ..base import Base + + +@dataclass +class CrawlspaceCamera: + """This class defines camera data for crawlspaces, if used""" + + points: list[tuple[int, int, int]] + camIndex: int + arrayIndex: int = 0 + + def __post_init__(self): + self.points = [self.points[0], self.points[0], self.points[0], self.points[1], self.points[1], self.points[1]] + + def getDataEntryC(self): + """Returns an entry for the camera data array""" + + return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) + + def getInfoEntryC(self, posDataName: str): + """Returns a crawlspace entry for the camera informations array""" + + return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" + + +@dataclass +class CameraData: + """This class defines camera data, if used""" + + pos: tuple[int, int, int] + rot: tuple[int, int, int] + fov: int + roomImageOverrideBgCamIndex: int + + def getEntryC(self): + """Returns an entry for the camera data array""" + + return ( + (indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " },\n") + + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.rot) + " },\n") + + (indent + "{ " + f"{self.fov:6}, {self.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") + ) + + +@dataclass +class CameraInfo: + """This class defines camera information data""" + + setting: str + count: int + data: CameraData + camIndex: int + arrayIndex: int = 0 + hasPosData: bool = False + + def __post_init__(self): + self.hasPosData = self.data is not None + + def getInfoEntryC(self, posDataName: str): + """Returns an entry for the camera information array""" + ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" + return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" + + +@dataclass +class BgCamInformations(Base): + """This class defines the array of camera informations and the array of the associated data""" + + sceneObj: Object + transform: Matrix + name: str + posDataName: str + + bgCamInfoList: list[CameraInfo] = field(default_factory=list) + crawlspacePosList: list[CrawlspaceCamera] = field(default_factory=list) + arrayIdx: int = 0 + crawlspaceCount: int = 6 + camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(default_factory=dict) + + def initCrawlspaceList(self): + """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" + + crawlspaceObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" + ] + + for obj in crawlspaceObjList: + if self.validateCurveData(obj): + self.crawlspacePosList.append( + CrawlspaceCamera( + [ + [round(value) for value in self.transform @ obj.matrix_world @ point.co] + for point in obj.data.splines[0].points + ], + obj.ootSplineProperty.index, + ) + ) + + def initBgCamInfoList(self): + """Returns a list of camera informations from camera objects""" + + camObjList: list[Object] = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] + camPosData: dict[int, CameraData] = {} + camInfoData: dict[int, CameraInfo] = {} + + for camObj in camObjList: + camProp: OOTCameraPositionProperty = camObj.ootCameraPositionProperty + + if camProp.camSType == "Custom": + setting = camProp.camSTypeCustom + else: + setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) + + if camProp.hasPositionData: + if camProp.index in camPosData: + raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") + + # Camera faces opposite direction + pos, rot, _, _ = self.getConvertedTransformWithOrientation( + self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + ) + + fov = math.degrees(camObj.data.angle) + camPosData[camProp.index] = CameraData( + pos, + rot, + round(fov * 100 if fov > 3.6 else fov), # see CAM_DATA_SCALED() macro + camObj.ootCameraPositionProperty.bgImageOverrideIndex, + ) + + if camProp.index in camInfoData: + raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") + + camInfoData[camProp.index] = CameraInfo( + setting, + 3 if camProp.hasPositionData else 0, # cameras are using 3 entries in the data array + camPosData[camProp.index] if camProp.hasPositionData else None, + camProp.index, + ) + self.bgCamInfoList = list(camInfoData.values()) + + def initCamTable(self): + for bgCam in self.bgCamInfoList: + if not bgCam.camIndex in self.camFromIndex: + self.camFromIndex[bgCam.camIndex] = bgCam + else: + raise PluginError(f"ERROR (CameraInfo): Camera index already used: {bgCam.camIndex}") + + for crawlCam in self.crawlspacePosList: + if not crawlCam.camIndex in self.camFromIndex: + self.camFromIndex[crawlCam.camIndex] = crawlCam + else: + raise PluginError(f"ERROR (Crawlspace): Camera index already used: {crawlCam.camIndex}") + + self.camFromIndex = dict(sorted(self.camFromIndex.items())) + if list(self.camFromIndex.keys()) != list(range(len(self.camFromIndex))): + raise PluginError("ERROR: The camera indices are not consecutive!") + + i = 0 + for val in self.camFromIndex.values(): + if isinstance(val, CrawlspaceCamera): + val.arrayIndex = i + i += 6 # crawlspaces are using 6 entries in the data array + elif val.hasPosData: + val.arrayIndex = i + i += 3 + + def __post_init__(self): + self.initCrawlspaceList() + self.initBgCamInfoList() + self.initCamTable() + + def getDataArrayC(self): + """Returns the camera data/crawlspace positions array""" + + posData = CData() + listName = f"Vec3s {self.posDataName}[]" + + # .h + posData.header = f"extern {listName};\n" + + # .c + posData.source = listName + " = {\n" + for val in self.camFromIndex.values(): + if isinstance(val, CrawlspaceCamera): + posData.source += val.getDataEntryC() + "\n" + elif val.hasPosData: + posData.source += val.data.getEntryC() + "\n" + posData.source = posData.source[:-1] # remove extra newline + posData.source += "};\n\n" + + return posData + + def getInfoArrayC(self): + """Returns the array containing the informations of each cameras""" + + bgCamInfoData = CData() + listName = f"BgCamInfo {self.name}[]" + + # .h + bgCamInfoData.header = f"extern {listName};\n" + + # .c + bgCamInfoData.source = ( + (listName + " = {\n") + + "".join(val.getInfoEntryC(self.posDataName) for val in self.camFromIndex.values()) + + "};\n\n" + ) + + return bgCamInfoData diff --git a/fast64_internal/oot/exporter/collision/classes.py b/fast64_internal/oot/exporter/collision/classes.py deleted file mode 100644 index 096fdbbad..000000000 --- a/fast64_internal/oot/exporter/collision/classes.py +++ /dev/null @@ -1,334 +0,0 @@ -from dataclasses import dataclass -from mathutils import Vector -from ....utility import PluginError, indent - - -@dataclass -class CollisionPoly: - """This class defines a single collision poly""" - - indices: list[int] - ignoreCamera: bool - ignoreEntity: bool - ignoreProjectile: bool - enableConveyor: bool - normal: Vector - dist: int - useMacros: bool - type: int = None - - def getFlags_vIA(self): - """Returns the value of ``flags_vIA``""" - - vtxId = self.indices[0] & 0x1FFF - if self.ignoreProjectile or self.ignoreEntity or self.ignoreCamera: - flag1 = ("COLPOLY_IGNORE_PROJECTILES" if self.useMacros else "(1 << 2)") if self.ignoreProjectile else "" - flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreEntity else "" - flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreCamera else "" - flags = "(" + " | ".join(flag for flag in [flag1, flag2, flag3] if len(flag) > 0) + ")" - else: - flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" - - return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" - - def getFlags_vIB(self): - """Returns the value of ``flags_vIB``""" - - vtxId = self.indices[1] & 0x1FFF - if self.enableConveyor: - flags = "COLPOLY_IS_FLOOR_CONVEYOR" if self.useMacros else "(1 << 0)" - else: - flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" - return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" - - def getEntryC(self): - """Returns an entry for the collision poly array""" - - vtxId = self.indices[2] & 0x1FFF - if self.type is None: - raise PluginError("ERROR: Surface Type missing!") - return ( - (indent + "{ ") - + ", ".join( - ( - f"{self.type}", - self.getFlags_vIA(), - self.getFlags_vIB(), - f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF", - ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), - f"{self.dist}", - ) - ) - + " }," - ) - - -@dataclass -class SurfaceType: - """This class defines a single surface type""" - - bgCamIndex: int - exitIndex: int - floorType: int - unk18: int # unused? - wallType: int - floorProperty: int - isSoft: bool - isHorseBlocked: bool - - material: int - floorEffect: int - lightSetting: int - echo: int - canHookshot: bool - conveyorSpeed: int - conveyorDirection: int - isWallDamage: bool # unk27 - - conveyorKeepMomentum: bool - useMacros: bool - isSoftC: str = None - isHorseBlockedC: str = None - canHookshotC: str = None - isWallDamageC: str = None - - def __hash__(self): - return hash( - ( - self.bgCamIndex, - self.exitIndex, - self.floorType, - self.unk18, - self.wallType, - self.floorProperty, - self.isSoft, - self.isHorseBlocked, - self.material, - self.floorEffect, - self.lightSetting, - self.echo, - self.canHookshot, - self.conveyorSpeed, - self.conveyorDirection, - self.isWallDamage, - self.conveyorKeepMomentum, - ) - ) - - def __post_init__(self): - if self.conveyorKeepMomentum: - self.conveyorSpeed += 4 - - self.isSoftC = "1" if self.isSoft else "0" - self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" - self.canHookshotC = "1" if self.canHookshot else "0" - self.isWallDamageC = "1" if self.isWallDamage else "0" - - def getSurfaceType0(self): - """Returns surface type properties for the first element of the data array""" - - if self.useMacros: - return ( - ("SURFACETYPE0(") - + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " - + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" - + ")" - ) - else: - return ( - (indent * 2 + "(") - + " | ".join( - prop - for prop in [ - f"(({self.isHorseBlockedC} & 1) << 31)", - f"(({self.isSoftC} & 1) << 30)", - f"(({self.floorProperty} & 0x0F) << 26)", - f"(({self.wallType} & 0x1F) << 21)", - f"(({self.unk18} & 0x07) << 18)", - f"(({self.floorType} & 0x1F) << 13)", - f"(({self.exitIndex} & 0x1F) << 8)", - f"({self.bgCamIndex} & 0xFF)", - ] - ) - + ")" - ) - - def getSurfaceType1(self): - """Returns surface type properties for the second element of the data array""" - - if self.useMacros: - return ( - ("SURFACETYPE1(") - + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " - + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" - + ")" - ) - else: - return ( - (indent * 2 + "(") - + " | ".join( - prop - for prop in [ - f"(({self.isWallDamageC} & 1) << 27)", - f"(({self.conveyorDirection} & 0x3F) << 21)", - f"(({self.conveyorSpeed} & 0x07) << 18)", - f"(({self.canHookshotC} & 1) << 17)", - f"(({self.echo} & 0x3F) << 11)", - f"(({self.lightSetting} & 0x1F) << 6)", - f"(({self.floorEffect} & 0x03) << 4)", - f"({self.material} & 0x0F)", - ] - ) - + ")" - ) - - def getEntryC(self): - """Returns an entry for the surface type array""" - - if self.useMacros: - return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," - else: - return (indent + "{\n") + self.getSurfaceType0() + ",\n" + self.getSurfaceType1() + ("\n" + indent + "},") - - -@dataclass -class CrawlspaceCamera: - """This class defines camera data for crawlspaces, if used""" - - points: list[tuple[int, int, int]] - camIndex: int - arrayIndex: int = 0 - - def __post_init__(self): - self.points = [self.points[0], self.points[0], self.points[0], self.points[1], self.points[1], self.points[1]] - - def getDataEntryC(self): - """Returns an entry for the camera data array""" - - return "".join(indent + "{ " + f"{point[0]:6}, {point[1]:6}, {point[2]:6}" + " },\n" for point in self.points) - - def getInfoEntryC(self, posDataName: str): - """Returns a crawlspace entry for the camera informations array""" - - return indent + "{ " + f"CAM_SET_CRAWLSPACE, 6, &{posDataName}[{self.arrayIndex}]" + " },\n" - - -@dataclass -class CameraData: - """This class defines camera data, if used""" - - pos: tuple[int, int, int] - rot: tuple[int, int, int] - fov: int - roomImageOverrideBgCamIndex: int - - def getEntryC(self): - """Returns an entry for the camera data array""" - - return ( - (indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " },\n") - + (indent + "{ " + ", ".join(f"0x{r:04X}" for r in self.rot) + " },\n") - + (indent + "{ " + f"{self.fov:6}, {self.roomImageOverrideBgCamIndex:6}, {-1:6}" + " },\n") - ) - - -@dataclass -class CameraInfo: - """This class defines camera information data""" - - setting: str - count: int - data: CameraData - camIndex: int - arrayIndex: int = 0 - hasPosData: bool = False - - def __post_init__(self): - self.hasPosData = self.data is not None - - def getInfoEntryC(self, posDataName: str): - """Returns an entry for the camera information array""" - ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" - return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" - - -@dataclass -class WaterBox: - """This class defines waterbox data""" - - position: tuple[int, int, int] - scale: float - emptyDisplaySize: float - - # Properties - bgCamIndex: int - lightIndex: int - roomIndex: int - setFlag19: bool - - useMacros: bool - xMin: int = None - ySurface: int = None - zMin: int = None - xLength: int = None - zLength: int = None - - setFlag19C: str = None - roomIndexC: str = None - - def __post_init__(self): - self.setFlag19C = "1" if self.setFlag19 else "0" - self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" - - # The scale ordering is due to the fact that scaling happens AFTER rotation. - # Thus the translation uses Y-up, while the scale uses Z-up. - xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) - zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) - - self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) - self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) - self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) - self.xLength = xMax - self.xMin - self.zLength = zMax - self.zMin - - def getProperties(self): - """Returns the waterbox properties""" - - if self.useMacros: - return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" - else: - return ( - "(" - + " | ".join( - prop - for prop in [ - f"(({self.setFlag19C} & 1) << 19)", - f"(({self.roomIndexC} & 0x3F) << 13)", - f"(({self.lightIndex} & 0x1F) << 8)", - f"(({self.bgCamIndex}) & 0xFF)", - ] - ) - + ")" - ) - - def getEntryC(self): - """Returns a waterbox entry""" - - return ( - (indent + "{ ") - + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " - + self.getProperties() - + " }," - ) - - -@dataclass -class Vertex: - """This class defines a vertex data""" - - pos: tuple[int, int, int] - - def getEntryC(self): - """Returns a vertex entry""" - - return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," diff --git a/fast64_internal/oot/exporter/collision/header.py b/fast64_internal/oot/exporter/collision/header.py deleted file mode 100644 index ce247b0f8..000000000 --- a/fast64_internal/oot/exporter/collision/header.py +++ /dev/null @@ -1,385 +0,0 @@ -import math - -from mathutils import Quaternion, Matrix -from bpy.types import Object -from dataclasses import dataclass, field -from ....utility import PluginError, CData, checkIdentityRotation, indent -from ...oot_collision_classes import decomp_compat_map_CameraSType -from ...collision.properties import OOTCameraPositionProperty -from ..base import Base - -from .classes import ( - CollisionPoly, - SurfaceType, - CameraData, - CrawlspaceCamera, - CameraInfo, - WaterBox, - Vertex, -) - - -@dataclass -class HeaderBase(Base): - sceneObj: Object - transform: Matrix - - -@dataclass -class Vertices: - """This class defines the array of vertices""" - - name: str - vertexList: list[Vertex] - - def getC(self): - vertData = CData() - listName = f"Vec3s {self.name}[{len(self.vertexList)}]" - - # .h - vertData.header = f"extern {listName};\n" - - # .c - vertData.source = ( - (listName + " = {\n") + "\n".join(vertex.getEntryC() for vertex in self.vertexList) + "\n};\n\n" - ) - - return vertData - - -@dataclass -class CollisionPolygons: - """This class defines the array of collision polys""" - - name: str - polyList: list[CollisionPoly] - - def getC(self): - colPolyData = CData() - listName = f"CollisionPoly {self.name}[{len(self.polyList)}]" - - # .h - colPolyData.header = f"extern {listName};\n" - - # .c - colPolyData.source = (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.polyList) + "\n};\n\n" - - return colPolyData - - -@dataclass -class SurfaceTypes: - """This class defines the array of surface types""" - - name: str - surfaceTypeList: list[SurfaceType] - - def getC(self): - surfaceData = CData() - listName = f"SurfaceType {self.name}[{len(self.surfaceTypeList)}]" - - # .h - surfaceData.header = f"extern {listName};\n" - - # .c - surfaceData.source = ( - (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.surfaceTypeList) + "\n};\n\n" - ) - - return surfaceData - - -@dataclass -class BgCamInformations(HeaderBase): - """This class defines the array of camera informations and the array of the associated data""" - - name: str - posDataName: str - - bgCamInfoList: list[CameraInfo] = field(default_factory=list) - crawlspacePosList: list[CrawlspaceCamera] = field(default_factory=list) - arrayIdx: int = 0 - crawlspaceCount: int = 6 - camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(default_factory=dict) - - def initCrawlspaceList(self): - """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" - - crawlspaceObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" - ] - - for obj in crawlspaceObjList: - if self.validateCurveData(obj): - self.crawlspacePosList.append( - CrawlspaceCamera( - [ - [round(value) for value in self.transform @ obj.matrix_world @ point.co] - for point in obj.data.splines[0].points - ], - obj.ootSplineProperty.index, - ) - ) - - def initBgCamInfoList(self): - """Returns a list of camera informations from camera objects""" - - camObjList: list[Object] = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] - camPosData: dict[int, CameraData] = {} - camInfoData: dict[int, CameraInfo] = {} - - for camObj in camObjList: - camProp: OOTCameraPositionProperty = camObj.ootCameraPositionProperty - - if camProp.camSType == "Custom": - setting = camProp.camSTypeCustom - else: - setting = decomp_compat_map_CameraSType.get(camProp.camSType, camProp.camSType) - - if camProp.hasPositionData: - if camProp.index in camPosData: - raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") - - # Camera faces opposite direction - pos, rot, _, _ = self.getConvertedTransformWithOrientation( - self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) - ) - - fov = math.degrees(camObj.data.angle) - camPosData[camProp.index] = CameraData( - pos, - rot, - round(fov * 100 if fov > 3.6 else fov), # see CAM_DATA_SCALED() macro - camObj.ootCameraPositionProperty.bgImageOverrideIndex, - ) - - if camProp.index in camInfoData: - raise PluginError(f"ERROR: Repeated camera entry: {camProp.index} for {camObj.name}") - - camInfoData[camProp.index] = CameraInfo( - setting, - 3 if camProp.hasPositionData else 0, # cameras are using 3 entries in the data array - camPosData[camProp.index] if camProp.hasPositionData else None, - camProp.index, - ) - self.bgCamInfoList = list(camInfoData.values()) - - def initCamTable(self): - for bgCam in self.bgCamInfoList: - if not bgCam.camIndex in self.camFromIndex: - self.camFromIndex[bgCam.camIndex] = bgCam - else: - raise PluginError(f"ERROR (CameraInfo): Camera index already used: {bgCam.camIndex}") - - for crawlCam in self.crawlspacePosList: - if not crawlCam.camIndex in self.camFromIndex: - self.camFromIndex[crawlCam.camIndex] = crawlCam - else: - raise PluginError(f"ERROR (Crawlspace): Camera index already used: {crawlCam.camIndex}") - - self.camFromIndex = dict(sorted(self.camFromIndex.items())) - if list(self.camFromIndex.keys()) != list(range(len(self.camFromIndex))): - raise PluginError("ERROR: The camera indices are not consecutive!") - - i = 0 - for val in self.camFromIndex.values(): - if isinstance(val, CrawlspaceCamera): - val.arrayIndex = i - i += 6 # crawlspaces are using 6 entries in the data array - elif val.hasPosData: - val.arrayIndex = i - i += 3 - - def __post_init__(self): - self.initCrawlspaceList() - self.initBgCamInfoList() - self.initCamTable() - - def getDataArrayC(self): - """Returns the camera data/crawlspace positions array""" - - posData = CData() - listName = f"Vec3s {self.posDataName}[]" - - # .h - posData.header = f"extern {listName};\n" - - # .c - posData.source = listName + " = {\n" - for val in self.camFromIndex.values(): - if isinstance(val, CrawlspaceCamera): - posData.source += val.getDataEntryC() + "\n" - elif val.hasPosData: - posData.source += val.data.getEntryC() + "\n" - posData.source = posData.source[:-1] # remove extra newline - posData.source += "};\n\n" - - return posData - - def getInfoArrayC(self): - """Returns the array containing the informations of each cameras""" - - bgCamInfoData = CData() - listName = f"BgCamInfo {self.name}[]" - - # .h - bgCamInfoData.header = f"extern {listName};\n" - - # .c - bgCamInfoData.source = ( - (listName + " = {\n") - + "".join(val.getInfoEntryC(self.posDataName) for val in self.camFromIndex.values()) - + "};\n\n" - ) - - return bgCamInfoData - - -@dataclass -class WaterBoxes(HeaderBase): - """This class defines the array of waterboxes""" - - name: str - useMacros: bool - - waterboxList: list[WaterBox] = field(default_factory=list) - - def __post_init__(self): - waterboxObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" - ] - - for waterboxObj in waterboxObjList: - emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) - checkIdentityRotation(waterboxObj, orientedRot, False) - - wboxProp = waterboxObj.ootWaterBoxProperty - roomObj = self.getRoomObjectFromChild(waterboxObj) - self.waterboxList.append( - WaterBox( - pos, - scale, - emptyScale, - wboxProp.camera, - wboxProp.lighting, - roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, - wboxProp.flag19, - self.useMacros, - ) - ) - - def getC(self): - wboxData = CData() - listName = f"WaterBox {self.name}[{len(self.waterboxList)}]" - - # .h - wboxData.header = f"extern {listName};\n" - - # .c - wboxData.source = (listName + " = {\n") + "\n".join(wBox.getEntryC() for wBox in self.waterboxList) + "\n};\n\n" - - return wboxData - - -@dataclass -class CollisionHeader(HeaderBase): - """This class defines the collision header used by the scene""" - - name: str - sceneName: str - useMacros: bool - - # Ideally functions inside base.py would be there but the file would be really long - colBounds: list[tuple[int, int, int]] - vertexList: list[Vertex] - polyList: list[CollisionPoly] - surfaceTypeList: list[SurfaceType] - - minBounds: tuple[int, int, int] = None - maxBounds: tuple[int, int, int] = None - vertices: Vertices = None - collisionPoly: CollisionPolygons = None - surfaceType: SurfaceTypes = None - bgCamInfo: BgCamInformations = None - waterbox: WaterBoxes = None - - def __post_init__(self): - self.minBounds = self.colBounds[0] - self.maxBounds = self.colBounds[1] - self.vertices = Vertices(f"{self.sceneName}_vertices", self.vertexList) - self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", self.polyList) - self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", self.surfaceTypeList) - self.bgCamInfo = BgCamInformations( - self.sceneObj, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" - ) - self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) - - def getCmd(self): - return indent + f"SCENE_CMD_COL_HEADER(&{self.name}),\n" - - def getC(self): - """Returns the collision header for the selected scene""" - - headerData = CData() - colData = CData() - varName = f"CollisionHeader {self.name}" - - wBoxPtrLine = colPolyPtrLine = vtxPtrLine = "0, NULL" - camPtrLine = surfacePtrLine = "NULL" - - # Add waterbox data if necessary - if len(self.waterbox.waterboxList) > 0: - colData.append(self.waterbox.getC()) - wBoxPtrLine = f"ARRAY_COUNT({self.waterbox.name}), {self.waterbox.name}" - - # Add camera data if necessary - if len(self.bgCamInfo.bgCamInfoList) > 0 or len(self.bgCamInfo.crawlspacePosList) > 0: - infoData = self.bgCamInfo.getInfoArrayC() - if "&" in infoData.source: - colData.append(self.bgCamInfo.getDataArrayC()) - colData.append(infoData) - camPtrLine = f"{self.bgCamInfo.name}" - - # Add surface types - if len(self.surfaceType.surfaceTypeList) > 0: - colData.append(self.surfaceType.getC()) - surfacePtrLine = f"{self.surfaceType.name}" - - # Add vertex data - if len(self.vertices.vertexList) > 0: - colData.append(self.vertices.getC()) - vtxPtrLine = f"ARRAY_COUNT({self.vertices.name}), {self.vertices.name}" - - # Add collision poly data - if len(self.collisionPoly.polyList) > 0: - colData.append(self.collisionPoly.getC()) - colPolyPtrLine = f"ARRAY_COUNT({self.collisionPoly.name}), {self.collisionPoly.name}" - - # build the C data of the collision header - - # .h - headerData.header = f"extern {varName};\n" - - # .c - headerData.source += ( - (varName + " = {\n") - + ",\n".join( - indent + val - for val in [ - ("{ " + ", ".join(f"{val}" for val in self.minBounds) + " }"), - ("{ " + ", ".join(f"{val}" for val in self.maxBounds) + " }"), - vtxPtrLine, - colPolyPtrLine, - surfacePtrLine, - camPtrLine, - wBoxPtrLine, - ] - ) - + "\n};\n\n" - ) - - headerData.append(colData) - return headerData diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py new file mode 100644 index 000000000..a6c5c9117 --- /dev/null +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass +from mathutils import Vector +from ....utility import PluginError, CData, indent + + +@dataclass +class CollisionPoly: + """This class defines a single collision poly""" + + indices: list[int] + ignoreCamera: bool + ignoreEntity: bool + ignoreProjectile: bool + enableConveyor: bool + normal: Vector + dist: int + useMacros: bool + type: int = None + + def getFlags_vIA(self): + """Returns the value of ``flags_vIA``""" + + vtxId = self.indices[0] & 0x1FFF + if self.ignoreProjectile or self.ignoreEntity or self.ignoreCamera: + flag1 = ("COLPOLY_IGNORE_PROJECTILES" if self.useMacros else "(1 << 2)") if self.ignoreProjectile else "" + flag2 = ("COLPOLY_IGNORE_ENTITY" if self.useMacros else "(1 << 1)") if self.ignoreEntity else "" + flag3 = ("COLPOLY_IGNORE_CAMERA" if self.useMacros else "(1 << 0)") if self.ignoreCamera else "" + flags = "(" + " | ".join(flag for flag in [flag1, flag2, flag3] if len(flag) > 0) + ")" + else: + flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" + + return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" + + def getFlags_vIB(self): + """Returns the value of ``flags_vIB``""" + + vtxId = self.indices[1] & 0x1FFF + if self.enableConveyor: + flags = "COLPOLY_IS_FLOOR_CONVEYOR" if self.useMacros else "(1 << 0)" + else: + flags = "COLPOLY_IGNORE_NONE" if self.useMacros else "0" + return f"COLPOLY_VTX({vtxId}, {flags})" if self.useMacros else f"((({flags} & 7) << 13) | ({vtxId} & 0x1FFF))" + + def getEntryC(self): + """Returns an entry for the collision poly array""" + + vtxId = self.indices[2] & 0x1FFF + if self.type is None: + raise PluginError("ERROR: Surface Type missing!") + return ( + (indent + "{ ") + + ", ".join( + ( + f"{self.type}", + self.getFlags_vIA(), + self.getFlags_vIB(), + f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF", + ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), + f"{self.dist}", + ) + ) + + " }," + ) + + +@dataclass +class CollisionPolygons: + """This class defines the array of collision polys""" + + name: str + polyList: list[CollisionPoly] + + def getC(self): + colPolyData = CData() + listName = f"CollisionPoly {self.name}[{len(self.polyList)}]" + + # .h + colPolyData.header = f"extern {listName};\n" + + # .c + colPolyData.source = (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.polyList) + "\n};\n\n" + + return colPolyData diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py new file mode 100644 index 000000000..20ceffcf7 --- /dev/null +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -0,0 +1,152 @@ +from dataclasses import dataclass +from ....utility import CData, indent + + +@dataclass +class SurfaceType: + """This class defines a single surface type""" + + bgCamIndex: int + exitIndex: int + floorType: int + unk18: int # unused? + wallType: int + floorProperty: int + isSoft: bool + isHorseBlocked: bool + + material: int + floorEffect: int + lightSetting: int + echo: int + canHookshot: bool + conveyorSpeed: int + conveyorDirection: int + isWallDamage: bool # unk27 + + conveyorKeepMomentum: bool + useMacros: bool + isSoftC: str = None + isHorseBlockedC: str = None + canHookshotC: str = None + isWallDamageC: str = None + + def __hash__(self): + return hash( + ( + self.bgCamIndex, + self.exitIndex, + self.floorType, + self.unk18, + self.wallType, + self.floorProperty, + self.isSoft, + self.isHorseBlocked, + self.material, + self.floorEffect, + self.lightSetting, + self.echo, + self.canHookshot, + self.conveyorSpeed, + self.conveyorDirection, + self.isWallDamage, + self.conveyorKeepMomentum, + ) + ) + + def __post_init__(self): + if self.conveyorKeepMomentum: + self.conveyorSpeed += 4 + + self.isSoftC = "1" if self.isSoft else "0" + self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" + self.canHookshotC = "1" if self.canHookshot else "0" + self.isWallDamageC = "1" if self.isWallDamage else "0" + + def getSurfaceType0(self): + """Returns surface type properties for the first element of the data array""" + + if self.useMacros: + return ( + ("SURFACETYPE0(") + + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " + + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isHorseBlockedC} & 1) << 31)", + f"(({self.isSoftC} & 1) << 30)", + f"(({self.floorProperty} & 0x0F) << 26)", + f"(({self.wallType} & 0x1F) << 21)", + f"(({self.unk18} & 0x07) << 18)", + f"(({self.floorType} & 0x1F) << 13)", + f"(({self.exitIndex} & 0x1F) << 8)", + f"({self.bgCamIndex} & 0xFF)", + ] + ) + + ")" + ) + + def getSurfaceType1(self): + """Returns surface type properties for the second element of the data array""" + + if self.useMacros: + return ( + ("SURFACETYPE1(") + + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " + + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" + + ")" + ) + else: + return ( + (indent * 2 + "(") + + " | ".join( + prop + for prop in [ + f"(({self.isWallDamageC} & 1) << 27)", + f"(({self.conveyorDirection} & 0x3F) << 21)", + f"(({self.conveyorSpeed} & 0x07) << 18)", + f"(({self.canHookshotC} & 1) << 17)", + f"(({self.echo} & 0x3F) << 11)", + f"(({self.lightSetting} & 0x1F) << 6)", + f"(({self.floorEffect} & 0x03) << 4)", + f"({self.material} & 0x0F)", + ] + ) + + ")" + ) + + def getEntryC(self): + """Returns an entry for the surface type array""" + + if self.useMacros: + return indent + "{ " + self.getSurfaceType0() + ", " + self.getSurfaceType1() + " }," + else: + return (indent + "{\n") + self.getSurfaceType0() + ",\n" + self.getSurfaceType1() + ("\n" + indent + "},") + + +@dataclass +class SurfaceTypes: + """This class defines the array of surface types""" + + name: str + surfaceTypeList: list[SurfaceType] + + def getC(self): + surfaceData = CData() + listName = f"SurfaceType {self.name}[{len(self.surfaceTypeList)}]" + + # .h + surfaceData.header = f"extern {listName};\n" + + # .c + surfaceData.source = ( + (listName + " = {\n") + "\n".join(poly.getEntryC() for poly in self.surfaceTypeList) + "\n};\n\n" + ) + + return surfaceData diff --git a/fast64_internal/oot/exporter/collision/vertex.py b/fast64_internal/oot/exporter/collision/vertex.py new file mode 100644 index 000000000..4a1dad701 --- /dev/null +++ b/fast64_internal/oot/exporter/collision/vertex.py @@ -0,0 +1,36 @@ +from dataclasses import dataclass +from ....utility import CData, indent + + +@dataclass +class Vertex: + """This class defines a vertex data""" + + pos: tuple[int, int, int] + + def getEntryC(self): + """Returns a vertex entry""" + + return indent + "{ " + ", ".join(f"{p:6}" for p in self.pos) + " }," + + +@dataclass +class Vertices: + """This class defines the array of vertices""" + + name: str + vertexList: list[Vertex] + + def getC(self): + vertData = CData() + listName = f"Vec3s {self.name}[{len(self.vertexList)}]" + + # .h + vertData.header = f"extern {listName};\n" + + # .c + vertData.source = ( + (listName + " = {\n") + "\n".join(vertex.getEntryC() for vertex in self.vertexList) + "\n};\n\n" + ) + + return vertData diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py new file mode 100644 index 000000000..d448aff68 --- /dev/null +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -0,0 +1,124 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from ....utility import CData, checkIdentityRotation, indent +from ..base import Base + + +@dataclass +class WaterBox: + """This class defines waterbox data""" + + position: tuple[int, int, int] + scale: float + emptyDisplaySize: float + + # Properties + bgCamIndex: int + lightIndex: int + roomIndex: int + setFlag19: bool + + useMacros: bool + xMin: int = None + ySurface: int = None + zMin: int = None + xLength: int = None + zLength: int = None + + setFlag19C: str = None + roomIndexC: str = None + + def __post_init__(self): + self.setFlag19C = "1" if self.setFlag19 else "0" + self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" + + # The scale ordering is due to the fact that scaling happens AFTER rotation. + # Thus the translation uses Y-up, while the scale uses Z-up. + xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) + zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) + + self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) + self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) + self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) + self.xLength = xMax - self.xMin + self.zLength = zMax - self.zMin + + def getProperties(self): + """Returns the waterbox properties""" + + if self.useMacros: + return f"WATERBOX_PROPERTIES({self.bgCamIndex}, {self.lightIndex}, {self.roomIndexC}, {self.setFlag19C})" + else: + return ( + "(" + + " | ".join( + prop + for prop in [ + f"(({self.setFlag19C} & 1) << 19)", + f"(({self.roomIndexC} & 0x3F) << 13)", + f"(({self.lightIndex} & 0x1F) << 8)", + f"(({self.bgCamIndex}) & 0xFF)", + ] + ) + + ")" + ) + + def getEntryC(self): + """Returns a waterbox entry""" + + return ( + (indent + "{ ") + + f"{self.xMin}, {self.ySurface}, {self.zMin}, {self.xLength}, {self.zLength}, " + + self.getProperties() + + " }," + ) + + +@dataclass +class WaterBoxes(Base): + """This class defines the array of waterboxes""" + + sceneObj: Object + transform: Matrix + name: str + useMacros: bool + + waterboxList: list[WaterBox] = field(default_factory=list) + + def __post_init__(self): + waterboxObjList: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" + ] + + for waterboxObj in waterboxObjList: + emptyScale = waterboxObj.empty_display_size + pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) + checkIdentityRotation(waterboxObj, orientedRot, False) + + wboxProp = waterboxObj.ootWaterBoxProperty + roomObj = self.getRoomObjectFromChild(waterboxObj) + self.waterboxList.append( + WaterBox( + pos, + scale, + emptyScale, + wboxProp.camera, + wboxProp.lighting, + roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, + wboxProp.flag19, + self.useMacros, + ) + ) + + def getC(self): + wboxData = CData() + listName = f"WaterBox {self.name}[{len(self.waterboxList)}]" + + # .h + wboxData.header = f"extern {listName};\n" + + # .c + wboxData.source = (listName + " = {\n") + "\n".join(wBox.getEntryC() for wBox in self.waterboxList) + "\n};\n\n" + + return wboxData diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index fb206c612..127518c77 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -4,7 +4,6 @@ from ....utility import CData, indent from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ...room.properties import OOTRoomHeaderProperty -from ...oot_constants import ootData from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter from ..classes import RoomFile diff --git a/fast64_internal/oot/exporter/scene/classes.py b/fast64_internal/oot/exporter/scene/classes.py deleted file mode 100644 index 9ca83cabb..000000000 --- a/fast64_internal/oot/exporter/scene/classes.py +++ /dev/null @@ -1,148 +0,0 @@ -from dataclasses import dataclass, field -from ....utility import CData, indent -from ..base import Actor - - -@dataclass -class TransitionActor(Actor): - """Defines a Transition Actor""" - - dontTransition: bool = None - roomFrom: int = None - roomTo: int = None - cameraFront: str = None - cameraBack: str = None - - def getEntryC(self): - """Returns a single transition actor entry""" - - sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] - roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" - - actorInfos = [roomData, self.id, posData, self.rot, self.params] - infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] - - return ( - (indent + f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -@dataclass -class EntranceActor(Actor): - """Defines an Entrance Actor""" - - roomIndex: int = None - spawnIndex: int = None - - def getEntryC(self): - """Returns a single spawn entry""" - - return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" - - -@dataclass -class EnvLightSettings: - """This class defines the information of one environment light setting""" - - envLightMode: str - ambientColor: tuple[int, int, int] - light1Color: tuple[int, int, int] - light1Dir: tuple[int, int, int] - light2Color: tuple[int, int, int] - light2Dir: tuple[int, int, int] - fogColor: tuple[int, int, int] - fogNear: int - zFar: int - blendRate: int - - def getBlendFogNear(self): - """Returns the packed blend rate and fog near values""" - - return f"(({self.blendRate} << 10) | {self.fogNear})" - - def getColorValues(self, vector: tuple[int, int, int]): - """Returns and formats color values""" - - return ", ".join(f"{v:5}" for v in vector) - - def getDirectionValues(self, vector: tuple[int, int, int]): - """Returns and formats direction values""" - - return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - - def getEntryC(self, index: int): - """Returns an environment light entry""" - - isLightingCustom = self.envLightMode == "Custom" - - vectors = [ - (self.ambientColor, "Ambient Color", self.getColorValues), - (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), - (self.light1Color, "Diffuse0 Color", self.getColorValues), - (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), - (self.light2Color, "Diffuse1 Color", self.getColorValues), - (self.fogColor, "Fog Color", self.getColorValues), - ] - - fogData = [ - (self.getBlendFogNear(), "Blend Rate & Fog Near"), - (f"{self.zFar}", "Fog Far"), - ] - - lightDescs = ["Dawn", "Day", "Dusk", "Night"] - - if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": - # @TODO: Improve the lighting system. - # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. - # This is not accurate and more complicated, - # for now we are doing ``index % 4`` to avoid having an OoB read in the list - # but this will need to be changed the day the lighting system is updated. - lightDesc = f"// {lightDescs[index % 4]} Lighting\n" - else: - isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" - lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" - - lightData = ( - (indent + lightDesc) - + (indent + "{\n") - + "".join( - indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors - ) - + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) - + (indent + "},\n") - ) - - return lightData - - -@dataclass -class Path: - """This class defines a pathway""" - - name: str - points: list[tuple[int, int, int]] = field(default_factory=list) - - def getC(self): - """Returns the pathway position array""" - - pathData = CData() - pathName = f"Vec3s {self.name}" - - # .h - pathData.header = f"extern {pathName}[];\n" - - # .c - pathData.source = ( - f"{pathName}[]" - + " = {\n" - + "\n".join( - indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points - ) - + "\n};\n\n" - ) - - return pathData diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py deleted file mode 100644 index 5ea3da44b..000000000 --- a/fast64_internal/oot/exporter/scene/header.py +++ /dev/null @@ -1,531 +0,0 @@ -from dataclasses import dataclass, field -from mathutils import Matrix -from bpy.types import Object -from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent -from ...oot_constants import ootData -from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ..base import Base -from .classes import TransitionActor, EntranceActor, EnvLightSettings, Path - - -@dataclass -class HeaderBase(Base): - props: OOTSceneHeaderProperty - - -@dataclass -class SceneInfos(HeaderBase): - """This class stores various scene header informations""" - - sceneObj: Object - - ### General ### - - keepObjectID: str = None - naviHintType: str = None - drawConfig: str = None - appendNullEntrance: bool = None - useDummyRoomList: bool = None - - ### Skybox And Sound ### - - # Skybox - skyboxID: str = None - skyboxConfig: str = None - - # Sound - sequenceID: str = None - ambienceID: str = None - specID: str = None - - ### Camera And World Map ### - - # World Map - worldMapLocation: str = None - - # Camera - sceneCamType: str = None - - def __post_init__(self): - self.keepObjectID = self.getPropValue(self.props, "globalObject") - self.naviHintType = self.getPropValue(self.props, "naviCup") - self.drawConfig = self.getPropValue(self.props.sceneTableEntry, "drawConfig") - self.appendNullEntrance = self.props.appendNullEntrance - self.useDummyRoomList = self.sceneObj.fast64.oot.scene.write_dummy_room_list - self.skyboxID = self.getPropValue(self.props, "skyboxID") - self.skyboxConfig = self.getPropValue(self.props, "skyboxCloudiness") - self.sequenceID = self.getPropValue(self.props, "musicSeq") - self.ambienceID = self.getPropValue(self.props, "nightSeq") - self.specID = self.getPropValue(self.props, "audioSessionPreset") - self.worldMapLocation = self.getPropValue(self.props, "mapLocation") - self.sceneCamType = self.getPropValue(self.props, "cameraMode") - - def getCmds(self, lights: "SceneLighting"): - return ( - indent - + f",\n{indent}".join( - [ - f"SCENE_CMD_SOUND_SETTINGS({self.specID}, {self.ambienceID}, {self.sequenceID})", - f"SCENE_CMD_MISC_SETTINGS({self.sceneCamType}, {self.worldMapLocation})", - f"SCENE_CMD_SPECIAL_FILES({self.naviHintType}, {self.keepObjectID})", - f"SCENE_CMD_SKYBOX_SETTINGS({self.skyboxID}, {self.skyboxConfig}, {lights.envLightMode})", - ] - ) - + ",\n" - ) - - -@dataclass -class SceneLighting(HeaderBase): - """This class hosts lighting data""" - - name: str - - envLightMode: str = None - settings: list[EnvLightSettings] = field(default_factory=list) - - def __post_init__(self): - self.envLightMode = self.getPropValue(self.props, "skyboxLighting") - lightList: list[OOTLightProperty] = [] - - if self.envLightMode == "LIGHT_MODE_TIME": - todLights = self.props.timeOfDayLights - lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] - else: - lightList = self.props.lightList - - for lightProp in lightList: - light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) - light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) - self.settings.append( - EnvLightSettings( - self.envLightMode, - exportColor(lightProp.ambient), - light1[0], - light1[1], - light2[0], - light2[1], - exportColor(lightProp.fogColor), - lightProp.fogNear, - lightProp.fogFar, - lightProp.transitionSpeed, - ) - ) - - def getCmd(self): - return ( - indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" - ) + f"{len(self.settings)}, {self.name if len(self.settings) > 0 else 'NULL'}),\n" - - def getC(self): - """Returns a ``CData`` containing the C data of env. light settings""" - - lightSettingsC = CData() - lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" - - # .h - lightSettingsC.header = f"extern {lightName};\n" - - # .c - lightSettingsC.source = ( - (lightName + " = {\n") + "".join(light.getEntryC(i) for i, light in enumerate(self.settings)) + "};\n\n" - ) - - return lightSettingsC - - -@dataclass -class SceneCutscene(HeaderBase): - """This class hosts cutscene data (unfinished)""" - - headerIndex: int - - writeType: str = None - writeCutscene: bool = None - csObj: Object = None - csWriteCustom: str = None - extraCutscenes: list[Object] = field(default_factory=list) - name: str = None - - def __post_init__(self): - self.writeType = self.props.csWriteType - self.writeCutscene = self.props.writeCutscene - self.csObj = self.props.csWriteObject - self.csWriteCustom = self.props.csWriteCustom if self.props.csWriteType == "Custom" else None - self.extraCutscenes = [csObj for csObj in self.props.extraCutscenes] - - if self.writeCutscene and self.writeType == "Embedded": - raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") - - if self.headerIndex > 0 and len(self.extraCutscenes) > 0: - raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") - - if self.csObj is not None: - self.name = self.csObj.name.removeprefix("Cutscene.") - - if self.csObj.ootEmptyType != "Cutscene": - raise PluginError("ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type") - elif self.csObj.parent is not None: - raise PluginError("ERROR: Cutscene empty object should not be parented to anything") - else: - raise PluginError("ERROR: No object selected for cutscene reference") - - def getCmd(self): - csDataName = self.csObj.name if self.writeType == "Object" else self.csWriteCustom - return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" - - def getC(self): - # will be implemented when PR #208 is merged - cutsceneData = CData() - return cutsceneData - - -@dataclass -class SceneExits(HeaderBase): - """This class hosts exit data""" - - name: str - exitList: list[tuple[int, str]] = field(default_factory=list) - - def __post_init__(self): - for i, exitProp in enumerate(self.props.exitList): - if exitProp.exitIndex != "Custom": - raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") - self.exitList.append((i, exitProp.exitIndexCustom)) - - def getCmd(self): - return indent + f"SCENE_CMD_EXIT_LIST({self.name}),\n" - - def getC(self): - """Returns a ``CData`` containing the C data of the exit array""" - - exitListC = CData() - listName = f"u16 {self.name}[{len(self.exitList)}]" - - # .h - exitListC.header = f"extern {listName};\n" - - # .c - exitListC.source = ( - (listName + " = {\n") - # @TODO: use the enum name instead of the raw index - + "\n".join(indent + f"{value}," for (_, value) in self.exitList) - + "\n};\n\n" - ) - - return exitListC - - -@dataclass -class SceneTransitionActors(HeaderBase): - name: str - sceneObj: Object - transform: Matrix - headerIndex: int - - entries: list[TransitionActor] = field(default_factory=list) - - def __post_init__(self): - actorObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - self.roomIndex = roomObj.ootRoomHeader.roomIndex - - transActorProp = obj.ootTransitionActorProperty - - if ( - self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) - and transActorProp.actor.actorID != "None" - ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - transActor = TransitionActor() - - if transActorProp.dontTransition: - front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) - back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - else: - front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) - - if transActorProp.actor.actorID == "Custom": - transActor.id = transActorProp.actor.actorIDCustom - else: - transActor.id = transActorProp.actor.actorID - - transActor.name = ( - ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( - f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if transActorProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - transActor.pos = pos - transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? - transActor.params = transActorProp.actor.actorParam - transActor.roomFrom, transActor.cameraFront = front - transActor.roomTo, transActor.cameraBack = back - self.entries.append(transActor) - - def getCmd(self): - return indent + f"SCENE_CMD_TRANSITION_ACTOR_LIST({len(self.entries)}, {self.name}),\n" - - def getC(self): - """Returns the transition actor array""" - - transActorList = CData() - listName = f"TransitionActorEntry {self.name}" - - # .h - transActorList.header = f"extern {listName}[];\n" - - # .c - transActorList.source = ( - (f"{listName}[]" + " = {\n") + "\n".join(transActor.getEntryC() for transActor in self.entries) + "};\n\n" - ) - - return transActorList - - -@dataclass -class SceneEntranceActors(HeaderBase): - name: str - sceneObj: Object - transform: Matrix - headerIndex: int - - entries: list[EntranceActor] = field(default_factory=list) - - def __post_init__(self): - """Returns the entrance actor list based on empty objects with the type 'Entrance'""" - - entranceActorFromIndex: dict[int, EntranceActor] = {} - actorObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" - ] - for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - - entranceProp = obj.ootEntranceProperty - if ( - self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) - and entranceProp.actor.actorID != "None" - ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) - entranceActor = EntranceActor() - - entranceActor.name = ( - ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( - f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if entranceProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom - entranceActor.pos = pos - entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) - entranceActor.params = entranceProp.actor.actorParam - entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex - entranceActor.spawnIndex = entranceProp.spawnIndex - - if not entranceProp.spawnIndex in entranceActorFromIndex: - entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor - else: - raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") - - entranceActorFromIndex = dict(sorted(entranceActorFromIndex.items())) - if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): - raise PluginError("ERROR: The spawn indices are not consecutive!") - - self.entries = list(entranceActorFromIndex.values()) - - def getCmd(self): - name = self.name if len(self.entries) > 0 else "NULL" - return indent + f"SCENE_CMD_SPAWN_LIST({len(self.entries)}, {name}),\n" - - def getC(self): - """Returns the spawn actor array""" - - spawnActorList = CData() - listName = f"ActorEntry {self.name}" - - # .h - spawnActorList.header = f"extern {listName}[];\n" - - # .c - spawnActorList.source = ( - (f"{listName}[]" + " = {\n") + "".join(entrance.getActorEntry() for entrance in self.entries) + "};\n\n" - ) - - return spawnActorList - - -@dataclass -class SceneSpawns(HeaderBase): - """This class handles scene actors (transition actors and entrance actors)""" - - name: str - entries: list[EntranceActor] - - def getCmd(self): - return indent + f"SCENE_CMD_ENTRANCE_LIST({self.name if len(self.entries) > 0 else 'NULL'}),\n" - - def getC(self): - """Returns the spawn array""" - - spawnList = CData() - listName = f"Spawn {self.name}" - - # .h - spawnList.header = f"extern {listName}[];\n" - - # .c - spawnList.source = ( - (f"{listName}[]" + " = {\n") - + (indent + "// { Spawn Actor List Index, Room Index }\n") - + "".join(entrance.getEntryC() for entrance in self.entries) - + "};\n\n" - ) - - return spawnList - - -@dataclass -class ScenePathways(HeaderBase): - """This class hosts pathways array data""" - - name: str - sceneObj: Object - transform: Matrix - headerIndex: int - - pathList: list[Path] = field(default_factory=list) - - def __post_init__(self): - pathObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" - ] - - for i, obj in enumerate(pathObjList): - isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) - if isHeaderValid and self.validateCurveData(obj): - self.pathList.append( - Path( - f"{self.name}List{i:02}", - [self.transform @ point.co.xyz for point in obj.data.splines[0].points], - ) - ) - - def getCmd(self): - return indent + f"SCENE_CMD_PATH_LIST({self.name}),\n" if len(self.pathList) > 0 else "" - - def getC(self): - """Returns a ``CData`` containing the C data of the pathway array""" - - pathData = CData() - pathListData = CData() - listName = f"Path {self.name}[{len(self.pathList)}]" - - # .h - pathListData.header = f"extern {listName};\n" - - # .c - pathListData.source = listName + " = {\n" - - for path in self.pathList: - pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" - pathData.append(path.getC()) - - pathListData.source += "};\n\n" - pathData.append(pathListData) - - return pathData - - -@dataclass -class SceneHeader(HeaderBase): - """This class defines a scene header""" - - name: str - sceneObj: Object - transform: Matrix - headerIndex: int - - infos: SceneInfos = None - lighting: SceneLighting = None - cutscene: SceneCutscene = None - exits: SceneExits = None - transitionActors: SceneTransitionActors = None - entranceActors: SceneEntranceActors = None - spawns: SceneSpawns = None - path: ScenePathways = None - - def __post_init__(self): - self.infos = SceneInfos(self.props, self.sceneObj) - self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") - - if self.props.writeCutscene: - self.cutscene = SceneCutscene(self.props, self.headerIndex) - - self.exits = SceneExits(self.props, f"{self.name}_exitList") - - self.transitionActors = SceneTransitionActors( - None, f"{self.name}_transitionActors", self.sceneObj, self.transform, self.headerIndex - ) - - self.entranceActors = SceneEntranceActors( - None, f"{self.name}_playerEntryList", self.sceneObj, self.transform, self.headerIndex - ) - - self.spawns = SceneSpawns(None, f"{self.name}_entranceList", self.entranceActors.entries) - self.path = ScenePathways(self.props, f"{self.name}_pathway", self.sceneObj, self.transform, self.headerIndex) - - def getC(self): - """Returns the ``CData`` containing the header's data""" - - headerData = CData() - - # Write the spawn position list data and the entrance list - if len(self.entranceActors.entries) > 0: - headerData.append(self.entranceActors.getC()) - headerData.append(self.spawns.getC()) - - # Write the transition actor list data - if len(self.transitionActors.entries) > 0: - headerData.append(self.transitionActors.getC()) - - # Write the exit list - if len(self.exits.exitList) > 0: - headerData.append(self.exits.getC()) - - # Write the light data - if len(self.lighting.settings) > 0: - headerData.append(self.lighting.getC()) - - # Write the path data, if used - if len(self.path.pathList) > 0: - headerData.append(self.path.getC()) - - return headerData - - -@dataclass -class SceneAlternateHeader: - """This class stores alternate header data for the scene""" - - name: str - childNight: SceneHeader = None - adultDay: SceneHeader = None - adultNight: SceneHeader = None - cutscenes: list[SceneHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/exporter/scene/header/__init__.py b/fast64_internal/oot/exporter/scene/header/__init__.py new file mode 100644 index 000000000..9eb05c729 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/header/__init__.py @@ -0,0 +1,89 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from .....utility import CData +from ....scene.properties import OOTSceneHeaderProperty +from ...base import Base +from .general import SceneLighting, SceneInfos, SceneExits +from .cutscene import SceneCutscene +from .actors import SceneTransitionActors, SceneEntranceActors, SceneSpawns +from .pathways import ScenePathways + + +@dataclass +class SceneHeader(Base): + """This class defines a scene header""" + + props: OOTSceneHeaderProperty + name: str + sceneObj: Object + transform: Matrix + headerIndex: int + + infos: SceneInfos = None + lighting: SceneLighting = None + cutscene: SceneCutscene = None + exits: SceneExits = None + transitionActors: SceneTransitionActors = None + entranceActors: SceneEntranceActors = None + spawns: SceneSpawns = None + path: ScenePathways = None + + def __post_init__(self): + self.infos = SceneInfos(self.props, self.sceneObj) + self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") + + if self.props.writeCutscene: + self.cutscene = SceneCutscene(self.props, self.headerIndex) + + self.exits = SceneExits(self.props, f"{self.name}_exitList") + + self.transitionActors = SceneTransitionActors( + None, f"{self.name}_transitionActors", self.sceneObj, self.transform, self.headerIndex + ) + + self.entranceActors = SceneEntranceActors( + None, f"{self.name}_playerEntryList", self.sceneObj, self.transform, self.headerIndex + ) + + self.spawns = SceneSpawns(None, f"{self.name}_entranceList", self.entranceActors.entries) + self.path = ScenePathways(self.props, f"{self.name}_pathway", self.sceneObj, self.transform, self.headerIndex) + + def getC(self): + """Returns the ``CData`` containing the header's data""" + + headerData = CData() + + # Write the spawn position list data and the entrance list + if len(self.entranceActors.entries) > 0: + headerData.append(self.entranceActors.getC()) + headerData.append(self.spawns.getC()) + + # Write the transition actor list data + if len(self.transitionActors.entries) > 0: + headerData.append(self.transitionActors.getC()) + + # Write the exit list + if len(self.exits.exitList) > 0: + headerData.append(self.exits.getC()) + + # Write the light data + if len(self.lighting.settings) > 0: + headerData.append(self.lighting.getC()) + + # Write the path data, if used + if len(self.path.pathList) > 0: + headerData.append(self.path.getC()) + + return headerData + + +@dataclass +class SceneAlternateHeader: + """This class stores alternate header data for the scene""" + + name: str + childNight: SceneHeader = None + adultDay: SceneHeader = None + adultNight: SceneHeader = None + cutscenes: list[SceneHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/exporter/scene/header/actors.py b/fast64_internal/oot/exporter/scene/header/actors.py new file mode 100644 index 000000000..5d4579546 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/header/actors.py @@ -0,0 +1,234 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from .....utility import PluginError, CData, indent +from ....oot_constants import ootData +from ....scene.properties import OOTSceneHeaderProperty +from ...base import Base, Actor + + +@dataclass +class TransitionActor(Actor): + """Defines a Transition Actor""" + + dontTransition: bool = None + roomFrom: int = None + roomTo: int = None + cameraFront: str = None + cameraBack: str = None + + def getEntryC(self): + """Returns a single transition actor entry""" + + sides = [(self.roomFrom, self.cameraFront), (self.roomTo, self.cameraBack)] + roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" + posData = "{ " + ", ".join(f"{round(pos)}" for pos in self.pos) + " }" + + actorInfos = [roomData, self.id, posData, self.rot, self.params] + infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] + + return ( + (indent + f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) + + +@dataclass +class SceneTransitionActors(Base): + props: OOTSceneHeaderProperty + name: str + sceneObj: Object + transform: Matrix + headerIndex: int + + entries: list[TransitionActor] = field(default_factory=list) + + def __post_init__(self): + actorObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + self.roomIndex = roomObj.ootRoomHeader.roomIndex + + transActorProp = obj.ootTransitionActorProperty + + if ( + self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) + and transActorProp.actor.actorID != "None" + ): + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + transActor = TransitionActor() + + if transActorProp.dontTransition: + front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) + back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + else: + front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + + if transActorProp.actor.actorID == "Custom": + transActor.id = transActorProp.actor.actorIDCustom + else: + transActor.id = transActorProp.actor.actorID + + transActor.name = ( + ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( + f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if transActorProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + transActor.pos = pos + transActor.rot = f"DEG_TO_BINANG({(rot[1] * (180 / 0x8000)):.3f})" # TODO: Correct axis? + transActor.params = transActorProp.actor.actorParam + transActor.roomFrom, transActor.cameraFront = front + transActor.roomTo, transActor.cameraBack = back + self.entries.append(transActor) + + def getCmd(self): + return indent + f"SCENE_CMD_TRANSITION_ACTOR_LIST({len(self.entries)}, {self.name}),\n" + + def getC(self): + """Returns the transition actor array""" + + transActorList = CData() + listName = f"TransitionActorEntry {self.name}" + + # .h + transActorList.header = f"extern {listName}[];\n" + + # .c + transActorList.source = ( + (f"{listName}[]" + " = {\n") + "\n".join(transActor.getEntryC() for transActor in self.entries) + "};\n\n" + ) + + return transActorList + + +@dataclass +class EntranceActor(Actor): + """Defines an Entrance Actor""" + + roomIndex: int = None + spawnIndex: int = None + + def getEntryC(self): + """Returns a single spawn entry""" + + return indent + "{ " + f"{self.spawnIndex}, {self.roomIndex}" + " },\n" + + +@dataclass +class SceneEntranceActors(Base): + props: OOTSceneHeaderProperty + name: str + sceneObj: Object + transform: Matrix + headerIndex: int + + entries: list[EntranceActor] = field(default_factory=list) + + def __post_init__(self): + """Returns the entrance actor list based on empty objects with the type 'Entrance'""" + + entranceActorFromIndex: dict[int, EntranceActor] = {} + actorObjList: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" + ] + for obj in actorObjList: + roomObj = self.getRoomObjectFromChild(obj) + if roomObj is None: + raise PluginError("ERROR: Room Object not found!") + + entranceProp = obj.ootEntranceProperty + if ( + self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) + and entranceProp.actor.actorID != "None" + ): + pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + entranceActor = EntranceActor() + + entranceActor.name = ( + ootData.actorData.actorsByID[entranceProp.actor.actorID].name.replace( + f" - {entranceProp.actor.actorID.removeprefix('ACTOR_')}", "" + ) + if entranceProp.actor.actorID != "Custom" + else "Custom Actor" + ) + + entranceActor.id = "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom + entranceActor.pos = pos + entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) + entranceActor.params = entranceProp.actor.actorParam + entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex + entranceActor.spawnIndex = entranceProp.spawnIndex + + if not entranceProp.spawnIndex in entranceActorFromIndex: + entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor + else: + raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") + + entranceActorFromIndex = dict(sorted(entranceActorFromIndex.items())) + if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): + raise PluginError("ERROR: The spawn indices are not consecutive!") + + self.entries = list(entranceActorFromIndex.values()) + + def getCmd(self): + name = self.name if len(self.entries) > 0 else "NULL" + return indent + f"SCENE_CMD_SPAWN_LIST({len(self.entries)}, {name}),\n" + + def getC(self): + """Returns the spawn actor array""" + + spawnActorList = CData() + listName = f"ActorEntry {self.name}" + + # .h + spawnActorList.header = f"extern {listName}[];\n" + + # .c + spawnActorList.source = ( + (f"{listName}[]" + " = {\n") + "".join(entrance.getActorEntry() for entrance in self.entries) + "};\n\n" + ) + + return spawnActorList + + +@dataclass +class SceneSpawns(Base): + """This class handles scene actors (transition actors and entrance actors)""" + + props: OOTSceneHeaderProperty + name: str + entries: list[EntranceActor] + + def getCmd(self): + return indent + f"SCENE_CMD_ENTRANCE_LIST({self.name if len(self.entries) > 0 else 'NULL'}),\n" + + def getC(self): + """Returns the spawn array""" + + spawnList = CData() + listName = f"Spawn {self.name}" + + # .h + spawnList.header = f"extern {listName}[];\n" + + # .c + spawnList.source = ( + (f"{listName}[]" + " = {\n") + + (indent + "// { Spawn Actor List Index, Room Index }\n") + + "".join(entrance.getEntryC() for entrance in self.entries) + + "};\n\n" + ) + + return spawnList diff --git a/fast64_internal/oot/exporter/scene/header/cutscene.py b/fast64_internal/oot/exporter/scene/header/cutscene.py new file mode 100644 index 000000000..266bdd752 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/header/cutscene.py @@ -0,0 +1,52 @@ +from dataclasses import dataclass, field +from bpy.types import Object +from .....utility import PluginError, CData, indent +from ....scene.properties import OOTSceneHeaderProperty +from ...base import Base + + +@dataclass +class SceneCutscene(Base): + """This class hosts cutscene data (unfinished)""" + + props: OOTSceneHeaderProperty + headerIndex: int + + writeType: str = None + writeCutscene: bool = None + csObj: Object = None + csWriteCustom: str = None + extraCutscenes: list[Object] = field(default_factory=list) + name: str = None + + def __post_init__(self): + self.writeType = self.props.csWriteType + self.writeCutscene = self.props.writeCutscene + self.csObj = self.props.csWriteObject + self.csWriteCustom = self.props.csWriteCustom if self.props.csWriteType == "Custom" else None + self.extraCutscenes = [csObj for csObj in self.props.extraCutscenes] + + if self.writeCutscene and self.writeType == "Embedded": + raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") + + if self.headerIndex > 0 and len(self.extraCutscenes) > 0: + raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") + + if self.csObj is not None: + self.name = self.csObj.name.removeprefix("Cutscene.") + + if self.csObj.ootEmptyType != "Cutscene": + raise PluginError("ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type") + elif self.csObj.parent is not None: + raise PluginError("ERROR: Cutscene empty object should not be parented to anything") + else: + raise PluginError("ERROR: No object selected for cutscene reference") + + def getCmd(self): + csDataName = self.csObj.name if self.writeType == "Object" else self.csWriteCustom + return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" + + def getC(self): + # will be implemented when PR #208 is merged + cutsceneData = CData() + return cutsceneData diff --git a/fast64_internal/oot/exporter/scene/header/general.py b/fast64_internal/oot/exporter/scene/header/general.py new file mode 100644 index 000000000..d8070f847 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/header/general.py @@ -0,0 +1,240 @@ +from dataclasses import dataclass, field +from bpy.types import Object +from .....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent +from ....scene.properties import OOTSceneHeaderProperty, OOTLightProperty +from ...base import Base + + +@dataclass +class EnvLightSettings: + """This class defines the information of one environment light setting""" + + envLightMode: str + ambientColor: tuple[int, int, int] + light1Color: tuple[int, int, int] + light1Dir: tuple[int, int, int] + light2Color: tuple[int, int, int] + light2Dir: tuple[int, int, int] + fogColor: tuple[int, int, int] + fogNear: int + zFar: int + blendRate: int + + def getBlendFogNear(self): + """Returns the packed blend rate and fog near values""" + + return f"(({self.blendRate} << 10) | {self.fogNear})" + + def getColorValues(self, vector: tuple[int, int, int]): + """Returns and formats color values""" + + return ", ".join(f"{v:5}" for v in vector) + + def getDirectionValues(self, vector: tuple[int, int, int]): + """Returns and formats direction values""" + + return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) + + def getEntryC(self, index: int): + """Returns an environment light entry""" + + isLightingCustom = self.envLightMode == "Custom" + + vectors = [ + (self.ambientColor, "Ambient Color", self.getColorValues), + (self.light1Dir, "Diffuse0 Direction", self.getDirectionValues), + (self.light1Color, "Diffuse0 Color", self.getColorValues), + (self.light2Dir, "Diffuse1 Direction", self.getDirectionValues), + (self.light2Color, "Diffuse1 Color", self.getColorValues), + (self.fogColor, "Fog Color", self.getColorValues), + ] + + fogData = [ + (self.getBlendFogNear(), "Blend Rate & Fog Near"), + (f"{self.zFar}", "Fog Far"), + ] + + lightDescs = ["Dawn", "Day", "Dusk", "Night"] + + if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": + # @TODO: Improve the lighting system. + # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. + # This is not accurate and more complicated, + # for now we are doing ``index % 4`` to avoid having an OoB read in the list + # but this will need to be changed the day the lighting system is updated. + lightDesc = f"// {lightDescs[index % 4]} Lighting\n" + else: + isIndoor = not isLightingCustom and self.envLightMode == "LIGHT_MODE_SETTINGS" + lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" + + lightData = ( + (indent + lightDesc) + + (indent + "{\n") + + "".join( + indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for (vector, desc, vecToC) in vectors + ) + + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) + + (indent + "},\n") + ) + + return lightData + + +@dataclass +class SceneLighting(Base): + """This class hosts lighting data""" + + props: OOTSceneHeaderProperty + name: str + + envLightMode: str = None + settings: list[EnvLightSettings] = field(default_factory=list) + + def __post_init__(self): + self.envLightMode = self.getPropValue(self.props, "skyboxLighting") + lightList: list[OOTLightProperty] = [] + + if self.envLightMode == "LIGHT_MODE_TIME": + todLights = self.props.timeOfDayLights + lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] + else: + lightList = self.props.lightList + + for lightProp in lightList: + light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) + light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) + self.settings.append( + EnvLightSettings( + self.envLightMode, + exportColor(lightProp.ambient), + light1[0], + light1[1], + light2[0], + light2[1], + exportColor(lightProp.fogColor), + lightProp.fogNear, + lightProp.fogFar, + lightProp.transitionSpeed, + ) + ) + + def getCmd(self): + return ( + indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" + ) + f"{len(self.settings)}, {self.name if len(self.settings) > 0 else 'NULL'}),\n" + + def getC(self): + """Returns a ``CData`` containing the C data of env. light settings""" + + lightSettingsC = CData() + lightName = f"EnvLightSettings {self.name}[{len(self.settings)}]" + + # .h + lightSettingsC.header = f"extern {lightName};\n" + + # .c + lightSettingsC.source = ( + (lightName + " = {\n") + "".join(light.getEntryC(i) for i, light in enumerate(self.settings)) + "};\n\n" + ) + + return lightSettingsC + + +@dataclass +class SceneInfos(Base): + """This class stores various scene header informations""" + + props: OOTSceneHeaderProperty + sceneObj: Object + + ### General ### + + keepObjectID: str = None + naviHintType: str = None + drawConfig: str = None + appendNullEntrance: bool = None + useDummyRoomList: bool = None + + ### Skybox And Sound ### + + # Skybox + skyboxID: str = None + skyboxConfig: str = None + + # Sound + sequenceID: str = None + ambienceID: str = None + specID: str = None + + ### Camera And World Map ### + + # World Map + worldMapLocation: str = None + + # Camera + sceneCamType: str = None + + def __post_init__(self): + self.keepObjectID = self.getPropValue(self.props, "globalObject") + self.naviHintType = self.getPropValue(self.props, "naviCup") + self.drawConfig = self.getPropValue(self.props.sceneTableEntry, "drawConfig") + self.appendNullEntrance = self.props.appendNullEntrance + self.useDummyRoomList = self.sceneObj.fast64.oot.scene.write_dummy_room_list + self.skyboxID = self.getPropValue(self.props, "skyboxID") + self.skyboxConfig = self.getPropValue(self.props, "skyboxCloudiness") + self.sequenceID = self.getPropValue(self.props, "musicSeq") + self.ambienceID = self.getPropValue(self.props, "nightSeq") + self.specID = self.getPropValue(self.props, "audioSessionPreset") + self.worldMapLocation = self.getPropValue(self.props, "mapLocation") + self.sceneCamType = self.getPropValue(self.props, "cameraMode") + + def getCmds(self, lights: SceneLighting): + return ( + indent + + f",\n{indent}".join( + [ + f"SCENE_CMD_SOUND_SETTINGS({self.specID}, {self.ambienceID}, {self.sequenceID})", + f"SCENE_CMD_MISC_SETTINGS({self.sceneCamType}, {self.worldMapLocation})", + f"SCENE_CMD_SPECIAL_FILES({self.naviHintType}, {self.keepObjectID})", + f"SCENE_CMD_SKYBOX_SETTINGS({self.skyboxID}, {self.skyboxConfig}, {lights.envLightMode})", + ] + ) + + ",\n" + ) + + +@dataclass +class SceneExits(Base): + """This class hosts exit data""" + + props: OOTSceneHeaderProperty + name: str + exitList: list[tuple[int, str]] = field(default_factory=list) + + def __post_init__(self): + for i, exitProp in enumerate(self.props.exitList): + if exitProp.exitIndex != "Custom": + raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") + self.exitList.append((i, exitProp.exitIndexCustom)) + + def getCmd(self): + return indent + f"SCENE_CMD_EXIT_LIST({self.name}),\n" + + def getC(self): + """Returns a ``CData`` containing the C data of the exit array""" + + exitListC = CData() + listName = f"u16 {self.name}[{len(self.exitList)}]" + + # .h + exitListC.header = f"extern {listName};\n" + + # .c + exitListC.source = ( + (listName + " = {\n") + # @TODO: use the enum name instead of the raw index + + "\n".join(indent + f"{value}," for (_, value) in self.exitList) + + "\n};\n\n" + ) + + return exitListC diff --git a/fast64_internal/oot/exporter/scene/header/pathways.py b/fast64_internal/oot/exporter/scene/header/pathways.py new file mode 100644 index 000000000..47bcbebe7 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/header/pathways.py @@ -0,0 +1,90 @@ +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from .....utility import CData, indent +from ....scene.properties import OOTSceneHeaderProperty +from ...base import Base + + +@dataclass +class Path: + """This class defines a pathway""" + + name: str + points: list[tuple[int, int, int]] = field(default_factory=list) + + def getC(self): + """Returns the pathway position array""" + + pathData = CData() + pathName = f"Vec3s {self.name}" + + # .h + pathData.header = f"extern {pathName}[];\n" + + # .c + pathData.source = ( + f"{pathName}[]" + + " = {\n" + + "\n".join( + indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in self.points + ) + + "\n};\n\n" + ) + + return pathData + + +@dataclass +class ScenePathways(Base): + """This class hosts pathways array data""" + + props: OOTSceneHeaderProperty + name: str + sceneObj: Object + transform: Matrix + headerIndex: int + + pathList: list[Path] = field(default_factory=list) + + def __post_init__(self): + pathObjList: list[Object] = [ + obj + for obj in self.sceneObj.children_recursive + if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" + ] + + for i, obj in enumerate(pathObjList): + isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) + if isHeaderValid and self.validateCurveData(obj): + self.pathList.append( + Path( + f"{self.name}List{i:02}", + [self.transform @ point.co.xyz for point in obj.data.splines[0].points], + ) + ) + + def getCmd(self): + return indent + f"SCENE_CMD_PATH_LIST({self.name}),\n" if len(self.pathList) > 0 else "" + + def getC(self): + """Returns a ``CData`` containing the C data of the pathway array""" + + pathData = CData() + pathListData = CData() + listName = f"Path {self.name}[{len(self.pathList)}]" + + # .h + pathListData.header = f"extern {listName};\n" + + # .c + pathListData.source = listName + " = {\n" + + for path in self.pathList: + pathListData.source += indent + "{ " + f"ARRAY_COUNTU({path.name}), {path.name}" + " },\n" + pathData.append(path.getC()) + + pathListData.source += "};\n\n" + pathData.append(pathListData) + + return pathData diff --git a/fast64_internal/oot/exporter/scene/main.py b/fast64_internal/oot/exporter/scene/main.py index aed020134..b7647de66 100644 --- a/fast64_internal/oot/exporter/scene/main.py +++ b/fast64_internal/oot/exporter/scene/main.py @@ -1,15 +1,14 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING +from mathutils import Matrix +from bpy.types import Object from ....utility import PluginError, CData, indent from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod -from ...oot_model_classes import OOTGfxFormatter from ...scene.properties import OOTSceneHeaderProperty +from ...oot_model_classes import OOTModel, OOTGfxFormatter from ..classes import SceneFile +from ..base import Base, altHeaderList from ..collision import CollisionHeader -from .header import SceneHeader -from ...oot_model_classes import OOTModel -from ..base import altHeaderList -from ..collision import CollisionBase from .header import SceneAlternateHeader, SceneHeader if TYPE_CHECKING: @@ -17,9 +16,12 @@ @dataclass -class Scene(CollisionBase): +class Scene(Base): """This class defines a scene""" + sceneObj: Object + transform: Matrix + useMacros: bool name: str = None model: OOTModel = None headerIndex: int = None @@ -70,17 +72,12 @@ def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: def getNewCollisionHeader(self): """Returns and creates collision data""" - colBounds, vertexList, polyList, surfaceTypeList = self.getCollisionData() return CollisionHeader( self.sceneObj, self.transform, + self.useMacros, f"{self.name}_collisionHeader", self.name, - self.useMacros, - colBounds, - vertexList, - polyList, - surfaceTypeList, ) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): From 2efe6c0ecd814830cceee81bd3feebabb1ba3341 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:03:28 +0200 Subject: [PATCH 50/98] improvements part 4 --- fast64_internal/oot/exporter/main.py | 126 +-------- fast64_internal/oot/exporter/other/file.py | 2 +- fast64_internal/oot/exporter/room/main.py | 86 +++++-- .../oot/exporter/scene/__init__.py | 227 ++++++++++++++++- .../oot/exporter/scene/{header => }/actors.py | 8 +- .../exporter/scene/{header => }/cutscene.py | 6 +- .../exporter/scene/{header => }/general.py | 6 +- .../scene/{header/__init__.py => header.py} | 6 +- fast64_internal/oot/exporter/scene/main.py | 241 ------------------ .../exporter/scene/{header => }/pathways.py | 6 +- fast64_internal/oot/exporter/scene/rooms.py | 108 ++++++++ 11 files changed, 416 insertions(+), 406 deletions(-) rename fast64_internal/oot/exporter/scene/{header => }/actors.py (97%) rename fast64_internal/oot/exporter/scene/{header => }/cutscene.py (93%) rename fast64_internal/oot/exporter/scene/{header => }/general.py (97%) rename fast64_internal/oot/exporter/scene/{header/__init__.py => header.py} (96%) delete mode 100644 fast64_internal/oot/exporter/scene/main.py rename fast64_internal/oot/exporter/scene/{header => }/pathways.py (95%) create mode 100644 fast64_internal/oot/exporter/scene/rooms.py diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index cff566de8..2559417aa 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -63,98 +63,6 @@ class SceneExporter: hasCutscenes: bool = False hasSceneTextures: bool = False - def getNewRoomList(self, scene: Scene): - """Returns the room list from empty objects with the type 'Room'""" - - roomDict: dict[int, Room] = {} - roomObjs: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" - ] - - if len(roomObjs) == 0: - raise PluginError("ERROR: The scene has no child empties with the 'Room' empty type.") - - for roomObj in roomObjs: - altProp = roomObj.ootAlternateRoomHeaders - roomHeader = roomObj.ootRoomHeader - roomIndex = roomHeader.roomIndex - - if roomIndex in roomDict: - raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") - - roomName = f"{toAlnum(self.sceneName)}_room_{roomIndex}" - roomDict[roomIndex] = Room( - roomName, - self.transform, - self.sceneObj, - roomObj, - roomHeader.roomShape, - scene.model.addSubModel( - OOTModel( - scene.model.f3d.F3D_VER, - scene.model.f3d._HW_VERSION_1, - roomName + "_dl", - scene.model.DLFormat, - None, - ) - ), - roomIndex, - ) - - # Mesh stuff - pos, _, scale, _ = Base().getConvertedTransform(self.transform, self.sceneObj, roomObj, True) - cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) - DLGroup = roomDict[roomIndex].mesh.addMeshGroup(cullGroup).DLGroup - boundingBox = BoundingBox() - ootProcessMesh( - roomDict[roomIndex].mesh, - DLGroup, - self.sceneObj, - roomObj, - self.transform, - not self.saveTexturesAsPNG, - None, - boundingBox, - ) - - centroid, radius = boundingBox.getEnclosingSphere() - cullGroup.position = centroid - cullGroup.cullDepth = radius - - roomDict[roomIndex].mesh.terminateDLs() - roomDict[roomIndex].mesh.removeUnusedEntries() - - # Other - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomHeader.bgImageList) < 1: - raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') - - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomDict) > 1: - raise PluginError(f'Room shape "Image" can only have one room in the scene.') - - roomDict[roomIndex].roomShape = roomDict[roomIndex].getNewRoomShape(roomHeader, self.sceneName) - altHeaderData = RoomAlternateHeader(f"{roomDict[roomIndex].name}_alternateHeaders") - roomDict[roomIndex].mainHeader = roomDict[roomIndex].getNewRoomHeader(roomHeader) - hasAltHeader = False - - for i, header in enumerate(altHeaderList, 1): - altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") - if not altP.usePreviousHeader: - hasAltHeader = True - setattr(altHeaderData, header, roomDict[roomIndex].getNewRoomHeader(altP, i)) - - altHeaderData.cutscenes = [ - roomDict[roomIndex].getNewRoomHeader(csHeader, i) - for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) - ] - - if len(altHeaderData.cutscenes) > 0: - hasAltHeader = True - - roomDict[roomIndex].altHeader = altHeaderData if hasAltHeader else None - addMissingObjectsToAllRoomHeadersNew(roomObj, roomDict[roomIndex], ootData) - - return [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] - def getNewScene(self): """Returns and creates scene data""" # init @@ -171,29 +79,15 @@ def getNewScene(self): restoreHiddenState(hiddenState) try: - altProp = self.sceneObj.ootAlternateSceneHeaders - sceneData = Scene(self.sceneObj, self.transform, self.useMacros, f"{toAlnum(self.sceneName)}_scene") - sceneData.model = OOTModel(self.f3dType, self.isHWv1, f"{sceneData.name}_dl", self.dlFormat, False) - altHeaderData = SceneAlternateHeader(f"{sceneData.name}_alternateHeaders") - sceneData.mainHeader = sceneData.getNewSceneHeader(self.sceneObj.ootSceneHeader) - hasAltHeader = False - - for i, header in enumerate(altHeaderList, 1): - altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") - if not altP.usePreviousHeader: - setattr(altHeaderData, header, sceneData.getNewSceneHeader(altP, i)) - hasAltHeader = True - - altHeaderData.cutscenes = [ - sceneData.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) - ] - - if len(altHeaderData.cutscenes) > 0: - hasAltHeader = True - - sceneData.altHeader = altHeaderData if hasAltHeader else None - sceneData.roomList = self.getNewRoomList(sceneData) - sceneData.colHeader = sceneData.getNewCollisionHeader() + sceneName = f"{toAlnum(self.sceneName)}_scene" + sceneData = Scene( + self.sceneObj, + self.transform, + self.useMacros, + sceneName, + self.saveTexturesAsPNG, + OOTModel(self.f3dType, self.isHWv1, f"{sceneName}_dl", self.dlFormat, False), + ) sceneData.validateScene() if sceneData.mainHeader.cutscene is not None: @@ -241,7 +135,7 @@ def export(self): self.sceneFile.header += textureArrayData.header self.sceneFile.write() - for room in self.scene.roomList: + for room in self.scene.rooms.entries: room.mesh.copyBgImages(self.path) if not isCustomExport: diff --git a/fast64_internal/oot/exporter/other/file.py b/fast64_internal/oot/exporter/other/file.py index 8336101c7..803914a41 100644 --- a/fast64_internal/oot/exporter/other/file.py +++ b/fast64_internal/oot/exporter/other/file.py @@ -28,7 +28,7 @@ def modifySceneFiles(self): filepath = os.path.join(scenePath, filename) if os.path.isfile(filepath): match = re.match(self.exporter.scene.name + "\_room\_(\d+)\.[ch]", filename) - if match is not None and int(match.group(1)) >= len(self.exporter.scene.roomList): + if match is not None and int(match.group(1)) >= len(self.exporter.scene.rooms.entries): os.remove(filepath) def editFiles(self): diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 127518c77..6faabc9a4 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -1,11 +1,15 @@ from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object -from ....utility import CData, indent +from ....utility import PluginError, CData, indent from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ...room.properties import OOTRoomHeaderProperty +from ...oot_constants import ootData +from ...oot_object import addMissingObjectsToAllRoomHeadersNew from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter +from ...oot_level_writer import BoundingBox, ootProcessMesh +from ...oot_utility import CullGroup from ..classes import RoomFile from ..base import Base, altHeaderList from .header import RoomAlternateHeader, RoomHeader @@ -23,17 +27,75 @@ class Room(Base): roomShapeType: str model: OOTModel roomIndex: int + sceneName: str + saveTexturesAsPNG: bool - headerIndex: int = None mainHeader: RoomHeader = None altHeader: RoomAlternateHeader = None mesh: OOTRoomMesh = None roomShape: RoomShape = None hasAlternateHeaders: bool = False + def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): + """Returns a new room header with the informations from the scene empty object""" + + return RoomHeader( + f"{self.name}_header{headerIndex:02}", + headerProp, + self.sceneObj, + self.roomObj, + self.transform, + headerIndex, + ) + def __post_init__(self): + mainHeaderProps = self.roomObj.ootRoomHeader + altHeader = RoomAlternateHeader(f"{self.name}_alternateHeaders") + altProp = self.roomObj.ootAlternateRoomHeaders + + if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(mainHeaderProps.bgImageList) == 0: + raise PluginError(f'Room {self.roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') + + if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and self.roomIndex >= 1: + raise PluginError(f'Room shape "Image" can only have one room in the scene.') + self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) - self.hasAlternateHeaders = self.altHeader is not None + self.roomShape = RoomShape(self.roomShapeType, mainHeaderProps, self.mesh, self.sceneName, self.name) + self.mainHeader = self.getNewRoomHeader(mainHeaderProps) + self.hasAlternateHeaders = False + + for i, header in enumerate(altHeaderList, 1): + altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + self.hasAlternateHeaders = True + setattr(altHeader, header, self.getNewRoomHeader(altP, i)) + + altHeader.cutscenes = [ + self.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + self.hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else self.hasAlternateHeaders + self.altHeader = altHeader if self.hasAlternateHeaders else None + addMissingObjectsToAllRoomHeadersNew(self.roomObj, self, ootData) + + # Mesh stuff + pos, _, scale, _ = Base().getConvertedTransform(self.transform, self.sceneObj, self.roomObj, True) + cullGroup = CullGroup(pos, scale, self.roomObj.ootRoomHeader.defaultCullDistance) + DLGroup = self.mesh.addMeshGroup(cullGroup).DLGroup + boundingBox = BoundingBox() + ootProcessMesh( + self.mesh, + DLGroup, + self.sceneObj, + self.roomObj, + self.transform, + not self.saveTexturesAsPNG, + None, + boundingBox, + ) + cullGroup.position, cullGroup.cullDepth = boundingBox.getEnclosingSphere() + self.mesh.terminateDLs() + self.mesh.removeUnusedEntries() def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: """Returns the current room header based on the header index""" @@ -51,24 +113,6 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: return None - def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): - """Returns a new room header with the informations from the scene empty object""" - - self.headerIndex = headerIndex - return RoomHeader( - f"{self.name}_header{self.headerIndex:02}", - headerProp, - self.sceneObj, - self.roomObj, - self.transform, - self.headerIndex, - ) - - def getNewRoomShape(self, headerProp: OOTRoomHeaderProperty, sceneName: str): - """Returns a new room shape""" - - return RoomShape(self.roomShapeType, headerProp, self.mesh, sceneName, self.name) - def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): cmdListData = CData() listName = f"SceneCmd {curHeader.name}" diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 4b1a26411..6ef63c4cc 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -1,11 +1,216 @@ -from .header import ( - SceneInfos, - SceneHeader, - SceneLighting, - SceneCutscene, - SceneTransitionActors, - ScenePathways, - SceneAlternateHeader, -) - -from .main import Scene +from dataclasses import dataclass, field +from typing import TYPE_CHECKING +from mathutils import Matrix +from bpy.types import Object +from ....utility import PluginError, CData, indent +from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod +from ...scene.properties import OOTSceneHeaderProperty +from ...oot_model_classes import OOTModel, OOTGfxFormatter +from ..classes import SceneFile +from ..base import Base, altHeaderList +from ..collision import CollisionHeader +from .header import SceneAlternateHeader, SceneHeader +from .rooms import RoomEntries + +if TYPE_CHECKING: + from ..room import Room + + +@dataclass +class Scene(Base): + """This class defines a scene""" + + sceneObj: Object + transform: Matrix + useMacros: bool + name: str + saveTexturesAsPNG: bool + model: OOTModel + + mainHeader: SceneHeader = None + altHeader: SceneAlternateHeader = None + rooms: RoomEntries = None + colHeader: CollisionHeader = None + hasAlternateHeaders: bool = False + + def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): + """Returns a scene header""" + + return SceneHeader( + headerProp, f"{self.name}_header{headerIndex:02}", self.sceneObj, self.transform, headerIndex + ) + + def __post_init__(self): + self.rooms = RoomEntries(f"{self.name}_roomList", self, self.sceneObj, self.transform, self.saveTexturesAsPNG) + + self.colHeader = CollisionHeader( + self.sceneObj, + self.transform, + self.useMacros, + f"{self.name}_collisionHeader", + self.name, + ) + + self.mainHeader = self.getNewSceneHeader(self.sceneObj.ootSceneHeader) + self.hasAlternateHeaders = False + altHeader = SceneAlternateHeader(f"{self.name}_alternateHeaders") + altProp = self.sceneObj.ootAlternateSceneHeaders + + for i, header in enumerate(altHeaderList, 1): + altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + setattr(altHeader, header, self.getNewSceneHeader(altP, i)) + self.hasAlternateHeaders = True + + altHeader.cutscenes = [ + self.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + self.hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else self.hasAlternateHeaders + self.altHeader = altHeader if self.hasAlternateHeaders else None + + def validateRoomIndices(self): + """Checks if there are multiple rooms with the same room index""" + + for i, room in enumerate(self.rooms.entries): + if i != room.roomIndex: + return False + return True + + def validateScene(self): + """Performs safety checks related to the scene data""" + + if not len(self.rooms.entries) > 0: + raise PluginError("ERROR: This scene does not have any rooms!") + + if not self.validateRoomIndices(): + raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") + + def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: + """Returns the scene header based on the header index""" + + if headerIndex == 0: + return self.mainHeader + + for i, header in enumerate(altHeaderList, 1): + if headerIndex == i: + return getattr(self.altHeader, header) + + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): + if headerIndex == i: + return csHeader + + return None + + def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): + cmdListData = CData() + listName = f"SceneCmd {curHeader.name}" + + # .h + cmdListData.header = f"extern {listName}[]" + ";\n" + + # .c + cmdListData.source = ( + (f"{listName}[]" + " = {\n") + + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + self.colHeader.getCmd() + + self.rooms.getCmd() + + curHeader.infos.getCmds(curHeader.lighting) + + curHeader.lighting.getCmd() + + curHeader.path.getCmd() + + (curHeader.transitionActors.getCmd() if len(curHeader.transitionActors.entries) > 0 else "") + + curHeader.spawns.getCmd() + + curHeader.entranceActors.getCmd() + + (curHeader.exits.getCmd() if len(curHeader.exits.exitList) > 0 else "") + # + (curHeader.cutscene.getCmd() if curHeader.cutscene.writeCutscene else "") + + self.getEndCmd() + + "};\n\n" + ) + + return cmdListData + + def getSceneMainC(self): + """Returns the main informations of the scene as ``CData``""" + + sceneC = CData() + headers: list[tuple[SceneHeader, str]] = [] + altHeaderPtrs = None + + if self.hasAlternateHeaders: + headers = [ + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.altHeader.cutscenes): + headers.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrs = "\n".join( + indent + curHeader.name + "," if curHeader is not None else indent + "NULL," if i < 4 else "" + for i, (curHeader, _) in enumerate(headers, 1) + ) + + headers.insert(0, (self.mainHeader, "Child Day (Default)")) + for i, (curHeader, headerDesc) in enumerate(headers): + if curHeader is not None: + sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" + sceneC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) + + if i == 0: + if self.hasAlternateHeaders and altHeaderPtrs is not None: + altHeaderListName = f"SceneCmd* {self.altHeader.name}[]" + sceneC.header += f"extern {altHeaderListName};\n" + sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" + + # Write the room segment list + sceneC.append(self.rooms.getC(self.mainHeader.infos.useDummyRoomList)) + + sceneC.append(curHeader.getC()) + + return sceneC + + def getSceneCutscenesC(self): + """Returns the cutscene informations of the scene as ``CData`` (unfinished)""" + + # will be implemented when PR #208 is merged + csDataList: list[CData] = [] + return csDataList + + def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): + """ + Writes the textures and material setup displaylists that are shared between multiple rooms + (is written to the scene) + """ + + return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() + + def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): + """Gets and sets C data for every scene elements""" + + sceneMainData = self.getSceneMainC() + sceneCollisionData = self.colHeader.getC() + sceneCutsceneData = self.getSceneCutscenesC() + sceneTexturesData = self.getSceneTexturesC(textureExportSettings) + + return SceneFile( + self.name, + sceneMainData.source, + sceneCollisionData.source, + [cs.source for cs in sceneCutsceneData], + sceneTexturesData.source, + { + room.roomIndex: room.getNewRoomFile(path, isSingleFile, textureExportSettings) + for room in self.rooms.entries + }, + isSingleFile, + path, + ( + f"#ifndef {self.name.upper()}_H\n" + + f"#define {self.name.upper()}_H\n\n" + + sceneMainData.header + + "".join(cs.header for cs in sceneCutsceneData) + + sceneCollisionData.header + + sceneTexturesData.header + ), + ) diff --git a/fast64_internal/oot/exporter/scene/header/actors.py b/fast64_internal/oot/exporter/scene/actors.py similarity index 97% rename from fast64_internal/oot/exporter/scene/header/actors.py rename to fast64_internal/oot/exporter/scene/actors.py index 5d4579546..29705f33f 100644 --- a/fast64_internal/oot/exporter/scene/header/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -1,10 +1,10 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from .....utility import PluginError, CData, indent -from ....oot_constants import ootData -from ....scene.properties import OOTSceneHeaderProperty -from ...base import Base, Actor +from ....utility import PluginError, CData, indent +from ...oot_constants import ootData +from ...scene.properties import OOTSceneHeaderProperty +from ..base import Base, Actor @dataclass diff --git a/fast64_internal/oot/exporter/scene/header/cutscene.py b/fast64_internal/oot/exporter/scene/cutscene.py similarity index 93% rename from fast64_internal/oot/exporter/scene/header/cutscene.py rename to fast64_internal/oot/exporter/scene/cutscene.py index 266bdd752..e3c0e403d 100644 --- a/fast64_internal/oot/exporter/scene/header/cutscene.py +++ b/fast64_internal/oot/exporter/scene/cutscene.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field from bpy.types import Object -from .....utility import PluginError, CData, indent -from ....scene.properties import OOTSceneHeaderProperty -from ...base import Base +from ....utility import PluginError, CData, indent +from ...scene.properties import OOTSceneHeaderProperty +from ..base import Base @dataclass diff --git a/fast64_internal/oot/exporter/scene/header/general.py b/fast64_internal/oot/exporter/scene/general.py similarity index 97% rename from fast64_internal/oot/exporter/scene/header/general.py rename to fast64_internal/oot/exporter/scene/general.py index d8070f847..71fce4255 100644 --- a/fast64_internal/oot/exporter/scene/header/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field from bpy.types import Object -from .....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent -from ....scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ...base import Base +from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent +from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty +from ..base import Base @dataclass diff --git a/fast64_internal/oot/exporter/scene/header/__init__.py b/fast64_internal/oot/exporter/scene/header.py similarity index 96% rename from fast64_internal/oot/exporter/scene/header/__init__.py rename to fast64_internal/oot/exporter/scene/header.py index 9eb05c729..0a27ae1f7 100644 --- a/fast64_internal/oot/exporter/scene/header/__init__.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from .....utility import CData -from ....scene.properties import OOTSceneHeaderProperty -from ...base import Base +from ....utility import CData +from ...scene.properties import OOTSceneHeaderProperty +from ..base import Base from .general import SceneLighting, SceneInfos, SceneExits from .cutscene import SceneCutscene from .actors import SceneTransitionActors, SceneEntranceActors, SceneSpawns diff --git a/fast64_internal/oot/exporter/scene/main.py b/fast64_internal/oot/exporter/scene/main.py deleted file mode 100644 index b7647de66..000000000 --- a/fast64_internal/oot/exporter/scene/main.py +++ /dev/null @@ -1,241 +0,0 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING -from mathutils import Matrix -from bpy.types import Object -from ....utility import PluginError, CData, indent -from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod -from ...scene.properties import OOTSceneHeaderProperty -from ...oot_model_classes import OOTModel, OOTGfxFormatter -from ..classes import SceneFile -from ..base import Base, altHeaderList -from ..collision import CollisionHeader -from .header import SceneAlternateHeader, SceneHeader - -if TYPE_CHECKING: - from ..room import Room - - -@dataclass -class Scene(Base): - """This class defines a scene""" - - sceneObj: Object - transform: Matrix - useMacros: bool - name: str = None - model: OOTModel = None - headerIndex: int = None - mainHeader: SceneHeader = None - altHeader: SceneAlternateHeader = None - roomList: list["Room"] = field(default_factory=list) - roomListName: str = None - colHeader: CollisionHeader = None - hasAlternateHeaders: bool = False - - def __post_init__(self): - self.roomListName = f"{self.name}_roomList" - self.hasAlternateHeaders = self.altHeader is not None - - def validateRoomIndices(self): - """Checks if there are multiple rooms with the same room index""" - - for i, room in enumerate(self.roomList): - if i != room.roomIndex: - return False - return True - - def validateScene(self): - """Performs safety checks related to the scene data""" - - if not len(self.roomList) > 0: - raise PluginError("ERROR: This scene does not have any rooms!") - - if not self.validateRoomIndices(): - raise PluginError("ERROR: Room indices do not have a consecutive list of indices.") - - def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: - """Returns the scene header based on the header index""" - - if headerIndex == 0: - return self.mainHeader - - for i, header in enumerate(altHeaderList, 1): - if headerIndex == i: - return getattr(self.altHeader, header) - - for i, csHeader in enumerate(self.altHeader.cutscenes, 4): - if headerIndex == i: - return csHeader - - return None - - def getNewCollisionHeader(self): - """Returns and creates collision data""" - - return CollisionHeader( - self.sceneObj, - self.transform, - self.useMacros, - f"{self.name}_collisionHeader", - self.name, - ) - - def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): - """Returns the scene header""" - - self.headerIndex = headerIndex - return SceneHeader( - headerProp, f"{self.name}_header{self.headerIndex:02}", self.sceneObj, self.transform, headerIndex - ) - - def getRoomListCmd(self): - return indent + f"SCENE_CMD_ROOM_LIST({len(self.roomList)}, {self.roomListName}),\n" - - def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): - cmdListData = CData() - listName = f"SceneCmd {curHeader.name}" - - # .h - cmdListData.header = f"extern {listName}[]" + ";\n" - - # .c - cmdListData.source = ( - (f"{listName}[]" + " = {\n") - + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") - + self.colHeader.getCmd() - + self.getRoomListCmd() - + curHeader.infos.getCmds(curHeader.lighting) - + curHeader.lighting.getCmd() - + curHeader.path.getCmd() - + (curHeader.transitionActors.getCmd() if len(curHeader.transitionActors.entries) > 0 else "") - + curHeader.spawns.getCmd() - + curHeader.entranceActors.getCmd() - + (curHeader.exits.getCmd() if len(curHeader.exits.exitList) > 0 else "") - # + (curHeader.cutscene.getCmd() if curHeader.cutscene.writeCutscene else "") - + self.getEndCmd() - + "};\n\n" - ) - - return cmdListData - - def getRoomListC(self): - """Returns the ``CData`` containing the room list array""" - - roomList = CData() - listName = f"RomFile {self.roomListName}[]" - - # generating segment rom names for every room - segNames = [] - for i in range(len(self.roomList)): - roomName = self.roomList[i].name - segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) - - # .h - roomList.header += f"extern {listName};\n" - - if not self.mainHeader.infos.useDummyRoomList: - # Write externs for rom segments - roomList.header += "".join( - f"extern u8 {startName}[];\n" + f"extern u8 {stopName}[];\n" for startName, stopName in segNames - ) - - # .c - roomList.source = listName + " = {\n" - - if self.mainHeader.infos.useDummyRoomList: - roomList.source = ( - "// Dummy room list\n" + roomList.source + ((indent + "{ NULL, NULL },\n") * len(self.roomList)) - ) - else: - roomList.source += ( - " },\n".join( - indent + "{ " + f"(uintptr_t){startName}, (uintptr_t){stopName}" for startName, stopName in segNames - ) - + " },\n" - ) - - roomList.source += "};\n\n" - return roomList - - def getSceneMainC(self): - """Returns the main informations of the scene as ``CData``""" - - sceneC = CData() - headers: list[tuple[SceneHeader, str]] = [] - altHeaderPtrs = None - - if self.hasAlternateHeaders: - headers = [ - (self.altHeader.childNight, "Child Night"), - (self.altHeader.adultDay, "Adult Day"), - (self.altHeader.adultNight, "Adult Night"), - ] - - for i, csHeader in enumerate(self.altHeader.cutscenes): - headers.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrs = "\n".join( - indent + curHeader.name + "," if curHeader is not None else indent + "NULL," if i < 4 else "" - for i, (curHeader, _) in enumerate(headers, 1) - ) - - headers.insert(0, (self.mainHeader, "Child Day (Default)")) - for i, (curHeader, headerDesc) in enumerate(headers): - if curHeader is not None: - sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - sceneC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) - - if i == 0: - if self.hasAlternateHeaders and altHeaderPtrs is not None: - altHeaderListName = f"SceneCmd* {self.altHeader.name}[]" - sceneC.header += f"extern {altHeaderListName};\n" - sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" - - # Write the room segment list - sceneC.append(self.getRoomListC()) - - sceneC.append(curHeader.getC()) - - return sceneC - - def getSceneCutscenesC(self): - """Returns the cutscene informations of the scene as ``CData`` (unfinished)""" - - # will be implemented when PR #208 is merged - csDataList: list[CData] = [] - return csDataList - - def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): - """ - Writes the textures and material setup displaylists that are shared between multiple rooms - (is written to the scene) - """ - - return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() - - def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): - """Gets and sets C data for every scene elements""" - - sceneMainData = self.getSceneMainC() - sceneCollisionData = self.colHeader.getC() - sceneCutsceneData = self.getSceneCutscenesC() - sceneTexturesData = self.getSceneTexturesC(textureExportSettings) - - return SceneFile( - self.name, - sceneMainData.source, - sceneCollisionData.source, - [cs.source for cs in sceneCutsceneData], - sceneTexturesData.source, - {room.roomIndex: room.getNewRoomFile(path, isSingleFile, textureExportSettings) for room in self.roomList}, - isSingleFile, - path, - ( - f"#ifndef {self.name.upper()}_H\n" - + f"#define {self.name.upper()}_H\n\n" - + sceneMainData.header - + "".join(cs.header for cs in sceneCutsceneData) - + sceneCollisionData.header - + sceneTexturesData.header - ), - ) diff --git a/fast64_internal/oot/exporter/scene/header/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py similarity index 95% rename from fast64_internal/oot/exporter/scene/header/pathways.py rename to fast64_internal/oot/exporter/scene/pathways.py index 47bcbebe7..a39a0cdb2 100644 --- a/fast64_internal/oot/exporter/scene/header/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -1,9 +1,9 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from .....utility import CData, indent -from ....scene.properties import OOTSceneHeaderProperty -from ...base import Base +from ....utility import CData, indent +from ...scene.properties import OOTSceneHeaderProperty +from ..base import Base @dataclass diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py new file mode 100644 index 000000000..e6f40ecc5 --- /dev/null +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -0,0 +1,108 @@ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING +from mathutils import Matrix +from bpy.types import Object +from ....utility import PluginError, CData, indent +from ...oot_model_classes import OOTModel +from ...oot_level_writer import BoundingBox, ootProcessMesh +from ...oot_utility import CullGroup +from ..base import Base +from ..room import Room + +if TYPE_CHECKING: + from . import Scene + + +@dataclass +class RoomEntries: + name: str + scene: "Scene" + sceneObj: Object + transform: Matrix + saveTexturesAsPNG: bool + + entries: list[Room] = field(default_factory=list) + + def __post_init__(self): + """Returns the room list from empty objects with the type 'Room'""" + + sceneName = self.scene.name.removesuffix("_scene") + roomDict: dict[int, Room] = {} + roomObjs: list[Object] = [ + obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" + ] + + if len(roomObjs) == 0: + raise PluginError("ERROR: The scene has no child empties with the 'Room' empty type.") + + for roomObj in roomObjs: + roomHeader = roomObj.ootRoomHeader + roomIndex = roomHeader.roomIndex + + if roomIndex in roomDict: + raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") + + roomName = f"{sceneName}_room_{roomIndex}" + roomDict[roomIndex] = Room( + roomName, + self.transform, + self.sceneObj, + roomObj, + roomHeader.roomShape, + self.scene.model.addSubModel( + OOTModel( + self.scene.model.f3d.F3D_VER, + self.scene.model.f3d._HW_VERSION_1, + f"{roomName}_dl", + self.scene.model.DLFormat, + None, + ) + ), + roomIndex, + sceneName, + self.saveTexturesAsPNG, + ) + + self.entries = [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] + + def getCmd(self): + return indent + f"SCENE_CMD_ROOM_LIST({len(self.entries)}, {self.name}),\n" + + def getC(self, useDummyRoomList: bool): + """Returns the ``CData`` containing the room list array""" + + roomList = CData() + listName = f"RomFile {self.name}[]" + + # generating segment rom names for every room + segNames = [] + for i in range(len(self.entries)): + roomName = self.entries[i].name + segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) + + # .h + roomList.header += f"extern {listName};\n" + + if not useDummyRoomList: + # Write externs for rom segments + roomList.header += "".join( + f"extern u8 {startName}[];\n" + f"extern u8 {stopName}[];\n" for startName, stopName in segNames + ) + + # .c + roomList.source = listName + " = {\n" + + if useDummyRoomList: + roomList.source = ( + "// Dummy room list\n" + roomList.source + ((indent + "{ NULL, NULL },\n") * len(self.entries)) + ) + else: + roomList.source += ( + " },\n".join( + indent + "{ " + f"(uintptr_t){startName}, (uintptr_t){stopName}" for startName, stopName in segNames + ) + + " },\n" + ) + + roomList.source += "};\n\n" + return roomList From a05b16572c8a902286c5d4c0e149978f5ed24209 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:07:27 +0200 Subject: [PATCH 51/98] import cleanup --- fast64_internal/oot/exporter/main.py | 12 +++--------- fast64_internal/oot/exporter/room/__init__.py | 2 +- fast64_internal/oot/exporter/scene/__init__.py | 6 +----- fast64_internal/oot/exporter/scene/rooms.py | 3 --- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 2559417aa..98b071186 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -5,18 +5,12 @@ from mathutils import Matrix from bpy.types import Object from ...f3d.f3d_gbi import DLFormat, TextureExportSettings -from ..scene.properties import OOTBootupSceneOptions, OOTSceneHeaderProperty +from ..scene.properties import OOTBootupSceneOptions from ..scene.exporter.to_c import setBootupScene -from ..room.properties import OOTRoomHeaderProperty -from ..oot_constants import ootData -from ..oot_object import addMissingObjectsToAllRoomHeadersNew from ..oot_model_classes import OOTModel from ..oot_f3d_writer import writeTextureArraysNew -from ..oot_level_writer import BoundingBox, writeTextureArraysExistingScene, ootProcessMesh -from ..oot_utility import CullGroup -from .base import Base, altHeaderList -from .scene import Scene, SceneAlternateHeader -from .room import Room, RoomAlternateHeader +from ..oot_level_writer import writeTextureArraysExistingScene +from .scene import Scene from .other import Files from .classes import SceneFile diff --git a/fast64_internal/oot/exporter/room/__init__.py b/fast64_internal/oot/exporter/room/__init__.py index 19c760661..db91d7961 100644 --- a/fast64_internal/oot/exporter/room/__init__.py +++ b/fast64_internal/oot/exporter/room/__init__.py @@ -1,2 +1,2 @@ -from .header import RoomAlternateHeader, RoomInfos, RoomObjects, RoomActors +from .header import RoomAlternateHeader from .main import Room diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 6ef63c4cc..7675dbf24 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING +from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent @@ -12,9 +11,6 @@ from .header import SceneAlternateHeader, SceneHeader from .rooms import RoomEntries -if TYPE_CHECKING: - from ..room import Room - @dataclass class Scene(Base): diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index e6f40ecc5..709d0106b 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -4,9 +4,6 @@ from bpy.types import Object from ....utility import PluginError, CData, indent from ...oot_model_classes import OOTModel -from ...oot_level_writer import BoundingBox, ootProcessMesh -from ...oot_utility import CullGroup -from ..base import Base from ..room import Room if TYPE_CHECKING: From d2ae64fd2c7fe68a7f348051cf069b9aa73b5f15 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:51:39 +0200 Subject: [PATCH 52/98] fixed DL entry issue --- fast64_internal/oot/exporter/room/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 6faabc9a4..43360789b 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -59,8 +59,6 @@ def __post_init__(self): if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and self.roomIndex >= 1: raise PluginError(f'Room shape "Image" can only have one room in the scene.') - self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) - self.roomShape = RoomShape(self.roomShapeType, mainHeaderProps, self.mesh, self.sceneName, self.name) self.mainHeader = self.getNewRoomHeader(mainHeaderProps) self.hasAlternateHeaders = False @@ -79,6 +77,7 @@ def __post_init__(self): addMissingObjectsToAllRoomHeadersNew(self.roomObj, self, ootData) # Mesh stuff + self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) pos, _, scale, _ = Base().getConvertedTransform(self.transform, self.sceneObj, self.roomObj, True) cullGroup = CullGroup(pos, scale, self.roomObj.ootRoomHeader.defaultCullDistance) DLGroup = self.mesh.addMeshGroup(cullGroup).DLGroup @@ -96,6 +95,7 @@ def __post_init__(self): cullGroup.position, cullGroup.cullDepth = boundingBox.getEnclosingSphere() self.mesh.terminateDLs() self.mesh.removeUnusedEntries() + self.roomShape = RoomShape(self.roomShapeType, mainHeaderProps, self.mesh, self.sceneName, self.name) def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: """Returns the current room header based on the header index""" From b69223670eea1b1df33ff551e6a2e04e4b322a1a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:34:37 +0200 Subject: [PATCH 53/98] include fix --- fast64_internal/oot/exporter/classes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 76a405aa2..06b11a9e8 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -62,10 +62,7 @@ def setIncludeData(self): includes = ( "\n".join( [ - '#include "ultra64/ultratypes.h"', - '#include "ultra64/gbi.h"', - '#include "libc/stddef.h"', - '#include "libc/stdint.h"', + '#include "ultra64.h"', '#include "macros.h"', '#include "z64.h"', ] From 3abdf59114e30dbb714d0f02068cd049a9b4316d Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:07:22 +0200 Subject: [PATCH 54/98] path indices --- .../oot/exporter/scene/pathways.py | 26 +++++++++++++------ fast64_internal/oot/spline/properties.py | 5 +++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index a39a0cdb2..fc1277b54 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object -from ....utility import CData, indent +from ....utility import PluginError, CData, indent from ...scene.properties import OOTSceneHeaderProperty from ..base import Base @@ -48,21 +48,31 @@ class ScenePathways(Base): pathList: list[Path] = field(default_factory=list) def __post_init__(self): + pathFromIndex: dict[int, Path] = {} pathObjList: list[Object] = [ obj for obj in self.sceneObj.children_recursive if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" ] - for i, obj in enumerate(pathObjList): - isHeaderValid = self.isCurrentHeaderValid(obj.ootSplineProperty.headerSettings, self.headerIndex) + for obj in pathObjList: + relativeTransform = self.transform @ self.sceneObj.matrix_world.inverted() @ obj.matrix_world + pathProps = obj.ootSplineProperty + isHeaderValid = self.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) if isHeaderValid and self.validateCurveData(obj): - self.pathList.append( - Path( - f"{self.name}List{i:02}", - [self.transform @ point.co.xyz for point in obj.data.splines[0].points], + if not pathProps.index in pathFromIndex: + pathFromIndex[pathProps.index] = Path( + f"{self.name}List{pathProps.index:02}", + [relativeTransform @ point.co.xyz for point in obj.data.splines[0].points], ) - ) + else: + raise PluginError(f"ERROR: Path index already used ({obj.name})") + + pathFromIndex = dict(sorted(pathFromIndex.items())) + if list(pathFromIndex.keys()) != list(range(len(pathFromIndex))): + raise PluginError("ERROR: Path indices are not consecutive!") + + self.pathList = list(pathFromIndex.values()) def getCmd(self): return indent + f"SCENE_CMD_PATH_LIST({self.name}),\n" if len(self.pathList) > 0 else "" diff --git a/fast64_internal/oot/spline/properties.py b/fast64_internal/oot/spline/properties.py index 9198866f2..2893240db 100644 --- a/fast64_internal/oot/spline/properties.py +++ b/fast64_internal/oot/spline/properties.py @@ -19,14 +19,17 @@ class OOTSplineProperty(PropertyGroup): camSTypeCustom: StringProperty(default="CAM_SET_CRAWLSPACE") def draw_props(self, layout: UILayout, altSceneProp: OOTAlternateSceneHeaderProperty, objName: str): + camIndexName = "" prop_split(layout, self, "splineType", "Type") if self.splineType == "Path": headerProp: OOTActorHeaderProperty = self.headerSettings headerProp.draw_props(layout, "Curve", altSceneProp, objName) + camIndexName = "Path Index" elif self.splineType == "Crawlspace": layout.label(text="This counts as a camera for index purposes.", icon="INFO") - prop_split(layout, self, "index", "Index") drawEnumWithCustom(layout, self, "camSType", "Camera S Type", "") + camIndexName = "Camera Index" + prop_split(layout, self, "index", camIndexName) oot_spline_classes = (OOTSplineProperty,) From fb98e8ccd967d5371442cc106d255a2b873da75f Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:29:50 +0200 Subject: [PATCH 55/98] single file fixes --- fast64_internal/oot/exporter/classes.py | 35 +++++++------------ fast64_internal/oot/exporter/other/spec.py | 4 ++- .../oot/exporter/scene/__init__.py | 12 +++++++ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 06b11a9e8..b77bae2f0 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -50,41 +50,32 @@ def __post_init__(self): self.hasCutscenes = len(self.sceneCutscenes) > 0 self.hasSceneTextures = len(self.sceneTextures) > 0 - def getSourceWithSceneInclude(self, sceneInclude: str, source: str, includeSrc: str): + def getSourceWithSceneInclude(self, sceneInclude: str, source: str): + ret = "" if not sceneInclude in source: - includeSrc += sceneInclude - return includeSrc + source + ret = sceneInclude + return ret + source def setIncludeData(self): """Adds includes at the beginning of each file to write""" - sceneInclude = f'\n#include "{self.name}.h"\n\n\n' - includes = ( - "\n".join( - [ - '#include "ultra64.h"', - '#include "macros.h"', - '#include "z64.h"', - ] - ) - + "\n" - ) + sceneInclude = f'#include "{self.name}.h"\n\n\n' for roomData in self.roomList.values(): - roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain, includes) + roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain) if not self.singleFileExport: - roomData.roomModelInfo = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModelInfo, includes) - roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel, includes) + roomData.roomModelInfo = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModelInfo) + roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel) - self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain, includes) - if not self.singleFileExport: - self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision, includes) - self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures, includes) + self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain) + self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) + if not self.singleFileExport: + self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) if self.hasCutscenes: for cs in self.sceneCutscenes: - cs = self.getSourceWithSceneInclude(sceneInclude, cs, includes) + cs = self.getSourceWithSceneInclude(sceneInclude, cs) def write(self): self.setIncludeData() diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py index 47e47402e..ed981bacd 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/other/spec.py @@ -76,6 +76,7 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" sceneName = exporter.scene.name + sceneTexturesSeg = indent + f'include "{includeDir}/{sceneName}_tex.o"\n' if exporter.isSingleFile: specEntries.insert( firstIndex, @@ -83,6 +84,7 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + compressFlag + (indent + "romalign 0x1000\n") + (indent + f'include "{includeDir}/{sceneName}.o"\n') + + sceneTexturesSeg + (indent + "number 2\n"), ) @@ -109,7 +111,7 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): ) if exporter.hasSceneTextures: - sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_tex.o"\n' + sceneSegInclude += sceneTexturesSeg if exporter.hasCutscenes: for i in range(len(exporter.sceneFile.sceneCutscenes)): diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 7675dbf24..6db43de0a 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -189,6 +189,17 @@ def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: sceneCutsceneData = self.getSceneCutscenesC() sceneTexturesData = self.getSceneTexturesC(textureExportSettings) + includes = ( + "\n".join( + [ + '#include "ultra64.h"', + '#include "macros.h"', + '#include "z64.h"', + ] + ) + + "\n\n\n" + ) + return SceneFile( self.name, sceneMainData.source, @@ -204,6 +215,7 @@ def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: ( f"#ifndef {self.name.upper()}_H\n" + f"#define {self.name.upper()}_H\n\n" + + includes + sceneMainData.header + "".join(cs.header for cs in sceneCutsceneData) + sceneCollisionData.header From cdbc8a92f0811f32acb30aa4114b144fe89f89f0 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 7 Jan 2024 20:46:18 +0100 Subject: [PATCH 56/98] remove hwv1 & f3d_type + ``Optional`` when needed --- fast64_internal/oot/exporter/base.py | 9 ++-- fast64_internal/oot/exporter/classes.py | 17 +++--- .../oot/exporter/collision/polygons.py | 3 +- .../oot/exporter/collision/surface.py | 9 ++-- .../oot/exporter/collision/waterbox.py | 17 +++--- fast64_internal/oot/exporter/main.py | 13 +++-- fast64_internal/oot/exporter/room/header.py | 53 ++++++++++--------- fast64_internal/oot/exporter/room/main.py | 9 ++-- fast64_internal/oot/exporter/room/shape.py | 11 ++-- fast64_internal/oot/exporter/scene/actors.py | 15 +++--- .../oot/exporter/scene/cutscene.py | 11 ++-- fast64_internal/oot/exporter/scene/general.py | 29 +++++----- fast64_internal/oot/exporter/scene/header.py | 23 ++++---- fast64_internal/oot/scene/operators.py | 2 - 14 files changed, 115 insertions(+), 106 deletions(-) diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/base.py index bfd510ef3..b24b303cb 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/base.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from math import radians from mathutils import Quaternion, Matrix from bpy.types import Object @@ -104,11 +105,11 @@ def getEndCmd(self): class Actor: """Defines an Actor""" - name: str = None - id: str = None + name: Optional[str] = None + id: Optional[str] = None pos: list[int] = field(default_factory=list) - rot: str = None - params: str = None + rot: Optional[str] = None + params: Optional[str] = None def getActorEntry(self): """Returns a single actor entry""" diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index b77bae2f0..9484163ef 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -1,6 +1,7 @@ import os from dataclasses import dataclass, field +from typing import Optional from ...utility import writeFile @@ -9,11 +10,11 @@ class RoomFile: """This class hosts the C data for every room files""" name: str - roomMain: str = None - roomModel: str = None - roomModelInfo: str = None + roomMain: Optional[str] = None + roomModel: Optional[str] = None + roomModelInfo: Optional[str] = None singleFileExport: bool = False - path: str = None + path: Optional[str] = None header: str = "" @@ -34,13 +35,13 @@ class SceneFile: """This class hosts the C data for every scene files""" name: str - sceneMain: str = None - sceneCollision: str = None + sceneMain: Optional[str] = None + sceneCollision: Optional[str] = None sceneCutscenes: list[str] = field(default_factory=list) - sceneTextures: str = None + sceneTextures: Optional[str] = None roomList: dict[int, RoomFile] = field(default_factory=dict) singleFileExport: bool = False - path: str = None + path: Optional[str] = None header: str = "" hasCutscenes: bool = False diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index a6c5c9117..3c8b65702 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional from mathutils import Vector from ....utility import PluginError, CData, indent @@ -15,7 +16,7 @@ class CollisionPoly: normal: Vector dist: int useMacros: bool - type: int = None + type: Optional[int] = None def getFlags_vIA(self): """Returns the value of ``flags_vIA``""" diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index 20ceffcf7..99be6a518 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional from ....utility import CData, indent @@ -26,10 +27,10 @@ class SurfaceType: conveyorKeepMomentum: bool useMacros: bool - isSoftC: str = None - isHorseBlockedC: str = None - canHookshotC: str = None - isWallDamageC: str = None + isSoftC: Optional[str] = None + isHorseBlockedC: Optional[str] = None + canHookshotC: Optional[str] = None + isWallDamageC: Optional[str] = None def __hash__(self): return hash( diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index d448aff68..13c99ec56 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import CData, checkIdentityRotation, indent @@ -20,14 +21,14 @@ class WaterBox: setFlag19: bool useMacros: bool - xMin: int = None - ySurface: int = None - zMin: int = None - xLength: int = None - zLength: int = None - - setFlag19C: str = None - roomIndexC: str = None + xMin: Optional[int] = None + ySurface: Optional[int] = None + zMin: Optional[int] = None + xLength: Optional[int] = None + zLength: Optional[int] = None + + setFlag19C: Optional[str] = None + roomIndexC: Optional[str] = None def __post_init__(self): self.setFlag19C = "1" if self.setFlag19 else "0" diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 98b071186..64d883211 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object +from typing import Optional from ...f3d.f3d_gbi import DLFormat, TextureExportSettings from ..scene.properties import OOTBootupSceneOptions from ..scene.exporter.to_c import setBootupScene @@ -41,19 +42,17 @@ class SceneExporter: sceneName: str ootBlenderScale: float transform: Matrix - f3dType: str saveTexturesAsPNG: bool hackerootBootOption: OOTBootupSceneOptions isSingleFile: bool - isHWv1: bool textureExportSettings: TextureExportSettings useMacros: bool dlFormat: DLFormat = DLFormat.Static - sceneObj: Object = None - scene: Scene = None - path: str = None - sceneFile: SceneFile = None + sceneObj: Optional[Object] = None + scene: Optional[Scene] = None + path: Optional[str] = None + sceneFile: Optional[SceneFile] = None hasCutscenes: bool = False hasSceneTextures: bool = False @@ -80,7 +79,7 @@ def getNewScene(self): self.useMacros, sceneName, self.saveTexturesAsPNG, - OOTModel(self.f3dType, self.isHWv1, f"{sceneName}_dl", self.dlFormat, False), + OOTModel(f"{sceneName}_dl", self.dlFormat, False), ) sceneData.validateScene() diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index dbb82c91a..2e08e4ec2 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import CData, indent @@ -9,12 +10,12 @@ @dataclass class HeaderBase(Base): - name: str = None - props: OOTRoomHeaderProperty = None - sceneObj: Object = None - roomObj: Object = None - transform: Matrix = None - headerIndex: int = None + name: Optional[str] = None + props: Optional[OOTRoomHeaderProperty] = None + sceneObj: Optional[Object] = None + roomObj: Optional[Object] = None + transform: Optional[Matrix] = None + headerIndex: Optional[int] = None @dataclass @@ -23,30 +24,30 @@ class RoomInfos(HeaderBase): ### General ### - index: int = None - roomShape: str = None + index: Optional[int] = None + roomShape: Optional[str] = None ### Behavior ### - roomBehavior: str = None - playerIdleType: str = None - disableWarpSongs: bool = None - showInvisActors: bool = None + roomBehavior: Optional[str] = None + playerIdleType: Optional[str] = None + disableWarpSongs: Optional[bool] = None + showInvisActors: Optional[bool] = None ### Skybox And Time ### - disableSky: bool = None - disableSunMoon: bool = None - hour: int = None - minute: int = None - timeSpeed: float = None - echo: str = None + disableSky: Optional[bool] = None + disableSunMoon: Optional[bool] = None + hour: Optional[int] = None + minute: Optional[int] = None + timeSpeed: Optional[float] = None + echo: Optional[str] = None ### Wind ### - setWind: bool = None + setWind: Optional[bool] = None direction: tuple[int, int, int] = None - strength: int = None + strength: Optional[int] = None def __post_init__(self): self.index = self.props.roomIndex @@ -203,9 +204,9 @@ def getC(self): class RoomHeader(HeaderBase): """This class defines a room header""" - infos: RoomInfos = None - objects: RoomObjects = None - actors: RoomActors = None + infos: Optional[RoomInfos] = None + objects: Optional[RoomObjects] = None + actors: Optional[RoomActors] = None def __post_init__(self): self.infos = RoomInfos(None, self.props) @@ -235,7 +236,7 @@ class RoomAlternateHeader: """This class stores alternate header data""" name: str - childNight: RoomHeader = None - adultDay: RoomHeader = None - adultNight: RoomHeader = None + childNight: Optional[RoomHeader] = None + adultDay: Optional[RoomHeader] = None + adultNight: Optional[RoomHeader] = None cutscenes: list[RoomHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 43360789b..27affd327 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent @@ -30,10 +31,10 @@ class Room(Base): sceneName: str saveTexturesAsPNG: bool - mainHeader: RoomHeader = None - altHeader: RoomAlternateHeader = None - mesh: OOTRoomMesh = None - roomShape: RoomShape = None + mainHeader: Optional[RoomHeader] = None + altHeader: Optional[RoomAlternateHeader] = None + mesh: Optional[OOTRoomMesh] = None + roomShape: Optional[RoomShape] = None hasAlternateHeaders: bool = False def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index f61723ea1..aec29ff64 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from ....utility import PluginError, CData, toAlnum, indent from ....f3d.f3d_gbi import TextureExportSettings from ...oot_level_classes import OOTRoomMesh @@ -242,11 +243,11 @@ class RoomShape(RoomShapeBase): sceneName: str roomName: str - dl: RoomShapeDLists = None - normal: RoomShapeNormal = None - single: RoomShapeImageSingle = None - multiImg: RoomShapeImageMultiBg = None - multi: RoomShapeImageMulti = None + dl: Optional[RoomShapeDLists] = None + normal: Optional[RoomShapeNormal] = None + single: Optional[RoomShapeImageSingle] = None + multiImg: Optional[RoomShapeImageMultiBg] = None + multi: Optional[RoomShapeImageMulti] = None def __post_init__(self): name = f"{self.roomName}_shapeHeader" diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 29705f33f..79d76ecec 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent @@ -11,11 +12,11 @@ class TransitionActor(Actor): """Defines a Transition Actor""" - dontTransition: bool = None - roomFrom: int = None - roomTo: int = None - cameraFront: str = None - cameraBack: str = None + dontTransition: Optional[bool] = None + roomFrom: Optional[int] = None + roomTo: Optional[int] = None + cameraFront: Optional[str] = None + cameraBack: Optional[str] = None def getEntryC(self): """Returns a single transition actor entry""" @@ -117,8 +118,8 @@ def getC(self): class EntranceActor(Actor): """Defines an Entrance Actor""" - roomIndex: int = None - spawnIndex: int = None + roomIndex: Optional[int] = None + spawnIndex: Optional[int] = None def getEntryC(self): """Returns a single spawn entry""" diff --git a/fast64_internal/oot/exporter/scene/cutscene.py b/fast64_internal/oot/exporter/scene/cutscene.py index e3c0e403d..e5adc3ffe 100644 --- a/fast64_internal/oot/exporter/scene/cutscene.py +++ b/fast64_internal/oot/exporter/scene/cutscene.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from bpy.types import Object from ....utility import PluginError, CData, indent from ...scene.properties import OOTSceneHeaderProperty @@ -12,12 +13,12 @@ class SceneCutscene(Base): props: OOTSceneHeaderProperty headerIndex: int - writeType: str = None - writeCutscene: bool = None - csObj: Object = None - csWriteCustom: str = None + writeType: Optional[str] = None + writeCutscene: Optional[bool] = None + csObj: Optional[Object] = None + csWriteCustom: Optional[str] = None extraCutscenes: list[Object] = field(default_factory=list) - name: str = None + name: Optional[str] = None def __post_init__(self): self.writeType = self.props.csWriteType diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index 71fce4255..5386f12e3 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from bpy.types import Object from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty @@ -57,7 +58,7 @@ def getEntryC(self, index: int): lightDescs = ["Dawn", "Day", "Dusk", "Night"] if not isLightingCustom and self.envLightMode == "LIGHT_MODE_TIME": - # @TODO: Improve the lighting system. + # TODO: Improve the lighting system. # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. # This is not accurate and more complicated, # for now we are doing ``index % 4`` to avoid having an OoB read in the list @@ -87,7 +88,7 @@ class SceneLighting(Base): props: OOTSceneHeaderProperty name: str - envLightMode: str = None + envLightMode: Optional[str] = None settings: list[EnvLightSettings] = field(default_factory=list) def __post_init__(self): @@ -149,30 +150,30 @@ class SceneInfos(Base): ### General ### - keepObjectID: str = None - naviHintType: str = None - drawConfig: str = None - appendNullEntrance: bool = None - useDummyRoomList: bool = None + keepObjectID: Optional[str] = None + naviHintType: Optional[str] = None + drawConfig: Optional[str] = None + appendNullEntrance: Optional[bool] = None + useDummyRoomList: Optional[bool] = None ### Skybox And Sound ### # Skybox - skyboxID: str = None - skyboxConfig: str = None + skyboxID: Optional[str] = None + skyboxConfig: Optional[str] = None # Sound - sequenceID: str = None - ambienceID: str = None - specID: str = None + sequenceID: Optional[str] = None + ambienceID: Optional[str] = None + specID: Optional[str] = None ### Camera And World Map ### # World Map - worldMapLocation: str = None + worldMapLocation: Optional[str] = None # Camera - sceneCamType: str = None + sceneCamType: Optional[str] = None def __post_init__(self): self.keepObjectID = self.getPropValue(self.props, "globalObject") diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index 0a27ae1f7..da763e27c 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import CData @@ -20,14 +21,14 @@ class SceneHeader(Base): transform: Matrix headerIndex: int - infos: SceneInfos = None - lighting: SceneLighting = None - cutscene: SceneCutscene = None - exits: SceneExits = None - transitionActors: SceneTransitionActors = None - entranceActors: SceneEntranceActors = None - spawns: SceneSpawns = None - path: ScenePathways = None + infos: Optional[SceneInfos] = None + lighting: Optional[SceneLighting] = None + cutscene: Optional[SceneCutscene] = None + exits: Optional[SceneExits] = None + transitionActors: Optional[SceneTransitionActors] = None + entranceActors: Optional[SceneEntranceActors] = None + spawns: Optional[SceneSpawns] = None + path: Optional[ScenePathways] = None def __post_init__(self): self.infos = SceneInfos(self.props, self.sceneObj) @@ -83,7 +84,7 @@ class SceneAlternateHeader: """This class stores alternate header data for the scene""" name: str - childNight: SceneHeader = None - adultDay: SceneHeader = None - adultNight: SceneHeader = None + childNight: Optional[SceneHeader] = None + adultDay: Optional[SceneHeader] = None + adultNight: Optional[SceneHeader] = None cutscenes: list[SceneHeader] = field(default_factory=list) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 916bba958..881b5681f 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -180,11 +180,9 @@ def execute(self, context): exportInfo.name, context.scene.ootBlenderScale, finalTransform, - bpy.context.scene.f3d_type, bpy.context.scene.saveTextures, bootOptions if hackerFeaturesEnabled else None, settings.singleFile, - context.scene.isHWv1, TextureExportSettings(False, context.scene.saveTextures, None, None), settings.useMacros, ).export() From 7e93da19d5dc005326ffac61c032bef484a9601c Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 7 Jan 2024 20:50:49 +0100 Subject: [PATCH 57/98] not x in y -> x not in y --- fast64_internal/oot/exporter/classes.py | 2 +- fast64_internal/oot/exporter/collision/__init__.py | 2 +- fast64_internal/oot/exporter/collision/camera.py | 4 ++-- fast64_internal/oot/exporter/other/scene_table.py | 2 +- fast64_internal/oot/exporter/scene/actors.py | 2 +- fast64_internal/oot/exporter/scene/pathways.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 9484163ef..05fe8b178 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -53,7 +53,7 @@ def __post_init__(self): def getSourceWithSceneInclude(self, sceneInclude: str, source: str): ret = "" - if not sceneInclude in source: + if sceneInclude not in source: ret = sceneInclude return ret + source diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index cca2f2d7d..d73214cfc 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -157,7 +157,7 @@ def getCollisionData(self): self.useMacros, ) - if not surfaceType in colPolyFromSurfaceType: + if surfaceType not in colPolyFromSurfaceType: colPolyFromSurfaceType[surfaceType] = [] colPolyFromSurfaceType[surfaceType].append( diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 1558eacb5..2a3f4381f 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -151,13 +151,13 @@ def initBgCamInfoList(self): def initCamTable(self): for bgCam in self.bgCamInfoList: - if not bgCam.camIndex in self.camFromIndex: + if bgCam.camIndex not in self.camFromIndex: self.camFromIndex[bgCam.camIndex] = bgCam else: raise PluginError(f"ERROR (CameraInfo): Camera index already used: {bgCam.camIndex}") for crawlCam in self.crawlspacePosList: - if not crawlCam.camIndex in self.camFromIndex: + if crawlCam.camIndex not in self.camFromIndex: self.camFromIndex[crawlCam.camIndex] = crawlCam else: raise PluginError(f"ERROR (Crawlspace): Camera index already used: {crawlCam.camIndex}") diff --git a/fast64_internal/oot/exporter/other/scene_table.py b/fast64_internal/oot/exporter/other/scene_table.py index a0642e5a2..0a34862cb 100644 --- a/fast64_internal/oot/exporter/other/scene_table.py +++ b/fast64_internal/oot/exporter/other/scene_table.py @@ -213,7 +213,7 @@ def addHackerOoTData(self, fileData: str): newFileData.append(f"{line}\n") - if not "// Added scenes" in fileData: + if "// Added scenes" not in fileData: newFileData.append("#endif\n") return "".join(newFileData) diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 79d76ecec..5a6ac5309 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -172,7 +172,7 @@ def __post_init__(self): entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex entranceActor.spawnIndex = entranceProp.spawnIndex - if not entranceProp.spawnIndex in entranceActorFromIndex: + if entranceProp.spawnIndex not in entranceActorFromIndex: entranceActorFromIndex[entranceProp.spawnIndex] = entranceActor else: raise PluginError(f"ERROR: Repeated Spawn Index: {entranceProp.spawnIndex}") diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index fc1277b54..498c81d16 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -60,7 +60,7 @@ def __post_init__(self): pathProps = obj.ootSplineProperty isHeaderValid = self.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) if isHeaderValid and self.validateCurveData(obj): - if not pathProps.index in pathFromIndex: + if pathProps.index not in pathFromIndex: pathFromIndex[pathProps.index] = Path( f"{self.name}List{pathProps.index:02}", [relativeTransform @ point.co.xyz for point in obj.data.splines[0].points], From 0bc1c93d8e149b210a04e872ce0f3b47d9379435 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 8 Jan 2024 00:28:44 +0100 Subject: [PATCH 58/98] fixes and cutscene support --- fast64_internal/oot/exporter/classes.py | 5 +- fast64_internal/oot/exporter/main.py | 4 +- .../oot/exporter/scene/__init__.py | 24 +- fast64_internal/oot/exporter/scene/actors.py | 20 +- .../oot/exporter/scene/cutscene.py | 542 +++++++++++++++++- fast64_internal/oot/exporter/scene/header.py | 6 +- fast64_internal/oot/exporter/scene/rooms.py | 2 - fast64_internal/oot/oot_upgrade.py | 2 + 8 files changed, 554 insertions(+), 51 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 05fe8b178..c00c77ac2 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -75,8 +75,9 @@ def setIncludeData(self): if not self.singleFileExport: self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) if self.hasCutscenes: - for cs in self.sceneCutscenes: - cs = self.getSourceWithSceneInclude(sceneInclude, cs) + csInclude = sceneInclude[:-2] + '#include "z64cutscene.h"\n' + '#include "z64cutscene_commands.h"\n\n\n' + for i in range(len(self.sceneCutscenes)): + self.sceneCutscenes[i] = self.getSourceWithSceneInclude(csInclude, self.sceneCutscenes[i]) def write(self): self.setIncludeData() diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 64d883211..bb597c48f 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -84,11 +84,11 @@ def getNewScene(self): sceneData.validateScene() if sceneData.mainHeader.cutscene is not None: - self.hasCutscenes = sceneData.mainHeader.cutscene.writeCutscene + self.hasCutscenes = len(sceneData.mainHeader.cutscene.entries) > 0 if not self.hasCutscenes: for cs in sceneData.altHeader.cutscenes: - if cs.cutscene.writeCutscene: + if len(cs.cutscene.entries) > 0: self.hasCutscenes = True break diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 6db43de0a..c401ddd4c 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -33,7 +33,12 @@ def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int """Returns a scene header""" return SceneHeader( - headerProp, f"{self.name}_header{headerIndex:02}", self.sceneObj, self.transform, headerIndex + headerProp, + f"{self.name}_header{headerIndex:02}", + self.sceneObj, + self.transform, + headerIndex, + self.useMacros, ) def __post_init__(self): @@ -118,7 +123,7 @@ def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): + curHeader.spawns.getCmd() + curHeader.entranceActors.getCmd() + (curHeader.exits.getCmd() if len(curHeader.exits.exitList) > 0 else "") - # + (curHeader.cutscene.getCmd() if curHeader.cutscene.writeCutscene else "") + + (curHeader.cutscene.getCmd() if len(curHeader.cutscene.entries) > 0 else "") + self.getEndCmd() + "};\n\n" ) @@ -167,10 +172,21 @@ def getSceneMainC(self): return sceneC def getSceneCutscenesC(self): - """Returns the cutscene informations of the scene as ``CData`` (unfinished)""" + """Returns the cutscene informations of the scene as ``CData``""" - # will be implemented when PR #208 is merged csDataList: list[CData] = [] + headers: list[SceneHeader] = [ + self.mainHeader, + self.altHeader.childNight, + self.altHeader.adultDay, + self.altHeader.adultNight, + ] + + for curHeader in headers: + if curHeader is not None: + for csEntry in curHeader.cutscene.entries: + csDataList.append(csEntry.getC()) + return csDataList def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 5a6ac5309..0a58b6845 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -12,7 +12,7 @@ class TransitionActor(Actor): """Defines a Transition Actor""" - dontTransition: Optional[bool] = None + isRoomTransition: Optional[bool] = None roomFrom: Optional[int] = None roomTo: Optional[int] = None cameraFront: Optional[str] = None @@ -67,12 +67,15 @@ def __post_init__(self): pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) transActor = TransitionActor() - if transActorProp.dontTransition: - front = (255, self.getPropValue(transActorProp, "cameraTransitionBack")) - back = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + if transActorProp.isRoomTransition: + if transActorProp.fromRoom is None or transActorProp.toRoom is None: + raise PluginError("ERROR: Missing room empty object assigned to transition.") + fromIndex = transActorProp.fromRoom.ootRoomHeader.roomIndex + toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex else: - front = (self.roomIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - back = (transActorProp.roomIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + fromIndex = toIndex = self.roomIndex + front = (fromIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) + back = (toIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) if transActorProp.actor.actorID == "Custom": transActor.id = transActorProp.actor.actorIDCustom @@ -169,7 +172,10 @@ def __post_init__(self): entranceActor.pos = pos entranceActor.rot = ", ".join(f"DEG_TO_BINANG({(r * (180 / 0x8000)):.3f})" for r in rot) entranceActor.params = entranceProp.actor.actorParam - entranceActor.roomIndex = roomObj.ootRoomHeader.roomIndex + if entranceProp.tiedRoom is not None: + entranceActor.roomIndex = entranceProp.tiedRoom.ootRoomHeader.roomIndex + else: + raise PluginError("ERROR: Missing room empty object assigned to the entrance.") entranceActor.spawnIndex = entranceProp.spawnIndex if entranceProp.spawnIndex not in entranceActorFromIndex: diff --git a/fast64_internal/oot/exporter/scene/cutscene.py b/fast64_internal/oot/exporter/scene/cutscene.py index e5adc3ffe..c80289791 100644 --- a/fast64_internal/oot/exporter/scene/cutscene.py +++ b/fast64_internal/oot/exporter/scene/cutscene.py @@ -1,53 +1,535 @@ +import math +import bpy + from dataclasses import dataclass, field -from typing import Optional +from typing import Optional, TYPE_CHECKING from bpy.types import Object from ....utility import PluginError, CData, indent +from ...oot_constants import ootData +from ...cutscene.constants import ootEnumCSListTypeListC +from ...oot_utility import getCustomProperty from ...scene.properties import OOTSceneHeaderProperty from ..base import Base +if TYPE_CHECKING: + from ...cutscene.properties import OOTCutsceneProperty, OOTCSTextProperty + +from ...cutscene.classes import ( + CutsceneCmdTransition, + CutsceneCmdRumbleController, + CutsceneCmdMisc, + CutsceneCmdTime, + CutsceneCmdLightSetting, + CutsceneCmdText, + CutsceneCmdTextNone, + CutsceneCmdTextOcarinaAction, + CutsceneCmdActorCueList, + CutsceneCmdActorCue, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, + CutsceneCmdCamPoint, +) + + +class CutsceneCommands: + """This class contains functions to create the cutscene commands""" + + def getEnumValue(self, enumKey: str, owner, propName: str): + item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) + return item.id if item is not None else getattr(owner, f"{propName}Custom") + + def getGenericListCmd(self, cmdName: str, entryTotal: int): + return indent * 2 + f"{cmdName}({entryTotal}),\n" + + def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): + return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" + + def getTransitionCmd(self, transition: CutsceneCmdTransition): + return indent * 2 + f"CS_TRANSITION({transition.type}, {transition.startFrame}, {transition.endFrame}),\n" + + def getRumbleControllerCmd(self, rumble: CutsceneCmdRumbleController): + return indent * 3 + ( + f"CS_RUMBLE_CONTROLLER(" + + f"0, {rumble.startFrame}, 0, " + + f"{rumble.sourceStrength}, {rumble.duration}, {rumble.decreaseRate}, 0, 0),\n" + ) + + def getMiscCmd(self, misc: CutsceneCmdMisc): + return indent * 3 + (f"CS_MISC(" + f"{misc.type}, {misc.startFrame}, {misc.endFrame}" + ", 0" * 11 + "),\n") + + def getTimeCmd(self, time: CutsceneCmdTime): + return indent * 3 + (f"CS_TIME(" + f"0, {time.startFrame}, 0, {time.hour}, {time.minute}" + "),\n") + + def getLightSettingCmd(self, lightSetting: CutsceneCmdLightSetting): + return indent * 3 + ( + f"CS_LIGHT_SETTING(" + f"{lightSetting.lightSetting}, {lightSetting.startFrame}" + ", 0" * 9 + "),\n" + ) + + def getTextCmd(self, text: CutsceneCmdText): + return indent * 3 + ( + f"CS_TEXT(" + + f"{text.textId}, {text.startFrame}, {text.endFrame}, {text.type}, {text.altTextId1}, {text.altTextId2}" + + "),\n" + ) + + def getTextNoneCmd(self, textNone: CutsceneCmdTextNone): + return indent * 3 + f"CS_TEXT_NONE({textNone.startFrame}, {textNone.endFrame}),\n" + + def getTextOcarinaActionCmd(self, ocarinaAction: CutsceneCmdTextOcarinaAction): + return indent * 3 + ( + f"CS_TEXT_OCARINA_ACTION(" + + f"{ocarinaAction.ocarinaActionId}, {ocarinaAction.startFrame}, " + + f"{ocarinaAction.endFrame}, {ocarinaAction.messageId}" + + "),\n" + ) + + def getDestinationCmd(self, csProp: "OOTCutsceneProperty"): + dest = self.getEnumValue("csDestination", csProp, "csDestination") + return indent * 2 + f"CS_DESTINATION({dest}, {csProp.csDestinationStartFrame}, 0),\n" + + def getActorCueListCmd(self, actorCueList: CutsceneCmdActorCueList, isPlayerActor: bool): + return indent * 2 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" + + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" + + f"{actorCueList.entryTotal}),\n" + ) + + def getActorCueCmd(self, actorCue: CutsceneCmdActorCue, isPlayerActor: bool): + return indent * 3 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE(" + + f"{actorCue.actionID}, {actorCue.startFrame}, {actorCue.endFrame}, " + + "".join(f"{rot}, " for rot in actorCue.rot) + + "".join(f"{pos}, " for pos in actorCue.startPos) + + "".join(f"{pos}, " for pos in actorCue.endPos) + + "0.0f, 0.0f, 0.0f),\n" + ) + + def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): + return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" + + def getCamEyeSplineCmd(self, camEyeSpline: CutsceneCmdCamEyeSpline): + return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) + + def getCamATSplineCmd(self, camATSpline: CutsceneCmdCamATSpline): + return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) + + def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: CutsceneCmdCamEyeSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame + ) + + def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: CutsceneCmdCamATSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame + ) + + def getCamEyeCmd(self, camEye: CutsceneCmdCamEye): + return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) + + def getCamATCmd(self, camAT: CutsceneCmdCamAT): + return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) + + def getCamPointCmd(self, camPoint: CutsceneCmdCamPoint): + return indent * 3 + ( + f"CS_CAM_POINT(" + + f"{camPoint.continueFlag}, {camPoint.camRoll}, {camPoint.frame}, {camPoint.viewAngle}f, " + + "".join(f"{pos}, " for pos in camPoint.pos) + + "0),\n" + ) + + +@dataclass +class Cutscene(CutsceneCommands): + """This class contains functions to create the new cutscene data""" + + name: str + csObj: Object + useMacros: bool + motionOnly: bool + + csObjects: dict[str, list[Object]] = field(default_factory=dict) + entryTotal: int = 0 + frameCount: int = 0 + motionFrameCount: int = 0 + camEndFrame: int = 0 + + def __post_init__(self): + self.csObjects = { + "CS Actor Cue List": [], + "CS Player Cue List": [], + "camShot": [], + } + + for obj in self.csObj.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType in self.csObjects.keys(): + self.csObjects[obj.ootEmptyType].append(obj) + elif obj.type == "ARMATURE": + self.csObjects["camShot"].append(obj) + + def getOoTRotation(self, obj: Object): + """Returns the converted Blender rotation""" + + def conv(r): + r /= 2.0 * math.pi + r -= math.floor(r) + r = round(r * 0x10000) + + if r >= 0x8000: + r += 0xFFFF0000 + + assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) + + return hex(r & 0xFFFF) + + rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] + return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] + + def getOoTPosition(self, pos): + """Returns the converted Blender position""" + + scale = bpy.context.scene.ootBlenderScale + + x = round(pos[0] * scale) + y = round(pos[2] * scale) + z = round(-pos[1] * scale) + + if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): + raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") + + return [x, y, z] + + def getActorCueListData(self, isPlayer: bool): + """Returns the Actor Cue List commands from the corresponding objects""" + + playerOrActor = f"{'Player' if isPlayer else 'Actor'}" + actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] + actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) + actorCueData = "" + + self.entryTotal += len(actorCueListObjects) + for obj in actorCueListObjects: + entryTotal = len(obj.children) + + if entryTotal == 0: + raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") + + if obj.children[-1].ootEmptyType != "CS Dummy Cue": + # we need an extra point that won't be exported to get the real last cue's + # end frame and end position + raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") + + commandType = obj.ootCSMotionProperty.actorCueListProp.commandType + + if commandType == "Custom": + commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom + elif self.useMacros: + commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id + + # ignoring dummy cue + actorCueList = CutsceneCmdActorCueList(None, entryTotal=entryTotal - 1, commandType=commandType) + actorCueData += self.getActorCueListCmd(actorCueList, isPlayer) + + for i, childObj in enumerate(obj.children, 1): + startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame + if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": + endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame + actionID = None + + if isPlayer: + cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID + if cueID != "Custom": + actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id + + if actionID is None: + actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID + + actorCue = CutsceneCmdActorCue( + None, + startFrame, + endFrame, + actionID, + self.getOoTRotation(childObj), + self.getOoTPosition(childObj.location), + self.getOoTPosition(obj.children[i].location), + ) + actorCueData += self.getActorCueCmd(actorCue, isPlayer) + + return actorCueData + + def getCameraShotPointData(self, bones, useAT: bool): + """Returns the Camera Point data from the bone data""" + + shotPoints: list[CutsceneCmdCamPoint] = [] + + if len(bones) < 4: + raise RuntimeError("Camera Armature needs at least 4 bones!") + + for bone in bones: + if bone.parent is not None: + raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") + + shotPoints.append( + CutsceneCmdCamPoint( + None, + None, + None, + ("CS_CAM_CONTINUE" if self.useMacros else "0"), + bone.ootCamShotPointProp.shotPointRoll if useAT else 0, + bone.ootCamShotPointProp.shotPointFrame, + bone.ootCamShotPointProp.shotPointViewAngle, + self.getOoTPosition(bone.head if not useAT else bone.tail), + ) + ) + + # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting + shotPoints.append( + CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useMacros else "-1", 0, 0, 0.0, [0, 0, 0]) + ) + return shotPoints + + def getCamCmdFunc(self, camMode: str, useAT: bool): + """Returns the camera get function depending on the camera mode""" + + camCmdFuncMap = { + "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, + "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd + if useAT + else self.getCamEyeSplineRelToPlayerCmd, + "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, + } + + return camCmdFuncMap[camMode] + + def getCamClass(self, camMode: str, useAT: bool): + """Returns the camera dataclass depending on the camera mode""" + + camCmdClassMap = { + "splineEyeOrAT": CutsceneCmdCamATSpline if useAT else CutsceneCmdCamEyeSpline, + "splineEyeOrATRelPlayer": CutsceneCmdCamATSplineRelToPlayer + if useAT + else CutsceneCmdCamEyeSplineRelToPlayer, + "eyeOrAT": CutsceneCmdCamAT if useAT else CutsceneCmdCamEye, + } + + return camCmdClassMap[camMode] + + def getCamListData(self, shotObj: Object, useAT: bool): + """Returns the Camera Shot data from the corresponding Armatures""" + + camPointList = self.getCameraShotPointData(shotObj.data.bones, useAT) + startFrame = shotObj.data.ootCamShotProp.shotStartFrame + + # "fake" end frame + endFrame = ( + startFrame + max(2, sum(point.frame for point in camPointList)) + (camPointList[-2].frame if useAT else 1) + ) + + if not useAT: + for pointData in camPointList: + pointData.frame = 0 + self.camEndFrame = endFrame + + camData = self.getCamClass(shotObj.data.ootCamShotProp.shotCamMode, useAT)(None, startFrame, endFrame) + return self.getCamCmdFunc(shotObj.data.ootCamShotProp.shotCamMode, useAT)(camData) + "".join( + self.getCamPointCmd(pointData) for pointData in camPointList + ) + + def getCameraShotData(self): + """Returns every Camera Shot commands""" + + shotObjects = self.csObjects["camShot"] + cameraShotData = "" + + if len(shotObjects) > 0: + motionFrameCount = -1 + for shotObj in shotObjects: + cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) + motionFrameCount = max(motionFrameCount, self.camEndFrame + 1) + self.motionFrameCount += motionFrameCount + self.entryTotal += len(shotObjects) * 2 + + return cameraShotData + + def getTextListData(self, textEntry: "OOTCSTextProperty"): + match textEntry.textboxType: + case "Text": + return self.getTextCmd( + CutsceneCmdText( + None, + textEntry.startFrame, + textEntry.endFrame, + textEntry.textID, + self.getEnumValue("csTextType", textEntry, "csTextType"), + textEntry.topOptionTextID, + textEntry.bottomOptionTextID, + ) + ) + case "None": + return self.getTextNoneCmd(CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame)) + case "OcarinaAction": + return self.getTextOcarinaActionCmd( + CutsceneCmdTextOcarinaAction( + None, + textEntry.startFrame, + textEntry.endFrame, + self.getEnumValue("ocarinaSongActionId", textEntry, "ocarinaAction"), + textEntry.ocarinaMessageId, + ) + ) + + def getCutsceneData(self): + csProp: "OOTCutsceneProperty" = self.csObj.ootCutsceneProperty + self.frameCount = csProp.csEndFrame + data = "" + + if csProp.csUseDestination: + data += self.getDestinationCmd(csProp) + self.entryTotal += 1 + + for entry in csProp.csLists: + subData = "" + listCmd = "" + entryTotal = 0 + match entry.listType: + case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": + entryTotal = len(entry.seqList) + for elem in entry.seqList: + enumKey = "csFadeOutSeqPlayer" if entry.listType == "FadeOutSeqList" else "seqId" + propName = "csSeqPlayer" if entry.listType == "FadeOutSeqList" else "csSeqID" + subData += self.getGenericSeqCmd( + ootEnumCSListTypeListC[entry.listType].removesuffix("_LIST"), + self.getEnumValue(enumKey, elem, propName), + elem.startFrame, + elem.endFrame, + ) + case "Transition": + subData += self.getTransitionCmd( + CutsceneCmdTransition( + None, + entry.transitionStartFrame, + entry.transitionEndFrame, + self.getEnumValue("csTransitionType", entry, "transitionType"), + ) + ) + case _: + curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) + entryTotal = len(curList) + for elem in curList: + match entry.listType: + case "TextList": + subData += self.getTextListData(elem) + case "LightSettingsList": + subData += self.getLightSettingCmd( + CutsceneCmdLightSetting( + None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex + ) + ) + case "TimeList": + subData += self.getTimeCmd( + CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) + ) + case "MiscList": + subData += self.getMiscCmd( + CutsceneCmdMisc( + None, + elem.startFrame, + elem.endFrame, + self.getEnumValue("csMiscType", elem, "csMiscType"), + ) + ) + case "RumbleList": + subData += self.getRumbleControllerCmd( + CutsceneCmdRumbleController( + None, + elem.startFrame, + elem.endFrame, + elem.rumbleSourceStrength, + elem.rumbleDuration, + elem.rumbleDecreaseRate, + ) + ) + case _: + raise PluginError("ERROR: Unknown Cutscene List Type!") + if entry.listType != "Transition": + listCmd = self.getGenericListCmd(ootEnumCSListTypeListC[entry.listType], entryTotal) + self.entryTotal += 1 + data += listCmd + subData + + return data + + def getC(self): + """Returns the cutscene data""" + + csData = CData() + declarationBase = f"CutsceneData {self.name}[]" + + if self.motionFrameCount > self.frameCount: + self.frameCount += self.motionFrameCount - self.frameCount + + # .h + csData.header = f"extern {declarationBase};\n" + + # .c + csData.source = ( + declarationBase + + " = {\n" + + (indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n") + + (self.getCutsceneData() if not self.motionOnly else "") + + self.getActorCueListData(False) + + self.getActorCueListData(True) + + self.getCameraShotData() + + "};\n\n" + ) + + return csData + @dataclass class SceneCutscene(Base): - """This class hosts cutscene data (unfinished)""" + """This class hosts cutscene data""" props: OOTSceneHeaderProperty headerIndex: int + useMacros: bool - writeType: Optional[str] = None - writeCutscene: Optional[bool] = None + entries: list[Cutscene] = field(default_factory=list) csObj: Optional[Object] = None - csWriteCustom: Optional[str] = None - extraCutscenes: list[Object] = field(default_factory=list) - name: Optional[str] = None + cutsceneObjects: list[Object] = field(default_factory=list) def __post_init__(self): - self.writeType = self.props.csWriteType - self.writeCutscene = self.props.writeCutscene - self.csObj = self.props.csWriteObject - self.csWriteCustom = self.props.csWriteCustom if self.props.csWriteType == "Custom" else None - self.extraCutscenes = [csObj for csObj in self.props.extraCutscenes] - - if self.writeCutscene and self.writeType == "Embedded": - raise PluginError("ERROR: 'Embedded' CS Write Type is not supported!") + self.csObj: Object = self.props.csWriteObject + self.cutsceneObjects = [csObj for csObj in self.props.extraCutscenes] - if self.headerIndex > 0 and len(self.extraCutscenes) > 0: + if self.headerIndex > 0 and len(self.cutsceneObjects) > 0: raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") - if self.csObj is not None: - self.name = self.csObj.name.removeprefix("Cutscene.") + self.cutsceneObjects.insert(0, self.csObj) + for csObj in self.cutsceneObjects: + if csObj is not None: + if csObj.ootEmptyType != "Cutscene": + raise PluginError( + "ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type" + ) + elif csObj.parent is not None: + raise PluginError("ERROR: Cutscene empty object should not be parented to anything") + + writeType = self.props.csWriteType + csWriteCustom = None + if writeType == "Custom": + csWriteCustom = getCustomProperty(self.props, "csWriteCustom") - if self.csObj.ootEmptyType != "Cutscene": - raise PluginError("ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type") - elif self.csObj.parent is not None: - raise PluginError("ERROR: Cutscene empty object should not be parented to anything") - else: - raise PluginError("ERROR: No object selected for cutscene reference") + if self.props.writeCutscene: + self.entries.append( + Cutscene(self.getCutsceneName(csObj, csWriteCustom), csObj, self.useMacros, False) + ) + + def getCutsceneName(self, csObj: Object, customName: Optional[str] = None) -> str: + return customName if customName is not None else csObj.name.removeprefix("Cutscene.") def getCmd(self): - csDataName = self.csObj.name if self.writeType == "Object" else self.csWriteCustom + csDataName = self.getCutsceneName(self.csObj) return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" - - def getC(self): - # will be implemented when PR #208 is merged - cutsceneData = CData() - return cutsceneData diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index da763e27c..d43323604 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -20,6 +20,7 @@ class SceneHeader(Base): sceneObj: Object transform: Matrix headerIndex: int + useMacros: bool infos: Optional[SceneInfos] = None lighting: Optional[SceneLighting] = None @@ -33,10 +34,7 @@ class SceneHeader(Base): def __post_init__(self): self.infos = SceneInfos(self.props, self.sceneObj) self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") - - if self.props.writeCutscene: - self.cutscene = SceneCutscene(self.props, self.headerIndex) - + self.cutscene = SceneCutscene(self.props, self.headerIndex, self.useMacros) self.exits = SceneExits(self.props, f"{self.name}_exitList") self.transitionActors = SceneTransitionActors( diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index 709d0106b..40eeb0dfd 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -48,8 +48,6 @@ def __post_init__(self): roomHeader.roomShape, self.scene.model.addSubModel( OOTModel( - self.scene.model.f3d.F3D_VER, - self.scene.model.f3d._HW_VERSION_1, f"{roomName}_dl", self.scene.model.DLFormat, None, diff --git a/fast64_internal/oot/oot_upgrade.py b/fast64_internal/oot/oot_upgrade.py index b3dfd90de..c49124826 100644 --- a/fast64_internal/oot/oot_upgrade.py +++ b/fast64_internal/oot/oot_upgrade.py @@ -1,3 +1,5 @@ +import bpy + from dataclasses import dataclass from typing import TYPE_CHECKING from bpy.types import Object, CollectionProperty From 251ecf7aff33baedc2a799586e509d80776b92f2 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:21:53 +0100 Subject: [PATCH 59/98] descriptions --- fast64_internal/oot/exporter/classes.py | 4 +++ .../oot/exporter/collision/__init__.py | 2 ++ .../oot/exporter/collision/camera.py | 1 + .../oot/exporter/collision/surface.py | 25 +------------------ fast64_internal/oot/exporter/main.py | 19 +++++++------- fast64_internal/oot/exporter/room/header.py | 7 ++++++ fast64_internal/oot/exporter/room/main.py | 2 ++ fast64_internal/oot/exporter/room/shape.py | 4 +++ .../oot/exporter/scene/__init__.py | 4 ++- fast64_internal/oot/exporter/scene/actors.py | 6 +++++ .../oot/exporter/scene/cutscene.py | 4 +++ fast64_internal/oot/exporter/scene/general.py | 8 ++++++ .../oot/exporter/scene/pathways.py | 2 ++ fast64_internal/oot/exporter/scene/rooms.py | 2 ++ 14 files changed, 55 insertions(+), 35 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index c00c77ac2..742ecc890 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -19,6 +19,8 @@ class RoomFile: header: str = "" def write(self): + """Writes the room files""" + if self.singleFileExport: roomMainPath = f"{self.name}.c" self.roomMain += self.roomModelInfo + self.roomModel @@ -52,6 +54,7 @@ def __post_init__(self): self.hasSceneTextures = len(self.sceneTextures) > 0 def getSourceWithSceneInclude(self, sceneInclude: str, source: str): + """Returns the source with the includes if missing""" ret = "" if sceneInclude not in source: ret = sceneInclude @@ -80,6 +83,7 @@ def setIncludeData(self): self.sceneCutscenes[i] = self.getSourceWithSceneInclude(csInclude, self.sceneCutscenes[i]) def write(self): + """Writes the scene files""" self.setIncludeData() for room in self.roomList.values(): diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index d73214cfc..580df2e75 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -214,6 +214,8 @@ def __post_init__(self): self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) def getCmd(self): + """Returns the collision header scene command""" + return indent + f"SCENE_CMD_COL_HEADER(&{self.name}),\n" def getC(self): diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 2a3f4381f..268b3702d 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -66,6 +66,7 @@ def __post_init__(self): def getInfoEntryC(self, posDataName: str): """Returns an entry for the camera information array""" + ptr = f"&{posDataName}[{self.arrayIndex}]" if self.hasPosData else "NULL" return indent + "{ " + f"{self.setting}, {self.count}, {ptr}" + " },\n" diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index 99be6a518..4b7283be6 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -3,7 +3,7 @@ from ....utility import CData, indent -@dataclass +@dataclass(unsafe_hash=True) class SurfaceType: """This class defines a single surface type""" @@ -32,29 +32,6 @@ class SurfaceType: canHookshotC: Optional[str] = None isWallDamageC: Optional[str] = None - def __hash__(self): - return hash( - ( - self.bgCamIndex, - self.exitIndex, - self.floorType, - self.unk18, - self.wallType, - self.floorProperty, - self.isSoft, - self.isHorseBlocked, - self.material, - self.floorEffect, - self.lightSetting, - self.echo, - self.canHookshot, - self.conveyorSpeed, - self.conveyorDirection, - self.isWallDamage, - self.conveyorKeepMomentum, - ) - ) - def __post_init__(self): if self.conveyorKeepMomentum: self.conveyorSpeed += 4 diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index bb597c48f..8df318ab0 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -73,7 +73,7 @@ def getNewScene(self): try: sceneName = f"{toAlnum(self.sceneName)}_scene" - sceneData = Scene( + newScene = Scene( self.sceneObj, self.transform, self.useMacros, @@ -81,23 +81,22 @@ def getNewScene(self): self.saveTexturesAsPNG, OOTModel(f"{sceneName}_dl", self.dlFormat, False), ) - sceneData.validateScene() + newScene.validateScene() - if sceneData.mainHeader.cutscene is not None: - self.hasCutscenes = len(sceneData.mainHeader.cutscene.entries) > 0 + if newScene.mainHeader.cutscene is not None: + self.hasCutscenes = len(newScene.mainHeader.cutscene.entries) > 0 if not self.hasCutscenes: - for cs in sceneData.altHeader.cutscenes: + for cs in newScene.altHeader.cutscenes: if len(cs.cutscene.entries) > 0: self.hasCutscenes = True break - - ootCleanupScene(self.originalSceneObj, allObjs) except Exception as e: - ootCleanupScene(self.originalSceneObj, allObjs) raise Exception(str(e)) + finally: + ootCleanupScene(self.originalSceneObj, allObjs) - return sceneData + return newScene def export(self): """Main function""" @@ -139,6 +138,6 @@ def export(self): os.path.join(exportPath, "include/config/config_debug.h") if not isCustomExport else os.path.join(self.path, "config_bootup.h"), - "ENTR_" + self.sceneName.upper() + "_" + str(self.hackerootBootOption.spawnIndex), + f"ENTR_{self.sceneName.upper()}_{self.hackerootBootOption.spawnIndex}", self.hackerootBootOption, ) diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 2e08e4ec2..9a995a7ae 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -10,6 +10,8 @@ @dataclass class HeaderBase(Base): + """Defines the base of a room header""" + name: Optional[str] = None props: Optional[OOTRoomHeaderProperty] = None sceneObj: Optional[Object] = None @@ -67,6 +69,7 @@ def __post_init__(self): self.strength = self.props.windStrength if self.props.setWind else None def getCmds(self): + """Returns the echo settings, room behavior, skybox disables and time settings room commands""" showInvisActors = "true" if self.showInvisActors else "false" disableWarpSongs = "true" if self.disableWarpSongs else "false" disableSkybox = "true" if self.disableSky else "false" @@ -105,6 +108,8 @@ def getDefineName(self): return f"LENGTH_{self.name.upper()}" def getCmd(self): + """Returns the object list room command""" + return indent + f"SCENE_CMD_OBJECT_LIST({self.getDefineName()}, {self.name}),\n" def getC(self): @@ -179,6 +184,8 @@ def getDefineName(self): return f"LENGTH_{self.name.upper()}" def getCmd(self): + """Returns the actor list room command""" + return indent + f"SCENE_CMD_ACTOR_LIST({self.getDefineName()}, {self.name}),\n" def getC(self): diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 27affd327..32b5be4e6 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -115,6 +115,8 @@ def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: return None def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): + """Returns the room commands list""" + cmdListData = CData() listName = f"SceneCmd {curHeader.name}" diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index aec29ff64..2d49e2361 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -9,6 +9,8 @@ @dataclass class RoomShapeBase: + """This class defines the basic informations of a non-image room shape""" + type: str props: OOTRoomHeaderProperty mesh: OOTRoomMesh @@ -304,6 +306,8 @@ def getName(self): raise PluginError("ERROR: Name not found!") def getCmd(self): + """Returns the room shape room command""" + return indent + f"SCENE_CMD_ROOM_SHAPE(&{self.getName()}),\n" def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index c401ddd4c..edea8d584 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -104,6 +104,8 @@ def getSceneHeaderFromIndex(self, headerIndex: int) -> SceneHeader | None: return None def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): + """Returns the scene's commands list""" + cmdListData = CData() listName = f"SceneCmd {curHeader.name}" @@ -198,7 +200,7 @@ def getSceneTexturesC(self, textureExportSettings: TextureExportSettings): return self.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() def getNewSceneFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): - """Gets and sets C data for every scene elements""" + """Returns a new scene file containing the C data""" sceneMainData = self.getSceneMainC() sceneCollisionData = self.colHeader.getC() diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 0a58b6845..370017506 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -98,6 +98,8 @@ def __post_init__(self): self.entries.append(transActor) def getCmd(self): + """Returns the transition actor list scene command""" + return indent + f"SCENE_CMD_TRANSITION_ACTOR_LIST({len(self.entries)}, {self.name}),\n" def getC(self): @@ -190,6 +192,8 @@ def __post_init__(self): self.entries = list(entranceActorFromIndex.values()) def getCmd(self): + """Returns the spawn list scene command""" + name = self.name if len(self.entries) > 0 else "NULL" return indent + f"SCENE_CMD_SPAWN_LIST({len(self.entries)}, {name}),\n" @@ -219,6 +223,8 @@ class SceneSpawns(Base): entries: list[EntranceActor] def getCmd(self): + """Returns the entrance list scene command""" + return indent + f"SCENE_CMD_ENTRANCE_LIST({self.name if len(self.entries) > 0 else 'NULL'}),\n" def getC(self): diff --git a/fast64_internal/oot/exporter/scene/cutscene.py b/fast64_internal/oot/exporter/scene/cutscene.py index c80289791..7a6404298 100644 --- a/fast64_internal/oot/exporter/scene/cutscene.py +++ b/fast64_internal/oot/exporter/scene/cutscene.py @@ -528,8 +528,12 @@ def __post_init__(self): ) def getCutsceneName(self, csObj: Object, customName: Optional[str] = None) -> str: + """Returns the cutscene's name""" + return customName if customName is not None else csObj.name.removeprefix("Cutscene.") def getCmd(self): + """Returns the cutscene data scene command""" + csDataName = self.getCutsceneName(self.csObj) return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index 5386f12e3..01aa11904 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -120,6 +120,8 @@ def __post_init__(self): ) def getCmd(self): + """Returns the env light settings scene command""" + return ( indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" ) + f"{len(self.settings)}, {self.name if len(self.settings) > 0 else 'NULL'}),\n" @@ -190,6 +192,8 @@ def __post_init__(self): self.sceneCamType = self.getPropValue(self.props, "cameraMode") def getCmds(self, lights: SceneLighting): + """Returns the sound settings, misc settings, special files and skybox settings scene commands""" + return ( indent + f",\n{indent}".join( @@ -213,12 +217,16 @@ class SceneExits(Base): exitList: list[tuple[int, str]] = field(default_factory=list) def __post_init__(self): + # TODO: proper implementation of exits + for i, exitProp in enumerate(self.props.exitList): if exitProp.exitIndex != "Custom": raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") self.exitList.append((i, exitProp.exitIndexCustom)) def getCmd(self): + """Returns the exit list scene command""" + return indent + f"SCENE_CMD_EXIT_LIST({self.name}),\n" def getC(self): diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index 498c81d16..a0e4263ee 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -75,6 +75,8 @@ def __post_init__(self): self.pathList = list(pathFromIndex.values()) def getCmd(self): + """Returns the path list scene command""" + return indent + f"SCENE_CMD_PATH_LIST({self.name}),\n" if len(self.pathList) > 0 else "" def getC(self): diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index 40eeb0dfd..b7ebd2553 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -61,6 +61,8 @@ def __post_init__(self): self.entries = [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] def getCmd(self): + """Returns the room list scene command""" + return indent + f"SCENE_CMD_ROOM_LIST({len(self.entries)}, {self.name}),\n" def getC(self, useDummyRoomList: bool): From 5c2db65496d34c11afe2e304db5c037625240b35 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:08:08 +0100 Subject: [PATCH 60/98] cutscene enhancements/reorg --- .../oot/exporter/cutscene/__init__.py | 141 +++++ .../oot/exporter/cutscene/actor_cue.py | 75 +++ .../oot/exporter/cutscene/camera.py | 145 +++++ .../oot/exporter/cutscene/common.py | 148 +++++ fast64_internal/oot/exporter/cutscene/data.py | 430 ++++++++++++++ fast64_internal/oot/exporter/cutscene/misc.py | 197 +++++++ fast64_internal/oot/exporter/cutscene/seq.py | 80 +++ fast64_internal/oot/exporter/cutscene/text.py | 93 +++ .../oot/exporter/scene/cutscene.py | 539 ------------------ fast64_internal/oot/exporter/scene/header.py | 2 +- 10 files changed, 1310 insertions(+), 540 deletions(-) create mode 100644 fast64_internal/oot/exporter/cutscene/__init__.py create mode 100644 fast64_internal/oot/exporter/cutscene/actor_cue.py create mode 100644 fast64_internal/oot/exporter/cutscene/camera.py create mode 100644 fast64_internal/oot/exporter/cutscene/common.py create mode 100644 fast64_internal/oot/exporter/cutscene/data.py create mode 100644 fast64_internal/oot/exporter/cutscene/misc.py create mode 100644 fast64_internal/oot/exporter/cutscene/seq.py create mode 100644 fast64_internal/oot/exporter/cutscene/text.py delete mode 100644 fast64_internal/oot/exporter/scene/cutscene.py diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py new file mode 100644 index 000000000..c116f3259 --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -0,0 +1,141 @@ +from dataclasses import dataclass, field +from typing import Optional +from bpy.types import Object +from ....utility import PluginError, CData, indent +from ...oot_utility import getCustomProperty +from ...scene.properties import OOTSceneHeaderProperty +from ..base import Base +from .data import CutsceneData + + +# NOTE: ``paramNumber`` is the expected number of parameters inside the parsed commands, +# this account for the unused parameters. Every classes are based on the commands arguments from ``z64cutscene_commands.h`` + +# NOTE: ``params`` is the list of parsed parameters, it can't be ``None`` if we're importing a scene, +# when it's ``None`` it will get the data from the cutscene objects + + +@dataclass +class Cutscene: + """This class defines a cutscene, including its data and its informations""" + + name: str + csObj: Object + useMacros: bool + motionOnly: bool = False + data: Optional[CutsceneData] = None + totalEntries: int = 0 + frameCount: int = 0 + paramNumber: int = 2 + + def __post_init__(self): + # when csObj is None it means we're in import context + if self.csObj is not None and self.data is None: + self.data = CutsceneData(self.csObj, self.useMacros, self.motionOnly) + self.totalEntries = self.data.totalEntries + self.frameCount = self.data.frameCount + + def getC(self): + """Returns the cutscene data""" + + if self.data is not None: + csData = CData() + declarationBase = f"CutsceneData {self.name}[]" + + # this list's order defines the order of the commands in the cutscene array + dataListNames = [] + + if not self.motionOnly: + dataListNames = [ + "textList", + "miscList", + "rumbleList", + "transitionList", + "lightSettingsList", + "timeList", + "seqList", + "fadeSeqList", + ] + + dataListNames.extend( + [ + "playerCueList", + "actorCueList", + "camEyeSplineList", + "camATSplineList", + "camEyeSplineRelPlayerList", + "camATSplineRelPlayerList", + "camEyeList", + "camATList", + ] + ) + + if self.data.motionFrameCount > self.frameCount: + self.frameCount += self.data.motionFrameCount - self.frameCount + + # .h + csData.header = f"extern {declarationBase};\n" + + # .c + csData.source = ( + declarationBase + + " = {\n" + + (indent + f"CS_BEGIN_CUTSCENE({self.totalEntries}, {self.frameCount}),\n") + + (self.data.destination.getCmd() if self.motionOnly else "") + + "".join(entry.getCmd() for curList in dataListNames for entry in getattr(self.data, curList)) + + (indent + "CS_END(),\n") + + "};\n\n" + ) + + return csData + else: + raise PluginError("ERROR: CutsceneData not initialised!") + + +@dataclass +class SceneCutscene(Base): + """This class hosts cutscene data""" + + props: OOTSceneHeaderProperty + headerIndex: int + useMacros: bool + + entries: list[Cutscene] = field(default_factory=list) + csObj: Optional[Object] = None + cutsceneObjects: list[Object] = field(default_factory=list) + + def __post_init__(self): + self.csObj: Object = self.props.csWriteObject + self.cutsceneObjects = [csObj for csObj in self.props.extraCutscenes] + + if self.headerIndex > 0 and len(self.cutsceneObjects) > 0: + raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") + + self.cutsceneObjects.insert(0, self.csObj) + for csObj in self.cutsceneObjects: + if csObj is not None: + if csObj.ootEmptyType != "Cutscene": + raise PluginError( + "ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type" + ) + elif csObj.parent is not None: + raise PluginError("ERROR: Cutscene empty object should not be parented to anything") + + writeType = self.props.csWriteType + csWriteCustom = None + if writeType == "Custom": + csWriteCustom = getCustomProperty(self.props, "csWriteCustom") + + if self.props.writeCutscene: + self.entries.append(Cutscene(self.getCutsceneName(csObj, csWriteCustom), csObj, self.useMacros)) + + def getCutsceneName(self, csObj: Object, customName: Optional[str] = None) -> str: + """Returns the cutscene's name""" + + return customName if customName is not None else csObj.name.removeprefix("Cutscene.") + + def getCmd(self): + """Returns the cutscene data scene command""" + + csDataName = self.getCutsceneName(self.csObj) + return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" diff --git a/fast64_internal/oot/exporter/cutscene/actor_cue.py b/fast64_internal/oot/exporter/cutscene/actor_cue.py new file mode 100644 index 000000000..4511ae667 --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/actor_cue.py @@ -0,0 +1,75 @@ +from dataclasses import dataclass, field +from typing import Optional +from ....utility import indent +from ...oot_constants import ootData +from ...cutscene.motion.utility import getRotation, getInteger +from .common import CutsceneCmdBase + + +@dataclass +class CutsceneCmdActorCue(CutsceneCmdBase): + """This class contains a single Actor Cue command data""" + + actionID: Optional[int] = None + rot: list[str] = field(default_factory=list) + startPos: list[int] = field(default_factory=list) + endPos: list[int] = field(default_factory=list) + isPlayer: bool = False + paramNumber: int = 15 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.actionID = getInteger(self.params[0]) + self.rot = [getRotation(self.params[3]), getRotation(self.params[4]), getRotation(self.params[5])] + self.startPos = [getInteger(self.params[6]), getInteger(self.params[7]), getInteger(self.params[8])] + self.endPos = [getInteger(self.params[9]), getInteger(self.params[10]), getInteger(self.params[11])] + + def getCmd(self): + return indent * 3 + ( + f"CS_{'PLAYER' if self.isPlayer else 'ACTOR'}_CUE(" + + f"{self.actionID}, {self.startFrame}, {self.endFrame}, " + + "".join(f"{rot}, " for rot in self.rot) + + "".join(f"{pos}, " for pos in self.startPos) + + "".join(f"{pos}, " for pos in self.endPos) + + "0.0f, 0.0f, 0.0f),\n" + ) + + +@dataclass +class CutsceneCmdActorCueList(CutsceneCmdBase): + """This class contains the Actor Cue List command data""" + + isPlayer: bool = False + commandType: Optional[str] = None + entryTotal: Optional[int] = None + entries: list[CutsceneCmdActorCue] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "actorCueList" + + def __post_init__(self): + if self.params is not None: + if self.isPlayer: + self.commandType = "Player" + self.entryTotal = getInteger(self.params[0]) + else: + self.commandType = self.params[0] + if self.commandType.startswith("0x"): + # make it a 4 digit hex + self.commandType = self.commandType.removeprefix("0x") + self.commandType = "0x" + "0" * (4 - len(self.commandType)) + self.commandType + else: + self.commandType = ootData.enumData.enumByKey["csCmd"].itemById[self.commandType].key + self.entryTotal = getInteger(self.params[1].strip()) + + def getCmd(self): + return ( + indent * 2 + + ( + f"CS_{'PLAYER' if self.isPlayer else 'ACTOR'}_CUE_LIST(" + + f"{self.commandType + ', ' if not self.isPlayer else ''}" + + f"{self.entryTotal}),\n" + ) + + "".join(entry.getCmd() for entry in self.entries) + ) diff --git a/fast64_internal/oot/exporter/cutscene/camera.py b/fast64_internal/oot/exporter/cutscene/camera.py new file mode 100644 index 000000000..813b28616 --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/camera.py @@ -0,0 +1,145 @@ +from dataclasses import dataclass, field +from typing import Optional +from ....utility import indent +from ...cutscene.motion.utility import getInteger +from .common import CutsceneCmdBase + + +@dataclass +class CutsceneCmdCamPoint(CutsceneCmdBase): + """This class contains a single Camera Point command data""" + + continueFlag: Optional[str] = None + camRoll: Optional[int] = None + frame: Optional[int] = None + viewAngle: Optional[float] = None + pos: list[int] = field(default_factory=list) + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.continueFlag = self.params[0] + self.camRoll = getInteger(self.params[1]) + self.frame = getInteger(self.params[2]) + self.viewAngle = float(self.params[3][:-1]) + self.pos = [getInteger(self.params[4]), getInteger(self.params[5]), getInteger(self.params[6])] + + def getCmd(self): + return indent * 3 + ( + f"CS_CAM_POINT(" + + f"{self.continueFlag}, {self.camRoll}, {self.frame}, {self.viewAngle}f, " + + "".join(f"{pos}, " for pos in self.pos) + + "0),\n" + ) + + +@dataclass +class CutsceneCmdCamEyeSpline(CutsceneCmdBase): + """This class contains the Camera Eye Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_EYE_SPLINE", self.startFrame, self.endFrame) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdCamATSpline(CutsceneCmdBase): + """This class contains the Camera AT (look-at) Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_AT_SPLINE", self.startFrame, self.endFrame) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdCamEyeSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera Eye Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_EYE_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera AT Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_AT_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdCamEye(CutsceneCmdBase): + """This class contains a single Camera Eye point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_EYE", self.startFrame, self.endFrame) + + +@dataclass +class CutsceneCmdCamAT(CutsceneCmdBase): + """This class contains a single Camera AT point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return self.getCamListCmd("CS_CAM_AT", self.startFrame, self.endFrame) diff --git a/fast64_internal/oot/exporter/cutscene/common.py b/fast64_internal/oot/exporter/cutscene/common.py new file mode 100644 index 000000000..12aac1fc1 --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/common.py @@ -0,0 +1,148 @@ +import bpy + +from dataclasses import dataclass +from bpy.types import Object +from typing import Optional +from ....utility import indent +from ...oot_constants import ootData +from ...cutscene.motion.utility import getBlenderPosition, getBlenderRotation, getInteger + + +@dataclass +class CutsceneCmdBase: + """This class contains common Cutscene data""" + + params: list[str] + + startFrame: Optional[int] = None + endFrame: Optional[int] = None + + def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): + enum = ootData.enumData.enumByKey[enumKey] + item = enum.itemById.get(self.params[index]) + if item is None: + setting = getInteger(self.params[index]) + if isSeqLegacy: + setting -= 1 + item = enum.itemByIndex.get(setting) + return item.key if item is not None else self.params[index] + + def getGenericListCmd(self, cmdName: str, entryTotal: int): + return indent * 2 + f"{cmdName}({entryTotal}),\n" + + def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): + return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" + + def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): + return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" + + +class CutsceneObjectFactory: + """This class contains functions to create new Blender objects""" + + def getNewObject(self, name: str, data, selectObject: bool, parentObj: Object) -> Object: + newObj = bpy.data.objects.new(name=name, object_data=data) + bpy.context.view_layer.active_layer_collection.collection.objects.link(newObj) + if selectObject: + newObj.select_set(True) + bpy.context.view_layer.objects.active = newObj + newObj.parent = parentObj + newObj.location = [0.0, 0.0, 0.0] + newObj.rotation_euler = [0.0, 0.0, 0.0] + newObj.scale = [1.0, 1.0, 1.0] + return newObj + + def getNewEmptyObject(self, name: str, selectObject: bool, parentObj: Object): + return self.getNewObject(name, None, selectObject, parentObj) + + def getNewArmatureObject(self, name: str, selectObject: bool, parentObj: Object): + newArmatureData = bpy.data.armatures.new(name) + newArmatureData.display_type = "STICK" + newArmatureData.show_names = True + newArmatureObject = self.getNewObject(name, newArmatureData, selectObject, parentObj) + return newArmatureObject + + def getNewCutsceneObject(self, name: str, frameCount: int, parentObj: Object): + newCSObj = self.getNewEmptyObject(name, True, parentObj) + newCSObj.ootEmptyType = "Cutscene" + newCSObj.ootCutsceneProperty.csEndFrame = frameCount + return newCSObj + + def getNewActorCueListObject(self, name: str, commandType: str, parentObj: Object): + newActorCueListObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueListObj.ootEmptyType = f"CS {'Player' if 'Player' in name else 'Actor'} Cue List" + cmdEnum = ootData.enumData.enumByKey["csCmd"] + + if commandType == "Player": + commandType = "player_cue" + + index = cmdEnum.itemByKey[commandType].index if commandType in cmdEnum.itemByKey else int(commandType, base=16) + item = cmdEnum.itemByIndex.get(index) + + if item is not None: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = item.key + else: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = "Custom" + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandTypeCustom = commandType + + return newActorCueListObj + + def getNewActorCueObject( + self, + name: str, + startFrame: int, + actionID: int | str, + location: list[int], + rot: list[str], + parentObj: Object, + ): + isDummy = "(D)" in name + isPlayer = not isDummy and not "Actor" in name + + newActorCueObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueObj.location = getBlenderPosition(location, bpy.context.scene.ootBlenderScale) + newActorCueObj.empty_display_type = "ARROWS" + newActorCueObj.rotation_mode = "XZY" + newActorCueObj.rotation_euler = getBlenderRotation(rot) + emptyType = "Dummy" if isDummy else "Player" if isPlayer else "Actor" + newActorCueObj.ootEmptyType = f"CS {emptyType} Cue" + newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame + + item = None + if isPlayer: + playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] + if isinstance(actionID, int): + item = playerEnum.itemByIndex.get(actionID) + else: + item = playerEnum.itemByKey.get(actionID) + + if item is not None: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = item.key + elif not isDummy: + if isPlayer: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = "Custom" + + if isinstance(actionID, int): + cueActionID = f"0x{actionID:04X}" + else: + cueActionID = actionID + + newActorCueObj.ootCSMotionProperty.actorCueProp.cueActionID = cueActionID + + return newActorCueObj + + def getNewCameraObject( + self, name: str, displaySize: float, clipStart: float, clipEnd: float, alpha: float, parentObj: Object + ): + newCamera = bpy.data.cameras.new(name) + newCameraObj = self.getNewObject(name, newCamera, False, parentObj) + newCameraObj.data.display_size = displaySize + newCameraObj.data.clip_start = clipStart + newCameraObj.data.clip_end = clipEnd + newCameraObj.data.passepartout_alpha = alpha + return newCameraObj + + def getNewActorCuePreviewObject(self, name: str, selectObject, parentObj: Object): + newPreviewObj = self.getNewEmptyObject(name, selectObject, parentObj) + newPreviewObj.ootEmptyType = f"CS {'Actor' if 'Actor' in name else 'Player'} Cue Preview" + return newPreviewObj diff --git a/fast64_internal/oot/exporter/cutscene/data.py b/fast64_internal/oot/exporter/cutscene/data.py new file mode 100644 index 000000000..d667ff77e --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/data.py @@ -0,0 +1,430 @@ +import bpy +import math + +from dataclasses import dataclass, field +from typing import Optional, TYPE_CHECKING +from bpy.types import Object +from ....utility import PluginError +from ...oot_constants import ootData +from .actor_cue import CutsceneCmdActorCueList, CutsceneCmdActorCue +from .seq import CutsceneCmdStartStopSeqList, CutsceneCmdFadeSeqList, CutsceneCmdStartStopSeq, CutsceneCmdFadeSeq +from .text import CutsceneCmdTextList, CutsceneCmdText, CutsceneCmdTextNone, CutsceneCmdTextOcarinaAction + +from .misc import ( + CutsceneCmdLightSetting, + CutsceneCmdTime, + CutsceneCmdMisc, + CutsceneCmdRumbleController, + CutsceneCmdDestination, + CutsceneCmdMiscList, + CutsceneCmdRumbleControllerList, + CutsceneCmdTransition, + CutsceneCmdLightSettingList, + CutsceneCmdTimeList, +) + +from .camera import ( + CutsceneCmdCamPoint, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, +) + +if TYPE_CHECKING: + from ...cutscene.properties import OOTCutsceneProperty, OOTCSTextProperty + + +cmdToClass = { + "TextList": CutsceneCmdTextList, + "LightSettingsList": CutsceneCmdLightSettingList, + "TimeList": CutsceneCmdTimeList, + "MiscList": CutsceneCmdMiscList, + "RumbleList": CutsceneCmdRumbleControllerList, + "StartSeqList": CutsceneCmdStartStopSeqList, + "StopSeqList": CutsceneCmdStartStopSeqList, + "FadeOutSeqList": CutsceneCmdFadeSeqList, + "StartSeq": CutsceneCmdStartStopSeq, + "StopSeq": CutsceneCmdStartStopSeq, + "FadeOutSeq": CutsceneCmdFadeSeq, +} + +cmdToList = { + "TextList": "textList", + "LightSettingsList": "lightSettingsList", + "TimeList": "timeList", + "MiscList": "miscList", + "RumbleList": "rumbleList", +} + + +@dataclass +class CutsceneData: + """This class defines the command data inside a cutscene""" + + csObj: Object + useMacros: bool + motionOnly: bool + csObjects: dict[str, list[Object]] = field(default_factory=dict) + csProp: Optional["OOTCutsceneProperty"] = None + totalEntries: int = 0 + frameCount: int = 0 + motionFrameCount: int = 0 + camEndFrame: int = 0 + + destination: Optional[CutsceneCmdDestination] = None + actorCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + playerCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(default_factory=list) + camATSplineList: list[CutsceneCmdCamATSpline] = field(default_factory=list) + camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(default_factory=list) + camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(default_factory=list) + camEyeList: list[CutsceneCmdCamEye] = field(default_factory=list) + camATList: list[CutsceneCmdCamAT] = field(default_factory=list) + textList: list[CutsceneCmdTextList] = field(default_factory=list) + miscList: list[CutsceneCmdMiscList] = field(default_factory=list) + rumbleList: list[CutsceneCmdRumbleControllerList] = field(default_factory=list) + transitionList: list[CutsceneCmdTransition] = field(default_factory=list) + lightSettingsList: list[CutsceneCmdLightSettingList] = field(default_factory=list) + timeList: list[CutsceneCmdTimeList] = field(default_factory=list) + seqList: list[CutsceneCmdStartStopSeqList] = field(default_factory=list) + fadeSeqList: list[CutsceneCmdFadeSeqList] = field(default_factory=list) + + def __post_init__(self): + self.csProp: "OOTCutsceneProperty" = self.csObj.ootCutsceneProperty + self.csObjects = { + "CS Actor Cue List": [], + "CS Player Cue List": [], + "camShot": [], + } + + for obj in self.csObj.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType in self.csObjects.keys(): + self.csObjects[obj.ootEmptyType].append(obj) + elif obj.type == "ARMATURE": + self.csObjects["camShot"].append(obj) + + self.csObjects["camShot"].sort(key=lambda obj: obj.name) + self.setCutsceneData() + + def getOoTRotation(self, obj: Object): + """Returns the converted Blender rotation""" + + def conv(r): + r /= 2.0 * math.pi + r -= math.floor(r) + r = round(r * 0x10000) + + if r >= 0x8000: + r += 0xFFFF0000 + + assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) + + return hex(r & 0xFFFF) + + rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] + return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] + + def getOoTPosition(self, pos): + """Returns the converted Blender position""" + + scale = bpy.context.scene.ootBlenderScale + + x = round(pos[0] * scale) + y = round(pos[2] * scale) + z = round(-pos[1] * scale) + + if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): + raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") + + return [x, y, z] + + def getEnumValueFromProp(self, enumKey: str, owner, propName: str): + item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) + return item.id if item is not None else getattr(owner, f"{propName}Custom") + + def setActorCueListData(self, isPlayer: bool): + """Returns the Actor Cue List commands from the corresponding objects""" + + playerOrActor = f"{'Player' if isPlayer else 'Actor'}" + actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] + actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) + + self.totalEntries += len(actorCueListObjects) + for obj in actorCueListObjects: + entryTotal = len(obj.children) + + if entryTotal == 0: + raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") + + if obj.children[-1].ootEmptyType != "CS Dummy Cue": + # we need an extra point that won't be exported to get the real last cue's + # end frame and end position + raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") + + commandType = obj.ootCSMotionProperty.actorCueListProp.commandType + + if commandType == "Custom": + commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom + elif self.useMacros: + commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id + + # ignoring dummy cue + newActorCueList = CutsceneCmdActorCueList( + None, isPlayer=isPlayer, entryTotal=entryTotal - 1, commandType=commandType + ) + + for i, childObj in enumerate(obj.children, 1): + startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame + if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": + endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame + actionID = None + + if isPlayer: + cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID + if cueID != "Custom": + actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id + + if actionID is None: + actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID + + newActorCueList.entries.append( + CutsceneCmdActorCue( + None, + startFrame, + endFrame, + actionID, + self.getOoTRotation(childObj), + self.getOoTPosition(childObj.location), + self.getOoTPosition(obj.children[i].location), + isPlayer, + ) + ) + + self.actorCueList.append(newActorCueList) + + def getCameraShotPointData(self, bones, useAT: bool): + """Returns the Camera Point data from the bone data""" + + shotPoints: list[CutsceneCmdCamPoint] = [] + + if len(bones) < 4: + raise RuntimeError("Camera Armature needs at least 4 bones!") + + for bone in bones: + if bone.parent is not None: + raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") + + shotPoints.append( + CutsceneCmdCamPoint( + None, + None, + None, + ("CS_CAM_CONTINUE" if self.useMacros else "0"), + bone.ootCamShotPointProp.shotPointRoll if useAT else 0, + bone.ootCamShotPointProp.shotPointFrame, + bone.ootCamShotPointProp.shotPointViewAngle, + self.getOoTPosition(bone.head if not useAT else bone.tail), + ) + ) + + # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting + shotPoints.append( + CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useMacros else "-1", 0, 0, 0.0, [0, 0, 0]) + ) + return shotPoints + + def getCamCmdFunc(self, camMode: str, useAT: bool): + """Returns the camera get function depending on the camera mode""" + + camCmdFuncMap = { + "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, + "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd + if useAT + else self.getCamEyeSplineRelToPlayerCmd, + "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, + } + + return camCmdFuncMap[camMode] + + def getCamClassOrList(self, isClass: bool, camMode: str, useAT: bool): + """Returns the camera dataclass depending on the camera mode""" + + # TODO: improve this + if isClass: + camCmdClassMap = { + "splineEyeOrAT": CutsceneCmdCamATSpline if useAT else CutsceneCmdCamEyeSpline, + "splineEyeOrATRelPlayer": CutsceneCmdCamATSplineRelToPlayer + if useAT + else CutsceneCmdCamEyeSplineRelToPlayer, + "eyeOrAT": CutsceneCmdCamAT if useAT else CutsceneCmdCamEye, + } + else: + camCmdClassMap = { + "splineEyeOrAT": "camATSplineList" if useAT else "camEyeSplineList", + "splineEyeOrATRelPlayer": "camATSplineRelPlayerList" if useAT else "camEyeSplineRelPlayerList", + "eyeOrAT": "camATList" if useAT else "camEyeList", + } + + return camCmdClassMap[camMode] + + def getNewCamData(self, shotObj: Object, useAT: bool): + """Returns the Camera Shot data from the corresponding Armatures""" + + newCamData = self.getCamClassOrList(True, shotObj.data.ootCamShotProp.shotCamMode, useAT)(None) + newCamData.entries = self.getCameraShotPointData(shotObj.data.bones, useAT) + startFrame = shotObj.data.ootCamShotProp.shotStartFrame + + # "fake" end frame + endFrame = ( + startFrame + + max(2, sum(point.frame for point in newCamData.entries)) + + (newCamData.entries[-2].frame if useAT else 1) + ) + + if not useAT: + for pointData in newCamData.entries: + pointData.frame = 0 + self.camEndFrame = endFrame + + newCamData.startFrame = startFrame + newCamData.endFrame = endFrame + + return newCamData + + def setCameraShotData(self): + """Returns every Camera Shot commands""" + + shotObjects = self.csObjects["camShot"] + + if len(shotObjects) > 0: + motionFrameCount = -1 + for shotObj in shotObjects: + camMode = shotObj.data.ootCamShotProp.shotCamMode + + eyeCamList = getattr(self, self.getCamClassOrList(False, camMode, False)) + eyeCamList.append(self.getNewCamData(shotObj, False)) + + atCamList = getattr(self, self.getCamClassOrList(False, camMode, True)) + atCamList.append(self.getNewCamData(shotObj, True)) + + motionFrameCount = max(motionFrameCount, self.camEndFrame + 1) + self.motionFrameCount += motionFrameCount + self.totalEntries += len(shotObjects) * 2 + + def getNewTextCmd(self, textEntry: "OOTCSTextProperty"): + match textEntry.textboxType: + case "Text": + return CutsceneCmdText( + None, + textEntry.startFrame, + textEntry.endFrame, + textEntry.textID, + self.getEnumValueFromProp("csTextType", textEntry, "csTextType"), + textEntry.topOptionTextID, + textEntry.bottomOptionTextID, + ) + case "None": + return CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame) + case "OcarinaAction": + return CutsceneCmdTextOcarinaAction( + None, + textEntry.startFrame, + textEntry.endFrame, + self.getEnumValueFromProp("ocarinaSongActionId", textEntry, "ocarinaAction"), + textEntry.ocarinaMessageId, + ) + raise PluginError("ERROR: Unknown text type!") + + def setCutsceneData(self): + self.setActorCueListData(True) + self.setActorCueListData(False) + self.setCameraShotData() + + # don't process the cutscene empty if we don't want its data + if self.motionOnly: + return + + if self.csProp.csUseDestination: + self.destination = CutsceneCmdDestination( + None, + self.csProp.csDestinationStartFrame, + None, + self.getEnumValueFromProp("csDestination", self.csProp, "csDestination"), + ) + self.totalEntries += 1 + + self.frameCount = self.csProp.csEndFrame + self.totalEntries += len(self.csProp.csLists) + + for entry in self.csProp.csLists: + match entry.listType: + case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": + isFadeOutSeq = entry.listType == "FadeOutSeqList" + cmdList = cmdToClass[entry.listType](None) + cmdList.entryTotal = len(entry.seqList) + cmdList.type = "start" if entry.listType == "StartSeqList" else "stop" + for elem in entry.seqList: + data = cmdToClass[entry.listType.removesuffix("List")](None, elem.startFrame, elem.endFrame) + if isFadeOutSeq: + data.seqPlayer = self.getEnumValueFromProp("csFadeOutSeqPlayer", elem, "csSeqPlayer") + else: + data.type = cmdList.type + data.seqId = self.getEnumValueFromProp("seqId", elem, "csSeqID") + cmdList.entries.append(data) + if isFadeOutSeq: + self.fadeSeqList.append(cmdList) + else: + self.seqList.append(cmdList) + case "Transition": + self.transitionList.append( + CutsceneCmdTransition( + None, + entry.transitionStartFrame, + entry.transitionEndFrame, + self.getEnumValueFromProp("csTransitionType", entry, "transitionType"), + ) + ) + case _: + curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) + cmdList = cmdToClass[entry.listType](None) + cmdList.entryTotal = len(curList) + for elem in curList: + match entry.listType: + case "TextList": + cmdList.entries.append(self.getNewTextCmd(elem)) + case "LightSettingsList": + cmdList.entries.append( + CutsceneCmdLightSetting( + None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex + ) + ) + case "TimeList": + cmdList.entries.append( + CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) + ) + case "MiscList": + cmdList.entries.append( + CutsceneCmdMisc( + None, + elem.startFrame, + elem.endFrame, + self.getEnumValueFromProp("csMiscType", elem, "csMiscType"), + ) + ) + case "RumbleList": + cmdList.entries.append( + CutsceneCmdRumbleController( + None, + elem.startFrame, + elem.endFrame, + elem.rumbleSourceStrength, + elem.rumbleDuration, + elem.rumbleDecreaseRate, + ) + ) + case _: + raise PluginError("ERROR: Unknown Cutscene List Type!") + getattr(self, cmdToList[entry.listType]).append(cmdList) diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py new file mode 100644 index 000000000..ba6748f4e --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -0,0 +1,197 @@ +from dataclasses import dataclass, field +from typing import Optional +from ....utility import indent +from ...cutscene.motion.utility import getInteger +from .common import CutsceneCmdBase + + +@dataclass +class CutsceneCmdMisc(CutsceneCmdBase): + """This class contains a single misc command entry""" + + type: Optional[str] = None # see ``CutsceneMiscType`` in decomp + paramNumber: int = 14 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csMiscType", 0) + + def getCmd(self): + return indent * 3 + (f"CS_MISC({self.type}, {self.startFrame}, {self.endFrame}" + ", 0" * 11 + "),\n") + + +@dataclass +class CutsceneCmdLightSetting(CutsceneCmdBase): + """This class contains Light Setting command data""" + + isLegacy: Optional[bool] = None + lightSetting: Optional[int] = None + paramNumber: int = 11 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.lightSetting = getInteger(self.params[0]) + if self.isLegacy: + self.lightSetting -= 1 + + def getCmd(self): + return indent * 3 + (f"CS_LIGHT_SETTING({self.lightSetting}, {self.startFrame}" + ", 0" * 9 + "),\n") + + +@dataclass +class CutsceneCmdTime(CutsceneCmdBase): + """This class contains Time Ocarina Action command data""" + + hour: Optional[int] = None + minute: Optional[int] = None + paramNumber: int = 5 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.hour = getInteger(self.params[3]) + self.minute = getInteger(self.params[4]) + + def getCmd(self): + return indent * 3 + f"CS_TIME(0, {self.startFrame}, 0, {self.hour}, {self.minute}),\n" + + +@dataclass +class CutsceneCmdRumbleController(CutsceneCmdBase): + """This class contains Rumble Controller command data""" + + sourceStrength: Optional[int] = None + duration: Optional[int] = None + decreaseRate: Optional[int] = None + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.sourceStrength = getInteger(self.params[3]) + self.duration = getInteger(self.params[4]) + self.decreaseRate = getInteger(self.params[5]) + + def getCmd(self): + return indent * 3 + ( + f"CS_RUMBLE_CONTROLLER(" + + f"0, {self.startFrame}, 0, " + + f"{self.sourceStrength}, {self.duration}, {self.decreaseRate}, 0, 0),\n" + ) + + +@dataclass +class CutsceneCmdMiscList(CutsceneCmdBase): + """This class contains Misc command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdMisc] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "miscList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_MISC_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdLightSettingList(CutsceneCmdBase): + """This class contains Light Setting List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdLightSetting] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "lightSettingsList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_LIGHT_SETTING_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdTimeList(CutsceneCmdBase): + """This class contains Time List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdTime] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "timeList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_TIME_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdRumbleControllerList(CutsceneCmdBase): + """This class contains Rumble Controller List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdRumbleController] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "rumbleList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_RUMBLE_CONTROLLER_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdDestination(CutsceneCmdBase): + """This class contains Destination command data""" + + id: Optional[str] = None + paramNumber: int = 3 + listName: str = "destination" + + def __post_init__(self): + if self.params is not None: + self.id = self.getEnumValue("csDestination", 0) + self.startFrame = getInteger(self.params[1]) + + def getCmd(self): + return indent * 2 + f"CS_DESTINATION({self.id}, {self.startFrame}, 0),\n" + + +@dataclass +class CutsceneCmdTransition(CutsceneCmdBase): + """This class contains Transition command data""" + + type: Optional[str] = None + paramNumber: int = 3 + listName: str = "transitionList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csTransitionType", 0) + + def getCmd(self): + return indent * 2 + f"CS_TRANSITION({self.type}, {self.startFrame}, {self.endFrame}),\n" diff --git a/fast64_internal/oot/exporter/cutscene/seq.py b/fast64_internal/oot/exporter/cutscene/seq.py new file mode 100644 index 000000000..539d45cbc --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/seq.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass, field +from typing import Optional +from ...cutscene.motion.utility import getInteger +from .common import CutsceneCmdBase + + +@dataclass +class CutsceneCmdStartStopSeq(CutsceneCmdBase): + """This class contains Start/Stop Seq command data""" + + isLegacy: Optional[bool] = None + seqId: Optional[str] = None + paramNumber: int = 11 + type: Optional[str] = None # "start" or "stop" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqId = self.getEnumValue("seqId", 0, self.isLegacy) + + def getCmd(self): + return self.getGenericSeqCmd(f"CS_{self.type.upper()}_SEQ", self.seqId, self.startFrame, self.endFrame) + + +@dataclass +class CutsceneCmdFadeSeq(CutsceneCmdBase): + """This class contains Fade Seq command data""" + + seqPlayer: Optional[str] = None + paramNumber: int = 11 + enumKey: str = "csFadeOutSeqPlayer" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqPlayer = self.getEnumValue(self.enumKey, 0) + + def getCmd(self): + return self.getGenericSeqCmd("CS_FADE_OUT_SEQ", self.seqPlayer, self.startFrame, self.endFrame) + + +@dataclass +class CutsceneCmdStartStopSeqList(CutsceneCmdBase): + """This class contains Start/Stop Seq List command data""" + + entryTotal: Optional[int] = None + type: Optional[str] = None # "start" or "stop" + entries: list[CutsceneCmdStartStopSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "seqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd(f"CS_{self.type.upper()}_SEQ_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) + + +@dataclass +class CutsceneCmdFadeSeqList(CutsceneCmdBase): + """This class contains Fade Seq List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdFadeSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "fadeSeqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_FADE_OUT_SEQ_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) diff --git a/fast64_internal/oot/exporter/cutscene/text.py b/fast64_internal/oot/exporter/cutscene/text.py new file mode 100644 index 000000000..5fd4aae19 --- /dev/null +++ b/fast64_internal/oot/exporter/cutscene/text.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass, field +from typing import Optional +from ....utility import indent +from ...cutscene.motion.utility import getInteger +from .common import CutsceneCmdBase + + +@dataclass +class CutsceneCmdText(CutsceneCmdBase): + """This class contains Text command data""" + + textId: Optional[int] = None + type: Optional[str] = None + altTextId1: Optional[int] = None + altTextId2: Optional[int] = None + paramNumber: int = 6 + id: str = "Text" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.textId = getInteger(self.params[0]) + self.type = self.getEnumValue("csTextType", 3) + self.altTextId1 = getInteger(self.params[4]) + self.altTextId2 = getInteger(self.params[5]) + + def getCmd(self): + return indent * 3 + ( + f"CS_TEXT(" + + f"{self.textId}, {self.startFrame}, {self.endFrame}, {self.type}, {self.altTextId1}, {self.altTextId2}" + + "),\n" + ) + + +@dataclass +class CutsceneCmdTextNone(CutsceneCmdBase): + """This class contains Text None command data""" + + paramNumber: int = 2 + id: str = "None" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + def getCmd(self): + return indent * 3 + f"CS_TEXT_NONE({self.startFrame}, {self.endFrame}),\n" + + +@dataclass +class CutsceneCmdTextOcarinaAction(CutsceneCmdBase): + """This class contains Text Ocarina Action command data""" + + ocarinaActionId: Optional[str] = None + messageId: Optional[int] = None + paramNumber: int = 4 + id: str = "OcarinaAction" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.ocarinaActionId = self.getEnumValue("ocarinaSongActionId", 0) + self.messageId = getInteger(self.params[3]) + + def getCmd(self): + return indent * 3 + ( + f"CS_TEXT_OCARINA_ACTION(" + + f"{self.ocarinaActionId}, {self.startFrame}, " + + f"{self.endFrame}, {self.messageId}" + + "),\n" + ) + + +@dataclass +class CutsceneCmdTextList(CutsceneCmdBase): + """This class contains Text List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdText | CutsceneCmdTextNone | CutsceneCmdTextOcarinaAction] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "textList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + def getCmd(self): + return self.getGenericListCmd("CS_TEXT_LIST", self.entryTotal) + "".join( + entry.getCmd() for entry in self.entries + ) diff --git a/fast64_internal/oot/exporter/scene/cutscene.py b/fast64_internal/oot/exporter/scene/cutscene.py deleted file mode 100644 index 7a6404298..000000000 --- a/fast64_internal/oot/exporter/scene/cutscene.py +++ /dev/null @@ -1,539 +0,0 @@ -import math -import bpy - -from dataclasses import dataclass, field -from typing import Optional, TYPE_CHECKING -from bpy.types import Object -from ....utility import PluginError, CData, indent -from ...oot_constants import ootData -from ...cutscene.constants import ootEnumCSListTypeListC -from ...oot_utility import getCustomProperty -from ...scene.properties import OOTSceneHeaderProperty -from ..base import Base - -if TYPE_CHECKING: - from ...cutscene.properties import OOTCutsceneProperty, OOTCSTextProperty - -from ...cutscene.classes import ( - CutsceneCmdTransition, - CutsceneCmdRumbleController, - CutsceneCmdMisc, - CutsceneCmdTime, - CutsceneCmdLightSetting, - CutsceneCmdText, - CutsceneCmdTextNone, - CutsceneCmdTextOcarinaAction, - CutsceneCmdActorCueList, - CutsceneCmdActorCue, - CutsceneCmdCamEyeSpline, - CutsceneCmdCamATSpline, - CutsceneCmdCamEyeSplineRelToPlayer, - CutsceneCmdCamATSplineRelToPlayer, - CutsceneCmdCamEye, - CutsceneCmdCamAT, - CutsceneCmdCamPoint, -) - - -class CutsceneCommands: - """This class contains functions to create the cutscene commands""" - - def getEnumValue(self, enumKey: str, owner, propName: str): - item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) - return item.id if item is not None else getattr(owner, f"{propName}Custom") - - def getGenericListCmd(self, cmdName: str, entryTotal: int): - return indent * 2 + f"{cmdName}({entryTotal}),\n" - - def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): - return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" - - def getTransitionCmd(self, transition: CutsceneCmdTransition): - return indent * 2 + f"CS_TRANSITION({transition.type}, {transition.startFrame}, {transition.endFrame}),\n" - - def getRumbleControllerCmd(self, rumble: CutsceneCmdRumbleController): - return indent * 3 + ( - f"CS_RUMBLE_CONTROLLER(" - + f"0, {rumble.startFrame}, 0, " - + f"{rumble.sourceStrength}, {rumble.duration}, {rumble.decreaseRate}, 0, 0),\n" - ) - - def getMiscCmd(self, misc: CutsceneCmdMisc): - return indent * 3 + (f"CS_MISC(" + f"{misc.type}, {misc.startFrame}, {misc.endFrame}" + ", 0" * 11 + "),\n") - - def getTimeCmd(self, time: CutsceneCmdTime): - return indent * 3 + (f"CS_TIME(" + f"0, {time.startFrame}, 0, {time.hour}, {time.minute}" + "),\n") - - def getLightSettingCmd(self, lightSetting: CutsceneCmdLightSetting): - return indent * 3 + ( - f"CS_LIGHT_SETTING(" + f"{lightSetting.lightSetting}, {lightSetting.startFrame}" + ", 0" * 9 + "),\n" - ) - - def getTextCmd(self, text: CutsceneCmdText): - return indent * 3 + ( - f"CS_TEXT(" - + f"{text.textId}, {text.startFrame}, {text.endFrame}, {text.type}, {text.altTextId1}, {text.altTextId2}" - + "),\n" - ) - - def getTextNoneCmd(self, textNone: CutsceneCmdTextNone): - return indent * 3 + f"CS_TEXT_NONE({textNone.startFrame}, {textNone.endFrame}),\n" - - def getTextOcarinaActionCmd(self, ocarinaAction: CutsceneCmdTextOcarinaAction): - return indent * 3 + ( - f"CS_TEXT_OCARINA_ACTION(" - + f"{ocarinaAction.ocarinaActionId}, {ocarinaAction.startFrame}, " - + f"{ocarinaAction.endFrame}, {ocarinaAction.messageId}" - + "),\n" - ) - - def getDestinationCmd(self, csProp: "OOTCutsceneProperty"): - dest = self.getEnumValue("csDestination", csProp, "csDestination") - return indent * 2 + f"CS_DESTINATION({dest}, {csProp.csDestinationStartFrame}, 0),\n" - - def getActorCueListCmd(self, actorCueList: CutsceneCmdActorCueList, isPlayerActor: bool): - return indent * 2 + ( - f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" - + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" - + f"{actorCueList.entryTotal}),\n" - ) - - def getActorCueCmd(self, actorCue: CutsceneCmdActorCue, isPlayerActor: bool): - return indent * 3 + ( - f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE(" - + f"{actorCue.actionID}, {actorCue.startFrame}, {actorCue.endFrame}, " - + "".join(f"{rot}, " for rot in actorCue.rot) - + "".join(f"{pos}, " for pos in actorCue.startPos) - + "".join(f"{pos}, " for pos in actorCue.endPos) - + "0.0f, 0.0f, 0.0f),\n" - ) - - def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): - return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" - - def getCamEyeSplineCmd(self, camEyeSpline: CutsceneCmdCamEyeSpline): - return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) - - def getCamATSplineCmd(self, camATSpline: CutsceneCmdCamATSpline): - return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) - - def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: CutsceneCmdCamEyeSplineRelToPlayer): - return self.getCamListCmd( - "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame - ) - - def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: CutsceneCmdCamATSplineRelToPlayer): - return self.getCamListCmd( - "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame - ) - - def getCamEyeCmd(self, camEye: CutsceneCmdCamEye): - return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) - - def getCamATCmd(self, camAT: CutsceneCmdCamAT): - return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) - - def getCamPointCmd(self, camPoint: CutsceneCmdCamPoint): - return indent * 3 + ( - f"CS_CAM_POINT(" - + f"{camPoint.continueFlag}, {camPoint.camRoll}, {camPoint.frame}, {camPoint.viewAngle}f, " - + "".join(f"{pos}, " for pos in camPoint.pos) - + "0),\n" - ) - - -@dataclass -class Cutscene(CutsceneCommands): - """This class contains functions to create the new cutscene data""" - - name: str - csObj: Object - useMacros: bool - motionOnly: bool - - csObjects: dict[str, list[Object]] = field(default_factory=dict) - entryTotal: int = 0 - frameCount: int = 0 - motionFrameCount: int = 0 - camEndFrame: int = 0 - - def __post_init__(self): - self.csObjects = { - "CS Actor Cue List": [], - "CS Player Cue List": [], - "camShot": [], - } - - for obj in self.csObj.children_recursive: - if obj.type == "EMPTY" and obj.ootEmptyType in self.csObjects.keys(): - self.csObjects[obj.ootEmptyType].append(obj) - elif obj.type == "ARMATURE": - self.csObjects["camShot"].append(obj) - - def getOoTRotation(self, obj: Object): - """Returns the converted Blender rotation""" - - def conv(r): - r /= 2.0 * math.pi - r -= math.floor(r) - r = round(r * 0x10000) - - if r >= 0x8000: - r += 0xFFFF0000 - - assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) - - return hex(r & 0xFFFF) - - rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] - return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] - - def getOoTPosition(self, pos): - """Returns the converted Blender position""" - - scale = bpy.context.scene.ootBlenderScale - - x = round(pos[0] * scale) - y = round(pos[2] * scale) - z = round(-pos[1] * scale) - - if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): - raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") - - return [x, y, z] - - def getActorCueListData(self, isPlayer: bool): - """Returns the Actor Cue List commands from the corresponding objects""" - - playerOrActor = f"{'Player' if isPlayer else 'Actor'}" - actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] - actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) - actorCueData = "" - - self.entryTotal += len(actorCueListObjects) - for obj in actorCueListObjects: - entryTotal = len(obj.children) - - if entryTotal == 0: - raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") - - if obj.children[-1].ootEmptyType != "CS Dummy Cue": - # we need an extra point that won't be exported to get the real last cue's - # end frame and end position - raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") - - commandType = obj.ootCSMotionProperty.actorCueListProp.commandType - - if commandType == "Custom": - commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom - elif self.useMacros: - commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id - - # ignoring dummy cue - actorCueList = CutsceneCmdActorCueList(None, entryTotal=entryTotal - 1, commandType=commandType) - actorCueData += self.getActorCueListCmd(actorCueList, isPlayer) - - for i, childObj in enumerate(obj.children, 1): - startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame - if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": - endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame - actionID = None - - if isPlayer: - cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID - if cueID != "Custom": - actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id - - if actionID is None: - actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID - - actorCue = CutsceneCmdActorCue( - None, - startFrame, - endFrame, - actionID, - self.getOoTRotation(childObj), - self.getOoTPosition(childObj.location), - self.getOoTPosition(obj.children[i].location), - ) - actorCueData += self.getActorCueCmd(actorCue, isPlayer) - - return actorCueData - - def getCameraShotPointData(self, bones, useAT: bool): - """Returns the Camera Point data from the bone data""" - - shotPoints: list[CutsceneCmdCamPoint] = [] - - if len(bones) < 4: - raise RuntimeError("Camera Armature needs at least 4 bones!") - - for bone in bones: - if bone.parent is not None: - raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") - - shotPoints.append( - CutsceneCmdCamPoint( - None, - None, - None, - ("CS_CAM_CONTINUE" if self.useMacros else "0"), - bone.ootCamShotPointProp.shotPointRoll if useAT else 0, - bone.ootCamShotPointProp.shotPointFrame, - bone.ootCamShotPointProp.shotPointViewAngle, - self.getOoTPosition(bone.head if not useAT else bone.tail), - ) - ) - - # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting - shotPoints.append( - CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useMacros else "-1", 0, 0, 0.0, [0, 0, 0]) - ) - return shotPoints - - def getCamCmdFunc(self, camMode: str, useAT: bool): - """Returns the camera get function depending on the camera mode""" - - camCmdFuncMap = { - "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, - "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd - if useAT - else self.getCamEyeSplineRelToPlayerCmd, - "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, - } - - return camCmdFuncMap[camMode] - - def getCamClass(self, camMode: str, useAT: bool): - """Returns the camera dataclass depending on the camera mode""" - - camCmdClassMap = { - "splineEyeOrAT": CutsceneCmdCamATSpline if useAT else CutsceneCmdCamEyeSpline, - "splineEyeOrATRelPlayer": CutsceneCmdCamATSplineRelToPlayer - if useAT - else CutsceneCmdCamEyeSplineRelToPlayer, - "eyeOrAT": CutsceneCmdCamAT if useAT else CutsceneCmdCamEye, - } - - return camCmdClassMap[camMode] - - def getCamListData(self, shotObj: Object, useAT: bool): - """Returns the Camera Shot data from the corresponding Armatures""" - - camPointList = self.getCameraShotPointData(shotObj.data.bones, useAT) - startFrame = shotObj.data.ootCamShotProp.shotStartFrame - - # "fake" end frame - endFrame = ( - startFrame + max(2, sum(point.frame for point in camPointList)) + (camPointList[-2].frame if useAT else 1) - ) - - if not useAT: - for pointData in camPointList: - pointData.frame = 0 - self.camEndFrame = endFrame - - camData = self.getCamClass(shotObj.data.ootCamShotProp.shotCamMode, useAT)(None, startFrame, endFrame) - return self.getCamCmdFunc(shotObj.data.ootCamShotProp.shotCamMode, useAT)(camData) + "".join( - self.getCamPointCmd(pointData) for pointData in camPointList - ) - - def getCameraShotData(self): - """Returns every Camera Shot commands""" - - shotObjects = self.csObjects["camShot"] - cameraShotData = "" - - if len(shotObjects) > 0: - motionFrameCount = -1 - for shotObj in shotObjects: - cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) - motionFrameCount = max(motionFrameCount, self.camEndFrame + 1) - self.motionFrameCount += motionFrameCount - self.entryTotal += len(shotObjects) * 2 - - return cameraShotData - - def getTextListData(self, textEntry: "OOTCSTextProperty"): - match textEntry.textboxType: - case "Text": - return self.getTextCmd( - CutsceneCmdText( - None, - textEntry.startFrame, - textEntry.endFrame, - textEntry.textID, - self.getEnumValue("csTextType", textEntry, "csTextType"), - textEntry.topOptionTextID, - textEntry.bottomOptionTextID, - ) - ) - case "None": - return self.getTextNoneCmd(CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame)) - case "OcarinaAction": - return self.getTextOcarinaActionCmd( - CutsceneCmdTextOcarinaAction( - None, - textEntry.startFrame, - textEntry.endFrame, - self.getEnumValue("ocarinaSongActionId", textEntry, "ocarinaAction"), - textEntry.ocarinaMessageId, - ) - ) - - def getCutsceneData(self): - csProp: "OOTCutsceneProperty" = self.csObj.ootCutsceneProperty - self.frameCount = csProp.csEndFrame - data = "" - - if csProp.csUseDestination: - data += self.getDestinationCmd(csProp) - self.entryTotal += 1 - - for entry in csProp.csLists: - subData = "" - listCmd = "" - entryTotal = 0 - match entry.listType: - case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": - entryTotal = len(entry.seqList) - for elem in entry.seqList: - enumKey = "csFadeOutSeqPlayer" if entry.listType == "FadeOutSeqList" else "seqId" - propName = "csSeqPlayer" if entry.listType == "FadeOutSeqList" else "csSeqID" - subData += self.getGenericSeqCmd( - ootEnumCSListTypeListC[entry.listType].removesuffix("_LIST"), - self.getEnumValue(enumKey, elem, propName), - elem.startFrame, - elem.endFrame, - ) - case "Transition": - subData += self.getTransitionCmd( - CutsceneCmdTransition( - None, - entry.transitionStartFrame, - entry.transitionEndFrame, - self.getEnumValue("csTransitionType", entry, "transitionType"), - ) - ) - case _: - curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) - entryTotal = len(curList) - for elem in curList: - match entry.listType: - case "TextList": - subData += self.getTextListData(elem) - case "LightSettingsList": - subData += self.getLightSettingCmd( - CutsceneCmdLightSetting( - None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex - ) - ) - case "TimeList": - subData += self.getTimeCmd( - CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) - ) - case "MiscList": - subData += self.getMiscCmd( - CutsceneCmdMisc( - None, - elem.startFrame, - elem.endFrame, - self.getEnumValue("csMiscType", elem, "csMiscType"), - ) - ) - case "RumbleList": - subData += self.getRumbleControllerCmd( - CutsceneCmdRumbleController( - None, - elem.startFrame, - elem.endFrame, - elem.rumbleSourceStrength, - elem.rumbleDuration, - elem.rumbleDecreaseRate, - ) - ) - case _: - raise PluginError("ERROR: Unknown Cutscene List Type!") - if entry.listType != "Transition": - listCmd = self.getGenericListCmd(ootEnumCSListTypeListC[entry.listType], entryTotal) - self.entryTotal += 1 - data += listCmd + subData - - return data - - def getC(self): - """Returns the cutscene data""" - - csData = CData() - declarationBase = f"CutsceneData {self.name}[]" - - if self.motionFrameCount > self.frameCount: - self.frameCount += self.motionFrameCount - self.frameCount - - # .h - csData.header = f"extern {declarationBase};\n" - - # .c - csData.source = ( - declarationBase - + " = {\n" - + (indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n") - + (self.getCutsceneData() if not self.motionOnly else "") - + self.getActorCueListData(False) - + self.getActorCueListData(True) - + self.getCameraShotData() - + "};\n\n" - ) - - return csData - - -@dataclass -class SceneCutscene(Base): - """This class hosts cutscene data""" - - props: OOTSceneHeaderProperty - headerIndex: int - useMacros: bool - - entries: list[Cutscene] = field(default_factory=list) - csObj: Optional[Object] = None - cutsceneObjects: list[Object] = field(default_factory=list) - - def __post_init__(self): - self.csObj: Object = self.props.csWriteObject - self.cutsceneObjects = [csObj for csObj in self.props.extraCutscenes] - - if self.headerIndex > 0 and len(self.cutsceneObjects) > 0: - raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") - - self.cutsceneObjects.insert(0, self.csObj) - for csObj in self.cutsceneObjects: - if csObj is not None: - if csObj.ootEmptyType != "Cutscene": - raise PluginError( - "ERROR: Object selected as cutscene is wrong type, must be empty with Cutscene type" - ) - elif csObj.parent is not None: - raise PluginError("ERROR: Cutscene empty object should not be parented to anything") - - writeType = self.props.csWriteType - csWriteCustom = None - if writeType == "Custom": - csWriteCustom = getCustomProperty(self.props, "csWriteCustom") - - if self.props.writeCutscene: - self.entries.append( - Cutscene(self.getCutsceneName(csObj, csWriteCustom), csObj, self.useMacros, False) - ) - - def getCutsceneName(self, csObj: Object, customName: Optional[str] = None) -> str: - """Returns the cutscene's name""" - - return customName if customName is not None else csObj.name.removeprefix("Cutscene.") - - def getCmd(self): - """Returns the cutscene data scene command""" - - csDataName = self.getCutsceneName(self.csObj) - return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index d43323604..c22ed998e 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -5,8 +5,8 @@ from ....utility import CData from ...scene.properties import OOTSceneHeaderProperty from ..base import Base +from ..cutscene import SceneCutscene from .general import SceneLighting, SceneInfos, SceneExits -from .cutscene import SceneCutscene from .actors import SceneTransitionActors, SceneEntranceActors, SceneSpawns from .pathways import ScenePathways From bff524fdb51141969e5466910fa9d86df2317c65 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:45:03 +0100 Subject: [PATCH 61/98] sort object lists by name --- .../oot/exporter/collision/camera.py | 10 ++----- .../oot/exporter/collision/waterbox.py | 6 ++-- fast64_internal/oot/exporter/room/header.py | 5 ++-- fast64_internal/oot/exporter/scene/actors.py | 11 ++----- .../oot/exporter/scene/pathways.py | 7 ++--- fast64_internal/oot/exporter/scene/rooms.py | 5 ++-- fast64_internal/oot/oot_utility.py | 30 ++++++++++++++++++- 7 files changed, 43 insertions(+), 31 deletions(-) diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 268b3702d..3b8abe925 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -4,6 +4,7 @@ from mathutils import Quaternion, Matrix from bpy.types import Object from ....utility import PluginError, CData, indent +from ...oot_utility import getObjectList from ...oot_collision_classes import decomp_compat_map_CameraSType from ...collision.properties import OOTCameraPositionProperty from ..base import Base @@ -89,12 +90,7 @@ class BgCamInformations(Base): def initCrawlspaceList(self): """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" - crawlspaceObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Crawlspace" - ] - + crawlspaceObjList = getObjectList(self.sceneObj.children_recursive, "CURVE", splineType="Crawlspace") for obj in crawlspaceObjList: if self.validateCurveData(obj): self.crawlspacePosList.append( @@ -110,7 +106,7 @@ def initCrawlspaceList(self): def initBgCamInfoList(self): """Returns a list of camera informations from camera objects""" - camObjList: list[Object] = [obj for obj in self.sceneObj.children_recursive if obj.type == "CAMERA"] + camObjList = getObjectList(self.sceneObj.children_recursive, "CAMERA") camPosData: dict[int, CameraData] = {} camInfoData: dict[int, CameraInfo] = {} diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 13c99ec56..d6fed7213 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -2,6 +2,7 @@ from typing import Optional from mathutils import Matrix from bpy.types import Object +from ...oot_utility import getObjectList from ....utility import CData, checkIdentityRotation, indent from ..base import Base @@ -88,10 +89,7 @@ class WaterBoxes(Base): waterboxList: list[WaterBox] = field(default_factory=list) def __post_init__(self): - waterboxObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Water Box" - ] - + waterboxObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Water Box") for waterboxObj in waterboxObjList: emptyScale = waterboxObj.empty_display_size pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 9a995a7ae..1dbbe81d0 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -3,6 +3,7 @@ from mathutils import Matrix from bpy.types import Object from ....utility import CData, indent +from ...oot_utility import getObjectList from ...oot_constants import ootData from ...room.properties import OOTRoomHeaderProperty from ..base import Base, Actor @@ -139,9 +140,7 @@ class RoomActors(HeaderBase): actorList: list[Actor] = field(default_factory=list) def __post_init__(self): - actorObjList: list[Object] = [ - obj for obj in self.roomObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Actor" - ] + actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor") for obj in actorObjList: actorProp = obj.ootActorProperty if not self.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 370017506..e03d90558 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -3,6 +3,7 @@ from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent +from ...oot_utility import getObjectList from ...oot_constants import ootData from ...scene.properties import OOTSceneHeaderProperty from ..base import Base, Actor @@ -47,11 +48,7 @@ class SceneTransitionActors(Base): entries: list[TransitionActor] = field(default_factory=list) def __post_init__(self): - actorObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "EMPTY" and obj.ootEmptyType == "Transition Actor" - ] + actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") for obj in actorObjList: roomObj = self.getRoomObjectFromChild(obj) if roomObj is None: @@ -146,9 +143,7 @@ def __post_init__(self): """Returns the entrance actor list based on empty objects with the type 'Entrance'""" entranceActorFromIndex: dict[int, EntranceActor] = {} - actorObjList: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Entrance" - ] + actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Entrance") for obj in actorObjList: roomObj = self.getRoomObjectFromChild(obj) if roomObj is None: diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index a0e4263ee..cdbd61d69 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -2,6 +2,7 @@ from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent +from ...oot_utility import getObjectList from ...scene.properties import OOTSceneHeaderProperty from ..base import Base @@ -49,11 +50,7 @@ class ScenePathways(Base): def __post_init__(self): pathFromIndex: dict[int, Path] = {} - pathObjList: list[Object] = [ - obj - for obj in self.sceneObj.children_recursive - if obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path" - ] + pathObjList = getObjectList(self.sceneObj.children_recursive, "CURVE", splineType="Path") for obj in pathObjList: relativeTransform = self.transform @ self.sceneObj.matrix_world.inverted() @ obj.matrix_world diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index b7ebd2553..16adf25bd 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -3,6 +3,7 @@ from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent +from ...oot_utility import getObjectList from ...oot_model_classes import OOTModel from ..room import Room @@ -25,9 +26,7 @@ def __post_init__(self): sceneName = self.scene.name.removesuffix("_scene") roomDict: dict[int, Room] = {} - roomObjs: list[Object] = [ - obj for obj in self.sceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" - ] + roomObjs = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Room") if len(roomObjs) == 0: raise PluginError("ERROR: The scene has no child empties with the 'Room' empty type.") diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 7b83e9cfc..3fef8656a 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -8,7 +8,7 @@ from bpy.types import Object from bpy.utils import register_class, unregister_class from bpy.types import Object -from typing import Callable +from typing import Callable, Optional from .oot_constants import ootSceneIDToName from ..utility import ( @@ -940,3 +940,31 @@ def getNewPath(type: str, isClosedShape: bool): bpy.context.view_layer.active_layer_collection.collection.objects.link(newPath) return newPath + + +def getObjectList( + objList: list[Object], objType: str, emptyType: Optional[str] = None, splineType: Optional[str] = None +): + """ + Returns a list containing objects matching ``objType``. Sorts by object name. + + Parameters: + - ``objList``: the list of objects to iterate through, usually ``obj.children_recursive`` + - ``objType``: the object's type (``EMPTY``, ``CURVE``, etc.) + - ``emptyType``: optional, filters the object by the given empty type + - ``splineType``: optional, filters the object by the given spline type + """ + + ret: list[Object] = [] + for obj in objList: + cond = True + + if emptyType is not None: + cond = obj.ootEmptyType == emptyType + elif splineType is not None: + cond = obj.ootSplineProperty.splineType == splineType + + if obj.type == objType and cond: + ret.append(obj) + ret.sort(key=lambda o: o.name) + return ret From 72fd5fac76441bc9d30407553f244fb420301784 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 9 Jan 2024 01:54:28 +0100 Subject: [PATCH 62/98] added safety checks to cutscene export --- .../oot/exporter/cutscene/actor_cue.py | 17 ++- .../oot/exporter/cutscene/camera.py | 23 +++- .../oot/exporter/cutscene/common.py | 130 ++---------------- fast64_internal/oot/exporter/cutscene/misc.py | 37 ++++- fast64_internal/oot/exporter/cutscene/seq.py | 11 ++ fast64_internal/oot/exporter/cutscene/text.py | 22 ++- 6 files changed, 114 insertions(+), 126 deletions(-) diff --git a/fast64_internal/oot/exporter/cutscene/actor_cue.py b/fast64_internal/oot/exporter/cutscene/actor_cue.py index 4511ae667..2ebe62da6 100644 --- a/fast64_internal/oot/exporter/cutscene/actor_cue.py +++ b/fast64_internal/oot/exporter/cutscene/actor_cue.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from typing import Optional -from ....utility import indent +from ....utility import PluginError, indent from ...oot_constants import ootData from ...cutscene.motion.utility import getRotation, getInteger from .common import CutsceneCmdBase @@ -27,6 +27,15 @@ def __post_init__(self): self.endPos = [getInteger(self.params[9]), getInteger(self.params[10]), getInteger(self.params[11])] def getCmd(self): + self.validateFrames() + if self.actionID is None: + raise PluginError("ERROR: Action ID is None!") + if len(self.rot) == 0: + raise PluginError("ERROR: Rotation list is empty!") + if len(self.startPos) == 0: + raise PluginError("ERROR: Start Position list is empty!") + if len(self.endPos) == 0: + raise PluginError("ERROR: End Position list is empty!") return indent * 3 + ( f"CS_{'PLAYER' if self.isPlayer else 'ACTOR'}_CUE(" + f"{self.actionID}, {self.startFrame}, {self.endFrame}, " @@ -64,6 +73,12 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[1].strip()) def getCmd(self): + if self.commandType is None: + raise PluginError("ERROR: ``commandType`` is None!") + if self.entryTotal is None: + raise PluginError("ERROR: ``entryTotal`` is None!") + if len(self.entries) == 0: + raise PluginError("ERROR: No Actor Cue entry found!") return ( indent * 2 + ( diff --git a/fast64_internal/oot/exporter/cutscene/camera.py b/fast64_internal/oot/exporter/cutscene/camera.py index 813b28616..214c744a0 100644 --- a/fast64_internal/oot/exporter/cutscene/camera.py +++ b/fast64_internal/oot/exporter/cutscene/camera.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from typing import Optional -from ....utility import indent +from ....utility import PluginError, indent from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase @@ -25,9 +25,18 @@ def __post_init__(self): self.pos = [getInteger(self.params[4]), getInteger(self.params[5]), getInteger(self.params[6])] def getCmd(self): + if self.continueFlag is None: + raise PluginError("ERROR: ``continueFlag`` is None!") + if self.camRoll is None: + raise PluginError("ERROR: ``camRoll`` is None!") + if self.frame is None: + raise PluginError("ERROR: ``frame`` is None!") + if self.viewAngle is None: + raise PluginError("ERROR: ``viewAngle`` is None!") + if len(self.pos) == 0: + raise PluginError("ERROR: Pos list is empty!") return indent * 3 + ( - f"CS_CAM_POINT(" - + f"{self.continueFlag}, {self.camRoll}, {self.frame}, {self.viewAngle}f, " + f"CS_CAM_POINT({self.continueFlag}, {self.camRoll}, {self.frame}, {self.viewAngle}f, " + "".join(f"{pos}, " for pos in self.pos) + "0),\n" ) @@ -47,6 +56,8 @@ def __post_init__(self): self.endFrame = getInteger(self.params[1]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getCamListCmd("CS_CAM_EYE_SPLINE", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -66,6 +77,8 @@ def __post_init__(self): self.endFrame = getInteger(self.params[1]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getCamListCmd("CS_CAM_AT_SPLINE", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -85,6 +98,8 @@ def __post_init__(self): self.endFrame = getInteger(self.params[1]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getCamListCmd("CS_CAM_EYE_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -104,6 +119,8 @@ def __post_init__(self): self.endFrame = getInteger(self.params[1]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getCamListCmd("CS_CAM_AT_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) diff --git a/fast64_internal/oot/exporter/cutscene/common.py b/fast64_internal/oot/exporter/cutscene/common.py index 12aac1fc1..21da0eb7a 100644 --- a/fast64_internal/oot/exporter/cutscene/common.py +++ b/fast64_internal/oot/exporter/cutscene/common.py @@ -1,11 +1,8 @@ -import bpy - from dataclasses import dataclass -from bpy.types import Object from typing import Optional -from ....utility import indent +from ....utility import PluginError, indent from ...oot_constants import ootData -from ...cutscene.motion.utility import getBlenderPosition, getBlenderRotation, getInteger +from ...cutscene.motion.utility import getInteger @dataclass @@ -17,6 +14,12 @@ class CutsceneCmdBase: startFrame: Optional[int] = None endFrame: Optional[int] = None + def validateFrames(self, checkEndFrame: bool = True): + if self.startFrame is None: + raise PluginError("ERROR: Start Frame is None!") + if checkEndFrame and self.endFrame is None: + raise PluginError("ERROR: End Frame is None!") + def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): enum = ootData.enumData.enumByKey[enumKey] item = enum.itemById.get(self.params[index]) @@ -28,121 +31,16 @@ def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): return item.key if item is not None else self.params[index] def getGenericListCmd(self, cmdName: str, entryTotal: int): + if entryTotal is None: + raise PluginError(f"ERROR: ``{cmdName}``'s entry total is None!") return indent * 2 + f"{cmdName}({entryTotal}),\n" def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): + self.validateFrames() return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): + self.validateFrames() + if type is None: + raise PluginError("ERROR: Seq type is None!") return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" - - -class CutsceneObjectFactory: - """This class contains functions to create new Blender objects""" - - def getNewObject(self, name: str, data, selectObject: bool, parentObj: Object) -> Object: - newObj = bpy.data.objects.new(name=name, object_data=data) - bpy.context.view_layer.active_layer_collection.collection.objects.link(newObj) - if selectObject: - newObj.select_set(True) - bpy.context.view_layer.objects.active = newObj - newObj.parent = parentObj - newObj.location = [0.0, 0.0, 0.0] - newObj.rotation_euler = [0.0, 0.0, 0.0] - newObj.scale = [1.0, 1.0, 1.0] - return newObj - - def getNewEmptyObject(self, name: str, selectObject: bool, parentObj: Object): - return self.getNewObject(name, None, selectObject, parentObj) - - def getNewArmatureObject(self, name: str, selectObject: bool, parentObj: Object): - newArmatureData = bpy.data.armatures.new(name) - newArmatureData.display_type = "STICK" - newArmatureData.show_names = True - newArmatureObject = self.getNewObject(name, newArmatureData, selectObject, parentObj) - return newArmatureObject - - def getNewCutsceneObject(self, name: str, frameCount: int, parentObj: Object): - newCSObj = self.getNewEmptyObject(name, True, parentObj) - newCSObj.ootEmptyType = "Cutscene" - newCSObj.ootCutsceneProperty.csEndFrame = frameCount - return newCSObj - - def getNewActorCueListObject(self, name: str, commandType: str, parentObj: Object): - newActorCueListObj = self.getNewEmptyObject(name, False, parentObj) - newActorCueListObj.ootEmptyType = f"CS {'Player' if 'Player' in name else 'Actor'} Cue List" - cmdEnum = ootData.enumData.enumByKey["csCmd"] - - if commandType == "Player": - commandType = "player_cue" - - index = cmdEnum.itemByKey[commandType].index if commandType in cmdEnum.itemByKey else int(commandType, base=16) - item = cmdEnum.itemByIndex.get(index) - - if item is not None: - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = item.key - else: - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = "Custom" - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandTypeCustom = commandType - - return newActorCueListObj - - def getNewActorCueObject( - self, - name: str, - startFrame: int, - actionID: int | str, - location: list[int], - rot: list[str], - parentObj: Object, - ): - isDummy = "(D)" in name - isPlayer = not isDummy and not "Actor" in name - - newActorCueObj = self.getNewEmptyObject(name, False, parentObj) - newActorCueObj.location = getBlenderPosition(location, bpy.context.scene.ootBlenderScale) - newActorCueObj.empty_display_type = "ARROWS" - newActorCueObj.rotation_mode = "XZY" - newActorCueObj.rotation_euler = getBlenderRotation(rot) - emptyType = "Dummy" if isDummy else "Player" if isPlayer else "Actor" - newActorCueObj.ootEmptyType = f"CS {emptyType} Cue" - newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame - - item = None - if isPlayer: - playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] - if isinstance(actionID, int): - item = playerEnum.itemByIndex.get(actionID) - else: - item = playerEnum.itemByKey.get(actionID) - - if item is not None: - newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = item.key - elif not isDummy: - if isPlayer: - newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = "Custom" - - if isinstance(actionID, int): - cueActionID = f"0x{actionID:04X}" - else: - cueActionID = actionID - - newActorCueObj.ootCSMotionProperty.actorCueProp.cueActionID = cueActionID - - return newActorCueObj - - def getNewCameraObject( - self, name: str, displaySize: float, clipStart: float, clipEnd: float, alpha: float, parentObj: Object - ): - newCamera = bpy.data.cameras.new(name) - newCameraObj = self.getNewObject(name, newCamera, False, parentObj) - newCameraObj.data.display_size = displaySize - newCameraObj.data.clip_start = clipStart - newCameraObj.data.clip_end = clipEnd - newCameraObj.data.passepartout_alpha = alpha - return newCameraObj - - def getNewActorCuePreviewObject(self, name: str, selectObject, parentObj: Object): - newPreviewObj = self.getNewEmptyObject(name, selectObject, parentObj) - newPreviewObj.ootEmptyType = f"CS {'Actor' if 'Actor' in name else 'Player'} Cue Preview" - return newPreviewObj diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py index ba6748f4e..3672e45b5 100644 --- a/fast64_internal/oot/exporter/cutscene/misc.py +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from typing import Optional -from ....utility import indent +from ....utility import PluginError, indent from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase @@ -19,6 +19,9 @@ def __post_init__(self): self.type = self.getEnumValue("csMiscType", 0) def getCmd(self): + self.validateFrames() + if self.type is None: + raise PluginError("ERROR: Misc Type is None!") return indent * 3 + (f"CS_MISC({self.type}, {self.startFrame}, {self.endFrame}" + ", 0" * 11 + "),\n") @@ -39,6 +42,9 @@ def __post_init__(self): self.lightSetting -= 1 def getCmd(self): + self.validateFrames(False) + if self.lightSetting is None: + raise PluginError("ERROR: Light Setting is None!") return indent * 3 + (f"CS_LIGHT_SETTING({self.lightSetting}, {self.startFrame}" + ", 0" * 9 + "),\n") @@ -58,6 +64,11 @@ def __post_init__(self): self.minute = getInteger(self.params[4]) def getCmd(self): + self.validateFrames(False) + if self.hour is None: + raise PluginError("ERROR: ``hour`` is None!") + if self.minute is None: + raise PluginError("ERROR: ``minute`` is None!") return indent * 3 + f"CS_TIME(0, {self.startFrame}, 0, {self.hour}, {self.minute}),\n" @@ -79,10 +90,16 @@ def __post_init__(self): self.decreaseRate = getInteger(self.params[5]) def getCmd(self): + self.validateFrames(False) + if self.sourceStrength is None: + raise PluginError("ERROR: ``sourceStrength`` is None!") + if self.duration is None: + raise PluginError("ERROR: ``duration`` is None!") + if self.decreaseRate is None: + raise PluginError("ERROR: ``decreaseRate`` is None!") return indent * 3 + ( f"CS_RUMBLE_CONTROLLER(" - + f"0, {self.startFrame}, 0, " - + f"{self.sourceStrength}, {self.duration}, {self.decreaseRate}, 0, 0),\n" + + f"0, {self.startFrame}, 0, {self.sourceStrength}, {self.duration}, {self.decreaseRate}, 0, 0),\n" ) @@ -100,6 +117,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_MISC_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -119,6 +138,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_LIGHT_SETTING_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -138,6 +159,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_TIME_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -157,6 +180,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_RUMBLE_CONTROLLER_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -176,6 +201,9 @@ def __post_init__(self): self.startFrame = getInteger(self.params[1]) def getCmd(self): + self.validateFrames(False) + if self.id is None: + raise PluginError("ERROR: Destination ID is None!") return indent * 2 + f"CS_DESTINATION({self.id}, {self.startFrame}, 0),\n" @@ -194,4 +222,7 @@ def __post_init__(self): self.type = self.getEnumValue("csTransitionType", 0) def getCmd(self): + self.validateFrames() + if self.type is None: + raise PluginError("ERROR: Transition type is None!") return indent * 2 + f"CS_TRANSITION({self.type}, {self.startFrame}, {self.endFrame}),\n" diff --git a/fast64_internal/oot/exporter/cutscene/seq.py b/fast64_internal/oot/exporter/cutscene/seq.py index 539d45cbc..7ad043f24 100644 --- a/fast64_internal/oot/exporter/cutscene/seq.py +++ b/fast64_internal/oot/exporter/cutscene/seq.py @@ -1,5 +1,6 @@ from dataclasses import dataclass, field from typing import Optional +from ....utility import PluginError from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase @@ -20,6 +21,9 @@ def __post_init__(self): self.seqId = self.getEnumValue("seqId", 0, self.isLegacy) def getCmd(self): + self.validateFrames() + if self.type is None: + raise PluginError("ERROR: Type is None!") return self.getGenericSeqCmd(f"CS_{self.type.upper()}_SEQ", self.seqId, self.startFrame, self.endFrame) @@ -38,6 +42,7 @@ def __post_init__(self): self.seqPlayer = self.getEnumValue(self.enumKey, 0) def getCmd(self): + self.validateFrames() return self.getGenericSeqCmd("CS_FADE_OUT_SEQ", self.seqPlayer, self.startFrame, self.endFrame) @@ -56,6 +61,10 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") + if self.type is None: + raise PluginError("ERROR: Seq Type is None!") return self.getGenericListCmd(f"CS_{self.type.upper()}_SEQ_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -75,6 +84,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_FADE_OUT_SEQ_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) diff --git a/fast64_internal/oot/exporter/cutscene/text.py b/fast64_internal/oot/exporter/cutscene/text.py index 5fd4aae19..eaf7382b5 100644 --- a/fast64_internal/oot/exporter/cutscene/text.py +++ b/fast64_internal/oot/exporter/cutscene/text.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from typing import Optional -from ....utility import indent +from ....utility import PluginError, indent from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase @@ -26,6 +26,15 @@ def __post_init__(self): self.altTextId2 = getInteger(self.params[5]) def getCmd(self): + self.validateFrames() + if self.textId is None: + raise PluginError("ERROR: ``textId`` is None!") + if self.type is None: + raise PluginError("ERROR: ``type`` is None!") + if self.altTextId1 is None: + raise PluginError("ERROR: ``altTextId1`` is None!") + if self.altTextId2 is None: + raise PluginError("ERROR: ``altTextId2`` is None!") return indent * 3 + ( f"CS_TEXT(" + f"{self.textId}, {self.startFrame}, {self.endFrame}, {self.type}, {self.altTextId1}, {self.altTextId2}" @@ -46,6 +55,7 @@ def __post_init__(self): self.endFrame = getInteger(self.params[1]) def getCmd(self): + self.validateFrames() return indent * 3 + f"CS_TEXT_NONE({self.startFrame}, {self.endFrame}),\n" @@ -66,10 +76,14 @@ def __post_init__(self): self.messageId = getInteger(self.params[3]) def getCmd(self): + self.validateFrames() + if self.ocarinaActionId is None: + raise PluginError("ERROR: ``ocarinaActionId`` is None!") + if self.messageId is None: + raise PluginError("ERROR: ``messageId`` is None!") return indent * 3 + ( f"CS_TEXT_OCARINA_ACTION(" - + f"{self.ocarinaActionId}, {self.startFrame}, " - + f"{self.endFrame}, {self.messageId}" + + f"{self.ocarinaActionId}, {self.startFrame}, {self.endFrame}, {self.messageId}" + "),\n" ) @@ -88,6 +102,8 @@ def __post_init__(self): self.entryTotal = getInteger(self.params[0]) def getCmd(self): + if len(self.entries) == 0: + raise PluginError("ERROR: Entry list is empty!") return self.getGenericListCmd("CS_TEXT_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) From db272b51c5942385f750e61f3e936e0cf74a9f08 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 9 Jan 2024 03:39:59 +0100 Subject: [PATCH 63/98] bugfixes (actors, objects, headers none) --- fast64_internal/oot/exporter/classes.py | 6 ++++-- fast64_internal/oot/exporter/main.py | 2 +- fast64_internal/oot/exporter/room/header.py | 2 +- fast64_internal/oot/exporter/room/main.py | 2 +- fast64_internal/oot/exporter/scene/__init__.py | 12 +++++++++--- fast64_internal/oot/oot_object.py | 17 ++++++++++------- fast64_internal/oot/oot_utility.py | 10 +++++++++- 7 files changed, 35 insertions(+), 16 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 742ecc890..a44009f46 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -64,6 +64,7 @@ def setIncludeData(self): """Adds includes at the beginning of each file to write""" sceneInclude = f'#include "{self.name}.h"\n\n\n' + csInclude = sceneInclude[:-2] + '#include "z64cutscene.h"\n' + '#include "z64cutscene_commands.h"\n\n\n' for roomData in self.roomList.values(): roomData.roomMain = self.getSourceWithSceneInclude(sceneInclude, roomData.roomMain) @@ -72,13 +73,14 @@ def setIncludeData(self): roomData.roomModelInfo = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModelInfo) roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel) - self.sceneMain = self.getSourceWithSceneInclude(sceneInclude, self.sceneMain) + self.sceneMain = self.getSourceWithSceneInclude( + sceneInclude if not self.singleFileExport else csInclude, self.sceneMain + ) self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) if not self.singleFileExport: self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) if self.hasCutscenes: - csInclude = sceneInclude[:-2] + '#include "z64cutscene.h"\n' + '#include "z64cutscene_commands.h"\n\n\n' for i in range(len(self.sceneCutscenes)): self.sceneCutscenes[i] = self.getSourceWithSceneInclude(csInclude, self.sceneCutscenes[i]) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 8df318ab0..85b19cc46 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -86,7 +86,7 @@ def getNewScene(self): if newScene.mainHeader.cutscene is not None: self.hasCutscenes = len(newScene.mainHeader.cutscene.entries) > 0 - if not self.hasCutscenes: + if not self.hasCutscenes and newScene.altHeader is not None: for cs in newScene.altHeader.cutscenes: if len(cs.cutscene.entries) > 0: self.hasCutscenes = True diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 1dbbe81d0..f16577160 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -140,7 +140,7 @@ class RoomActors(HeaderBase): actorList: list[Actor] = field(default_factory=list) def __post_init__(self): - actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor") + actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor", parentObj=self.roomObj) for obj in actorObjList: actorProp = obj.ootActorProperty if not self.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 32b5be4e6..cd4750a55 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -75,7 +75,7 @@ def __post_init__(self): self.hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else self.hasAlternateHeaders self.altHeader = altHeader if self.hasAlternateHeaders else None - addMissingObjectsToAllRoomHeadersNew(self.roomObj, self, ootData) + addMissingObjectsToAllRoomHeadersNew(self.roomObj, self) # Mesh stuff self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index edea8d584..c68f8d822 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -179,11 +179,17 @@ def getSceneCutscenesC(self): csDataList: list[CData] = [] headers: list[SceneHeader] = [ self.mainHeader, - self.altHeader.childNight, - self.altHeader.adultDay, - self.altHeader.adultNight, ] + if self.altHeader is not None: + headers.extend( + [ + self.altHeader.childNight, + self.altHeader.adultDay, + self.altHeader.adultNight, + ] + ) + for curHeader in headers: if curHeader is not None: for csEntry in curHeader.cutscene.entries: diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index 9490530b5..fd0f143cd 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -2,6 +2,7 @@ from ..utility import ootGetSceneOrRoomHeader from .data import OoT_Data from .oot_level_classes import OOTRoom +from .oot_constants import ootData def addMissingObjectToProp(roomObj: Object, headerIndex: int, objectKey: str): @@ -41,7 +42,7 @@ def addMissingObjectsToAllRoomHeaders(roomObj: Object, room: OOTRoom, ootData: O addMissingObjectsToRoomHeader(roomObj, room.cutsceneHeaders[i], ootData, i + 4) -def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, ootData: OoT_Data, headerIndex: int): +def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, headerIndex: int): """Adds missing objects to the object list""" if len(curHeader.actors.actorList) > 0: for roomActor in curHeader.actors.actorList: @@ -55,15 +56,17 @@ def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, ootData: OoT_Da addMissingObjectToProp(roomObj, headerIndex, objKey) -def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room, ootData: OoT_Data): +def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room): """ Adds missing objects (required by actors) to all headers of a room, both to the roomObj empty and the exported room """ + headers = [room.mainHeader] if room.altHeader is not None: - sceneLayers = [room.mainHeader, room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight] + headers.extend([room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight]) if len(room.altHeader.cutscenes) > 0: - sceneLayers.extend(room.altHeader.cutscenes) - for i, layer in enumerate(sceneLayers): - if layer is not None: - addMissingObjectsToRoomHeaderNew(roomObj, layer, ootData, i) + headers.extend(room.altHeader.cutscenes) + + for i, curHeader in enumerate(headers): + if curHeader is not None: + addMissingObjectsToRoomHeaderNew(roomObj, curHeader, i) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 3fef8656a..75aa7f240 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -943,7 +943,11 @@ def getNewPath(type: str, isClosedShape: bool): def getObjectList( - objList: list[Object], objType: str, emptyType: Optional[str] = None, splineType: Optional[str] = None + objList: list[Object], + objType: str, + emptyType: Optional[str] = None, + splineType: Optional[str] = None, + parentObj: Object = None, ): """ Returns a list containing objects matching ``objType``. Sorts by object name. @@ -953,6 +957,7 @@ def getObjectList( - ``objType``: the object's type (``EMPTY``, ``CURVE``, etc.) - ``emptyType``: optional, filters the object by the given empty type - ``splineType``: optional, filters the object by the given spline type + - ``parentObj``: optional, checks if the found object is parented to ``parentObj`` """ ret: list[Object] = [] @@ -964,6 +969,9 @@ def getObjectList( elif splineType is not None: cond = obj.ootSplineProperty.splineType == splineType + if parentObj is not None: + cond = cond and obj.parent is not None and obj.parent.name == parentObj.name + if obj.type == objType and cond: ret.append(obj) ret.sort(key=lambda o: o.name) From 8497ae367df92b4bd87a6f0baeb57a7364d9a8fc Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 10 Jan 2024 19:50:32 +0100 Subject: [PATCH 64/98] autoset cs name, moved cs export bools to file settings --- fast64_internal/oot/cutscene/panels.py | 20 ------------------ .../oot/exporter/cutscene/__init__.py | 20 ++++++++++-------- fast64_internal/oot/file_settings.py | 21 ++++++++++++++++++- fast64_internal/oot/scene/operators.py | 2 +- fast64_internal/oot/scene/properties.py | 3 --- 5 files changed, 32 insertions(+), 34 deletions(-) diff --git a/fast64_internal/oot/cutscene/panels.py b/fast64_internal/oot/cutscene/panels.py index 76685144a..44f3de3e5 100644 --- a/fast64_internal/oot/cutscene/panels.py +++ b/fast64_internal/oot/cutscene/panels.py @@ -27,11 +27,6 @@ def draw(self, context): exportBox = layout.box() exportBox.label(text="Cutscene Exporter") - col = exportBox.column() - if not context.scene.fast64.oot.hackerFeaturesEnabled: - col.prop(context.scene, "useDecompFeatures") - col.prop(context.scene, "exportMotionOnly") - prop_split(exportBox, context.scene, "ootCutsceneExportPath", "Export To") activeObj = context.view_layer.objects.active @@ -66,25 +61,10 @@ def draw(self, context): def cutscene_panels_register(): - Scene.useDecompFeatures = BoolProperty( - name="Use Decomp for Export", description="Use names and macros from decomp when exporting", default=True - ) - - Scene.exportMotionOnly = BoolProperty( - name="Export Motion Data Only", - description=( - "Export everything or only the camera and actor motion data.\n" - + "This will insert the data into the cutscene." - ), - ) - for cls in oot_cutscene_panel_classes: register_class(cls) def cutscene_panels_unregister(): - del Scene.exportMotionOnly - del Scene.useDecompFeatures - for cls in oot_cutscene_panel_classes: unregister_class(cls) diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index c116f3259..d19eca85b 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -1,3 +1,5 @@ +import bpy + from dataclasses import dataclass, field from typing import Optional from bpy.types import Object @@ -19,9 +21,9 @@ class Cutscene: """This class defines a cutscene, including its data and its informations""" - name: str csObj: Object useMacros: bool + name: Optional[str] = None motionOnly: bool = False data: Optional[CutsceneData] = None totalEntries: int = 0 @@ -31,6 +33,8 @@ class Cutscene: def __post_init__(self): # when csObj is None it means we're in import context if self.csObj is not None and self.data is None: + if self.name is None: + self.name = self.csObj.name.removeprefix("Cutscene.").replace(".", "_") self.data = CutsceneData(self.csObj, self.useMacros, self.motionOnly) self.totalEntries = self.data.totalEntries self.frameCount = self.data.frameCount @@ -127,15 +131,13 @@ def __post_init__(self): csWriteCustom = getCustomProperty(self.props, "csWriteCustom") if self.props.writeCutscene: - self.entries.append(Cutscene(self.getCutsceneName(csObj, csWriteCustom), csObj, self.useMacros)) - - def getCutsceneName(self, csObj: Object, customName: Optional[str] = None) -> str: - """Returns the cutscene's name""" - - return customName if customName is not None else csObj.name.removeprefix("Cutscene.") + # if csWriteCustom is None then the name will auto-set from the csObj passed in the class + self.entries.append( + Cutscene(csObj, self.useMacros, csWriteCustom, bpy.context.scene.exportMotionOnly) + ) def getCmd(self): """Returns the cutscene data scene command""" - csDataName = self.getCutsceneName(self.csObj) - return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName}),\n" + # entry No. 0 is always self.csObj + return indent + f"SCENE_CMD_CUTSCENE_DATA({self.entries[0].name}),\n" diff --git a/fast64_internal/oot/file_settings.py b/fast64_internal/oot/file_settings.py index 3cf6c8701..1905877d5 100644 --- a/fast64_internal/oot/file_settings.py +++ b/fast64_internal/oot/file_settings.py @@ -1,5 +1,5 @@ from bpy.utils import register_class, unregister_class -from bpy.props import StringProperty, FloatProperty +from bpy.props import StringProperty, FloatProperty, BoolProperty from bpy.types import Scene from ..utility import prop_split from ..render_settings import on_update_render_settings @@ -21,6 +21,10 @@ def draw(self, context): col.prop(context.scene.fast64.oot, "headerTabAffectsVisibility") col.prop(context.scene.fast64.oot, "hackerFeaturesEnabled") + if not context.scene.fast64.oot.hackerFeaturesEnabled: + col.prop(context.scene, "useDecompFeatures") + col.prop(context.scene, "exportMotionOnly") + oot_classes = (OOT_FileSettingsPanel,) @@ -32,10 +36,25 @@ def file_register(): Scene.ootBlenderScale = FloatProperty(name="Blender To OOT Scale", default=10, update=on_update_render_settings) Scene.ootDecompPath = StringProperty(name="Decomp Folder", subtype="FILE_PATH") + Scene.useDecompFeatures = BoolProperty( + name="Use Decomp for CS Export", description="Use names and macros from decomp when exporting", default=True + ) + + Scene.exportMotionOnly = BoolProperty( + name="Export CS Motion Data Only", + description=( + "Export everything or only the camera and actor motion data.\n" + + "This will insert the data into the cutscene." + ), + default=False, + ) + def file_unregister(): for cls in reversed(oot_classes): unregister_class(cls) + del Scene.exportMotionOnly + del Scene.useDecompFeatures del Scene.ootBlenderScale del Scene.ootDecompPath diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 881b5681f..0fe495f7e 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -184,7 +184,7 @@ def execute(self, context): bootOptions if hackerFeaturesEnabled else None, settings.singleFile, TextureExportSettings(False, context.scene.saveTextures, None, None), - settings.useMacros, + context.scene.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, ).export() else: ootExportSceneToC( diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index 35f852890..2ed6c1926 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -490,7 +490,6 @@ class OOTExportSceneSettingsProperty(PropertyGroup): option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE") useNewExporter: BoolProperty(name="Use Experimental Exporter", default=True) - useMacros: BoolProperty(name="Use Decomp Macros", default=True) def draw_props(self, layout: UILayout): if self.customExport: @@ -507,8 +506,6 @@ def draw_props(self, layout: UILayout): layout.prop(self, "singleFile") layout.prop(self, "customExport") layout.prop(self, "useNewExporter") - if self.useNewExporter: - layout.prop(self, "useMacros") class OOTImportSceneSettingsProperty(PropertyGroup): From 7903fde2212e5e44ceccd08149c38582ba4172f1 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Wed, 10 Jan 2024 22:35:35 +0100 Subject: [PATCH 65/98] more bugfixes --- fast64_internal/oot/exporter/cutscene/__init__.py | 4 +++- fast64_internal/oot/exporter/main.py | 2 +- fast64_internal/oot/exporter/other/spec.py | 7 ++++--- fast64_internal/oot/exporter/scene/__init__.py | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index d19eca85b..20bf3f1ce 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -85,7 +85,7 @@ def getC(self): declarationBase + " = {\n" + (indent + f"CS_BEGIN_CUTSCENE({self.totalEntries}, {self.frameCount}),\n") - + (self.data.destination.getCmd() if self.motionOnly else "") + + (self.data.destination.getCmd() if self.data.destination is not None else "") + "".join(entry.getCmd() for curList in dataListNames for entry in getattr(self.data, curList)) + (indent + "CS_END(),\n") + "};\n\n" @@ -138,6 +138,8 @@ def __post_init__(self): def getCmd(self): """Returns the cutscene data scene command""" + if len(self.entries) == 0: + raise PluginError("ERROR: Cutscene entry list is empty!") # entry No. 0 is always self.csObj return indent + f"SCENE_CMD_CUTSCENE_DATA({self.entries[0].name}),\n" diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 85b19cc46..1036b2774 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -117,7 +117,7 @@ def export(self): self.textureExportSettings.includeDir = sceneInclude self.textureExportSettings.exportPath = self.path self.sceneFile = self.scene.getNewSceneFile(self.path, self.isSingleFile, self.textureExportSettings) - self.hasSceneTextures = len(self.sceneFile.sceneTextures) > 0 + self.hasSceneTextures = self.sceneFile.sceneTextures is not None and len(self.sceneFile.sceneTextures) > 0 if not isCustomExport: writeTextureArraysExistingScene(self.scene.model, exportPath, sceneInclude + self.sceneName + "_scene.h") diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py index ed981bacd..49379f2a4 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/other/spec.py @@ -76,7 +76,9 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" sceneName = exporter.scene.name - sceneTexturesSeg = indent + f'include "{includeDir}/{sceneName}_tex.o"\n' + sceneTexturesSeg = ( + (indent + f'include "{includeDir}/{sceneName}_tex.o"\n') if exporter.hasSceneTextures else "" + ) if exporter.isSingleFile: specEntries.insert( firstIndex, @@ -110,8 +112,7 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + (indent + f'include "{includeDir}/{sceneName}_col.o"\n') ) - if exporter.hasSceneTextures: - sceneSegInclude += sceneTexturesSeg + sceneSegInclude += sceneTexturesSeg if exporter.hasCutscenes: for i in range(len(exporter.sceneFile.sceneCutscenes)): diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index c68f8d822..79849e07f 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -189,6 +189,7 @@ def getSceneCutscenesC(self): self.altHeader.adultNight, ] ) + headers.extend(self.altHeader.cutscenes) for curHeader in headers: if curHeader is not None: From d6e851ef41224a0baef321a8f690d39b7889d009 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:52:08 +0100 Subject: [PATCH 66/98] fixed collision issues --- fast64_internal/oot/exporter/collision/__init__.py | 11 ++++++++++- fast64_internal/oot/exporter/collision/polygons.py | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 580df2e75..3603ff1d7 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -100,6 +100,15 @@ def getCollisionData(self): ) distance = convertIntTo2sComplement(distance, 2, True) + # TODO: can this be improved? + nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2) + ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2) + nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2) + magSqr = nx * nx + ny * ny + nz * nz + if magSqr <= 0: + print("INFO: Ignore denormalized triangle.") + continue + indices: list[int] = [] for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: vertexIndex = self.getVertexIndex(pos, vertexList) @@ -157,7 +166,7 @@ def getCollisionData(self): self.useMacros, ) - if surfaceType not in colPolyFromSurfaceType: + if surfaceType not in colPolyFromSurfaceType.keys(): colPolyFromSurfaceType[surfaceType] = [] colPolyFromSurfaceType[surfaceType].append( diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index 3c8b65702..e4caa4727 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -18,6 +18,14 @@ class CollisionPoly: useMacros: bool type: Optional[int] = None + def __post_init__(self): + if self.normal[0] == self.normal[2] == 0.0: + raise PluginError("ERROR: Normal X and Z are both 0, this will result in a crash in-game.") + + for i, val in enumerate(self.normal): + if val < -1.0 or val > 1.0: + raise PluginError(f"ERROR: Invalid value for normal {['X', 'Y', 'Z'][i]}! (``{val}``)") + def getFlags_vIA(self): """Returns the value of ``flags_vIA``""" @@ -56,7 +64,7 @@ def getEntryC(self): self.getFlags_vIA(), self.getFlags_vIB(), f"COLPOLY_VTX_INDEX({vtxId})" if self.useMacros else f"{vtxId} & 0x1FFF", - ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal), + ("{ " + ", ".join(f"COLPOLY_SNORMAL({val})" for val in self.normal) + " }"), f"{self.dist}", ) ) From efdee235cd457be93c20ef48f78b401395b5e3a6 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 12 Jan 2024 01:11:38 +0100 Subject: [PATCH 67/98] removed useless check --- fast64_internal/oot/exporter/collision/polygons.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index e4caa4727..7ccd43da3 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -19,9 +19,6 @@ class CollisionPoly: type: Optional[int] = None def __post_init__(self): - if self.normal[0] == self.normal[2] == 0.0: - raise PluginError("ERROR: Normal X and Z are both 0, this will result in a crash in-game.") - for i, val in enumerate(self.normal): if val < -1.0 or val > 1.0: raise PluginError(f"ERROR: Invalid value for normal {['X', 'Y', 'Z'][i]}! (``{val}``)") From 2f2bba822fd7219dbced585e4a36c7249abd22bf Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:39:03 +0100 Subject: [PATCH 68/98] fixes and minor improvements --- fast64_internal/oot/exporter/classes.py | 11 ++++++----- fast64_internal/oot/exporter/other/spec.py | 7 +------ fast64_internal/oot/exporter/scene/actors.py | 1 + fast64_internal/oot/file_settings.py | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index a44009f46..41cb1230d 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -74,11 +74,11 @@ def setIncludeData(self): roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel) self.sceneMain = self.getSourceWithSceneInclude( - sceneInclude if not self.singleFileExport else csInclude, self.sceneMain + sceneInclude if not self.hasCutscenes else csInclude, self.sceneMain ) - self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) if not self.singleFileExport: + self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) if self.hasCutscenes: for i in range(len(self.sceneCutscenes)): @@ -97,15 +97,16 @@ def write(self): if self.hasCutscenes: self.sceneMain += "".join(cs for cs in self.sceneCutscenes) self.sceneMain += self.sceneCollision + if self.hasSceneTextures: + self.sceneMain += self.sceneTextures else: sceneMainPath = f"{self.name}_main.c" writeFile(os.path.join(self.path, f"{self.name}_col.c"), self.sceneCollision) if self.hasCutscenes: for i, cs in enumerate(self.sceneCutscenes): writeFile(os.path.join(self.path, f"{self.name}_cs_{i}.c"), cs) - - if self.hasSceneTextures: - writeFile(os.path.join(self.path, f"{self.name}_tex.c"), self.sceneTextures) + if self.hasSceneTextures: + writeFile(os.path.join(self.path, f"{self.name}_tex.c"), self.sceneTextures) writeFile(os.path.join(self.path, sceneMainPath), self.sceneMain) diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py index 49379f2a4..720018aad 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/other/spec.py @@ -76,9 +76,6 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" sceneName = exporter.scene.name - sceneTexturesSeg = ( - (indent + f'include "{includeDir}/{sceneName}_tex.o"\n') if exporter.hasSceneTextures else "" - ) if exporter.isSingleFile: specEntries.insert( firstIndex, @@ -86,7 +83,6 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + compressFlag + (indent + "romalign 0x1000\n") + (indent + f'include "{includeDir}/{sceneName}.o"\n') - + sceneTexturesSeg + (indent + "number 2\n"), ) @@ -110,10 +106,9 @@ def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + (indent + "romalign 0x1000\n") + (indent + f'include "{includeDir}/{sceneName}_main.o"\n') + (indent + f'include "{includeDir}/{sceneName}_col.o"\n') + + ((indent + f'include "{includeDir}/{sceneName}_tex.o"\n') if exporter.hasSceneTextures else "") ) - sceneSegInclude += sceneTexturesSeg - if exporter.hasCutscenes: for i in range(len(exporter.sceneFile.sceneCutscenes)): sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_cs_{i}.o"\n' diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index e03d90558..7dcd8e60a 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -49,6 +49,7 @@ class SceneTransitionActors(Base): def __post_init__(self): actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") + actorObjList.sort(key=lambda obj: obj.ootTransitionActorProperty.fromRoom.ootRoomHeader.roomIndex) for obj in actorObjList: roomObj = self.getRoomObjectFromChild(obj) if roomObj is None: diff --git a/fast64_internal/oot/file_settings.py b/fast64_internal/oot/file_settings.py index 1905877d5..1d50ef40b 100644 --- a/fast64_internal/oot/file_settings.py +++ b/fast64_internal/oot/file_settings.py @@ -37,7 +37,7 @@ def file_register(): Scene.ootDecompPath = StringProperty(name="Decomp Folder", subtype="FILE_PATH") Scene.useDecompFeatures = BoolProperty( - name="Use Decomp for CS Export", description="Use names and macros from decomp when exporting", default=True + name="Use decomp for export", description="Use names and macros from decomp when exporting", default=True ) Scene.exportMotionOnly = BoolProperty( From f5d20a9d59c41f740d666f5c4a4cf2412bb449d6 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:34:32 +0100 Subject: [PATCH 69/98] minor change --- fast64_internal/oot/exporter/collision/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 3603ff1d7..24f682031 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -166,7 +166,7 @@ def getCollisionData(self): self.useMacros, ) - if surfaceType not in colPolyFromSurfaceType.keys(): + if surfaceType not in colPolyFromSurfaceType: colPolyFromSurfaceType[surfaceType] = [] colPolyFromSurfaceType[surfaceType].append( From 3e891d29896c68d4868cb8dca8956ee2296cb41e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:48:41 +0100 Subject: [PATCH 70/98] implemented cutscenes --- fast64_internal/oot/cutscene/operators.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index bf02ff845..b207f9632 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -8,12 +8,12 @@ from bpy.types import Scene, Operator, Context from bpy.utils import register_class, unregister_class from ...utility import CData, PluginError, writeCData, raisePluginError -from ..oot_utility import getCollection, getCutsceneName +from ..oot_utility import getCollection from ..oot_constants import ootData -from ..scene.exporter.to_c import getCutsceneC from .constants import ootEnumCSTextboxType, ootEnumCSListType from .importer import importCutsceneData from .exporter import getNewCutsceneExport +from ..exporter.cutscene import Cutscene def checkGetFilePaths(context: Context): @@ -181,9 +181,10 @@ def execute(self, context): csdata = ootCutsceneIncludes(headerfilename) if context.scene.exportMotionOnly: + # TODO: improve this csdata.append(insertCutsceneData(cpath, activeObj.name.removeprefix("Cutscene."))) else: - csdata.append(getCutsceneC(getCutsceneName(activeObj))) + csdata.append(Cutscene(activeObj, context.scene.useDecompFeatures).getC()) writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported cutscene") @@ -216,7 +217,7 @@ def execute(self, context): if context.scene.exportMotionOnly: raise PluginError("ERROR: Not implemented yet.") else: - csdata.append(getCutsceneC(getCutsceneName(obj))) + csdata.append(Cutscene(obj, context.scene.useDecompFeatures).getC()) count += 1 if count == 0: From 16df442d73e60ea23ba3da00b77f7e3d772e5514 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:51:12 +0100 Subject: [PATCH 71/98] collision exporter --- fast64_internal/oot/collision/operators.py | 56 +++++++++++-------- fast64_internal/oot/exporter/base.py | 8 +-- .../oot/exporter/collision/__init__.py | 42 +++++++++----- .../oot/exporter/collision/camera.py | 8 +-- .../oot/exporter/collision/waterbox.py | 9 +-- .../oot/exporter/scene/__init__.py | 2 + 6 files changed, 75 insertions(+), 50 deletions(-) diff --git a/fast64_internal/oot/collision/operators.py b/fast64_internal/oot/collision/operators.py index a640b9a3f..6c1d4528a 100644 --- a/fast64_internal/oot/collision/operators.py +++ b/fast64_internal/oot/collision/operators.py @@ -1,11 +1,11 @@ import bpy, os, mathutils -from bpy.types import Operator, Mesh +from bpy.types import Operator from bpy.utils import register_class, unregister_class from bpy.ops import object from mathutils import Matrix -from ..oot_collision import exportCollisionCommon, ootCollisionToC from ..oot_collision_classes import OOTCollision, OOTCameraData from .properties import OOTCollisionExportSettings +from ..exporter.collision import CollisionHeader from ...utility import ( PluginError, @@ -31,7 +31,6 @@ def exportCollisionToC( originalObj: bpy.types.Object, transformMatrix: mathutils.Matrix, exportSettings: OOTCollisionExportSettings ): - includeChildren = exportSettings.includeChildren name = toAlnum(originalObj.name) isCustomExport = exportSettings.customExport folderName = exportSettings.folder @@ -50,29 +49,38 @@ def exportCollisionToC( restoreHiddenState(hiddenState) try: - exportCollisionCommon(collision, obj, transformMatrix, includeChildren, name) - ootCleanupScene(originalObj, allObjs) + if not obj.ignore_collision: + # get C data + colData = CData() + colData.source = '#include "ultra64.h"\n#include "z64.h"\n#include "macros.h"\n' + if not isCustomExport: + colData.source += f'#include "{folderName}.h"\n\n' + else: + colData.source += "\n" + colData.append( + CollisionHeader( + None, + obj, + transformMatrix, + bpy.context.scene.useDecompFeatures, + exportSettings.includeChildren, + f"{name}_collisionHeader", + name, + ).getC() + ) + + # write file + path = ootGetPath(exportPath, isCustomExport, "assets/objects/", folderName, False, True) + filename = exportSettings.filename if exportSettings.isCustomFilename else f"{name}_collision" + writeCData(colData, os.path.join(path, f"{filename}.h"), os.path.join(path, f"{filename}.c")) + if not isCustomExport: + addIncludeFiles(folderName, path, name) + else: + raise PluginError("ERROR: The selected mesh object ignores collision!") except Exception as e: - ootCleanupScene(originalObj, allObjs) raise Exception(str(e)) - - collisionC = ootCollisionToC(collision) - - data = CData() - data.source += '#include "ultra64.h"\n#include "z64.h"\n#include "macros.h"\n' - if not isCustomExport: - data.source += '#include "' + folderName + '.h"\n\n' - else: - data.source += "\n" - - data.append(collisionC) - - path = ootGetPath(exportPath, isCustomExport, "assets/objects/", folderName, False, True) - filename = exportSettings.filename if exportSettings.isCustomFilename else f"{name}_collision" - writeCData(data, os.path.join(path, f"{filename}.h"), os.path.join(path, f"{filename}.c")) - - if not isCustomExport: - addIncludeFiles(folderName, path, name) + finally: + ootCleanupScene(originalObj, allObjs) class OOT_ExportCollision(Operator): diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/base.py index b24b303cb..8b030b1e5 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/base.py @@ -71,9 +71,9 @@ def getPropValue(self, data, propName: str): return value if value != "Custom" else getattr(data, f"{propName}Custom") def getConvertedTransformWithOrientation( - self, transform: Matrix, sceneObj: Object, obj: Object, orientation: Quaternion | Matrix + self, transform: Matrix, dataHolder: Object, obj: Object, orientation: Quaternion | Matrix ): - relativeTransform = transform @ sceneObj.matrix_world.inverted() @ obj.matrix_world + relativeTransform = transform @ dataHolder.matrix_world.inverted() @ obj.matrix_world blenderTranslation, blenderRotation, scale = relativeTransform.decompose() rotation = blenderRotation @ orientation convertedTranslation = ootConvertTranslation(blenderTranslation) @@ -81,14 +81,14 @@ def getConvertedTransformWithOrientation( return convertedTranslation, convertedRotation, scale, rotation - def getConvertedTransform(self, transform: Matrix, sceneObj: Object, obj: Object, handleOrientation: bool): + def getConvertedTransform(self, transform: Matrix, dataHolder: Object, obj: Object, handleOrientation: bool): # Hacky solution to handle Z-up to Y-up conversion # We cannot apply rotation to empty, as that modifies scale if handleOrientation: orientation = Quaternion((1, 0, 0), radians(90.0)) else: orientation = Matrix.Identity(4) - return self.getConvertedTransformWithOrientation(transform, sceneObj, obj, orientation) + return self.getConvertedTransformWithOrientation(transform, dataHolder, obj, orientation) def getAltHeaderListCmd(self, altName: str): """Returns the scene alternate header list command""" diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 24f682031..08d4edfcd 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -4,6 +4,7 @@ from mathutils import Matrix, Vector from bpy.types import Mesh, Object from bpy.ops import object +from typing import Optional from ....utility import PluginError, CData, indent from ...oot_utility import convertIntTo2sComplement from ..base import Base @@ -18,9 +19,11 @@ class CollisionBase(Base): """This class hosts different functions used to convert mesh data""" - sceneObj: Object + sceneObj: Optional[Object] + meshObj: Optional[Object] transform: Matrix useMacros: bool + includeChildren: bool def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): """This is used to update the scene's boundaries""" @@ -46,26 +49,35 @@ def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Verte return i return None - def getMeshObjects(self, parentObj: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): + def getMeshObjects(self, dataHolder: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" - objList: list[Object] = parentObj.children - for obj in objList: - newTransform = curTransform @ obj.matrix_local + if self.includeChildren: + for obj in dataHolder.children: + newTransform = curTransform @ obj.matrix_local - if obj.type == "MESH" and not obj.ignore_collision: - transformFromMeshObj[obj] = newTransform + if obj.type == "MESH" and not obj.ignore_collision: + transformFromMeshObj[obj] = newTransform - if len(obj.children) > 0: - self.getMeshObjects(obj, newTransform, transformFromMeshObj) + if len(obj.children) > 0: + self.getMeshObjects(obj, newTransform, transformFromMeshObj) return transformFromMeshObj + def getDataHolder(self): + if self.sceneObj is not None: + return self.sceneObj + elif self.meshObj is not None: + return self.meshObj + else: + raise PluginError("ERROR: Object not found.") + def getCollisionData(self): """Returns collision data, surface types and vertex positions from mesh objects""" object.select_all(action="DESELECT") - self.sceneObj.select_set(True) + dataHolder = self.getDataHolder() + dataHolder.select_set(True) colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} surfaceList: list[SurfaceType] = [] @@ -74,7 +86,9 @@ def getCollisionData(self): colBounds: list[tuple[int, int, int]] = [] transformFromMeshObj: dict[Object, Matrix] = {} - transformFromMeshObj = self.getMeshObjects(self.sceneObj, self.transform, transformFromMeshObj) + if dataHolder.type == "MESH" and not dataHolder.ignore_collision: + transformFromMeshObj[dataHolder] = self.transform + transformFromMeshObj = self.getMeshObjects(dataHolder, self.transform, transformFromMeshObj) for meshObj, transform in transformFromMeshObj.items(): # Note: ``isinstance``only used to get the proper type hints if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): @@ -100,7 +114,6 @@ def getCollisionData(self): ) distance = convertIntTo2sComplement(distance, 2, True) - # TODO: can this be improved? nx = (y2 - y1) * (z3 - z2) - (z2 - z1) * (y3 - y2) ny = (z2 - z1) * (x3 - x2) - (x2 - x1) * (z3 - z2) nz = (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2) @@ -211,6 +224,7 @@ class CollisionHeader(CollisionBase): def __post_init__(self): # Ideally everything would be separated but this is complicated since it's all tied together colBounds, vertexList, polyList, surfaceTypeList = self.getCollisionData() + dataHolder = self.getDataHolder() self.minBounds = colBounds[0] self.maxBounds = colBounds[1] @@ -218,9 +232,9 @@ def __post_init__(self): self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", polyList) self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", surfaceTypeList) self.bgCamInfo = BgCamInformations( - self.sceneObj, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" + dataHolder, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" ) - self.waterbox = WaterBoxes(self.sceneObj, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) + self.waterbox = WaterBoxes(dataHolder, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) def getCmd(self): """Returns the collision header scene command""" diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 3b8abe925..2335bd88b 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -76,7 +76,7 @@ def getInfoEntryC(self, posDataName: str): class BgCamInformations(Base): """This class defines the array of camera informations and the array of the associated data""" - sceneObj: Object + dataHolder: Object transform: Matrix name: str posDataName: str @@ -90,7 +90,7 @@ class BgCamInformations(Base): def initCrawlspaceList(self): """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" - crawlspaceObjList = getObjectList(self.sceneObj.children_recursive, "CURVE", splineType="Crawlspace") + crawlspaceObjList = getObjectList(self.dataHolder.children_recursive, "CURVE", splineType="Crawlspace") for obj in crawlspaceObjList: if self.validateCurveData(obj): self.crawlspacePosList.append( @@ -106,7 +106,7 @@ def initCrawlspaceList(self): def initBgCamInfoList(self): """Returns a list of camera informations from camera objects""" - camObjList = getObjectList(self.sceneObj.children_recursive, "CAMERA") + camObjList = getObjectList(self.dataHolder.children_recursive, "CAMERA") camPosData: dict[int, CameraData] = {} camInfoData: dict[int, CameraInfo] = {} @@ -124,7 +124,7 @@ def initBgCamInfoList(self): # Camera faces opposite direction pos, rot, _, _ = self.getConvertedTransformWithOrientation( - self.transform, self.sceneObj, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + self.transform, self.dataHolder, camObj, Quaternion((0, 1, 0), math.radians(180.0)) ) fov = math.degrees(camObj.data.angle) diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index d6fed7213..19bb371b9 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -81,7 +81,7 @@ def getEntryC(self): class WaterBoxes(Base): """This class defines the array of waterboxes""" - sceneObj: Object + dataHolder: Object transform: Matrix name: str useMacros: bool @@ -89,14 +89,15 @@ class WaterBoxes(Base): waterboxList: list[WaterBox] = field(default_factory=list) def __post_init__(self): - waterboxObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Water Box") + waterboxObjList = getObjectList(self.dataHolder.children_recursive, "EMPTY", "Water Box") for waterboxObj in waterboxObjList: emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.sceneObj, waterboxObj, True) + pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.dataHolder, waterboxObj, True) checkIdentityRotation(waterboxObj, orientedRot, False) wboxProp = waterboxObj.ootWaterBoxProperty - roomObj = self.getRoomObjectFromChild(waterboxObj) + # temp solution + roomObj = self.getRoomObjectFromChild(waterboxObj) if self.dataHolder.type == "EMPTY" else None self.waterboxList.append( WaterBox( pos, diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 79849e07f..26c111c38 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -46,8 +46,10 @@ def __post_init__(self): self.colHeader = CollisionHeader( self.sceneObj, + None, self.transform, self.useMacros, + True, f"{self.name}_collisionHeader", self.name, ) From bab124b4818ee2deb495a34abb7a5b4a119cbb85 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:18:34 +0100 Subject: [PATCH 72/98] "disable" old/new toggle --- fast64_internal/oot/scene/operators.py | 47 +++++++++++++------------ fast64_internal/oot/scene/properties.py | 5 +-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 0fe495f7e..9032292c5 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -173,29 +173,30 @@ def execute(self, context): bootOptions = context.scene.fast64.oot.bootupSceneOptions hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled - if settings.useNewExporter: - SceneExporter( - exportInfo, - obj, - exportInfo.name, - context.scene.ootBlenderScale, - finalTransform, - bpy.context.scene.saveTextures, - bootOptions if hackerFeaturesEnabled else None, - settings.singleFile, - TextureExportSettings(False, context.scene.saveTextures, None, None), - context.scene.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, - ).export() - else: - ootExportSceneToC( - obj, - finalTransform, - levelName, - DLFormat.Static, - context.scene.saveTextures, - exportInfo, - bootOptions if hackerFeaturesEnabled else None, - ) + # keeping this on purpose, will be removed once old code is cleaned-up + # if settings.useNewExporter: + SceneExporter( + exportInfo, + obj, + exportInfo.name, + context.scene.ootBlenderScale, + finalTransform, + bpy.context.scene.saveTextures, + bootOptions if hackerFeaturesEnabled else None, + settings.singleFile, + TextureExportSettings(False, context.scene.saveTextures, None, None), + context.scene.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, + ).export() + # else: + # ootExportSceneToC( + # obj, + # finalTransform, + # levelName, + # DLFormat.Static, + # context.scene.saveTextures, + # exportInfo, + # bootOptions if hackerFeaturesEnabled else None, + # ) self.report({"INFO"}, "Success!") diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index 2ed6c1926..6e4759622 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -489,7 +489,8 @@ class OOTExportSceneSettingsProperty(PropertyGroup): ) option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE") - useNewExporter: BoolProperty(name="Use Experimental Exporter", default=True) + # keeping this on purpose, will be removed once old code is cleaned-up + useNewExporter: BoolProperty(name="Use New Exporter", default=True) def draw_props(self, layout: UILayout): if self.customExport: @@ -505,7 +506,7 @@ def draw_props(self, layout: UILayout): layout.prop(self, "singleFile") layout.prop(self, "customExport") - layout.prop(self, "useNewExporter") + # layout.prop(self, "useNewExporter") class OOTImportSceneSettingsProperty(PropertyGroup): From 836faef4cfa9699e8a5533eec17f35c4295b3077 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:07:18 +0100 Subject: [PATCH 73/98] review part 1 --- fast64_internal/oot/exporter/collision/__init__.py | 14 +++++++------- fast64_internal/oot/exporter/collision/vertex.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 08d4edfcd..692cb64b9 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -12,7 +12,7 @@ from .surface import SurfaceType, SurfaceTypes from .camera import BgCamInformations from .waterbox import WaterBoxes -from .vertex import Vertex, Vertices +from .vertex import CollisionVertex, CollisionVertices @dataclass @@ -41,8 +41,8 @@ def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int if position[i] > maxBounds[i]: maxBounds[i] = position[i] - def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Vertex]): - """Returns the index of a Vertex based on position data, returns None if no match found""" + def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[CollisionVertex]): + """Returns the index of a CollisionVertex based on position data, returns None if no match found""" for i in range(len(vertexList)): if vertexList[i].pos == vertexPos: @@ -82,7 +82,7 @@ def getCollisionData(self): colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} surfaceList: list[SurfaceType] = [] polyList: list[CollisionPoly] = [] - vertexList: list[Vertex] = [] + vertexList: list[CollisionVertex] = [] colBounds: list[tuple[int, int, int]] = [] transformFromMeshObj: dict[Object, Matrix] = {} @@ -126,7 +126,7 @@ def getCollisionData(self): for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: vertexIndex = self.getVertexIndex(pos, vertexList) if vertexIndex is None: - vertexList.append(Vertex(pos)) + vertexList.append(CollisionVertex(pos)) indices.append(len(vertexList) - 1) else: indices.append(vertexIndex) @@ -215,7 +215,7 @@ class CollisionHeader(CollisionBase): minBounds: tuple[int, int, int] = None maxBounds: tuple[int, int, int] = None - vertices: Vertices = None + vertices: CollisionVertices = None collisionPoly: CollisionPolygons = None surfaceType: SurfaceTypes = None bgCamInfo: BgCamInformations = None @@ -228,7 +228,7 @@ def __post_init__(self): self.minBounds = colBounds[0] self.maxBounds = colBounds[1] - self.vertices = Vertices(f"{self.sceneName}_vertices", vertexList) + self.vertices = CollisionVertices(f"{self.sceneName}_vertices", vertexList) self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", polyList) self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", surfaceTypeList) self.bgCamInfo = BgCamInformations( diff --git a/fast64_internal/oot/exporter/collision/vertex.py b/fast64_internal/oot/exporter/collision/vertex.py index 4a1dad701..39d081bab 100644 --- a/fast64_internal/oot/exporter/collision/vertex.py +++ b/fast64_internal/oot/exporter/collision/vertex.py @@ -3,7 +3,7 @@ @dataclass -class Vertex: +class CollisionVertex: """This class defines a vertex data""" pos: tuple[int, int, int] @@ -15,11 +15,11 @@ def getEntryC(self): @dataclass -class Vertices: +class CollisionVertices: """This class defines the array of vertices""" name: str - vertexList: list[Vertex] + vertexList: list[CollisionVertex] def getC(self): vertData = CData() From 78efdd2a07d53ee0c8b0b0e0a92f798728b9ca64 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:27:18 +0100 Subject: [PATCH 74/98] use ``field(init=False)`` for unused class members when calling one --- fast64_internal/oot/exporter/base.py | 10 +-- fast64_internal/oot/exporter/classes.py | 5 +- .../oot/exporter/collision/__init__.py | 21 ++--- .../oot/exporter/collision/camera.py | 30 +++---- .../oot/exporter/collision/polygons.py | 5 +- .../oot/exporter/collision/surface.py | 17 ++-- .../oot/exporter/collision/waterbox.py | 17 ++-- .../oot/exporter/cutscene/__init__.py | 22 ++--- .../oot/exporter/cutscene/actor_cue.py | 10 ++- .../oot/exporter/cutscene/camera.py | 39 ++++----- .../oot/exporter/cutscene/common.py | 2 +- fast64_internal/oot/exporter/cutscene/data.py | 56 ++++++------- fast64_internal/oot/exporter/cutscene/misc.py | 80 +++++++++---------- fast64_internal/oot/exporter/cutscene/seq.py | 34 ++++---- fast64_internal/oot/exporter/cutscene/text.py | 44 +++++----- fast64_internal/oot/exporter/main.py | 18 ++--- fast64_internal/oot/exporter/room/header.py | 49 ++++++------ fast64_internal/oot/exporter/room/main.py | 13 ++- fast64_internal/oot/exporter/room/shape.py | 38 ++++----- .../oot/exporter/scene/__init__.py | 13 +-- fast64_internal/oot/exporter/scene/actors.py | 18 ++--- fast64_internal/oot/exporter/scene/general.py | 31 +++---- fast64_internal/oot/exporter/scene/header.py | 25 +++--- .../oot/exporter/scene/pathways.py | 2 +- fast64_internal/oot/exporter/scene/rooms.py | 2 +- 25 files changed, 299 insertions(+), 302 deletions(-) diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/base.py index 8b030b1e5..e2fcba1f2 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/base.py @@ -105,11 +105,11 @@ def getEndCmd(self): class Actor: """Defines an Actor""" - name: Optional[str] = None - id: Optional[str] = None - pos: list[int] = field(default_factory=list) - rot: Optional[str] = None - params: Optional[str] = None + name: Optional[str] = field(init=False, default=None) + id: Optional[str] = field(init=False, default=None) + pos: list[int] = field(init=False, default_factory=list) + rot: Optional[str] = field(init=False, default=None) + params: Optional[str] = field(init=False, default=None) def getActorEntry(self): """Returns a single actor entry""" diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 41cb1230d..213ebbd65 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -15,7 +15,6 @@ class RoomFile: roomModelInfo: Optional[str] = None singleFileExport: bool = False path: Optional[str] = None - header: str = "" def write(self): @@ -46,8 +45,8 @@ class SceneFile: path: Optional[str] = None header: str = "" - hasCutscenes: bool = False - hasSceneTextures: bool = False + hasCutscenes: bool = field(init=False) + hasSceneTextures: bool = field(init=False) def __post_init__(self): self.hasCutscenes = len(self.sceneCutscenes) > 0 diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 692cb64b9..c0285438b 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -1,6 +1,6 @@ import math -from dataclasses import dataclass +from dataclasses import dataclass, field from mathutils import Matrix, Vector from bpy.types import Mesh, Object from bpy.ops import object @@ -158,6 +158,8 @@ def getCollisionData(self): # get surface type and collision poly data useConveyor = colProp.conveyorOption != "None" + conveyorSpeed = int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0 + shouldKeepMomentum = colProp.conveyorKeepMomentum if useConveyor else False surfaceType = SurfaceType( colProp.cameraID, colProp.exitID, @@ -172,10 +174,9 @@ def getCollisionData(self): colProp.lightingSetting, int(colProp.echo, base=16), colProp.hookshotable, - int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0, + conveyorSpeed + (4 if shouldKeepMomentum else 0), int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, colProp.isWallDamage, - colProp.conveyorKeepMomentum if useConveyor else False, self.useMacros, ) @@ -213,13 +214,13 @@ class CollisionHeader(CollisionBase): name: str sceneName: str - minBounds: tuple[int, int, int] = None - maxBounds: tuple[int, int, int] = None - vertices: CollisionVertices = None - collisionPoly: CollisionPolygons = None - surfaceType: SurfaceTypes = None - bgCamInfo: BgCamInformations = None - waterbox: WaterBoxes = None + minBounds: tuple[int, int, int] = field(init=False) + maxBounds: tuple[int, int, int] = field(init=False) + vertices: CollisionVertices = field(init=False) + collisionPoly: CollisionPolygons = field(init=False) + surfaceType: SurfaceTypes = field(init=False) + bgCamInfo: BgCamInformations = field(init=False) + waterbox: WaterBoxes = field(init=False) def __post_init__(self): # Ideally everything would be separated but this is complicated since it's all tied together diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 2335bd88b..49064d603 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -5,7 +5,7 @@ from bpy.types import Object from ....utility import PluginError, CData, indent from ...oot_utility import getObjectList -from ...oot_collision_classes import decomp_compat_map_CameraSType +from ...collision.constants import decomp_compat_map_CameraSType from ...collision.properties import OOTCameraPositionProperty from ..base import Base @@ -16,10 +16,8 @@ class CrawlspaceCamera: points: list[tuple[int, int, int]] camIndex: int - arrayIndex: int = 0 - def __post_init__(self): - self.points = [self.points[0], self.points[0], self.points[0], self.points[1], self.points[1], self.points[1]] + arrayIndex: int = field(init=False, default=0) def getDataEntryC(self): """Returns an entry for the camera data array""" @@ -59,8 +57,9 @@ class CameraInfo: count: int data: CameraData camIndex: int - arrayIndex: int = 0 - hasPosData: bool = False + + arrayIndex: int = field(init=False, default=0) + hasPosData: bool = field(init=False) def __post_init__(self): self.hasPosData = self.data is not None @@ -81,11 +80,11 @@ class BgCamInformations(Base): name: str posDataName: str - bgCamInfoList: list[CameraInfo] = field(default_factory=list) - crawlspacePosList: list[CrawlspaceCamera] = field(default_factory=list) - arrayIdx: int = 0 - crawlspaceCount: int = 6 - camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(default_factory=dict) + bgCamInfoList: list[CameraInfo] = field(init=False, default_factory=list) + crawlspacePosList: list[CrawlspaceCamera] = field(init=False, default_factory=list) + arrayIdx: int = field(init=False, default=0) + crawlspaceCount: int = field(init=False, default=6) + camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(init=False, default_factory=dict) def initCrawlspaceList(self): """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" @@ -93,12 +92,13 @@ def initCrawlspaceList(self): crawlspaceObjList = getObjectList(self.dataHolder.children_recursive, "CURVE", splineType="Crawlspace") for obj in crawlspaceObjList: if self.validateCurveData(obj): + points = [ + [round(value) for value in self.transform @ obj.matrix_world @ point.co] + for point in obj.data.splines[0].points + ] self.crawlspacePosList.append( CrawlspaceCamera( - [ - [round(value) for value in self.transform @ obj.matrix_world @ point.co] - for point in obj.data.splines[0].points - ], + [points[0], points[0], points[0], points[1], points[1], points[1]], obj.ootSplineProperty.index, ) ) diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index 7ccd43da3..3339d42cc 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from mathutils import Vector from ....utility import PluginError, CData, indent @@ -16,7 +16,8 @@ class CollisionPoly: normal: Vector dist: int useMacros: bool - type: Optional[int] = None + + type: Optional[int] = field(init=False, default=None) def __post_init__(self): for i, val in enumerate(self.normal): diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index 4b7283be6..999ca9b17 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from ....utility import CData, indent @@ -7,6 +7,7 @@ class SurfaceType: """This class defines a single surface type""" + # surface type 0 bgCamIndex: int exitIndex: int floorType: int @@ -16,6 +17,7 @@ class SurfaceType: isSoft: bool isHorseBlocked: bool + # surface type 1 material: int floorEffect: int lightSetting: int @@ -25,17 +27,14 @@ class SurfaceType: conveyorDirection: int isWallDamage: bool # unk27 - conveyorKeepMomentum: bool useMacros: bool - isSoftC: Optional[str] = None - isHorseBlockedC: Optional[str] = None - canHookshotC: Optional[str] = None - isWallDamageC: Optional[str] = None - def __post_init__(self): - if self.conveyorKeepMomentum: - self.conveyorSpeed += 4 + isSoftC: str = field(init=False) + isHorseBlockedC: str = field(init=False) + canHookshotC: str = field(init=False) + isWallDamageC: str = field(init=False) + def __post_init__(self): self.isSoftC = "1" if self.isSoft else "0" self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" self.canHookshotC = "1" if self.canHookshot else "0" diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 19bb371b9..883240ee8 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -22,14 +22,15 @@ class WaterBox: setFlag19: bool useMacros: bool - xMin: Optional[int] = None - ySurface: Optional[int] = None - zMin: Optional[int] = None - xLength: Optional[int] = None - zLength: Optional[int] = None - setFlag19C: Optional[str] = None - roomIndexC: Optional[str] = None + xMin: int = field(init=False) + ySurface: int = field(init=False) + zMin: int = field(init=False) + xLength: int = field(init=False) + zLength: int = field(init=False) + + setFlag19C: str = field(init=False) + roomIndexC: str = field(init=False) def __post_init__(self): self.setFlag19C = "1" if self.setFlag19 else "0" @@ -86,7 +87,7 @@ class WaterBoxes(Base): name: str useMacros: bool - waterboxList: list[WaterBox] = field(default_factory=list) + waterboxList: list[WaterBox] = field(init=False, default_factory=list) def __post_init__(self): waterboxObjList = getObjectList(self.dataHolder.children_recursive, "EMPTY", "Water Box") diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index 20bf3f1ce..7d9ad5ade 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -23,18 +23,18 @@ class Cutscene: csObj: Object useMacros: bool - name: Optional[str] = None - motionOnly: bool = False - data: Optional[CutsceneData] = None - totalEntries: int = 0 - frameCount: int = 0 - paramNumber: int = 2 + + name: str = field(init=False) + motionOnly: bool = field(init=False, default=False) + data: CutsceneData = field(init=False) + totalEntries: int = field(init=False) + frameCount: int = field(init=False) + paramNumber: int = field(init=False, default=2) def __post_init__(self): # when csObj is None it means we're in import context if self.csObj is not None and self.data is None: - if self.name is None: - self.name = self.csObj.name.removeprefix("Cutscene.").replace(".", "_") + self.name = self.csObj.name.removeprefix("Cutscene.").replace(".", "_") self.data = CutsceneData(self.csObj, self.useMacros, self.motionOnly) self.totalEntries = self.data.totalEntries self.frameCount = self.data.frameCount @@ -104,9 +104,9 @@ class SceneCutscene(Base): headerIndex: int useMacros: bool - entries: list[Cutscene] = field(default_factory=list) - csObj: Optional[Object] = None - cutsceneObjects: list[Object] = field(default_factory=list) + entries: list[Cutscene] = field(init=False, default_factory=list) + csObj: Object = field(init=False) + cutsceneObjects: list[Object] = field(init=False, default_factory=list) def __post_init__(self): self.csObj: Object = self.props.csWriteObject diff --git a/fast64_internal/oot/exporter/cutscene/actor_cue.py b/fast64_internal/oot/exporter/cutscene/actor_cue.py index 2ebe62da6..4b5df5230 100644 --- a/fast64_internal/oot/exporter/cutscene/actor_cue.py +++ b/fast64_internal/oot/exporter/cutscene/actor_cue.py @@ -15,7 +15,8 @@ class CutsceneCmdActorCue(CutsceneCmdBase): startPos: list[int] = field(default_factory=list) endPos: list[int] = field(default_factory=list) isPlayer: bool = False - paramNumber: int = 15 + + paramNumber: int = field(init=False, default=15) def __post_init__(self): if self.params is not None: @@ -53,9 +54,10 @@ class CutsceneCmdActorCueList(CutsceneCmdBase): isPlayer: bool = False commandType: Optional[str] = None entryTotal: Optional[int] = None - entries: list[CutsceneCmdActorCue] = field(default_factory=list) - paramNumber: int = 2 - listName: str = "actorCueList" + + entries: list[CutsceneCmdActorCue] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="actorCueList") def __post_init__(self): if self.params is not None: diff --git a/fast64_internal/oot/exporter/cutscene/camera.py b/fast64_internal/oot/exporter/cutscene/camera.py index 214c744a0..561ed0d10 100644 --- a/fast64_internal/oot/exporter/cutscene/camera.py +++ b/fast64_internal/oot/exporter/cutscene/camera.py @@ -14,7 +14,8 @@ class CutsceneCmdCamPoint(CutsceneCmdBase): frame: Optional[int] = None viewAngle: Optional[float] = None pos: list[int] = field(default_factory=list) - paramNumber: int = 8 + + paramNumber: int = field(init=False, default=8) def __post_init__(self): if self.params is not None: @@ -46,9 +47,9 @@ def getCmd(self): class CutsceneCmdCamEyeSpline(CutsceneCmdBase): """This class contains the Camera Eye Spline data""" - entries: list[CutsceneCmdCamPoint] = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camEyeSplineList" + entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camEyeSplineList") def __post_init__(self): if self.params is not None: @@ -67,9 +68,9 @@ def getCmd(self): class CutsceneCmdCamATSpline(CutsceneCmdBase): """This class contains the Camera AT (look-at) Spline data""" - entries: list[CutsceneCmdCamPoint] = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camATSplineList" + entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camATSplineList") def __post_init__(self): if self.params is not None: @@ -88,9 +89,9 @@ def getCmd(self): class CutsceneCmdCamEyeSplineRelToPlayer(CutsceneCmdBase): """This class contains the Camera Eye Spline Relative to the Player data""" - entries: list[CutsceneCmdCamPoint] = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camEyeSplineRelPlayerList" + entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camEyeSplineRelPlayerList") def __post_init__(self): if self.params is not None: @@ -109,9 +110,9 @@ def getCmd(self): class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): """This class contains the Camera AT Spline Relative to the Player data""" - entries: list[CutsceneCmdCamPoint] = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camATSplineRelPlayerList" + entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camATSplineRelPlayerList") def __post_init__(self): if self.params is not None: @@ -131,9 +132,9 @@ class CutsceneCmdCamEye(CutsceneCmdBase): """This class contains a single Camera Eye point""" # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camEyeList" + entries: list = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camEyeList") def __post_init__(self): if self.params is not None: @@ -149,9 +150,9 @@ class CutsceneCmdCamAT(CutsceneCmdBase): """This class contains a single Camera AT point""" # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(default_factory=list) - paramNumber: int = 2 - listName: str = "camATList" + entries: list = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=2) + listName: str = field(init=False, default="camATList") def __post_init__(self): if self.params is not None: diff --git a/fast64_internal/oot/exporter/cutscene/common.py b/fast64_internal/oot/exporter/cutscene/common.py index 21da0eb7a..57ae1e450 100644 --- a/fast64_internal/oot/exporter/cutscene/common.py +++ b/fast64_internal/oot/exporter/cutscene/common.py @@ -9,7 +9,7 @@ class CutsceneCmdBase: """This class contains common Cutscene data""" - params: list[str] + params: Optional[list[str]] startFrame: Optional[int] = None endFrame: Optional[int] = None diff --git a/fast64_internal/oot/exporter/cutscene/data.py b/fast64_internal/oot/exporter/cutscene/data.py index d667ff77e..cbf210176 100644 --- a/fast64_internal/oot/exporter/cutscene/data.py +++ b/fast64_internal/oot/exporter/cutscene/data.py @@ -67,30 +67,31 @@ class CutsceneData: csObj: Object useMacros: bool motionOnly: bool - csObjects: dict[str, list[Object]] = field(default_factory=dict) - csProp: Optional["OOTCutsceneProperty"] = None - totalEntries: int = 0 - frameCount: int = 0 - motionFrameCount: int = 0 - camEndFrame: int = 0 - - destination: Optional[CutsceneCmdDestination] = None - actorCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) - playerCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) - camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(default_factory=list) - camATSplineList: list[CutsceneCmdCamATSpline] = field(default_factory=list) - camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(default_factory=list) - camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(default_factory=list) - camEyeList: list[CutsceneCmdCamEye] = field(default_factory=list) - camATList: list[CutsceneCmdCamAT] = field(default_factory=list) - textList: list[CutsceneCmdTextList] = field(default_factory=list) - miscList: list[CutsceneCmdMiscList] = field(default_factory=list) - rumbleList: list[CutsceneCmdRumbleControllerList] = field(default_factory=list) - transitionList: list[CutsceneCmdTransition] = field(default_factory=list) - lightSettingsList: list[CutsceneCmdLightSettingList] = field(default_factory=list) - timeList: list[CutsceneCmdTimeList] = field(default_factory=list) - seqList: list[CutsceneCmdStartStopSeqList] = field(default_factory=list) - fadeSeqList: list[CutsceneCmdFadeSeqList] = field(default_factory=list) + + csObjects: dict[str, list[Object]] = field(init=False, default_factory=dict) + csProp: "OOTCutsceneProperty" = field(init=False) + totalEntries: int = field(init=False, default=0) + frameCount: int = field(init=False, default=0) + motionFrameCount: int = field(init=False, default=0) + camEndFrame: int = field(init=False, default=0) + + destination: Optional[CutsceneCmdDestination] = field(init=False, default=None) + actorCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) + playerCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) + camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(init=False, default_factory=list) + camATSplineList: list[CutsceneCmdCamATSpline] = field(init=False, default_factory=list) + camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(init=False, default_factory=list) + camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(init=False, default_factory=list) + camEyeList: list[CutsceneCmdCamEye] = field(init=False, default_factory=list) + camATList: list[CutsceneCmdCamAT] = field(init=False, default_factory=list) + textList: list[CutsceneCmdTextList] = field(init=False, default_factory=list) + miscList: list[CutsceneCmdMiscList] = field(init=False, default_factory=list) + rumbleList: list[CutsceneCmdRumbleControllerList] = field(init=False, default_factory=list) + transitionList: list[CutsceneCmdTransition] = field(init=False, default_factory=list) + lightSettingsList: list[CutsceneCmdLightSettingList] = field(init=False, default_factory=list) + timeList: list[CutsceneCmdTimeList] = field(init=False, default_factory=list) + seqList: list[CutsceneCmdStartStopSeqList] = field(init=False, default_factory=list) + fadeSeqList: list[CutsceneCmdFadeSeqList] = field(init=False, default_factory=list) def __post_init__(self): self.csProp: "OOTCutsceneProperty" = self.csObj.ootCutsceneProperty @@ -295,8 +296,6 @@ def getNewCamData(self, shotObj: Object, useAT: bool): return newCamData def setCameraShotData(self): - """Returns every Camera Shot commands""" - shotObjects = self.csObjects["camShot"] if len(shotObjects) > 0: @@ -365,7 +364,8 @@ def setCutsceneData(self): isFadeOutSeq = entry.listType == "FadeOutSeqList" cmdList = cmdToClass[entry.listType](None) cmdList.entryTotal = len(entry.seqList) - cmdList.type = "start" if entry.listType == "StartSeqList" else "stop" + if not isFadeOutSeq: + cmdList.type = "start" if entry.listType == "StartSeqList" else "stop" for elem in entry.seqList: data = cmdToClass[entry.listType.removesuffix("List")](None, elem.startFrame, elem.endFrame) if isFadeOutSeq: @@ -398,7 +398,7 @@ def setCutsceneData(self): case "LightSettingsList": cmdList.entries.append( CutsceneCmdLightSetting( - None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex + None, elem.startFrame, elem.endFrame, False, elem.lightSettingsIndex ) ) case "TimeList": diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py index 3672e45b5..3db77f9f9 100644 --- a/fast64_internal/oot/exporter/cutscene/misc.py +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -10,7 +10,8 @@ class CutsceneCmdMisc(CutsceneCmdBase): """This class contains a single misc command entry""" type: Optional[str] = None # see ``CutsceneMiscType`` in decomp - paramNumber: int = 14 + + paramNumber: int = field(init=False, default=14) def __post_init__(self): if self.params is not None: @@ -29,9 +30,10 @@ def getCmd(self): class CutsceneCmdLightSetting(CutsceneCmdBase): """This class contains Light Setting command data""" - isLegacy: Optional[bool] = None - lightSetting: Optional[int] = None - paramNumber: int = 11 + isLegacy: bool = False + lightSetting: int = 0 + + paramNumber: int = field(init=False, default=11) def __post_init__(self): if self.params is not None: @@ -43,8 +45,6 @@ def __post_init__(self): def getCmd(self): self.validateFrames(False) - if self.lightSetting is None: - raise PluginError("ERROR: Light Setting is None!") return indent * 3 + (f"CS_LIGHT_SETTING({self.lightSetting}, {self.startFrame}" + ", 0" * 9 + "),\n") @@ -52,9 +52,10 @@ def getCmd(self): class CutsceneCmdTime(CutsceneCmdBase): """This class contains Time Ocarina Action command data""" - hour: Optional[int] = None - minute: Optional[int] = None - paramNumber: int = 5 + hour: int = 0 + minute: int = 0 + + paramNumber: int = field(init=False, default=5) def __post_init__(self): if self.params is not None: @@ -65,10 +66,6 @@ def __post_init__(self): def getCmd(self): self.validateFrames(False) - if self.hour is None: - raise PluginError("ERROR: ``hour`` is None!") - if self.minute is None: - raise PluginError("ERROR: ``minute`` is None!") return indent * 3 + f"CS_TIME(0, {self.startFrame}, 0, {self.hour}, {self.minute}),\n" @@ -76,10 +73,11 @@ def getCmd(self): class CutsceneCmdRumbleController(CutsceneCmdBase): """This class contains Rumble Controller command data""" - sourceStrength: Optional[int] = None - duration: Optional[int] = None - decreaseRate: Optional[int] = None - paramNumber: int = 8 + sourceStrength: int = 0 + duration: int = 0 + decreaseRate: int = 0 + + paramNumber: int = field(init=False, default=8) def __post_init__(self): if self.params is not None: @@ -91,12 +89,6 @@ def __post_init__(self): def getCmd(self): self.validateFrames(False) - if self.sourceStrength is None: - raise PluginError("ERROR: ``sourceStrength`` is None!") - if self.duration is None: - raise PluginError("ERROR: ``duration`` is None!") - if self.decreaseRate is None: - raise PluginError("ERROR: ``decreaseRate`` is None!") return indent * 3 + ( f"CS_RUMBLE_CONTROLLER(" + f"0, {self.startFrame}, 0, {self.sourceStrength}, {self.duration}, {self.decreaseRate}, 0, 0),\n" @@ -107,10 +99,10 @@ def getCmd(self): class CutsceneCmdMiscList(CutsceneCmdBase): """This class contains Misc command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdMisc] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "miscList" + entryTotal: Optional[int] = field(init=False, default=None) + entries: list[CutsceneCmdMisc] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="miscList") def __post_init__(self): if self.params is not None: @@ -128,10 +120,10 @@ def getCmd(self): class CutsceneCmdLightSettingList(CutsceneCmdBase): """This class contains Light Setting List command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdLightSetting] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "lightSettingsList" + entryTotal: Optional[int] = field(init=False, default=None) + entries: list[CutsceneCmdLightSetting] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="lightSettingsList") def __post_init__(self): if self.params is not None: @@ -149,10 +141,10 @@ def getCmd(self): class CutsceneCmdTimeList(CutsceneCmdBase): """This class contains Time List command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdTime] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "timeList" + entryTotal: Optional[int] = field(init=False, default=None) + entries: list[CutsceneCmdTime] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="timeList") def __post_init__(self): if self.params is not None: @@ -170,10 +162,10 @@ def getCmd(self): class CutsceneCmdRumbleControllerList(CutsceneCmdBase): """This class contains Rumble Controller List command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdRumbleController] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "rumbleList" + entryTotal: Optional[int] = field(init=False, default=None) + entries: list[CutsceneCmdRumbleController] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="rumbleList") def __post_init__(self): if self.params is not None: @@ -192,8 +184,9 @@ class CutsceneCmdDestination(CutsceneCmdBase): """This class contains Destination command data""" id: Optional[str] = None - paramNumber: int = 3 - listName: str = "destination" + + paramNumber: int = field(init=False, default=3) + listName: str = field(init=False, default="destination") def __post_init__(self): if self.params is not None: @@ -212,8 +205,9 @@ class CutsceneCmdTransition(CutsceneCmdBase): """This class contains Transition command data""" type: Optional[str] = None - paramNumber: int = 3 - listName: str = "transitionList" + + paramNumber: int = field(init=False, default=3) + listName: str = field(init=False, default="transitionList") def __post_init__(self): if self.params is not None: diff --git a/fast64_internal/oot/exporter/cutscene/seq.py b/fast64_internal/oot/exporter/cutscene/seq.py index 7ad043f24..4ade02384 100644 --- a/fast64_internal/oot/exporter/cutscene/seq.py +++ b/fast64_internal/oot/exporter/cutscene/seq.py @@ -9,10 +9,10 @@ class CutsceneCmdStartStopSeq(CutsceneCmdBase): """This class contains Start/Stop Seq command data""" - isLegacy: Optional[bool] = None - seqId: Optional[str] = None - paramNumber: int = 11 - type: Optional[str] = None # "start" or "stop" + isLegacy: bool = field(init=False, default=False) + seqId: Optional[str] = field(init=False, default=None) + paramNumber: int = field(init=False, default=11) + type: Optional[str] = field(init=False, default=None) # "start" or "stop" def __post_init__(self): if self.params is not None: @@ -31,9 +31,9 @@ def getCmd(self): class CutsceneCmdFadeSeq(CutsceneCmdBase): """This class contains Fade Seq command data""" - seqPlayer: Optional[str] = None - paramNumber: int = 11 - enumKey: str = "csFadeOutSeqPlayer" + seqPlayer: str = field(init=False, default=str()) + paramNumber: int = field(init=False, default=11) + enumKey: str = field(init=False, default="csFadeOutSeqPlayer") def __post_init__(self): if self.params is not None: @@ -50,11 +50,11 @@ def getCmd(self): class CutsceneCmdStartStopSeqList(CutsceneCmdBase): """This class contains Start/Stop Seq List command data""" - entryTotal: Optional[int] = None - type: Optional[str] = None # "start" or "stop" - entries: list[CutsceneCmdStartStopSeq] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "seqList" + entryTotal: int = field(init=False, default=0) + type: str = field(init=False, default=str()) # "start" or "stop" + entries: list[CutsceneCmdStartStopSeq] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="seqList") def __post_init__(self): if self.params is not None: @@ -63,8 +63,6 @@ def __post_init__(self): def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") - if self.type is None: - raise PluginError("ERROR: Seq Type is None!") return self.getGenericListCmd(f"CS_{self.type.upper()}_SEQ_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) @@ -74,10 +72,10 @@ def getCmd(self): class CutsceneCmdFadeSeqList(CutsceneCmdBase): """This class contains Fade Seq List command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdFadeSeq] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "fadeSeqList" + entryTotal: int = field(init=False, default=0) + entries: list[CutsceneCmdFadeSeq] = field(init=False, default_factory=list) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="fadeSeqList") def __post_init__(self): if self.params is not None: diff --git a/fast64_internal/oot/exporter/cutscene/text.py b/fast64_internal/oot/exporter/cutscene/text.py index eaf7382b5..f5e79d622 100644 --- a/fast64_internal/oot/exporter/cutscene/text.py +++ b/fast64_internal/oot/exporter/cutscene/text.py @@ -9,12 +9,13 @@ class CutsceneCmdText(CutsceneCmdBase): """This class contains Text command data""" - textId: Optional[int] = None - type: Optional[str] = None - altTextId1: Optional[int] = None - altTextId2: Optional[int] = None - paramNumber: int = 6 - id: str = "Text" + textId: int = 0 + type: str = str() + altTextId1: int = 0 + altTextId2: int = 0 + + paramNumber: int = field(init=False, default=6) + id: str = field(init=False, default="Text") def __post_init__(self): if self.params is not None: @@ -27,14 +28,6 @@ def __post_init__(self): def getCmd(self): self.validateFrames() - if self.textId is None: - raise PluginError("ERROR: ``textId`` is None!") - if self.type is None: - raise PluginError("ERROR: ``type`` is None!") - if self.altTextId1 is None: - raise PluginError("ERROR: ``altTextId1`` is None!") - if self.altTextId2 is None: - raise PluginError("ERROR: ``altTextId2`` is None!") return indent * 3 + ( f"CS_TEXT(" + f"{self.textId}, {self.startFrame}, {self.endFrame}, {self.type}, {self.altTextId1}, {self.altTextId2}" @@ -46,8 +39,8 @@ def getCmd(self): class CutsceneCmdTextNone(CutsceneCmdBase): """This class contains Text None command data""" - paramNumber: int = 2 - id: str = "None" + paramNumber: int = field(init=False, default=2) + id: str = field(init=False, default="None") def __post_init__(self): if self.params is not None: @@ -63,10 +56,11 @@ def getCmd(self): class CutsceneCmdTextOcarinaAction(CutsceneCmdBase): """This class contains Text Ocarina Action command data""" - ocarinaActionId: Optional[str] = None - messageId: Optional[int] = None - paramNumber: int = 4 - id: str = "OcarinaAction" + ocarinaActionId: str = str() + messageId: int = 0 + + paramNumber: int = field(init=False, default=4) + id: str = field(init=False, default="OcarinaAction") def __post_init__(self): if self.params is not None: @@ -92,10 +86,12 @@ def getCmd(self): class CutsceneCmdTextList(CutsceneCmdBase): """This class contains Text List command data""" - entryTotal: Optional[int] = None - entries: list[CutsceneCmdText | CutsceneCmdTextNone | CutsceneCmdTextOcarinaAction] = field(default_factory=list) - paramNumber: int = 1 - listName: str = "textList" + entryTotal: int = field(init=False, default=0) + entries: list[CutsceneCmdText | CutsceneCmdTextNone | CutsceneCmdTextOcarinaAction] = field( + init=False, default_factory=list + ) + paramNumber: int = field(init=False, default=1) + listName: str = field(init=False, default="textList") def __post_init__(self): if self.params is not None: diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 1036b2774..2876b0c97 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -1,7 +1,7 @@ import bpy import os -from dataclasses import dataclass +from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object from typing import Optional @@ -47,14 +47,14 @@ class SceneExporter: isSingleFile: bool textureExportSettings: TextureExportSettings useMacros: bool - dlFormat: DLFormat = DLFormat.Static - - sceneObj: Optional[Object] = None - scene: Optional[Scene] = None - path: Optional[str] = None - sceneFile: Optional[SceneFile] = None - hasCutscenes: bool = False - hasSceneTextures: bool = False + + dlFormat: DLFormat = field(init=False, default=DLFormat.Static) + sceneObj: Optional[Object] = field(init=False, default=None) + scene: Optional[Scene] = field(init=False, default=None) + path: Optional[str] = field(init=False, default=None) + sceneFile: Optional[SceneFile] = field(init=False, default=None) + hasCutscenes: bool = field(init=False, default=False) + hasSceneTextures: bool = field(init=False, default=False) def getNewScene(self): """Returns and creates scene data""" diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index f16577160..64347eca3 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -27,30 +27,30 @@ class RoomInfos(HeaderBase): ### General ### - index: Optional[int] = None - roomShape: Optional[str] = None + index: int = field(init=False) + roomShape: str = field(init=False) ### Behavior ### - roomBehavior: Optional[str] = None - playerIdleType: Optional[str] = None - disableWarpSongs: Optional[bool] = None - showInvisActors: Optional[bool] = None + roomBehavior: str = field(init=False) + playerIdleType: str = field(init=False) + disableWarpSongs: bool = field(init=False) + showInvisActors: bool = field(init=False) ### Skybox And Time ### - disableSky: Optional[bool] = None - disableSunMoon: Optional[bool] = None - hour: Optional[int] = None - minute: Optional[int] = None - timeSpeed: Optional[float] = None - echo: Optional[str] = None + disableSky: bool = field(init=False) + disableSunMoon: bool = field(init=False) + hour: int = field(init=False) + minute: int = field(init=False) + timeSpeed: float = field(init=False) + echo: str = field(init=False) ### Wind ### - setWind: Optional[bool] = None - direction: tuple[int, int, int] = None - strength: Optional[int] = None + setWind: bool = field(init=False) + direction: tuple[int, int, int] = field(init=False) + strength: int = field(init=False) def __post_init__(self): self.index = self.props.roomIndex @@ -94,7 +94,7 @@ def getCmds(self): class RoomObjects(HeaderBase): """This class defines an OoT object array""" - objectList: list[str] = field(default_factory=list) + objectList: list[str] = field(init=False, default_factory=list) def __post_init__(self): for objProp in self.props.objectList: @@ -137,7 +137,7 @@ def getC(self): class RoomActors(HeaderBase): """This class defines an OoT actor array""" - actorList: list[Actor] = field(default_factory=list) + actorList: list[Actor] = field(init=False, default_factory=list) def __post_init__(self): actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor", parentObj=self.roomObj) @@ -210,9 +210,9 @@ def getC(self): class RoomHeader(HeaderBase): """This class defines a room header""" - infos: Optional[RoomInfos] = None - objects: Optional[RoomObjects] = None - actors: Optional[RoomActors] = None + infos: Optional[RoomInfos] = field(init=False, default=None) + objects: Optional[RoomObjects] = field(init=False, default=None) + actors: Optional[RoomActors] = field(init=False, default=None) def __post_init__(self): self.infos = RoomInfos(None, self.props) @@ -242,7 +242,8 @@ class RoomAlternateHeader: """This class stores alternate header data""" name: str - childNight: Optional[RoomHeader] = None - adultDay: Optional[RoomHeader] = None - adultNight: Optional[RoomHeader] = None - cutscenes: list[RoomHeader] = field(default_factory=list) + + childNight: Optional[RoomHeader] = field(init=False, default=None) + adultDay: Optional[RoomHeader] = field(init=False, default=None) + adultNight: Optional[RoomHeader] = field(init=False, default=None) + cutscenes: list[RoomHeader] = field(init=False, default_factory=list) diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index cd4750a55..9c6c60fe8 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -1,11 +1,10 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ...room.properties import OOTRoomHeaderProperty -from ...oot_constants import ootData from ...oot_object import addMissingObjectsToAllRoomHeadersNew from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter @@ -31,11 +30,11 @@ class Room(Base): sceneName: str saveTexturesAsPNG: bool - mainHeader: Optional[RoomHeader] = None - altHeader: Optional[RoomAlternateHeader] = None - mesh: Optional[OOTRoomMesh] = None - roomShape: Optional[RoomShape] = None - hasAlternateHeaders: bool = False + mainHeader: Optional[RoomHeader] = field(init=False, default=None) + altHeader: Optional[RoomAlternateHeader] = field(init=False, default=None) + mesh: Optional[OOTRoomMesh] = field(init=False, default=None) + roomShape: Optional[RoomShape] = field(init=False, default=None) + hasAlternateHeaders: bool = field(init=False) def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): """Returns a new room header with the informations from the scene empty object""" diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 2d49e2361..5245360aa 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -47,12 +47,12 @@ class RoomShapeImageMultiBgEntry: height: int otherModeFlags: str - unk_00: int = 130 - unk_0C: int = 0 - tlut: str = "NULL" - format: str = "G_IM_FMT_RGBA" - size: str = "G_IM_SIZ_16b" - tlutCount: int = 0 + unk_00: int = field(init=False, default=130) + unk_0C: int = field(init=False, default=0) + tlut: str = field(init=False, default="NULL") + format: str = field(init=False, default="G_IM_FMT_RGBA") + size: str = field(init=False, default="G_IM_SIZ_16b") + tlutCount: int = field(init=False, default=0) def getEntryC(self): return ( @@ -79,7 +79,8 @@ class RoomShapeImageMultiBg(RoomShapeBase): """This class defines the multiple background image array""" name: str - entries: list[RoomShapeImageMultiBgEntry] = field(default_factory=list) + + entries: list[RoomShapeImageMultiBgEntry] = field(init=False, default_factory=list) def __post_init__(self): for i, bgImg in enumerate(self.mesh.bgImages): @@ -109,7 +110,8 @@ class RoomShapeDLists: name: str isArray: bool mesh: OOTRoomMesh - entries: list[RoomShapeDListsEntry] = field(default_factory=list) + + entries: list[RoomShapeDListsEntry] = field(init=False, default_factory=list) def __post_init__(self): for meshGrp in self.mesh.meshEntries: @@ -150,11 +152,11 @@ class RoomShapeImageSingle(RoomShapeImageBase): height: int otherModeFlags: str - unk_0C: int = 0 - tlut: str = "NULL" - format: str = "G_IM_FMT_RGBA" - size: str = "G_IM_SIZ_16b" - tlutCount: int = 0 + unk_0C: int = field(init=False, default=0) + tlut: str = field(init=False, default="NULL") + format: str = field(init=False, default="G_IM_FMT_RGBA") + size: str = field(init=False, default="G_IM_SIZ_16b") + tlutCount: int = field(init=False, default=0) def getC(self): """Returns the single background image mode variable""" @@ -245,11 +247,11 @@ class RoomShape(RoomShapeBase): sceneName: str roomName: str - dl: Optional[RoomShapeDLists] = None - normal: Optional[RoomShapeNormal] = None - single: Optional[RoomShapeImageSingle] = None - multiImg: Optional[RoomShapeImageMultiBg] = None - multi: Optional[RoomShapeImageMulti] = None + dl: Optional[RoomShapeDLists] = field(init=False, default=None) + normal: Optional[RoomShapeNormal] = field(init=False, default=None) + single: Optional[RoomShapeImageSingle] = field(init=False, default=None) + multiImg: Optional[RoomShapeImageMultiBg] = field(init=False, default=None) + multi: Optional[RoomShapeImageMulti] = field(init=False, default=None) def __post_init__(self): name = f"{self.roomName}_shapeHeader" diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 26c111c38..0894c8957 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -1,6 +1,7 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object +from typing import Optional from ....utility import PluginError, CData, indent from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod from ...scene.properties import OOTSceneHeaderProperty @@ -23,11 +24,11 @@ class Scene(Base): saveTexturesAsPNG: bool model: OOTModel - mainHeader: SceneHeader = None - altHeader: SceneAlternateHeader = None - rooms: RoomEntries = None - colHeader: CollisionHeader = None - hasAlternateHeaders: bool = False + mainHeader: Optional[SceneHeader] = field(init=False, default=None) + altHeader: Optional[SceneAlternateHeader] = field(init=False, default=None) + rooms: Optional[RoomEntries] = field(init=False, default=None) + colHeader: Optional[CollisionHeader] = field(init=False, default=None) + hasAlternateHeaders: bool = field(init=False, default=False) def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): """Returns a scene header""" diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 7dcd8e60a..9bbbcfe3e 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -13,11 +13,11 @@ class TransitionActor(Actor): """Defines a Transition Actor""" - isRoomTransition: Optional[bool] = None - roomFrom: Optional[int] = None - roomTo: Optional[int] = None - cameraFront: Optional[str] = None - cameraBack: Optional[str] = None + isRoomTransition: Optional[bool] = field(init=False, default=None) + roomFrom: Optional[int] = field(init=False, default=None) + roomTo: Optional[int] = field(init=False, default=None) + cameraFront: Optional[str] = field(init=False, default=None) + cameraBack: Optional[str] = field(init=False, default=None) def getEntryC(self): """Returns a single transition actor entry""" @@ -45,7 +45,7 @@ class SceneTransitionActors(Base): transform: Matrix headerIndex: int - entries: list[TransitionActor] = field(default_factory=list) + entries: list[TransitionActor] = field(init=False, default_factory=list) def __post_init__(self): actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") @@ -121,8 +121,8 @@ def getC(self): class EntranceActor(Actor): """Defines an Entrance Actor""" - roomIndex: Optional[int] = None - spawnIndex: Optional[int] = None + roomIndex: Optional[int] = field(init=False, default=None) + spawnIndex: Optional[int] = field(init=False, default=None) def getEntryC(self): """Returns a single spawn entry""" @@ -138,7 +138,7 @@ class SceneEntranceActors(Base): transform: Matrix headerIndex: int - entries: list[EntranceActor] = field(default_factory=list) + entries: list[EntranceActor] = field(init=False, default_factory=list) def __post_init__(self): """Returns the entrance actor list based on empty objects with the type 'Entrance'""" diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index 01aa11904..45aba19db 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -88,8 +88,8 @@ class SceneLighting(Base): props: OOTSceneHeaderProperty name: str - envLightMode: Optional[str] = None - settings: list[EnvLightSettings] = field(default_factory=list) + envLightMode: str = field(init=False) + settings: list[EnvLightSettings] = field(init=False, default_factory=list) def __post_init__(self): self.envLightMode = self.getPropValue(self.props, "skyboxLighting") @@ -152,30 +152,30 @@ class SceneInfos(Base): ### General ### - keepObjectID: Optional[str] = None - naviHintType: Optional[str] = None - drawConfig: Optional[str] = None - appendNullEntrance: Optional[bool] = None - useDummyRoomList: Optional[bool] = None + keepObjectID: str = field(init=False) + naviHintType: str = field(init=False) + drawConfig: str = field(init=False) + appendNullEntrance: bool = field(init=False) + useDummyRoomList: bool = field(init=False) ### Skybox And Sound ### # Skybox - skyboxID: Optional[str] = None - skyboxConfig: Optional[str] = None + skyboxID: str = field(init=False) + skyboxConfig: str = field(init=False) # Sound - sequenceID: Optional[str] = None - ambienceID: Optional[str] = None - specID: Optional[str] = None + sequenceID: str = field(init=False) + ambienceID: str = field(init=False) + specID: str = field(init=False) ### Camera And World Map ### # World Map - worldMapLocation: Optional[str] = None + worldMapLocation: str = field(init=False) # Camera - sceneCamType: Optional[str] = None + sceneCamType: str = field(init=False) def __post_init__(self): self.keepObjectID = self.getPropValue(self.props, "globalObject") @@ -214,7 +214,8 @@ class SceneExits(Base): props: OOTSceneHeaderProperty name: str - exitList: list[tuple[int, str]] = field(default_factory=list) + + exitList: list[tuple[int, str]] = field(init=False, default_factory=list) def __post_init__(self): # TODO: proper implementation of exits diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index c22ed998e..48edbaebd 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -22,14 +22,14 @@ class SceneHeader(Base): headerIndex: int useMacros: bool - infos: Optional[SceneInfos] = None - lighting: Optional[SceneLighting] = None - cutscene: Optional[SceneCutscene] = None - exits: Optional[SceneExits] = None - transitionActors: Optional[SceneTransitionActors] = None - entranceActors: Optional[SceneEntranceActors] = None - spawns: Optional[SceneSpawns] = None - path: Optional[ScenePathways] = None + infos: Optional[SceneInfos] = field(init=False, default=None) + lighting: Optional[SceneLighting] = field(init=False, default=None) + cutscene: Optional[SceneCutscene] = field(init=False, default=None) + exits: Optional[SceneExits] = field(init=False, default=None) + transitionActors: Optional[SceneTransitionActors] = field(init=False, default=None) + entranceActors: Optional[SceneEntranceActors] = field(init=False, default=None) + spawns: Optional[SceneSpawns] = field(init=False, default=None) + path: Optional[ScenePathways] = field(init=False, default=None) def __post_init__(self): self.infos = SceneInfos(self.props, self.sceneObj) @@ -82,7 +82,8 @@ class SceneAlternateHeader: """This class stores alternate header data for the scene""" name: str - childNight: Optional[SceneHeader] = None - adultDay: Optional[SceneHeader] = None - adultNight: Optional[SceneHeader] = None - cutscenes: list[SceneHeader] = field(default_factory=list) + + childNight: Optional[SceneHeader] = field(init=False, default=None) + adultDay: Optional[SceneHeader] = field(init=False, default=None) + adultNight: Optional[SceneHeader] = field(init=False, default=None) + cutscenes: list[SceneHeader] = field(init=False, default_factory=list) diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index cdbd61d69..656de6692 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -46,7 +46,7 @@ class ScenePathways(Base): transform: Matrix headerIndex: int - pathList: list[Path] = field(default_factory=list) + pathList: list[Path] = field(init=False, default_factory=list) def __post_init__(self): pathFromIndex: dict[int, Path] = {} diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index 16adf25bd..306c28af5 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -19,7 +19,7 @@ class RoomEntries: transform: Matrix saveTexturesAsPNG: bool - entries: list[Room] = field(default_factory=list) + entries: list[Room] = field(init=False, default_factory=list) def __post_init__(self): """Returns the room list from empty objects with the type 'Room'""" From 9189064eecef6562989f3a7b491c0b470be7de92 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:36:13 +0100 Subject: [PATCH 75/98] fix issues --- fast64_internal/oot/exporter/cutscene/__init__.py | 6 +++--- fast64_internal/oot/exporter/main.py | 5 +++-- fast64_internal/oot/exporter/room/main.py | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index 7d9ad5ade..98920be56 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -23,10 +23,10 @@ class Cutscene: csObj: Object useMacros: bool + name: str = str() + motionOnly: bool = False - name: str = field(init=False) - motionOnly: bool = field(init=False, default=False) - data: CutsceneData = field(init=False) + data: Optional[CutsceneData] = field(init=False, default=None) totalEntries: int = field(init=False) frameCount: int = field(init=False) paramNumber: int = field(init=False, default=2) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 2876b0c97..b5ce1375a 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -7,10 +7,8 @@ from typing import Optional from ...f3d.f3d_gbi import DLFormat, TextureExportSettings from ..scene.properties import OOTBootupSceneOptions -from ..scene.exporter.to_c import setBootupScene from ..oot_model_classes import OOTModel from ..oot_f3d_writer import writeTextureArraysNew -from ..oot_level_writer import writeTextureArraysExistingScene from .scene import Scene from .other import Files from .classes import SceneFile @@ -100,6 +98,9 @@ def getNewScene(self): def export(self): """Main function""" + # circular import fixes + from ..scene.exporter.to_c import setBootupScene + from ..oot_level_writer import writeTextureArraysExistingScene checkObjectReference(self.originalSceneObj, "Scene object") isCustomExport = self.exportInfo.isCustomExportPath diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 9c6c60fe8..d9599e2dc 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -8,7 +8,6 @@ from ...oot_object import addMissingObjectsToAllRoomHeadersNew from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter -from ...oot_level_writer import BoundingBox, ootProcessMesh from ...oot_utility import CullGroup from ..classes import RoomFile from ..base import Base, altHeaderList @@ -49,6 +48,8 @@ def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = ) def __post_init__(self): + from ...oot_level_writer import BoundingBox, ootProcessMesh # circular import fix + mainHeaderProps = self.roomObj.ootRoomHeader altHeader = RoomAlternateHeader(f"{self.name}_alternateHeaders") altProp = self.roomObj.ootAlternateRoomHeaders From 730ed9887f42794b91c0d7c83a510f5aeaac76c2 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:13:15 +0100 Subject: [PATCH 76/98] review part 2 --- fast64_internal/oot/exporter/__init__.py | 2 +- fast64_internal/oot/exporter/base.py | 42 +++++++++---------- .../oot/exporter/collision/__init__.py | 22 +++++----- .../oot/exporter/collision/camera.py | 8 ++-- .../oot/exporter/collision/waterbox.py | 19 +++++++-- .../oot/exporter/cutscene/__init__.py | 4 +- fast64_internal/oot/exporter/main.py | 2 +- fast64_internal/oot/exporter/other/file.py | 4 +- .../oot/exporter/other/scene_table.py | 6 +-- fast64_internal/oot/exporter/other/spec.py | 4 +- fast64_internal/oot/exporter/room/header.py | 18 ++++---- fast64_internal/oot/exporter/room/main.py | 10 ++--- .../oot/exporter/scene/__init__.py | 8 ++-- fast64_internal/oot/exporter/scene/actors.py | 30 +++++-------- fast64_internal/oot/exporter/scene/general.py | 30 ++++++------- fast64_internal/oot/exporter/scene/header.py | 4 +- .../oot/exporter/scene/pathways.py | 6 +-- fast64_internal/oot/scene/operators.py | 4 +- 18 files changed, 111 insertions(+), 112 deletions(-) diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 63e005639..3b7228c36 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -1 +1 @@ -from .main import SceneExporter +from .main import SceneExport diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/base.py index e2fcba1f2..3faefbd08 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/base.py @@ -11,22 +11,11 @@ altHeaderList = ["childNight", "adultDay", "adultNight"] -@dataclass -class Base: - """This class hosts common data used across different sub-systems of this exporter""" - - def getRoomObjectFromChild(self, childObj: Object) -> Object | None: - """Returns the room empty object from one of its child""" - - # Note: temporary solution until PRs #243 & #255 are merged - for obj in self.sceneObj.children_recursive: - if obj.type == "EMPTY" and obj.ootEmptyType == "Room": - for o in obj.children_recursive: - if o == childObj: - return obj - return None +class Utility: + """This class hosts different functions used across different sub-systems of this exporter""" - def validateCurveData(self, curveObj: Object): + @staticmethod + def validateCurveData(curveObj: Object): """Performs safety checks related to curve objects""" curveData = curveObj.data @@ -40,12 +29,14 @@ def validateCurveData(self, curveObj: Object): return True - def roundPosition(self, position) -> tuple[int, int, int]: + @staticmethod + def roundPosition(position) -> tuple[int, int, int]: """Returns the rounded position values""" return (round(position[0]), round(position[1]), round(position[2])) - def isCurrentHeaderValid(self, headerSettings: OOTActorHeaderProperty, headerIndex: int): + @staticmethod + def isCurrentHeaderValid(headerSettings: OOTActorHeaderProperty, headerIndex: int): """Checks if the an alternate header can be used""" preset = headerSettings.sceneSetupPreset @@ -64,14 +55,16 @@ def isCurrentHeaderValid(self, headerSettings: OOTActorHeaderProperty, headerInd return False - def getPropValue(self, data, propName: str): + @staticmethod + def getPropValue(data, propName: str): """Returns a property's value based on if the value is 'Custom'""" value = getattr(data, propName) return value if value != "Custom" else getattr(data, f"{propName}Custom") + @staticmethod def getConvertedTransformWithOrientation( - self, transform: Matrix, dataHolder: Object, obj: Object, orientation: Quaternion | Matrix + transform: Matrix, dataHolder: Object, obj: Object, orientation: Quaternion | Matrix ): relativeTransform = transform @ dataHolder.matrix_world.inverted() @ obj.matrix_world blenderTranslation, blenderRotation, scale = relativeTransform.decompose() @@ -81,21 +74,24 @@ def getConvertedTransformWithOrientation( return convertedTranslation, convertedRotation, scale, rotation - def getConvertedTransform(self, transform: Matrix, dataHolder: Object, obj: Object, handleOrientation: bool): + @staticmethod + def getConvertedTransform(transform: Matrix, dataHolder: Object, obj: Object, handleOrientation: bool): # Hacky solution to handle Z-up to Y-up conversion # We cannot apply rotation to empty, as that modifies scale if handleOrientation: orientation = Quaternion((1, 0, 0), radians(90.0)) else: orientation = Matrix.Identity(4) - return self.getConvertedTransformWithOrientation(transform, dataHolder, obj, orientation) + return Utility.getConvertedTransformWithOrientation(transform, dataHolder, obj, orientation) - def getAltHeaderListCmd(self, altName: str): + @staticmethod + def getAltHeaderListCmd(altName: str): """Returns the scene alternate header list command""" return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" - def getEndCmd(self): + @staticmethod + def getEndCmd(): """Returns the scene end command""" return indent + "SCENE_CMD_END(),\n" diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index c0285438b..d555302ff 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -7,7 +7,7 @@ from typing import Optional from ....utility import PluginError, CData, indent from ...oot_utility import convertIntTo2sComplement -from ..base import Base +from ..base import Utility from .polygons import CollisionPoly, CollisionPolygons from .surface import SurfaceType, SurfaceTypes from .camera import BgCamInformations @@ -16,7 +16,7 @@ @dataclass -class CollisionBase(Base): +class CollisionBase: """This class hosts different functions used to convert mesh data""" sceneObj: Optional[Object] @@ -101,9 +101,9 @@ def getCollisionData(self): # get bounds and vertices data planePoint = transform @ meshObj.data.vertices[face.vertices[0]].co - (x1, y1, z1) = self.roundPosition(planePoint) - (x2, y2, z2) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) - (x3, y3, z3) = self.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) + (x1, y1, z1) = Utility.roundPosition(planePoint) + (x2, y2, z2) = Utility.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) + (x3, y3, z3) = Utility.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) self.updateBounds((x1, y1, z1), colBounds) self.updateBounds((x2, y2, z2), colBounds) self.updateBounds((x3, y3, z3), colBounds) @@ -158,19 +158,19 @@ def getCollisionData(self): # get surface type and collision poly data useConveyor = colProp.conveyorOption != "None" - conveyorSpeed = int(self.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0 + conveyorSpeed = int(Utility.getPropValue(colProp, "conveyorSpeed"), base=16) if useConveyor else 0 shouldKeepMomentum = colProp.conveyorKeepMomentum if useConveyor else False surfaceType = SurfaceType( colProp.cameraID, colProp.exitID, - int(self.getPropValue(colProp, "floorProperty"), base=16), + int(Utility.getPropValue(colProp, "floorProperty"), base=16), 0, # unused? - int(self.getPropValue(colProp, "wallSetting"), base=16), - int(self.getPropValue(colProp, "floorSetting"), base=16), + int(Utility.getPropValue(colProp, "wallSetting"), base=16), + int(Utility.getPropValue(colProp, "floorSetting"), base=16), colProp.decreaseHeight, colProp.eponaBlock, - int(self.getPropValue(colProp, "sound"), base=16), - int(self.getPropValue(colProp, "terrain"), base=16), + int(Utility.getPropValue(colProp, "sound"), base=16), + int(Utility.getPropValue(colProp, "terrain"), base=16), colProp.lightingSetting, int(colProp.echo, base=16), colProp.hookshotable, diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 49064d603..28f16b4f0 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -7,7 +7,7 @@ from ...oot_utility import getObjectList from ...collision.constants import decomp_compat_map_CameraSType from ...collision.properties import OOTCameraPositionProperty -from ..base import Base +from ..base import Utility @dataclass @@ -72,7 +72,7 @@ def getInfoEntryC(self, posDataName: str): @dataclass -class BgCamInformations(Base): +class BgCamInformations: """This class defines the array of camera informations and the array of the associated data""" dataHolder: Object @@ -91,7 +91,7 @@ def initCrawlspaceList(self): crawlspaceObjList = getObjectList(self.dataHolder.children_recursive, "CURVE", splineType="Crawlspace") for obj in crawlspaceObjList: - if self.validateCurveData(obj): + if Utility.validateCurveData(obj): points = [ [round(value) for value in self.transform @ obj.matrix_world @ point.co] for point in obj.data.splines[0].points @@ -123,7 +123,7 @@ def initBgCamInfoList(self): raise PluginError(f"ERROR: Repeated camera position index: {camProp.index} for {camObj.name}") # Camera faces opposite direction - pos, rot, _, _ = self.getConvertedTransformWithOrientation( + pos, rot, _, _ = Utility.getConvertedTransformWithOrientation( self.transform, self.dataHolder, camObj, Quaternion((0, 1, 0), math.radians(180.0)) ) diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 883240ee8..7a9916c49 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -4,7 +4,7 @@ from bpy.types import Object from ...oot_utility import getObjectList from ....utility import CData, checkIdentityRotation, indent -from ..base import Base +from ..base import Utility @dataclass @@ -79,7 +79,7 @@ def getEntryC(self): @dataclass -class WaterBoxes(Base): +class WaterBoxes: """This class defines the array of waterboxes""" dataHolder: Object @@ -93,12 +93,23 @@ def __post_init__(self): waterboxObjList = getObjectList(self.dataHolder.children_recursive, "EMPTY", "Water Box") for waterboxObj in waterboxObjList: emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = self.getConvertedTransform(self.transform, self.dataHolder, waterboxObj, True) + pos, _, scale, orientedRot = Utility.getConvertedTransform( + self.transform, self.dataHolder, waterboxObj, True + ) checkIdentityRotation(waterboxObj, orientedRot, False) wboxProp = waterboxObj.ootWaterBoxProperty + # temp solution - roomObj = self.getRoomObjectFromChild(waterboxObj) if self.dataHolder.type == "EMPTY" else None + roomObj = None + if self.dataHolder.type == "EMPTY" and self.dataHolder.ootEmptyType == "Scene": + for obj in self.dataHolder.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": + for o in obj.children_recursive: + if o == waterboxObj: + roomObj = obj + break + self.waterboxList.append( WaterBox( pos, diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index 98920be56..c54e228e9 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -6,7 +6,7 @@ from ....utility import PluginError, CData, indent from ...oot_utility import getCustomProperty from ...scene.properties import OOTSceneHeaderProperty -from ..base import Base +from ..base import Utility from .data import CutsceneData @@ -97,7 +97,7 @@ def getC(self): @dataclass -class SceneCutscene(Base): +class SceneCutscene(Utility): """This class hosts cutscene data""" props: OOTSceneHeaderProperty diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index b5ce1375a..854f113fd 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -32,7 +32,7 @@ @dataclass -class SceneExporter: +class SceneExport: """This class is the main exporter class, it handles generating the C data and writing the files""" exportInfo: ExportInfo diff --git a/fast64_internal/oot/exporter/other/file.py b/fast64_internal/oot/exporter/other/file.py index 803914a41..5d85b5e7a 100644 --- a/fast64_internal/oot/exporter/other/file.py +++ b/fast64_internal/oot/exporter/other/file.py @@ -8,14 +8,14 @@ from .spec import Spec if TYPE_CHECKING: - from ..main import SceneExporter + from ..main import SceneExport @dataclass class Files: # TODO: find a better name """This class handles editing decomp files""" - exporter: "SceneExporter" + exporter: "SceneExport" def modifySceneFiles(self): if self.exporter.exportInfo.customSubPath is not None: diff --git a/fast64_internal/oot/exporter/other/scene_table.py b/fast64_internal/oot/exporter/other/scene_table.py index 0a34862cb..574b3c683 100644 --- a/fast64_internal/oot/exporter/other/scene_table.py +++ b/fast64_internal/oot/exporter/other/scene_table.py @@ -7,7 +7,7 @@ from ...oot_utility import ExportInfo if TYPE_CHECKING: - from ..main import SceneExporter + from ..main import SceneExport class SceneTable: @@ -138,7 +138,7 @@ def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: st # if the index hasn't been found yet, do it again but decrement the index return self.getInsertionIndex(isExport, sceneNames, sceneName, currentIndex - 1, mode) - def getSceneParams(self, exporter: "SceneExporter", exportInfo: ExportInfo, sceneNames: list[str]): + def getSceneParams(self, exporter: "SceneExport", exportInfo: ExportInfo, sceneNames: list[str]): """Returns the parameters that needs to be set in ``DEFINE_SCENE()``""" # in order to replace the values of ``unk10``, ``unk12`` and basically every parameters from ``DEFINE_SCENE``, @@ -218,7 +218,7 @@ def addHackerOoTData(self, fileData: str): return "".join(newFileData) - def editSceneTable(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + def editSceneTable(self, exporter: "SceneExport", exportInfo: ExportInfo = None): """Edit the scene table with the new data""" isExport = exporter is not None diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py index 720018aad..58a5b725b 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/other/spec.py @@ -6,7 +6,7 @@ from ...oot_utility import ExportInfo, getSceneDirFromLevelName if TYPE_CHECKING: - from ..main import SceneExporter + from ..main import SceneExport class Spec: @@ -45,7 +45,7 @@ def getSpecEntries(self, fileData: str): return entries, compressFlag, includes - def editSpec(self, exporter: "SceneExporter", exportInfo: ExportInfo = None): + def editSpec(self, exporter: "SceneExport", exportInfo: ExportInfo = None): """Adds or removes entries for the selected scene in the spec file""" isExport = exporter is not None diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 64347eca3..07ff2fa47 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -6,19 +6,19 @@ from ...oot_utility import getObjectList from ...oot_constants import ootData from ...room.properties import OOTRoomHeaderProperty -from ..base import Base, Actor +from ..base import Utility, Actor @dataclass -class HeaderBase(Base): +class HeaderBase: """Defines the base of a room header""" - name: Optional[str] = None + name: str = str() props: Optional[OOTRoomHeaderProperty] = None sceneObj: Optional[Object] = None roomObj: Optional[Object] = None - transform: Optional[Matrix] = None - headerIndex: Optional[int] = None + transform: Matrix = Matrix() + headerIndex: int = 0 @dataclass @@ -55,8 +55,8 @@ class RoomInfos(HeaderBase): def __post_init__(self): self.index = self.props.roomIndex self.roomShape = self.props.roomShape - self.roomBehavior = self.getPropValue(self.props, "roomBehaviour") - self.playerIdleType = self.getPropValue(self.props, "linkIdleMode") + self.roomBehavior = Utility.getPropValue(self.props, "roomBehaviour") + self.playerIdleType = Utility.getPropValue(self.props, "linkIdleMode") self.disableWarpSongs = self.props.disableWarpSongs self.showInvisActors = self.props.showInvisibleActors self.disableSky = self.props.disableSkybox @@ -143,7 +143,7 @@ def __post_init__(self): actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor", parentObj=self.roomObj) for obj in actorObjList: actorProp = obj.ootActorProperty - if not self.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): + if not Utility.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): continue # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for @@ -152,7 +152,7 @@ def __post_init__(self): # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if # the current Actor has the ID `None` to avoid export issues. if actorProp.actorID != "None": - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) actor = Actor() if actorProp.actorID == "Custom": diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index d9599e2dc..d34003fb3 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -10,13 +10,13 @@ from ...oot_model_classes import OOTModel, OOTGfxFormatter from ...oot_utility import CullGroup from ..classes import RoomFile -from ..base import Base, altHeaderList +from ..base import Utility, altHeaderList from .header import RoomAlternateHeader, RoomHeader from .shape import RoomShape @dataclass -class Room(Base): +class Room: """This class defines a room""" name: str @@ -79,7 +79,7 @@ def __post_init__(self): # Mesh stuff self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) - pos, _, scale, _ = Base().getConvertedTransform(self.transform, self.sceneObj, self.roomObj, True) + pos, _, scale, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, self.roomObj, True) cullGroup = CullGroup(pos, scale, self.roomObj.ootRoomHeader.defaultCullDistance) DLGroup = self.mesh.addMeshGroup(cullGroup).DLGroup boundingBox = BoundingBox() @@ -126,12 +126,12 @@ def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): # .c cmdListData.source = ( (f"{listName}[]" + " = {\n") - + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + (Utility.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + self.roomShape.getCmd() + curHeader.infos.getCmds() + (curHeader.objects.getCmd() if len(curHeader.objects.objectList) > 0 else "") + (curHeader.actors.getCmd() if len(curHeader.actors.actorList) > 0 else "") - + self.getEndCmd() + + Utility.getEndCmd() + "};\n\n" ) diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 0894c8957..17fb6dbb5 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -7,14 +7,14 @@ from ...scene.properties import OOTSceneHeaderProperty from ...oot_model_classes import OOTModel, OOTGfxFormatter from ..classes import SceneFile -from ..base import Base, altHeaderList +from ..base import Utility, altHeaderList from ..collision import CollisionHeader from .header import SceneAlternateHeader, SceneHeader from .rooms import RoomEntries @dataclass -class Scene(Base): +class Scene: """This class defines a scene""" sceneObj: Object @@ -118,7 +118,7 @@ def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): # .c cmdListData.source = ( (f"{listName}[]" + " = {\n") - + (self.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + (Utility.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + self.colHeader.getCmd() + self.rooms.getCmd() + curHeader.infos.getCmds(curHeader.lighting) @@ -129,7 +129,7 @@ def getCmdList(self, curHeader: SceneHeader, hasAltHeaders: bool): + curHeader.entranceActors.getCmd() + (curHeader.exits.getCmd() if len(curHeader.exits.exitList) > 0 else "") + (curHeader.cutscene.getCmd() if len(curHeader.cutscene.entries) > 0 else "") - + self.getEndCmd() + + Utility.getEndCmd() + "};\n\n" ) diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 9bbbcfe3e..f7a122bcd 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -6,7 +6,7 @@ from ...oot_utility import getObjectList from ...oot_constants import ootData from ...scene.properties import OOTSceneHeaderProperty -from ..base import Base, Actor +from ..base import Utility, Actor @dataclass @@ -38,7 +38,7 @@ def getEntryC(self): @dataclass -class SceneTransitionActors(Base): +class SceneTransitionActors: props: OOTSceneHeaderProperty name: str sceneObj: Object @@ -51,18 +51,14 @@ def __post_init__(self): actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") actorObjList.sort(key=lambda obj: obj.ootTransitionActorProperty.fromRoom.ootRoomHeader.roomIndex) for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - self.roomIndex = roomObj.ootRoomHeader.roomIndex - transActorProp = obj.ootTransitionActorProperty + self.roomIndex = transActorProp.fromRoom.roomIndex if ( - self.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) + Utility.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) and transActorProp.actor.actorID != "None" ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) transActor = TransitionActor() if transActorProp.isRoomTransition: @@ -72,8 +68,8 @@ def __post_init__(self): toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex else: fromIndex = toIndex = self.roomIndex - front = (fromIndex, self.getPropValue(transActorProp, "cameraTransitionFront")) - back = (toIndex, self.getPropValue(transActorProp, "cameraTransitionBack")) + front = (fromIndex, Utility.getPropValue(transActorProp, "cameraTransitionFront")) + back = (toIndex, Utility.getPropValue(transActorProp, "cameraTransitionBack")) if transActorProp.actor.actorID == "Custom": transActor.id = transActorProp.actor.actorIDCustom @@ -131,7 +127,7 @@ def getEntryC(self): @dataclass -class SceneEntranceActors(Base): +class SceneEntranceActors: props: OOTSceneHeaderProperty name: str sceneObj: Object @@ -146,16 +142,12 @@ def __post_init__(self): entranceActorFromIndex: dict[int, EntranceActor] = {} actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Entrance") for obj in actorObjList: - roomObj = self.getRoomObjectFromChild(obj) - if roomObj is None: - raise PluginError("ERROR: Room Object not found!") - entranceProp = obj.ootEntranceProperty if ( - self.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) + Utility.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) and entranceProp.actor.actorID != "None" ): - pos, rot, _, _ = self.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) entranceActor = EntranceActor() entranceActor.name = ( @@ -211,7 +203,7 @@ def getC(self): @dataclass -class SceneSpawns(Base): +class SceneSpawns(Utility): """This class handles scene actors (transition actors and entrance actors)""" props: OOTSceneHeaderProperty diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index 45aba19db..743de55d2 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -3,7 +3,7 @@ from bpy.types import Object from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ..base import Base +from ..base import Utility @dataclass @@ -82,7 +82,7 @@ def getEntryC(self, index: int): @dataclass -class SceneLighting(Base): +class SceneLighting: """This class hosts lighting data""" props: OOTSceneHeaderProperty @@ -92,7 +92,7 @@ class SceneLighting(Base): settings: list[EnvLightSettings] = field(init=False, default_factory=list) def __post_init__(self): - self.envLightMode = self.getPropValue(self.props, "skyboxLighting") + self.envLightMode = Utility.getPropValue(self.props, "skyboxLighting") lightList: list[OOTLightProperty] = [] if self.envLightMode == "LIGHT_MODE_TIME": @@ -144,7 +144,7 @@ def getC(self): @dataclass -class SceneInfos(Base): +class SceneInfos: """This class stores various scene header informations""" props: OOTSceneHeaderProperty @@ -178,18 +178,18 @@ class SceneInfos(Base): sceneCamType: str = field(init=False) def __post_init__(self): - self.keepObjectID = self.getPropValue(self.props, "globalObject") - self.naviHintType = self.getPropValue(self.props, "naviCup") - self.drawConfig = self.getPropValue(self.props.sceneTableEntry, "drawConfig") + self.keepObjectID = Utility.getPropValue(self.props, "globalObject") + self.naviHintType = Utility.getPropValue(self.props, "naviCup") + self.drawConfig = Utility.getPropValue(self.props.sceneTableEntry, "drawConfig") self.appendNullEntrance = self.props.appendNullEntrance self.useDummyRoomList = self.sceneObj.fast64.oot.scene.write_dummy_room_list - self.skyboxID = self.getPropValue(self.props, "skyboxID") - self.skyboxConfig = self.getPropValue(self.props, "skyboxCloudiness") - self.sequenceID = self.getPropValue(self.props, "musicSeq") - self.ambienceID = self.getPropValue(self.props, "nightSeq") - self.specID = self.getPropValue(self.props, "audioSessionPreset") - self.worldMapLocation = self.getPropValue(self.props, "mapLocation") - self.sceneCamType = self.getPropValue(self.props, "cameraMode") + self.skyboxID = Utility.getPropValue(self.props, "skyboxID") + self.skyboxConfig = Utility.getPropValue(self.props, "skyboxCloudiness") + self.sequenceID = Utility.getPropValue(self.props, "musicSeq") + self.ambienceID = Utility.getPropValue(self.props, "nightSeq") + self.specID = Utility.getPropValue(self.props, "audioSessionPreset") + self.worldMapLocation = Utility.getPropValue(self.props, "mapLocation") + self.sceneCamType = Utility.getPropValue(self.props, "cameraMode") def getCmds(self, lights: SceneLighting): """Returns the sound settings, misc settings, special files and skybox settings scene commands""" @@ -209,7 +209,7 @@ def getCmds(self, lights: SceneLighting): @dataclass -class SceneExits(Base): +class SceneExits(Utility): """This class hosts exit data""" props: OOTSceneHeaderProperty diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index 48edbaebd..63371c094 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -4,7 +4,7 @@ from bpy.types import Object from ....utility import CData from ...scene.properties import OOTSceneHeaderProperty -from ..base import Base +from ..base import Utility from ..cutscene import SceneCutscene from .general import SceneLighting, SceneInfos, SceneExits from .actors import SceneTransitionActors, SceneEntranceActors, SceneSpawns @@ -12,7 +12,7 @@ @dataclass -class SceneHeader(Base): +class SceneHeader(Utility): """This class defines a scene header""" props: OOTSceneHeaderProperty diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index 656de6692..8a52551d6 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -4,7 +4,7 @@ from ....utility import PluginError, CData, indent from ...oot_utility import getObjectList from ...scene.properties import OOTSceneHeaderProperty -from ..base import Base +from ..base import Utility @dataclass @@ -37,7 +37,7 @@ def getC(self): @dataclass -class ScenePathways(Base): +class ScenePathways: """This class hosts pathways array data""" props: OOTSceneHeaderProperty @@ -56,7 +56,7 @@ def __post_init__(self): relativeTransform = self.transform @ self.sceneObj.matrix_world.inverted() @ obj.matrix_world pathProps = obj.ootSplineProperty isHeaderValid = self.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) - if isHeaderValid and self.validateCurveData(obj): + if isHeaderValid and Utility.validateCurveData(obj): if pathProps.index not in pathFromIndex: pathFromIndex[pathProps.index] = Path( f"{self.name}List{pathProps.index:02}", diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 9032292c5..e96a8ba44 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -14,7 +14,7 @@ from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles -from ..exporter import SceneExporter +from ..exporter import SceneExport def ootRemoveSceneC(exportInfo): @@ -175,7 +175,7 @@ def execute(self, context): # keeping this on purpose, will be removed once old code is cleaned-up # if settings.useNewExporter: - SceneExporter( + SceneExport( exportInfo, obj, exportInfo.name, From dc7f17549ef5d3a450591a60a18d998e2d0bfab4 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sat, 10 Feb 2024 01:02:14 +0100 Subject: [PATCH 77/98] bugfixes --- fast64_internal/oot/exporter/scene/actors.py | 16 ++++++++++++---- fast64_internal/oot/exporter/scene/header.py | 2 +- fast64_internal/oot/exporter/scene/pathways.py | 2 +- fast64_internal/oot/oot_level_parser.py | 12 +++++++++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index f7a122bcd..56f8e49a4 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -48,12 +48,20 @@ class SceneTransitionActors: entries: list[TransitionActor] = field(init=False, default_factory=list) def __post_init__(self): + # we need to get the corresponding room index if a transition actor + # do not change rooms + roomObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Room") + actorToRoom: dict[Object, Object] = {} + for obj in roomObjList: + for childObj in obj.children_recursive: + if childObj.type == "EMPTY" and childObj.ootEmptyType == "Transition Actor": + actorToRoom[childObj] = obj + actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") - actorObjList.sort(key=lambda obj: obj.ootTransitionActorProperty.fromRoom.ootRoomHeader.roomIndex) + actorObjList.sort(key=lambda obj: actorToRoom[obj].ootRoomHeader.roomIndex) + for obj in actorObjList: transActorProp = obj.ootTransitionActorProperty - self.roomIndex = transActorProp.fromRoom.roomIndex - if ( Utility.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) and transActorProp.actor.actorID != "None" @@ -67,7 +75,7 @@ def __post_init__(self): fromIndex = transActorProp.fromRoom.ootRoomHeader.roomIndex toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex else: - fromIndex = toIndex = self.roomIndex + fromIndex = toIndex = actorToRoom[obj].ootRoomHeader.roomIndex front = (fromIndex, Utility.getPropValue(transActorProp, "cameraTransitionFront")) back = (toIndex, Utility.getPropValue(transActorProp, "cameraTransitionBack")) diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index 63371c094..fd409fb1a 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -12,7 +12,7 @@ @dataclass -class SceneHeader(Utility): +class SceneHeader: """This class defines a scene header""" props: OOTSceneHeaderProperty diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index 8a52551d6..5397c8657 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -55,7 +55,7 @@ def __post_init__(self): for obj in pathObjList: relativeTransform = self.transform @ self.sceneObj.matrix_world.inverted() @ obj.matrix_world pathProps = obj.ootSplineProperty - isHeaderValid = self.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) + isHeaderValid = Utility.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) if isHeaderValid and Utility.validateCurveData(obj): if pathProps.index not in pathFromIndex: pathFromIndex[pathProps.index] = Path( diff --git a/fast64_internal/oot/oot_level_parser.py b/fast64_internal/oot/oot_level_parser.py index 35b0f500e..d966e103d 100644 --- a/fast64_internal/oot/oot_level_parser.py +++ b/fast64_internal/oot/oot_level_parser.py @@ -946,9 +946,9 @@ def parsePathList( ): pathData = getDataMatch(sceneData, pathListName, "Path", "path list") pathList = [value.replace("{", "").strip() for value in pathData.split("},") if value.strip() != ""] - for pathEntry in pathList: + for i, pathEntry in enumerate(pathList): numPoints, pathName = [value.strip() for value in pathEntry.split(",")] - parsePath(sceneObj, sceneData, pathName, headerIndex, sharedSceneData) + parsePath(sceneObj, sceneData, pathName, headerIndex, sharedSceneData, i) def createCurveFromPoints(points: list[tuple[float, float, float]], name: str): @@ -979,7 +979,12 @@ def createCurveFromPoints(points: list[tuple[float, float, float]], name: str): def parsePath( - sceneObj: bpy.types.Object, sceneData: str, pathName: str, headerIndex: int, sharedSceneData: SharedSceneData + sceneObj: bpy.types.Object, + sceneData: str, + pathName: str, + headerIndex: int, + sharedSceneData: SharedSceneData, + orderIndex: int, ): pathData = getDataMatch(sceneData, pathName, "Vec3s", "path") pathPointsEntries = [value.replace("{", "").strip() for value in pathData.split("},") if value.strip() != ""] @@ -993,6 +998,7 @@ def parsePath( curveObj = createCurveFromPoints(pathPoints, pathName) splineProp = curveObj.ootSplineProperty + splineProp.index = orderIndex unsetAllHeadersExceptSpecified(splineProp.headerSettings, headerIndex) sharedSceneData.pathDict[pathPoints] = curveObj From cda94ac947c7c3842ae28acde57f0b7228066a4e Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:06:21 +0200 Subject: [PATCH 78/98] review changes part 1 --- fast64_internal/oot/exporter/classes.py | 3 - .../oot/exporter/collision/__init__.py | 99 ++-- .../oot/exporter/collision/camera.py | 73 +-- .../oot/exporter/collision/polygons.py | 3 +- .../oot/exporter/collision/surface.py | 5 - .../oot/exporter/collision/waterbox.py | 79 ++- fast64_internal/oot/exporter/main.py | 6 +- fast64_internal/oot/exporter/other/file.py | 8 +- .../oot/exporter/other/scene_table.py | 509 +++++++++--------- fast64_internal/oot/exporter/other/spec.py | 377 +++++++++---- fast64_internal/oot/exporter/room/header.py | 138 ++--- fast64_internal/oot/exporter/room/main.py | 115 ++-- fast64_internal/oot/exporter/room/shape.py | 94 ++-- .../oot/exporter/scene/__init__.py | 69 ++- fast64_internal/oot/exporter/scene/actors.py | 41 +- fast64_internal/oot/exporter/scene/general.py | 94 ++-- fast64_internal/oot/exporter/scene/header.py | 52 +- .../oot/exporter/scene/pathways.py | 20 +- fast64_internal/oot/exporter/scene/rooms.py | 27 +- fast64_internal/oot/oot_object.py | 8 +- fast64_internal/oot/oot_utility.py | 31 +- .../oot/scene/exporter/to_c/__init__.py | 2 +- 22 files changed, 1008 insertions(+), 845 deletions(-) diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/classes.py index 213ebbd65..2be685993 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/classes.py @@ -45,9 +45,6 @@ class SceneFile: path: Optional[str] = None header: str = "" - hasCutscenes: bool = field(init=False) - hasSceneTextures: bool = field(init=False) - def __post_init__(self): self.hasCutscenes = len(self.sceneCutscenes) > 0 self.hasSceneTextures = len(self.sceneTextures) > 0 diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index d555302ff..ef167040d 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -16,16 +16,11 @@ @dataclass -class CollisionBase: +class CollisionUtility: """This class hosts different functions used to convert mesh data""" - sceneObj: Optional[Object] - meshObj: Optional[Object] - transform: Matrix - useMacros: bool - includeChildren: bool - - def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): + @staticmethod + def updateBounds(position: tuple[int, int, int], colBounds: list[tuple[int, int, int]]): """This is used to update the scene's boundaries""" if len(colBounds) == 0: @@ -41,7 +36,8 @@ def updateBounds(self, position: tuple[int, int, int], colBounds: list[tuple[int if position[i] > maxBounds[i]: maxBounds[i] = position[i] - def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[CollisionVertex]): + @staticmethod + def getVertexIndex(vertexPos: tuple[int, int, int], vertexList: list[CollisionVertex]): """Returns the index of a CollisionVertex based on position data, returns None if no match found""" for i in range(len(vertexList)): @@ -49,10 +45,11 @@ def getVertexIndex(self, vertexPos: tuple[int, int, int], vertexList: list[Colli return i return None - def getMeshObjects(self, dataHolder: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix]): + @staticmethod + def getMeshObjects(dataHolder: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix], includeChildren: bool): """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" - if self.includeChildren: + if includeChildren: for obj in dataHolder.children: newTransform = curTransform @ obj.matrix_local @@ -60,23 +57,25 @@ def getMeshObjects(self, dataHolder: Object, curTransform: Matrix, transformFrom transformFromMeshObj[obj] = newTransform if len(obj.children) > 0: - self.getMeshObjects(obj, newTransform, transformFromMeshObj) + CollisionUtility.getMeshObjects(obj, newTransform, transformFromMeshObj, includeChildren) return transformFromMeshObj - def getDataHolder(self): - if self.sceneObj is not None: - return self.sceneObj - elif self.meshObj is not None: - return self.meshObj + @staticmethod + def getDataHolder(sceneObj: Optional[Object], meshObj: Optional[Object]): + if sceneObj is not None: + return sceneObj + elif meshObj is not None: + return meshObj else: raise PluginError("ERROR: Object not found.") - def getCollisionData(self): + @staticmethod + def getCollisionData(sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool): """Returns collision data, surface types and vertex positions from mesh objects""" object.select_all(action="DESELECT") - dataHolder = self.getDataHolder() + dataHolder = CollisionUtility.getDataHolder(sceneObj, meshObj) dataHolder.select_set(True) colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} @@ -87,8 +86,8 @@ def getCollisionData(self): transformFromMeshObj: dict[Object, Matrix] = {} if dataHolder.type == "MESH" and not dataHolder.ignore_collision: - transformFromMeshObj[dataHolder] = self.transform - transformFromMeshObj = self.getMeshObjects(dataHolder, self.transform, transformFromMeshObj) + transformFromMeshObj[dataHolder] = transform + transformFromMeshObj = CollisionUtility.getMeshObjects(dataHolder, transform, transformFromMeshObj, includeChildren) for meshObj, transform in transformFromMeshObj.items(): # Note: ``isinstance``only used to get the proper type hints if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): @@ -104,9 +103,9 @@ def getCollisionData(self): (x1, y1, z1) = Utility.roundPosition(planePoint) (x2, y2, z2) = Utility.roundPosition(transform @ meshObj.data.vertices[face.vertices[1]].co) (x3, y3, z3) = Utility.roundPosition(transform @ meshObj.data.vertices[face.vertices[2]].co) - self.updateBounds((x1, y1, z1), colBounds) - self.updateBounds((x2, y2, z2), colBounds) - self.updateBounds((x3, y3, z3), colBounds) + CollisionUtility.updateBounds((x1, y1, z1), colBounds) + CollisionUtility.updateBounds((x2, y2, z2), colBounds) + CollisionUtility.updateBounds((x3, y3, z3), colBounds) normal = (transform.inverted().transposed() @ face.normal).normalized() distance = round( @@ -124,7 +123,7 @@ def getCollisionData(self): indices: list[int] = [] for pos in [(x1, y1, z1), (x2, y2, z2), (x3, y3, z3)]: - vertexIndex = self.getVertexIndex(pos, vertexList) + vertexIndex = CollisionUtility.getVertexIndex(pos, vertexList) if vertexIndex is None: vertexList.append(CollisionVertex(pos)) indices.append(len(vertexList) - 1) @@ -177,7 +176,7 @@ def getCollisionData(self): conveyorSpeed + (4 if shouldKeepMomentum else 0), int(colProp.conveyorRotation / (2 * math.pi) * 0x3F) if useConveyor else 0, colProp.isWallDamage, - self.useMacros, + useMacros, ) if surfaceType not in colPolyFromSurfaceType: @@ -192,7 +191,7 @@ def getCollisionData(self): useConveyor, normal, distance, - self.useMacros, + useMacros, ) ) @@ -208,34 +207,34 @@ def getCollisionData(self): @dataclass -class CollisionHeader(CollisionBase): +class CollisionHeader: """This class defines the collision header used by the scene""" name: str - sceneName: str - - minBounds: tuple[int, int, int] = field(init=False) - maxBounds: tuple[int, int, int] = field(init=False) - vertices: CollisionVertices = field(init=False) - collisionPoly: CollisionPolygons = field(init=False) - surfaceType: SurfaceTypes = field(init=False) - bgCamInfo: BgCamInformations = field(init=False) - waterbox: WaterBoxes = field(init=False) - - def __post_init__(self): + minBounds: tuple[int, int, int] + maxBounds: tuple[int, int, int] + vertices: CollisionVertices + collisionPoly: CollisionPolygons + surfaceType: SurfaceTypes + bgCamInfo: BgCamInformations + waterbox: WaterBoxes + + @staticmethod + def new(name: str, sceneName: str, sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool): # Ideally everything would be separated but this is complicated since it's all tied together - colBounds, vertexList, polyList, surfaceTypeList = self.getCollisionData() - dataHolder = self.getDataHolder() - - self.minBounds = colBounds[0] - self.maxBounds = colBounds[1] - self.vertices = CollisionVertices(f"{self.sceneName}_vertices", vertexList) - self.collisionPoly = CollisionPolygons(f"{self.sceneName}_polygons", polyList) - self.surfaceType = SurfaceTypes(f"{self.sceneName}_polygonTypes", surfaceTypeList) - self.bgCamInfo = BgCamInformations( - dataHolder, self.transform, f"{self.sceneName}_bgCamInfo", f"{self.sceneName}_camPosData" + colBounds, vertexList, polyList, surfaceTypeList = CollisionUtility.getCollisionData(sceneObj, meshObj, transform, useMacros, includeChildren) + dataHolder = CollisionUtility.getDataHolder(sceneObj, meshObj) + + return CollisionHeader( + name, + colBounds[0], + colBounds[1], + CollisionVertices(f"{sceneName}_vertices", vertexList), + CollisionPolygons(f"{sceneName}_polygons", polyList), + SurfaceTypes(f"{sceneName}_polygonTypes", surfaceTypeList), + BgCamInformations.new(f"{sceneName}_bgCamInfo", f"{sceneName}_camPosData", dataHolder, transform), + WaterBoxes.new(f"{sceneName}_waterBoxes", dataHolder, transform, useMacros), ) - self.waterbox = WaterBoxes(dataHolder, self.transform, f"{self.sceneName}_waterBoxes", self.useMacros) def getCmd(self): """Returns the collision header scene command""" diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index 28f16b4f0..afe2eef9e 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -59,7 +59,6 @@ class CameraInfo: camIndex: int arrayIndex: int = field(init=False, default=0) - hasPosData: bool = field(init=False) def __post_init__(self): self.hasPosData = self.data is not None @@ -75,38 +74,39 @@ def getInfoEntryC(self, posDataName: str): class BgCamInformations: """This class defines the array of camera informations and the array of the associated data""" - dataHolder: Object - transform: Matrix name: str posDataName: str - - bgCamInfoList: list[CameraInfo] = field(init=False, default_factory=list) - crawlspacePosList: list[CrawlspaceCamera] = field(init=False, default_factory=list) - arrayIdx: int = field(init=False, default=0) - crawlspaceCount: int = field(init=False, default=6) - camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = field(init=False, default_factory=dict) - - def initCrawlspaceList(self): + bgCamInfoList: list[CameraInfo] + crawlspacePosList: list[CrawlspaceCamera] + arrayIdx: int + crawlspaceCount: int + camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] + + @staticmethod + def getCrawlspacePosList(dataHolder: Object, transform: Matrix): """Returns a list of crawlspace data from every splines objects with the type 'Crawlspace'""" - crawlspaceObjList = getObjectList(self.dataHolder.children_recursive, "CURVE", splineType="Crawlspace") + crawlspacePosList: list[CrawlspaceCamera] = [] + crawlspaceObjList = getObjectList(dataHolder.children_recursive, "CURVE", splineType="Crawlspace") for obj in crawlspaceObjList: if Utility.validateCurveData(obj): points = [ - [round(value) for value in self.transform @ obj.matrix_world @ point.co] + [round(value) for value in transform @ obj.matrix_world @ point.co] for point in obj.data.splines[0].points ] - self.crawlspacePosList.append( + crawlspacePosList.append( CrawlspaceCamera( [points[0], points[0], points[0], points[1], points[1], points[1]], obj.ootSplineProperty.index, ) ) + return crawlspacePosList - def initBgCamInfoList(self): + @staticmethod + def getBgCamInfoList(dataHolder: Object, transform: Matrix): """Returns a list of camera informations from camera objects""" - camObjList = getObjectList(self.dataHolder.children_recursive, "CAMERA") + camObjList = getObjectList(dataHolder.children_recursive, "CAMERA") camPosData: dict[int, CameraData] = {} camInfoData: dict[int, CameraInfo] = {} @@ -124,7 +124,7 @@ def initBgCamInfoList(self): # Camera faces opposite direction pos, rot, _, _ = Utility.getConvertedTransformWithOrientation( - self.transform, self.dataHolder, camObj, Quaternion((0, 1, 0), math.radians(180.0)) + transform, dataHolder, camObj, Quaternion((0, 1, 0), math.radians(180.0)) ) fov = math.degrees(camObj.data.angle) @@ -144,38 +144,43 @@ def initBgCamInfoList(self): camPosData[camProp.index] if camProp.hasPositionData else None, camProp.index, ) - self.bgCamInfoList = list(camInfoData.values()) - - def initCamTable(self): - for bgCam in self.bgCamInfoList: - if bgCam.camIndex not in self.camFromIndex: - self.camFromIndex[bgCam.camIndex] = bgCam + return list(camInfoData.values()) + + @staticmethod + def getCamTable(dataHolder: Object, crawlspacePosList: list[CrawlspaceCamera], bgCamInfoList: list[CameraInfo]): + camFromIndex: dict[int, CameraInfo | CrawlspaceCamera] = {} + for bgCam in bgCamInfoList: + if bgCam.camIndex not in camFromIndex: + camFromIndex[bgCam.camIndex] = bgCam else: raise PluginError(f"ERROR (CameraInfo): Camera index already used: {bgCam.camIndex}") - for crawlCam in self.crawlspacePosList: - if crawlCam.camIndex not in self.camFromIndex: - self.camFromIndex[crawlCam.camIndex] = crawlCam + for crawlCam in crawlspacePosList: + if crawlCam.camIndex not in camFromIndex: + camFromIndex[crawlCam.camIndex] = crawlCam else: raise PluginError(f"ERROR (Crawlspace): Camera index already used: {crawlCam.camIndex}") - self.camFromIndex = dict(sorted(self.camFromIndex.items())) - if list(self.camFromIndex.keys()) != list(range(len(self.camFromIndex))): + camFromIndex = dict(sorted(camFromIndex.items())) + if list(camFromIndex.keys()) != list(range(len(camFromIndex))): raise PluginError("ERROR: The camera indices are not consecutive!") i = 0 - for val in self.camFromIndex.values(): + for val in camFromIndex.values(): if isinstance(val, CrawlspaceCamera): val.arrayIndex = i i += 6 # crawlspaces are using 6 entries in the data array elif val.hasPosData: val.arrayIndex = i i += 3 - - def __post_init__(self): - self.initCrawlspaceList() - self.initBgCamInfoList() - self.initCamTable() + return camFromIndex + + @staticmethod + def new(name: str, posDataName: str, dataHolder: Object, transform: Matrix): + crawlspacePosList = BgCamInformations.getCrawlspacePosList(dataHolder, transform) + bgCamInfoList = BgCamInformations.getBgCamInfoList(dataHolder, transform) + camFromIndex = BgCamInformations.getCamTable(dataHolder, crawlspacePosList, bgCamInfoList) + return BgCamInformations(name, posDataName, bgCamInfoList, crawlspacePosList, 0, 6, camFromIndex) def getDataArrayC(self): """Returns the camera data/crawlspace positions array""" diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index 3339d42cc..24cc6daf0 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -17,9 +17,8 @@ class CollisionPoly: dist: int useMacros: bool - type: Optional[int] = field(init=False, default=None) - def __post_init__(self): + self.type: Optional[int] = None for i, val in enumerate(self.normal): if val < -1.0 or val > 1.0: raise PluginError(f"ERROR: Invalid value for normal {['X', 'Y', 'Z'][i]}! (``{val}``)") diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index 999ca9b17..73fa2c95c 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -29,11 +29,6 @@ class SurfaceType: useMacros: bool - isSoftC: str = field(init=False) - isHorseBlockedC: str = field(init=False) - canHookshotC: str = field(init=False) - isWallDamageC: str = field(init=False) - def __post_init__(self): self.isSoftC = "1" if self.isSoft else "0" self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 7a9916c49..26b385199 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -11,41 +11,41 @@ class WaterBox: """This class defines waterbox data""" - position: tuple[int, int, int] - scale: float - emptyDisplaySize: float - # Properties bgCamIndex: int lightIndex: int - roomIndex: int - setFlag19: bool - - useMacros: bool + roomIndexC: str + setFlag19C: str - xMin: int = field(init=False) - ySurface: int = field(init=False) - zMin: int = field(init=False) - xLength: int = field(init=False) - zLength: int = field(init=False) + xMin: int + ySurface: int + zMin: int + xLength: int + zLength: int - setFlag19C: str = field(init=False) - roomIndexC: str = field(init=False) - - def __post_init__(self): - self.setFlag19C = "1" if self.setFlag19 else "0" - self.roomIndexC = f"0x{self.roomIndex:02X}" if self.roomIndex == 0x3F else f"{self.roomIndex}" + useMacros: bool + @staticmethod + def new(position: tuple[int, int, int], scale: float, emptyDisplaySize: float, bgCamIndex: int, lightIndex: int, roomIndex: int, setFlag19: bool, useMacros: bool): # The scale ordering is due to the fact that scaling happens AFTER rotation. # Thus the translation uses Y-up, while the scale uses Z-up. - xMax = round(self.position[0] + self.scale[0] * self.emptyDisplaySize) - zMax = round(self.position[2] + self.scale[1] * self.emptyDisplaySize) - - self.xMin = round(self.position[0] - self.scale[0] * self.emptyDisplaySize) - self.ySurface = round(self.position[1] + self.scale[2] * self.emptyDisplaySize) - self.zMin = round(self.position[2] - self.scale[1] * self.emptyDisplaySize) - self.xLength = xMax - self.xMin - self.zLength = zMax - self.zMin + xMax = round(position[0] + scale[0] * emptyDisplaySize) + zMax = round(position[2] + scale[1] * emptyDisplaySize) + xMin = round(position[0] - scale[0] * emptyDisplaySize) + zMin = round(position[2] - scale[1] * emptyDisplaySize) + + return WaterBox( + bgCamIndex, + lightIndex, + f"0x{roomIndex:02X}" if roomIndex == 0x3F else f"{roomIndex}", + "1" if setFlag19 else "0", + xMin, + round(position[1] + scale[2] * emptyDisplaySize), + zMin, + xMax - xMin, + zMax - zMin, + useMacros + ) def getProperties(self): """Returns the waterbox properties""" @@ -82,19 +82,17 @@ def getEntryC(self): class WaterBoxes: """This class defines the array of waterboxes""" - dataHolder: Object - transform: Matrix name: str - useMacros: bool - - waterboxList: list[WaterBox] = field(init=False, default_factory=list) + waterboxList: list[WaterBox] - def __post_init__(self): - waterboxObjList = getObjectList(self.dataHolder.children_recursive, "EMPTY", "Water Box") + @staticmethod + def new(name: str, dataHolder: Object, transform: Matrix, useMacros: bool): + waterboxList: list[WaterBox] = [] + waterboxObjList = getObjectList(dataHolder.children_recursive, "EMPTY", "Water Box") for waterboxObj in waterboxObjList: emptyScale = waterboxObj.empty_display_size pos, _, scale, orientedRot = Utility.getConvertedTransform( - self.transform, self.dataHolder, waterboxObj, True + transform, dataHolder, waterboxObj, True ) checkIdentityRotation(waterboxObj, orientedRot, False) @@ -102,16 +100,16 @@ def __post_init__(self): # temp solution roomObj = None - if self.dataHolder.type == "EMPTY" and self.dataHolder.ootEmptyType == "Scene": - for obj in self.dataHolder.children_recursive: + if dataHolder.type == "EMPTY" and dataHolder.ootEmptyType == "Scene": + for obj in dataHolder.children_recursive: if obj.type == "EMPTY" and obj.ootEmptyType == "Room": for o in obj.children_recursive: if o == waterboxObj: roomObj = obj break - self.waterboxList.append( - WaterBox( + waterboxList.append( + WaterBox.new( pos, scale, emptyScale, @@ -119,9 +117,10 @@ def __post_init__(self): wboxProp.lighting, roomObj.ootRoomHeader.roomIndex if roomObj is not None else 0x3F, wboxProp.flag19, - self.useMacros, + useMacros, ) ) + return WaterBoxes(name, waterboxList) def getC(self): wboxData = CData() diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 854f113fd..e22ad1cd8 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -71,13 +71,13 @@ def getNewScene(self): try: sceneName = f"{toAlnum(self.sceneName)}_scene" - newScene = Scene( + newScene = Scene.new( + sceneName, self.sceneObj, self.transform, self.useMacros, - sceneName, self.saveTexturesAsPNG, - OOTModel(f"{sceneName}_dl", self.dlFormat, False), + OOTModel(f"{sceneName}_dl", self.dlFormat, False) ) newScene.validateScene() diff --git a/fast64_internal/oot/exporter/other/file.py b/fast64_internal/oot/exporter/other/file.py index 5d85b5e7a..f8f621589 100644 --- a/fast64_internal/oot/exporter/other/file.py +++ b/fast64_internal/oot/exporter/other/file.py @@ -4,8 +4,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING from ...oot_utility import getSceneDirFromLevelName -from .scene_table import SceneTable -from .spec import Spec +from .scene_table import SceneTableUtility +from .spec import SpecUtility if TYPE_CHECKING: from ..main import SceneExport @@ -34,5 +34,5 @@ def modifySceneFiles(self): def editFiles(self): """Edits decomp files""" self.modifySceneFiles() - Spec().editSpec(self.exporter) - SceneTable().editSceneTable(self.exporter) + SpecUtility.editSpec(self.exporter) + SceneTableUtility.editSceneTable(self.exporter) diff --git a/fast64_internal/oot/exporter/other/scene_table.py b/fast64_internal/oot/exporter/other/scene_table.py index 574b3c683..a558b4c6d 100644 --- a/fast64_internal/oot/exporter/other/scene_table.py +++ b/fast64_internal/oot/exporter/other/scene_table.py @@ -1,303 +1,324 @@ import os +import enum import bpy -from typing import TYPE_CHECKING +from dataclasses import dataclass, field +from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile from ...oot_constants import ootEnumSceneID, ootSceneNameToID -from ...oot_utility import ExportInfo +from ...oot_utility import getCustomProperty, ExportInfo if TYPE_CHECKING: from ..main import SceneExport -class SceneTable: - """This class hosts different function to edit the scene table""" - - def getSceneNameSettings(self, isExport: bool): - """Returns the scene name""" - - if isExport: - return bpy.context.scene.ootSceneExportSettings.option - else: - return bpy.context.scene.ootSceneRemoveSettings.option - - def isHackerOoT(self, line: str): - """Returns ``True`` if HackerOoT-related data has been found on the current line""" +ADDED_SCENES_COMMENT = "// Added scenes" + + +class SceneIndexType(enum.IntEnum): + """Used to figure out the value of ``selectedSceneIndex``""" + + # this is using negative numbers since this is used as a return type if the scene index wasn't found + CUSTOM = -1 # custom scene + VANILLA_REMOVED = -2 # vanilla scene that was removed, this is to know if it should insert an entry + + +@dataclass +class SceneTableEntry: + """Defines an entry of ``scene_table.h``""" + + index: int + original: Optional[str] # the original line from the parsed file + exporter: Optional["SceneExport"] = None + exportName: Optional[str] = None + prefix: Optional[str] = None # ifdefs, endifs, comments etc, everything before the current entry + suffix: Optional[str] = None # remaining data after the last entry + parsed: Optional[str] = None + + # macro parameters + specName: Optional[str] = None # name of the scene segment in spec + titleCardName: Optional[str] = None # name of the title card segment in spec, or `none` for no title card + enumValue: Optional[str] = None # enum value for this scene + drawConfigIdx: Optional[str] = None # scene draw config index + unk1: Optional[str] = None + unk2: Optional[str] = None + + def __post_init__(self): + # parse the entry parameters from file data or an ``OOTScene`` + macroStart = "DEFINE_SCENE(" + if self.original is not None and macroStart in self.original: + # remove the index and the macro's name with the parenthesis + index = self.original.index(macroStart) + len(macroStart) + self.parsed = self.original[index:].removesuffix(")\n") + + parameters = self.parsed.split(", ") + assert len(parameters) == 6 + self.setParameters(*parameters) + elif self.exporter is not None: + self.setParametersFromScene() + + def setParameters( + self, specName: str, titleCardName: str, enumValue: str, drawConfigIdx: str, unk1: str = "0", unk2: str = "0" + ): + """Sets the entry's parameters""" + self.specName = specName + self.titleCardName = titleCardName + self.enumValue = enumValue + self.drawConfigIdx = drawConfigIdx + self.unk1 = unk1 + self.unk2 = unk2 + + def setParametersFromScene(self, exporter: Optional["SceneExport"] = None): + """Use the ``OOTScene`` data to set the entry's parameters""" + exporter = self.exporter if exporter is None else exporter + # TODO: Implement title cards + name = exporter.scene.name if exporter is not None else self.exportName + self.setParameters( + f"{exporter.scene.name.lower()}_scene", + "none", + ootSceneNameToID.get(name, f"SCENE_{name.upper()}"), + exporter.scene.mainHeader.infos.drawConfig, + ) + def to_c(self): + """Returns the entry as C code""" return ( - line != "\n" - and '#include "config.h"\n' not in line - and "#ifdef INCLUDE_TEST_SCENES" not in line - and "#endif" not in line - and not line.startswith("// ") + (self.prefix if self.prefix is not None else "") + + f"/* 0x{self.index:02X} */ " + + f"DEFINE_SCENE({self.specName}, {self.titleCardName}, {self.enumValue}, " + + f"{self.drawConfigIdx}, {self.unk1}, {self.unk2})\n" + + (self.suffix if self.suffix is not None else "") ) - def getSceneTable(self, exportPath: str): - """Read and remove unwanted stuff from ``scene_table.h``""" - dataList = [] - sceneNames = [] - fileHeader = "" - - # read the scene table +@dataclass +class SceneTable: + """Defines a ``scene_table.h`` file data""" + + exportPath: str + exportName: Optional[str] + selectedSceneEnumValue: Optional[str] + entries: list[SceneTableEntry] = field(default_factory=list) + sceneEnumValues: list[str] = field(default_factory=list) # existing values in ``scene_table.h`` + isFirstCustom: bool = False # if true, adds the "Added Scenes" comment to the C data + selectedSceneIndex: int = 0 + customSceneIndex: Optional[int] = None # None if the selected custom scene isn't in the table yet + + def __post_init__(self): + # read the file's data try: - with open(os.path.join(exportPath, "include/tables/scene_table.h")) as fileData: - # keep the relevant data and do some formatting - for i, line in enumerate(fileData): - # remove empty lines from the file - if not line.strip(): - continue - - if not bpy.context.scene.fast64.oot.hackerFeaturesEnabled or self.isHackerOoT(line): - if not ( - # Detects the multiline comment at the top of the file: - (line.startswith("/**") or line.startswith(" *")) - # Detects single line comments: - # (meant to detect the built-in single-line comments - # "// Debug-only scenes" and "// Added scenes") - or line.startswith("//") - ): - dataList.append(line[(line.find("(") + 1) :].rstrip(")\n").replace(" ", "").split(",")) - else: - # Only keep comments before the data (as indicated by dataList being empty). - # This prevents duplicating the built-in single-line comments to the header. - # It also means other handwritten single-line comments are removed from the file. - if not dataList: - fileHeader += line - if line.startswith("/* 0x"): - startIndex = line.find("SCENE_") - sceneNames.append(line[startIndex : line.find(",", startIndex)]) + with open(self.exportPath) as fileData: + data = fileData.read() + fileData.seek(0) + lines = fileData.readlines() except FileNotFoundError: raise PluginError("ERROR: Can't find scene_table.h!") - # return the parsed data, the header comment and the comment mentionning debug scenes - return dataList, fileHeader, sceneNames - - def getSceneIndex(self, sceneNameList: list[str], sceneName: str): - """Returns the index (int) of the chosen scene, returns None if ``Custom`` is chosen""" - - if sceneName == "Custom": - return None - - if sceneNameList is not None: - for i in range(len(sceneNameList)): - if sceneNameList[i] == sceneName: - return i - - # intended return value to check if the chosen scene was removed - return None - - def getOriginalIndex(self, sceneName): + # parse the entries and populate the list of entries (``self.entries``) + prefix = "" + self.isFirstCustom = ADDED_SCENES_COMMENT not in data + entryIndex = 0 # we don't use ``enumerate`` since not every line is an actual entry + assert len(lines) > 0 + for line in lines: + # skip the lines before an entry, create one from the file's data + # and add the skipped lines as a prefix of the current entry + if ( + not line.startswith("#") # ifdefs or endifs + and not line.startswith(" *") # multi-line comments + and "//" not in line # single line comments + and "/**" not in line # multi-line comments + and line != "\n" + and line != "" + ): + entry = SceneTableEntry(entryIndex, line, prefix=prefix) + self.entries.append(entry) + self.sceneEnumValues.append(entry.enumValue) + prefix = "" + entryIndex += 1 + else: + prefix += line + + # add whatever's after the last entry + if len(prefix) > 0 and prefix != "\n": + self.entries[-1].suffix = prefix + + # get the scene index for the scene chosen by the user + if self.selectedSceneEnumValue is not None: + self.selectedSceneIndex = self.getIndexFromEnumValue() + + # dictionary of entries from spec names + self.entryBySpecName = {entry.specName: entry for entry in self.entries} + + # set the custom scene index + if self.selectedSceneIndex == SceneIndexType.CUSTOM: + entry = self.entryBySpecName.get(f"{self.exportName}_scene") + if entry is not None: + self.customSceneIndex = entry.index + + def getIndexFromEnumValue(self): + """Returns the index (int) of the chosen scene if vanilla and found, else return an enum value from ``SceneIndexType``""" + if self.selectedSceneEnumValue == "Custom": + return SceneIndexType.CUSTOM + for i in range(len(self.sceneEnumValues)): + if self.sceneEnumValues[i] == self.selectedSceneEnumValue: + return i + # if the index is not found and it's not a custom export it means it's a vanilla scene that was removed + return SceneIndexType.VANILLA_REMOVED + + def getOriginalIndex(self): """ Returns the index of a specific scene defined by which one the user chose - or by the ``sceneName`` parameter if it's not set to ``None`` + or by the ``sceneName`` parameter if it's not set to ``None`` """ - i = 0 - - if sceneName != "Custom": + if self.selectedSceneEnumValue != "Custom": for elem in ootEnumSceneID: - if elem[0] == sceneName: + if elem[0] == self.selectedSceneEnumValue: # returns i - 1 because the first entry is the ``Custom`` option return i - 1 i += 1 - raise PluginError("ERROR: Scene Index not found!") - def getInsertionIndex(self, isExport: bool, sceneNames: list[str], sceneName: str, index: int, mode: str): + def getInsertionIndex(self, index: Optional[int] = None) -> int: """Returns the index to know where to insert data""" - # special case where the scene is "Inside the Great Deku Tree" # since it's the first scene simply return 0 - if sceneName == "SCENE_DEKU_TREE": + if self.selectedSceneEnumValue == "SCENE_DEKU_TREE": return 0 # if index is None this means this is looking for ``original_scene_index - 1`` # else, this means the table is shifted if index is None: - currentIndex = self.getOriginalIndex(sceneName) + currentIndex = self.getOriginalIndex() else: currentIndex = index - for i in range(len(sceneNames)): - if sceneNames[i] == ootEnumSceneID[currentIndex][0]: - # return an index to insert new data - if mode == "INSERT": - return i + 1 - # return an index to insert a comment - elif mode == "EXPORT": - return ( - i if not sceneName in sceneNames and sceneName != self.getSceneNameSettings(isExport) else i + 1 - ) - # same but don't check for chosen scene - elif mode == "REMOVE": - return i if not sceneName in sceneNames else i + 1 - else: - raise NotImplementedError + for i in range(len(self.sceneEnumValues)): + if self.sceneEnumValues[i] == ootEnumSceneID[currentIndex][0]: + return i + 1 # if the index hasn't been found yet, do it again but decrement the index - return self.getInsertionIndex(isExport, sceneNames, sceneName, currentIndex - 1, mode) - - def getSceneParams(self, exporter: "SceneExport", exportInfo: ExportInfo, sceneNames: list[str]): - """Returns the parameters that needs to be set in ``DEFINE_SCENE()``""" - - # in order to replace the values of ``unk10``, ``unk12`` and basically every parameters from ``DEFINE_SCENE``, - # you just have to make it return something other than None, not necessarily a string - sceneIndex = self.getSceneIndex(sceneNames, self.getSceneNameSettings(exporter is not None)) - sceneName = sceneTitle = sceneID = sceneUnk10 = sceneUnk12 = None - name = exporter.sceneName if exporter is not None else exportInfo.name - - # if the index is None then this is a custom scene - if sceneIndex is None and exporter is not None: - sceneName = name.lower() + "_scene" - sceneTitle = "none" - sceneID = ootSceneNameToID.get(name, f"SCENE_{name.upper()}") - sceneUnk10 = sceneUnk12 = 0 - - return sceneName, sceneTitle, sceneID, sceneUnk10, sceneUnk12, sceneIndex - - def sceneTableToC(self, data, header: str, sceneNames: list[str], isExport: bool): - """Converts the Scene Table to C code""" - - # start the data with the header comment explaining the format of the file - fileData = header - - # determine if this function is called by 'Remove Scene' or 'Export Scene' - mode = "EXPORT" if isExport else "REMOVE" + return self.getInsertionIndex(currentIndex - 1) + + def updateEntryIndex(self): + """Updates every entry index so they follow each other""" + for i, entry in enumerate(self.entries): + if entry.index != i: + entry.index = i + + def getIndex(self): + """Returns the selected scene index if it's a vanilla one, else returns the custom scene index""" + assert self.selectedSceneIndex != SceneIndexType.VANILLA_REMOVED + + # this function's usage makes ``customSceneIndex is None`` impossible + if self.selectedSceneIndex < 0 and self.customSceneIndex is None: + raise PluginError("ERROR: Custom Scene Index is None!") + + return self.selectedSceneIndex if self.selectedSceneIndex >= 0 else self.customSceneIndex + + def append(self, entry: SceneTableEntry): + """Appends an entry to the scene table, only used by custom scenes""" + # add the "added scenes" comment if it's not already there + if self.isFirstCustom: + entry.prefix = f"\n{ADDED_SCENES_COMMENT}\n" + self.isFirstCustom = False + + if entry not in self.entries: + if entry.index >= 0: + self.customSceneIndex = entry.index + self.entries.append(entry) + else: + raise PluginError(f"ERROR: (Append) The index is not valid! ({entry.index})") + else: + raise PluginError("ERROR: (Append) Entry already in the table!") - # get the index of the last non-debug scene - lastNonDebugSceneIdx = self.getInsertionIndex(isExport, sceneNames, "SCENE_OUTSIDE_GANONS_CASTLE", None, mode) - lastSceneIdx = self.getInsertionIndex(isExport, sceneNames, "SCENE_TESTROOM", None, mode) + def insert(self, entry: SceneTableEntry): + """Inserts an entry in the scene table, only used by non-custom scenes""" + if not entry in self.entries: + if entry.index >= 0: + if entry.index < len(self.entries): + nextEntry = self.entries[entry.index] # the next entry is at the insertion index - # add the actual lines with the same formatting - for i in range(len(data)): - # adds the "// Debug-only scenes" - # if both lastScene indexes are the same values this means there's no debug scene - if ((i - 1) == lastNonDebugSceneIdx) and (lastSceneIdx != lastNonDebugSceneIdx): - fileData += "// Debug-only scenes\n" + # move the next entry's prefix to the one we're going to insert + if len(nextEntry.prefix) > 0 and not "INCLUDE_TEST_SCENES" in nextEntry.prefix: + entry.prefix = nextEntry.prefix + nextEntry.prefix = "" - # add a comment to show when it's new scenes - if (i - 1) == lastSceneIdx: - fileData += "// Added scenes\n" + self.entries.insert(entry.index, entry) + else: + raise PluginError(f"ERROR: (Insert) The index is not valid! ({entry.index})") + else: + raise PluginError("ERROR: (Insert) Entry already in the table!") + + def remove(self, index: int): + """Removes an entry from the scene table""" + isCustom = index == SceneIndexType.CUSTOM + if index >= 0 or isCustom: + entry = self.entries[self.getIndex()] + + # move the prefix of the entry to remove to the next entry + # if there's no next entry this prefix becomes the suffix of the last entry + if len(entry.prefix) > 0: + nextIndex = index + 1 + if not isCustom and nextIndex < len(self.entries): + self.entries[nextIndex].prefix = entry.prefix + else: + previousIndex = entry.index - 1 + if entry.index == len(self.entries) - 1 and ADDED_SCENES_COMMENT in entry.prefix: + entry.prefix = entry.prefix.removesuffix(f"\n{ADDED_SCENES_COMMENT}\n") + self.entries[previousIndex].suffix = entry.prefix + + self.entries.remove(entry) + elif index == SceneIndexType.VANILLA_REMOVED: + raise PluginError("INFO: This scene was already removed.") + else: + raise PluginError("ERROR: Unexpected scene index value.") - fileData += f"/* 0x{i:02X} */ DEFINE_SCENE(" - fileData += ", ".join(str(d) for d in data[i]) + def to_c(self): + """Returns the scene table as C code""" + return "".join(entry.to_c() for entry in self.entries) - fileData += ")\n" - # return the string containing the file data to write - return fileData +class SceneTableUtility: + """This class hosts different function to edit the scene table""" - def getDrawConfig(self, sceneName: str): + @staticmethod + def getDrawConfig(sceneName: str): """Read draw config from scene table""" + sceneTable = SceneTable( + os.path.join(bpy.path.abspath(bpy.context.scene.ootDecompPath), "include/tables/scene_table.h"), None, None + ) - fileData, _, _ = self.getSceneTable(bpy.path.abspath(bpy.context.scene.ootDecompPath)) - - for sceneEntry in fileData: - if sceneEntry[0] == f"{sceneName}_scene": - return sceneEntry[3] - - raise PluginError(f"Scene name {sceneName} not found in scene table.") - - def addHackerOoTData(self, fileData: str): - """Reads the file and adds HackerOoT's modifications to the scene table file""" - - newFileData = ['#include "config.h"\n\n'] - - for line in fileData.splitlines(): - if "// Debug-only scenes" in line: - newFileData.append("\n#ifdef INCLUDE_TEST_SCENES\n") - - if "// Added scenes" in line: - newFileData.append("#endif\n\n") - - newFileData.append(f"{line}\n") - - if "// Added scenes" not in fileData: - newFileData.append("#endif\n") - - return "".join(newFileData) - - def editSceneTable(self, exporter: "SceneExport", exportInfo: ExportInfo = None): - """Edit the scene table with the new data""" + entry = sceneTable.entryBySpecName.get(f"{sceneName}_scene") + if entry is not None: + return entry.drawConfigIdx - isExport = exporter is not None - if exportInfo is None: - exportInfo = exporter.exportInfo + raise PluginError(f"ERROR: Scene name {sceneName} not found in scene table.") - exportPath = exportInfo.exportPath - # the list ``sceneNames`` needs to be synced with ``fileData`` - fileData, header, sceneNames = self.getSceneTable(exportPath) - sceneName, sceneTitle, sceneID, sceneUnk10, sceneUnk12, sceneIndex = self.getSceneParams( - exporter, exportInfo, sceneNames + @staticmethod + def editSceneTable(exporter: Optional["SceneExport"]): + """Remove, append, insert or update the scene table entry of the selected scene""" + sceneTable = SceneTable( + os.path.join(exporter.exportInfo.exportPath, "include/tables/scene_table.h"), + exporter.exportInfo.name if exporter.exportInfo.option == "Custom" else None, + exporter.exportInfo.option, ) - if isExport: - sceneDrawConfig = exporter.scene.mainHeader.infos.drawConfig - else: - sceneDrawConfig = None - - # ``DEFINE_SCENE()`` parameters - sceneParams = [sceneName, sceneTitle, sceneID, sceneDrawConfig, sceneUnk10, sceneUnk12] - - # check if it's a custom scene name - # sceneIndex can be None and ootSceneOption not "Custom", - # that means the selected scene has been removed from the table - # however if the scene variable is not None - # set it to "INSERT" because we need to insert the scene in the right place - if sceneIndex is None and self.getSceneNameSettings(isExport) == "Custom": - mode = "CUSTOM" - elif sceneIndex is None and isExport: - mode = "INSERT" - elif sceneIndex is not None: - mode = "NORMAL" + if exporter is None: + # remove mode + sceneTable.remove(sceneTable.selectedSceneIndex) + elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: + # custom mode: new custom scene + sceneTable.append(SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name)) + elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: + # insert mode + sceneTable.insert(SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name)) else: - mode = None - - if mode is not None: - # if so, check if the custom scene already exists in the data - # if it already exists set mode to NORMAL to consider it like a normal scene - if mode == "CUSTOM": - exportName = exportInfo.name.lower() - for i in range(len(fileData)): - if fileData[i][0] == exportName + "_scene": - sceneIndex = i - mode = "NORMAL" - break - else: - exportName = exportInfo.name - - # edit the current data or append new one if we are in a ``Custom`` context - if mode == "NORMAL": - for i in range(6): - if sceneParams[i] is not None and fileData[sceneIndex][i] != sceneParams[i]: - fileData[sceneIndex][i] = sceneParams[i] - elif mode == "CUSTOM": - sceneNames.append(sceneParams[2]) - fileData.append(sceneParams) - sceneIndex = len(fileData) - 1 - elif mode == "INSERT": - # if this the user chose a vanilla scene, removed it and want to export - # insert the data in the normal location - # shifted index = vanilla index - (vanilla last scene index - new last scene index) - index = self.getInsertionIndex(isExport, sceneNames, sceneID, None, mode) - sceneNames.insert(index, sceneParams[2]) - fileData.insert(index, sceneParams) - - # remove the scene data if scene is None (`Remove Scene` button) - if not isExport: - if sceneIndex is not None: - sceneNames.pop(sceneIndex) - fileData.pop(sceneIndex) - else: - raise PluginError("ERROR: Scene not found in ``scene_table.h``!") - - # get the new file data - newFileData = self.sceneTableToC(fileData, header, sceneNames, isExport) + # update mode (for both vanilla and custom scenes since they already exist in the table) + sceneTable.entries[sceneTable.getIndex()].setParametersFromScene(exporter) - # apply HackerOoT changes if needed - if bpy.context.scene.fast64.oot.hackerFeaturesEnabled: - newFileData = self.addHackerOoTData(newFileData) + # update the indices + sceneTable.updateEntryIndex() # write the file with the final data - writeFile(os.path.join(exportPath, "include/tables/scene_table.h"), newFileData) + writeFile(sceneTable.exportPath, sceneTable.to_c()) diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/other/spec.py index 58a5b725b..4ef3eef09 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/other/spec.py @@ -1,142 +1,309 @@ import os -import re +import bpy +import enum -from typing import TYPE_CHECKING -from ....utility import readFile, writeFile, indent +from dataclasses import dataclass, field +from typing import Optional, TYPE_CHECKING +from ....utility import PluginError, writeFile, indent from ...oot_utility import ExportInfo, getSceneDirFromLevelName if TYPE_CHECKING: from ..main import SceneExport -class Spec: - """This class hosts different functions to edit the spec file""" +# either "$(BUILD_DIR)", "$(BUILD)" or "build" +buildDirectory = None + - def getSceneSpecEntries(self, segmentDefinition: list[str], sceneName: str): - """Returns the existing spec entries for the selected scene""" +class CommandType(enum.Enum): + """This class defines the different spec command types""" - entries = [] - matchText = rf'\s*name\s*"{sceneName}\_' + NAME = 0 + COMPRESS = 1 + AFTER = 2 + FLAGS = 3 + ALIGN = 4 + ADDRESS = 5 + ROMALIGN = 6 + INCLUDE = 7 + INCLUDE_DATA_WITH_RODATA = 8 + NUMBER = 9 + PAD_TEXT = 10 - for entry in segmentDefinition: - if re.match(matchText + 'scene"', entry) or re.match(matchText + 'room\_\d+"', entry): - entries.append(entry) + @staticmethod + def from_string(value: str): + """Returns one of the enum values from a string""" - return entries + cmdType = CommandType._member_map_.get(value.upper()) + if cmdType is None: + raise PluginError(f"ERROR: Can't find value: ``{value}`` in the enum!") + return cmdType - def getSpecEntries(self, fileData: str): - """Returns the existing spec entries for the whole file""" - entries = [] - compressFlag = "" +@dataclass +class SpecEntryCommand: + """This class defines a single spec command""" - for match in re.finditer("beginseg(((?!endseg).)*)endseg", fileData, re.DOTALL): - segData = match.group(1) - entries.append(segData) + type: CommandType + content: str = "" + prefix: str = "" + suffix: str = "" - # avoid deleting compress flag if the user is using it - # (defined by whether it is present at least once in spec or not) - if "compress" in segData: - compressFlag = indent + "compress\n" + def to_c(self): + return self.prefix + indent + f"{self.type.name.lower()} {self.content}".strip() + self.suffix + "\n" - includes = [] - for match in re.finditer("(#include.*)", fileData): - includes.append(match.group(0)) - return entries, compressFlag, includes +@dataclass +class SpecEntry: + """Defines an entry of ``spec``""" - def editSpec(self, exporter: "SceneExport", exportInfo: ExportInfo = None): - """Adds or removes entries for the selected scene in the spec file""" + original: Optional[list[str]] = field(default_factory=list) # the original lines from the parsed file + commands: list[SpecEntryCommand] = field(default_factory=list) # list of the different spec commands + segmentName: str = "" # the name of the current segment + prefix: str = "" # data between two commands + suffix: str = "" # remaining data after the entry (used for the last entry) + contentSuffix: str = "" # remaining data after the last command in the current entry - isExport = exporter is not None - if exportInfo is None: - exportInfo = exporter.exportInfo + def __post_init__(self): + if self.original is not None: + global buildDirectory + # parse the commands from the existing data + prefix = "" + for line in self.original: + line = line.strip() + dontHaveComments = ( + not line.startswith("// ") and not line.startswith("/* ") and not line.startswith(" */") + ) - exportPath = exportInfo.exportPath - sceneName = exporter.sceneName if isExport else exportInfo.name - fileData = readFile(os.path.join(exportPath, "spec")) + if line != "\n": + if not line.startswith("#") and dontHaveComments: + split = line.split(" ") + command = split[0] + if len(split) > 2: + content = " ".join(elem for i, elem in enumerate(split) if i > 0) + elif len(split) > 1: + content = split[1] + elif command == "name": + content = self.segmentName + else: + content = "" - specEntries, compressFlag, includes = self.getSpecEntries(fileData) - sceneSpecEntries = self.getSceneSpecEntries(specEntries, sceneName) + if buildDirectory is None and (content.startswith('"build') or content.startswith('"$(BUILD')): + buildDirectory = content.split("/")[0].removeprefix('"') - if len(sceneSpecEntries) > 0: - firstIndex = specEntries.index(sceneSpecEntries[0]) + self.commands.append( + SpecEntryCommand( + CommandType.from_string(command), + content, + (prefix + ("\n" if len(prefix) > 0 else "")) if prefix != "\n" else "", + ) + ) + prefix = "" + else: + if prefix.startswith("#") and line.startswith("#"): + # add newline if there's two consecutive preprocessor directives + prefix += "\n" + prefix += (f"\n{indent}" if not dontHaveComments else "") + line + # if there's a prefix it's the remaining data after the last entry + if len(prefix) > 0: + self.contentSuffix = prefix - # remove the entries of the selected scene - for entry in sceneSpecEntries: - specEntries.remove(entry) + if len(self.segmentName) == 0 and len(self.commands[0].content) > 0: + self.segmentName = self.commands[0].content else: - firstIndex = len(specEntries) + raise PluginError("ERROR: The segment name can't be set!") - # Add the spec data for the exported scene - if isExport: - if exportInfo.customSubPath is not None: - includeDir = f"build/{exportInfo.customSubPath + sceneName}" + def to_c(self): + return ( + (self.prefix if len(self.prefix) > 0 else "\n") + + "beginseg\n" + + "".join(cmd.to_c() for cmd in self.commands) + + (f"{self.contentSuffix}\n" if len(self.contentSuffix) > 0 else "") + + "endseg" + + (self.suffix if self.suffix == "\n" else f"\n{self.suffix}\n" if len(self.suffix) > 0 else "") + ) + + +@dataclass +class SpecFile: + """This class defines the spec's file data""" + + exportPath: str # path to the spec file + entries: list[SpecEntry] = field(default_factory=list) # list of the different spec entries + + def __post_init__(self): + # read the file's data + try: + with open(self.exportPath, "r") as fileData: + lines = fileData.readlines() + except FileNotFoundError: + raise PluginError("ERROR: Can't find spec!") + + prefix = "" + parsedLines = [] + assert len(lines) > 0 + for line in lines: + # if we're inside a spec entry or if the lines between two entries do not contains these characters + # fill the ``parsedLine`` list if it's inside a segment + # when we reach the end of the current segment add a new ``SpecEntry`` to ``self.entries`` + isNotEmptyOrNewline = len(line) > 0 and line != "\n" + if ( + len(parsedLines) > 0 + or not line.startswith(" *") + and "/*\n" not in line + and not line.startswith("#") + and isNotEmptyOrNewline + ): + if "beginseg" not in line and "endseg" not in line: + # if inside a segment, between beginseg and endseg + parsedLines.append(line) + elif "endseg" in line: + # else, if the line has endseg in it (> if we reached the end of the current segment) + entry = SpecEntry(parsedLines, prefix=prefix) + self.entries.append(entry) + prefix = "" + parsedLines = [] else: - includeDir = f"build/{getSceneDirFromLevelName(sceneName)}" - - sceneName = exporter.scene.name - if exporter.isSingleFile: - specEntries.insert( - firstIndex, - ("\n" + indent + f'name "{sceneName}"\n') - + compressFlag - + (indent + "romalign 0x1000\n") - + (indent + f'include "{includeDir}/{sceneName}.o"\n') - + (indent + "number 2\n"), - ) + # else, if between 2 segments and the line is something we don't need + prefix += line + # set the last's entry's suffix to the remaining prefix + self.entries[-1].suffix = prefix.removesuffix("\n") - firstIndex += 1 + def find(self, segmentName: str): + """Returns an entry from a segment name, returns ``None`` if nothing was found""" - for room in exporter.sceneFile.roomList.values(): - specEntries.insert( - firstIndex, - ("\n" + indent + f'name "{room.name}"\n') - + compressFlag - + (indent + "romalign 0x1000\n") - + (indent + f'include "{includeDir}/{room.name}.o"\n') - + (indent + "number 3\n"), - ) + for i, entry in enumerate(self.entries): + if entry.segmentName == segmentName: + return self.entries[i] + return None + + def append(self, entry: SpecEntry): + """Appends an entry to the list""" + + # prefix/suffix shenanigans + lastEntry = self.entries[-1] + if len(lastEntry.suffix) > 0: + entry.prefix = f"{lastEntry.suffix}\n\n" + lastEntry.suffix = "" + self.entries.append(entry) + + def remove(self, segmentName: str): + """Removes an entry from a segment name""" - firstIndex += 1 + # prefix/suffix shenanigans + entry = self.find(segmentName) + if entry is not None: + if len(entry.prefix) > 0 and entry.prefix != "\n": + lastEntry = self.entries[self.entries.index(entry) - 1] + lastEntry.suffix = (lastEntry.suffix if lastEntry.suffix is not None else "") + entry.prefix[:-2] + self.entries.remove(entry) + + def to_c(self): + return "\n".join(entry.to_c() for entry in self.entries) + + +class SpecUtility: + """This class hosts different functions to edit the spec file""" + + @staticmethod + def editSpec(exporter: "SceneExport"): + global buildDirectory + + isScene = True + exportInfo = exporter.exportInfo + hasSceneTex = exporter.hasSceneTextures + hasSceneCS = exporter.hasCutscenes + roomTotal = len(exporter.scene.rooms.entries) + csTotal = 0 + + csTotal += len(exporter.scene.mainHeader.cutscene.entries) + if exporter.scene.altHeader is not None: + for cs in exporter.scene.altHeader.cutscenes: + csTotal += len(cs.cutscene.entries) + + # get the spec's data + specFile = SpecFile(os.path.join(exportInfo.exportPath, "spec")) + + # get the scene and current segment name and remove the scene + sceneName = exportInfo.name + sceneSegmentName = f"{sceneName}_scene" + specFile.remove(f'"{sceneSegmentName}"') + + # mark the other scene elements to remove (like rooms) + segmentsToRemove: list[str] = [] + for entry in specFile.entries: + if entry.segmentName.startswith(f'"{sceneName}_'): + segmentsToRemove.append(entry.segmentName) + + # remove the segments + for segmentName in segmentsToRemove: + specFile.remove(segmentName) + + if isScene: + assert buildDirectory is not None + isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile + includeDir = f"{buildDirectory}/" + if exportInfo.customSubPath is not None: + includeDir += f"{exportInfo.customSubPath + sceneName}" else: - sceneSegInclude = ( - ("\n" + indent + f'name "{sceneName}"\n') - + compressFlag - + (indent + "romalign 0x1000\n") - + (indent + f'include "{includeDir}/{sceneName}_main.o"\n') - + (indent + f'include "{includeDir}/{sceneName}_col.o"\n') - + ((indent + f'include "{includeDir}/{sceneName}_tex.o"\n') if exporter.hasSceneTextures else "") + includeDir += f"{getSceneDirFromLevelName(sceneName)}" + + sceneCmds = [ + SpecEntryCommand(CommandType.NAME, f'"{sceneSegmentName}"'), + SpecEntryCommand(CommandType.COMPRESS), + SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), + ] + + # scene + if isSingleFile: + sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}.o"')) + else: + sceneCmds.extend( + [ + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_main.o"'), + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_col.o"'), + ] ) - if exporter.hasCutscenes: - for i in range(len(exporter.sceneFile.sceneCutscenes)): - sceneSegInclude += indent + f'include "{includeDir}/{sceneName}_cs_{i}.o"\n' - - sceneSegInclude += indent + "number 2\n" - specEntries.insert(firstIndex, sceneSegInclude) - firstIndex += 1 - - for room in exporter.sceneFile.roomList.values(): - specEntries.insert( - firstIndex, - ("\n" + indent + f'name "{room.name}"\n') - + compressFlag - + (indent + "romalign 0x1000\n") - + (indent + f'include "{includeDir}/{room.name}_main.o"\n') - + (indent + f'include "{includeDir}/{room.name}_model_info.o"\n') - + (indent + f'include "{includeDir}/{room.name}_model.o"\n') - + (indent + "number 3\n"), + if hasSceneTex: + sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_tex.o"')) + + if hasSceneCS: + for i in range(csTotal): + sceneCmds.append( + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"') + ) + + sceneCmds.append(SpecEntryCommand(CommandType.NUMBER, "2")) + specFile.append(SpecEntry(None, sceneCmds)) + + # rooms + for i in range(roomTotal): + roomSegmentName = f"{sceneName}_room_{i}" + + roomCmds = [ + SpecEntryCommand(CommandType.NAME, f'"{roomSegmentName}"'), + SpecEntryCommand(CommandType.COMPRESS), + SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), + ] + + if isSingleFile: + roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}.o"')) + else: + roomCmds.extend( + [ + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_main.o"'), + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model_info.o"'), + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model.o"'), + ] ) - firstIndex += 1 + roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) + specFile.append(SpecEntry(None, roomCmds)) + specFile.entries[-1].suffix = "\n" - # Write the file data - newFileData = ( - "/*\n * ROM spec file\n */\n\n" - + ("\n".join(includes) + "\n\n" if len(includes) > 0 else "") - + "\n".join("beginseg" + entry + "endseg\n" for entry in specEntries) - ) + # finally, write the spec file + writeFile(specFile.exportPath, specFile.to_c()) - if newFileData != fileData: - writeFile(os.path.join(exportPath, "spec"), newFileData) + # reset build directory name so it can update properly on the next run + buildDirectory = None diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 07ff2fa47..d5931d344 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -10,64 +10,55 @@ @dataclass -class HeaderBase: - """Defines the base of a room header""" - - name: str = str() - props: Optional[OOTRoomHeaderProperty] = None - sceneObj: Optional[Object] = None - roomObj: Optional[Object] = None - transform: Matrix = Matrix() - headerIndex: int = 0 - - -@dataclass -class RoomInfos(HeaderBase): +class RoomInfos: """This class stores various room header informations""" ### General ### - index: int = field(init=False) - roomShape: str = field(init=False) + index: int + roomShape: str ### Behavior ### - roomBehavior: str = field(init=False) - playerIdleType: str = field(init=False) - disableWarpSongs: bool = field(init=False) - showInvisActors: bool = field(init=False) + roomBehavior: str + playerIdleType: str + disableWarpSongs: bool + showInvisActors: bool ### Skybox And Time ### - disableSky: bool = field(init=False) - disableSunMoon: bool = field(init=False) - hour: int = field(init=False) - minute: int = field(init=False) - timeSpeed: float = field(init=False) - echo: str = field(init=False) + disableSky: bool + disableSunMoon: bool + hour: int + minute: int + timeSpeed: float + echo: str ### Wind ### - setWind: bool = field(init=False) - direction: tuple[int, int, int] = field(init=False) - strength: int = field(init=False) - - def __post_init__(self): - self.index = self.props.roomIndex - self.roomShape = self.props.roomShape - self.roomBehavior = Utility.getPropValue(self.props, "roomBehaviour") - self.playerIdleType = Utility.getPropValue(self.props, "linkIdleMode") - self.disableWarpSongs = self.props.disableWarpSongs - self.showInvisActors = self.props.showInvisibleActors - self.disableSky = self.props.disableSkybox - self.disableSunMoon = self.props.disableSunMoon - self.hour = 0xFF if self.props.leaveTimeUnchanged else self.props.timeHours - self.minute = 0xFF if self.props.leaveTimeUnchanged else self.props.timeMinutes - self.timeSpeed = max(-128, min(127, round(self.props.timeSpeed * 0xA))) - self.echo = self.props.echo - self.setWind = self.props.setWind - self.direction = [d for d in self.props.windVector] if self.props.setWind else None - self.strength = self.props.windStrength if self.props.setWind else None + setWind: bool + direction: tuple[int, int, int] + strength: int + + @staticmethod + def new(props: Optional[OOTRoomHeaderProperty]): + return RoomInfos( + props.roomIndex, + props.roomShape, + Utility.getPropValue(props, "roomBehaviour"), + Utility.getPropValue(props, "linkIdleMode"), + props.disableWarpSongs, + props.showInvisibleActors, + props.disableSkybox, + props.disableSunMoon, + 0xFF if props.leaveTimeUnchanged else props.timeHours, + 0xFF if props.leaveTimeUnchanged else props.timeMinutes, + max(-128, min(127, round(props.timeSpeed * 0xA))), + props.echo, + props.setWind, + [d for d in props.windVector] if props.setWind else None, + props.windStrength if props.setWind else None, + ) def getCmds(self): """Returns the echo settings, room behavior, skybox disables and time settings room commands""" @@ -91,17 +82,21 @@ def getCmds(self): @dataclass -class RoomObjects(HeaderBase): +class RoomObjects: """This class defines an OoT object array""" - objectList: list[str] = field(init=False, default_factory=list) + name: str + objectList: list[str] - def __post_init__(self): - for objProp in self.props.objectList: + @staticmethod + def new(name: str, props: Optional[OOTRoomHeaderProperty]): + objectList: list[str] = [] + for objProp in props.objectList: if objProp.objectKey == "Custom": - self.objectList.append(objProp.objectIDCustom) + objectList.append(objProp.objectIDCustom) else: - self.objectList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) + objectList.append(ootData.objectData.objectsByKey[objProp.objectKey].id) + return RoomObjects(name, objectList) def getDefineName(self): """Returns the name of the define for the total of entries in the object list""" @@ -134,16 +129,19 @@ def getC(self): @dataclass -class RoomActors(HeaderBase): +class RoomActors: """This class defines an OoT actor array""" - actorList: list[Actor] = field(init=False, default_factory=list) + name: str + actorList: list[Actor] - def __post_init__(self): - actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Actor", parentObj=self.roomObj) + @staticmethod + def new(name: str, sceneObj: Optional[Object], roomObj: Optional[Object], transform: Matrix, headerIndex: int): + actorList: list[Actor] = [] + actorObjList = getObjectList(sceneObj.children_recursive, "EMPTY", "Actor", parentObj=roomObj) for obj in actorObjList: actorProp = obj.ootActorProperty - if not Utility.isCurrentHeaderValid(actorProp.headerSettings, self.headerIndex): + if not Utility.isCurrentHeaderValid(actorProp.headerSettings, headerIndex): continue # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for @@ -152,7 +150,7 @@ def __post_init__(self): # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if # the current Actor has the ID `None` to avoid export issues. if actorProp.actorID != "None": - pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(transform, sceneObj, obj, True) actor = Actor() if actorProp.actorID == "Custom": @@ -175,7 +173,8 @@ def __post_init__(self): actor.pos = pos actor.params = actorProp.actorParam - self.actorList.append(actor) + actorList.append(actor) + return RoomActors(name, actorList) def getDefineName(self): """Returns the name of the define for the total of entries in the actor list""" @@ -207,18 +206,21 @@ def getC(self): @dataclass -class RoomHeader(HeaderBase): +class RoomHeader: """This class defines a room header""" - infos: Optional[RoomInfos] = field(init=False, default=None) - objects: Optional[RoomObjects] = field(init=False, default=None) - actors: Optional[RoomActors] = field(init=False, default=None) - - def __post_init__(self): - self.infos = RoomInfos(None, self.props) - self.objects = RoomObjects(f"{self.name}_objectList", self.props) - self.actors = RoomActors( - f"{self.name}_actorList", None, self.sceneObj, self.roomObj, self.transform, self.headerIndex + name: str + infos: Optional[RoomInfos] + objects: Optional[RoomObjects] + actors: Optional[RoomActors] + + @staticmethod + def new(name: str, props: Optional[OOTRoomHeaderProperty], sceneObj: Optional[Object], roomObj: Optional[Object], transform: Matrix, headerIndex: int): + return RoomHeader( + name, + RoomInfos.new(props), + RoomObjects.new(f"{name}_objectList", props), + RoomActors.new(f"{name}_actorList", sceneObj, roomObj, transform, headerIndex), ) def getHeaderDefines(self): diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index d34003fb3..77bb33abc 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -20,83 +20,94 @@ class Room: """This class defines a room""" name: str - transform: Matrix - sceneObj: Object - roomObj: Object - roomShapeType: str - model: OOTModel roomIndex: int - sceneName: str - saveTexturesAsPNG: bool - - mainHeader: Optional[RoomHeader] = field(init=False, default=None) - altHeader: Optional[RoomAlternateHeader] = field(init=False, default=None) - mesh: Optional[OOTRoomMesh] = field(init=False, default=None) - roomShape: Optional[RoomShape] = field(init=False, default=None) - hasAlternateHeaders: bool = field(init=False) - - def getNewRoomHeader(self, headerProp: OOTRoomHeaderProperty, headerIndex: int = 0): - """Returns a new room header with the informations from the scene empty object""" - - return RoomHeader( - f"{self.name}_header{headerIndex:02}", - headerProp, - self.sceneObj, - self.roomObj, - self.transform, - headerIndex, - ) - - def __post_init__(self): + mainHeader: Optional[RoomHeader] + altHeader: Optional[RoomAlternateHeader] + mesh: Optional[OOTRoomMesh] + roomShape: Optional[RoomShape] + hasAlternateHeaders: bool + + @staticmethod + def new(name: str, transform: Matrix, sceneObj: Object, roomObj: Object, roomShapeType: str, model: OOTModel, roomIndex: int, sceneName: str, saveTexturesAsPNG: bool): from ...oot_level_writer import BoundingBox, ootProcessMesh # circular import fix - mainHeaderProps = self.roomObj.ootRoomHeader - altHeader = RoomAlternateHeader(f"{self.name}_alternateHeaders") - altProp = self.roomObj.ootAlternateRoomHeaders + i = 0 + mainHeaderProps = roomObj.ootRoomHeader + altHeader = RoomAlternateHeader(f"{name}_alternateHeaders") + altProp = roomObj.ootAlternateRoomHeaders if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(mainHeaderProps.bgImageList) == 0: - raise PluginError(f'Room {self.roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') + raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') - if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and self.roomIndex >= 1: + if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and roomIndex >= 1: raise PluginError(f'Room shape "Image" can only have one room in the scene.') - self.mainHeader = self.getNewRoomHeader(mainHeaderProps) - self.hasAlternateHeaders = False + mainHeader = RoomHeader.new( + f"{name}_header{i:02}", + mainHeaderProps, + sceneObj, + roomObj, + transform, + i, + ) + hasAlternateHeaders = False for i, header in enumerate(altHeaderList, 1): altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: - self.hasAlternateHeaders = True - setattr(altHeader, header, self.getNewRoomHeader(altP, i)) + hasAlternateHeaders = True + newRoomHeader = RoomHeader.new( + f"{name}_header{i:02}", + altP, + sceneObj, + roomObj, + transform, + i, + ) + setattr(altHeader, header, newRoomHeader) altHeader.cutscenes = [ - self.getNewRoomHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + RoomHeader.new( + f"{name}_header{i:02}", + csHeader, + sceneObj, + roomObj, + transform, + i, + ) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - self.hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else self.hasAlternateHeaders - self.altHeader = altHeader if self.hasAlternateHeaders else None - addMissingObjectsToAllRoomHeadersNew(self.roomObj, self) + hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else hasAlternateHeaders + altHeader = altHeader if hasAlternateHeaders else None + headers: list[RoomHeader] = [mainHeader] + if altHeader is not None: + headers.extend([altHeader.childNight, altHeader.adultDay, altHeader.adultNight]) + if len(altHeader.cutscenes) > 0: + headers.extend(altHeader.cutscenes) + addMissingObjectsToAllRoomHeadersNew(roomObj, headers) # Mesh stuff - self.mesh = OOTRoomMesh(self.name, self.roomShapeType, self.model) - pos, _, scale, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, self.roomObj, True) - cullGroup = CullGroup(pos, scale, self.roomObj.ootRoomHeader.defaultCullDistance) - DLGroup = self.mesh.addMeshGroup(cullGroup).DLGroup + mesh = OOTRoomMesh(name, roomShapeType, model) + pos, _, scale, _ = Utility.getConvertedTransform(transform, sceneObj, roomObj, True) + cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) + DLGroup = mesh.addMeshGroup(cullGroup).DLGroup boundingBox = BoundingBox() ootProcessMesh( - self.mesh, + mesh, DLGroup, - self.sceneObj, - self.roomObj, - self.transform, - not self.saveTexturesAsPNG, + sceneObj, + roomObj, + transform, + not saveTexturesAsPNG, None, boundingBox, ) cullGroup.position, cullGroup.cullDepth = boundingBox.getEnclosingSphere() - self.mesh.terminateDLs() - self.mesh.removeUnusedEntries() - self.roomShape = RoomShape(self.roomShapeType, mainHeaderProps, self.mesh, self.sceneName, self.name) + mesh.terminateDLs() + mesh.removeUnusedEntries() + roomShape = RoomShape.new(roomShapeType, mainHeaderProps, mesh, sceneName, name) + return Room(name, roomIndex, mainHeader, altHeader, mesh, roomShape, hasAlternateHeaders) def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: """Returns the current room header based on the header index""" diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 5245360aa..3905da537 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -7,15 +7,6 @@ from ...room.properties import OOTRoomHeaderProperty -@dataclass -class RoomShapeBase: - """This class defines the basic informations of a non-image room shape""" - - type: str - props: OOTRoomHeaderProperty - mesh: OOTRoomMesh - - @dataclass class RoomShapeImageBase: """This class defines the basic informations shared by other image classes""" @@ -75,20 +66,22 @@ def getEntryC(self): @dataclass -class RoomShapeImageMultiBg(RoomShapeBase): +class RoomShapeImageMultiBg: """This class defines the multiple background image array""" name: str + entries: list[RoomShapeImageMultiBgEntry] - entries: list[RoomShapeImageMultiBgEntry] = field(init=False, default_factory=list) - - def __post_init__(self): - for i, bgImg in enumerate(self.mesh.bgImages): - self.entries.append( + @staticmethod + def new(name: str, mesh: OOTRoomMesh): + entries: list[RoomShapeImageMultiBgEntry] = [] + for i, bgImg in enumerate(mesh.bgImages): + entries.append( RoomShapeImageMultiBgEntry( i, bgImg.name, bgImg.image.size[0], bgImg.image.size[1], bgImg.otherModeFlags ) ) + return RoomShapeImageMultiBg(name, entries) def getC(self): infoData = CData() @@ -109,18 +102,19 @@ class RoomShapeDLists: name: str isArray: bool - mesh: OOTRoomMesh - - entries: list[RoomShapeDListsEntry] = field(init=False, default_factory=list) + entries: list[RoomShapeDListsEntry] - def __post_init__(self): - for meshGrp in self.mesh.meshEntries: - self.entries.append( + @staticmethod + def new(name: str, isArray: bool, mesh: OOTRoomMesh): + entries: list[RoomShapeDListsEntry] = [] + for meshGrp in mesh.meshEntries: + entries.append( RoomShapeDListsEntry( meshGrp.DLGroup.opaque.name if meshGrp.DLGroup.opaque is not None else "NULL", meshGrp.DLGroup.transparent.name if meshGrp.DLGroup.transparent is not None else "NULL", ) ) + return RoomShapeDLists(name, isArray, entries) def getC(self): infoData = CData() @@ -241,49 +235,51 @@ def getC(self): @dataclass -class RoomShape(RoomShapeBase): +class RoomShape: """This class hosts every type of room shape""" - sceneName: str - roomName: str - - dl: Optional[RoomShapeDLists] = field(init=False, default=None) - normal: Optional[RoomShapeNormal] = field(init=False, default=None) - single: Optional[RoomShapeImageSingle] = field(init=False, default=None) - multiImg: Optional[RoomShapeImageMultiBg] = field(init=False, default=None) - multi: Optional[RoomShapeImageMulti] = field(init=False, default=None) - - def __post_init__(self): - name = f"{self.roomName}_shapeHeader" - dlName = f"{self.roomName}_shapeDListEntry" - - match self.type: + dl: Optional[RoomShapeDLists] + normal: Optional[RoomShapeNormal] + single: Optional[RoomShapeImageSingle] + multiImg: Optional[RoomShapeImageMultiBg] + multi: Optional[RoomShapeImageMulti] + + @staticmethod + def new(type: str, props: OOTRoomHeaderProperty, mesh: OOTRoomMesh, sceneName: str, roomName: str): + name = f"{roomName}_shapeHeader" + dlName = f"{roomName}_shapeDListEntry" + normal = None + single = None + multiImg = None + multi = None + + match type: case "ROOM_SHAPE_TYPE_NORMAL": - self.normal = RoomShapeNormal(name, self.type, dlName) + normal = RoomShapeNormal(name, type, dlName) case "ROOM_SHAPE_TYPE_IMAGE": - for bgImage in self.props.bgImageList: + for bgImage in props.bgImageList: if bgImage.image is None: raise PluginError( 'A room is has room shape "Image" but does not have an image set in one of its BG images.' ) - self.mesh.bgImages.append( + mesh.bgImages.append( OOTBGImage( - toAlnum(self.sceneName + "_bg_" + bgImage.image.name), + toAlnum(sceneName + "_bg_" + bgImage.image.name), bgImage.image, bgImage.otherModeFlags, ) ) - if len(self.mesh.bgImages) > 1: - self.multiImg = RoomShapeImageMultiBg(None, None, self.mesh, f"{self.roomName}_shapeMultiBg") - self.multi = RoomShapeImageMulti( - name, self.type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, self.multiImg.name + if len(mesh.bgImages) > 1: + multiImg = RoomShapeImageMultiBg.new(f"{roomName}_shapeMultiBg", mesh) + multi = RoomShapeImageMulti( + name, type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name ) else: - bgImg = self.mesh.bgImages[0] - self.single = RoomShapeImageSingle( + bgImg = mesh.bgImages[0] + single = RoomShapeImageSingle( name, - self.type, + type, "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE", dlName, bgImg.name, @@ -292,8 +288,8 @@ def __post_init__(self): bgImg.otherModeFlags, ) case _: - raise PluginError(f"ERROR: Room Shape not supported: {self.type}") - self.dl = RoomShapeDLists(dlName, self.normal is not None, self.mesh) + raise PluginError(f"ERROR: Room Shape not supported: {type}") + return RoomShape(RoomShapeDLists.new(dlName, normal is not None, mesh), normal, single, multiImg, multi) def getName(self): """Returns the correct room shape name based on the type used""" diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 17fb6dbb5..73f506ada 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -17,61 +17,52 @@ class Scene: """This class defines a scene""" - sceneObj: Object - transform: Matrix - useMacros: bool name: str - saveTexturesAsPNG: bool model: OOTModel - - mainHeader: Optional[SceneHeader] = field(init=False, default=None) - altHeader: Optional[SceneAlternateHeader] = field(init=False, default=None) - rooms: Optional[RoomEntries] = field(init=False, default=None) - colHeader: Optional[CollisionHeader] = field(init=False, default=None) - hasAlternateHeaders: bool = field(init=False, default=False) - - def getNewSceneHeader(self, headerProp: OOTSceneHeaderProperty, headerIndex: int = 0): - """Returns a scene header""" - - return SceneHeader( - headerProp, - f"{self.name}_header{headerIndex:02}", - self.sceneObj, - self.transform, - headerIndex, - self.useMacros, + mainHeader: Optional[SceneHeader] + altHeader: Optional[SceneAlternateHeader] + rooms: Optional[RoomEntries] + colHeader: Optional[CollisionHeader] + hasAlternateHeaders: bool + + @staticmethod + def new(name: str, sceneObj: Object, transform: Matrix, useMacros: bool, saveTexturesAsPNG: bool, model: OOTModel): + i = 0 + rooms = RoomEntries.new( + f"{name}_roomList", name.removesuffix("_scene"), model, sceneObj, transform, saveTexturesAsPNG ) - def __post_init__(self): - self.rooms = RoomEntries(f"{self.name}_roomList", self, self.sceneObj, self.transform, self.saveTexturesAsPNG) - - self.colHeader = CollisionHeader( - self.sceneObj, + colHeader = CollisionHeader.new( + f"{name}_collisionHeader", + name, + sceneObj, None, - self.transform, - self.useMacros, + transform, + useMacros, True, - f"{self.name}_collisionHeader", - self.name, ) - self.mainHeader = self.getNewSceneHeader(self.sceneObj.ootSceneHeader) - self.hasAlternateHeaders = False - altHeader = SceneAlternateHeader(f"{self.name}_alternateHeaders") - altProp = self.sceneObj.ootAlternateSceneHeaders + mainHeader = SceneHeader.new(f"{name}_header{i:02}", sceneObj.ootSceneHeader, sceneObj, transform, i, useMacros) + hasAlternateHeaders = False + altHeader = SceneAlternateHeader(f"{name}_alternateHeaders") + altProp = sceneObj.ootAlternateSceneHeaders for i, header in enumerate(altHeaderList, 1): altP: OOTSceneHeaderProperty = getattr(altProp, f"{header}Header") if not altP.usePreviousHeader: - setattr(altHeader, header, self.getNewSceneHeader(altP, i)) - self.hasAlternateHeaders = True + setattr( + altHeader, header, SceneHeader.new(f"{name}_header{i:02}", altP, sceneObj, transform, i, useMacros) + ) + hasAlternateHeaders = True altHeader.cutscenes = [ - self.getNewSceneHeader(csHeader, i) for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + SceneHeader.new(f"{name}_header{i:02}", csHeader, sceneObj, transform, i, useMacros) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) ] - self.hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else self.hasAlternateHeaders - self.altHeader = altHeader if self.hasAlternateHeaders else None + hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else hasAlternateHeaders + altHeader = altHeader if hasAlternateHeaders else None + return Scene(name, model, mainHeader, altHeader, rooms, colHeader, hasAlternateHeaders) def validateRoomIndices(self): """Checks if there are multiple rooms with the same room index""" diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 56f8e49a4..28f5d4103 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -39,34 +39,31 @@ def getEntryC(self): @dataclass class SceneTransitionActors: - props: OOTSceneHeaderProperty name: str - sceneObj: Object - transform: Matrix - headerIndex: int - - entries: list[TransitionActor] = field(init=False, default_factory=list) + entries: list[TransitionActor] - def __post_init__(self): + @staticmethod + def new(name: str, sceneObj: Object, transform: Matrix, headerIndex: int): # we need to get the corresponding room index if a transition actor # do not change rooms - roomObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Room") + roomObjList = getObjectList(sceneObj.children_recursive, "EMPTY", "Room") actorToRoom: dict[Object, Object] = {} for obj in roomObjList: for childObj in obj.children_recursive: if childObj.type == "EMPTY" and childObj.ootEmptyType == "Transition Actor": actorToRoom[childObj] = obj - actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Transition Actor") + actorObjList = getObjectList(sceneObj.children_recursive, "EMPTY", "Transition Actor") actorObjList.sort(key=lambda obj: actorToRoom[obj].ootRoomHeader.roomIndex) + entries: list[TransitionActor] = [] for obj in actorObjList: transActorProp = obj.ootTransitionActorProperty if ( - Utility.isCurrentHeaderValid(transActorProp.actor.headerSettings, self.headerIndex) + Utility.isCurrentHeaderValid(transActorProp.actor.headerSettings, headerIndex) and transActorProp.actor.actorID != "None" ): - pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(transform, sceneObj, obj, True) transActor = TransitionActor() if transActorProp.isRoomTransition: @@ -97,7 +94,8 @@ def __post_init__(self): transActor.params = transActorProp.actor.actorParam transActor.roomFrom, transActor.cameraFront = front transActor.roomTo, transActor.cameraBack = back - self.entries.append(transActor) + entries.append(transActor) + return SceneTransitionActors(name, entries) def getCmd(self): """Returns the transition actor list scene command""" @@ -136,26 +134,23 @@ def getEntryC(self): @dataclass class SceneEntranceActors: - props: OOTSceneHeaderProperty - name: str - sceneObj: Object - transform: Matrix - headerIndex: int - entries: list[EntranceActor] = field(init=False, default_factory=list) + name: str + entries: list[EntranceActor] - def __post_init__(self): + @staticmethod + def new(name: str, sceneObj: Object, transform: Matrix, headerIndex: int): """Returns the entrance actor list based on empty objects with the type 'Entrance'""" entranceActorFromIndex: dict[int, EntranceActor] = {} - actorObjList = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Entrance") + actorObjList = getObjectList(sceneObj.children_recursive, "EMPTY", "Entrance") for obj in actorObjList: entranceProp = obj.ootEntranceProperty if ( - Utility.isCurrentHeaderValid(entranceProp.actor.headerSettings, self.headerIndex) + Utility.isCurrentHeaderValid(entranceProp.actor.headerSettings, headerIndex) and entranceProp.actor.actorID != "None" ): - pos, rot, _, _ = Utility.getConvertedTransform(self.transform, self.sceneObj, obj, True) + pos, rot, _, _ = Utility.getConvertedTransform(transform, sceneObj, obj, True) entranceActor = EntranceActor() entranceActor.name = ( @@ -185,7 +180,7 @@ def __post_init__(self): if list(entranceActorFromIndex.keys()) != list(range(len(entranceActorFromIndex))): raise PluginError("ERROR: The spawn indices are not consecutive!") - self.entries = list(entranceActorFromIndex.values()) + return SceneEntranceActors(name, list(entranceActorFromIndex.values())) def getCmd(self): """Returns the spawn list scene command""" diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index 743de55d2..e92baac9d 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -85,28 +85,28 @@ def getEntryC(self, index: int): class SceneLighting: """This class hosts lighting data""" - props: OOTSceneHeaderProperty name: str + envLightMode: str + settings: list[EnvLightSettings] - envLightMode: str = field(init=False) - settings: list[EnvLightSettings] = field(init=False, default_factory=list) - - def __post_init__(self): - self.envLightMode = Utility.getPropValue(self.props, "skyboxLighting") + @staticmethod + def new(name: str, props: OOTSceneHeaderProperty): + envLightMode = Utility.getPropValue(props, "skyboxLighting") lightList: list[OOTLightProperty] = [] + settings: list[EnvLightSettings] = [] - if self.envLightMode == "LIGHT_MODE_TIME": - todLights = self.props.timeOfDayLights + if envLightMode == "LIGHT_MODE_TIME": + todLights = props.timeOfDayLights lightList = [todLights.dawn, todLights.day, todLights.dusk, todLights.night] else: - lightList = self.props.lightList + lightList = props.lightList for lightProp in lightList: light1 = ootGetBaseOrCustomLight(lightProp, 0, True, True) light2 = ootGetBaseOrCustomLight(lightProp, 1, True, True) - self.settings.append( + settings.append( EnvLightSettings( - self.envLightMode, + envLightMode, exportColor(lightProp.ambient), light1[0], light1[1], @@ -118,6 +118,7 @@ def __post_init__(self): lightProp.transitionSpeed, ) ) + return SceneLighting(name, envLightMode, settings) def getCmd(self): """Returns the env light settings scene command""" @@ -147,49 +148,49 @@ def getC(self): class SceneInfos: """This class stores various scene header informations""" - props: OOTSceneHeaderProperty - sceneObj: Object - ### General ### - keepObjectID: str = field(init=False) - naviHintType: str = field(init=False) - drawConfig: str = field(init=False) - appendNullEntrance: bool = field(init=False) - useDummyRoomList: bool = field(init=False) + keepObjectID: str + naviHintType: str + drawConfig: str + appendNullEntrance: bool + useDummyRoomList: bool ### Skybox And Sound ### # Skybox - skyboxID: str = field(init=False) - skyboxConfig: str = field(init=False) + skyboxID: str + skyboxConfig: str # Sound - sequenceID: str = field(init=False) - ambienceID: str = field(init=False) - specID: str = field(init=False) + sequenceID: str + ambienceID: str + specID: str ### Camera And World Map ### # World Map - worldMapLocation: str = field(init=False) + worldMapLocation: str # Camera - sceneCamType: str = field(init=False) - - def __post_init__(self): - self.keepObjectID = Utility.getPropValue(self.props, "globalObject") - self.naviHintType = Utility.getPropValue(self.props, "naviCup") - self.drawConfig = Utility.getPropValue(self.props.sceneTableEntry, "drawConfig") - self.appendNullEntrance = self.props.appendNullEntrance - self.useDummyRoomList = self.sceneObj.fast64.oot.scene.write_dummy_room_list - self.skyboxID = Utility.getPropValue(self.props, "skyboxID") - self.skyboxConfig = Utility.getPropValue(self.props, "skyboxCloudiness") - self.sequenceID = Utility.getPropValue(self.props, "musicSeq") - self.ambienceID = Utility.getPropValue(self.props, "nightSeq") - self.specID = Utility.getPropValue(self.props, "audioSessionPreset") - self.worldMapLocation = Utility.getPropValue(self.props, "mapLocation") - self.sceneCamType = Utility.getPropValue(self.props, "cameraMode") + sceneCamType: str + + @staticmethod + def new(props: OOTSceneHeaderProperty, sceneObj: Object): + return SceneInfos( + Utility.getPropValue(props, "globalObject"), + Utility.getPropValue(props, "naviCup"), + Utility.getPropValue(props.sceneTableEntry, "drawConfig"), + props.appendNullEntrance, + sceneObj.fast64.oot.scene.write_dummy_room_list, + Utility.getPropValue(props, "skyboxID"), + Utility.getPropValue(props, "skyboxCloudiness"), + Utility.getPropValue(props, "musicSeq"), + Utility.getPropValue(props, "nightSeq"), + Utility.getPropValue(props, "audioSessionPreset"), + Utility.getPropValue(props, "mapLocation"), + Utility.getPropValue(props, "cameraMode"), + ) def getCmds(self, lights: SceneLighting): """Returns the sound settings, misc settings, special files and skybox settings scene commands""" @@ -212,18 +213,19 @@ def getCmds(self, lights: SceneLighting): class SceneExits(Utility): """This class hosts exit data""" - props: OOTSceneHeaderProperty name: str + exitList: list[tuple[int, str]] - exitList: list[tuple[int, str]] = field(init=False, default_factory=list) - - def __post_init__(self): + @staticmethod + def new(name: str, props: OOTSceneHeaderProperty): # TODO: proper implementation of exits - for i, exitProp in enumerate(self.props.exitList): + exitList: list[tuple[int, str]] = [] + for i, exitProp in enumerate(props.exitList): if exitProp.exitIndex != "Custom": raise PluginError("ERROR: Exits are unfinished, please use 'Custom'.") - self.exitList.append((i, exitProp.exitIndexCustom)) + exitList.append((i, exitProp.exitIndexCustom)) + return SceneExits(name, exitList) def getCmd(self): """Returns the exit list scene command""" diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index fd409fb1a..c57ad70be 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -15,39 +15,31 @@ class SceneHeader: """This class defines a scene header""" - props: OOTSceneHeaderProperty name: str - sceneObj: Object - transform: Matrix - headerIndex: int - useMacros: bool - - infos: Optional[SceneInfos] = field(init=False, default=None) - lighting: Optional[SceneLighting] = field(init=False, default=None) - cutscene: Optional[SceneCutscene] = field(init=False, default=None) - exits: Optional[SceneExits] = field(init=False, default=None) - transitionActors: Optional[SceneTransitionActors] = field(init=False, default=None) - entranceActors: Optional[SceneEntranceActors] = field(init=False, default=None) - spawns: Optional[SceneSpawns] = field(init=False, default=None) - path: Optional[ScenePathways] = field(init=False, default=None) - - def __post_init__(self): - self.infos = SceneInfos(self.props, self.sceneObj) - self.lighting = SceneLighting(self.props, f"{self.name}_lightSettings") - self.cutscene = SceneCutscene(self.props, self.headerIndex, self.useMacros) - self.exits = SceneExits(self.props, f"{self.name}_exitList") - - self.transitionActors = SceneTransitionActors( - None, f"{self.name}_transitionActors", self.sceneObj, self.transform, self.headerIndex + infos: Optional[SceneInfos] + lighting: Optional[SceneLighting] + cutscene: Optional[SceneCutscene] + exits: Optional[SceneExits] + transitionActors: Optional[SceneTransitionActors] + entranceActors: Optional[SceneEntranceActors] + spawns: Optional[SceneSpawns] + path: Optional[ScenePathways] + + @staticmethod + def new(name: str, props: OOTSceneHeaderProperty, sceneObj: Object, transform: Matrix, headerIndex: int, useMacros: bool): + entranceActors = SceneEntranceActors.new(f"{name}_playerEntryList", sceneObj, transform, headerIndex) + return SceneHeader( + name, + SceneInfos.new(props, sceneObj), + SceneLighting.new(f"{name}_lightSettings", props), + SceneCutscene(props, headerIndex, useMacros), + SceneExits.new(f"{name}_exitList", props), + SceneTransitionActors.new(f"{name}_transitionActors", sceneObj, transform, headerIndex), + entranceActors, + SceneSpawns(None, f"{name}_entranceList", entranceActors.entries), + ScenePathways.new(f"{name}_pathway", sceneObj, transform, headerIndex), ) - self.entranceActors = SceneEntranceActors( - None, f"{self.name}_playerEntryList", self.sceneObj, self.transform, self.headerIndex - ) - - self.spawns = SceneSpawns(None, f"{self.name}_entranceList", self.entranceActors.entries) - self.path = ScenePathways(self.props, f"{self.name}_pathway", self.sceneObj, self.transform, self.headerIndex) - def getC(self): """Returns the ``CData`` containing the header's data""" diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index 5397c8657..a933f586c 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -40,26 +40,22 @@ def getC(self): class ScenePathways: """This class hosts pathways array data""" - props: OOTSceneHeaderProperty name: str - sceneObj: Object - transform: Matrix - headerIndex: int + pathList: list[Path] - pathList: list[Path] = field(init=False, default_factory=list) - - def __post_init__(self): + @staticmethod + def new(name: str, sceneObj: Object, transform: Matrix, headerIndex: int): pathFromIndex: dict[int, Path] = {} - pathObjList = getObjectList(self.sceneObj.children_recursive, "CURVE", splineType="Path") + pathObjList = getObjectList(sceneObj.children_recursive, "CURVE", splineType="Path") for obj in pathObjList: - relativeTransform = self.transform @ self.sceneObj.matrix_world.inverted() @ obj.matrix_world + relativeTransform = transform @ sceneObj.matrix_world.inverted() @ obj.matrix_world pathProps = obj.ootSplineProperty - isHeaderValid = Utility.isCurrentHeaderValid(pathProps.headerSettings, self.headerIndex) + isHeaderValid = Utility.isCurrentHeaderValid(pathProps.headerSettings, headerIndex) if isHeaderValid and Utility.validateCurveData(obj): if pathProps.index not in pathFromIndex: pathFromIndex[pathProps.index] = Path( - f"{self.name}List{pathProps.index:02}", + f"{name}List{pathProps.index:02}", [relativeTransform @ point.co.xyz for point in obj.data.splines[0].points], ) else: @@ -69,7 +65,7 @@ def __post_init__(self): if list(pathFromIndex.keys()) != list(range(len(pathFromIndex))): raise PluginError("ERROR: Path indices are not consecutive!") - self.pathList = list(pathFromIndex.values()) + return ScenePathways(name, list(pathFromIndex.values())) def getCmd(self): """Returns the path list scene command""" diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index 306c28af5..a91585b88 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -14,19 +14,14 @@ @dataclass class RoomEntries: name: str - scene: "Scene" - sceneObj: Object - transform: Matrix - saveTexturesAsPNG: bool + entries: list[Room] - entries: list[Room] = field(init=False, default_factory=list) - - def __post_init__(self): + @staticmethod + def new(name: str, sceneName: str, model: OOTModel, sceneObj: Object, transform: Matrix, saveTexturesAsPNG: bool): """Returns the room list from empty objects with the type 'Room'""" - sceneName = self.scene.name.removesuffix("_scene") roomDict: dict[int, Room] = {} - roomObjs = getObjectList(self.sceneObj.children_recursive, "EMPTY", "Room") + roomObjs = getObjectList(sceneObj.children_recursive, "EMPTY", "Room") if len(roomObjs) == 0: raise PluginError("ERROR: The scene has no child empties with the 'Room' empty type.") @@ -39,25 +34,25 @@ def __post_init__(self): raise PluginError(f"ERROR: Room index {roomIndex} used more than once!") roomName = f"{sceneName}_room_{roomIndex}" - roomDict[roomIndex] = Room( + roomDict[roomIndex] = Room.new( roomName, - self.transform, - self.sceneObj, + transform, + sceneObj, roomObj, roomHeader.roomShape, - self.scene.model.addSubModel( + model.addSubModel( OOTModel( f"{roomName}_dl", - self.scene.model.DLFormat, + model.DLFormat, None, ) ), roomIndex, sceneName, - self.saveTexturesAsPNG, + saveTexturesAsPNG, ) - self.entries = [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))] + return RoomEntries(name, [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))]) def getCmd(self): """Returns the room list scene command""" diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index fd0f143cd..b20b2b941 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -56,17 +56,11 @@ def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, headerIndex: in addMissingObjectToProp(roomObj, headerIndex, objKey) -def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, room): +def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, headers: list): """ Adds missing objects (required by actors) to all headers of a room, both to the roomObj empty and the exported room """ - headers = [room.mainHeader] - if room.altHeader is not None: - headers.extend([room.altHeader.childNight, room.altHeader.adultDay, room.altHeader.adultNight]) - if len(room.altHeader.cutscenes) > 0: - headers.extend(room.altHeader.cutscenes) - for i, curHeader in enumerate(headers): if curHeader is not None: addMissingObjectsToRoomHeaderNew(roomObj, curHeader, i) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 69a272236..9a0bc6d94 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -963,17 +963,24 @@ def getObjectList( ret: list[Object] = [] for obj in objList: - cond = True - - if emptyType is not None: - cond = obj.ootEmptyType == emptyType - elif splineType is not None: - cond = obj.ootSplineProperty.splineType == splineType - - if parentObj is not None: - cond = cond and obj.parent is not None and obj.parent.name == parentObj.name - - if obj.type == objType and cond: - ret.append(obj) + if obj.type == objType: + cond = True + + if emptyType is not None: + cond = obj.ootEmptyType == emptyType + elif splineType is not None: + cond = obj.ootSplineProperty.splineType == splineType + + if parentObj is not None: + if emptyType == "Actor" and obj.ootEmptyType == "Room": + for o in obj.children_recursive: + if o.type == objType and o.ootEmptyType == emptyType: + ret.append(o) + continue + else: + cond = cond and obj.parent is not None and obj.parent.name == parentObj.name + + if cond: + ret.append(obj) ret.sort(key=lambda o: o.name) return ret diff --git a/fast64_internal/oot/scene/exporter/to_c/__init__.py b/fast64_internal/oot/scene/exporter/to_c/__init__.py index 2205fe1aa..61a8fed1f 100644 --- a/fast64_internal/oot/scene/exporter/to_c/__init__.py +++ b/fast64_internal/oot/scene/exporter/to_c/__init__.py @@ -1,5 +1,5 @@ from .scene import getIncludes, getSceneC -from .scene_table_c import modifySceneTable, getDrawConfig, getSceneTable +from .scene_table_c import modifySceneTable, getDrawConfig from .spec import editSpecFile from .scene_folder import modifySceneFiles, deleteSceneFiles from .scene_bootup import setBootupScene, clearBootupScene From d506e8212ede907bb5ba70e526b5ee5c03673162 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:06:30 +0200 Subject: [PATCH 79/98] format --- .../oot/exporter/collision/__init__.py | 26 +++++++++++++++---- .../oot/exporter/collision/waterbox.py | 21 ++++++++++----- fast64_internal/oot/exporter/main.py | 2 +- fast64_internal/oot/exporter/room/header.py | 9 ++++++- fast64_internal/oot/exporter/room/main.py | 12 ++++++++- fast64_internal/oot/exporter/room/shape.py | 4 +-- fast64_internal/oot/exporter/scene/actors.py | 1 - fast64_internal/oot/exporter/scene/header.py | 4 ++- 8 files changed, 59 insertions(+), 20 deletions(-) diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index ef167040d..5e54b9cb1 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -46,7 +46,9 @@ def getVertexIndex(vertexPos: tuple[int, int, int], vertexList: list[CollisionVe return None @staticmethod - def getMeshObjects(dataHolder: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix], includeChildren: bool): + def getMeshObjects( + dataHolder: Object, curTransform: Matrix, transformFromMeshObj: dict[Object, Matrix], includeChildren: bool + ): """Returns and updates a dictionnary containing mesh objects associated with their correct transforms""" if includeChildren: @@ -71,7 +73,9 @@ def getDataHolder(sceneObj: Optional[Object], meshObj: Optional[Object]): raise PluginError("ERROR: Object not found.") @staticmethod - def getCollisionData(sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool): + def getCollisionData( + sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool + ): """Returns collision data, surface types and vertex positions from mesh objects""" object.select_all(action="DESELECT") @@ -87,7 +91,9 @@ def getCollisionData(sceneObj: Optional[Object], meshObj: Optional[Object], tran transformFromMeshObj: dict[Object, Matrix] = {} if dataHolder.type == "MESH" and not dataHolder.ignore_collision: transformFromMeshObj[dataHolder] = transform - transformFromMeshObj = CollisionUtility.getMeshObjects(dataHolder, transform, transformFromMeshObj, includeChildren) + transformFromMeshObj = CollisionUtility.getMeshObjects( + dataHolder, transform, transformFromMeshObj, includeChildren + ) for meshObj, transform in transformFromMeshObj.items(): # Note: ``isinstance``only used to get the proper type hints if not meshObj.ignore_collision and isinstance(meshObj.data, Mesh): @@ -220,9 +226,19 @@ class CollisionHeader: waterbox: WaterBoxes @staticmethod - def new(name: str, sceneName: str, sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool): + def new( + name: str, + sceneName: str, + sceneObj: Optional[Object], + meshObj: Optional[Object], + transform: Matrix, + useMacros: bool, + includeChildren: bool, + ): # Ideally everything would be separated but this is complicated since it's all tied together - colBounds, vertexList, polyList, surfaceTypeList = CollisionUtility.getCollisionData(sceneObj, meshObj, transform, useMacros, includeChildren) + colBounds, vertexList, polyList, surfaceTypeList = CollisionUtility.getCollisionData( + sceneObj, meshObj, transform, useMacros, includeChildren + ) dataHolder = CollisionUtility.getDataHolder(sceneObj, meshObj) return CollisionHeader( diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 26b385199..4de647632 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -26,7 +26,16 @@ class WaterBox: useMacros: bool @staticmethod - def new(position: tuple[int, int, int], scale: float, emptyDisplaySize: float, bgCamIndex: int, lightIndex: int, roomIndex: int, setFlag19: bool, useMacros: bool): + def new( + position: tuple[int, int, int], + scale: float, + emptyDisplaySize: float, + bgCamIndex: int, + lightIndex: int, + roomIndex: int, + setFlag19: bool, + useMacros: bool, + ): # The scale ordering is due to the fact that scaling happens AFTER rotation. # Thus the translation uses Y-up, while the scale uses Z-up. xMax = round(position[0] + scale[0] * emptyDisplaySize) @@ -35,8 +44,8 @@ def new(position: tuple[int, int, int], scale: float, emptyDisplaySize: float, b zMin = round(position[2] - scale[1] * emptyDisplaySize) return WaterBox( - bgCamIndex, - lightIndex, + bgCamIndex, + lightIndex, f"0x{roomIndex:02X}" if roomIndex == 0x3F else f"{roomIndex}", "1" if setFlag19 else "0", xMin, @@ -44,7 +53,7 @@ def new(position: tuple[int, int, int], scale: float, emptyDisplaySize: float, b zMin, xMax - xMin, zMax - zMin, - useMacros + useMacros, ) def getProperties(self): @@ -91,9 +100,7 @@ def new(name: str, dataHolder: Object, transform: Matrix, useMacros: bool): waterboxObjList = getObjectList(dataHolder.children_recursive, "EMPTY", "Water Box") for waterboxObj in waterboxObjList: emptyScale = waterboxObj.empty_display_size - pos, _, scale, orientedRot = Utility.getConvertedTransform( - transform, dataHolder, waterboxObj, True - ) + pos, _, scale, orientedRot = Utility.getConvertedTransform(transform, dataHolder, waterboxObj, True) checkIdentityRotation(waterboxObj, orientedRot, False) wboxProp = waterboxObj.ootWaterBoxProperty diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index e22ad1cd8..02a706741 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -77,7 +77,7 @@ def getNewScene(self): self.transform, self.useMacros, self.saveTexturesAsPNG, - OOTModel(f"{sceneName}_dl", self.dlFormat, False) + OOTModel(f"{sceneName}_dl", self.dlFormat, False), ) newScene.validateScene() diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index d5931d344..ec6320206 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -215,7 +215,14 @@ class RoomHeader: actors: Optional[RoomActors] @staticmethod - def new(name: str, props: Optional[OOTRoomHeaderProperty], sceneObj: Optional[Object], roomObj: Optional[Object], transform: Matrix, headerIndex: int): + def new( + name: str, + props: Optional[OOTRoomHeaderProperty], + sceneObj: Optional[Object], + roomObj: Optional[Object], + transform: Matrix, + headerIndex: int, + ): return RoomHeader( name, RoomInfos.new(props), diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index 77bb33abc..ce10dd9b8 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -28,7 +28,17 @@ class Room: hasAlternateHeaders: bool @staticmethod - def new(name: str, transform: Matrix, sceneObj: Object, roomObj: Object, roomShapeType: str, model: OOTModel, roomIndex: int, sceneName: str, saveTexturesAsPNG: bool): + def new( + name: str, + transform: Matrix, + sceneObj: Object, + roomObj: Object, + roomShapeType: str, + model: OOTModel, + roomIndex: int, + sceneName: str, + saveTexturesAsPNG: bool, + ): from ...oot_level_writer import BoundingBox, ootProcessMesh # circular import fix i = 0 diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 3905da537..6a218202f 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -272,9 +272,7 @@ def new(type: str, props: OOTRoomHeaderProperty, mesh: OOTRoomMesh, sceneName: s if len(mesh.bgImages) > 1: multiImg = RoomShapeImageMultiBg.new(f"{roomName}_shapeMultiBg", mesh) - multi = RoomShapeImageMulti( - name, type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name - ) + multi = RoomShapeImageMulti(name, type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name) else: bgImg = mesh.bgImages[0] single = RoomShapeImageSingle( diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 28f5d4103..2f30e02cb 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -134,7 +134,6 @@ def getEntryC(self): @dataclass class SceneEntranceActors: - name: str entries: list[EntranceActor] diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index c57ad70be..5ae3ba739 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -26,7 +26,9 @@ class SceneHeader: path: Optional[ScenePathways] @staticmethod - def new(name: str, props: OOTSceneHeaderProperty, sceneObj: Object, transform: Matrix, headerIndex: int, useMacros: bool): + def new( + name: str, props: OOTSceneHeaderProperty, sceneObj: Object, transform: Matrix, headerIndex: int, useMacros: bool + ): entranceActors = SceneEntranceActors.new(f"{name}_playerEntryList", sceneObj, transform, headerIndex) return SceneHeader( name, From 72a52a0f67bd2df055e2840cd4b3948dfff27326 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:08:11 +0200 Subject: [PATCH 80/98] fixed cs_light_setting missing 3 parameters --- fast64_internal/oot/exporter/cutscene/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py index 3db77f9f9..f04bdf8dc 100644 --- a/fast64_internal/oot/exporter/cutscene/misc.py +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -45,7 +45,7 @@ def __post_init__(self): def getCmd(self): self.validateFrames(False) - return indent * 3 + (f"CS_LIGHT_SETTING({self.lightSetting}, {self.startFrame}" + ", 0" * 9 + "),\n") + return indent * 3 + (f"CS_LIGHT_SETTING({self.lightSetting}, {self.startFrame}" + ", 0" * 12 + "),\n") @dataclass From cdf031b2dfcd37752e31ac1724008d78d513aedb Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:59:46 +0200 Subject: [PATCH 81/98] review changes part 2 --- .../oot/exporter/cutscene/__init__.py | 59 ++++++++------- fast64_internal/oot/exporter/cutscene/data.py | 72 ++++++++++--------- fast64_internal/oot/exporter/scene/actors.py | 1 - fast64_internal/oot/exporter/scene/header.py | 4 +- 4 files changed, 69 insertions(+), 67 deletions(-) diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index c54e228e9..0b1731352 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -21,23 +21,25 @@ class Cutscene: """This class defines a cutscene, including its data and its informations""" + name: str csObj: Object + data: CutsceneData + totalEntries: int + frameCount: int useMacros: bool - name: str = str() - motionOnly: bool = False + motionOnly: bool - data: Optional[CutsceneData] = field(init=False, default=None) - totalEntries: int = field(init=False) - frameCount: int = field(init=False) - paramNumber: int = field(init=False, default=2) + @staticmethod + def new(name: Optional[str], csObj: Optional[Object], useMacros: bool, motionOnly: bool): + # when csObj is None it means we're in import context + if csObj is not None: + if name is None: + name = csObj.name.removeprefix("Cutscene.").replace(".", "_") + data = CutsceneData.new(csObj, useMacros, motionOnly) + return Cutscene(name, csObj, data, data.totalEntries, data.frameCount, useMacros, motionOnly) def __post_init__(self): - # when csObj is None it means we're in import context - if self.csObj is not None and self.data is None: - self.name = self.csObj.name.removeprefix("Cutscene.").replace(".", "_") - self.data = CutsceneData(self.csObj, self.useMacros, self.motionOnly) - self.totalEntries = self.data.totalEntries - self.frameCount = self.data.frameCount + self.paramNumber = 2 def getC(self): """Returns the cutscene data""" @@ -100,23 +102,19 @@ def getC(self): class SceneCutscene(Utility): """This class hosts cutscene data""" - props: OOTSceneHeaderProperty - headerIndex: int - useMacros: bool + entries: list[Cutscene] - entries: list[Cutscene] = field(init=False, default_factory=list) - csObj: Object = field(init=False) - cutsceneObjects: list[Object] = field(init=False, default_factory=list) - - def __post_init__(self): - self.csObj: Object = self.props.csWriteObject - self.cutsceneObjects = [csObj for csObj in self.props.extraCutscenes] + @staticmethod + def new(props: OOTSceneHeaderProperty, headerIndex: int, useMacros: bool): + csObj: Object = props.csWriteObject + cutsceneObjects: list[Object] = [csObj for csObj in props.extraCutscenes] + entries: list[Cutscene] = [] - if self.headerIndex > 0 and len(self.cutsceneObjects) > 0: + if headerIndex > 0 and len(cutsceneObjects) > 0: raise PluginError("ERROR: Extra cutscenes can only belong to the main header!") - self.cutsceneObjects.insert(0, self.csObj) - for csObj in self.cutsceneObjects: + cutsceneObjects.insert(0, csObj) + for csObj in cutsceneObjects: if csObj is not None: if csObj.ootEmptyType != "Cutscene": raise PluginError( @@ -125,16 +123,17 @@ def __post_init__(self): elif csObj.parent is not None: raise PluginError("ERROR: Cutscene empty object should not be parented to anything") - writeType = self.props.csWriteType + writeType = props.csWriteType csWriteCustom = None if writeType == "Custom": - csWriteCustom = getCustomProperty(self.props, "csWriteCustom") + csWriteCustom = getCustomProperty(props, "csWriteCustom") - if self.props.writeCutscene: + if props.writeCutscene: # if csWriteCustom is None then the name will auto-set from the csObj passed in the class - self.entries.append( - Cutscene(csObj, self.useMacros, csWriteCustom, bpy.context.scene.exportMotionOnly) + entries.append( + Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.exportMotionOnly) ) + return SceneCutscene(entries) def getCmd(self): """Returns the cutscene data scene command""" diff --git a/fast64_internal/oot/exporter/cutscene/data.py b/fast64_internal/oot/exporter/cutscene/data.py index cbf210176..1c3573eb8 100644 --- a/fast64_internal/oot/exporter/cutscene/data.py +++ b/fast64_internal/oot/exporter/cutscene/data.py @@ -65,50 +65,54 @@ class CutsceneData: """This class defines the command data inside a cutscene""" csObj: Object + csObjects: dict[str, list[Object]] + csProp: "OOTCutsceneProperty" useMacros: bool motionOnly: bool - csObjects: dict[str, list[Object]] = field(init=False, default_factory=dict) - csProp: "OOTCutsceneProperty" = field(init=False) - totalEntries: int = field(init=False, default=0) - frameCount: int = field(init=False, default=0) - motionFrameCount: int = field(init=False, default=0) - camEndFrame: int = field(init=False, default=0) - - destination: Optional[CutsceneCmdDestination] = field(init=False, default=None) - actorCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) - playerCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) - camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(init=False, default_factory=list) - camATSplineList: list[CutsceneCmdCamATSpline] = field(init=False, default_factory=list) - camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(init=False, default_factory=list) - camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(init=False, default_factory=list) - camEyeList: list[CutsceneCmdCamEye] = field(init=False, default_factory=list) - camATList: list[CutsceneCmdCamAT] = field(init=False, default_factory=list) - textList: list[CutsceneCmdTextList] = field(init=False, default_factory=list) - miscList: list[CutsceneCmdMiscList] = field(init=False, default_factory=list) - rumbleList: list[CutsceneCmdRumbleControllerList] = field(init=False, default_factory=list) - transitionList: list[CutsceneCmdTransition] = field(init=False, default_factory=list) - lightSettingsList: list[CutsceneCmdLightSettingList] = field(init=False, default_factory=list) - timeList: list[CutsceneCmdTimeList] = field(init=False, default_factory=list) - seqList: list[CutsceneCmdStartStopSeqList] = field(init=False, default_factory=list) - fadeSeqList: list[CutsceneCmdFadeSeqList] = field(init=False, default_factory=list) - - def __post_init__(self): - self.csProp: "OOTCutsceneProperty" = self.csObj.ootCutsceneProperty - self.csObjects = { + @staticmethod + def new(csObj: Object, useMacros: bool, motionOnly: bool): + csProp: "OOTCutsceneProperty" = csObj.ootCutsceneProperty + csObjects = { "CS Actor Cue List": [], "CS Player Cue List": [], "camShot": [], } - for obj in self.csObj.children_recursive: - if obj.type == "EMPTY" and obj.ootEmptyType in self.csObjects.keys(): - self.csObjects[obj.ootEmptyType].append(obj) + for obj in csObj.children_recursive: + if obj.type == "EMPTY" and obj.ootEmptyType in csObjects.keys(): + csObjects[obj.ootEmptyType].append(obj) elif obj.type == "ARMATURE": - self.csObjects["camShot"].append(obj) + csObjects["camShot"].append(obj) + csObjects["camShot"].sort(key=lambda obj: obj.name) - self.csObjects["camShot"].sort(key=lambda obj: obj.name) - self.setCutsceneData() + newCutsceneData = CutsceneData(csObj, csObjects, csProp, useMacros, motionOnly) + newCutsceneData.setCutsceneData() + return newCutsceneData + + def __post_init__(self): + self.totalEntries = 0 + self.frameCount = 0 + self.motionFrameCount = 0 + self.camEndFrame = 0 + + self.destination: Optional[CutsceneCmdDestination] = None + self.actorCueList: list[CutsceneCmdActorCueList] = [] + self.playerCueList: list[CutsceneCmdActorCueList] = [] + self.camEyeSplineList: list[CutsceneCmdCamEyeSpline] = [] + self.camATSplineList: list[CutsceneCmdCamATSpline] = [] + self.camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = [] + self.camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = [] + self.camEyeList: list[CutsceneCmdCamEye] = [] + self.camATList: list[CutsceneCmdCamAT] = [] + self.textList: list[CutsceneCmdTextList] = [] + self.miscList: list[CutsceneCmdMiscList] = [] + self.rumbleList: list[CutsceneCmdRumbleControllerList] = [] + self.transitionList: list[CutsceneCmdTransition] = [] + self.lightSettingsList: list[CutsceneCmdLightSettingList] = [] + self.timeList: list[CutsceneCmdTimeList] = [] + self.seqList: list[CutsceneCmdStartStopSeqList] = [] + self.fadeSeqList: list[CutsceneCmdFadeSeqList] = [] def getOoTRotation(self, obj: Object): """Returns the converted Blender rotation""" diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 2f30e02cb..271a4335c 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -208,7 +208,6 @@ def getC(self): class SceneSpawns(Utility): """This class handles scene actors (transition actors and entrance actors)""" - props: OOTSceneHeaderProperty name: str entries: list[EntranceActor] diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index 5ae3ba739..fd6069e4d 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -34,11 +34,11 @@ def new( name, SceneInfos.new(props, sceneObj), SceneLighting.new(f"{name}_lightSettings", props), - SceneCutscene(props, headerIndex, useMacros), + SceneCutscene.new(props, headerIndex, useMacros), SceneExits.new(f"{name}_exitList", props), SceneTransitionActors.new(f"{name}_transitionActors", sceneObj, transform, headerIndex), entranceActors, - SceneSpawns(None, f"{name}_entranceList", entranceActors.entries), + SceneSpawns(f"{name}_entranceList", entranceActors.entries), ScenePathways.new(f"{name}_pathway", sceneObj, transform, headerIndex), ) From 47aa1b9f02d0748e50a29703ac1d4d2dbe475c6c Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:59:54 +0200 Subject: [PATCH 82/98] format --- fast64_internal/oot/exporter/cutscene/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index 0b1731352..aefdfad45 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -130,9 +130,7 @@ def new(props: OOTSceneHeaderProperty, headerIndex: int, useMacros: bool): if props.writeCutscene: # if csWriteCustom is None then the name will auto-set from the csObj passed in the class - entries.append( - Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.exportMotionOnly) - ) + entries.append(Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.exportMotionOnly)) return SceneCutscene(entries) def getCmd(self): From d16bc9fddfeef143a9c3ce2c5824129631687a59 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:13:57 +0200 Subject: [PATCH 83/98] review changes part 3 --- fast64_internal/oot/__init__.py | 13 ++ .../oot/collision/exporter/to_c/collision.py | 11 +- .../oot/cutscene/exporter/functions.py | 2 +- fast64_internal/oot/cutscene/operators.py | 8 +- fast64_internal/oot/exporter/actor.py | 30 ++++ .../oot/exporter/collision/__init__.py | 22 +-- .../oot/exporter/collision/camera.py | 2 +- .../oot/exporter/collision/polygons.py | 3 +- .../oot/exporter/collision/surface.py | 28 ++-- .../oot/exporter/collision/waterbox.py | 5 +- .../oot/exporter/cutscene/__init__.py | 13 +- .../oot/exporter/cutscene/actor_cue.py | 73 +++++----- .../oot/exporter/cutscene/camera.py | 96 ++++++------- .../oot/exporter/cutscene/common.py | 15 +- fast64_internal/oot/exporter/cutscene/data.py | 125 +++++++--------- fast64_internal/oot/exporter/cutscene/misc.py | 133 ++++++++++-------- fast64_internal/oot/exporter/cutscene/seq.py | 40 +++--- fast64_internal/oot/exporter/cutscene/text.py | 64 +++++---- .../oot/exporter/decomp_edit/__init__.py | 38 +++++ .../{other => decomp_edit}/scene_table.py | 13 +- .../exporter/{other => decomp_edit}/spec.py | 40 +++--- .../oot/exporter/{classes.py => file.py} | 53 +++---- fast64_internal/oot/exporter/main.py | 7 +- .../oot/exporter/other/__init__.py | 1 - fast64_internal/oot/exporter/other/file.py | 38 ----- fast64_internal/oot/exporter/room/header.py | 4 +- fast64_internal/oot/exporter/room/main.py | 6 +- .../oot/exporter/scene/__init__.py | 7 +- fast64_internal/oot/exporter/scene/actors.py | 4 +- fast64_internal/oot/exporter/scene/general.py | 5 +- fast64_internal/oot/exporter/scene/header.py | 1 - .../oot/exporter/scene/pathways.py | 3 +- .../oot/exporter/{base.py => utility.py} | 29 ---- fast64_internal/oot/file_settings.py | 19 +-- .../oot/scene/exporter/to_c/scene_cutscene.py | 2 +- fast64_internal/oot/scene/operators.py | 2 +- 36 files changed, 473 insertions(+), 482 deletions(-) create mode 100644 fast64_internal/oot/exporter/actor.py create mode 100644 fast64_internal/oot/exporter/decomp_edit/__init__.py rename fast64_internal/oot/exporter/{other => decomp_edit}/scene_table.py (96%) rename fast64_internal/oot/exporter/{other => decomp_edit}/spec.py (89%) rename fast64_internal/oot/exporter/{classes.py => file.py} (76%) delete mode 100644 fast64_internal/oot/exporter/other/__init__.py delete mode 100644 fast64_internal/oot/exporter/other/file.py rename fast64_internal/oot/exporter/{base.py => utility.py} (78%) diff --git a/fast64_internal/oot/__init__.py b/fast64_internal/oot/__init__.py index 22a324464..1e9ed07ae 100644 --- a/fast64_internal/oot/__init__.py +++ b/fast64_internal/oot/__init__.py @@ -76,6 +76,19 @@ class OOT_Properties(bpy.types.PropertyGroup): animImportSettings: bpy.props.PointerProperty(type=OOTAnimImportSettingsProperty) collisionExportSettings: bpy.props.PointerProperty(type=OOTCollisionExportSettings) + useDecompFeatures: bpy.props.BoolProperty( + name="Use decomp for export", description="Use names and macros from decomp when exporting", default=True + ) + + exportMotionOnly: bpy.props.BoolProperty( + name="Export CS Motion Data Only", + description=( + "Export everything or only the camera and actor motion data.\n" + + "This will insert the data into the cutscene." + ), + default=False, + ) + oot_classes = (OOT_Properties,) diff --git a/fast64_internal/oot/collision/exporter/to_c/collision.py b/fast64_internal/oot/collision/exporter/to_c/collision.py index 12dc97574..554061602 100644 --- a/fast64_internal/oot/collision/exporter/to_c/collision.py +++ b/fast64_internal/oot/collision/exporter/to_c/collision.py @@ -297,14 +297,13 @@ def exportCollisionToC( else: colData.source += "\n" colData.append( - CollisionHeader( - None, - obj, - transformMatrix, - bpy.context.scene.useDecompFeatures, - exportSettings.includeChildren, + CollisionHeader.new( f"{name}_collisionHeader", name, + obj, + transformMatrix, + bpy.context.scene.fast64.oot.useDecompFeatures, + exportSettings.includeChildren ).getC() ) diff --git a/fast64_internal/oot/cutscene/exporter/functions.py b/fast64_internal/oot/cutscene/exporter/functions.py index 4512f1512..9d901b704 100644 --- a/fast64_internal/oot/cutscene/exporter/functions.py +++ b/fast64_internal/oot/cutscene/exporter/functions.py @@ -44,6 +44,6 @@ def getNewCutsceneExport(csName: str, motionOnly: bool): # this allows us to change the exporter's variables to get what we need return CutsceneExport( getCutsceneObjects(csName), - bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.useDecompFeatures, + bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.fast64.oot.useDecompFeatures, motionOnly, ) diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index b207f9632..29f727dd4 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -180,11 +180,11 @@ def execute(self, context): cpath, hpath, headerfilename = checkGetFilePaths(context) csdata = ootCutsceneIncludes(headerfilename) - if context.scene.exportMotionOnly: + if context.scene.fast64.oot.exportMotionOnly: # TODO: improve this csdata.append(insertCutsceneData(cpath, activeObj.name.removeprefix("Cutscene."))) else: - csdata.append(Cutscene(activeObj, context.scene.useDecompFeatures).getC()) + csdata.append(Cutscene(activeObj, context.scene.fast64.oot.useDecompFeatures).getC()) writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported cutscene") @@ -214,10 +214,10 @@ def execute(self, context): print(f"Parent: {obj.parent.name}, Object: {obj.name}") raise PluginError("Cutscene object must not be parented to anything") - if context.scene.exportMotionOnly: + if context.scene.fast64.oot.exportMotionOnly: raise PluginError("ERROR: Not implemented yet.") else: - csdata.append(Cutscene(obj, context.scene.useDecompFeatures).getC()) + csdata.append(Cutscene(obj, context.scene.fast64.oot.useDecompFeatures).getC()) count += 1 if count == 0: diff --git a/fast64_internal/oot/exporter/actor.py b/fast64_internal/oot/exporter/actor.py new file mode 100644 index 000000000..ccf07eaf1 --- /dev/null +++ b/fast64_internal/oot/exporter/actor.py @@ -0,0 +1,30 @@ +from ...utility import indent + +# this file is not inside the room folder since the scene data can have actors too + +class Actor: + """Defines an Actor""" + + def __init__(self): + self.name = str() + self.id = str() + self.pos: list[int] = [] + self.rot = str() + self.params = str() + + def getActorEntry(self): + """Returns a single actor entry""" + + posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" + rotData = "{ " + self.rot + " }" + + actorInfos = [self.id, posData, rotData, self.params] + infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] + + return ( + indent + + (f"// {self.name}\n" + indent if self.name != "" else "") + + "{\n" + + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) + + ("\n" + indent + "},\n") + ) diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 5e54b9cb1..11060bd59 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -1,13 +1,13 @@ import math -from dataclasses import dataclass, field +from dataclasses import dataclass from mathutils import Matrix, Vector from bpy.types import Mesh, Object from bpy.ops import object from typing import Optional from ....utility import PluginError, CData, indent from ...oot_utility import convertIntTo2sComplement -from ..base import Utility +from ..utility import Utility from .polygons import CollisionPoly, CollisionPolygons from .surface import SurfaceType, SurfaceTypes from .camera import BgCamInformations @@ -63,23 +63,13 @@ def getMeshObjects( return transformFromMeshObj - @staticmethod - def getDataHolder(sceneObj: Optional[Object], meshObj: Optional[Object]): - if sceneObj is not None: - return sceneObj - elif meshObj is not None: - return meshObj - else: - raise PluginError("ERROR: Object not found.") - @staticmethod def getCollisionData( - sceneObj: Optional[Object], meshObj: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool + dataHolder: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool ): """Returns collision data, surface types and vertex positions from mesh objects""" object.select_all(action="DESELECT") - dataHolder = CollisionUtility.getDataHolder(sceneObj, meshObj) dataHolder.select_set(True) colPolyFromSurfaceType: dict[SurfaceType, list[CollisionPoly]] = {} @@ -229,17 +219,15 @@ class CollisionHeader: def new( name: str, sceneName: str, - sceneObj: Optional[Object], - meshObj: Optional[Object], + dataHolder: Object, transform: Matrix, useMacros: bool, includeChildren: bool, ): # Ideally everything would be separated but this is complicated since it's all tied together colBounds, vertexList, polyList, surfaceTypeList = CollisionUtility.getCollisionData( - sceneObj, meshObj, transform, useMacros, includeChildren + dataHolder, transform, useMacros, includeChildren ) - dataHolder = CollisionUtility.getDataHolder(sceneObj, meshObj) return CollisionHeader( name, diff --git a/fast64_internal/oot/exporter/collision/camera.py b/fast64_internal/oot/exporter/collision/camera.py index afe2eef9e..012defcee 100644 --- a/fast64_internal/oot/exporter/collision/camera.py +++ b/fast64_internal/oot/exporter/collision/camera.py @@ -7,7 +7,7 @@ from ...oot_utility import getObjectList from ...collision.constants import decomp_compat_map_CameraSType from ...collision.properties import OOTCameraPositionProperty -from ..base import Utility +from ..utility import Utility @dataclass diff --git a/fast64_internal/oot/exporter/collision/polygons.py b/fast64_internal/oot/exporter/collision/polygons.py index 24cc6daf0..3339d42cc 100644 --- a/fast64_internal/oot/exporter/collision/polygons.py +++ b/fast64_internal/oot/exporter/collision/polygons.py @@ -17,8 +17,9 @@ class CollisionPoly: dist: int useMacros: bool + type: Optional[int] = field(init=False, default=None) + def __post_init__(self): - self.type: Optional[int] = None for i, val in enumerate(self.normal): if val < -1.0 or val > 1.0: raise PluginError(f"ERROR: Invalid value for normal {['X', 'Y', 'Z'][i]}! (``{val}``)") diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index 73fa2c95c..ae1c2bfc8 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -29,11 +29,17 @@ class SurfaceType: useMacros: bool - def __post_init__(self): - self.isSoftC = "1" if self.isSoft else "0" - self.isHorseBlockedC = "1" if self.isHorseBlocked else "0" - self.canHookshotC = "1" if self.canHookshot else "0" - self.isWallDamageC = "1" if self.isWallDamage else "0" + def getIsSoftC(self): + return "1" if self.isSoft else "0" + + def getIsHorseBlockedC(self): + return "1" if self.isHorseBlocked else "0" + + def getCanHookshotC(self): + return "1" if self.canHookshot else "0" + + def getIsWallDamageC(self): + return "1" if self.isWallDamage else "0" def getSurfaceType0(self): """Returns surface type properties for the first element of the data array""" @@ -42,7 +48,7 @@ def getSurfaceType0(self): return ( ("SURFACETYPE0(") + f"{self.bgCamIndex}, {self.exitIndex}, {self.floorType}, {self.unk18}, " - + f"{self.wallType}, {self.floorProperty}, {self.isSoftC}, {self.isHorseBlockedC}" + + f"{self.wallType}, {self.floorProperty}, {self.getIsSoftC()}, {self.getIsHorseBlockedC()}" + ")" ) else: @@ -51,8 +57,8 @@ def getSurfaceType0(self): + " | ".join( prop for prop in [ - f"(({self.isHorseBlockedC} & 1) << 31)", - f"(({self.isSoftC} & 1) << 30)", + f"(({self.getIsHorseBlockedC()} & 1) << 31)", + f"(({self.getIsSoftC()} & 1) << 30)", f"(({self.floorProperty} & 0x0F) << 26)", f"(({self.wallType} & 0x1F) << 21)", f"(({self.unk18} & 0x07) << 18)", @@ -71,7 +77,7 @@ def getSurfaceType1(self): return ( ("SURFACETYPE1(") + f"{self.material}, {self.floorEffect}, {self.lightSetting}, {self.echo}, " - + f"{self.canHookshotC}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.isWallDamageC}" + + f"{self.getCanHookshotC()}, {self.conveyorSpeed}, {self.conveyorDirection}, {self.getIsWallDamageC()}" + ")" ) else: @@ -80,10 +86,10 @@ def getSurfaceType1(self): + " | ".join( prop for prop in [ - f"(({self.isWallDamageC} & 1) << 27)", + f"(({self.getIsWallDamageC()} & 1) << 27)", f"(({self.conveyorDirection} & 0x3F) << 21)", f"(({self.conveyorSpeed} & 0x07) << 18)", - f"(({self.canHookshotC} & 1) << 17)", + f"(({self.getCanHookshotC()} & 1) << 17)", f"(({self.echo} & 0x3F) << 11)", f"(({self.lightSetting} & 0x1F) << 6)", f"(({self.floorEffect} & 0x03) << 4)", diff --git a/fast64_internal/oot/exporter/collision/waterbox.py b/fast64_internal/oot/exporter/collision/waterbox.py index 4de647632..15d158239 100644 --- a/fast64_internal/oot/exporter/collision/waterbox.py +++ b/fast64_internal/oot/exporter/collision/waterbox.py @@ -1,10 +1,9 @@ -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object from ...oot_utility import getObjectList from ....utility import CData, checkIdentityRotation, indent -from ..base import Utility +from ..utility import Utility @dataclass diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index aefdfad45..54c60488e 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -6,7 +6,6 @@ from ....utility import PluginError, CData, indent from ...oot_utility import getCustomProperty from ...scene.properties import OOTSceneHeaderProperty -from ..base import Utility from .data import CutsceneData @@ -22,13 +21,14 @@ class Cutscene: """This class defines a cutscene, including its data and its informations""" name: str - csObj: Object data: CutsceneData totalEntries: int frameCount: int useMacros: bool motionOnly: bool + paramNumber: int = field(init=False, default=2) + @staticmethod def new(name: Optional[str], csObj: Optional[Object], useMacros: bool, motionOnly: bool): # when csObj is None it means we're in import context @@ -36,10 +36,7 @@ def new(name: Optional[str], csObj: Optional[Object], useMacros: bool, motionOnl if name is None: name = csObj.name.removeprefix("Cutscene.").replace(".", "_") data = CutsceneData.new(csObj, useMacros, motionOnly) - return Cutscene(name, csObj, data, data.totalEntries, data.frameCount, useMacros, motionOnly) - - def __post_init__(self): - self.paramNumber = 2 + return Cutscene(name, data, data.totalEntries, data.frameCount, useMacros, motionOnly) def getC(self): """Returns the cutscene data""" @@ -99,7 +96,7 @@ def getC(self): @dataclass -class SceneCutscene(Utility): +class SceneCutscene: """This class hosts cutscene data""" entries: list[Cutscene] @@ -130,7 +127,7 @@ def new(props: OOTSceneHeaderProperty, headerIndex: int, useMacros: bool): if props.writeCutscene: # if csWriteCustom is None then the name will auto-set from the csObj passed in the class - entries.append(Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.exportMotionOnly)) + entries.append(Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.fast64.oot.exportMotionOnly)) return SceneCutscene(entries) def getCmd(self): diff --git a/fast64_internal/oot/exporter/cutscene/actor_cue.py b/fast64_internal/oot/exporter/cutscene/actor_cue.py index 4b5df5230..c81ffd32e 100644 --- a/fast64_internal/oot/exporter/cutscene/actor_cue.py +++ b/fast64_internal/oot/exporter/cutscene/actor_cue.py @@ -10,33 +10,37 @@ class CutsceneCmdActorCue(CutsceneCmdBase): """This class contains a single Actor Cue command data""" - actionID: Optional[int] = None - rot: list[str] = field(default_factory=list) - startPos: list[int] = field(default_factory=list) - endPos: list[int] = field(default_factory=list) - isPlayer: bool = False + actionID: int + rot: list[str] + startPos: list[int] + endPos: list[int] + isPlayer: bool paramNumber: int = field(init=False, default=15) - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.actionID = getInteger(self.params[0]) - self.rot = [getRotation(self.params[3]), getRotation(self.params[4]), getRotation(self.params[5])] - self.startPos = [getInteger(self.params[6]), getInteger(self.params[7]), getInteger(self.params[8])] - self.endPos = [getInteger(self.params[9]), getInteger(self.params[10]), getInteger(self.params[11])] + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdActorCue( + getInteger(params[1]), + getInteger(params[2]), + getInteger(params[0]), + [getRotation(params[3]), getRotation(params[4]), getRotation(params[5])], + [getInteger(params[6]), getInteger(params[7]), getInteger(params[8])], + [getInteger(params[9]), getInteger(params[10]), getInteger(params[11])], + ) def getCmd(self): self.validateFrames() - if self.actionID is None: - raise PluginError("ERROR: Action ID is None!") + if len(self.rot) == 0: raise PluginError("ERROR: Rotation list is empty!") + if len(self.startPos) == 0: raise PluginError("ERROR: Start Position list is empty!") + if len(self.endPos) == 0: raise PluginError("ERROR: End Position list is empty!") + return indent * 3 + ( f"CS_{'PLAYER' if self.isPlayer else 'ACTOR'}_CUE(" + f"{self.actionID}, {self.startFrame}, {self.endFrame}, " @@ -51,36 +55,35 @@ def getCmd(self): class CutsceneCmdActorCueList(CutsceneCmdBase): """This class contains the Actor Cue List command data""" - isPlayer: bool = False - commandType: Optional[str] = None - entryTotal: Optional[int] = None + isPlayer: bool + commandType: str + entryTotal: int entries: list[CutsceneCmdActorCue] = field(init=False, default_factory=list) paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="actorCueList") - def __post_init__(self): - if self.params is not None: - if self.isPlayer: - self.commandType = "Player" - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str], isPlayer: bool): + if isPlayer: + commandType = "Player" + entryTotal = getInteger(params[0]) + else: + commandType = params[0] + if commandType.startswith("0x"): + # make it a 4 digit hex + commandType = commandType.removeprefix("0x") + commandType = "0x" + "0" * (4 - len(commandType)) + commandType else: - self.commandType = self.params[0] - if self.commandType.startswith("0x"): - # make it a 4 digit hex - self.commandType = self.commandType.removeprefix("0x") - self.commandType = "0x" + "0" * (4 - len(self.commandType)) + self.commandType - else: - self.commandType = ootData.enumData.enumByKey["csCmd"].itemById[self.commandType].key - self.entryTotal = getInteger(self.params[1].strip()) + commandType = ootData.enumData.enumByKey["csCmd"].itemById[commandType].key + entryTotal = getInteger(params[1].strip()) + + return CutsceneCmdActorCueList(None, None, isPlayer, commandType, entryTotal) def getCmd(self): - if self.commandType is None: - raise PluginError("ERROR: ``commandType`` is None!") - if self.entryTotal is None: - raise PluginError("ERROR: ``entryTotal`` is None!") if len(self.entries) == 0: raise PluginError("ERROR: No Actor Cue entry found!") + return ( indent * 2 + ( diff --git a/fast64_internal/oot/exporter/cutscene/camera.py b/fast64_internal/oot/exporter/cutscene/camera.py index 561ed0d10..1a6746e40 100644 --- a/fast64_internal/oot/exporter/cutscene/camera.py +++ b/fast64_internal/oot/exporter/cutscene/camera.py @@ -1,5 +1,4 @@ from dataclasses import dataclass, field -from typing import Optional from ....utility import PluginError, indent from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase @@ -9,33 +8,30 @@ class CutsceneCmdCamPoint(CutsceneCmdBase): """This class contains a single Camera Point command data""" - continueFlag: Optional[str] = None - camRoll: Optional[int] = None - frame: Optional[int] = None - viewAngle: Optional[float] = None - pos: list[int] = field(default_factory=list) + continueFlag: str + camRoll: int + frame: int + viewAngle: float + pos: list[int] paramNumber: int = field(init=False, default=8) - def __post_init__(self): - if self.params is not None: - self.continueFlag = self.params[0] - self.camRoll = getInteger(self.params[1]) - self.frame = getInteger(self.params[2]) - self.viewAngle = float(self.params[3][:-1]) - self.pos = [getInteger(self.params[4]), getInteger(self.params[5]), getInteger(self.params[6])] + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamPoint( + None, + None, + params[0], + getInteger(params[1]), + getInteger(params[2]), + float(params[3][:-1]), + [getInteger(params[4]), getInteger(params[5]), getInteger(params[6])], + ) def getCmd(self): - if self.continueFlag is None: - raise PluginError("ERROR: ``continueFlag`` is None!") - if self.camRoll is None: - raise PluginError("ERROR: ``camRoll`` is None!") - if self.frame is None: - raise PluginError("ERROR: ``frame`` is None!") - if self.viewAngle is None: - raise PluginError("ERROR: ``viewAngle`` is None!") if len(self.pos) == 0: raise PluginError("ERROR: Pos list is empty!") + return indent * 3 + ( f"CS_CAM_POINT({self.continueFlag}, {self.camRoll}, {self.frame}, {self.viewAngle}f, " + "".join(f"{pos}, " for pos in self.pos) @@ -47,18 +43,18 @@ def getCmd(self): class CutsceneCmdCamEyeSpline(CutsceneCmdBase): """This class contains the Camera Eye Spline data""" - entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + entries: list[CutsceneCmdCamPoint] paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camEyeSplineList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamEyeSpline(getInteger(params[0]), getInteger(params[1])) def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") + return self.getCamListCmd("CS_CAM_EYE_SPLINE", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -68,18 +64,18 @@ def getCmd(self): class CutsceneCmdCamATSpline(CutsceneCmdBase): """This class contains the Camera AT (look-at) Spline data""" - entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + entries: list[CutsceneCmdCamPoint] paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camATSplineList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamATSpline(getInteger(params[0]), getInteger(params[1])) def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") + return self.getCamListCmd("CS_CAM_AT_SPLINE", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -89,18 +85,18 @@ def getCmd(self): class CutsceneCmdCamEyeSplineRelToPlayer(CutsceneCmdBase): """This class contains the Camera Eye Spline Relative to the Player data""" - entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + entries: list[CutsceneCmdCamPoint] paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camEyeSplineRelPlayerList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamEyeSplineRelToPlayer(getInteger(params[0]), getInteger(params[1])) def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") + return self.getCamListCmd("CS_CAM_EYE_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -110,18 +106,18 @@ def getCmd(self): class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): """This class contains the Camera AT Spline Relative to the Player data""" - entries: list[CutsceneCmdCamPoint] = field(init=False, default_factory=list) + entries: list[CutsceneCmdCamPoint] paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camATSplineRelPlayerList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamATSplineRelToPlayer(getInteger(params[0]), getInteger(params[1])) def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") + return self.getCamListCmd("CS_CAM_AT_SPLINE_REL_TO_PLAYER", self.startFrame, self.endFrame) + "".join( entry.getCmd() for entry in self.entries ) @@ -132,14 +128,13 @@ class CutsceneCmdCamEye(CutsceneCmdBase): """This class contains a single Camera Eye point""" # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(init=False, default_factory=list) + entries: list paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camEyeList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamEye(getInteger(params[0]), getInteger(params[1])) def getCmd(self): return self.getCamListCmd("CS_CAM_EYE", self.startFrame, self.endFrame) @@ -150,14 +145,13 @@ class CutsceneCmdCamAT(CutsceneCmdBase): """This class contains a single Camera AT point""" # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(init=False, default_factory=list) + entries: list paramNumber: int = field(init=False, default=2) listName: str = field(init=False, default="camATList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdCamAT(getInteger(params[0]), getInteger(params[1])) def getCmd(self): return self.getCamListCmd("CS_CAM_AT", self.startFrame, self.endFrame) diff --git a/fast64_internal/oot/exporter/cutscene/common.py b/fast64_internal/oot/exporter/cutscene/common.py index 57ae1e450..ab91fb283 100644 --- a/fast64_internal/oot/exporter/cutscene/common.py +++ b/fast64_internal/oot/exporter/cutscene/common.py @@ -9,10 +9,8 @@ class CutsceneCmdBase: """This class contains common Cutscene data""" - params: Optional[list[str]] - - startFrame: Optional[int] = None - endFrame: Optional[int] = None + startFrame: Optional[int] + endFrame: Optional[int] def validateFrames(self, checkEndFrame: bool = True): if self.startFrame is None: @@ -20,15 +18,16 @@ def validateFrames(self, checkEndFrame: bool = True): if checkEndFrame and self.endFrame is None: raise PluginError("ERROR: End Frame is None!") - def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): + @staticmethod + def getEnumValue(enumKey: str, value: str, isSeqLegacy: bool = False): enum = ootData.enumData.enumByKey[enumKey] - item = enum.itemById.get(self.params[index]) + item = enum.itemById.get(value) if item is None: - setting = getInteger(self.params[index]) + setting = getInteger(value) if isSeqLegacy: setting -= 1 item = enum.itemByIndex.get(setting) - return item.key if item is not None else self.params[index] + return item.key if item is not None else value def getGenericListCmd(self, cmdName: str, entryTotal: int): if entryTotal is None: diff --git a/fast64_internal/oot/exporter/cutscene/data.py b/fast64_internal/oot/exporter/cutscene/data.py index 1c3573eb8..3dd016a62 100644 --- a/fast64_internal/oot/exporter/cutscene/data.py +++ b/fast64_internal/oot/exporter/cutscene/data.py @@ -2,8 +2,8 @@ import math from dataclasses import dataclass, field -from typing import Optional, TYPE_CHECKING -from bpy.types import Object +from typing import TYPE_CHECKING +from bpy.types import Object, Bone from ....utility import PluginError from ...oot_constants import ootData from .actor_cue import CutsceneCmdActorCueList, CutsceneCmdActorCue @@ -64,12 +64,31 @@ class CutsceneData: """This class defines the command data inside a cutscene""" - csObj: Object - csObjects: dict[str, list[Object]] - csProp: "OOTCutsceneProperty" useMacros: bool motionOnly: bool + totalEntries: int = field(init=False, default=0) + frameCount: int = field(init=False, default=0) + motionFrameCount: int = field(init=False, default=0) + camEndFrame: int = field(init=False, default=0) + destination: CutsceneCmdDestination = field(init=False, default=None) + actorCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) + playerCueList: list[CutsceneCmdActorCueList] = field(init=False, default_factory=list) + camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(init=False, default_factory=list) + camATSplineList: list[CutsceneCmdCamATSpline] = field(init=False, default_factory=list) + camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(init=False, default_factory=list) + camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(init=False, default_factory=list) + camEyeList: list[CutsceneCmdCamEye] = field(init=False, default_factory=list) + camATList: list[CutsceneCmdCamAT] = field(init=False, default_factory=list) + textList: list[CutsceneCmdTextList] = field(init=False, default_factory=list) + miscList: list[CutsceneCmdMiscList] = field(init=False, default_factory=list) + rumbleList: list[CutsceneCmdRumbleControllerList] = field(init=False, default_factory=list) + transitionList: list[CutsceneCmdTransition] = field(init=False, default_factory=list) + lightSettingsList: list[CutsceneCmdLightSettingList] = field(init=False, default_factory=list) + timeList: list[CutsceneCmdTimeList] = field(init=False, default_factory=list) + seqList: list[CutsceneCmdStartStopSeqList] = field(init=False, default_factory=list) + fadeSeqList: list[CutsceneCmdFadeSeqList] = field(init=False, default_factory=list) + @staticmethod def new(csObj: Object, useMacros: bool, motionOnly: bool): csProp: "OOTCutsceneProperty" = csObj.ootCutsceneProperty @@ -86,34 +105,10 @@ def new(csObj: Object, useMacros: bool, motionOnly: bool): csObjects["camShot"].append(obj) csObjects["camShot"].sort(key=lambda obj: obj.name) - newCutsceneData = CutsceneData(csObj, csObjects, csProp, useMacros, motionOnly) - newCutsceneData.setCutsceneData() + newCutsceneData = CutsceneData(useMacros, motionOnly) + newCutsceneData.setCutsceneData(csObjects, csProp) return newCutsceneData - def __post_init__(self): - self.totalEntries = 0 - self.frameCount = 0 - self.motionFrameCount = 0 - self.camEndFrame = 0 - - self.destination: Optional[CutsceneCmdDestination] = None - self.actorCueList: list[CutsceneCmdActorCueList] = [] - self.playerCueList: list[CutsceneCmdActorCueList] = [] - self.camEyeSplineList: list[CutsceneCmdCamEyeSpline] = [] - self.camATSplineList: list[CutsceneCmdCamATSpline] = [] - self.camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = [] - self.camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = [] - self.camEyeList: list[CutsceneCmdCamEye] = [] - self.camATList: list[CutsceneCmdCamAT] = [] - self.textList: list[CutsceneCmdTextList] = [] - self.miscList: list[CutsceneCmdMiscList] = [] - self.rumbleList: list[CutsceneCmdRumbleControllerList] = [] - self.transitionList: list[CutsceneCmdTransition] = [] - self.lightSettingsList: list[CutsceneCmdLightSettingList] = [] - self.timeList: list[CutsceneCmdTimeList] = [] - self.seqList: list[CutsceneCmdStartStopSeqList] = [] - self.fadeSeqList: list[CutsceneCmdFadeSeqList] = [] - def getOoTRotation(self, obj: Object): """Returns the converted Blender rotation""" @@ -150,11 +145,11 @@ def getEnumValueFromProp(self, enumKey: str, owner, propName: str): item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) return item.id if item is not None else getattr(owner, f"{propName}Custom") - def setActorCueListData(self, isPlayer: bool): + def setActorCueListData(self, csObjects: dict[str, list[Object]], isPlayer: bool): """Returns the Actor Cue List commands from the corresponding objects""" playerOrActor = f"{'Player' if isPlayer else 'Actor'}" - actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] + actorCueListObjects = csObjects[f"CS {playerOrActor} Cue List"] actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) self.totalEntries += len(actorCueListObjects) @@ -178,7 +173,7 @@ def setActorCueListData(self, isPlayer: bool): # ignoring dummy cue newActorCueList = CutsceneCmdActorCueList( - None, isPlayer=isPlayer, entryTotal=entryTotal - 1, commandType=commandType + None, None, isPlayer, commandType, entryTotal - 1 ) for i, childObj in enumerate(obj.children, 1): @@ -197,7 +192,6 @@ def setActorCueListData(self, isPlayer: bool): newActorCueList.entries.append( CutsceneCmdActorCue( - None, startFrame, endFrame, actionID, @@ -210,7 +204,7 @@ def setActorCueListData(self, isPlayer: bool): self.actorCueList.append(newActorCueList) - def getCameraShotPointData(self, bones, useAT: bool): + def getCameraShotPointData(self, bones: list[Bone], useAT: bool): """Returns the Camera Point data from the bone data""" shotPoints: list[CutsceneCmdCamPoint] = [] @@ -224,7 +218,6 @@ def getCameraShotPointData(self, bones, useAT: bool): shotPoints.append( CutsceneCmdCamPoint( - None, None, None, ("CS_CAM_CONTINUE" if self.useMacros else "0"), @@ -237,7 +230,7 @@ def getCameraShotPointData(self, bones, useAT: bool): # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting shotPoints.append( - CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useMacros else "-1", 0, 0, 0.0, [0, 0, 0]) + CutsceneCmdCamPoint(None, None, "CS_CAM_STOP" if self.useMacros else "-1", 0, 0, 0.0, [0, 0, 0]) ) return shotPoints @@ -278,29 +271,25 @@ def getCamClassOrList(self, isClass: bool, camMode: str, useAT: bool): def getNewCamData(self, shotObj: Object, useAT: bool): """Returns the Camera Shot data from the corresponding Armatures""" - newCamData = self.getCamClassOrList(True, shotObj.data.ootCamShotProp.shotCamMode, useAT)(None) - newCamData.entries = self.getCameraShotPointData(shotObj.data.bones, useAT) + entries = self.getCameraShotPointData(shotObj.data.bones, useAT) startFrame = shotObj.data.ootCamShotProp.shotStartFrame # "fake" end frame endFrame = ( startFrame - + max(2, sum(point.frame for point in newCamData.entries)) - + (newCamData.entries[-2].frame if useAT else 1) + + max(2, sum(point.frame for point in entries)) + + (entries[-2].frame if useAT else 1) ) if not useAT: - for pointData in newCamData.entries: + for pointData in entries: pointData.frame = 0 self.camEndFrame = endFrame - newCamData.startFrame = startFrame - newCamData.endFrame = endFrame - - return newCamData + return self.getCamClassOrList(True, shotObj.data.ootCamShotProp.shotCamMode, useAT)(startFrame, endFrame, entries) - def setCameraShotData(self): - shotObjects = self.csObjects["camShot"] + def setCameraShotData(self, csObjects: dict[str, list[Object]]): + shotObjects = csObjects["camShot"] if len(shotObjects) > 0: motionFrameCount = -1 @@ -321,7 +310,6 @@ def getNewTextCmd(self, textEntry: "OOTCSTextProperty"): match textEntry.textboxType: case "Text": return CutsceneCmdText( - None, textEntry.startFrame, textEntry.endFrame, textEntry.textID, @@ -330,10 +318,9 @@ def getNewTextCmd(self, textEntry: "OOTCSTextProperty"): textEntry.bottomOptionTextID, ) case "None": - return CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame) + return CutsceneCmdTextNone(textEntry.startFrame, textEntry.endFrame) case "OcarinaAction": return CutsceneCmdTextOcarinaAction( - None, textEntry.startFrame, textEntry.endFrame, self.getEnumValueFromProp("ocarinaSongActionId", textEntry, "ocarinaAction"), @@ -341,37 +328,36 @@ def getNewTextCmd(self, textEntry: "OOTCSTextProperty"): ) raise PluginError("ERROR: Unknown text type!") - def setCutsceneData(self): - self.setActorCueListData(True) - self.setActorCueListData(False) - self.setCameraShotData() + def setCutsceneData(self, csObjects: dict[str, list[Object]], csProp: "OOTCutsceneProperty"): + self.setActorCueListData(csObjects, True) + self.setActorCueListData(csObjects, False) + self.setCameraShotData(csObjects) # don't process the cutscene empty if we don't want its data if self.motionOnly: return - if self.csProp.csUseDestination: + if csProp.csUseDestination: self.destination = CutsceneCmdDestination( + csProp.csDestinationStartFrame, None, - self.csProp.csDestinationStartFrame, - None, - self.getEnumValueFromProp("csDestination", self.csProp, "csDestination"), + self.getEnumValueFromProp("csDestination", csProp, "csDestination"), ) self.totalEntries += 1 - self.frameCount = self.csProp.csEndFrame - self.totalEntries += len(self.csProp.csLists) + self.frameCount = csProp.csEndFrame + self.totalEntries += len(csProp.csLists) - for entry in self.csProp.csLists: + for entry in csProp.csLists: match entry.listType: case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": isFadeOutSeq = entry.listType == "FadeOutSeqList" - cmdList = cmdToClass[entry.listType](None) + cmdList = cmdToClass[entry.listType](None, None) cmdList.entryTotal = len(entry.seqList) if not isFadeOutSeq: cmdList.type = "start" if entry.listType == "StartSeqList" else "stop" for elem in entry.seqList: - data = cmdToClass[entry.listType.removesuffix("List")](None, elem.startFrame, elem.endFrame) + data = cmdToClass[entry.listType.removesuffix("List")](elem.startFrame, elem.endFrame) if isFadeOutSeq: data.seqPlayer = self.getEnumValueFromProp("csFadeOutSeqPlayer", elem, "csSeqPlayer") else: @@ -385,7 +371,6 @@ def setCutsceneData(self): case "Transition": self.transitionList.append( CutsceneCmdTransition( - None, entry.transitionStartFrame, entry.transitionEndFrame, self.getEnumValueFromProp("csTransitionType", entry, "transitionType"), @@ -393,7 +378,7 @@ def setCutsceneData(self): ) case _: curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) - cmdList = cmdToClass[entry.listType](None) + cmdList = cmdToClass[entry.listType](None, None) cmdList.entryTotal = len(curList) for elem in curList: match entry.listType: @@ -402,17 +387,16 @@ def setCutsceneData(self): case "LightSettingsList": cmdList.entries.append( CutsceneCmdLightSetting( - None, elem.startFrame, elem.endFrame, False, elem.lightSettingsIndex + elem.startFrame, elem.endFrame, False, elem.lightSettingsIndex ) ) case "TimeList": cmdList.entries.append( - CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) + CutsceneCmdTime(elem.startFrame, elem.endFrame, elem.hour, elem.minute) ) case "MiscList": cmdList.entries.append( CutsceneCmdMisc( - None, elem.startFrame, elem.endFrame, self.getEnumValueFromProp("csMiscType", elem, "csMiscType"), @@ -421,7 +405,6 @@ def setCutsceneData(self): case "RumbleList": cmdList.entries.append( CutsceneCmdRumbleController( - None, elem.startFrame, elem.endFrame, elem.rumbleSourceStrength, diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py index f04bdf8dc..b37e521ad 100644 --- a/fast64_internal/oot/exporter/cutscene/misc.py +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -9,20 +9,18 @@ class CutsceneCmdMisc(CutsceneCmdBase): """This class contains a single misc command entry""" - type: Optional[str] = None # see ``CutsceneMiscType`` in decomp + type: str # see ``CutsceneMiscType`` in decomp paramNumber: int = field(init=False, default=14) - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.type = self.getEnumValue("csMiscType", 0) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdMisc( + getInteger(params[1]), getInteger(params[2]), CutsceneCmdBase.getEnumValue("csMiscType", params[0]) + ) def getCmd(self): self.validateFrames() - if self.type is None: - raise PluginError("ERROR: Misc Type is None!") return indent * 3 + (f"CS_MISC({self.type}, {self.startFrame}, {self.endFrame}" + ", 0" * 11 + "),\n") @@ -30,18 +28,20 @@ def getCmd(self): class CutsceneCmdLightSetting(CutsceneCmdBase): """This class contains Light Setting command data""" - isLegacy: bool = False - lightSetting: int = 0 + isLegacy: bool + lightSetting: int paramNumber: int = field(init=False, default=11) - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.lightSetting = getInteger(self.params[0]) - if self.isLegacy: - self.lightSetting -= 1 + @staticmethod + def from_params(params: list[str], isLegacy: bool): + lightSetting = getInteger(params[0]) + return CutsceneCmdLightSetting( + getInteger(params[1]), + getInteger(params[2]), + isLegacy, + lightSetting - 1 if isLegacy else lightSetting + ) def getCmd(self): self.validateFrames(False) @@ -52,17 +52,19 @@ def getCmd(self): class CutsceneCmdTime(CutsceneCmdBase): """This class contains Time Ocarina Action command data""" - hour: int = 0 - minute: int = 0 + hour: int + minute: int paramNumber: int = field(init=False, default=5) - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.hour = getInteger(self.params[3]) - self.minute = getInteger(self.params[4]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdTime( + getInteger(params[1]), + getInteger(params[2]), + getInteger(params[3]), + getInteger(params[4]), + ) def getCmd(self): self.validateFrames(False) @@ -73,19 +75,21 @@ def getCmd(self): class CutsceneCmdRumbleController(CutsceneCmdBase): """This class contains Rumble Controller command data""" - sourceStrength: int = 0 - duration: int = 0 - decreaseRate: int = 0 + sourceStrength: int + duration: int + decreaseRate: int paramNumber: int = field(init=False, default=8) - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.sourceStrength = getInteger(self.params[3]) - self.duration = getInteger(self.params[4]) - self.decreaseRate = getInteger(self.params[5]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdRumbleController( + getInteger(params[1]), + getInteger(params[2]), + getInteger(params[3]), + getInteger(params[4]), + getInteger(params[5]), + ) def getCmd(self): self.validateFrames(False) @@ -104,9 +108,11 @@ class CutsceneCmdMiscList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="miscList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdMiscList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: @@ -125,9 +131,11 @@ class CutsceneCmdLightSettingList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="lightSettingsList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdLightSettingList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: @@ -146,9 +154,11 @@ class CutsceneCmdTimeList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="timeList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdTimeList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: @@ -167,9 +177,11 @@ class CutsceneCmdRumbleControllerList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="rumbleList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdRumbleControllerList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: @@ -183,20 +195,19 @@ def getCmd(self): class CutsceneCmdDestination(CutsceneCmdBase): """This class contains Destination command data""" - id: Optional[str] = None + id: str paramNumber: int = field(init=False, default=3) listName: str = field(init=False, default="destination") - def __post_init__(self): - if self.params is not None: - self.id = self.getEnumValue("csDestination", 0) - self.startFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdDestination( + getInteger(params[1]), None, CutsceneCmdBase.getEnumValue("csDestination", params[0]) + ) def getCmd(self): self.validateFrames(False) - if self.id is None: - raise PluginError("ERROR: Destination ID is None!") return indent * 2 + f"CS_DESTINATION({self.id}, {self.startFrame}, 0),\n" @@ -204,19 +215,17 @@ def getCmd(self): class CutsceneCmdTransition(CutsceneCmdBase): """This class contains Transition command data""" - type: Optional[str] = None + type: str paramNumber: int = field(init=False, default=3) listName: str = field(init=False, default="transitionList") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.type = self.getEnumValue("csTransitionType", 0) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdTransition( + getInteger(params[1]), getInteger(params[2]), CutsceneCmdBase.getEnumValue("csTransitionType", params[0]) + ) def getCmd(self): self.validateFrames() - if self.type is None: - raise PluginError("ERROR: Transition type is None!") return indent * 2 + f"CS_TRANSITION({self.type}, {self.startFrame}, {self.endFrame}),\n" diff --git a/fast64_internal/oot/exporter/cutscene/seq.py b/fast64_internal/oot/exporter/cutscene/seq.py index 4ade02384..09b462d81 100644 --- a/fast64_internal/oot/exporter/cutscene/seq.py +++ b/fast64_internal/oot/exporter/cutscene/seq.py @@ -14,11 +14,13 @@ class CutsceneCmdStartStopSeq(CutsceneCmdBase): paramNumber: int = field(init=False, default=11) type: Optional[str] = field(init=False, default=None) # "start" or "stop" - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.seqId = self.getEnumValue("seqId", 0, self.isLegacy) + @staticmethod + def from_params(params: list[str], isLegacy: bool): + return CutsceneCmdFadeSeq( + getInteger(params[1]), + getInteger(params[2]), + CutsceneCmdBase.getEnumValue("seqId", params[0], isLegacy) + ) def getCmd(self): self.validateFrames() @@ -35,11 +37,13 @@ class CutsceneCmdFadeSeq(CutsceneCmdBase): paramNumber: int = field(init=False, default=11) enumKey: str = field(init=False, default="csFadeOutSeqPlayer") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.seqPlayer = self.getEnumValue(self.enumKey, 0) + @staticmethod + def from_params(params: list[str], enumKey: str): + return CutsceneCmdFadeSeq( + getInteger(params[1]), + getInteger(params[2]), + CutsceneCmdBase.getEnumValue(enumKey, params[0]) + ) def getCmd(self): self.validateFrames() @@ -56,9 +60,11 @@ class CutsceneCmdStartStopSeqList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="seqList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdStartStopSeqList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: @@ -77,9 +83,11 @@ class CutsceneCmdFadeSeqList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="fadeSeqList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdFadeSeqList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: diff --git a/fast64_internal/oot/exporter/cutscene/text.py b/fast64_internal/oot/exporter/cutscene/text.py index f5e79d622..cf085d435 100644 --- a/fast64_internal/oot/exporter/cutscene/text.py +++ b/fast64_internal/oot/exporter/cutscene/text.py @@ -9,22 +9,24 @@ class CutsceneCmdText(CutsceneCmdBase): """This class contains Text command data""" - textId: int = 0 - type: str = str() - altTextId1: int = 0 - altTextId2: int = 0 + textId: int + type: str + altTextId1: int + altTextId2: int paramNumber: int = field(init=False, default=6) id: str = field(init=False, default="Text") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.textId = getInteger(self.params[0]) - self.type = self.getEnumValue("csTextType", 3) - self.altTextId1 = getInteger(self.params[4]) - self.altTextId2 = getInteger(self.params[5]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdText( + getInteger(params[1]), + getInteger(params[2]), + getInteger(params[0]), + CutsceneCmdBase.getEnumValue("csTextType", params[3]), + getInteger(params[4]), + getInteger(params[5]), + ) def getCmd(self): self.validateFrames() @@ -42,10 +44,9 @@ class CutsceneCmdTextNone(CutsceneCmdBase): paramNumber: int = field(init=False, default=2) id: str = field(init=False, default="None") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[0]) - self.endFrame = getInteger(self.params[1]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdTextNone(getInteger(params[0]), getInteger(params[1])) def getCmd(self): self.validateFrames() @@ -56,25 +57,23 @@ def getCmd(self): class CutsceneCmdTextOcarinaAction(CutsceneCmdBase): """This class contains Text Ocarina Action command data""" - ocarinaActionId: str = str() - messageId: int = 0 + ocarinaActionId: str + messageId: int paramNumber: int = field(init=False, default=4) id: str = field(init=False, default="OcarinaAction") - def __post_init__(self): - if self.params is not None: - self.startFrame = getInteger(self.params[1]) - self.endFrame = getInteger(self.params[2]) - self.ocarinaActionId = self.getEnumValue("ocarinaSongActionId", 0) - self.messageId = getInteger(self.params[3]) + @staticmethod + def from_params(params: list[str]): + return CutsceneCmdTextOcarinaAction( + getInteger(params[1]), + getInteger(params[2]), + CutsceneCmdBase.getEnumValue("ocarinaSongActionId", params[0]), + getInteger(params[3]), + ) def getCmd(self): self.validateFrames() - if self.ocarinaActionId is None: - raise PluginError("ERROR: ``ocarinaActionId`` is None!") - if self.messageId is None: - raise PluginError("ERROR: ``messageId`` is None!") return indent * 3 + ( f"CS_TEXT_OCARINA_ACTION(" + f"{self.ocarinaActionId}, {self.startFrame}, {self.endFrame}, {self.messageId}" @@ -93,13 +92,16 @@ class CutsceneCmdTextList(CutsceneCmdBase): paramNumber: int = field(init=False, default=1) listName: str = field(init=False, default="textList") - def __post_init__(self): - if self.params is not None: - self.entryTotal = getInteger(self.params[0]) + @staticmethod + def from_params(params: list[str]): + new = CutsceneCmdTextList() + new.entryTotal = getInteger(params[0]) + return new def getCmd(self): if len(self.entries) == 0: raise PluginError("ERROR: Entry list is empty!") + return self.getGenericListCmd("CS_TEXT_LIST", self.entryTotal) + "".join( entry.getCmd() for entry in self.entries ) diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py new file mode 100644 index 000000000..b3f77ebd3 --- /dev/null +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -0,0 +1,38 @@ +import os +import re + +from typing import TYPE_CHECKING +from ...oot_utility import getSceneDirFromLevelName +from .scene_table import SceneTableUtility +from .spec import SpecUtility + +if TYPE_CHECKING: + from ..main import SceneExport + + + +class Files: # TODO: find a better name + """This class handles editing decomp files""" + + @staticmethod + def modifySceneFiles(exporter: "SceneExport"): + if exporter.exportInfo.customSubPath is not None: + sceneDir = exporter.exportInfo.customSubPath + exporter.exportInfo.name + else: + sceneDir = getSceneDirFromLevelName(exporter.sceneName) + + scenePath = os.path.join(exporter.exportInfo.exportPath, sceneDir) + for filename in os.listdir(scenePath): + filepath = os.path.join(scenePath, filename) + if os.path.isfile(filepath): + match = re.match(exporter.scene.name + "\_room\_(\d+)\.[ch]", filename) + if match is not None and int(match.group(1)) >= len(exporter.scene.rooms.entries): + os.remove(filepath) + + @staticmethod + def editFiles(exporter: "SceneExport"): + """Edits decomp files""" + + Files.modifySceneFiles(exporter) + SpecUtility.editSpec(exporter) + SceneTableUtility.editSceneTable(exporter) diff --git a/fast64_internal/oot/exporter/other/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py similarity index 96% rename from fast64_internal/oot/exporter/other/scene_table.py rename to fast64_internal/oot/exporter/decomp_edit/scene_table.py index a558b4c6d..2278b002e 100644 --- a/fast64_internal/oot/exporter/other/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -6,7 +6,6 @@ from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile from ...oot_constants import ootEnumSceneID, ootSceneNameToID -from ...oot_utility import getCustomProperty, ExportInfo if TYPE_CHECKING: from ..main import SceneExport @@ -31,6 +30,7 @@ class SceneTableEntry: original: Optional[str] # the original line from the parsed file exporter: Optional["SceneExport"] = None exportName: Optional[str] = None + isCustomScene: bool = False prefix: Optional[str] = None # ifdefs, endifs, comments etc, everything before the current entry suffix: Optional[str] = None # remaining data after the last entry parsed: Optional[str] = None @@ -74,7 +74,7 @@ def setParametersFromScene(self, exporter: Optional["SceneExport"] = None): # TODO: Implement title cards name = exporter.scene.name if exporter is not None else self.exportName self.setParameters( - f"{exporter.scene.name.lower()}_scene", + f"{exporter.scene.name.lower() if self.isCustomScene else exporter.scene.name}_scene", "none", ootSceneNameToID.get(name, f"SCENE_{name.upper()}"), exporter.scene.mainHeader.infos.drawConfig, @@ -120,6 +120,8 @@ def __post_init__(self): entryIndex = 0 # we don't use ``enumerate`` since not every line is an actual entry assert len(lines) > 0 for line in lines: + line = line.strip() + # skip the lines before an entry, create one from the file's data # and add the skipped lines as a prefix of the current entry if ( @@ -136,6 +138,9 @@ def __post_init__(self): prefix = "" entryIndex += 1 else: + if prefix.startswith("#") and line.startswith("#"): + # add newline if there's two consecutive preprocessor directives + prefix += "\n" prefix += line # add whatever's after the last entry @@ -309,10 +314,10 @@ def editSceneTable(exporter: Optional["SceneExport"]): sceneTable.remove(sceneTable.selectedSceneIndex) elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: # custom mode: new custom scene - sceneTable.append(SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name)) + sceneTable.append(SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name, True)) elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: # insert mode - sceneTable.insert(SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name)) + sceneTable.insert(SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name, False)) else: # update mode (for both vanilla and custom scenes since they already exist in the table) sceneTable.entries[sceneTable.getIndex()].setParametersFromScene(exporter) diff --git a/fast64_internal/oot/exporter/other/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py similarity index 89% rename from fast64_internal/oot/exporter/other/spec.py rename to fast64_internal/oot/exporter/decomp_edit/spec.py index 4ef3eef09..2f26e1cf8 100644 --- a/fast64_internal/oot/exporter/other/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile, indent -from ...oot_utility import ExportInfo, getSceneDirFromLevelName +from ...oot_utility import getSceneDirFromLevelName if TYPE_CHECKING: from ..main import SceneExport @@ -53,23 +53,21 @@ def to_c(self): return self.prefix + indent + f"{self.type.name.lower()} {self.content}".strip() + self.suffix + "\n" -@dataclass class SpecEntry: """Defines an entry of ``spec``""" - original: Optional[list[str]] = field(default_factory=list) # the original lines from the parsed file - commands: list[SpecEntryCommand] = field(default_factory=list) # list of the different spec commands - segmentName: str = "" # the name of the current segment - prefix: str = "" # data between two commands - suffix: str = "" # remaining data after the entry (used for the last entry) - contentSuffix: str = "" # remaining data after the last command in the current entry + def __init__(self, commands: list[SpecEntryCommand] = [], original: Optional[list[str]] = None, prefix = str()): + self.commands = commands # list of the different spec commands + self.segmentName = str() # the name of the current segment + self.prefix = prefix # data between two commands + self.suffix = str() # remaining data after the entry (used for the last entry) + self.contentSuffix = str() # remaining data after the last command in the current entry - def __post_init__(self): - if self.original is not None: + if original is not None: global buildDirectory # parse the commands from the existing data prefix = "" - for line in self.original: + for line in original: line = line.strip() dontHaveComments = ( not line.startswith("// ") and not line.startswith("/* ") and not line.startswith(" */") @@ -124,17 +122,15 @@ def to_c(self): ) -@dataclass class SpecFile: """This class defines the spec's file data""" - exportPath: str # path to the spec file - entries: list[SpecEntry] = field(default_factory=list) # list of the different spec entries + def __init__(self, exportPath: str): + self.entries: list[SpecEntry] = [] # list of the different spec entries - def __post_init__(self): # read the file's data try: - with open(self.exportPath, "r") as fileData: + with open(exportPath, "r") as fileData: lines = fileData.readlines() except FileNotFoundError: raise PluginError("ERROR: Can't find spec!") @@ -159,12 +155,15 @@ def __post_init__(self): parsedLines.append(line) elif "endseg" in line: # else, if the line has endseg in it (> if we reached the end of the current segment) - entry = SpecEntry(parsedLines, prefix=prefix) + entry = SpecEntry(original=parsedLines, prefix=prefix) self.entries.append(entry) prefix = "" parsedLines = [] else: # else, if between 2 segments and the line is something we don't need + if prefix.startswith("#") and line.startswith("#"): + # add newline if there's two consecutive preprocessor directives + prefix += "\n" prefix += line # set the last's entry's suffix to the remaining prefix self.entries[-1].suffix = prefix.removesuffix("\n") @@ -275,7 +274,7 @@ def editSpec(exporter: "SceneExport"): ) sceneCmds.append(SpecEntryCommand(CommandType.NUMBER, "2")) - specFile.append(SpecEntry(None, sceneCmds)) + specFile.append(SpecEntry(sceneCmds)) # rooms for i in range(roomTotal): @@ -298,8 +297,11 @@ def editSpec(exporter: "SceneExport"): ] ) + if exporter.roomIndexHasOcclusion[i]: + roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"')) + roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) - specFile.append(SpecEntry(None, roomCmds)) + specFile.append(SpecEntry(roomCmds)) specFile.entries[-1].suffix = "\n" # finally, write the spec file diff --git a/fast64_internal/oot/exporter/classes.py b/fast64_internal/oot/exporter/file.py similarity index 76% rename from fast64_internal/oot/exporter/classes.py rename to fast64_internal/oot/exporter/file.py index 2be685993..e15e396c2 100644 --- a/fast64_internal/oot/exporter/classes.py +++ b/fast64_internal/oot/exporter/file.py @@ -1,7 +1,6 @@ import os -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass from ...utility import writeFile @@ -10,12 +9,12 @@ class RoomFile: """This class hosts the C data for every room files""" name: str - roomMain: Optional[str] = None - roomModel: Optional[str] = None - roomModelInfo: Optional[str] = None - singleFileExport: bool = False - path: Optional[str] = None - header: str = "" + roomMain: str + roomModel: str + roomModelInfo: str + singleFileExport: bool + path: str + header: str def write(self): """Writes the room files""" @@ -36,18 +35,20 @@ class SceneFile: """This class hosts the C data for every scene files""" name: str - sceneMain: Optional[str] = None - sceneCollision: Optional[str] = None - sceneCutscenes: list[str] = field(default_factory=list) - sceneTextures: Optional[str] = None - roomList: dict[int, RoomFile] = field(default_factory=dict) - singleFileExport: bool = False - path: Optional[str] = None - header: str = "" - - def __post_init__(self): - self.hasCutscenes = len(self.sceneCutscenes) > 0 - self.hasSceneTextures = len(self.sceneTextures) > 0 + sceneMain: str + sceneCollision: str + sceneCutscenes: list[str] + sceneTextures: str + roomList: dict[int, RoomFile] + singleFileExport: bool + path: str + header: str + + def hasCutscenes(self): + return len(self.sceneCutscenes) > 0 + + def hasSceneTextures(self): + return len(self.sceneTextures) > 0 def getSourceWithSceneInclude(self, sceneInclude: str, source: str): """Returns the source with the includes if missing""" @@ -70,13 +71,13 @@ def setIncludeData(self): roomData.roomModel = self.getSourceWithSceneInclude(sceneInclude, roomData.roomModel) self.sceneMain = self.getSourceWithSceneInclude( - sceneInclude if not self.hasCutscenes else csInclude, self.sceneMain + sceneInclude if not self.hasCutscenes() else csInclude, self.sceneMain ) if not self.singleFileExport: self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) - if self.hasCutscenes: + if self.hasCutscenes(): for i in range(len(self.sceneCutscenes)): self.sceneCutscenes[i] = self.getSourceWithSceneInclude(csInclude, self.sceneCutscenes[i]) @@ -90,18 +91,18 @@ def write(self): if self.singleFileExport: sceneMainPath = f"{self.name}.c" - if self.hasCutscenes: + if self.hasCutscenes(): self.sceneMain += "".join(cs for cs in self.sceneCutscenes) self.sceneMain += self.sceneCollision - if self.hasSceneTextures: + if self.hasSceneTextures(): self.sceneMain += self.sceneTextures else: sceneMainPath = f"{self.name}_main.c" writeFile(os.path.join(self.path, f"{self.name}_col.c"), self.sceneCollision) - if self.hasCutscenes: + if self.hasCutscenes(): for i, cs in enumerate(self.sceneCutscenes): writeFile(os.path.join(self.path, f"{self.name}_cs_{i}.c"), cs) - if self.hasSceneTextures: + if self.hasSceneTextures(): writeFile(os.path.join(self.path, f"{self.name}_tex.c"), self.sceneTextures) writeFile(os.path.join(self.path, sceneMainPath), self.sceneMain) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 02a706741..4d6c52578 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -1,5 +1,6 @@ import bpy import os +import traceback from dataclasses import dataclass, field from mathutils import Matrix @@ -10,8 +11,8 @@ from ..oot_model_classes import OOTModel from ..oot_f3d_writer import writeTextureArraysNew from .scene import Scene -from .other import Files -from .classes import SceneFile +from .decomp_edit import Files +from .file import SceneFile from ...utility import ( PluginError, @@ -132,7 +133,7 @@ def export(self): room.mesh.copyBgImages(self.path) if not isCustomExport: - Files(self).editFiles() + Files.editFiles(self) if self.hackerootBootOption is not None and self.hackerootBootOption.bootToScene: setBootupScene( diff --git a/fast64_internal/oot/exporter/other/__init__.py b/fast64_internal/oot/exporter/other/__init__.py deleted file mode 100644 index 8ce89661f..000000000 --- a/fast64_internal/oot/exporter/other/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .file import Files diff --git a/fast64_internal/oot/exporter/other/file.py b/fast64_internal/oot/exporter/other/file.py deleted file mode 100644 index f8f621589..000000000 --- a/fast64_internal/oot/exporter/other/file.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -import re - -from dataclasses import dataclass -from typing import TYPE_CHECKING -from ...oot_utility import getSceneDirFromLevelName -from .scene_table import SceneTableUtility -from .spec import SpecUtility - -if TYPE_CHECKING: - from ..main import SceneExport - - -@dataclass -class Files: # TODO: find a better name - """This class handles editing decomp files""" - - exporter: "SceneExport" - - def modifySceneFiles(self): - if self.exporter.exportInfo.customSubPath is not None: - sceneDir = self.exporter.exportInfo.customSubPath + self.exporter.exportInfo.name - else: - sceneDir = getSceneDirFromLevelName(self.exporter.sceneName) - - scenePath = os.path.join(self.exporter.exportInfo.exportPath, sceneDir) - for filename in os.listdir(scenePath): - filepath = os.path.join(scenePath, filename) - if os.path.isfile(filepath): - match = re.match(self.exporter.scene.name + "\_room\_(\d+)\.[ch]", filename) - if match is not None and int(match.group(1)) >= len(self.exporter.scene.rooms.entries): - os.remove(filepath) - - def editFiles(self): - """Edits decomp files""" - self.modifySceneFiles() - SpecUtility.editSpec(self.exporter) - SceneTableUtility.editSceneTable(self.exporter) diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index ec6320206..0884c84cd 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -6,7 +6,8 @@ from ...oot_utility import getObjectList from ...oot_constants import ootData from ...room.properties import OOTRoomHeaderProperty -from ..base import Utility, Actor +from ..utility import Utility +from ..actor import Actor @dataclass @@ -204,7 +205,6 @@ def getC(self): return actorList - @dataclass class RoomHeader: """This class defines a room header""" diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index ce10dd9b8..f62e16e66 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Optional from mathutils import Matrix from bpy.types import Object @@ -9,8 +9,8 @@ from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter from ...oot_utility import CullGroup -from ..classes import RoomFile -from ..base import Utility, altHeaderList +from ..file import RoomFile +from ..utility import Utility, altHeaderList from .header import RoomAlternateHeader, RoomHeader from .shape import RoomShape diff --git a/fast64_internal/oot/exporter/scene/__init__.py b/fast64_internal/oot/exporter/scene/__init__.py index 73f506ada..f8b83cdd2 100644 --- a/fast64_internal/oot/exporter/scene/__init__.py +++ b/fast64_internal/oot/exporter/scene/__init__.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object from typing import Optional @@ -6,8 +6,8 @@ from ....f3d.f3d_gbi import TextureExportSettings, ScrollMethod from ...scene.properties import OOTSceneHeaderProperty from ...oot_model_classes import OOTModel, OOTGfxFormatter -from ..classes import SceneFile -from ..base import Utility, altHeaderList +from ..file import SceneFile +from ..utility import Utility, altHeaderList from ..collision import CollisionHeader from .header import SceneAlternateHeader, SceneHeader from .rooms import RoomEntries @@ -36,7 +36,6 @@ def new(name: str, sceneObj: Object, transform: Matrix, useMacros: bool, saveTex f"{name}_collisionHeader", name, sceneObj, - None, transform, useMacros, True, diff --git a/fast64_internal/oot/exporter/scene/actors.py b/fast64_internal/oot/exporter/scene/actors.py index 271a4335c..8f7735b6e 100644 --- a/fast64_internal/oot/exporter/scene/actors.py +++ b/fast64_internal/oot/exporter/scene/actors.py @@ -5,8 +5,8 @@ from ....utility import PluginError, CData, indent from ...oot_utility import getObjectList from ...oot_constants import ootData -from ...scene.properties import OOTSceneHeaderProperty -from ..base import Utility, Actor +from ..utility import Utility +from ..actor import Actor @dataclass diff --git a/fast64_internal/oot/exporter/scene/general.py b/fast64_internal/oot/exporter/scene/general.py index e92baac9d..9d8949ed0 100644 --- a/fast64_internal/oot/exporter/scene/general.py +++ b/fast64_internal/oot/exporter/scene/general.py @@ -1,9 +1,8 @@ -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass from bpy.types import Object from ....utility import PluginError, CData, exportColor, ootGetBaseOrCustomLight, indent from ...scene.properties import OOTSceneHeaderProperty, OOTLightProperty -from ..base import Utility +from ..utility import Utility @dataclass diff --git a/fast64_internal/oot/exporter/scene/header.py b/fast64_internal/oot/exporter/scene/header.py index fd6069e4d..72bc8d32f 100644 --- a/fast64_internal/oot/exporter/scene/header.py +++ b/fast64_internal/oot/exporter/scene/header.py @@ -4,7 +4,6 @@ from bpy.types import Object from ....utility import CData from ...scene.properties import OOTSceneHeaderProperty -from ..base import Utility from ..cutscene import SceneCutscene from .general import SceneLighting, SceneInfos, SceneExits from .actors import SceneTransitionActors, SceneEntranceActors, SceneSpawns diff --git a/fast64_internal/oot/exporter/scene/pathways.py b/fast64_internal/oot/exporter/scene/pathways.py index a933f586c..331af8dbe 100644 --- a/fast64_internal/oot/exporter/scene/pathways.py +++ b/fast64_internal/oot/exporter/scene/pathways.py @@ -3,8 +3,7 @@ from bpy.types import Object from ....utility import PluginError, CData, indent from ...oot_utility import getObjectList -from ...scene.properties import OOTSceneHeaderProperty -from ..base import Utility +from ..utility import Utility @dataclass diff --git a/fast64_internal/oot/exporter/base.py b/fast64_internal/oot/exporter/utility.py similarity index 78% rename from fast64_internal/oot/exporter/base.py rename to fast64_internal/oot/exporter/utility.py index 3faefbd08..29e4ad076 100644 --- a/fast64_internal/oot/exporter/base.py +++ b/fast64_internal/oot/exporter/utility.py @@ -1,5 +1,3 @@ -from dataclasses import dataclass, field -from typing import Optional from math import radians from mathutils import Quaternion, Matrix from bpy.types import Object @@ -96,30 +94,3 @@ def getEndCmd(): return indent + "SCENE_CMD_END(),\n" - -@dataclass -class Actor: - """Defines an Actor""" - - name: Optional[str] = field(init=False, default=None) - id: Optional[str] = field(init=False, default=None) - pos: list[int] = field(init=False, default_factory=list) - rot: Optional[str] = field(init=False, default=None) - params: Optional[str] = field(init=False, default=None) - - def getActorEntry(self): - """Returns a single actor entry""" - - posData = "{ " + ", ".join(f"{round(p)}" for p in self.pos) + " }" - rotData = "{ " + self.rot + " }" - - actorInfos = [self.id, posData, rotData, self.params] - infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] - - return ( - indent - + (f"// {self.name}\n" + indent if self.name != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) diff --git a/fast64_internal/oot/file_settings.py b/fast64_internal/oot/file_settings.py index 1d50ef40b..1f451becd 100644 --- a/fast64_internal/oot/file_settings.py +++ b/fast64_internal/oot/file_settings.py @@ -22,8 +22,8 @@ def draw(self, context): col.prop(context.scene.fast64.oot, "hackerFeaturesEnabled") if not context.scene.fast64.oot.hackerFeaturesEnabled: - col.prop(context.scene, "useDecompFeatures") - col.prop(context.scene, "exportMotionOnly") + col.prop(context.scene.fast64.oot, "useDecompFeatures") + col.prop(context.scene.fast64.oot, "exportMotionOnly") oot_classes = (OOT_FileSettingsPanel,) @@ -36,25 +36,10 @@ def file_register(): Scene.ootBlenderScale = FloatProperty(name="Blender To OOT Scale", default=10, update=on_update_render_settings) Scene.ootDecompPath = StringProperty(name="Decomp Folder", subtype="FILE_PATH") - Scene.useDecompFeatures = BoolProperty( - name="Use decomp for export", description="Use names and macros from decomp when exporting", default=True - ) - - Scene.exportMotionOnly = BoolProperty( - name="Export CS Motion Data Only", - description=( - "Export everything or only the camera and actor motion data.\n" - + "This will insert the data into the cutscene." - ), - default=False, - ) - def file_unregister(): for cls in reversed(oot_classes): unregister_class(cls) - del Scene.exportMotionOnly - del Scene.useDecompFeatures del Scene.ootBlenderScale del Scene.ootDecompPath diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py index 8f0ab5c09..fa7685625 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py @@ -16,7 +16,7 @@ def getCutsceneC(csName: str): csData.source = ( declarationBase + " = {\n" - + getNewCutsceneExport(csName, bpy.context.scene.exportMotionOnly).getExportData() + + getNewCutsceneExport(csName, bpy.context.scene.fast64.oot.exportMotionOnly).getExportData() + "};\n\n" ) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 26908a28f..ad2534e38 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -186,7 +186,7 @@ def execute(self, context): bootOptions if hackerFeaturesEnabled else None, settings.singleFile, TextureExportSettings(False, context.scene.saveTextures, None, None), - context.scene.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, + context.scene.fast64.oot.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, ).export() # else: # ootExportSceneToC( From 1e2d9309f0d132102d326a3dd8d33ec061beee4a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:14:08 +0200 Subject: [PATCH 84/98] format --- .../oot/collision/exporter/to_c/collision.py | 2 +- fast64_internal/oot/exporter/actor.py | 1 + .../oot/exporter/collision/__init__.py | 4 +--- .../oot/exporter/cutscene/__init__.py | 4 +++- fast64_internal/oot/exporter/cutscene/camera.py | 2 +- fast64_internal/oot/exporter/cutscene/data.py | 14 +++++--------- fast64_internal/oot/exporter/cutscene/misc.py | 5 +---- fast64_internal/oot/exporter/cutscene/seq.py | 8 ++------ .../oot/exporter/decomp_edit/__init__.py | 1 - .../oot/exporter/decomp_edit/scene_table.py | 8 ++++++-- fast64_internal/oot/exporter/decomp_edit/spec.py | 16 +++++++++------- fast64_internal/oot/exporter/file.py | 2 +- fast64_internal/oot/exporter/room/header.py | 1 + fast64_internal/oot/exporter/utility.py | 1 - 14 files changed, 32 insertions(+), 37 deletions(-) diff --git a/fast64_internal/oot/collision/exporter/to_c/collision.py b/fast64_internal/oot/collision/exporter/to_c/collision.py index 554061602..4120776be 100644 --- a/fast64_internal/oot/collision/exporter/to_c/collision.py +++ b/fast64_internal/oot/collision/exporter/to_c/collision.py @@ -303,7 +303,7 @@ def exportCollisionToC( obj, transformMatrix, bpy.context.scene.fast64.oot.useDecompFeatures, - exportSettings.includeChildren + exportSettings.includeChildren, ).getC() ) diff --git a/fast64_internal/oot/exporter/actor.py b/fast64_internal/oot/exporter/actor.py index ccf07eaf1..612437c65 100644 --- a/fast64_internal/oot/exporter/actor.py +++ b/fast64_internal/oot/exporter/actor.py @@ -2,6 +2,7 @@ # this file is not inside the room folder since the scene data can have actors too + class Actor: """Defines an Actor""" diff --git a/fast64_internal/oot/exporter/collision/__init__.py b/fast64_internal/oot/exporter/collision/__init__.py index 11060bd59..d2ef96e7d 100644 --- a/fast64_internal/oot/exporter/collision/__init__.py +++ b/fast64_internal/oot/exporter/collision/__init__.py @@ -64,9 +64,7 @@ def getMeshObjects( return transformFromMeshObj @staticmethod - def getCollisionData( - dataHolder: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool - ): + def getCollisionData(dataHolder: Optional[Object], transform: Matrix, useMacros: bool, includeChildren: bool): """Returns collision data, surface types and vertex positions from mesh objects""" object.select_all(action="DESELECT") diff --git a/fast64_internal/oot/exporter/cutscene/__init__.py b/fast64_internal/oot/exporter/cutscene/__init__.py index 54c60488e..f1cca53b6 100644 --- a/fast64_internal/oot/exporter/cutscene/__init__.py +++ b/fast64_internal/oot/exporter/cutscene/__init__.py @@ -127,7 +127,9 @@ def new(props: OOTSceneHeaderProperty, headerIndex: int, useMacros: bool): if props.writeCutscene: # if csWriteCustom is None then the name will auto-set from the csObj passed in the class - entries.append(Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.fast64.oot.exportMotionOnly)) + entries.append( + Cutscene.new(csWriteCustom, csObj, useMacros, bpy.context.scene.fast64.oot.exportMotionOnly) + ) return SceneCutscene(entries) def getCmd(self): diff --git a/fast64_internal/oot/exporter/cutscene/camera.py b/fast64_internal/oot/exporter/cutscene/camera.py index 1a6746e40..fa9ddc469 100644 --- a/fast64_internal/oot/exporter/cutscene/camera.py +++ b/fast64_internal/oot/exporter/cutscene/camera.py @@ -112,7 +112,7 @@ class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): @staticmethod def from_params(params: list[str]): - return CutsceneCmdCamATSplineRelToPlayer(getInteger(params[0]), getInteger(params[1])) + return CutsceneCmdCamATSplineRelToPlayer(getInteger(params[0]), getInteger(params[1])) def getCmd(self): if len(self.entries) == 0: diff --git a/fast64_internal/oot/exporter/cutscene/data.py b/fast64_internal/oot/exporter/cutscene/data.py index 3dd016a62..25a92db7d 100644 --- a/fast64_internal/oot/exporter/cutscene/data.py +++ b/fast64_internal/oot/exporter/cutscene/data.py @@ -172,9 +172,7 @@ def setActorCueListData(self, csObjects: dict[str, list[Object]], isPlayer: bool commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id # ignoring dummy cue - newActorCueList = CutsceneCmdActorCueList( - None, None, isPlayer, commandType, entryTotal - 1 - ) + newActorCueList = CutsceneCmdActorCueList(None, None, isPlayer, commandType, entryTotal - 1) for i, childObj in enumerate(obj.children, 1): startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame @@ -275,18 +273,16 @@ def getNewCamData(self, shotObj: Object, useAT: bool): startFrame = shotObj.data.ootCamShotProp.shotStartFrame # "fake" end frame - endFrame = ( - startFrame - + max(2, sum(point.frame for point in entries)) - + (entries[-2].frame if useAT else 1) - ) + endFrame = startFrame + max(2, sum(point.frame for point in entries)) + (entries[-2].frame if useAT else 1) if not useAT: for pointData in entries: pointData.frame = 0 self.camEndFrame = endFrame - return self.getCamClassOrList(True, shotObj.data.ootCamShotProp.shotCamMode, useAT)(startFrame, endFrame, entries) + return self.getCamClassOrList(True, shotObj.data.ootCamShotProp.shotCamMode, useAT)( + startFrame, endFrame, entries + ) def setCameraShotData(self, csObjects: dict[str, list[Object]]): shotObjects = csObjects["camShot"] diff --git a/fast64_internal/oot/exporter/cutscene/misc.py b/fast64_internal/oot/exporter/cutscene/misc.py index b37e521ad..a332dbf6c 100644 --- a/fast64_internal/oot/exporter/cutscene/misc.py +++ b/fast64_internal/oot/exporter/cutscene/misc.py @@ -37,10 +37,7 @@ class CutsceneCmdLightSetting(CutsceneCmdBase): def from_params(params: list[str], isLegacy: bool): lightSetting = getInteger(params[0]) return CutsceneCmdLightSetting( - getInteger(params[1]), - getInteger(params[2]), - isLegacy, - lightSetting - 1 if isLegacy else lightSetting + getInteger(params[1]), getInteger(params[2]), isLegacy, lightSetting - 1 if isLegacy else lightSetting ) def getCmd(self): diff --git a/fast64_internal/oot/exporter/cutscene/seq.py b/fast64_internal/oot/exporter/cutscene/seq.py index 09b462d81..f8afeb78b 100644 --- a/fast64_internal/oot/exporter/cutscene/seq.py +++ b/fast64_internal/oot/exporter/cutscene/seq.py @@ -17,9 +17,7 @@ class CutsceneCmdStartStopSeq(CutsceneCmdBase): @staticmethod def from_params(params: list[str], isLegacy: bool): return CutsceneCmdFadeSeq( - getInteger(params[1]), - getInteger(params[2]), - CutsceneCmdBase.getEnumValue("seqId", params[0], isLegacy) + getInteger(params[1]), getInteger(params[2]), CutsceneCmdBase.getEnumValue("seqId", params[0], isLegacy) ) def getCmd(self): @@ -40,9 +38,7 @@ class CutsceneCmdFadeSeq(CutsceneCmdBase): @staticmethod def from_params(params: list[str], enumKey: str): return CutsceneCmdFadeSeq( - getInteger(params[1]), - getInteger(params[2]), - CutsceneCmdBase.getEnumValue(enumKey, params[0]) + getInteger(params[1]), getInteger(params[2]), CutsceneCmdBase.getEnumValue(enumKey, params[0]) ) def getCmd(self): diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index b3f77ebd3..910355623 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -10,7 +10,6 @@ from ..main import SceneExport - class Files: # TODO: find a better name """This class handles editing decomp files""" diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index 2278b002e..f194beb0e 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -314,10 +314,14 @@ def editSceneTable(exporter: Optional["SceneExport"]): sceneTable.remove(sceneTable.selectedSceneIndex) elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: # custom mode: new custom scene - sceneTable.append(SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name, True)) + sceneTable.append( + SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name, True) + ) elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: # insert mode - sceneTable.insert(SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name, False)) + sceneTable.insert( + SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name, False) + ) else: # update mode (for both vanilla and custom scenes since they already exist in the table) sceneTable.entries[sceneTable.getIndex()].setParametersFromScene(exporter) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 2f26e1cf8..5f99e27ce 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -56,12 +56,12 @@ def to_c(self): class SpecEntry: """Defines an entry of ``spec``""" - def __init__(self, commands: list[SpecEntryCommand] = [], original: Optional[list[str]] = None, prefix = str()): - self.commands = commands # list of the different spec commands - self.segmentName = str() # the name of the current segment - self.prefix = prefix # data between two commands - self.suffix = str() # remaining data after the entry (used for the last entry) - self.contentSuffix = str() # remaining data after the last command in the current entry + def __init__(self, commands: list[SpecEntryCommand] = [], original: Optional[list[str]] = None, prefix=str()): + self.commands = commands # list of the different spec commands + self.segmentName = str() # the name of the current segment + self.prefix = prefix # data between two commands + self.suffix = str() # remaining data after the entry (used for the last entry) + self.contentSuffix = str() # remaining data after the last command in the current entry if original is not None: global buildDirectory @@ -298,7 +298,9 @@ def editSpec(exporter: "SceneExport"): ) if exporter.roomIndexHasOcclusion[i]: - roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"')) + roomCmds.append( + SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"') + ) roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) specFile.append(SpecEntry(roomCmds)) diff --git a/fast64_internal/oot/exporter/file.py b/fast64_internal/oot/exporter/file.py index e15e396c2..f4fbe7fe1 100644 --- a/fast64_internal/oot/exporter/file.py +++ b/fast64_internal/oot/exporter/file.py @@ -46,7 +46,7 @@ class SceneFile: def hasCutscenes(self): return len(self.sceneCutscenes) > 0 - + def hasSceneTextures(self): return len(self.sceneTextures) > 0 diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 0884c84cd..433659b3b 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -205,6 +205,7 @@ def getC(self): return actorList + @dataclass class RoomHeader: """This class defines a room header""" diff --git a/fast64_internal/oot/exporter/utility.py b/fast64_internal/oot/exporter/utility.py index 29e4ad076..16a76590f 100644 --- a/fast64_internal/oot/exporter/utility.py +++ b/fast64_internal/oot/exporter/utility.py @@ -93,4 +93,3 @@ def getEndCmd(): """Returns the scene end command""" return indent + "SCENE_CMD_END(),\n" - From c6a828ea5ae9d1ea673f861cc8ffd4806762520a Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:18:46 +0200 Subject: [PATCH 85/98] fix issues --- .../oot/exporter/decomp_edit/scene_table.py | 4 +- .../oot/exporter/decomp_edit/spec.py | 43 ++++++++++--------- fast64_internal/oot/exporter/file.py | 8 +++- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index f194beb0e..38e49adea 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -120,8 +120,6 @@ def __post_init__(self): entryIndex = 0 # we don't use ``enumerate`` since not every line is an actual entry assert len(lines) > 0 for line in lines: - line = line.strip() - # skip the lines before an entry, create one from the file's data # and add the skipped lines as a prefix of the current entry if ( @@ -130,7 +128,7 @@ def __post_init__(self): and "//" not in line # single line comments and "/**" not in line # multi-line comments and line != "\n" - and line != "" + and line.strip() != "" ): entry = SceneTableEntry(entryIndex, line, prefix=prefix) self.entries.append(entry) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 5f99e27ce..6c07aeb50 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile, indent -from ...oot_utility import getSceneDirFromLevelName +from ...oot_utility import ExportInfo, getSceneDirFromLevelName if TYPE_CHECKING: from ..main import SceneExport @@ -29,6 +29,8 @@ class CommandType(enum.Enum): INCLUDE_DATA_WITH_RODATA = 8 NUMBER = 9 PAD_TEXT = 10 + INCLUDE_DATA_ONLY_WITHIN_RODATA = 11 + INCLUDE_NO_DATA = 12 @staticmethod def from_string(value: str): @@ -53,21 +55,23 @@ def to_c(self): return self.prefix + indent + f"{self.type.name.lower()} {self.content}".strip() + self.suffix + "\n" +@dataclass class SpecEntry: """Defines an entry of ``spec``""" - def __init__(self, commands: list[SpecEntryCommand] = [], original: Optional[list[str]] = None, prefix=str()): - self.commands = commands # list of the different spec commands - self.segmentName = str() # the name of the current segment - self.prefix = prefix # data between two commands - self.suffix = str() # remaining data after the entry (used for the last entry) - self.contentSuffix = str() # remaining data after the last command in the current entry + original: Optional[list[str]] = field(default_factory=list) # the original lines from the parsed file + commands: list[SpecEntryCommand] = field(default_factory=list) # list of the different spec commands + segmentName: str = "" # the name of the current segment + prefix: str = "" # data between two commands + suffix: str = "" # remaining data after the entry (used for the last entry) + contentSuffix: str = "" # remaining data after the last command in the current entry - if original is not None: + def __post_init__(self): + if self.original is not None: global buildDirectory # parse the commands from the existing data prefix = "" - for line in original: + for line in self.original: line = line.strip() dontHaveComments = ( not line.startswith("// ") and not line.startswith("/* ") and not line.startswith(" */") @@ -122,15 +126,17 @@ def to_c(self): ) +@dataclass class SpecFile: """This class defines the spec's file data""" - def __init__(self, exportPath: str): - self.entries: list[SpecEntry] = [] # list of the different spec entries + exportPath: str # path to the spec file + entries: list[SpecEntry] = field(default_factory=list) # list of the different spec entries + def __post_init__(self): # read the file's data try: - with open(exportPath, "r") as fileData: + with open(self.exportPath, "r") as fileData: lines = fileData.readlines() except FileNotFoundError: raise PluginError("ERROR: Can't find spec!") @@ -155,7 +161,7 @@ def __init__(self, exportPath: str): parsedLines.append(line) elif "endseg" in line: # else, if the line has endseg in it (> if we reached the end of the current segment) - entry = SpecEntry(original=parsedLines, prefix=prefix) + entry = SpecEntry(parsedLines, prefix=prefix) self.entries.append(entry) prefix = "" parsedLines = [] @@ -194,7 +200,7 @@ def remove(self, segmentName: str): if entry is not None: if len(entry.prefix) > 0 and entry.prefix != "\n": lastEntry = self.entries[self.entries.index(entry) - 1] - lastEntry.suffix = (lastEntry.suffix if lastEntry.suffix is not None else "") + entry.prefix[:-2] + lastEntry.suffix = (lastEntry.suffix if lastEntry.suffix is not None else "") + entry.prefix[:-1] self.entries.remove(entry) def to_c(self): @@ -274,7 +280,7 @@ def editSpec(exporter: "SceneExport"): ) sceneCmds.append(SpecEntryCommand(CommandType.NUMBER, "2")) - specFile.append(SpecEntry(sceneCmds)) + specFile.append(SpecEntry(None, sceneCmds)) # rooms for i in range(roomTotal): @@ -297,13 +303,8 @@ def editSpec(exporter: "SceneExport"): ] ) - if exporter.roomIndexHasOcclusion[i]: - roomCmds.append( - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"') - ) - roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) - specFile.append(SpecEntry(roomCmds)) + specFile.append(SpecEntry(None, roomCmds)) specFile.entries[-1].suffix = "\n" # finally, write the spec file diff --git a/fast64_internal/oot/exporter/file.py b/fast64_internal/oot/exporter/file.py index f4fbe7fe1..892702cd3 100644 --- a/fast64_internal/oot/exporter/file.py +++ b/fast64_internal/oot/exporter/file.py @@ -75,8 +75,11 @@ def setIncludeData(self): ) if not self.singleFileExport: - self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) self.sceneCollision = self.getSourceWithSceneInclude(sceneInclude, self.sceneCollision) + + if self.hasSceneTextures(): + self.sceneTextures = self.getSourceWithSceneInclude(sceneInclude, self.sceneTextures) + if self.hasCutscenes(): for i in range(len(self.sceneCutscenes)): self.sceneCutscenes[i] = self.getSourceWithSceneInclude(csInclude, self.sceneCutscenes[i]) @@ -91,9 +94,12 @@ def write(self): if self.singleFileExport: sceneMainPath = f"{self.name}.c" + if self.hasCutscenes(): self.sceneMain += "".join(cs for cs in self.sceneCutscenes) + self.sceneMain += self.sceneCollision + if self.hasSceneTextures(): self.sceneMain += self.sceneTextures else: From 3ebda98bf1df2bfc04637140906331c78c05bc69 Mon Sep 17 00:00:00 2001 From: kurethedead Date: Sat, 20 Jul 2024 23:18:14 -0700 Subject: [PATCH 86/98] Refactor spec.py --- .../oot/exporter/decomp_edit/spec.py | 371 +++++++++--------- 1 file changed, 177 insertions(+), 194 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 6c07aeb50..2dddc0e9a 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -1,219 +1,202 @@ import os import bpy import enum +import re from dataclasses import dataclass, field from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile, indent from ...oot_utility import ExportInfo, getSceneDirFromLevelName +from collections import OrderedDict if TYPE_CHECKING: from ..main import SceneExport - - -# either "$(BUILD_DIR)", "$(BUILD)" or "build" -buildDirectory = None - - -class CommandType(enum.Enum): - """This class defines the different spec command types""" - - NAME = 0 - COMPRESS = 1 - AFTER = 2 - FLAGS = 3 - ALIGN = 4 - ADDRESS = 5 - ROMALIGN = 6 - INCLUDE = 7 - INCLUDE_DATA_WITH_RODATA = 8 - NUMBER = 9 - PAD_TEXT = 10 - INCLUDE_DATA_ONLY_WITHIN_RODATA = 11 - INCLUDE_NO_DATA = 12 - - @staticmethod - def from_string(value: str): - """Returns one of the enum values from a string""" - - cmdType = CommandType._member_map_.get(value.upper()) - if cmdType is None: - raise PluginError(f"ERROR: Can't find value: ``{value}`` in the enum!") - return cmdType - - + @dataclass -class SpecEntryCommand: +class SpecCommand: """This class defines a single spec command""" - type: CommandType - content: str = "" - prefix: str = "" - suffix: str = "" + type: str + content : str = "" + comment : str = "" def to_c(self): - return self.prefix + indent + f"{self.type.name.lower()} {self.content}".strip() + self.suffix + "\n" - + comment = f" //{self.comment}" if self.comment != "" else "" + + # Note: This is a hacky way of handling internal preprocessor directives, which would be parsed as if they were commands. + # This is fine as long you are not trying to modify this spec entry, and currently there are no internal preprocessor directives + # in scene segments anyway. + + indent_string = indent if not self.type.startswith('#') else '' + content = f" {self.content.strip()}" if self.content != "" else "" + return f"{indent_string}{self.type}{content}{comment}\n" @dataclass class SpecEntry: """Defines an entry of ``spec``""" - original: Optional[list[str]] = field(default_factory=list) # the original lines from the parsed file - commands: list[SpecEntryCommand] = field(default_factory=list) # list of the different spec commands - segmentName: str = "" # the name of the current segment - prefix: str = "" # data between two commands - suffix: str = "" # remaining data after the entry (used for the last entry) - contentSuffix: str = "" # remaining data after the last command in the current entry - - def __post_init__(self): - if self.original is not None: - global buildDirectory - # parse the commands from the existing data - prefix = "" - for line in self.original: - line = line.strip() - dontHaveComments = ( - not line.startswith("// ") and not line.startswith("/* ") and not line.startswith(" */") - ) - - if line != "\n": - if not line.startswith("#") and dontHaveComments: - split = line.split(" ") - command = split[0] - if len(split) > 2: - content = " ".join(elem for i, elem in enumerate(split) if i > 0) - elif len(split) > 1: - content = split[1] - elif command == "name": - content = self.segmentName - else: - content = "" - - if buildDirectory is None and (content.startswith('"build') or content.startswith('"$(BUILD')): - buildDirectory = content.split("/")[0].removeprefix('"') - - self.commands.append( - SpecEntryCommand( - CommandType.from_string(command), - content, - (prefix + ("\n" if len(prefix) > 0 else "")) if prefix != "\n" else "", - ) - ) - prefix = "" - else: - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" - prefix += (f"\n{indent}" if not dontHaveComments else "") + line - # if there's a prefix it's the remaining data after the last entry - if len(prefix) > 0: - self.contentSuffix = prefix - - if len(self.segmentName) == 0 and len(self.commands[0].content) > 0: - self.segmentName = self.commands[0].content - else: - raise PluginError("ERROR: The segment name can't be set!") + commands: list[SpecCommand] + + @staticmethod + def new(original : list[str]): + commands: list[SpecCommand] = [] + for line in original: + comment = "" + if '//' in line: + comment = line[line.index('//') + len("//") : ] + line = line[:line.index('//')].strip() + split = line.split(" ") + commands.append(SpecCommand(split[0], " ".join(split[1:]) if len(split) > 1 else "", comment)) + + return SpecEntry(commands) + + def get_name(self) -> Optional[str]: + """Returns segment name""" + for command in self.commands: + if command.type == "name": + return command.content + return "" def to_c(self): return ( - (self.prefix if len(self.prefix) > 0 else "\n") - + "beginseg\n" - + "".join(cmd.to_c() for cmd in self.commands) - + (f"{self.contentSuffix}\n" if len(self.contentSuffix) > 0 else "") + "beginseg\n" + + "".join(command.to_c() for command in self.commands) + "endseg" - + (self.suffix if self.suffix == "\n" else f"\n{self.suffix}\n" if len(self.suffix) > 0 else "") ) - + +@dataclass +class SpecSection: + """Defines an 'section' of ``spec``, which is a list of segment definitions that are optionally surrounded by a preprocessor directive""" + + directive : Optional[str] + entries : list[SpecEntry] = field(default_factory=list) + + def to_c(self): + directive = f"{self.directive}\n" if self.directive else "" + terminator = "\n#endif" if self.directive and self.directive.startswith("#if") else "" + entry_string = '\n\n'.join(entry.to_c() for entry in self.entries) + return f"{directive}{entry_string}{terminator}\n\n" @dataclass class SpecFile: """This class defines the spec's file data""" - exportPath: str # path to the spec file - entries: list[SpecEntry] = field(default_factory=list) # list of the different spec entries + header : str # contents of file before scene segment definitions + build_directory : Optional[str] + sections: list[SpecSection] = field(default_factory=list) # list of the different spec entries - def __post_init__(self): + @staticmethod + def new(export_path : str): # read the file's data + data = "" try: - with open(self.exportPath, "r") as fileData: - lines = fileData.readlines() + with open(export_path, "r") as file_data: + data = file_data.read() except FileNotFoundError: raise PluginError("ERROR: Can't find spec!") - - prefix = "" - parsedLines = [] - assert len(lines) > 0 - for line in lines: - # if we're inside a spec entry or if the lines between two entries do not contains these characters - # fill the ``parsedLine`` list if it's inside a segment - # when we reach the end of the current segment add a new ``SpecEntry`` to ``self.entries`` - isNotEmptyOrNewline = len(line) > 0 and line != "\n" - if ( - len(parsedLines) > 0 - or not line.startswith(" *") - and "/*\n" not in line - and not line.startswith("#") - and isNotEmptyOrNewline - ): - if "beginseg" not in line and "endseg" not in line: - # if inside a segment, between beginseg and endseg - parsedLines.append(line) - elif "endseg" in line: - # else, if the line has endseg in it (> if we reached the end of the current segment) - entry = SpecEntry(parsedLines, prefix=prefix) - self.entries.append(entry) - prefix = "" - parsedLines = [] + + # Find first instance of "/assets/scenes/", indicating a scene file + first_scene_include_index = data.index("/assets/scenes/") + if first_scene_include_index == -1: + return SpecFile(data, None, []) # No scene files found - add to end + + # Get build directory, which is text right before /assets/scenes/... + build_directory = None + for dir in ["$(BUILD_DIR)", "build"]: + if data[:first_scene_include_index].endswith(dir): + build_directory = dir + + # Go backwards up to previous "endseg" definition + try: + header_endseg_index = data[:first_scene_include_index].rfind("endseg") + except ValueError: + raise PluginError("endseg not found, scene segements cannot be the first segments in spec file") + + header = data[:header_endseg_index + len("endseg")] + + # This technically includes data after scene segments + # However, as long as we don't have to modify them, they should be fine + lines = data[header_endseg_index + len("endseg"):].split("\n") + lines = list(filter(None, lines)) # removes empty lines + lines = [line.strip() for line in lines] + + sections : list[SpecSection] = [] + current_section : Optional[SpecSection] = None + while len(lines) > 0: + line = lines.pop(0) + if line.startswith("#if"): + if current_section: # handles non-directive section preceding directive section + sections.append(current_section) + current_section = SpecSection(line) + elif line.startswith("#endif"): + sections.append(current_section) + current_section = None # handles back-to-back directive sections + elif line.startswith("beginseg"): + if not current_section: + current_section = SpecSection(None) + segment_lines = [] + while len(lines) > 0 and not lines[0].startswith("endseg"): + next_line = lines.pop(0) + segment_lines.append(next_line) + if len(lines) == 0: + raise PluginError("In spec file, a beginseg was found unterminated.") + lines.pop(0) # remove endseg line + current_section.entries.append(SpecEntry.new(segment_lines)) else: - # else, if between 2 segments and the line is something we don't need - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" - prefix += line - # set the last's entry's suffix to the remaining prefix - self.entries[-1].suffix = prefix.removesuffix("\n") - - def find(self, segmentName: str): + # This code should ignore any other line, including comments. + pass + + + if current_section: + sections.append(current_section) # add last section if non-directive + + return SpecFile(header, build_directory, sections) + + def get_entries_flattened(self): + ''' + Returns all entries as a single array, without sections. + This is a copy of the data and modifying this will not change the spec file internally. + ''' + return [entry for section in self.sections for entry in section.entries] + + def find(self, segment_name: str) -> SpecEntry: """Returns an entry from a segment name, returns ``None`` if nothing was found""" - - for i, entry in enumerate(self.entries): - if entry.segmentName == segmentName: - return self.entries[i] + + for entry in self.get_entries_flattened(): + if entry.get_name() == segment_name: + return entry return None def append(self, entry: SpecEntry): """Appends an entry to the list""" + + if len(self.sections) > 0 and self.sections[-1].directive is None: + self.sections[-1].entries.append(entry) + else: + section = SpecSection(None, [entry]) + self.sections.append(section) + - # prefix/suffix shenanigans - lastEntry = self.entries[-1] - if len(lastEntry.suffix) > 0: - entry.prefix = f"{lastEntry.suffix}\n\n" - lastEntry.suffix = "" - self.entries.append(entry) - - def remove(self, segmentName: str): + def remove(self, segment_name: str): """Removes an entry from a segment name""" - - # prefix/suffix shenanigans - entry = self.find(segmentName) - if entry is not None: - if len(entry.prefix) > 0 and entry.prefix != "\n": - lastEntry = self.entries[self.entries.index(entry) - 1] - lastEntry.suffix = (lastEntry.suffix if lastEntry.suffix is not None else "") + entry.prefix[:-1] - self.entries.remove(entry) + for i in range(len(self.sections)): + section = self.sections[i] + for j in range(len(section.entries)): + entry = section.entries[j] + if entry.get_name() == segment_name: + section.entries.remove(entry) + if len(section.entries) == 0: + self.sections.remove(section) + return def to_c(self): - return "\n".join(entry.to_c() for entry in self.entries) - + return f"{self.header}\n\n" + "".join(section.to_c() for section in self.sections) class SpecUtility: """This class hosts different functions to edit the spec file""" @staticmethod def editSpec(exporter: "SceneExport"): - global buildDirectory - isScene = True exportInfo = exporter.exportInfo hasSceneTex = exporter.hasSceneTextures @@ -227,7 +210,9 @@ def editSpec(exporter: "SceneExport"): csTotal += len(cs.cutscene.entries) # get the spec's data - specFile = SpecFile(os.path.join(exportInfo.exportPath, "spec")) + exportPath = os.path.join(exportInfo.exportPath, "spec") + specFile = SpecFile.new(exportPath) + build_directory = specFile.build_directory # get the scene and current segment name and remove the scene sceneName = exportInfo.name @@ -236,79 +221,77 @@ def editSpec(exporter: "SceneExport"): # mark the other scene elements to remove (like rooms) segmentsToRemove: list[str] = [] - for entry in specFile.entries: - if entry.segmentName.startswith(f'"{sceneName}_'): - segmentsToRemove.append(entry.segmentName) + for entry in specFile.get_entries_flattened(): + # Note: you cannot do startswith(sceneName), ex. entra vs entra_n + if entry.get_name() == f'"{sceneName}_scene"' or \ + re.match(f'^\"{sceneName}\_room\_[0-9]+\"$', entry.get_name()): + segmentsToRemove.append(entry.get_name()) # remove the segments for segmentName in segmentsToRemove: specFile.remove(segmentName) if isScene: - assert buildDirectory is not None + assert build_directory is not None isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile - includeDir = f"{buildDirectory}/" + includeDir = f"{build_directory}/" if exportInfo.customSubPath is not None: includeDir += f"{exportInfo.customSubPath + sceneName}" else: includeDir += f"{getSceneDirFromLevelName(sceneName)}" sceneCmds = [ - SpecEntryCommand(CommandType.NAME, f'"{sceneSegmentName}"'), - SpecEntryCommand(CommandType.COMPRESS), - SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), + SpecCommand("name", f'"{sceneSegmentName}"'), + SpecCommand("compress", ""), + SpecCommand("romalign", "0x1000"), ] # scene if isSingleFile: - sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}.o"')) + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}.o"')) else: sceneCmds.extend( [ - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_main.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_col.o"'), + SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_main.o"'), + SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_col.o"'), ] ) if hasSceneTex: - sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_tex.o"')) + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_tex.o"')) if hasSceneCS: for i in range(csTotal): sceneCmds.append( - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"') + SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"') ) - sceneCmds.append(SpecEntryCommand(CommandType.NUMBER, "2")) - specFile.append(SpecEntry(None, sceneCmds)) + sceneCmds.append(SpecCommand("number", "2")) + specFile.append(SpecEntry(sceneCmds)) # rooms for i in range(roomTotal): roomSegmentName = f"{sceneName}_room_{i}" roomCmds = [ - SpecEntryCommand(CommandType.NAME, f'"{roomSegmentName}"'), - SpecEntryCommand(CommandType.COMPRESS), - SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), + SpecCommand("name", f'"{roomSegmentName}"'), + SpecCommand("compress"), + SpecCommand("romalign", "0x1000"), ] if isSingleFile: - roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}.o"')) + roomCmds.append(SpecCommand("include", f'"{includeDir}/{roomSegmentName}.o"')) else: roomCmds.extend( [ - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_main.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model_info.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_main.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model_info.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model.o"'), ] ) - roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) - specFile.append(SpecEntry(None, roomCmds)) - specFile.entries[-1].suffix = "\n" + roomCmds.append(SpecCommand("number", "3")) + specFile.append(SpecEntry(roomCmds)) # finally, write the spec file - writeFile(specFile.exportPath, specFile.to_c()) - - # reset build directory name so it can update properly on the next run - buildDirectory = None + writeFile(exportPath, specFile.to_c()) From 0796e3d8ecec1013e02950286160ef068ee28a85 Mon Sep 17 00:00:00 2001 From: kurethedead Date: Sat, 20 Jul 2024 23:43:52 -0700 Subject: [PATCH 87/98] Black formatting spec.py --- .../oot/exporter/decomp_edit/spec.py | 122 +++++++++--------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 2dddc0e9a..ceb3bb33b 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -11,45 +11,47 @@ if TYPE_CHECKING: from ..main import SceneExport - + + @dataclass class SpecCommand: """This class defines a single spec command""" type: str - content : str = "" - comment : str = "" + content: str = "" + comment: str = "" def to_c(self): comment = f" //{self.comment}" if self.comment != "" else "" - + # Note: This is a hacky way of handling internal preprocessor directives, which would be parsed as if they were commands. # This is fine as long you are not trying to modify this spec entry, and currently there are no internal preprocessor directives # in scene segments anyway. - - indent_string = indent if not self.type.startswith('#') else '' + + indent_string = indent if not self.type.startswith("#") else "" content = f" {self.content.strip()}" if self.content != "" else "" return f"{indent_string}{self.type}{content}{comment}\n" + @dataclass class SpecEntry: """Defines an entry of ``spec``""" commands: list[SpecCommand] - + @staticmethod - def new(original : list[str]): + def new(original: list[str]): commands: list[SpecCommand] = [] for line in original: comment = "" - if '//' in line: - comment = line[line.index('//') + len("//") : ] - line = line[:line.index('//')].strip() + if "//" in line: + comment = line[line.index("//") + len("//") :] + line = line[: line.index("//")].strip() split = line.split(" ") commands.append(SpecCommand(split[0], " ".join(split[1:]) if len(split) > 1 else "", comment)) - + return SpecEntry(commands) - + def get_name(self) -> Optional[str]: """Returns segment name""" for command in self.commands: @@ -58,35 +60,33 @@ def get_name(self) -> Optional[str]: return "" def to_c(self): - return ( - "beginseg\n" - + "".join(command.to_c() for command in self.commands) - + "endseg" - ) - + return "beginseg\n" + "".join(command.to_c() for command in self.commands) + "endseg" + + @dataclass class SpecSection: """Defines an 'section' of ``spec``, which is a list of segment definitions that are optionally surrounded by a preprocessor directive""" - - directive : Optional[str] - entries : list[SpecEntry] = field(default_factory=list) - + + directive: Optional[str] + entries: list[SpecEntry] = field(default_factory=list) + def to_c(self): directive = f"{self.directive}\n" if self.directive else "" terminator = "\n#endif" if self.directive and self.directive.startswith("#if") else "" - entry_string = '\n\n'.join(entry.to_c() for entry in self.entries) + entry_string = "\n\n".join(entry.to_c() for entry in self.entries) return f"{directive}{entry_string}{terminator}\n\n" + @dataclass class SpecFile: """This class defines the spec's file data""" - header : str # contents of file before scene segment definitions - build_directory : Optional[str] + header: str # contents of file before scene segment definitions + build_directory: Optional[str] sections: list[SpecSection] = field(default_factory=list) # list of the different spec entries @staticmethod - def new(export_path : str): + def new(export_path: str): # read the file's data data = "" try: @@ -94,43 +94,43 @@ def new(export_path : str): data = file_data.read() except FileNotFoundError: raise PluginError("ERROR: Can't find spec!") - + # Find first instance of "/assets/scenes/", indicating a scene file first_scene_include_index = data.index("/assets/scenes/") if first_scene_include_index == -1: - return SpecFile(data, None, []) # No scene files found - add to end - + return SpecFile(data, None, []) # No scene files found - add to end + # Get build directory, which is text right before /assets/scenes/... build_directory = None for dir in ["$(BUILD_DIR)", "build"]: if data[:first_scene_include_index].endswith(dir): build_directory = dir - - # Go backwards up to previous "endseg" definition - try: + + # Go backwards up to previous "endseg" definition + try: header_endseg_index = data[:first_scene_include_index].rfind("endseg") except ValueError: raise PluginError("endseg not found, scene segements cannot be the first segments in spec file") - - header = data[:header_endseg_index + len("endseg")] - + + header = data[: header_endseg_index + len("endseg")] + # This technically includes data after scene segments # However, as long as we don't have to modify them, they should be fine - lines = data[header_endseg_index + len("endseg"):].split("\n") - lines = list(filter(None, lines)) # removes empty lines + lines = data[header_endseg_index + len("endseg") :].split("\n") + lines = list(filter(None, lines)) # removes empty lines lines = [line.strip() for line in lines] - - sections : list[SpecSection] = [] - current_section : Optional[SpecSection] = None + + sections: list[SpecSection] = [] + current_section: Optional[SpecSection] = None while len(lines) > 0: line = lines.pop(0) - if line.startswith("#if"): - if current_section: # handles non-directive section preceding directive section - sections.append(current_section) + if line.startswith("#if"): + if current_section: # handles non-directive section preceding directive section + sections.append(current_section) current_section = SpecSection(line) elif line.startswith("#endif"): sections.append(current_section) - current_section = None # handles back-to-back directive sections + current_section = None # handles back-to-back directive sections elif line.startswith("beginseg"): if not current_section: current_section = SpecSection(None) @@ -140,28 +140,27 @@ def new(export_path : str): segment_lines.append(next_line) if len(lines) == 0: raise PluginError("In spec file, a beginseg was found unterminated.") - lines.pop(0) # remove endseg line + lines.pop(0) # remove endseg line current_section.entries.append(SpecEntry.new(segment_lines)) else: # This code should ignore any other line, including comments. pass - - + if current_section: - sections.append(current_section) # add last section if non-directive - + sections.append(current_section) # add last section if non-directive + return SpecFile(header, build_directory, sections) - + def get_entries_flattened(self): - ''' - Returns all entries as a single array, without sections. + """ + Returns all entries as a single array, without sections. This is a copy of the data and modifying this will not change the spec file internally. - ''' + """ return [entry for section in self.sections for entry in section.entries] def find(self, segment_name: str) -> SpecEntry: """Returns an entry from a segment name, returns ``None`` if nothing was found""" - + for entry in self.get_entries_flattened(): if entry.get_name() == segment_name: return entry @@ -169,13 +168,12 @@ def find(self, segment_name: str) -> SpecEntry: def append(self, entry: SpecEntry): """Appends an entry to the list""" - + if len(self.sections) > 0 and self.sections[-1].directive is None: self.sections[-1].entries.append(entry) else: section = SpecSection(None, [entry]) self.sections.append(section) - def remove(self, segment_name: str): """Removes an entry from a segment name""" @@ -192,6 +190,7 @@ def remove(self, segment_name: str): def to_c(self): return f"{self.header}\n\n" + "".join(section.to_c() for section in self.sections) + class SpecUtility: """This class hosts different functions to edit the spec file""" @@ -223,8 +222,9 @@ def editSpec(exporter: "SceneExport"): segmentsToRemove: list[str] = [] for entry in specFile.get_entries_flattened(): # Note: you cannot do startswith(sceneName), ex. entra vs entra_n - if entry.get_name() == f'"{sceneName}_scene"' or \ - re.match(f'^\"{sceneName}\_room\_[0-9]+\"$', entry.get_name()): + if entry.get_name() == f'"{sceneName}_scene"' or re.match( + f'^"{sceneName}\_room\_[0-9]+"$', entry.get_name() + ): segmentsToRemove.append(entry.get_name()) # remove the segments @@ -262,9 +262,7 @@ def editSpec(exporter: "SceneExport"): if hasSceneCS: for i in range(csTotal): - sceneCmds.append( - SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"') - ) + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"')) sceneCmds.append(SpecCommand("number", "2")) specFile.append(SpecEntry(sceneCmds)) From 179f23401f12f5cfd81010d291d2cef20756dc5c Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:14:48 +0200 Subject: [PATCH 88/98] review changes part 4 + fixed scene table issues --- .../oot/exporter/decomp_edit/__init__.py | 2 +- .../oot/exporter/decomp_edit/scene_table.py | 157 ++++++++---------- .../oot/exporter/decomp_edit/spec.py | 4 +- 3 files changed, 71 insertions(+), 92 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index 910355623..d5b4de7c4 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -34,4 +34,4 @@ def editFiles(exporter: "SceneExport"): Files.modifySceneFiles(exporter) SpecUtility.editSpec(exporter) - SceneTableUtility.editSceneTable(exporter) + SceneTableUtility.editSceneTable(exporter, exporter.exportInfo) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index 38e49adea..ce76c0770 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -5,6 +5,7 @@ from dataclasses import dataclass, field from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile +from ...oot_utility import ExportInfo from ...oot_constants import ootEnumSceneID, ootSceneNameToID if TYPE_CHECKING: @@ -26,68 +27,54 @@ class SceneIndexType(enum.IntEnum): class SceneTableEntry: """Defines an entry of ``scene_table.h``""" - index: int - original: Optional[str] # the original line from the parsed file - exporter: Optional["SceneExport"] = None - exportName: Optional[str] = None - isCustomScene: bool = False - prefix: Optional[str] = None # ifdefs, endifs, comments etc, everything before the current entry - suffix: Optional[str] = None # remaining data after the last entry - parsed: Optional[str] = None - # macro parameters - specName: Optional[str] = None # name of the scene segment in spec - titleCardName: Optional[str] = None # name of the title card segment in spec, or `none` for no title card - enumValue: Optional[str] = None # enum value for this scene - drawConfigIdx: Optional[str] = None # scene draw config index - unk1: Optional[str] = None - unk2: Optional[str] = None + specName: str # name of the scene segment in spec + titleCardName: str # name of the title card segment in spec, or `none` for no title card + enumValue: str # enum value for this scene + drawConfigIdx: str # scene draw config index + unk1: str + unk2: str - def __post_init__(self): - # parse the entry parameters from file data or an ``OOTScene`` - macroStart = "DEFINE_SCENE(" - if self.original is not None and macroStart in self.original: + prefix: str = str() # ifdefs, endifs, comments etc, everything before the current entry + suffix: str = str() # remaining data after the last entry + + @staticmethod + def from_line(original_line: str, prefix: str): + macro_start = "DEFINE_SCENE(" + + if macro_start in original_line: # remove the index and the macro's name with the parenthesis - index = self.original.index(macroStart) + len(macroStart) - self.parsed = self.original[index:].removesuffix(")\n") - - parameters = self.parsed.split(", ") - assert len(parameters) == 6 - self.setParameters(*parameters) - elif self.exporter is not None: - self.setParametersFromScene() - - def setParameters( - self, specName: str, titleCardName: str, enumValue: str, drawConfigIdx: str, unk1: str = "0", unk2: str = "0" - ): - """Sets the entry's parameters""" - self.specName = specName - self.titleCardName = titleCardName - self.enumValue = enumValue - self.drawConfigIdx = drawConfigIdx - self.unk1 = unk1 - self.unk2 = unk2 - - def setParametersFromScene(self, exporter: Optional["SceneExport"] = None): - """Use the ``OOTScene`` data to set the entry's parameters""" - exporter = self.exporter if exporter is None else exporter + index = original_line.index(macro_start) + len(macro_start) + parsed = original_line[index:].removesuffix(")\n") + + params = parsed.split(", ") + assert len(params) == 6 + + return SceneTableEntry(*params, prefix) + else: + raise PluginError("ERROR: This line is not a scene table entry!") + + @staticmethod + def from_scene(exporter: "SceneExport", export_name: str, is_custom_scene: bool): # TODO: Implement title cards - name = exporter.scene.name if exporter is not None else self.exportName - self.setParameters( - f"{exporter.scene.name.lower() if self.isCustomScene else exporter.scene.name}_scene", + scene_name = exporter.scene.name.lower() if is_custom_scene else export_name + return SceneTableEntry( + scene_name if scene_name.endswith("_scene") else f"{scene_name}_scene", "none", - ootSceneNameToID.get(name, f"SCENE_{name.upper()}"), + ootSceneNameToID.get(export_name, f"SCENE_{export_name.upper()}"), exporter.scene.mainHeader.infos.drawConfig, + "0", + "0", ) - def to_c(self): + def to_c(self, index: int): """Returns the entry as C code""" return ( - (self.prefix if self.prefix is not None else "") - + f"/* 0x{self.index:02X} */ " + self.prefix + + f"/* 0x{index:02X} */ " + f"DEFINE_SCENE({self.specName}, {self.titleCardName}, {self.enumValue}, " + f"{self.drawConfigIdx}, {self.unk1}, {self.unk2})\n" - + (self.suffix if self.suffix is not None else "") + + self.suffix ) @@ -117,8 +104,9 @@ def __post_init__(self): # parse the entries and populate the list of entries (``self.entries``) prefix = "" self.isFirstCustom = ADDED_SCENES_COMMENT not in data - entryIndex = 0 # we don't use ``enumerate`` since not every line is an actual entry + assert len(lines) > 0 + for line in lines: # skip the lines before an entry, create one from the file's data # and add the skipped lines as a prefix of the current entry @@ -130,15 +118,11 @@ def __post_init__(self): and line != "\n" and line.strip() != "" ): - entry = SceneTableEntry(entryIndex, line, prefix=prefix) + entry = SceneTableEntry.from_line(line, prefix) self.entries.append(entry) self.sceneEnumValues.append(entry.enumValue) prefix = "" - entryIndex += 1 else: - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" prefix += line # add whatever's after the last entry @@ -156,7 +140,7 @@ def __post_init__(self): if self.selectedSceneIndex == SceneIndexType.CUSTOM: entry = self.entryBySpecName.get(f"{self.exportName}_scene") if entry is not None: - self.customSceneIndex = entry.index + self.customSceneIndex = self.entries.index(entry) def getIndexFromEnumValue(self): """Returns the index (int) of the chosen scene if vanilla and found, else return an enum value from ``SceneIndexType``""" @@ -203,13 +187,7 @@ def getInsertionIndex(self, index: Optional[int] = None) -> int: # if the index hasn't been found yet, do it again but decrement the index return self.getInsertionIndex(currentIndex - 1) - def updateEntryIndex(self): - """Updates every entry index so they follow each other""" - for i, entry in enumerate(self.entries): - if entry.index != i: - entry.index = i - - def getIndex(self): + def getIndex(self) -> int: """Returns the selected scene index if it's a vanilla one, else returns the custom scene index""" assert self.selectedSceneIndex != SceneIndexType.VANILLA_REMOVED @@ -219,7 +197,7 @@ def getIndex(self): return self.selectedSceneIndex if self.selectedSceneIndex >= 0 else self.customSceneIndex - def append(self, entry: SceneTableEntry): + def append(self, entry: SceneTableEntry, index: int): """Appends an entry to the scene table, only used by custom scenes""" # add the "added scenes" comment if it's not already there if self.isFirstCustom: @@ -227,29 +205,29 @@ def append(self, entry: SceneTableEntry): self.isFirstCustom = False if entry not in self.entries: - if entry.index >= 0: - self.customSceneIndex = entry.index + if index >= 0: + self.customSceneIndex = index self.entries.append(entry) else: - raise PluginError(f"ERROR: (Append) The index is not valid! ({entry.index})") + raise PluginError(f"ERROR: (Append) The index is not valid! ({index})") else: raise PluginError("ERROR: (Append) Entry already in the table!") - def insert(self, entry: SceneTableEntry): + def insert(self, entry: SceneTableEntry, index: int): """Inserts an entry in the scene table, only used by non-custom scenes""" if not entry in self.entries: - if entry.index >= 0: - if entry.index < len(self.entries): - nextEntry = self.entries[entry.index] # the next entry is at the insertion index + if index >= 0: + if index < len(self.entries): + nextEntry = self.entries[index] # the next entry is at the insertion index # move the next entry's prefix to the one we're going to insert if len(nextEntry.prefix) > 0 and not "INCLUDE_TEST_SCENES" in nextEntry.prefix: entry.prefix = nextEntry.prefix nextEntry.prefix = "" - self.entries.insert(entry.index, entry) + self.entries.insert(index, entry) else: - raise PluginError(f"ERROR: (Insert) The index is not valid! ({entry.index})") + raise PluginError(f"ERROR: (Insert) The index is not valid! ({index})") else: raise PluginError("ERROR: (Insert) Entry already in the table!") @@ -257,7 +235,8 @@ def remove(self, index: int): """Removes an entry from the scene table""" isCustom = index == SceneIndexType.CUSTOM if index >= 0 or isCustom: - entry = self.entries[self.getIndex()] + idx = self.getIndex() + entry = self.entries[idx] # move the prefix of the entry to remove to the next entry # if there's no next entry this prefix becomes the suffix of the last entry @@ -266,8 +245,8 @@ def remove(self, index: int): if not isCustom and nextIndex < len(self.entries): self.entries[nextIndex].prefix = entry.prefix else: - previousIndex = entry.index - 1 - if entry.index == len(self.entries) - 1 and ADDED_SCENES_COMMENT in entry.prefix: + previousIndex = idx - 1 + if idx == len(self.entries) - 1 and ADDED_SCENES_COMMENT in entry.prefix: entry.prefix = entry.prefix.removesuffix(f"\n{ADDED_SCENES_COMMENT}\n") self.entries[previousIndex].suffix = entry.prefix @@ -279,7 +258,7 @@ def remove(self, index: int): def to_c(self): """Returns the scene table as C code""" - return "".join(entry.to_c() for entry in self.entries) + return "".join(entry.to_c(i) for i, entry in enumerate(self.entries)) class SceneTableUtility: @@ -299,12 +278,12 @@ def getDrawConfig(sceneName: str): raise PluginError(f"ERROR: Scene name {sceneName} not found in scene table.") @staticmethod - def editSceneTable(exporter: Optional["SceneExport"]): + def editSceneTable(exporter: Optional["SceneExport"], exportInfo: ExportInfo): """Remove, append, insert or update the scene table entry of the selected scene""" sceneTable = SceneTable( - os.path.join(exporter.exportInfo.exportPath, "include/tables/scene_table.h"), - exporter.exportInfo.name if exporter.exportInfo.option == "Custom" else None, - exporter.exportInfo.option, + os.path.join(exportInfo.exportPath, "include/tables/scene_table.h"), + exportInfo.name if exportInfo.option == "Custom" else None, + exportInfo.option, ) if exporter is None: @@ -313,19 +292,21 @@ def editSceneTable(exporter: Optional["SceneExport"]): elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: # custom mode: new custom scene sceneTable.append( - SceneTableEntry(len(sceneTable.entries) - 1, None, exporter, exporter.exportInfo.name, True) + SceneTableEntry.from_scene(exporter, exporter.exportInfo.name, True), len(sceneTable.entries) - 1 ) elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: # insert mode sceneTable.insert( - SceneTableEntry(sceneTable.getInsertionIndex(), None, exporter, exporter.exportInfo.name, False) + SceneTableEntry.from_scene(exporter, exporter.exportInfo.name, False), sceneTable.getInsertionIndex() ) else: # update mode (for both vanilla and custom scenes since they already exist in the table) - sceneTable.entries[sceneTable.getIndex()].setParametersFromScene(exporter) - - # update the indices - sceneTable.updateEntryIndex() + index = sceneTable.getIndex() + entry = sceneTable.entries[index] + new_entry = SceneTableEntry.from_scene(exporter, exporter.scene.name, False) + new_entry.prefix = entry.prefix + new_entry.suffix = entry.suffix + sceneTable.entries[index] = new_entry # write the file with the final data writeFile(sceneTable.exportPath, sceneTable.to_c()) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index ceb3bb33b..5c523d0d4 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -1,13 +1,11 @@ import os import bpy -import enum import re from dataclasses import dataclass, field from typing import Optional, TYPE_CHECKING from ....utility import PluginError, writeFile, indent -from ...oot_utility import ExportInfo, getSceneDirFromLevelName -from collections import OrderedDict +from ...oot_utility import getSceneDirFromLevelName if TYPE_CHECKING: from ..main import SceneExport From 5f934d565e97eb9edac0c12e2f3df075e2e4c8f5 Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Sun, 21 Jul 2024 18:58:34 +0200 Subject: [PATCH 89/98] raise an error if the insertion index was not found --- fast64_internal/oot/exporter/decomp_edit/scene_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index ce76c0770..c4845ea44 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -184,8 +184,8 @@ def getInsertionIndex(self, index: Optional[int] = None) -> int: if self.sceneEnumValues[i] == ootEnumSceneID[currentIndex][0]: return i + 1 - # if the index hasn't been found yet, do it again but decrement the index - return self.getInsertionIndex(currentIndex - 1) + # if the index hasn't been found yet, throw an error + raise PluginError("ERROR: the insertion index was not found") def getIndex(self) -> int: """Returns the selected scene index if it's a vanilla one, else returns the custom scene index""" From 16d9d8be860efd884959eee3e3cea0790a758c63 Mon Sep 17 00:00:00 2001 From: kurethedead Date: Mon, 22 Jul 2024 18:38:37 -0700 Subject: [PATCH 90/98] Refactor scene_table.py --- .../oot/exporter/decomp_edit/__init__.py | 9 +- .../oot/exporter/decomp_edit/scene_table.py | 389 ++++++++---------- .../oot/exporter/decomp_edit/spec.py | 6 +- 3 files changed, 193 insertions(+), 211 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index d5b4de7c4..8de592357 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -33,5 +33,10 @@ def editFiles(exporter: "SceneExport"): """Edits decomp files""" Files.modifySceneFiles(exporter) - SpecUtility.editSpec(exporter) - SceneTableUtility.editSceneTable(exporter, exporter.exportInfo) + SpecUtility.edit_spec(exporter) + SceneTableUtility.edit_scene_table( + exporter.exportInfo.exportPath, + exporter.exportInfo.name, + exporter.exportInfo.option, + exporter.scene.mainHeader.infos.drawConfig, + ) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index c4845ea44..50a8e6ae9 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -15,12 +15,16 @@ ADDED_SCENES_COMMENT = "// Added scenes" -class SceneIndexType(enum.IntEnum): - """Used to figure out the value of ``selectedSceneIndex``""" - - # this is using negative numbers since this is used as a return type if the scene index wasn't found - CUSTOM = -1 # custom scene - VANILLA_REMOVED = -2 # vanilla scene that was removed, this is to know if it should insert an entry +def get_original_index(enum_value: str) -> Optional[int]: + """ + Returns the original index of a specific scene + """ + for index, scene_enum in enumerate( + [elem[0] for elem in ootEnumSceneID[1:]] + ): # ignore first value in array ('Custom') + if scene_enum == enum_value: + return index + return None @dataclass @@ -28,41 +32,37 @@ class SceneTableEntry: """Defines an entry of ``scene_table.h``""" # macro parameters - specName: str # name of the scene segment in spec - titleCardName: str # name of the title card segment in spec, or `none` for no title card - enumValue: str # enum value for this scene - drawConfigIdx: str # scene draw config index + spec_name: str # name of the scene segment in spec + title_card_name: str # name of the title card segment in spec, or `none` for no title card + enum_value: str # enum value for this scene + draw_config: str # scene draw config index unk1: str unk2: str - prefix: str = str() # ifdefs, endifs, comments etc, everything before the current entry - suffix: str = str() # remaining data after the last entry - @staticmethod - def from_line(original_line: str, prefix: str): + def from_line(original_line: str): macro_start = "DEFINE_SCENE(" if macro_start in original_line: # remove the index and the macro's name with the parenthesis index = original_line.index(macro_start) + len(macro_start) - parsed = original_line[index:].removesuffix(")\n") + parsed = original_line[index:].removesuffix(")") params = parsed.split(", ") assert len(params) == 6 - return SceneTableEntry(*params, prefix) + return SceneTableEntry(*params) else: raise PluginError("ERROR: This line is not a scene table entry!") @staticmethod - def from_scene(exporter: "SceneExport", export_name: str, is_custom_scene: bool): + def from_scene(scene_name: str, draw_config: str): # TODO: Implement title cards - scene_name = exporter.scene.name.lower() if is_custom_scene else export_name return SceneTableEntry( scene_name if scene_name.endswith("_scene") else f"{scene_name}_scene", "none", - ootSceneNameToID.get(export_name, f"SCENE_{export_name.upper()}"), - exporter.scene.mainHeader.infos.drawConfig, + ootSceneNameToID.get(scene_name, f"SCENE_{scene_name.upper()}"), + draw_config, "0", "0", ) @@ -70,243 +70,220 @@ def from_scene(exporter: "SceneExport", export_name: str, is_custom_scene: bool) def to_c(self, index: int): """Returns the entry as C code""" return ( - self.prefix - + f"/* 0x{index:02X} */ " - + f"DEFINE_SCENE({self.specName}, {self.titleCardName}, {self.enumValue}, " - + f"{self.drawConfigIdx}, {self.unk1}, {self.unk2})\n" - + self.suffix + f"/* 0x{index:02X} */ " + f"DEFINE_SCENE({self.spec_name}, {self.title_card_name}, {self.enum_value}, " + f"{self.draw_config}, {self.unk1}, {self.unk2})" ) +@dataclass +class SceneTableSection: + """Defines a section of the scene table, with is a list of entires with an optional preprocessor directive / comment""" + + directive: Optional[str] # can also be a comment starting with // + entries: list[SceneTableEntry] = field(default_factory=list) + + def to_c(self, index: int): + directive = f"{self.directive}\n" if self.directive else "" + terminator = "\n#endif" if self.directive and self.directive.startswith("#if") else "" + entry_string = "\n".join(entry.to_c(index + i) for i, entry in enumerate(self.entries)) + return f"{directive}{entry_string}{terminator}\n\n" + + @dataclass class SceneTable: """Defines a ``scene_table.h`` file data""" - exportPath: str - exportName: Optional[str] - selectedSceneEnumValue: Optional[str] - entries: list[SceneTableEntry] = field(default_factory=list) - sceneEnumValues: list[str] = field(default_factory=list) # existing values in ``scene_table.h`` - isFirstCustom: bool = False # if true, adds the "Added Scenes" comment to the C data - selectedSceneIndex: int = 0 - customSceneIndex: Optional[int] = None # None if the selected custom scene isn't in the table yet + header: str + sections: list[SceneTableSection] = field(default_factory=list) - def __post_init__(self): + @staticmethod + def new(export_path: str): # read the file's data try: - with open(self.exportPath) as fileData: - data = fileData.read() - fileData.seek(0) - lines = fileData.readlines() + with open(export_path) as file_data: + data = file_data.read() + file_data.seek(0) + lines = file_data.readlines() except FileNotFoundError: raise PluginError("ERROR: Can't find scene_table.h!") - # parse the entries and populate the list of entries (``self.entries``) - prefix = "" - self.isFirstCustom = ADDED_SCENES_COMMENT not in data - - assert len(lines) > 0 - - for line in lines: - # skip the lines before an entry, create one from the file's data - # and add the skipped lines as a prefix of the current entry - if ( - not line.startswith("#") # ifdefs or endifs - and not line.startswith(" *") # multi-line comments - and "//" not in line # single line comments - and "/**" not in line # multi-line comments - and line != "\n" - and line.strip() != "" - ): - entry = SceneTableEntry.from_line(line, prefix) - self.entries.append(entry) - self.sceneEnumValues.append(entry.enumValue) - prefix = "" + # Find first instance of "DEFINE_SCENE(", indicating a scene define macro + first_macro_index = data.index("DEFINE_SCENE(") + if first_macro_index == -1: + return SceneTable(data, []) # No scene defines found - add to end + + # Go backwards up to previous newline + try: + header_end_index = data[:first_macro_index].rfind("\n") + except ValueError: + header_end_index = 0 + + header = data[: header_end_index + 1] + + lines = data[header_end_index + 1 :].split("\n") + lines = list(filter(None, lines)) # removes empty lines + lines = [line.strip() for line in lines] + + sections: list[SceneTableSection] = [] + current_section: Optional[SceneTableSection] = None + + while len(lines) > 0: + line = lines.pop(0) + if line.startswith("#if"): + if current_section: # handles non-directive section preceding directive section + sections.append(current_section) + current_section = SceneTableSection(line) + elif line.startswith("#endif"): + sections.append(current_section) + current_section = None # handles back-to-back directive sections + elif line.startswith("//"): + if current_section: # handles non-directive section preceding directive section + sections.append(current_section) + current_section = SceneTableSection(line) else: - prefix += line - - # add whatever's after the last entry - if len(prefix) > 0 and prefix != "\n": - self.entries[-1].suffix = prefix - - # get the scene index for the scene chosen by the user - if self.selectedSceneEnumValue is not None: - self.selectedSceneIndex = self.getIndexFromEnumValue() - - # dictionary of entries from spec names - self.entryBySpecName = {entry.specName: entry for entry in self.entries} - - # set the custom scene index - if self.selectedSceneIndex == SceneIndexType.CUSTOM: - entry = self.entryBySpecName.get(f"{self.exportName}_scene") - if entry is not None: - self.customSceneIndex = self.entries.index(entry) - - def getIndexFromEnumValue(self): - """Returns the index (int) of the chosen scene if vanilla and found, else return an enum value from ``SceneIndexType``""" - if self.selectedSceneEnumValue == "Custom": - return SceneIndexType.CUSTOM - for i in range(len(self.sceneEnumValues)): - if self.sceneEnumValues[i] == self.selectedSceneEnumValue: - return i - # if the index is not found and it's not a custom export it means it's a vanilla scene that was removed - return SceneIndexType.VANILLA_REMOVED + if not current_section: + current_section = SceneTableSection(None) + current_section.entries.append(SceneTableEntry.from_line(line)) + + if current_section: + sections.append(current_section) # add last section if non-directive + + return SceneTable(header, sections) - def getOriginalIndex(self): + def get_entries_flattened(self) -> list[SceneTableEntry]: """ - Returns the index of a specific scene defined by which one the user chose - or by the ``sceneName`` parameter if it's not set to ``None`` + Returns all entries as a single array, without sections. + This is a shallow copy of the data and adding/removing from this list not change the scene table internally. """ - i = 0 - if self.selectedSceneEnumValue != "Custom": - for elem in ootEnumSceneID: - if elem[0] == self.selectedSceneEnumValue: - # returns i - 1 because the first entry is the ``Custom`` option - return i - 1 - i += 1 - raise PluginError("ERROR: Scene Index not found!") - - def getInsertionIndex(self, index: Optional[int] = None) -> int: - """Returns the index to know where to insert data""" - # special case where the scene is "Inside the Great Deku Tree" - # since it's the first scene simply return 0 - if self.selectedSceneEnumValue == "SCENE_DEKU_TREE": - return 0 - - # if index is None this means this is looking for ``original_scene_index - 1`` - # else, this means the table is shifted - if index is None: - currentIndex = self.getOriginalIndex() - else: - currentIndex = index - for i in range(len(self.sceneEnumValues)): - if self.sceneEnumValues[i] == ootEnumSceneID[currentIndex][0]: - return i + 1 + return [entry for section in self.sections for entry in section.entries] - # if the index hasn't been found yet, throw an error - raise PluginError("ERROR: the insertion index was not found") + def get_index_from_enum(self, enum_value: str) -> Optional[int]: + """Returns the index (int) of the chosen scene if found, else return ``None``""" - def getIndex(self) -> int: - """Returns the selected scene index if it's a vanilla one, else returns the custom scene index""" - assert self.selectedSceneIndex != SceneIndexType.VANILLA_REMOVED + for i, entry in enumerate(self.get_entries_flattened()): + if entry.enum_value == enum_value: + return i - # this function's usage makes ``customSceneIndex is None`` impossible - if self.selectedSceneIndex < 0 and self.customSceneIndex is None: - raise PluginError("ERROR: Custom Scene Index is None!") + return None - return self.selectedSceneIndex if self.selectedSceneIndex >= 0 else self.customSceneIndex + def set_entry_at_enum(self, entry: SceneTableEntry, enum_value: str): + """Replaces entry in the scene table with the given enum_value""" + for section in self.sections: + for entry_index in range(len(section.entries)): + if section.entries[entry_index].enum_value == enum_value: + section.entries[entry_index] = entry - def append(self, entry: SceneTableEntry, index: int): + def append(self, entry: SceneTableEntry): """Appends an entry to the scene table, only used by custom scenes""" - # add the "added scenes" comment if it's not already there - if self.isFirstCustom: - entry.prefix = f"\n{ADDED_SCENES_COMMENT}\n" - self.isFirstCustom = False - - if entry not in self.entries: - if index >= 0: - self.customSceneIndex = index - self.entries.append(entry) - else: - raise PluginError(f"ERROR: (Append) The index is not valid! ({index})") + + # Find current added scenes comment, or add one if not found + current_section = None + for section in self.sections: + if section.directive == ADDED_SCENES_COMMENT: + current_section = section + break + if current_section is None: + current_section = SceneTableSection(ADDED_SCENES_COMMENT) + self.sections.append(current_section) + + if entry not in current_section.entries: + current_section.entries.append(entry) else: raise PluginError("ERROR: (Append) Entry already in the table!") def insert(self, entry: SceneTableEntry, index: int): """Inserts an entry in the scene table, only used by non-custom scenes""" - if not entry in self.entries: - if index >= 0: - if index < len(self.entries): - nextEntry = self.entries[index] # the next entry is at the insertion index - # move the next entry's prefix to the one we're going to insert - if len(nextEntry.prefix) > 0 and not "INCLUDE_TEST_SCENES" in nextEntry.prefix: - entry.prefix = nextEntry.prefix - nextEntry.prefix = "" + if entry in self.get_entries_flattened(): + raise PluginError("ERROR: (Insert) Entry already in the table!") + if index < 0 or index > len(self.get_entries_flattened()) - 1: + raise PluginError(f"ERROR: (Insert) The index is not valid! ({index})") + + i = 0 + for section in self.sections: + for entry_index in range(len(section.entries)): + if i == index: + section.entries.insert(entry_index, entry) + return + else: + i += 1 + + def update(self, entry: SceneTableEntry, enum_value: str): + """Updates an entry if the enum_value exists in the scene table, otherwise appends/inserts entry depending on if custom or not""" - self.entries.insert(index, entry) + original_index = get_original_index(enum_value) # index in unmodified scene table + current_index = self.get_index_from_enum(enum_value) # index in current scene table + + if current_index is None: # Not in scene table currently + if original_index is not None: + # insert mode - we want to place vanilla scenes into their original locations if previously deleted + self.insert(entry, original_index) else: - raise PluginError(f"ERROR: (Insert) The index is not valid! ({index})") + # this is a custom level, append to end + self.append(entry) else: - raise PluginError("ERROR: (Insert) Entry already in the table!") + # update mode (for both vanilla and custom scenes since they already exist in the table) + self.set_entry_at_enum(entry, enum_value) - def remove(self, index: int): + def remove(self, enum_value: str): """Removes an entry from the scene table""" - isCustom = index == SceneIndexType.CUSTOM - if index >= 0 or isCustom: - idx = self.getIndex() - entry = self.entries[idx] - - # move the prefix of the entry to remove to the next entry - # if there's no next entry this prefix becomes the suffix of the last entry - if len(entry.prefix) > 0: - nextIndex = index + 1 - if not isCustom and nextIndex < len(self.entries): - self.entries[nextIndex].prefix = entry.prefix - else: - previousIndex = idx - 1 - if idx == len(self.entries) - 1 and ADDED_SCENES_COMMENT in entry.prefix: - entry.prefix = entry.prefix.removesuffix(f"\n{ADDED_SCENES_COMMENT}\n") - self.entries[previousIndex].suffix = entry.prefix - - self.entries.remove(entry) - elif index == SceneIndexType.VANILLA_REMOVED: - raise PluginError("INFO: This scene was already removed.") - else: - raise PluginError("ERROR: Unexpected scene index value.") + + for section in self.sections: + for entry in section.entries: + if entry.enum_value == enum_value: + section.entries.remove(entry) + return def to_c(self): """Returns the scene table as C code""" - return "".join(entry.to_c(i) for i, entry in enumerate(self.entries)) + data = f"{self.header}" + index = 0 + for section in self.sections: + data += section.to_c(index) + index += len(section.entries) + + if data[-2:] == "\n\n": # For consistency with vanilla + data = data[:-1] + return data class SceneTableUtility: """This class hosts different function to edit the scene table""" @staticmethod - def getDrawConfig(sceneName: str): + def get_draw_config(scene_name: str): """Read draw config from scene table""" - sceneTable = SceneTable( - os.path.join(bpy.path.abspath(bpy.context.scene.ootDecompPath), "include/tables/scene_table.h"), None, None + scene_table = SceneTable.new( + os.path.join(bpy.path.abspath(bpy.context.scene.ootDecompPath), "include/tables/scene_table.h") ) - entry = sceneTable.entryBySpecName.get(f"{sceneName}_scene") + spec_dict = {entry.spec_name: entry for entry in scene_table.get_entries_flattened()} + entry = spec_dict.get(f"{scene_name}_scene") if entry is not None: - return entry.drawConfigIdx + return entry.draw_config - raise PluginError(f"ERROR: Scene name {sceneName} not found in scene table.") + raise PluginError(f"ERROR: Scene name {scene_name} not found in scene table.") @staticmethod - def editSceneTable(exporter: Optional["SceneExport"], exportInfo: ExportInfo): - """Remove, append, insert or update the scene table entry of the selected scene""" - sceneTable = SceneTable( - os.path.join(exportInfo.exportPath, "include/tables/scene_table.h"), - exportInfo.name if exportInfo.option == "Custom" else None, - exportInfo.option, - ) + def edit_scene_table(export_path: str, export_name: str, export_enum: str, draw_config: str): + """Update the scene table entry of the selected scene""" + path = os.path.join(export_path, "include/tables/scene_table.h") + scene_table = SceneTable.new(path) - if exporter is None: - # remove mode - sceneTable.remove(sceneTable.selectedSceneIndex) - elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: - # custom mode: new custom scene - sceneTable.append( - SceneTableEntry.from_scene(exporter, exporter.exportInfo.name, True), len(sceneTable.entries) - 1 - ) - elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: - # insert mode - sceneTable.insert( - SceneTableEntry.from_scene(exporter, exporter.exportInfo.name, False), sceneTable.getInsertionIndex() - ) - else: - # update mode (for both vanilla and custom scenes since they already exist in the table) - index = sceneTable.getIndex() - entry = sceneTable.entries[index] - new_entry = SceneTableEntry.from_scene(exporter, exporter.scene.name, False) - new_entry.prefix = entry.prefix - new_entry.suffix = entry.suffix - sceneTable.entries[index] = new_entry + scene_table.update(SceneTableEntry.from_scene(export_name, draw_config), export_enum) + + # write the file with the final data + writeFile(path, scene_table.to_c()) + + @staticmethod + def delete_scene_table_entry(export_path: str, export_enum: str): + """Remove the scene table entry of the selected scene""" + path = os.path.join(export_path, "include/tables/scene_table.h") + scene_table = SceneTable.new(path) + + scene_table.remove(export_enum) # write the file with the final data - writeFile(sceneTable.exportPath, sceneTable.to_c()) + writeFile(path, scene_table.to_c()) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 5c523d0d4..ec7e4ad5c 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -149,10 +149,10 @@ def new(export_path: str): return SpecFile(header, build_directory, sections) - def get_entries_flattened(self): + def get_entries_flattened(self) -> list[SpecEntry]: """ Returns all entries as a single array, without sections. - This is a copy of the data and modifying this will not change the spec file internally. + This is a shallow copy of the data and adding/removing from this list not change the spec file internally. """ return [entry for section in self.sections for entry in section.entries] @@ -193,7 +193,7 @@ class SpecUtility: """This class hosts different functions to edit the spec file""" @staticmethod - def editSpec(exporter: "SceneExport"): + def edit_spec(exporter: "SceneExport"): isScene = True exportInfo = exporter.exportInfo hasSceneTex = exporter.hasSceneTextures From 4a1ad5d34b5a0b71e17816de2911cc5ff4158fae Mon Sep 17 00:00:00 2001 From: kurethedead Date: Mon, 22 Jul 2024 18:45:48 -0700 Subject: [PATCH 91/98] Removed uncecessary includes --- fast64_internal/oot/exporter/decomp_edit/scene_table.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index 50a8e6ae9..34b738275 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -1,17 +1,11 @@ import os -import enum import bpy from dataclasses import dataclass, field -from typing import Optional, TYPE_CHECKING +from typing import Optional from ....utility import PluginError, writeFile -from ...oot_utility import ExportInfo from ...oot_constants import ootEnumSceneID, ootSceneNameToID -if TYPE_CHECKING: - from ..main import SceneExport - - ADDED_SCENES_COMMENT = "// Added scenes" From f9ec52dcf200976d47eff8fcf2499d700a9b173a Mon Sep 17 00:00:00 2001 From: kurethedead Date: Mon, 22 Jul 2024 22:48:36 -0700 Subject: [PATCH 92/98] Converted scene removal to new exporter --- fast64_internal/oot/exporter/__init__.py | 2 +- .../oot/exporter/decomp_edit/__init__.py | 36 ++++- .../oot/exporter/decomp_edit/scene_table.py | 12 +- .../oot/exporter/decomp_edit/spec.py | 143 ++++++++++-------- fast64_internal/oot/exporter/main.py | 2 +- fast64_internal/oot/scene/operators.py | 10 +- 6 files changed, 118 insertions(+), 87 deletions(-) diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 3b7228c36..68e8a9d1d 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -1 +1 @@ -from .main import SceneExport +from .main import SceneExport, Files diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index 8de592357..8638979a9 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -1,5 +1,6 @@ import os import re +import shutil from typing import TYPE_CHECKING from ...oot_utility import getSceneDirFromLevelName @@ -7,14 +8,14 @@ from .spec import SpecUtility if TYPE_CHECKING: - from ..main import SceneExport + from ..main import SceneExport, ExportInfo class Files: # TODO: find a better name """This class handles editing decomp files""" @staticmethod - def modifySceneFiles(exporter: "SceneExport"): + def remove_old_room_files(exporter: "SceneExport"): if exporter.exportInfo.customSubPath is not None: sceneDir = exporter.exportInfo.customSubPath + exporter.exportInfo.name else: @@ -29,14 +30,33 @@ def modifySceneFiles(exporter: "SceneExport"): os.remove(filepath) @staticmethod - def editFiles(exporter: "SceneExport"): + def remove_scene_dir(exportInfo: "ExportInfo"): + if exportInfo.customSubPath is not None: + sceneDir = exportInfo.customSubPath + exportInfo.name + else: + sceneDir = getSceneDirFromLevelName(exportInfo.name) + + scenePath = os.path.join(exportInfo.exportPath, sceneDir) + if os.path.exists(scenePath): + shutil.rmtree(scenePath) + + @staticmethod + def add_scene_edits(exporter: "SceneExport"): """Edits decomp files""" + exportInfo = exporter.exportInfo - Files.modifySceneFiles(exporter) - SpecUtility.edit_spec(exporter) + Files.remove_old_room_files(exporter) + SpecUtility.add_segments(exporter) SceneTableUtility.edit_scene_table( - exporter.exportInfo.exportPath, - exporter.exportInfo.name, - exporter.exportInfo.option, + exportInfo.exportPath, + exportInfo.name, exporter.scene.mainHeader.infos.drawConfig, ) + + @staticmethod + def remove_scene(exportInfo: "ExportInfo"): + """Removes data from decomp files""" + + Files.remove_scene_dir(exportInfo) + SpecUtility.remove_segments(exportInfo.exportPath, exportInfo.name, True) + SceneTableUtility.delete_scene_table_entry(exportInfo.exportPath, exportInfo.name) diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index 34b738275..f838762d7 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -21,6 +21,10 @@ def get_original_index(enum_value: str) -> Optional[int]: return None +def get_scene_enum_from_name(scene_name: str): + return ootSceneNameToID.get(scene_name, f"SCENE_{scene_name.upper()}") + + @dataclass class SceneTableEntry: """Defines an entry of ``scene_table.h``""" @@ -55,7 +59,7 @@ def from_scene(scene_name: str, draw_config: str): return SceneTableEntry( scene_name if scene_name.endswith("_scene") else f"{scene_name}_scene", "none", - ootSceneNameToID.get(scene_name, f"SCENE_{scene_name.upper()}"), + get_scene_enum_from_name(scene_name), draw_config, "0", "0", @@ -261,10 +265,11 @@ def get_draw_config(scene_name: str): raise PluginError(f"ERROR: Scene name {scene_name} not found in scene table.") @staticmethod - def edit_scene_table(export_path: str, export_name: str, export_enum: str, draw_config: str): + def edit_scene_table(export_path: str, export_name: str, draw_config: str): """Update the scene table entry of the selected scene""" path = os.path.join(export_path, "include/tables/scene_table.h") scene_table = SceneTable.new(path) + export_enum = get_scene_enum_from_name(export_name) scene_table.update(SceneTableEntry.from_scene(export_name, draw_config), export_enum) @@ -272,10 +277,11 @@ def edit_scene_table(export_path: str, export_name: str, export_enum: str, draw_ writeFile(path, scene_table.to_c()) @staticmethod - def delete_scene_table_entry(export_path: str, export_enum: str): + def delete_scene_table_entry(export_path: str, export_name: str): """Remove the scene table entry of the selected scene""" path = os.path.join(export_path, "include/tables/scene_table.h") scene_table = SceneTable.new(path) + export_enum = get_scene_enum_from_name(export_name) scene_table.remove(export_enum) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index ec7e4ad5c..5e84e01dc 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -51,10 +51,10 @@ def new(original: list[str]): return SpecEntry(commands) def get_name(self) -> Optional[str]: - """Returns segment name""" + """Returns segment name, with quotes removed""" for command in self.commands: if command.type == "name": - return command.content + return command.content.replace('"', "") return "" def to_c(self): @@ -193,8 +193,33 @@ class SpecUtility: """This class hosts different functions to edit the spec file""" @staticmethod - def edit_spec(exporter: "SceneExport"): - isScene = True + def remove_segments(export_path: str, scene_name: str, write_to_file: bool): + print(f"Remove segments: {scene_name}, {export_path}") + # get the spec's data + path = os.path.join(export_path, "spec") + spec_file = SpecFile.new(path) + + # get the scene and current segment name and remove the scene + scene_segment_name = f"{scene_name}_scene" + spec_file.remove(scene_segment_name) + + # mark the other scene elements to remove (like rooms) + segments_to_remove: list[str] = [] + for entry in spec_file.get_entries_flattened(): + # Note: you cannot do startswith(scene_name), ex. entra vs entra_n + if entry.get_name() == f"{scene_name}_scene" or re.match(f"^{scene_name}\_room\_[0-9]+$", entry.get_name()): + segments_to_remove.append(entry.get_name()) + + # remove the segments + for segment_name in segments_to_remove: + spec_file.remove(segment_name) + + # finally, write the spec file + if write_to_file: + writeFile(path, spec_file.to_c()) + + @staticmethod + def add_segments(exporter: "SceneExport"): exportInfo = exporter.exportInfo hasSceneTex = exporter.hasSceneTextures hasSceneCS = exporter.hasCutscenes @@ -214,80 +239,66 @@ def edit_spec(exporter: "SceneExport"): # get the scene and current segment name and remove the scene sceneName = exportInfo.name sceneSegmentName = f"{sceneName}_scene" - specFile.remove(f'"{sceneSegmentName}"') + SpecUtility.remove_segments(exportInfo.exportPath, exportInfo.name, False) - # mark the other scene elements to remove (like rooms) - segmentsToRemove: list[str] = [] - for entry in specFile.get_entries_flattened(): - # Note: you cannot do startswith(sceneName), ex. entra vs entra_n - if entry.get_name() == f'"{sceneName}_scene"' or re.match( - f'^"{sceneName}\_room\_[0-9]+"$', entry.get_name() - ): - segmentsToRemove.append(entry.get_name()) + assert build_directory is not None + isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile + includeDir = f"{build_directory}/" + if exportInfo.customSubPath is not None: + includeDir += f"{exportInfo.customSubPath + sceneName}" + else: + includeDir += f"{getSceneDirFromLevelName(sceneName)}" - # remove the segments - for segmentName in segmentsToRemove: - specFile.remove(segmentName) - - if isScene: - assert build_directory is not None - isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile - includeDir = f"{build_directory}/" - if exportInfo.customSubPath is not None: - includeDir += f"{exportInfo.customSubPath + sceneName}" - else: - includeDir += f"{getSceneDirFromLevelName(sceneName)}" + sceneCmds = [ + SpecCommand("name", f'"{sceneSegmentName}"'), + SpecCommand("compress", ""), + SpecCommand("romalign", "0x1000"), + ] + + # scene + if isSingleFile: + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}.o"')) + else: + sceneCmds.extend( + [ + SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_main.o"'), + SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_col.o"'), + ] + ) + + if hasSceneTex: + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_tex.o"')) + + if hasSceneCS: + for i in range(csTotal): + sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"')) + + sceneCmds.append(SpecCommand("number", "2")) + specFile.append(SpecEntry(sceneCmds)) - sceneCmds = [ - SpecCommand("name", f'"{sceneSegmentName}"'), - SpecCommand("compress", ""), + # rooms + for i in range(roomTotal): + roomSegmentName = f"{sceneName}_room_{i}" + + roomCmds = [ + SpecCommand("name", f'"{roomSegmentName}"'), + SpecCommand("compress"), SpecCommand("romalign", "0x1000"), ] - # scene if isSingleFile: - sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}.o"')) + roomCmds.append(SpecCommand("include", f'"{includeDir}/{roomSegmentName}.o"')) else: - sceneCmds.extend( + roomCmds.extend( [ - SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_main.o"'), - SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_col.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_main.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model_info.o"'), + SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model.o"'), ] ) - if hasSceneTex: - sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_tex.o"')) - - if hasSceneCS: - for i in range(csTotal): - sceneCmds.append(SpecCommand("include", f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"')) - - sceneCmds.append(SpecCommand("number", "2")) - specFile.append(SpecEntry(sceneCmds)) - - # rooms - for i in range(roomTotal): - roomSegmentName = f"{sceneName}_room_{i}" - - roomCmds = [ - SpecCommand("name", f'"{roomSegmentName}"'), - SpecCommand("compress"), - SpecCommand("romalign", "0x1000"), - ] - - if isSingleFile: - roomCmds.append(SpecCommand("include", f'"{includeDir}/{roomSegmentName}.o"')) - else: - roomCmds.extend( - [ - SpecCommand("include", f'"{includeDir}/{roomSegmentName}_main.o"'), - SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model_info.o"'), - SpecCommand("include", f'"{includeDir}/{roomSegmentName}_model.o"'), - ] - ) - - roomCmds.append(SpecCommand("number", "3")) - specFile.append(SpecEntry(roomCmds)) + roomCmds.append(SpecCommand("number", "3")) + specFile.append(SpecEntry(roomCmds)) # finally, write the spec file writeFile(exportPath, specFile.to_c()) diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py index 4d6c52578..7e24416b5 100644 --- a/fast64_internal/oot/exporter/main.py +++ b/fast64_internal/oot/exporter/main.py @@ -133,7 +133,7 @@ def export(self): room.mesh.copyBgImages(self.path) if not isCustomExport: - Files.editFiles(self) + Files.add_scene_edits(self) if self.hackerootBootOption is not None and self.hackerootBootOption.bootToScene: setBootupScene( diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index ad2534e38..47883da9c 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -14,13 +14,7 @@ from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles -from ..exporter import SceneExport - - -def ootRemoveSceneC(exportInfo): - modifySceneTable(None, exportInfo) - editSpecFile(False, exportInfo, False, False, 0, 0) - deleteSceneFiles(exportInfo) +from ..exporter import SceneExport, Files def run_ops_without_view_layer_update(func): @@ -248,7 +242,7 @@ def execute(self, context): exportInfo = ExportInfo(False, abspath(context.scene.ootDecompPath), subfolder, levelName) exportInfo.option = option - ootRemoveSceneC(exportInfo) + Files.remove_scene(exportInfo) self.report({"INFO"}, "Success!") return {"FINISHED"} From 519205f3b4db7b8100fe8c421859c9100e604811 Mon Sep 17 00:00:00 2001 From: kurethedead Date: Tue, 23 Jul 2024 01:14:12 -0700 Subject: [PATCH 93/98] Refactored SceneExport, separated export from export params (ExportInfo) --- fast64_internal/oot/exporter/__init__.py | 124 ++++++++++++++- .../oot/exporter/decomp_edit/__init__.py | 43 +++--- .../oot/exporter/decomp_edit/spec.py | 31 ++-- fast64_internal/oot/exporter/main.py | 145 ------------------ fast64_internal/oot/oot_utility.py | 52 ++++++- fast64_internal/oot/scene/operators.py | 63 ++++---- 6 files changed, 233 insertions(+), 225 deletions(-) delete mode 100644 fast64_internal/oot/exporter/main.py diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 68e8a9d1d..eedf0c68d 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -1 +1,123 @@ -from .main import SceneExport, Files +import bpy +import os +import traceback + +from dataclasses import dataclass, field +from mathutils import Matrix +from bpy.types import Object +from typing import Optional +from ...f3d.f3d_gbi import DLFormat, TextureExportSettings +from ..scene.properties import OOTBootupSceneOptions +from ..oot_model_classes import OOTModel +from ..oot_f3d_writer import writeTextureArraysNew +from .scene import Scene +from .decomp_edit import Files +from .file import SceneFile + +from ...utility import ( + PluginError, + checkObjectReference, + unhideAllAndGetHiddenState, + restoreHiddenState, + toAlnum, +) + +from ..oot_utility import ( + ExportInfo, + RemoveInfo, + OOTObjectCategorizer, + ootDuplicateHierarchy, + ootCleanupScene, + getSceneDirFromLevelName, + ootGetPath, +) + + +@dataclass +class SceneExport: + """This class is the main exporter class, it handles generating the C data and writing the files""" + + @staticmethod + def create_scene(originalSceneObj: Object, transform: Matrix, exportInfo: ExportInfo) -> Scene: + """Returns and creates scene data""" + # init + if originalSceneObj.type != "EMPTY" or originalSceneObj.ootEmptyType != "Scene": + raise PluginError(f'{originalSceneObj.name} is not an empty with the "Scene" empty type.') + + if bpy.context.scene.exportHiddenGeometry: + hiddenState = unhideAllAndGetHiddenState(bpy.context.scene) + + # Don't remove ignore_render, as we want to reuse this for collision + sceneObj, allObjs = ootDuplicateHierarchy(originalSceneObj, None, True, OOTObjectCategorizer()) + + if bpy.context.scene.exportHiddenGeometry: + restoreHiddenState(hiddenState) + + try: + sceneName = f"{toAlnum(exportInfo.name)}_scene" + newScene = Scene.new( + sceneName, + sceneObj, + transform, + exportInfo.useMacros, + exportInfo.saveTexturesAsPNG, + OOTModel(f"{sceneName}_dl", DLFormat.Static, False), + ) + newScene.validateScene() + + except Exception as e: + raise Exception(str(e)) + finally: + ootCleanupScene(originalSceneObj, allObjs) + + return newScene + + @staticmethod + def export(originalSceneObj: Object, transform: Matrix, exportInfo: ExportInfo): + """Main function""" + # circular import fixes + from ..scene.exporter.to_c import setBootupScene + from ..oot_level_writer import writeTextureArraysExistingScene + + checkObjectReference(originalSceneObj, "Scene object") + scene = SceneExport.create_scene(originalSceneObj, transform, exportInfo) + + isCustomExport = exportInfo.isCustomExportPath + exportPath = exportInfo.exportPath + sceneName = exportInfo.name + + exportSubdir = "" + if exportInfo.customSubPath is not None: + exportSubdir = exportInfo.customSubPath + if not isCustomExport and exportInfo.customSubPath is None: + exportSubdir = os.path.dirname(getSceneDirFromLevelName(sceneName)) + + sceneInclude = exportSubdir + "/" + sceneName + "/" + path = ootGetPath(exportPath, isCustomExport, exportSubdir, sceneName, True, True) + textureExportSettings = TextureExportSettings(False, exportInfo.saveTexturesAsPNG, sceneInclude, path) + + sceneFile = scene.getNewSceneFile(path, exportInfo.isSingleFile, textureExportSettings) + + if not isCustomExport: + writeTextureArraysExistingScene(scene.model, exportPath, sceneInclude + sceneName + "_scene.h") + else: + textureArrayData = writeTextureArraysNew(scene.model, None) + sceneFile.sceneTextures += textureArrayData.source + sceneFile.header += textureArrayData.header + + sceneFile.write() + for room in scene.rooms.entries: + room.mesh.copyBgImages(path) + + if not isCustomExport: + Files.add_scene_edits(exportInfo, scene, sceneFile) + + hackerootBootOption = exportInfo.hackerootBootOption + if hackerootBootOption is not None and hackerootBootOption.bootToScene: + setBootupScene( + os.path.join(exportPath, "include/config/config_debug.h") + if not isCustomExport + else os.path.join(path, "config_bootup.h"), + f"ENTR_{sceneName.upper()}_{hackerootBootOption.spawnIndex}", + hackerootBootOption, + ) diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index 8638979a9..9502e69e8 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -8,55 +8,54 @@ from .spec import SpecUtility if TYPE_CHECKING: - from ..main import SceneExport, ExportInfo + from ...exporter import SceneExport, ExportInfo, RemoveInfo, Scene, SceneFile class Files: # TODO: find a better name """This class handles editing decomp files""" @staticmethod - def remove_old_room_files(exporter: "SceneExport"): - if exporter.exportInfo.customSubPath is not None: - sceneDir = exporter.exportInfo.customSubPath + exporter.exportInfo.name + def remove_old_room_files(exportInfo: "ExportInfo", scene: "Scene"): + if exportInfo.customSubPath is not None: + sceneDir = exportInfo.customSubPath + exportInfo.name else: - sceneDir = getSceneDirFromLevelName(exporter.sceneName) + sceneDir = getSceneDirFromLevelName(exportInfo.name) - scenePath = os.path.join(exporter.exportInfo.exportPath, sceneDir) + scenePath = os.path.join(exportInfo.exportPath, sceneDir) for filename in os.listdir(scenePath): filepath = os.path.join(scenePath, filename) if os.path.isfile(filepath): - match = re.match(exporter.scene.name + "\_room\_(\d+)\.[ch]", filename) - if match is not None and int(match.group(1)) >= len(exporter.scene.rooms.entries): + match = re.match(scene.name + "\_room\_(\d+)\.[ch]", filename) + if match is not None and int(match.group(1)) >= len(scene.rooms.entries): os.remove(filepath) @staticmethod - def remove_scene_dir(exportInfo: "ExportInfo"): - if exportInfo.customSubPath is not None: - sceneDir = exportInfo.customSubPath + exportInfo.name + def remove_scene_dir(remove_info: "RemoveInfo"): + if remove_info.customSubPath is not None: + sceneDir = remove_info.customSubPath + remove_info.name else: - sceneDir = getSceneDirFromLevelName(exportInfo.name) + sceneDir = getSceneDirFromLevelName(remove_info.name) - scenePath = os.path.join(exportInfo.exportPath, sceneDir) + scenePath = os.path.join(remove_info.exportPath, sceneDir) if os.path.exists(scenePath): shutil.rmtree(scenePath) @staticmethod - def add_scene_edits(exporter: "SceneExport"): + def add_scene_edits(exportInfo: "ExportInfo", scene: "Scene", sceneFile: "SceneFile"): """Edits decomp files""" - exportInfo = exporter.exportInfo - Files.remove_old_room_files(exporter) - SpecUtility.add_segments(exporter) + Files.remove_old_room_files(exportInfo, scene) + SpecUtility.add_segments(exportInfo, scene, sceneFile) SceneTableUtility.edit_scene_table( exportInfo.exportPath, exportInfo.name, - exporter.scene.mainHeader.infos.drawConfig, + scene.mainHeader.infos.drawConfig, ) @staticmethod - def remove_scene(exportInfo: "ExportInfo"): + def remove_scene(remove_info: "RemoveInfo"): """Removes data from decomp files""" - Files.remove_scene_dir(exportInfo) - SpecUtility.remove_segments(exportInfo.exportPath, exportInfo.name, True) - SceneTableUtility.delete_scene_table_entry(exportInfo.exportPath, exportInfo.name) + Files.remove_scene_dir(remove_info) + SpecUtility.remove_segments(remove_info.exportPath, remove_info.name) + SceneTableUtility.delete_scene_table_entry(remove_info.exportPath, remove_info.name) diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 5e84e01dc..3387f1ef2 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -8,7 +8,7 @@ from ...oot_utility import getSceneDirFromLevelName if TYPE_CHECKING: - from ..main import SceneExport + from ...exporter import ExportInfo, Scene, SceneFile @dataclass @@ -193,12 +193,14 @@ class SpecUtility: """This class hosts different functions to edit the spec file""" @staticmethod - def remove_segments(export_path: str, scene_name: str, write_to_file: bool): - print(f"Remove segments: {scene_name}, {export_path}") - # get the spec's data + def remove_segments(export_path: str, scene_name: str): path = os.path.join(export_path, "spec") spec_file = SpecFile.new(path) + SpecUtility.remove_segments_from_spec(spec_file, scene_name) + writeFile(path, spec_file.to_c()) + @staticmethod + def remove_segments_from_spec(spec_file: SpecFile, scene_name: str): # get the scene and current segment name and remove the scene scene_segment_name = f"{scene_name}_scene" spec_file.remove(scene_segment_name) @@ -214,21 +216,16 @@ def remove_segments(export_path: str, scene_name: str, write_to_file: bool): for segment_name in segments_to_remove: spec_file.remove(segment_name) - # finally, write the spec file - if write_to_file: - writeFile(path, spec_file.to_c()) - @staticmethod - def add_segments(exporter: "SceneExport"): - exportInfo = exporter.exportInfo - hasSceneTex = exporter.hasSceneTextures - hasSceneCS = exporter.hasCutscenes - roomTotal = len(exporter.scene.rooms.entries) + def add_segments(exportInfo: "ExportInfo", scene: "Scene", sceneFile: "SceneFile"): + hasSceneTex = sceneFile.hasSceneTextures() + hasSceneCS = sceneFile.hasCutscenes() + roomTotal = len(scene.rooms.entries) csTotal = 0 - csTotal += len(exporter.scene.mainHeader.cutscene.entries) - if exporter.scene.altHeader is not None: - for cs in exporter.scene.altHeader.cutscenes: + csTotal += len(scene.mainHeader.cutscene.entries) + if scene.altHeader is not None: + for cs in scene.altHeader.cutscenes: csTotal += len(cs.cutscene.entries) # get the spec's data @@ -239,7 +236,7 @@ def add_segments(exporter: "SceneExport"): # get the scene and current segment name and remove the scene sceneName = exportInfo.name sceneSegmentName = f"{sceneName}_scene" - SpecUtility.remove_segments(exportInfo.exportPath, exportInfo.name, False) + SpecUtility.remove_segments_from_spec(specFile, exportInfo.name) assert build_directory is not None isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile diff --git a/fast64_internal/oot/exporter/main.py b/fast64_internal/oot/exporter/main.py deleted file mode 100644 index 7e24416b5..000000000 --- a/fast64_internal/oot/exporter/main.py +++ /dev/null @@ -1,145 +0,0 @@ -import bpy -import os -import traceback - -from dataclasses import dataclass, field -from mathutils import Matrix -from bpy.types import Object -from typing import Optional -from ...f3d.f3d_gbi import DLFormat, TextureExportSettings -from ..scene.properties import OOTBootupSceneOptions -from ..oot_model_classes import OOTModel -from ..oot_f3d_writer import writeTextureArraysNew -from .scene import Scene -from .decomp_edit import Files -from .file import SceneFile - -from ...utility import ( - PluginError, - checkObjectReference, - unhideAllAndGetHiddenState, - restoreHiddenState, - toAlnum, -) - -from ..oot_utility import ( - ExportInfo, - OOTObjectCategorizer, - ootDuplicateHierarchy, - ootCleanupScene, - getSceneDirFromLevelName, - ootGetPath, -) - - -@dataclass -class SceneExport: - """This class is the main exporter class, it handles generating the C data and writing the files""" - - exportInfo: ExportInfo - originalSceneObj: Object - sceneName: str - ootBlenderScale: float - transform: Matrix - saveTexturesAsPNG: bool - hackerootBootOption: OOTBootupSceneOptions - isSingleFile: bool - textureExportSettings: TextureExportSettings - useMacros: bool - - dlFormat: DLFormat = field(init=False, default=DLFormat.Static) - sceneObj: Optional[Object] = field(init=False, default=None) - scene: Optional[Scene] = field(init=False, default=None) - path: Optional[str] = field(init=False, default=None) - sceneFile: Optional[SceneFile] = field(init=False, default=None) - hasCutscenes: bool = field(init=False, default=False) - hasSceneTextures: bool = field(init=False, default=False) - - def getNewScene(self): - """Returns and creates scene data""" - # init - if self.originalSceneObj.type != "EMPTY" or self.originalSceneObj.ootEmptyType != "Scene": - raise PluginError(f'{self.originalSceneObj.name} is not an empty with the "Scene" empty type.') - - if bpy.context.scene.exportHiddenGeometry: - hiddenState = unhideAllAndGetHiddenState(bpy.context.scene) - - # Don't remove ignore_render, as we want to reuse this for collision - self.sceneObj, allObjs = ootDuplicateHierarchy(self.originalSceneObj, None, True, OOTObjectCategorizer()) - - if bpy.context.scene.exportHiddenGeometry: - restoreHiddenState(hiddenState) - - try: - sceneName = f"{toAlnum(self.sceneName)}_scene" - newScene = Scene.new( - sceneName, - self.sceneObj, - self.transform, - self.useMacros, - self.saveTexturesAsPNG, - OOTModel(f"{sceneName}_dl", self.dlFormat, False), - ) - newScene.validateScene() - - if newScene.mainHeader.cutscene is not None: - self.hasCutscenes = len(newScene.mainHeader.cutscene.entries) > 0 - - if not self.hasCutscenes and newScene.altHeader is not None: - for cs in newScene.altHeader.cutscenes: - if len(cs.cutscene.entries) > 0: - self.hasCutscenes = True - break - except Exception as e: - raise Exception(str(e)) - finally: - ootCleanupScene(self.originalSceneObj, allObjs) - - return newScene - - def export(self): - """Main function""" - # circular import fixes - from ..scene.exporter.to_c import setBootupScene - from ..oot_level_writer import writeTextureArraysExistingScene - - checkObjectReference(self.originalSceneObj, "Scene object") - isCustomExport = self.exportInfo.isCustomExportPath - exportPath = self.exportInfo.exportPath - - exportSubdir = "" - if self.exportInfo.customSubPath is not None: - exportSubdir = self.exportInfo.customSubPath - if not isCustomExport and self.exportInfo.customSubPath is None: - exportSubdir = os.path.dirname(getSceneDirFromLevelName(self.sceneName)) - - sceneInclude = exportSubdir + "/" + self.sceneName + "/" - self.scene = self.getNewScene() - self.path = ootGetPath(exportPath, isCustomExport, exportSubdir, self.sceneName, True, True) - self.textureExportSettings.includeDir = sceneInclude - self.textureExportSettings.exportPath = self.path - self.sceneFile = self.scene.getNewSceneFile(self.path, self.isSingleFile, self.textureExportSettings) - self.hasSceneTextures = self.sceneFile.sceneTextures is not None and len(self.sceneFile.sceneTextures) > 0 - - if not isCustomExport: - writeTextureArraysExistingScene(self.scene.model, exportPath, sceneInclude + self.sceneName + "_scene.h") - else: - textureArrayData = writeTextureArraysNew(self.scene.model, None) - self.sceneFile.sceneTextures += textureArrayData.source - self.sceneFile.header += textureArrayData.header - - self.sceneFile.write() - for room in self.scene.rooms.entries: - room.mesh.copyBgImages(self.path) - - if not isCustomExport: - Files.add_scene_edits(self) - - if self.hackerootBootOption is not None and self.hackerootBootOption.bootToScene: - setBootupScene( - os.path.join(exportPath, "include/config/config_debug.h") - if not isCustomExport - else os.path.join(self.path, "config_bootup.h"), - f"ENTR_{self.sceneName.upper()}_{self.hackerootBootOption.spawnIndex}", - self.hackerootBootOption, - ) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 9a0bc6d94..6e8e5f049 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -10,6 +10,7 @@ from bpy.types import Object from typing import Callable, Optional from .oot_constants import ootSceneIDToName +from dataclasses import dataclass, field from ..utility import ( PluginError, @@ -209,13 +210,52 @@ def getSceneDirFromLevelName(name): return None +@dataclass class ExportInfo: - def __init__(self, isCustomExport, exportPath, customSubPath, name): - self.isCustomExportPath = isCustomExport - self.exportPath = exportPath - self.customSubPath = customSubPath - self.name: str = name - self.option: Optional[str] = None + """Contains all parameters used for a scene export. Any new parameters for scene export should be added here.""" + + isCustomExportPath: bool + """Whether or not we are exporting to a known decomp repo""" + + exportPath: str + """Either the decomp repo root, or a specified custom folder (if ``isCustomExportPath`` is true)""" + + customSubPath: Optional[str] + """If ``isCustomExportPath``, then this is the relative path used for writing filepaths in files like spec. + For decomp repos, the relative path is automatically determined and thus this will be ``None``.""" + + name: str + """ The name of the scene, similar to the folder names of scenes in decomp. + If ``option`` is not "Custom", then this is usually overriden by the name derived from ``option`` before being passed in.""" + + option: str + """ The scene enum value that we are exporting to (can be Custom)""" + + saveTexturesAsPNG: bool + """ Whether to write textures as C data or as .png files.""" + + isSingleFile: bool + """ Whether to export scene files as a single file or as multiple.""" + + useMacros: bool + """ Whether to use macros or numeric/binary representations of certain values.""" + + hackerootBootOption: "OOTBootupSceneOptions" + """ Options for setting the bootup scene in HackerOoT.""" + + +@dataclass +class RemoveInfo: + """Contains all parameters used for a scene removal.""" + + exportPath: str + """The path to the decomp repo root""" + + customSubPath: Optional[str] + """The relative path to the scene directory, if a custom scene is being removed""" + + name: str + """The name of the level to remove""" class OOTObjectCategorizer: diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 47883da9c..dd6c02367 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -9,7 +9,7 @@ from mathutils import Matrix, Vector from ...f3d.f3d_gbi import TextureExportSettings, DLFormat from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader -from ..oot_utility import ExportInfo, sceneNameFromID +from ..oot_utility import ExportInfo, RemoveInfo, sceneNameFromID from ..oot_level_writer import ootExportSceneToC from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene @@ -154,44 +154,40 @@ def execute(self, context): settings = context.scene.ootSceneExportSettings levelName = settings.name option = settings.option + + bootOptions = context.scene.fast64.oot.bootupSceneOptions + hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled + if settings.customExport: - exportInfo = ExportInfo(True, bpy.path.abspath(settings.exportPath), None, levelName) + isCustomExport = True + exportPath = bpy.path.abspath(settings.exportPath) + customSubPath = None else: if option == "Custom": - subfolder = "assets/scenes/" + settings.subFolder + "/" + customSubPath = "assets/scenes/" + settings.subFolder + "/" else: levelName = sceneNameFromID(option) - subfolder = None - exportInfo = ExportInfo(False, bpy.path.abspath(context.scene.ootDecompPath), subfolder, levelName) - - exportInfo.option = option - bootOptions = context.scene.fast64.oot.bootupSceneOptions - hackerFeaturesEnabled = context.scene.fast64.oot.hackerFeaturesEnabled - - # keeping this on purpose, will be removed once old code is cleaned-up - # if settings.useNewExporter: - SceneExport( - exportInfo, - obj, - exportInfo.name, - context.scene.ootBlenderScale, - finalTransform, + customSubPath = None + isCustomExport = False + exportPath = bpy.path.abspath(context.scene.ootDecompPath) + + exportInfo = ExportInfo( + isCustomExport, + exportPath, + customSubPath, + levelName, + option, bpy.context.scene.saveTextures, - bootOptions if hackerFeaturesEnabled else None, settings.singleFile, - TextureExportSettings(False, context.scene.saveTextures, None, None), context.scene.fast64.oot.useDecompFeatures if not hackerFeaturesEnabled else hackerFeaturesEnabled, - ).export() - # else: - # ootExportSceneToC( - # obj, - # finalTransform, - # levelName, - # DLFormat.Static, - # context.scene.saveTextures, - # exportInfo, - # bootOptions if hackerFeaturesEnabled else None, - # ) + bootOptions if hackerFeaturesEnabled else None, + ) + + SceneExport.export( + obj, + finalTransform, + exportInfo, + ) self.report({"INFO"}, "Success!") @@ -239,10 +235,9 @@ def execute(self, context): else: levelName = sceneNameFromID(option) subfolder = None - exportInfo = ExportInfo(False, abspath(context.scene.ootDecompPath), subfolder, levelName) - exportInfo.option = option + removeInfo = RemoveInfo(abspath(context.scene.ootDecompPath), subfolder, levelName) - Files.remove_scene(exportInfo) + Files.remove_scene(removeInfo) self.report({"INFO"}, "Success!") return {"FINISHED"} From e28069d48eb472e733aae6f0c217287c2f9328f6 Mon Sep 17 00:00:00 2001 From: kurethedead Date: Tue, 23 Jul 2024 10:10:55 -0700 Subject: [PATCH 94/98] Minor edits --- fast64_internal/oot/exporter/__init__.py | 2 -- fast64_internal/oot/exporter/decomp_edit/scene_table.py | 3 +-- fast64_internal/oot/oot_utility.py | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index eedf0c68d..247185ff2 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -2,7 +2,6 @@ import os import traceback -from dataclasses import dataclass, field from mathutils import Matrix from bpy.types import Object from typing import Optional @@ -33,7 +32,6 @@ ) -@dataclass class SceneExport: """This class is the main exporter class, it handles generating the C data and writing the files""" diff --git a/fast64_internal/oot/exporter/decomp_edit/scene_table.py b/fast64_internal/oot/exporter/decomp_edit/scene_table.py index f838762d7..8ae966864 100644 --- a/fast64_internal/oot/exporter/decomp_edit/scene_table.py +++ b/fast64_internal/oot/exporter/decomp_edit/scene_table.py @@ -126,8 +126,7 @@ def new(export_path: str): sections: list[SceneTableSection] = [] current_section: Optional[SceneTableSection] = None - while len(lines) > 0: - line = lines.pop(0) + for line in lines: if line.startswith("#if"): if current_section: # handles non-directive section preceding directive section sections.append(current_section) diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 6e8e5f049..1b044d21b 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -10,7 +10,7 @@ from bpy.types import Object from typing import Callable, Optional from .oot_constants import ootSceneIDToName -from dataclasses import dataclass, field +from dataclasses import dataclass from ..utility import ( PluginError, From a780c225ee6f79cc678cb27874e0c86f51e60262 Mon Sep 17 00:00:00 2001 From: kurethedead <59840896+kurethedead@users.noreply.github.com> Date: Tue, 23 Jul 2024 17:24:35 -0700 Subject: [PATCH 95/98] Delete Old Exporter Code (#4) * Deleted scene\exporter, fixed missing code references * Removed unused code from oot_level_writer and oot_level_classes * Removed oot_level_writer, contents moved into appropriate files * Remove unused OOTRoom * Black format --- fast64_internal/oot/exporter/__init__.py | 30 +- .../oot/exporter/decomp_edit/config.py | 153 +++ fast64_internal/oot/exporter/room/main.py | 9 +- fast64_internal/oot/exporter/room/mesh.py | 187 ++++ fast64_internal/oot/exporter/room/shape.py | 3 +- fast64_internal/oot/oot_level_classes.py | 370 ------- fast64_internal/oot/oot_level_parser.py | 9 +- fast64_internal/oot/oot_level_writer.py | 903 ------------------ fast64_internal/oot/oot_object.py | 36 +- .../oot/scene/exporter/to_c/__init__.py | 6 - .../oot/scene/exporter/to_c/actor.py | 132 --- .../oot/scene/exporter/to_c/room_commands.py | 100 -- .../oot/scene/exporter/to_c/room_header.py | 83 -- .../oot/scene/exporter/to_c/room_shape.py | 154 --- .../oot/scene/exporter/to_c/scene.py | 79 -- .../oot/scene/exporter/to_c/scene_bootup.py | 151 --- .../scene/exporter/to_c/scene_collision.py | 9 - .../oot/scene/exporter/to_c/scene_commands.py | 116 --- .../oot/scene/exporter/to_c/scene_cutscene.py | 51 - .../oot/scene/exporter/to_c/scene_folder.py | 29 - .../oot/scene/exporter/to_c/scene_header.py | 219 ----- .../oot/scene/exporter/to_c/scene_pathways.py | 47 - .../oot/scene/exporter/to_c/scene_table_c.py | 322 ------- .../oot/scene/exporter/to_c/spec.py | 303 ------ fast64_internal/oot/scene/operators.py | 5 +- 25 files changed, 382 insertions(+), 3124 deletions(-) create mode 100644 fast64_internal/oot/exporter/decomp_edit/config.py create mode 100644 fast64_internal/oot/exporter/room/mesh.py delete mode 100644 fast64_internal/oot/oot_level_writer.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/__init__.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/actor.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/room_commands.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/room_header.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/room_shape.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_bootup.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_collision.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_commands.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_folder.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_header.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_pathways.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/scene_table_c.py delete mode 100644 fast64_internal/oot/scene/exporter/to_c/spec.py diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 247185ff2..8450cc8c7 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -8,7 +8,7 @@ from ...f3d.f3d_gbi import DLFormat, TextureExportSettings from ..scene.properties import OOTBootupSceneOptions from ..oot_model_classes import OOTModel -from ..oot_f3d_writer import writeTextureArraysNew +from ..oot_f3d_writer import writeTextureArraysNew, writeTextureArraysExisting1D from .scene import Scene from .decomp_edit import Files from .file import SceneFile @@ -19,11 +19,12 @@ unhideAllAndGetHiddenState, restoreHiddenState, toAlnum, + readFile, + writeFile, ) from ..oot_utility import ( ExportInfo, - RemoveInfo, OOTObjectCategorizer, ootDuplicateHierarchy, ootCleanupScene, @@ -32,6 +33,26 @@ ) +def writeTextureArraysExistingScene(fModel: OOTModel, exportPath: str, sceneInclude: str): + drawConfigPath = os.path.join(exportPath, "src/code/z_scene_table.c") + drawConfigData = readFile(drawConfigPath) + newData = drawConfigData + + if f'#include "{sceneInclude}"' not in newData: + additionalIncludes = f'#include "{sceneInclude}"\n' + else: + additionalIncludes = "" + + for flipbook in fModel.flipbooks: + if flipbook.exportMode == "Array": + newData = writeTextureArraysExisting1D(newData, flipbook, additionalIncludes) + else: + raise PluginError("Scenes can only use array flipbooks.") + + if newData != drawConfigData: + writeFile(drawConfigPath, newData) + + class SceneExport: """This class is the main exporter class, it handles generating the C data and writing the files""" @@ -74,8 +95,7 @@ def create_scene(originalSceneObj: Object, transform: Matrix, exportInfo: Export def export(originalSceneObj: Object, transform: Matrix, exportInfo: ExportInfo): """Main function""" # circular import fixes - from ..scene.exporter.to_c import setBootupScene - from ..oot_level_writer import writeTextureArraysExistingScene + from .decomp_edit.config import Config checkObjectReference(originalSceneObj, "Scene object") scene = SceneExport.create_scene(originalSceneObj, transform, exportInfo) @@ -112,7 +132,7 @@ def export(originalSceneObj: Object, transform: Matrix, exportInfo: ExportInfo): hackerootBootOption = exportInfo.hackerootBootOption if hackerootBootOption is not None and hackerootBootOption.bootToScene: - setBootupScene( + Config.setBootupScene( os.path.join(exportPath, "include/config/config_debug.h") if not isCustomExport else os.path.join(path, "config_bootup.h"), diff --git a/fast64_internal/oot/exporter/decomp_edit/config.py b/fast64_internal/oot/exporter/decomp_edit/config.py new file mode 100644 index 000000000..14f2156ee --- /dev/null +++ b/fast64_internal/oot/exporter/decomp_edit/config.py @@ -0,0 +1,153 @@ +import os, re +from ....utility import PluginError, readFile, writeFile +from ...scene.properties import OOTBootupSceneOptions + + +class Config: + @staticmethod + def writeBootupSettings( + configPath: str, + bootMode: str, + newGameOnly: bool, + entranceIndex: str, + linkAge: str, + timeOfDay: str, + cutsceneIndex: str, + saveFileNameData: str, + ): + if os.path.exists(configPath): + originalData = readFile(configPath) + data = originalData + else: + originalData = "" + data = ( + f"// #define BOOT_TO_SCENE\n" + + f"// #define BOOT_TO_SCENE_NEW_GAME_ONLY\n" + + f"// #define BOOT_TO_FILE_SELECT\n" + + f"// #define BOOT_TO_MAP_SELECT\n" + + f"#define BOOT_ENTRANCE 0\n" + + f"#define BOOT_AGE LINK_AGE_CHILD\n" + + f"#define BOOT_TIME NEXT_TIME_NONE\n" + + f"#define BOOT_CUTSCENE 0xFFEF\n" + + f"#define BOOT_PLAYER_NAME 0x15, 0x12, 0x17, 0x14, 0x3E, 0x3E, 0x3E, 0x3E\n\n" + ) + + data = re.sub( + r"(//\s*)?#define\s*BOOT_TO_SCENE", + ("" if bootMode == "Play" else "// ") + "#define BOOT_TO_SCENE", + data, + ) + data = re.sub( + r"(//\s*)?#define\s*BOOT_TO_SCENE_NEW_GAME_ONLY", + ("" if newGameOnly else "// ") + "#define BOOT_TO_SCENE_NEW_GAME_ONLY", + data, + ) + data = re.sub( + r"(//\s*)?#define\s*BOOT_TO_FILE_SELECT", + ("" if bootMode == "File Select" else "// ") + "#define BOOT_TO_FILE_SELECT", + data, + ) + data = re.sub( + r"(//\s*)?#define\s*BOOT_TO_MAP_SELECT", + ("" if bootMode == "Map Select" else "// ") + "#define BOOT_TO_MAP_SELECT", + data, + ) + data = re.sub(r"#define\s*BOOT_ENTRANCE\s*[^\s]*", f"#define BOOT_ENTRANCE {entranceIndex}", data) + data = re.sub(r"#define\s*BOOT_AGE\s*[^\s]*", f"#define BOOT_AGE {linkAge}", data) + data = re.sub(r"#define\s*BOOT_TIME\s*[^\s]*", f"#define BOOT_TIME {timeOfDay}", data) + data = re.sub(r"#define\s*BOOT_CUTSCENE\s*[^\s]*", f"#define BOOT_CUTSCENE {cutsceneIndex}", data) + data = re.sub(r"#define\s*BOOT_PLAYER_NAME\s*[^\n]*", f"#define BOOT_PLAYER_NAME {saveFileNameData}", data) + + if data != originalData: + writeFile(configPath, data) + + @staticmethod + def setBootupScene(configPath: str, entranceIndex: str, options: "OOTBootupSceneOptions"): + # ``options`` argument type: OOTBootupSceneOptions + linkAge = "LINK_AGE_CHILD" + timeOfDay = "NEXT_TIME_NONE" + cutsceneIndex = "0xFFEF" + newEntranceIndex = "0" + saveName = "LINK" + + if options.bootMode != "Map Select": + newEntranceIndex = entranceIndex + saveName = options.newGameName + + if options.overrideHeader: + timeOfDay, linkAge = Config.getParamsFromOptions(options) + if options.headerOption == "Cutscene": + cutsceneIndex = "0xFFF" + format(options.cutsceneIndex - 4, "X") + + saveFileNameData = ", ".join(["0x" + format(i, "02X") for i in Config.stringToSaveNameBytes(saveName)]) + + Config.writeBootupSettings( + configPath, + options.bootMode, + options.newGameOnly, + newEntranceIndex, + linkAge, + timeOfDay, + cutsceneIndex, + saveFileNameData, + ) + + @staticmethod + def clearBootupScene(configPath: str): + Config.writeBootupSettings( + configPath, + "", + False, + "0", + "LINK_AGE_CHILD", + "NEXT_TIME_NONE", + "0xFFEF", + "0x15, 0x12, 0x17, 0x14, 0x3E, 0x3E, 0x3E, 0x3E", + ) + + @staticmethod + def getParamsFromOptions(options: "OOTBootupSceneOptions") -> tuple[str, str]: + timeOfDay = ( + "NEXT_TIME_DAY" + if options.headerOption == "Child Day" or options.headerOption == "Adult Day" + else "NEXT_TIME_NIGHT" + ) + + linkAge = ( + "LINK_AGE_ADULT" + if options.headerOption == "Adult Day" or options.headerOption == "Adult Night" + else "LINK_AGE_CHILD" + ) + + return timeOfDay, linkAge + + # converts ascii text to format for save file name. + # see src/code/z_message_PAL.c:Message_Decode() + @staticmethod + def stringToSaveNameBytes(name: str) -> bytearray: + specialChar = { + " ": 0x3E, + ".": 0x40, + "-": 0x3F, + } + + result = bytearray([0x3E] * 8) + + if len(name) > 8: + raise PluginError("Save file name for scene bootup must be 8 characters or less.") + for i in range(len(name)): + value = ord(name[i]) + if name[i] in specialChar: + result[i] = specialChar[name[i]] + elif value >= ord("0") and value <= ord("9"): # numbers + result[i] = value - ord("0") + elif value >= ord("A") and value <= ord("Z"): # uppercase + result[i] = value - ord("7") + elif value >= ord("a") and value <= ord("z"): # lowercase + result[i] = value - ord("=") + else: + raise PluginError( + name + " has some invalid characters and cannot be used as a save file name for scene bootup." + ) + + return result diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py index f62e16e66..e9a169c80 100644 --- a/fast64_internal/oot/exporter/room/main.py +++ b/fast64_internal/oot/exporter/room/main.py @@ -2,10 +2,10 @@ from typing import Optional from mathutils import Matrix from bpy.types import Object -from ....utility import PluginError, CData, indent +from ....utility import PluginError, CData, indent, readFile, writeFile from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings from ...room.properties import OOTRoomHeaderProperty -from ...oot_object import addMissingObjectsToAllRoomHeadersNew +from ...oot_object import addMissingObjectsToAllRoomHeaders from ...oot_level_classes import OOTRoomMesh from ...oot_model_classes import OOTModel, OOTGfxFormatter from ...oot_utility import CullGroup @@ -13,6 +13,7 @@ from ..utility import Utility, altHeaderList from .header import RoomAlternateHeader, RoomHeader from .shape import RoomShape +from .mesh import ootProcessMesh, BoundingBox @dataclass @@ -39,8 +40,6 @@ def new( sceneName: str, saveTexturesAsPNG: bool, ): - from ...oot_level_writer import BoundingBox, ootProcessMesh # circular import fix - i = 0 mainHeaderProps = roomObj.ootRoomHeader altHeader = RoomAlternateHeader(f"{name}_alternateHeaders") @@ -95,7 +94,7 @@ def new( headers.extend([altHeader.childNight, altHeader.adultDay, altHeader.adultNight]) if len(altHeader.cutscenes) > 0: headers.extend(altHeader.cutscenes) - addMissingObjectsToAllRoomHeadersNew(roomObj, headers) + addMissingObjectsToAllRoomHeaders(roomObj, headers) # Mesh stuff mesh = OOTRoomMesh(name, roomShapeType, model) diff --git a/fast64_internal/oot/exporter/room/mesh.py b/fast64_internal/oot/exporter/room/mesh.py new file mode 100644 index 000000000..abd7e8726 --- /dev/null +++ b/fast64_internal/oot/exporter/room/mesh.py @@ -0,0 +1,187 @@ +import os, bpy, mathutils +from ....f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict + +from ....utility import ( + PluginError, + toAlnum, +) + +from ...oot_utility import ( + CullGroup, + checkUniformScale, + ootConvertTranslation, +) + +from ...oot_level_classes import OOTDLGroup + + +class BoundingBox: + def __init__(self): + self.minPoint = None + self.maxPoint = None + self.points = [] + + def addPoint(self, point: tuple[float, float, float]): + if self.minPoint is None: + self.minPoint = list(point[:]) + else: + for i in range(3): + if point[i] < self.minPoint[i]: + self.minPoint[i] = point[i] + if self.maxPoint is None: + self.maxPoint = list(point[:]) + else: + for i in range(3): + if point[i] > self.maxPoint[i]: + self.maxPoint[i] = point[i] + self.points.append(point) + + def addMeshObj(self, obj: bpy.types.Object, transform: mathutils.Matrix): + mesh = obj.data + for vertex in mesh.vertices: + self.addPoint(transform @ vertex.co) + + def getEnclosingSphere(self) -> tuple[float, float]: + centroid = (mathutils.Vector(self.minPoint) + mathutils.Vector(self.maxPoint)) / 2 + radius = 0 + for point in self.points: + distance = (mathutils.Vector(point) - centroid).length + if distance > radius: + radius = distance + + # print(f"Radius: {radius}, Centroid: {centroid}") + + transformedCentroid = [round(value) for value in centroid] + transformedRadius = round(radius) + return transformedCentroid, transformedRadius + + +# This function should be called on a copy of an object +# The copy will have modifiers / scale applied and will be made single user +# When we duplicated obj hierarchy we stripped all ignore_renders from hierarchy. +def ootProcessMesh( + roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox +): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + translation, rotation, scale = relativeTransform.decompose() + + if obj.type == "EMPTY" and obj.ootEmptyType == "Cull Group": + if LODHierarchyObject is not None: + raise PluginError( + obj.name + + " cannot be used as a cull group because it is " + + "in the sub-hierarchy of the LOD group empty " + + LODHierarchyObject.name + ) + + cullProp = obj.ootCullGroupProperty + checkUniformScale(scale, obj) + DLGroup = roomMesh.addMeshGroup( + CullGroup( + ootConvertTranslation(translation), + scale if cullProp.sizeControlsCull else [cullProp.manualRadius], + obj.empty_display_size if cullProp.sizeControlsCull else 1, + ) + ).DLGroup + + elif obj.type == "MESH" and not obj.ignore_render: + triConverterInfo = TriangleConverterInfo(obj, None, roomMesh.model.f3d, relativeTransform, getInfoDict(obj)) + fMeshes = saveStaticModel( + triConverterInfo, + roomMesh.model, + obj, + relativeTransform, + roomMesh.model.name, + convertTextureData, + False, + "oot", + ) + if fMeshes is not None: + for drawLayer, fMesh in fMeshes.items(): + DLGroup.addDLCall(fMesh.draw, drawLayer) + + boundingBox.addMeshObj(obj, relativeTransform) + + alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) + for childObj in alphabeticalChildren: + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": + ootProcessLOD( + roomMesh, + DLGroup, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + else: + ootProcessMesh( + roomMesh, + DLGroup, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + + +def ootProcessLOD( + roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox +): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + translation, rotation, scale = relativeTransform.decompose() + ootTranslation = ootConvertTranslation(translation) + + LODHierarchyObject = obj + name = toAlnum(roomMesh.model.name + "_" + obj.name + "_lod") + opaqueLOD = roomMesh.model.addLODGroup(name + "_opaque", ootTranslation, obj.f3d_lod_always_render_farthest) + transparentLOD = roomMesh.model.addLODGroup( + name + "_transparent", ootTranslation, obj.f3d_lod_always_render_farthest + ) + + index = 0 + for childObj in obj.children: + # This group will not be converted to C directly, but its display lists will be converted through the FLODGroup. + childDLGroup = OOTDLGroup(name + str(index), roomMesh.model.DLFormat) + index += 1 + + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": + ootProcessLOD( + roomMesh, + childDLGroup, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + else: + ootProcessMesh( + roomMesh, + childDLGroup, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + + # We handle case with no geometry, for the cases where we have "gaps" in the LOD hierarchy. + # This can happen if a LOD does not use transparency while the levels above and below it does. + childDLGroup.createDLs() + childDLGroup.terminateDLs() + + # Add lod AFTER processing hierarchy, so that DLs will be built by then + opaqueLOD.add_lod(childDLGroup.opaque, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) + transparentLOD.add_lod(childDLGroup.transparent, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) + + opaqueLOD.create_data() + transparentLOD.create_data() + + DLGroup.addDLCall(opaqueLOD.draw, "Opaque") + DLGroup.addDLCall(transparentLOD.draw, "Transparent") diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 6a218202f..1f8f8bdec 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -2,8 +2,7 @@ from typing import Optional from ....utility import PluginError, CData, toAlnum, indent from ....f3d.f3d_gbi import TextureExportSettings -from ...oot_level_classes import OOTRoomMesh -from ...oot_level_classes import OOTBGImage +from ...oot_level_classes import OOTRoomMesh, OOTBGImage from ...room.properties import OOTRoomHeaderProperty diff --git a/fast64_internal/oot/oot_level_classes.py b/fast64_internal/oot/oot_level_classes.py index 4844f1b2f..c5b27d85b 100644 --- a/fast64_internal/oot/oot_level_classes.py +++ b/fast64_internal/oot/oot_level_classes.py @@ -2,11 +2,7 @@ import os import shutil -from typing import Optional -from bpy.types import Object from ..utility import PluginError, toAlnum, indent -from .collision.exporter import OOTCollision -from .oot_model_classes import OOTModel from ..f3d.f3d_gbi import ( SPDisplayList, SPEndDisplayList, @@ -16,222 +12,6 @@ from ..f3d.occlusion_planes.exporter import OcclusionPlaneCandidatesList -class OOTCommonCommands: - def getAltHeaderListCmd(self, altName): - return indent + f"SCENE_CMD_ALTERNATE_HEADER_LIST({altName}),\n" - - def getEndCmd(self): - return indent + "SCENE_CMD_END(),\n" - - -class OOTActor: - def __init__(self, actorName, actorID, position, rotation, actorParam): - self.actorName = actorName - self.actorID = actorID - self.actorParam = actorParam - self.position = position - self.rotation = rotation - - -class OOTTransitionActor: - def __init__(self, actorName, actorID, frontRoom, backRoom, frontCam, backCam, position, rotationY, actorParam): - self.actorName = actorName - self.actorID = actorID - self.actorParam = actorParam - self.frontRoom = frontRoom - self.backRoom = backRoom - self.frontCam = frontCam - self.backCam = backCam - self.position = position - self.rotationY = rotationY - - -class OOTExit: - def __init__(self, index): - self.index = index - - -class OOTEntrance: - def __init__(self, roomIndex, startPositionIndex): - self.roomIndex = roomIndex - self.startPositionIndex = startPositionIndex - - -class OOTLight: - def __init__(self): - self.ambient = (0, 0, 0) - self.diffuse0 = (0, 0, 0) - self.diffuseDir0 = (0, 0, 0) - self.diffuse1 = (0, 0, 0) - self.diffuseDir1 = (0, 0, 0) - self.fogColor = (0, 0, 0) - self.fogNear = 0 - self.fogFar = 0 - self.transitionSpeed = 0 - - def getBlendFogNear(self): - return f"(({self.transitionSpeed} << 10) | {self.fogNear})" - - -class OOTSceneTableEntry: - def __init__(self): - self.drawConfig = 0 - - -class OOTScene(OOTCommonCommands): - def __init__(self, name, model): - self.name: str = toAlnum(name) - self.write_dummy_room_list = False - self.rooms = {} - self.transitionActorList = set() - self.entranceList = set() - self.startPositions = {} - self.lights = [] - self.model = model - self.collision = OOTCollision(self.name) - - self.globalObject = None - self.naviCup = None - - # Skybox - self.skyboxID = None - self.skyboxCloudiness = None - self.skyboxLighting = None - self.isSkyboxLightingCustom = False - - # Camera - self.mapLocation = None - self.cameraMode = None - - self.musicSeq = None - self.nightSeq = None - - self.childNightHeader: Optional[OOTScene] = None - self.adultDayHeader: Optional[OOTScene] = None - self.adultNightHeader: Optional[OOTScene] = None - self.cutsceneHeaders: list["OOTScene"] = [] - - self.exitList = [] - self.pathList = set() - self.cameraList = [] - - self.writeCutscene = False - self.csWriteType = "Object" - self.csName = "" - self.csWriteCustom = "" - self.extraCutscenes: list[Object] = [] - - self.sceneTableEntry = OOTSceneTableEntry() - - def getAlternateHeaderScene(self, name): - scene = OOTScene(name, self.model) - scene.write_dummy_room_list = self.write_dummy_room_list - scene.rooms = self.rooms - scene.collision = self.collision - scene.exitList = [] - scene.pathList = set() - scene.cameraList = self.cameraList - return scene - - def sceneName(self): - return self.name + "_scene" - - def roomListName(self): - return self.sceneName() + "_roomList" - - def entranceListName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_entranceList" - - def startPositionsName(self, headerIndex): - return f"{self.sceneName()}_header{headerIndex:02}_playerEntryList" - - def exitListName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_exitList" - - def lightListName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_lightSettings" - - def transitionActorListName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_transitionActors" - - def pathListName(self, headerIndex: int): - return self.sceneName() + "_pathway" + str(headerIndex) - - def cameraListName(self): - return self.sceneName() + "_cameraList" - - def alternateHeadersName(self): - return self.sceneName() + "_alternateHeaders" - - def hasAlternateHeaders(self): - return not ( - self.childNightHeader == None - and self.adultDayHeader == None - and self.adultNightHeader == None - and len(self.cutsceneHeaders) == 0 - ) - - def validateIndices(self): - self.collision.cameraData.validateCamPositions() - self.validateStartPositions() - self.validateRoomIndices() - - def validateStartPositions(self): - count = 0 - while count < len(self.startPositions): - if count not in self.startPositions: - raise PluginError( - "Error: Entrances (start positions) do not have a consecutive list of indices. " - + "Missing index: " - + str(count) - ) - count = count + 1 - - def validateRoomIndices(self): - count = 0 - while count < len(self.rooms): - if count not in self.rooms: - raise PluginError( - "Error: Room indices do not have a consecutive list of indices. " + "Missing index: " + str(count) - ) - count = count + 1 - - def validatePathIndices(self): - count = 0 - while count < len(self.pathList): - if count not in self.pathList: - raise PluginError( - "Error: Path list does not have a consecutive list of indices.\n" + "Missing index: " + str(count) - ) - count = count + 1 - - def addRoom(self, roomIndex, roomName, roomShape): - roomModel = self.model.addSubModel(OOTModel(roomName + "_dl", self.model.DLFormat, None)) - room = OOTRoom(roomIndex, roomName, roomModel, roomShape) - if roomIndex in self.rooms: - raise PluginError("Repeat room index " + str(roomIndex) + " for " + str(roomName)) - self.rooms[roomIndex] = room - return room - - def sortEntrances(self): - self.entranceList = sorted(self.entranceList, key=lambda x: x.startPositionIndex) - if self.appendNullEntrance: - self.entranceList.append(OOTEntrance(0, 0)) - - if self.childNightHeader is not None: - self.childNightHeader.sortEntrances() - if self.adultDayHeader is not None: - self.adultDayHeader.sortEntrances() - if self.adultNightHeader is not None: - self.adultNightHeader.sortEntrances() - for header in self.cutsceneHeaders: - header.sortEntrances() - - def copyBgImages(self, exportPath: str): - for i in range(len(self.rooms)): - self.rooms[i].mesh.copyBgImages(exportPath) - - class OOTBGImage: def __init__(self, name: str, image: bpy.types.Image, otherModeFlags: str): self.name = name @@ -369,153 +149,3 @@ def __init__(self, cullGroup, DLFormat, roomName, entryIndex): def entryName(self): return self.roomName + "_entry_" + str(self.entryIndex) - - -class OOTRoom(OOTCommonCommands): - def __init__(self, index, name, model, roomShape): - self.ownerName = toAlnum(name) - self.index = index - self.actorList = set() - self.occlusion_planes = OcclusionPlaneCandidatesList(self.roomName()) - self.mesh = OOTRoomMesh(self.roomName(), roomShape, model) - - # Room behaviour - self.roomBehaviour = None - self.disableWarpSongs = False - self.showInvisibleActors = False - self.linkIdleMode = None - - # Wind - self.setWind = False - self.windVector = [0, 0, 0] - self.windStrength = 0 - - # Time - self.timeHours = 0x00 - self.timeMinutes = 0x00 - self.timeSpeed = 0xA - - # Skybox - self.disableSkybox = False - self.disableSunMoon = False - - # Echo - self.echo = 0x00 - - self.objectIDList = [] - - self.childNightHeader = None - self.adultDayHeader = None - self.adultNightHeader = None - self.cutsceneHeaders = [] - - self.appendNullEntrance = False - - def getAlternateHeaderRoom(self, name): - room = OOTRoom(self.index, name, self.mesh.model, self.mesh.roomShape) - room.mesh = self.mesh - return room - - def roomName(self): - return self.ownerName + "_room_" + str(self.index) - - def objectListName(self, headerIndex): - return self.roomName() + "_header" + format(headerIndex, "02") + "_objectList" - - def actorListName(self, headerIndex): - return self.roomName() + "_header" + format(headerIndex, "02") + "_actorList" - - def alternateHeadersName(self): - return self.roomName() + "_alternateHeaders" - - def hasAlternateHeaders(self): - return not ( - self.childNightHeader == None - and self.adultDayHeader == None - and self.adultNightHeader == None - and len(self.cutsceneHeaders) == 0 - ) - - def getObjectLengthDefineName(self, headerIndex: int): - return f"LENGTH_{self.objectListName(headerIndex).upper()}" - - def getActorLengthDefineName(self, headerIndex: int): - return f"LENGTH_{self.actorListName(headerIndex).upper()}" - - -def addActor(owner, actor, actorProp, propName, actorObjName): - sceneSetup = actorProp.headerSettings - if ( - sceneSetup.sceneSetupPreset == "All Scene Setups" - or sceneSetup.sceneSetupPreset == "All Non-Cutscene Scene Setups" - ): - getattr(owner, propName).add(actor) - if owner.childNightHeader is not None: - getattr(owner.childNightHeader, propName).add(actor) - if owner.adultDayHeader is not None: - getattr(owner.adultDayHeader, propName).add(actor) - if owner.adultNightHeader is not None: - getattr(owner.adultNightHeader, propName).add(actor) - if sceneSetup.sceneSetupPreset == "All Scene Setups": - for cutsceneHeader in owner.cutsceneHeaders: - getattr(cutsceneHeader, propName).add(actor) - elif sceneSetup.sceneSetupPreset == "Custom": - if sceneSetup.childDayHeader and owner is not None: - getattr(owner, propName).add(actor) - if sceneSetup.childNightHeader and owner.childNightHeader is not None: - getattr(owner.childNightHeader, propName).add(actor) - if sceneSetup.adultDayHeader and owner.adultDayHeader is not None: - getattr(owner.adultDayHeader, propName).add(actor) - if sceneSetup.adultNightHeader and owner.adultNightHeader is not None: - getattr(owner.adultNightHeader, propName).add(actor) - for cutsceneHeader in sceneSetup.cutsceneHeaders: - if cutsceneHeader.headerIndex >= len(owner.cutsceneHeaders) + 4: - raise PluginError( - actorObjName - + " uses a cutscene header index that is outside the range of the current number of cutscene headers." - ) - getattr(owner.cutsceneHeaders[cutsceneHeader.headerIndex - 4], propName).add(actor) - else: - raise PluginError("Unhandled scene setup preset: " + str(sceneSetup.sceneSetupPreset)) - - -def addStartPosition(scene, index, actor, actorProp, actorObjName): - sceneSetup = actorProp.headerSettings - if ( - sceneSetup.sceneSetupPreset == "All Scene Setups" - or sceneSetup.sceneSetupPreset == "All Non-Cutscene Scene Setups" - ): - addStartPosAtIndex(scene.startPositions, index, actor) - if scene.childNightHeader is not None: - addStartPosAtIndex(scene.childNightHeader.startPositions, index, actor) - if scene.adultDayHeader is not None: - addStartPosAtIndex(scene.adultDayHeader.startPositions, index, actor) - if scene.adultNightHeader is not None: - addStartPosAtIndex(scene.adultNightHeader.startPositions, index, actor) - if sceneSetup.sceneSetupPreset == "All Scene Setups": - for cutsceneHeader in scene.cutsceneHeaders: - addStartPosAtIndex(cutsceneHeader.startPositions, index, actor) - elif sceneSetup.sceneSetupPreset == "Custom": - if sceneSetup.childDayHeader and scene is not None: - addStartPosAtIndex(scene.startPositions, index, actor) - if sceneSetup.childNightHeader and scene.childNightHeader is not None: - addStartPosAtIndex(scene.childNightHeader.startPositions, index, actor) - if sceneSetup.adultDayHeader and scene.adultDayHeader is not None: - addStartPosAtIndex(scene.adultDayHeader.startPositions, index, actor) - if sceneSetup.adultNightHeader and scene.adultNightHeader is not None: - addStartPosAtIndex(scene.adultNightHeader.startPositions, index, actor) - for cutsceneHeader in sceneSetup.cutsceneHeaders: - if cutsceneHeader.headerIndex >= len(scene.cutsceneHeaders) + 4: - raise PluginError( - actorObjName - + " uses a cutscene header index that is outside the range of the current number of cutscene headers." - ) - addStartPosAtIndex(scene.cutsceneHeaders[cutsceneHeader.headerIndex - 4].startPositions, index, actor) - else: - raise PluginError("Unhandled scene setup preset: " + str(sceneSetup.sceneSetupPreset)) - - -def addStartPosAtIndex(startPosDict, index, value): - if index in startPosDict: - raise PluginError("Error: Repeated start position spawn index: " + str(index)) - startPosDict[index] = value diff --git a/fast64_internal/oot/oot_level_parser.py b/fast64_internal/oot/oot_level_parser.py index d966e103d..50137c01d 100644 --- a/fast64_internal/oot/oot_level_parser.py +++ b/fast64_internal/oot/oot_level_parser.py @@ -8,7 +8,7 @@ from .collision.properties import OOTMaterialCollisionProperty from .oot_model_classes import OOTF3DContext from .oot_f3d_writer import getColliderMat -from .scene.exporter.to_c import getDrawConfig +from .exporter.decomp_edit.scene_table import SceneTableUtility from .scene.properties import OOTSceneHeaderProperty, OOTLightProperty, OOTImportSceneSettingsProperty from .room.properties import OOTRoomHeaderProperty from .actor.properties import OOTActorProperty, OOTActorHeaderProperty @@ -241,7 +241,7 @@ def parseScene( f3dContext.addMatrix("&gMtxClear", mathutils.Matrix.Scale(1 / bpy.context.scene.ootBlenderScale, 4)) if not settings.isCustomDest: - drawConfigName = getDrawConfig(sceneName) + drawConfigName = SceneTableUtility.get_draw_config(sceneName) drawConfigData = readFile(os.path.join(importPath, "src/code/z_scene_table.c")) parseDrawConfig(drawConfigName, sceneData, drawConfigData, f3dContext) @@ -273,7 +273,10 @@ def parseScene( if not settings.isCustomDest: setCustomProperty( - sceneObj.ootSceneHeader.sceneTableEntry, "drawConfig", getDrawConfig(sceneName), ootEnumDrawConfig + sceneObj.ootSceneHeader.sceneTableEntry, + "drawConfig", + SceneTableUtility.get_draw_config(sceneName), + ootEnumDrawConfig, ) if bpy.context.scene.fast64.oot.headerTabAffectsVisibility: diff --git a/fast64_internal/oot/oot_level_writer.py b/fast64_internal/oot/oot_level_writer.py deleted file mode 100644 index 1e50ac51c..000000000 --- a/fast64_internal/oot/oot_level_writer.py +++ /dev/null @@ -1,903 +0,0 @@ -import bpy, os, math, mathutils -from ..f3d.f3d_gbi import TextureExportSettings -from ..f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict -from .scene.properties import OOTSceneProperties, OOTSceneHeaderProperty, OOTAlternateSceneHeaderProperty -from .room.properties import OOTRoomHeaderProperty, OOTAlternateRoomHeaderProperty -from .oot_constants import ootData -from .oot_spline import assertCurveValid, ootConvertPath -from .oot_model_classes import OOTModel -from .oot_object import addMissingObjectsToAllRoomHeaders -from .oot_f3d_writer import writeTextureArraysNew, writeTextureArraysExisting1D -from .collision.constants import decomp_compat_map_CameraSType -from ..f3d.occlusion_planes.exporter import addOcclusionQuads - -from .collision.exporter import ( - OOTCameraData, - OOTCameraPosData, - OOTWaterBox, - OOTCrawlspaceData, - exportCollisionCommon, -) - -from ..utility import ( - PluginError, - CData, - checkIdentityRotation, - restoreHiddenState, - unhideAllAndGetHiddenState, - ootGetBaseOrCustomLight, - exportColor, - toAlnum, - checkObjectReference, - writeCDataSourceOnly, - writeCDataHeaderOnly, - readFile, - writeFile, -) - -from .scene.exporter.to_c import ( - setBootupScene, - getIncludes, - getSceneC, - modifySceneTable, - editSpecFile, - modifySceneFiles, -) - -from .oot_utility import ( - OOTObjectCategorizer, - CullGroup, - checkUniformScale, - ootDuplicateHierarchy, - ootCleanupScene, - ootGetPath, - getCustomProperty, - ootConvertTranslation, - ootConvertRotation, - getSceneDirFromLevelName, - isPathObject, -) - -from .oot_level_classes import ( - OOTLight, - OOTExit, - OOTScene, - OOTRoom, - OOTActor, - OOTTransitionActor, - OOTEntrance, - OOTDLGroup, - OOTBGImage, - addActor, - addStartPosition, -) - - -def ootPreprendSceneIncludes(scene, file): - exportFile = getIncludes(scene) - exportFile.append(file) - return exportFile - - -def ootCreateSceneHeader(levelC): - sceneHeader = CData() - - sceneHeader.append(levelC.sceneMainC) - if levelC.sceneTexturesIsUsed(): - sceneHeader.append(levelC.sceneTexturesC) - sceneHeader.append(levelC.sceneCollisionC) - if levelC.sceneCutscenesIsUsed(): - for i in range(len(levelC.sceneCutscenesC)): - sceneHeader.append(levelC.sceneCutscenesC[i]) - for roomName, roomMainC in levelC.roomMainC.items(): - sceneHeader.append(roomMainC) - for roomName, roomOcclusionPlanesC in levelC.roomOcclusionPlanesC.items(): - sceneHeader.append(roomOcclusionPlanesC) - for roomName, roomShapeInfoC in levelC.roomShapeInfoC.items(): - sceneHeader.append(roomShapeInfoC) - for roomName, roomModelC in levelC.roomModelC.items(): - sceneHeader.append(roomModelC) - - return sceneHeader - - -def ootCombineSceneFiles(levelC): - sceneC = CData() - - sceneC.append(levelC.sceneMainC) - if levelC.sceneTexturesIsUsed(): - sceneC.append(levelC.sceneTexturesC) - sceneC.append(levelC.sceneCollisionC) - if levelC.sceneCutscenesIsUsed(): - for i in range(len(levelC.sceneCutscenesC)): - sceneC.append(levelC.sceneCutscenesC[i]) - return sceneC - - -def ootExportSceneToC(originalSceneObj, transformMatrix, sceneName, DLFormat, savePNG, exportInfo, bootToSceneOptions): - checkObjectReference(originalSceneObj, "Scene object") - isCustomExport = exportInfo.isCustomExportPath - exportPath = exportInfo.exportPath - - scene = ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, not savePNG) - - exportSubdir = "" - if exportInfo.customSubPath is not None: - exportSubdir = exportInfo.customSubPath - if not isCustomExport and exportInfo.customSubPath is None: - exportSubdir = os.path.dirname(getSceneDirFromLevelName(sceneName)) - - roomObjList = [ - obj for obj in originalSceneObj.children_recursive if obj.type == "EMPTY" and obj.ootEmptyType == "Room" - ] - for roomObj in roomObjList: - room = scene.rooms[roomObj.ootRoomHeader.roomIndex] - addMissingObjectsToAllRoomHeaders(roomObj, room, ootData) - - sceneInclude = exportSubdir + "/" + sceneName + "/" - levelPath = ootGetPath(exportPath, isCustomExport, exportSubdir, sceneName, True, True) - levelC = getSceneC(scene, TextureExportSettings(False, savePNG, sceneInclude, levelPath)) - - if not isCustomExport: - writeTextureArraysExistingScene(scene.model, exportPath, sceneInclude + sceneName + "_scene.h") - else: - textureArrayData = writeTextureArraysNew(scene.model, None) - levelC.sceneTexturesC.append(textureArrayData) - - if bpy.context.scene.ootSceneExportSettings.singleFile: - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, ootCombineSceneFiles(levelC)), - os.path.join(levelPath, scene.sceneName() + ".c"), - ) - for i in range(len(scene.rooms)): - roomC = CData() - roomC.append(levelC.roomMainC[scene.rooms[i].roomName()]) - roomC.append(levelC.roomOcclusionPlanesC[scene.rooms[i].roomName()]) - roomC.append(levelC.roomShapeInfoC[scene.rooms[i].roomName()]) - roomC.append(levelC.roomModelC[scene.rooms[i].roomName()]) - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, roomC), os.path.join(levelPath, scene.rooms[i].roomName() + ".c") - ) - else: - # Export the scene segment .c files - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, levelC.sceneMainC), os.path.join(levelPath, scene.sceneName() + "_main.c") - ) - if levelC.sceneTexturesIsUsed(): - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, levelC.sceneTexturesC), - os.path.join(levelPath, scene.sceneName() + "_tex.c"), - ) - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, levelC.sceneCollisionC), - os.path.join(levelPath, scene.sceneName() + "_col.c"), - ) - if levelC.sceneCutscenesIsUsed(): - for i in range(len(levelC.sceneCutscenesC)): - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, levelC.sceneCutscenesC[i]), - os.path.join(levelPath, scene.sceneName() + "_cs_" + str(i) + ".c"), - ) - - # Export the room segment .c files - for roomName, roomMainC in levelC.roomMainC.items(): - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, roomMainC), os.path.join(levelPath, roomName + "_main.c") - ) - for roomName, roomOcclusionPlanesC in levelC.roomOcclusionPlanesC.items(): - if len(roomOcclusionPlanesC.source) > 0: - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, roomOcclusionPlanesC), os.path.join(levelPath, roomName + "_occ.c") - ) - for roomName, roomShapeInfoC in levelC.roomShapeInfoC.items(): - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, roomShapeInfoC), os.path.join(levelPath, roomName + "_model_info.c") - ) - for roomName, roomModelC in levelC.roomModelC.items(): - writeCDataSourceOnly( - ootPreprendSceneIncludes(scene, roomModelC), os.path.join(levelPath, roomName + "_model.c") - ) - - # Export the scene .h file - writeCDataHeaderOnly(ootCreateSceneHeader(levelC), os.path.join(levelPath, scene.sceneName() + ".h")) - - # Copy bg images - scene.copyBgImages(levelPath) - - if not isCustomExport: - writeOtherSceneProperties(scene, exportInfo, levelC) - - if bootToSceneOptions is not None and bootToSceneOptions.bootToScene: - setBootupScene( - os.path.join(exportPath, "include/config/config_debug.h") - if not isCustomExport - else os.path.join(levelPath, "config_bootup.h"), - "ENTR_" + sceneName.upper() + "_" + str(bootToSceneOptions.spawnIndex), - bootToSceneOptions, - ) - - -def writeTextureArraysExistingScene(fModel: OOTModel, exportPath: str, sceneInclude: str): - drawConfigPath = os.path.join(exportPath, "src/code/z_scene_table.c") - drawConfigData = readFile(drawConfigPath) - newData = drawConfigData - - if f'#include "{sceneInclude}"' not in newData: - additionalIncludes = f'#include "{sceneInclude}"\n' - else: - additionalIncludes = "" - - for flipbook in fModel.flipbooks: - if flipbook.exportMode == "Array": - newData = writeTextureArraysExisting1D(newData, flipbook, additionalIncludes) - else: - raise PluginError("Scenes can only use array flipbooks.") - - if newData != drawConfigData: - writeFile(drawConfigPath, newData) - - -def writeOtherSceneProperties(scene, exportInfo, levelC): - modifySceneTable(scene, exportInfo) - editSpecFile( - True, - exportInfo, - levelC.sceneTexturesIsUsed(), - levelC.sceneCutscenesIsUsed(), - len(scene.rooms), - len(levelC.sceneCutscenesC), - [len(occ.source) > 0 for occ in levelC.roomOcclusionPlanesC.values()], - ) - modifySceneFiles(scene, exportInfo) - - -def readSceneData( - scene: OOTScene, - scene_properties: OOTSceneProperties, - sceneHeader: OOTSceneHeaderProperty, - alternateSceneHeaders: OOTAlternateSceneHeaderProperty, -): - scene.write_dummy_room_list = scene_properties.write_dummy_room_list - scene.sceneTableEntry.drawConfig = getCustomProperty(sceneHeader.sceneTableEntry, "drawConfig") - scene.globalObject = getCustomProperty(sceneHeader, "globalObject") - scene.naviCup = getCustomProperty(sceneHeader, "naviCup") - scene.skyboxID = getCustomProperty(sceneHeader, "skyboxID") - scene.skyboxCloudiness = getCustomProperty(sceneHeader, "skyboxCloudiness") - scene.skyboxLighting = getCustomProperty(sceneHeader, "skyboxLighting") - scene.isSkyboxLightingCustom = sceneHeader.skyboxLighting == "Custom" - scene.mapLocation = getCustomProperty(sceneHeader, "mapLocation") - scene.cameraMode = getCustomProperty(sceneHeader, "cameraMode") - scene.musicSeq = getCustomProperty(sceneHeader, "musicSeq") - scene.nightSeq = getCustomProperty(sceneHeader, "nightSeq") - scene.audioSessionPreset = getCustomProperty(sceneHeader, "audioSessionPreset") - scene.appendNullEntrance = sceneHeader.appendNullEntrance - - if ( - sceneHeader.skyboxLighting == "0x00" - or sceneHeader.skyboxLighting == "0" - or sceneHeader.skyboxLighting == "LIGHT_MODE_TIME" - ): # Time of Day - scene.lights.append(getLightData(sceneHeader.timeOfDayLights.dawn)) - scene.lights.append(getLightData(sceneHeader.timeOfDayLights.day)) - scene.lights.append(getLightData(sceneHeader.timeOfDayLights.dusk)) - scene.lights.append(getLightData(sceneHeader.timeOfDayLights.night)) - else: - for lightProp in sceneHeader.lightList: - scene.lights.append(getLightData(lightProp)) - - for exitProp in sceneHeader.exitList: - scene.exitList.append(getExitData(exitProp)) - - scene.writeCutscene = getCustomProperty(sceneHeader, "writeCutscene") - if scene.writeCutscene: - scene.csWriteType = getattr(sceneHeader, "csWriteType") - - if scene.csWriteType == "Custom": - scene.csWriteCustom = getCustomProperty(sceneHeader, "csWriteCustom") - else: - if sceneHeader.csWriteObject is None: - raise PluginError("No object selected for cutscene reference") - elif sceneHeader.csWriteObject.ootEmptyType != "Cutscene": - raise PluginError("Object selected as cutscene is wrong type, must be empty with Cutscene type") - elif sceneHeader.csWriteObject.parent is not None: - raise PluginError("Cutscene empty object should not be parented to anything") - else: - scene.csName = sceneHeader.csWriteObject.name.removeprefix("Cutscene.") - - if alternateSceneHeaders is not None: - scene.collision.cameraData = OOTCameraData(scene.name) - - if not alternateSceneHeaders.childNightHeader.usePreviousHeader: - scene.childNightHeader = scene.getAlternateHeaderScene(scene.name) - readSceneData(scene.childNightHeader, scene_properties, alternateSceneHeaders.childNightHeader, None) - - if not alternateSceneHeaders.adultDayHeader.usePreviousHeader: - scene.adultDayHeader = scene.getAlternateHeaderScene(scene.name) - readSceneData(scene.adultDayHeader, scene_properties, alternateSceneHeaders.adultDayHeader, None) - - if not alternateSceneHeaders.adultNightHeader.usePreviousHeader: - scene.adultNightHeader = scene.getAlternateHeaderScene(scene.name) - readSceneData(scene.adultNightHeader, scene_properties, alternateSceneHeaders.adultNightHeader, None) - - for i in range(len(alternateSceneHeaders.cutsceneHeaders)): - cutsceneHeaderProp = alternateSceneHeaders.cutsceneHeaders[i] - cutsceneHeader = scene.getAlternateHeaderScene(scene.name) - readSceneData(cutsceneHeader, scene_properties, cutsceneHeaderProp, None) - scene.cutsceneHeaders.append(cutsceneHeader) - - for extraCS in sceneHeader.extraCutscenes: - scene.extraCutscenes.append(extraCS.csObject) - else: - if len(sceneHeader.extraCutscenes) > 0: - raise PluginError( - "Extra cutscenes (not in any header) only belong in the main scene, not alternate headers" - ) - - -def getConvertedTransform(transformMatrix, sceneObj, obj, handleOrientation): - # Hacky solution to handle Z-up to Y-up conversion - # We cannot apply rotation to empty, as that modifies scale - if handleOrientation: - orientation = mathutils.Quaternion((1, 0, 0), math.radians(90.0)) - else: - orientation = mathutils.Matrix.Identity(4) - return getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation) - - -def getConvertedTransformWithOrientation(transformMatrix, sceneObj, obj, orientation): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - blenderTranslation, blenderRotation, scale = relativeTransform.decompose() - rotation = blenderRotation @ orientation - convertedTranslation = ootConvertTranslation(blenderTranslation) - convertedRotation = ootConvertRotation(rotation) - - return convertedTranslation, convertedRotation, scale, rotation - - -def getExitData(exitProp): - if exitProp.exitIndex != "Custom": - raise PluginError("Exit index enums not implemented yet.") - return OOTExit(exitProp.exitIndexCustom) - - -def getLightData(lightProp): - light = OOTLight() - light.ambient = exportColor(lightProp.ambient) - light.diffuse0, light.diffuseDir0 = ootGetBaseOrCustomLight(lightProp, 0, True, True) - light.diffuse1, light.diffuseDir1 = ootGetBaseOrCustomLight(lightProp, 1, True, True) - light.fogColor = exportColor(lightProp.fogColor) - light.fogNear = lightProp.fogNear - light.transitionSpeed = lightProp.transitionSpeed - light.fogFar = lightProp.fogFar - return light - - -def readRoomData( - sceneName: str, - room: OOTRoom, - roomHeader: OOTRoomHeaderProperty, - alternateRoomHeaders: OOTAlternateRoomHeaderProperty, -): - room.roomIndex = roomHeader.roomIndex - room.roomBehaviour = getCustomProperty(roomHeader, "roomBehaviour") - room.disableWarpSongs = roomHeader.disableWarpSongs - room.showInvisibleActors = roomHeader.showInvisibleActors - - # room heat behavior is active if the idle mode is 0x03 - room.linkIdleMode = getCustomProperty(roomHeader, "linkIdleMode") if not roomHeader.roomIsHot else "0x03" - - room.linkIdleModeCustom = roomHeader.linkIdleModeCustom - room.setWind = roomHeader.setWind - room.windVector = roomHeader.windVector[:] - room.windStrength = roomHeader.windStrength - if roomHeader.leaveTimeUnchanged: - room.timeHours = "0xFF" - room.timeMinutes = "0xFF" - else: - room.timeHours = roomHeader.timeHours - room.timeMinutes = roomHeader.timeMinutes - room.timeSpeed = max(-128, min(127, int(round(roomHeader.timeSpeed * 0xA)))) - room.disableSkybox = roomHeader.disableSkybox - room.disableSunMoon = roomHeader.disableSunMoon - room.echo = roomHeader.echo - - for obj in roomHeader.objectList: - # export using the key if the legacy prop isn't present - if "objectID" not in obj: - if obj.objectKey != "Custom": - objectID = ootData.objectData.objectsByKey[obj.objectKey].id - else: - objectID = obj.objectIDCustom - else: - objectID = ootData.objectData.ootEnumObjectIDLegacy[obj["objectID"]][0] - if objectID == "Custom": - objectID = obj.objectIDCustom - - room.objectIDList.append(objectID) - - if len(room.objectIDList) > 16: - print( - "Warning: A room can only have a maximum of 16 objects in its object list, unless more memory is allocated in code.", - ) - - if alternateRoomHeaders is not None: - if not alternateRoomHeaders.childNightHeader.usePreviousHeader: - room.childNightHeader = room.getAlternateHeaderRoom(room.ownerName) - readRoomData(sceneName, room.childNightHeader, alternateRoomHeaders.childNightHeader, None) - - if not alternateRoomHeaders.adultDayHeader.usePreviousHeader: - room.adultDayHeader = room.getAlternateHeaderRoom(room.ownerName) - readRoomData(sceneName, room.adultDayHeader, alternateRoomHeaders.adultDayHeader, None) - - if not alternateRoomHeaders.adultNightHeader.usePreviousHeader: - room.adultNightHeader = room.getAlternateHeaderRoom(room.ownerName) - readRoomData(sceneName, room.adultNightHeader, alternateRoomHeaders.adultNightHeader, None) - - for i in range(len(alternateRoomHeaders.cutsceneHeaders)): - cutsceneHeaderProp = alternateRoomHeaders.cutsceneHeaders[i] - cutsceneHeader = room.getAlternateHeaderRoom(room.ownerName) - readRoomData(sceneName, cutsceneHeader, cutsceneHeaderProp, None) - room.cutsceneHeaders.append(cutsceneHeader) - - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE": - for bgImage in roomHeader.bgImageList: - if bgImage.image is None: - raise PluginError( - 'A room is has room shape "Image" but does not have an image set in one of its BG images.' - ) - room.mesh.bgImages.append( - OOTBGImage( - toAlnum(sceneName + "_bg_" + bgImage.image.name), - bgImage.image, - bgImage.otherModeFlags, - ) - ) - - -def readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix): - # Camera faces opposite direction - orientation = mathutils.Quaternion((0, 1, 0), math.radians(180.0)) - translation, rotation, scale, orientedRotation = getConvertedTransformWithOrientation( - transformMatrix, sceneObj, obj, orientation - ) - camPosProp = obj.ootCameraPositionProperty - index = camPosProp.index - # TODO: FOV conversion? - if index in scene.collision.cameraData.camPosDict: - raise PluginError(f"Error: Repeated camera position index: {index} for {obj.name}") - if camPosProp.camSType == "Custom": - camSType = camPosProp.camSTypeCustom - else: - camSType = decomp_compat_map_CameraSType.get(camPosProp.camSType, camPosProp.camSType) - - fov = math.degrees(obj.data.angle) - if fov > 3.6: - fov *= 100 # see CAM_DATA_SCALED() macro - - scene.collision.cameraData.camPosDict[index] = OOTCameraPosData( - camSType, - camPosProp.hasPositionData, - translation, - rotation, - round(fov), - camPosProp.bgImageOverrideIndex, - ) - - -def readCrawlspace(obj, scene, transformMatrix): - splineProp = obj.ootSplineProperty - index = splineProp.index - - if index in scene.collision.cameraData.camPosDict: - raise PluginError(f"Error: Repeated camera position index: {index} for {obj.name}") - - if splineProp.camSType == "Custom": - camSType = splineProp.camSTypeCustom - else: - camSType = decomp_compat_map_CameraSType.get(splineProp.camSType, splineProp.camSType) - - crawlspace = OOTCrawlspaceData(camSType) - spline = obj.data.splines[0] - for point in spline.points: - position = [round(value) for value in transformMatrix @ obj.matrix_world @ point.co] - crawlspace.points.append(position) - - scene.collision.cameraData.camPosDict[index] = crawlspace - - -def readPathProp(pathProp, obj, scene, sceneObj, sceneName, transformMatrix): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - # scene.pathList[obj.name] = ootConvertPath(sceneName, obj, relativeTransform) - - # actorProp should be an actor, but its purpose is to access headerSettings so this also works. - addActor(scene, ootConvertPath(sceneName, obj, relativeTransform), obj.ootSplineProperty, "pathList", obj.name) - - -def ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, convertTextureData): - if originalSceneObj.type != "EMPTY" or originalSceneObj.ootEmptyType != "Scene": - raise PluginError(originalSceneObj.name + ' is not an empty with the "Scene" empty type.') - - if bpy.context.scene.exportHiddenGeometry: - hiddenState = unhideAllAndGetHiddenState(bpy.context.scene) - - # Don't remove ignore_render, as we want to reuse this for collision - sceneObj, allObjs = ootDuplicateHierarchy(originalSceneObj, None, True, OOTObjectCategorizer()) - - if bpy.context.scene.exportHiddenGeometry: - restoreHiddenState(hiddenState) - - roomObjs = [ - child for child in sceneObj.children_recursive if child.type == "EMPTY" and child.ootEmptyType == "Room" - ] - if len(roomObjs) == 0: - raise PluginError("The scene has no child empties with the 'Room' empty type.") - - try: - scene = OOTScene(sceneName, OOTModel(sceneName + "_dl", DLFormat, None)) - readSceneData(scene, sceneObj.fast64.oot.scene, sceneObj.ootSceneHeader, sceneObj.ootAlternateSceneHeaders) - processedRooms = set() - - for obj in sceneObj.children_recursive: - translation, rotation, scale, orientedRotation = getConvertedTransform(transformMatrix, sceneObj, obj, True) - - if obj.type == "EMPTY" and obj.ootEmptyType == "Room": - roomObj = obj - roomHeader = roomObj.ootRoomHeader - roomIndex = roomHeader.roomIndex - if roomIndex in processedRooms: - raise PluginError("Error: room index " + str(roomIndex) + " is used more than once.") - processedRooms.add(roomIndex) - room = scene.addRoom(roomIndex, sceneName, roomHeader.roomShape) - readRoomData(sceneName, room, roomHeader, roomObj.ootAlternateRoomHeaders) - - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(roomHeader.bgImageList) < 1: - raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') - if roomHeader.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(processedRooms) > 1: - raise PluginError(f'Room shape "Image" can only have one room in the scene.') - - cullGroup = CullGroup(translation, scale, obj.ootRoomHeader.defaultCullDistance) - DLGroup = room.mesh.addMeshGroup(cullGroup).DLGroup - boundingBox = BoundingBox() - ootProcessMesh( - room.mesh, DLGroup, sceneObj, roomObj, transformMatrix, convertTextureData, None, boundingBox - ) - centroid, radius = boundingBox.getEnclosingSphere() - cullGroup.position = centroid - cullGroup.cullDepth = radius - - if bpy.context.scene.f3d_type == "F3DEX3": - addOcclusionQuads( - obj, room.occlusion_planes, True, transformMatrix @ sceneObj.matrix_world.inverted() - ) - - room.mesh.terminateDLs() - room.mesh.removeUnusedEntries() - ootProcessEmpties(scene, room, sceneObj, roomObj, transformMatrix) - elif obj.type == "EMPTY" and obj.ootEmptyType == "Water Box": - # 0x3F = -1 in 6bit value - ootProcessWaterBox(sceneObj, obj, transformMatrix, scene, 0x3F) - elif obj.type == "CAMERA": - camPosProp = obj.ootCameraPositionProperty - readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix) - elif obj.type == "CURVE" and assertCurveValid(obj): - if isPathObject(obj): - readPathProp(obj.ootSplineProperty, obj, scene, sceneObj, sceneName, transformMatrix) - else: - readCrawlspace(obj, scene, transformMatrix) - - scene.validateIndices() - scene.sortEntrances() - exportCollisionCommon(scene.collision, sceneObj, transformMatrix, True, sceneName) - - ootCleanupScene(originalSceneObj, allObjs) - - except Exception as e: - ootCleanupScene(originalSceneObj, allObjs) - raise Exception(str(e)) - - return scene - - -class BoundingBox: - def __init__(self): - self.minPoint = None - self.maxPoint = None - self.points = [] - - def addPoint(self, point: tuple[float, float, float]): - if self.minPoint is None: - self.minPoint = list(point[:]) - else: - for i in range(3): - if point[i] < self.minPoint[i]: - self.minPoint[i] = point[i] - if self.maxPoint is None: - self.maxPoint = list(point[:]) - else: - for i in range(3): - if point[i] > self.maxPoint[i]: - self.maxPoint[i] = point[i] - self.points.append(point) - - def addMeshObj(self, obj: bpy.types.Object, transform: mathutils.Matrix): - mesh = obj.data - for vertex in mesh.vertices: - self.addPoint(transform @ vertex.co) - - def getEnclosingSphere(self) -> tuple[float, float]: - centroid = (mathutils.Vector(self.minPoint) + mathutils.Vector(self.maxPoint)) / 2 - radius = 0 - for point in self.points: - distance = (mathutils.Vector(point) - centroid).length - if distance > radius: - radius = distance - - # print(f"Radius: {radius}, Centroid: {centroid}") - - transformedCentroid = [round(value) for value in centroid] - transformedRadius = round(radius) - return transformedCentroid, transformedRadius - - -# This function should be called on a copy of an object -# The copy will have modifiers / scale applied and will be made single user -# When we duplicated obj hierarchy we stripped all ignore_renders from hierarchy. -def ootProcessMesh( - roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox -): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - translation, rotation, scale = relativeTransform.decompose() - - if obj.type == "EMPTY" and obj.ootEmptyType == "Cull Group": - if LODHierarchyObject is not None: - raise PluginError( - obj.name - + " cannot be used as a cull group because it is " - + "in the sub-hierarchy of the LOD group empty " - + LODHierarchyObject.name - ) - - cullProp = obj.ootCullGroupProperty - checkUniformScale(scale, obj) - DLGroup = roomMesh.addMeshGroup( - CullGroup( - ootConvertTranslation(translation), - scale if cullProp.sizeControlsCull else [cullProp.manualRadius], - obj.empty_display_size if cullProp.sizeControlsCull else 1, - ) - ).DLGroup - - elif obj.type == "MESH" and not obj.ignore_render: - triConverterInfo = TriangleConverterInfo(obj, None, roomMesh.model.f3d, relativeTransform, getInfoDict(obj)) - fMeshes = saveStaticModel( - triConverterInfo, - roomMesh.model, - obj, - relativeTransform, - roomMesh.model.name, - convertTextureData, - False, - "oot", - ) - if fMeshes is not None: - for drawLayer, fMesh in fMeshes.items(): - DLGroup.addDLCall(fMesh.draw, drawLayer) - - boundingBox.addMeshObj(obj, relativeTransform) - - alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) - for childObj in alphabeticalChildren: - if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": - ootProcessLOD( - roomMesh, - DLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - else: - ootProcessMesh( - roomMesh, - DLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - - -def ootProcessLOD( - roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox -): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - translation, rotation, scale = relativeTransform.decompose() - ootTranslation = ootConvertTranslation(translation) - - LODHierarchyObject = obj - name = toAlnum(roomMesh.model.name + "_" + obj.name + "_lod") - opaqueLOD = roomMesh.model.addLODGroup(name + "_opaque", ootTranslation, obj.f3d_lod_always_render_farthest) - transparentLOD = roomMesh.model.addLODGroup( - name + "_transparent", ootTranslation, obj.f3d_lod_always_render_farthest - ) - - index = 0 - for childObj in obj.children: - # This group will not be converted to C directly, but its display lists will be converted through the FLODGroup. - childDLGroup = OOTDLGroup(name + str(index), roomMesh.model.DLFormat) - index += 1 - - if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": - ootProcessLOD( - roomMesh, - childDLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - else: - ootProcessMesh( - roomMesh, - childDLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - - # We handle case with no geometry, for the cases where we have "gaps" in the LOD hierarchy. - # This can happen if a LOD does not use transparency while the levels above and below it does. - childDLGroup.createDLs() - childDLGroup.terminateDLs() - - # Add lod AFTER processing hierarchy, so that DLs will be built by then - opaqueLOD.add_lod(childDLGroup.opaque, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) - transparentLOD.add_lod(childDLGroup.transparent, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) - - opaqueLOD.create_data() - transparentLOD.create_data() - - DLGroup.addDLCall(opaqueLOD.draw, "Opaque") - DLGroup.addDLCall(transparentLOD.draw, "Transparent") - - -def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix): - translation, rotation, scale, orientedRotation = getConvertedTransform(transformMatrix, sceneObj, obj, True) - - if obj.type == "EMPTY": - if obj.ootEmptyType == "Actor": - actorProp = obj.ootActorProperty - - # The Actor list is filled with ``("None", f"{i} (Deleted from the XML)", "None")`` for - # the total number of actors defined in the XML. If the user deletes one, this will prevent - # any data loss as Blender saves the index of the element in the Actor list used for the EnumProperty - # and not the identifier as defined by the first element of the tuple. Therefore, we need to check if - # the current Actor has the ID `None` to avoid export issues. - if actorProp.actorID != "None": - if actorProp.rotOverride: - actorRot = ", ".join([actorProp.rotOverrideX, actorProp.rotOverrideY, actorProp.rotOverrideZ]) - else: - actorRot = ", ".join(f"DEG_TO_BINANG({(rot * (180 / 0x8000)):.3f})" for rot in rotation) - - actorName = ( - ootData.actorData.actorsByID[actorProp.actorID].name.replace( - f" - {actorProp.actorID.removeprefix('ACTOR_')}", "" - ) - if actorProp.actorID != "Custom" - else "Custom Actor" - ) - - addActor( - room, - OOTActor( - actorName, - getCustomProperty(actorProp, "actorID"), - translation, - actorRot, - actorProp.actorParam, - ), - actorProp, - "actorList", - obj.name, - ) - elif obj.ootEmptyType == "Transition Actor": - transActorProp = obj.ootTransitionActorProperty - if transActorProp.actor.actorID != "None": - if transActorProp.isRoomTransition: - if transActorProp.fromRoom is None or transActorProp.toRoom is None: - raise PluginError("ERROR: Missing room empty object assigned to transition.") - fromIndex = transActorProp.fromRoom.ootRoomHeader.roomIndex - toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex - else: - fromIndex = toIndex = room.roomIndex - front = (fromIndex, getCustomProperty(transActorProp, "cameraTransitionFront")) - back = (toIndex, getCustomProperty(transActorProp, "cameraTransitionBack")) - - transActorName = ( - ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace( - f" - {transActorProp.actor.actorID.removeprefix('ACTOR_')}", "" - ) - if transActorProp.actor.actorID != "Custom" - else "Custom Actor" - ) - - addActor( - scene, - OOTTransitionActor( - transActorName, - getCustomProperty(transActorProp.actor, "actorID"), - front[0], - back[0], - front[1], - back[1], - translation, - rotation[1], # TODO: Correct axis? - transActorProp.actor.actorParam, - ), - transActorProp.actor, - "transitionActorList", - obj.name, - ) - elif obj.ootEmptyType == "Entrance": - entranceProp = obj.ootEntranceProperty - spawnIndex = entranceProp.spawnIndex - - if entranceProp.tiedRoom is not None: - roomIndex = entranceProp.tiedRoom.ootRoomHeader.roomIndex - else: - raise PluginError("ERROR: Missing room empty object assigned to the entrance.") - - addActor(scene, OOTEntrance(roomIndex, spawnIndex), entranceProp.actor, "entranceList", obj.name) - addStartPosition( - scene, - spawnIndex, - OOTActor( - "", - "ACTOR_PLAYER" if not entranceProp.customActor else entranceProp.actor.actorIDCustom, - translation, - ", ".join(f"DEG_TO_BINANG({(rot * (180 / 0x8000)):.3f})" for rot in rotation), - entranceProp.actor.actorParam, - ), - entranceProp.actor, - obj.name, - ) - elif obj.ootEmptyType == "Water Box": - ootProcessWaterBox(sceneObj, obj, transformMatrix, scene, room.roomIndex) - elif obj.type == "CAMERA": - camPosProp = obj.ootCameraPositionProperty - readCamPos(camPosProp, obj, scene, sceneObj, transformMatrix) - elif obj.type == "CURVE" and assertCurveValid(obj): - if isPathObject(obj): - readPathProp(obj.ootSplineProperty, obj, scene, sceneObj, scene.name, transformMatrix) - else: - readCrawlspace(obj, scene, transformMatrix) - - for childObj in obj.children: - ootProcessEmpties(scene, room, sceneObj, childObj, transformMatrix) - - -def ootProcessWaterBox(sceneObj, obj, transformMatrix, scene, roomIndex): - translation, rotation, scale, orientedRotation = getConvertedTransform(transformMatrix, sceneObj, obj, True) - - checkIdentityRotation(obj, orientedRotation, False) - waterBoxProp = obj.ootWaterBoxProperty - scene.collision.waterBoxes.append( - OOTWaterBox( - roomIndex, - getCustomProperty(waterBoxProp, "lighting"), - getCustomProperty(waterBoxProp, "camera"), - waterBoxProp.flag19, - translation, - scale, - obj.empty_display_size, - ) - ) diff --git a/fast64_internal/oot/oot_object.py b/fast64_internal/oot/oot_object.py index b20b2b941..a4eb89f89 100644 --- a/fast64_internal/oot/oot_object.py +++ b/fast64_internal/oot/oot_object.py @@ -1,8 +1,7 @@ from bpy.types import Object from ..utility import ootGetSceneOrRoomHeader -from .data import OoT_Data -from .oot_level_classes import OOTRoom from .oot_constants import ootData +from .exporter.room.header import RoomHeader def addMissingObjectToProp(roomObj: Object, headerIndex: int, objectKey: str): @@ -15,34 +14,7 @@ def addMissingObjectToProp(roomObj: Object, headerIndex: int, objectKey: str): objectList[-1].objectKey = objectKey -def addMissingObjectsToRoomHeader(roomObj: Object, room: OOTRoom, ootData: OoT_Data, headerIndex: int): - """Adds missing objects to the object list""" - if len(room.actorList) > 0: - for roomActor in room.actorList: - actor = ootData.actorData.actorsByID.get(roomActor.actorID) - if actor is not None and actor.key != "player" and len(actor.tiedObjects) > 0: - for objKey in actor.tiedObjects: - if objKey not in ["obj_gameplay_keep", "obj_gameplay_field_keep", "obj_gameplay_dangeon_keep"]: - objID = ootData.objectData.objectsByKey[objKey].id - if not (objID in room.objectIDList): - room.objectIDList.append(objID) - addMissingObjectToProp(roomObj, headerIndex, objKey) - - -def addMissingObjectsToAllRoomHeaders(roomObj: Object, room: OOTRoom, ootData: OoT_Data): - """ - Adds missing objects (required by actors) to all headers of a room, - both to the roomObj empty and the exported room - """ - sceneLayers = [room, room.childNightHeader, room.adultDayHeader, room.adultNightHeader] - for i, layer in enumerate(sceneLayers): - if layer is not None: - addMissingObjectsToRoomHeader(roomObj, layer, ootData, i) - for i in range(len(room.cutsceneHeaders)): - addMissingObjectsToRoomHeader(roomObj, room.cutsceneHeaders[i], ootData, i + 4) - - -def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, headerIndex: int): +def addMissingObjectsToRoomHeader(roomObj: Object, curHeader: RoomHeader, headerIndex: int): """Adds missing objects to the object list""" if len(curHeader.actors.actorList) > 0: for roomActor in curHeader.actors.actorList: @@ -56,11 +28,11 @@ def addMissingObjectsToRoomHeaderNew(roomObj: Object, curHeader, headerIndex: in addMissingObjectToProp(roomObj, headerIndex, objKey) -def addMissingObjectsToAllRoomHeadersNew(roomObj: Object, headers: list): +def addMissingObjectsToAllRoomHeaders(roomObj: Object, headers: list[RoomHeader]): """ Adds missing objects (required by actors) to all headers of a room, both to the roomObj empty and the exported room """ for i, curHeader in enumerate(headers): if curHeader is not None: - addMissingObjectsToRoomHeaderNew(roomObj, curHeader, i) + addMissingObjectsToRoomHeader(roomObj, curHeader, i) diff --git a/fast64_internal/oot/scene/exporter/to_c/__init__.py b/fast64_internal/oot/scene/exporter/to_c/__init__.py deleted file mode 100644 index 61a8fed1f..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .scene import getIncludes, getSceneC -from .scene_table_c import modifySceneTable, getDrawConfig -from .spec import editSpecFile -from .scene_folder import modifySceneFiles, deleteSceneFiles -from .scene_bootup import setBootupScene, clearBootupScene -from .scene_cutscene import getCutsceneC diff --git a/fast64_internal/oot/scene/exporter/to_c/actor.py b/fast64_internal/oot/scene/exporter/to_c/actor.py deleted file mode 100644 index 446efc331..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/actor.py +++ /dev/null @@ -1,132 +0,0 @@ -from .....utility import CData, indent -from ....oot_level_classes import OOTScene, OOTRoom, OOTActor, OOTTransitionActor, OOTEntrance - - -################### -# Written to Room # -################### - -# Actor List - - -def getActorEntry(actor: OOTActor): - """Returns a single actor entry""" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in actor.position) + " }" - rotData = "{ " + "".join(actor.rotation) + " }" - - actorInfos = [actor.actorID, posData, rotData, actor.actorParam] - infoDescs = ["Actor ID", "Position", "Rotation", "Parameters"] - - return ( - indent - + (f"// {actor.actorName}\n" + indent if actor.actorName != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:10} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -def getActorList(outRoom: OOTRoom, headerIndex: int): - """Returns the actor list for the current header""" - actorList = CData() - declarationBase = f"ActorEntry {outRoom.actorListName(headerIndex)}" - - # .h - actorList.header = f"extern {declarationBase}[];\n" - - # .c - actorList.source = ( - (f"{declarationBase}[{outRoom.getActorLengthDefineName(headerIndex)}]" + " = {\n") - + "\n".join(getActorEntry(actor) for actor in outRoom.actorList) - + "};\n\n" - ) - - return actorList - - -#################### -# Written to Scene # -#################### - -# Transition Actor List - - -def getTransitionActorEntry(transActor: OOTTransitionActor): - """Returns a single transition actor entry""" - sides = [(transActor.frontRoom, transActor.frontCam), (transActor.backRoom, transActor.backCam)] - roomData = "{ " + ", ".join(f"{room}, {cam}" for room, cam in sides) + " }" - posData = "{ " + ", ".join(f"{round(pos)}" for pos in transActor.position) + " }" - rotData = f"DEG_TO_BINANG({(transActor.rotationY * (180 / 0x8000)):.3f})" - - actorInfos = [roomData, transActor.actorID, posData, rotData, transActor.actorParam] - infoDescs = ["Room & Cam Index (Front, Back)", "Actor ID", "Position", "Rotation Y", "Parameters"] - - return ( - (indent + f"// {transActor.actorName}\n" + indent if transActor.actorName != "" else "") - + "{\n" - + ",\n".join((indent * 2) + f"/* {desc:30} */ {info}" for desc, info in zip(infoDescs, actorInfos)) - + ("\n" + indent + "},\n") - ) - - -def getTransitionActorList(outScene: OOTScene, headerIndex: int): - """Returns the transition actor list for the current header""" - transActorList = CData() - declarationBase = f"TransitionActorEntry {outScene.transitionActorListName(headerIndex)}" - - # .h - transActorList.header = f"extern {declarationBase}[];\n" - - # .c - transActorList.source = ( - (f"{declarationBase}[]" + " = {\n") - + "\n".join(getTransitionActorEntry(transActor) for transActor in outScene.transitionActorList) - + "};\n\n" - ) - - return transActorList - - -# Entrance List - - -def getSpawnActorList(outScene: OOTScene, headerIndex: int): - """Returns the spawn actor list for the current header""" - spawnActorList = CData() - declarationBase = f"ActorEntry {outScene.startPositionsName(headerIndex)}" - - # .h - spawnActorList.header = f"extern {declarationBase}[];\n" - - # .c - spawnActorList.source = ( - (f"{declarationBase}[]" + " = {\n") - + "".join(getActorEntry(spawnActor) for spawnActor in outScene.startPositions.values()) - + "};\n\n" - ) - - return spawnActorList - - -def getSpawnEntry(entrance: OOTEntrance): - """Returns a single spawn entry""" - return indent + "{ " + f"{entrance.startPositionIndex}, {entrance.roomIndex}" + " },\n" - - -def getSpawnList(outScene: OOTScene, headerIndex: int): - """Returns the spawn list for the current header""" - spawnList = CData() - declarationBase = f"Spawn {outScene.entranceListName(headerIndex)}" - - # .h - spawnList.header = f"extern {declarationBase}[];\n" - - # .c - spawnList.source = ( - (f"{declarationBase}[]" + " = {\n") - + (indent + "// { Spawn Actor List Index, Room Index }\n") - + "".join(getSpawnEntry(entrance) for entrance in outScene.entranceList) - + "};\n\n" - ) - - return spawnList diff --git a/fast64_internal/oot/scene/exporter/to_c/room_commands.py b/fast64_internal/oot/scene/exporter/to_c/room_commands.py deleted file mode 100644 index b75efe252..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/room_commands.py +++ /dev/null @@ -1,100 +0,0 @@ -from .....utility import CData, indent -from ....oot_level_classes import OOTRoom - - -def getEchoSettingsCmd(outRoom: OOTRoom): - return indent + f"SCENE_CMD_ECHO_SETTINGS({outRoom.echo})" - - -def getRoomBehaviourCmd(outRoom: OOTRoom): - showInvisibleActors = "true" if outRoom.showInvisibleActors else "false" - disableWarpSongs = "true" if outRoom.disableWarpSongs else "false" - - return ( - (indent + "SCENE_CMD_ROOM_BEHAVIOR(") - + ", ".join([outRoom.roomBehaviour, outRoom.linkIdleMode, showInvisibleActors, disableWarpSongs]) - + ")" - ) - - -def getSkyboxDisablesCmd(outRoom: OOTRoom): - disableSkybox = "true" if outRoom.disableSkybox else "false" - disableSunMoon = "true" if outRoom.disableSunMoon else "false" - - return indent + f"SCENE_CMD_SKYBOX_DISABLES({disableSkybox}, {disableSunMoon})" - - -def getTimeSettingsCmd(outRoom: OOTRoom): - return indent + f"SCENE_CMD_TIME_SETTINGS({outRoom.timeHours}, {outRoom.timeMinutes}, {outRoom.timeSpeed})" - - -def getWindSettingsCmd(outRoom: OOTRoom): - return ( - indent - + f"SCENE_CMD_WIND_SETTINGS({', '.join(f'{dir}' for dir in outRoom.windVector)}, {outRoom.windStrength}),\n" - ) - - -def getOcclusionPlaneCandidatesListCmd(outRoom: OOTRoom): - return ( - indent - + f"SCENE_CMD_OCCLUSION_PLANE_CANDIDATES_LIST({len(outRoom.occlusion_planes.planes)}, {outRoom.occlusion_planes.name})" - ) - - -def getRoomShapeCmd(outRoom: OOTRoom): - return indent + f"SCENE_CMD_ROOM_SHAPE(&{outRoom.mesh.headerName()})" - - -def getObjectListCmd(outRoom: OOTRoom, headerIndex: int): - return ( - indent + "SCENE_CMD_OBJECT_LIST(" - ) + f"{outRoom.getObjectLengthDefineName(headerIndex)}, {outRoom.objectListName(headerIndex)})" - - -def getActorListCmd(outRoom: OOTRoom, headerIndex: int): - return ( - indent + "SCENE_CMD_ACTOR_LIST(" - ) + f"{outRoom.getActorLengthDefineName(headerIndex)}, {outRoom.actorListName(headerIndex)})" - - -def getRoomCommandList(outRoom: OOTRoom, headerIndex: int): - cmdListData = CData() - declarationBase = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}" - - getCmdFunc1ArgList = [ - getEchoSettingsCmd, - getRoomBehaviourCmd, - getSkyboxDisablesCmd, - getTimeSettingsCmd, - getRoomShapeCmd, - ] - - getCmdFunc2ArgList = [] - - if outRoom.setWind: - getCmdFunc1ArgList.append(getWindSettingsCmd) - - if len(outRoom.occlusion_planes.planes) > 0: - getCmdFunc1ArgList.append(getOcclusionPlaneCandidatesListCmd) - - if len(outRoom.objectIDList) > 0: - getCmdFunc2ArgList.append(getObjectListCmd) - - if len(outRoom.actorList) > 0: - getCmdFunc2ArgList.append(getActorListCmd) - - roomCmdData = ( - (outRoom.getAltHeaderListCmd(outRoom.alternateHeadersName()) if outRoom.hasAlternateHeaders() else "") - + "".join(getCmd(outRoom) + ",\n" for getCmd in getCmdFunc1ArgList) - + "".join(getCmd(outRoom, headerIndex) + ",\n" for getCmd in getCmdFunc2ArgList) - + outRoom.getEndCmd() - ) - - # .h - cmdListData.header = f"extern {declarationBase}[];\n" - - # .c - cmdListData.source = f"{declarationBase}[]" + " = {\n" + roomCmdData + "};\n\n" - - return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/room_header.py b/fast64_internal/oot/scene/exporter/to_c/room_header.py deleted file mode 100644 index ac93c14ce..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/room_header.py +++ /dev/null @@ -1,83 +0,0 @@ -from .....utility import CData, indent -from ....oot_level_classes import OOTRoom -from .actor import getActorList -from .room_commands import getRoomCommandList - - -def getHeaderDefines(outRoom: OOTRoom, headerIndex: int): - """Returns a string containing defines for actor and object lists lengths""" - headerDefines = "" - - if len(outRoom.objectIDList) > 0: - headerDefines += f"#define {outRoom.getObjectLengthDefineName(headerIndex)} {len(outRoom.objectIDList)}\n" - - if len(outRoom.actorList) > 0: - headerDefines += f"#define {outRoom.getActorLengthDefineName(headerIndex)} {len(outRoom.actorList)}\n" - - return headerDefines - - -# Object List -def getObjectList(outRoom: OOTRoom, headerIndex: int): - objectList = CData() - declarationBase = f"s16 {outRoom.objectListName(headerIndex)}" - - # .h - objectList.header = f"extern {declarationBase}[];\n" - - # .c - objectList.source = ( - (f"{declarationBase}[{outRoom.getObjectLengthDefineName(headerIndex)}]" + " = {\n") - + ",\n".join(indent + objectID for objectID in outRoom.objectIDList) - + ",\n};\n\n" - ) - - return objectList - - -# Room Header -def getRoomData(outRoom: OOTRoom): - roomC = CData() - - roomHeaders = [ - (outRoom.childNightHeader, "Child Night"), - (outRoom.adultDayHeader, "Adult Day"), - (outRoom.adultNightHeader, "Adult Night"), - ] - - for i, csHeader in enumerate(outRoom.cutsceneHeaders): - roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - - declarationBase = f"SceneCmd* {outRoom.alternateHeadersName()}" - - # .h - roomC.header = f"extern {declarationBase}[];\n" - - # .c - altHeaderPtrList = ( - f"{declarationBase}[]" - + " = {\n" - + "\n".join( - indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," - for i, (curHeader, headerDesc) in enumerate(roomHeaders, 1) - ) - + "\n};\n\n" - ) - - roomHeaders.insert(0, (outRoom, "Child Day (Default)")) - for i, (curHeader, headerDesc) in enumerate(roomHeaders): - if curHeader is not None: - roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - roomC.source += getHeaderDefines(curHeader, i) - roomC.append(getRoomCommandList(curHeader, i)) - - if i == 0 and outRoom.hasAlternateHeaders(): - roomC.source += altHeaderPtrList - - if len(curHeader.objectIDList) > 0: - roomC.append(getObjectList(curHeader, i)) - - if len(curHeader.actorList) > 0: - roomC.append(getActorList(curHeader, i)) - - return roomC diff --git a/fast64_internal/oot/scene/exporter/to_c/room_shape.py b/fast64_internal/oot/scene/exporter/to_c/room_shape.py deleted file mode 100644 index c4029d05f..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/room_shape.py +++ /dev/null @@ -1,154 +0,0 @@ -from .....utility import CData, indent -from .....f3d.f3d_gbi import ScrollMethod, TextureExportSettings -from ....oot_model_classes import OOTGfxFormatter -from ....oot_constants import ootEnumRoomShapeType -from ....oot_level_classes import OOTRoom, OOTRoomMeshGroup, OOTRoomMesh - -ootRoomShapeStructs = [ - "RoomShapeNormal", - "RoomShapeImage", - "RoomShapeCullable", -] - -ootRoomShapeEntryStructs = [ - "RoomShapeDListsEntry", - "RoomShapeDListsEntry", - "RoomShapeCullableEntry", -] - - -def getRoomShapeDLEntry(meshEntry: OOTRoomMeshGroup, roomShape: str): - opaqueName = meshEntry.DLGroup.opaque.name if meshEntry.DLGroup.opaque is not None else "NULL" - transparentName = meshEntry.DLGroup.transparent.name if meshEntry.DLGroup.transparent is not None else "NULL" - - roomShapeDListsEntries = "{ " - if roomShape == "ROOM_SHAPE_TYPE_CULLABLE": - roomShapeDListsEntries += ( - "{ " + ", ".join(f"{pos}" for pos in meshEntry.cullGroup.position) + " }, " - ) + f"{meshEntry.cullGroup.cullDepth}, " - roomShapeDListsEntries += f"{opaqueName}, {transparentName}" + " },\n" - - return roomShapeDListsEntries - - -# Texture files must be saved separately. -def getRoomShapeImageData(roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): - code = CData() - - if len(roomMesh.bgImages) > 1: - declarationBase = f"RoomShapeImageMultiBgEntry {roomMesh.getMultiBgStructName()}" - - # .h - code.header += f"extern {declarationBase}[{len(roomMesh.bgImages)}];\n" - - # .c - code.source += f"{declarationBase}[{len(roomMesh.bgImages)}] = {{\n" - for i in range(len(roomMesh.bgImages)): - bgImage = roomMesh.bgImages[i] - code.source += indent + "{\n" + bgImage.multiPropertiesC(2, i) + indent + "},\n" - code.source += f"}};\n\n" - - bitsPerValue = 64 - for bgImage in roomMesh.bgImages: - # .h - code.header += f"extern u{bitsPerValue} {bgImage.name}[];\n" - - # .c - code.source += ( - # This is to force 8 byte alignment - (f"Gfx {bgImage.name}_aligner[] = " + "{ gsSPEndDisplayList() };\n" if bitsPerValue != 64 else "") - + (f"u{bitsPerValue} {bgImage.name}[SCREEN_WIDTH * SCREEN_HEIGHT / 4]" + " = {\n") - + f'#include "{textureSettings.includeDir + bgImage.getFilename()}.inc.c"' - + "\n};\n\n" - ) - - return code - - -def getRoomShape(outRoom: OOTRoom): - roomShapeInfo = CData() - roomShapeDLArray = CData() - mesh = outRoom.mesh - - shapeTypeIdx = [value[0] for value in ootEnumRoomShapeType].index(mesh.roomShape) - dlEntryType = ootRoomShapeEntryStructs[shapeTypeIdx] - structName = ootRoomShapeStructs[shapeTypeIdx] - roomShapeImageFormat = "Multi" if len(mesh.bgImages) > 1 else "Single" - - if mesh.roomShape == "ROOM_SHAPE_TYPE_IMAGE": - structName += roomShapeImageFormat - - roomShapeInfo.header = f"extern {structName} {mesh.headerName()};\n" - - if mesh.roomShape != "ROOM_SHAPE_TYPE_IMAGE": - entryName = mesh.entriesName() - dlEntryDeclarationBase = f"{dlEntryType} {mesh.entriesName()}[{len(mesh.meshEntries)}]" - - roomShapeInfo.source = ( - "\n".join( - ( - f"{structName} {mesh.headerName()} = {{", - indent + f"{mesh.roomShape},", - indent + f"ARRAY_COUNT({entryName}),", - indent + f"{entryName},", - indent + f"{entryName} + ARRAY_COUNT({entryName})", - f"}};", - ) - ) - + "\n\n" - ) - - roomShapeDLArray.header = f"extern {dlEntryDeclarationBase};\n" - roomShapeDLArray.source = dlEntryDeclarationBase + " = {\n" - - for entry in mesh.meshEntries: - roomShapeDLArray.source += indent + getRoomShapeDLEntry(entry, mesh.roomShape) - - roomShapeDLArray.source += "};\n\n" - else: - # type 1 only allows 1 room - entry = mesh.meshEntries[0] - - roomShapeImageFormatValue = ( - "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE" if roomShapeImageFormat == "Single" else "ROOM_SHAPE_IMAGE_AMOUNT_MULTI" - ) - - roomShapeInfo.source += ( - (f"{structName} {mesh.headerName()}" + " = {\n") - + (indent + f"{{ ROOM_SHAPE_TYPE_IMAGE, {roomShapeImageFormatValue}, &{mesh.entriesName()} }},\n") - + ( - mesh.bgImages[0].singlePropertiesC(1) - if roomShapeImageFormat == "Single" - else indent + f"ARRAY_COUNTU({mesh.getMultiBgStructName()}), {mesh.getMultiBgStructName()}," - ) - + "\n};\n\n" - ) - - roomShapeDLArray.header = f"extern {dlEntryType} {mesh.entriesName()};\n" - roomShapeDLArray.source = ( - f"{dlEntryType} {mesh.entriesName()} = {getRoomShapeDLEntry(entry, mesh.roomShape)[:-2]};\n\n" - ) - - roomShapeInfo.append(roomShapeDLArray) - return roomShapeInfo - - -def getRoomModel(outRoom: OOTRoom, textureExportSettings: TextureExportSettings): - roomModel = CData() - mesh = outRoom.mesh - - for i, entry in enumerate(mesh.meshEntries): - if entry.DLGroup.opaque is not None: - roomModel.append(entry.DLGroup.opaque.to_c(mesh.model.f3d)) - - if entry.DLGroup.transparent is not None: - roomModel.append(entry.DLGroup.transparent.to_c(mesh.model.f3d)) - - # type ``ROOM_SHAPE_TYPE_IMAGE`` only allows 1 room - if i == 0 and mesh.roomShape == "ROOM_SHAPE_TYPE_IMAGE": - break - - roomModel.append(mesh.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all()) - roomModel.append(getRoomShapeImageData(outRoom.mesh, textureExportSettings)) - - return roomModel diff --git a/fast64_internal/oot/scene/exporter/to_c/scene.py b/fast64_internal/oot/scene/exporter/to_c/scene.py deleted file mode 100644 index 9045ae30b..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene.py +++ /dev/null @@ -1,79 +0,0 @@ -from .....utility import CData, PluginError -from .....f3d.f3d_gbi import TextureExportSettings -from ....oot_level_classes import OOTScene -from .scene_header import getSceneData, getSceneModel -from .scene_collision import getSceneCollision -from .scene_cutscene import getSceneCutscenes -from .room_header import getRoomData -from .room_shape import getRoomModel, getRoomShape - - -class OOTSceneC: - def sceneTexturesIsUsed(self): - return len(self.sceneTexturesC.source) > 0 - - def sceneCutscenesIsUsed(self): - return len(self.sceneCutscenesC) > 0 - - def __init__(self): - # Main header file for both the scene and room(s) - self.header = CData() - - # Files for the scene segment - self.sceneMainC = CData() - self.sceneTexturesC = CData() - self.sceneCollisionC = CData() - self.sceneCutscenesC = [] - - # Files for room segments - self.roomMainC = {} - self.roomOcclusionPlanesC = {} - self.roomShapeInfoC = {} - self.roomModelC = {} - - -def getSceneC(outScene: OOTScene, textureExportSettings: TextureExportSettings): - """Generates C code for each scene element and returns the data""" - sceneC = OOTSceneC() - - sceneC.sceneMainC = getSceneData(outScene) - sceneC.sceneTexturesC = getSceneModel(outScene, textureExportSettings) - sceneC.sceneCollisionC = getSceneCollision(outScene) - sceneC.sceneCutscenesC = getSceneCutscenes(outScene) - - for outRoom in outScene.rooms.values(): - outRoomName = outRoom.roomName() - - if len(outRoom.mesh.meshEntries) > 0: - roomShapeInfo = getRoomShape(outRoom) - roomModel = getRoomModel(outRoom, textureExportSettings) - else: - raise PluginError(f"Error: Room {outRoom.index} has no mesh children.") - - sceneC.roomMainC[outRoomName] = getRoomData(outRoom) - sceneC.roomOcclusionPlanesC[outRoomName] = outRoom.occlusion_planes.to_c() - sceneC.roomShapeInfoC[outRoomName] = roomShapeInfo - sceneC.roomModelC[outRoomName] = roomModel - - return sceneC - - -def getIncludes(outScene: OOTScene): - """Returns the files to include""" - # @TODO: avoid including files where it's not needed - includeData = CData() - - fileNames = [ - "ultra64", - "z64", - "macros", - outScene.sceneName(), - "segment_symbols", - "command_macros_base", - "z64cutscene_commands", - "variables", - ] - - includeData.source = "\n".join(f'#include "{fileName}.h"' for fileName in fileNames) + "\n\n" - - return includeData diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_bootup.py b/fast64_internal/oot/scene/exporter/to_c/scene_bootup.py deleted file mode 100644 index fd688a12d..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_bootup.py +++ /dev/null @@ -1,151 +0,0 @@ -import os, re -from typing import Any -from .....utility import PluginError, writeFile, readFile - - -def writeBootupSettings( - configPath: str, - bootMode: str, - newGameOnly: bool, - entranceIndex: str, - linkAge: str, - timeOfDay: str, - cutsceneIndex: str, - saveFileNameData: str, -): - if os.path.exists(configPath): - originalData = readFile(configPath) - data = originalData - else: - originalData = "" - data = ( - f"// #define BOOT_TO_SCENE\n" - + f"// #define BOOT_TO_SCENE_NEW_GAME_ONLY\n" - + f"// #define BOOT_TO_FILE_SELECT\n" - + f"// #define BOOT_TO_MAP_SELECT\n" - + f"#define BOOT_ENTRANCE 0\n" - + f"#define BOOT_AGE LINK_AGE_CHILD\n" - + f"#define BOOT_TIME NEXT_TIME_NONE\n" - + f"#define BOOT_CUTSCENE 0xFFEF\n" - + f"#define BOOT_PLAYER_NAME 0x15, 0x12, 0x17, 0x14, 0x3E, 0x3E, 0x3E, 0x3E\n\n" - ) - - data = re.sub( - r"(//\s*)?#define\s*BOOT_TO_SCENE", - ("" if bootMode == "Play" else "// ") + "#define BOOT_TO_SCENE", - data, - ) - data = re.sub( - r"(//\s*)?#define\s*BOOT_TO_SCENE_NEW_GAME_ONLY", - ("" if newGameOnly else "// ") + "#define BOOT_TO_SCENE_NEW_GAME_ONLY", - data, - ) - data = re.sub( - r"(//\s*)?#define\s*BOOT_TO_FILE_SELECT", - ("" if bootMode == "File Select" else "// ") + "#define BOOT_TO_FILE_SELECT", - data, - ) - data = re.sub( - r"(//\s*)?#define\s*BOOT_TO_MAP_SELECT", - ("" if bootMode == "Map Select" else "// ") + "#define BOOT_TO_MAP_SELECT", - data, - ) - data = re.sub(r"#define\s*BOOT_ENTRANCE\s*[^\s]*", f"#define BOOT_ENTRANCE {entranceIndex}", data) - data = re.sub(r"#define\s*BOOT_AGE\s*[^\s]*", f"#define BOOT_AGE {linkAge}", data) - data = re.sub(r"#define\s*BOOT_TIME\s*[^\s]*", f"#define BOOT_TIME {timeOfDay}", data) - data = re.sub(r"#define\s*BOOT_CUTSCENE\s*[^\s]*", f"#define BOOT_CUTSCENE {cutsceneIndex}", data) - data = re.sub(r"#define\s*BOOT_PLAYER_NAME\s*[^\n]*", f"#define BOOT_PLAYER_NAME {saveFileNameData}", data) - - if data != originalData: - writeFile(configPath, data) - - -def setBootupScene(configPath: str, entranceIndex: str, options): - # ``options`` argument type: OOTBootupSceneOptions - linkAge = "LINK_AGE_CHILD" - timeOfDay = "NEXT_TIME_NONE" - cutsceneIndex = "0xFFEF" - newEntranceIndex = "0" - saveName = "LINK" - - if options.bootMode != "Map Select": - newEntranceIndex = entranceIndex - saveName = options.newGameName - - if options.overrideHeader: - timeOfDay, linkAge = getParamsFromOptions(options) - if options.headerOption == "Cutscene": - cutsceneIndex = "0xFFF" + format(options.cutsceneIndex - 4, "X") - - saveFileNameData = ", ".join(["0x" + format(i, "02X") for i in stringToSaveNameBytes(saveName)]) - - writeBootupSettings( - configPath, - options.bootMode, - options.newGameOnly, - newEntranceIndex, - linkAge, - timeOfDay, - cutsceneIndex, - saveFileNameData, - ) - - -def clearBootupScene(configPath: str): - writeBootupSettings( - configPath, - "", - False, - "0", - "LINK_AGE_CHILD", - "NEXT_TIME_NONE", - "0xFFEF", - "0x15, 0x12, 0x17, 0x14, 0x3E, 0x3E, 0x3E, 0x3E", - ) - - -def getParamsFromOptions(options: Any) -> tuple[str, str]: - timeOfDay = ( - "NEXT_TIME_DAY" - if options.headerOption == "Child Day" or options.headerOption == "Adult Day" - else "NEXT_TIME_NIGHT" - ) - - linkAge = ( - "LINK_AGE_ADULT" - if options.headerOption == "Adult Day" or options.headerOption == "Adult Night" - else "LINK_AGE_CHILD" - ) - - return timeOfDay, linkAge - - -# converts ascii text to format for save file name. -# see src/code/z_message_PAL.c:Message_Decode() -def stringToSaveNameBytes(name: str) -> bytearray: - specialChar = { - " ": 0x3E, - ".": 0x40, - "-": 0x3F, - } - - result = bytearray([0x3E] * 8) - - if len(name) > 8: - raise PluginError("Save file name for scene bootup must be 8 characters or less.") - for i in range(len(name)): - value = ord(name[i]) - if name[i] in specialChar: - result[i] = specialChar[name[i]] - elif value >= ord("0") and value <= ord("9"): # numbers - result[i] = value - ord("0") - elif value >= ord("A") and value <= ord("Z"): # uppercase - result[i] = value - ord("7") - elif value >= ord("a") and value <= ord("z"): # lowercase - result[i] = value - ord("=") - else: - raise PluginError( - name + " has some invalid characters and cannot be used as a save file name for scene bootup." - ) - - return result diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_collision.py b/fast64_internal/oot/scene/exporter/to_c/scene_collision.py deleted file mode 100644 index 241b53dad..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_collision.py +++ /dev/null @@ -1,9 +0,0 @@ -from ....collision.exporter.to_c import ootCollisionToC -from ....oot_level_classes import OOTScene - - -# Writes the collision data for a scene -def getSceneCollision(outScene: OOTScene): - # @TODO: delete this function and rename ``ootCollisionToC`` into ``getSceneCollision`` - # when the ``oot_collision.py`` code is cleaned up - return ootCollisionToC(outScene.collision) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py deleted file mode 100644 index db7d85440..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py +++ /dev/null @@ -1,116 +0,0 @@ -from .....utility import CData, indent -from ....oot_level_classes import OOTScene - - -def getSoundSettingsCmd(outScene: OOTScene): - return indent + f"SCENE_CMD_SOUND_SETTINGS({outScene.audioSessionPreset}, {outScene.nightSeq}, {outScene.musicSeq})" - - -def getRoomListCmd(outScene: OOTScene): - return indent + f"SCENE_CMD_ROOM_LIST({len(outScene.rooms)}, {outScene.roomListName()})" - - -def getTransActorListCmd(outScene: OOTScene, headerIndex: int): - return ( - indent + "SCENE_CMD_TRANSITION_ACTOR_LIST(" - ) + f"{len(outScene.transitionActorList)}, {outScene.transitionActorListName(headerIndex)})" - - -def getMiscSettingsCmd(outScene: OOTScene): - return indent + f"SCENE_CMD_MISC_SETTINGS({outScene.cameraMode}, {outScene.mapLocation})" - - -def getColHeaderCmd(outScene: OOTScene): - return indent + f"SCENE_CMD_COL_HEADER(&{outScene.collision.headerName()})" - - -def getSpawnListCmd(outScene: OOTScene, headerIndex: int): - return ( - indent + "SCENE_CMD_ENTRANCE_LIST(" - ) + f"{outScene.entranceListName(headerIndex) if len(outScene.entranceList) > 0 else 'NULL'})" - - -def getSpecialFilesCmd(outScene: OOTScene): - return indent + f"SCENE_CMD_SPECIAL_FILES({outScene.naviCup}, {outScene.globalObject})" - - -def getPathListCmd(outScene: OOTScene, headerIndex: int): - return indent + f"SCENE_CMD_PATH_LIST({outScene.pathListName(headerIndex)})" - - -def getSpawnActorListCmd(outScene: OOTScene, headerIndex: int): - return ( - (indent + "SCENE_CMD_SPAWN_LIST(") - + f"{len(outScene.startPositions)}, " - + f"{outScene.startPositionsName(headerIndex) if len(outScene.startPositions) > 0 else 'NULL'})" - ) - - -def getSkyboxSettingsCmd(outScene: OOTScene): - return ( - indent - + f"SCENE_CMD_SKYBOX_SETTINGS({outScene.skyboxID}, {outScene.skyboxCloudiness}, {outScene.skyboxLighting})" - ) - - -def getExitListCmd(outScene: OOTScene, headerIndex: int): - return indent + f"SCENE_CMD_EXIT_LIST({outScene.exitListName(headerIndex)})" - - -def getLightSettingsCmd(outScene: OOTScene, headerIndex: int): - return ( - indent + "SCENE_CMD_ENV_LIGHT_SETTINGS(" - ) + f"{len(outScene.lights)}, {outScene.lightListName(headerIndex) if len(outScene.lights) > 0 else 'NULL'})" - - -def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int): - match outScene.csWriteType: - case "Object": - csDataName = outScene.csName - case _: - csDataName = outScene.csWriteCustom - - return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName})" - - -def getSceneCommandList(outScene: OOTScene, headerIndex: int): - cmdListData = CData() - declarationBase = f"SceneCmd {outScene.sceneName()}_header{headerIndex:02}" - - getCmdFunc1ArgList = [ - getSoundSettingsCmd, - getRoomListCmd, - getMiscSettingsCmd, - getColHeaderCmd, - getSpecialFilesCmd, - getSkyboxSettingsCmd, - ] - - getCmdFunc2ArgList = [getSpawnListCmd, getSpawnActorListCmd, getLightSettingsCmd] - - if len(outScene.transitionActorList) > 0: - getCmdFunc2ArgList.append(getTransActorListCmd) - - if len(outScene.pathList) > 0: - getCmdFunc2ArgList.append(getPathListCmd) - - if len(outScene.exitList) > 0: - getCmdFunc2ArgList.append(getExitListCmd) - - if outScene.writeCutscene: - getCmdFunc2ArgList.append(getCutsceneDataCmd) - - sceneCmdData = ( - (outScene.getAltHeaderListCmd(outScene.alternateHeadersName()) if outScene.hasAlternateHeaders() else "") - + "".join(getCmd(outScene) + ",\n" for getCmd in getCmdFunc1ArgList) - + "".join(getCmd(outScene, headerIndex) + ",\n" for getCmd in getCmdFunc2ArgList) - + outScene.getEndCmd() - ) - - # .h - cmdListData.header = f"extern {declarationBase}[]" + ";\n" - - # .c - cmdListData.source = f"{declarationBase}[]" + " = {\n" + sceneCmdData + "};\n\n" - - return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py deleted file mode 100644 index fa7685625..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py +++ /dev/null @@ -1,51 +0,0 @@ -import bpy - -from .....utility import CData -from ....oot_level_classes import OOTScene -from ....cutscene.exporter import getNewCutsceneExport - - -def getCutsceneC(csName: str): - csData = CData() - declarationBase = f"CutsceneData {csName}[]" - - # .h - csData.header = f"extern {declarationBase};\n" - - # .c - csData.source = ( - declarationBase - + " = {\n" - + getNewCutsceneExport(csName, bpy.context.scene.fast64.oot.exportMotionOnly).getExportData() - + "};\n\n" - ) - - return csData - - -def getSceneCutscenes(outScene: OOTScene): - cutscenes: list[CData] = [] - altHeaders: list[OOTScene] = [ - outScene, - outScene.childNightHeader, - outScene.adultDayHeader, - outScene.adultNightHeader, - ] - altHeaders.extend(outScene.cutsceneHeaders) - csObjects = [] - - for curHeader in altHeaders: - # curHeader is either None or an OOTScene. This can either be the main scene itself, - # or one of the alternate / cutscene headers. - if curHeader is not None and curHeader.writeCutscene: - if curHeader.csWriteType == "Object" and curHeader.csName not in csObjects: - cutscenes.append(getCutsceneC(curHeader.csName)) - csObjects.append(curHeader.csName) - - for csObj in outScene.extraCutscenes: - name = csObj.name.removeprefix("Cutscene.") - if not name in csObjects: - cutscenes.append(getCutsceneC(name)) - csObjects.append(name) - - return cutscenes diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_folder.py b/fast64_internal/oot/scene/exporter/to_c/scene_folder.py deleted file mode 100644 index 57e7873ff..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_folder.py +++ /dev/null @@ -1,29 +0,0 @@ -import os, re, shutil -from ....oot_utility import ExportInfo, getSceneDirFromLevelName -from ....oot_level_classes import OOTScene - - -def modifySceneFiles(outScene: OOTScene, exportInfo: ExportInfo): - if exportInfo.customSubPath is not None: - sceneDir = exportInfo.customSubPath + exportInfo.name - else: - sceneDir = getSceneDirFromLevelName(outScene.name) - - scenePath = os.path.join(exportInfo.exportPath, sceneDir) - for filename in os.listdir(scenePath): - filepath = os.path.join(scenePath, filename) - if os.path.isfile(filepath): - match = re.match(outScene.name + "\_room\_(\d+)\.[ch]", filename) - if match is not None and int(match.group(1)) >= len(outScene.rooms): - os.remove(filepath) - - -def deleteSceneFiles(exportInfo: ExportInfo): - if exportInfo.customSubPath is not None: - sceneDir = exportInfo.customSubPath + exportInfo.name - else: - sceneDir = getSceneDirFromLevelName(exportInfo.name) - - scenePath = os.path.join(exportInfo.exportPath, sceneDir) - if os.path.exists(scenePath): - shutil.rmtree(scenePath) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_header.py b/fast64_internal/oot/scene/exporter/to_c/scene_header.py deleted file mode 100644 index 61b3c1621..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_header.py +++ /dev/null @@ -1,219 +0,0 @@ -from .....utility import CData, indent -from .....f3d.f3d_gbi import ScrollMethod, TextureExportSettings -from ....oot_model_classes import OOTGfxFormatter -from ....oot_level_classes import OOTScene, OOTLight -from .scene_pathways import getPathData -from .actor import getTransitionActorList, getSpawnActorList, getSpawnList -from .scene_commands import getSceneCommandList - - -################## -# Light Settings # -################## -def getColorValues(vector: tuple[int, int, int]): - return ", ".join(f"{v:5}" for v in vector) - - -def getDirectionValues(vector: tuple[int, int, int]): - return ", ".join(f"{v - 0x100 if v > 0x7F else v:5}" for v in vector) - - -def getLightSettingsEntry(light: OOTLight, lightMode: str, isLightingCustom: bool, index: int): - vectors = [ - (light.ambient, "Ambient Color", getColorValues), - (light.diffuseDir0, "Diffuse0 Direction", getDirectionValues), - (light.diffuse0, "Diffuse0 Color", getColorValues), - (light.diffuseDir1, "Diffuse1 Direction", getDirectionValues), - (light.diffuse1, "Diffuse1 Color", getColorValues), - (light.fogColor, "Fog Color", getColorValues), - ] - - fogData = [ - (light.getBlendFogNear(), "Blend Rate & Fog Near"), - (f"{light.fogFar}", "Fog Far"), - ] - - lightDescs = ["Dawn", "Day", "Dusk", "Night"] - - if not isLightingCustom and lightMode == "LIGHT_MODE_TIME": - # @TODO: Improve the lighting system. - # Currently Fast64 assumes there's only 4 possible settings for "Time of Day" lighting. - # This is not accurate and more complicated, - # for now we are doing ``index % 4`` to avoid having an OoB read in the list - # but this will need to be changed the day the lighting system is updated. - lightDesc = f"// {lightDescs[index % 4]} Lighting\n" - else: - isIndoor = not isLightingCustom and lightMode == "LIGHT_MODE_SETTINGS" - lightDesc = f"// {'Indoor' if isIndoor else 'Custom'} No. {index + 1} Lighting\n" - - lightData = ( - (indent + lightDesc) - + (indent + "{\n") - + "".join(indent * 2 + f"{'{ ' + vecToC(vector) + ' },':26} // {desc}\n" for vector, desc, vecToC in vectors) - + "".join(indent * 2 + f"{fogValue + ',':26} // {fogDesc}\n" for fogValue, fogDesc in fogData) - + (indent + "},\n") - ) - - return lightData - - -def getLightSettings(outScene: OOTScene, headerIndex: int): - lightSettingsData = CData() - declarationBase = f"EnvLightSettings {outScene.lightListName(headerIndex)}[{len(outScene.lights)}]" - - # .h - lightSettingsData.header = f"extern {declarationBase};\n" - - # .c - lightSettingsData.source = ( - (declarationBase + " = {\n") - + "".join( - getLightSettingsEntry(light, outScene.skyboxLighting, outScene.isSkyboxLightingCustom, i) - for i, light in enumerate(outScene.lights) - ) - + "};\n\n" - ) - - return lightSettingsData - - -######## -# Mesh # -######## -# Writes the textures and material setup displaylists that are shared between multiple rooms (is written to the scene) -def getSceneModel(outScene: OOTScene, textureExportSettings: TextureExportSettings) -> CData: - return outScene.model.to_c(textureExportSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all() - - -############# -# Exit List # -############# -def getExitList(outScene: OOTScene, headerIndex: int): - exitList = CData() - declarationBase = f"u16 {outScene.exitListName(headerIndex)}[{len(outScene.exitList)}]" - - # .h - exitList.header = f"extern {declarationBase};\n" - - # .c - exitList.source = ( - (declarationBase + " = {\n") - # @TODO: use the enum name instead of the raw index - + "\n".join(indent + f"{exitEntry.index}," for exitEntry in outScene.exitList) - + "\n};\n\n" - ) - - return exitList - - -############# -# Room List # -############# -def getRoomList(outScene: OOTScene): - roomList = CData() - declarationBase = f"RomFile {outScene.roomListName()}[]" - - # generating segment rom names for every room - segNames = [] - for i in range(len(outScene.rooms)): - roomName = outScene.rooms[i].roomName() - segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) - - # .h - roomList.header += f"extern {declarationBase};\n" - - if not outScene.write_dummy_room_list: - # Write externs for rom segments - roomList.header += "".join( - f"extern u8 {startName}[];\n" + f"extern u8 {stopName}[];\n" for startName, stopName in segNames - ) - - # .c - roomList.source = declarationBase + " = {\n" - - if outScene.write_dummy_room_list: - roomList.source = ( - "// Dummy room list\n" + roomList.source + ((indent + "{ NULL, NULL },\n") * len(outScene.rooms)) - ) - else: - roomList.source += ( - " },\n".join(indent + "{ " + f"(u32){startName}, (u32){stopName}" for startName, stopName in segNames) - + " },\n" - ) - - roomList.source += "};\n\n" - return roomList - - -################ -# Scene Header # -################ -def getHeaderData(header: OOTScene, headerIndex: int): - headerData = CData() - - # Write the spawn position list data - if len(header.startPositions) > 0: - headerData.append(getSpawnActorList(header, headerIndex)) - - # Write the transition actor list data - if len(header.transitionActorList) > 0: - headerData.append(getTransitionActorList(header, headerIndex)) - - # Write the entrance list - if len(header.entranceList) > 0: - headerData.append(getSpawnList(header, headerIndex)) - - # Write the exit list - if len(header.exitList) > 0: - headerData.append(getExitList(header, headerIndex)) - - # Write the light data - if len(header.lights) > 0: - headerData.append(getLightSettings(header, headerIndex)) - - # Write the path data, if used - if len(header.pathList) > 0: - headerData.append(getPathData(header, headerIndex)) - - return headerData - - -def getSceneData(outScene: OOTScene): - sceneC = CData() - - headers = [ - (outScene.childNightHeader, "Child Night"), - (outScene.adultDayHeader, "Adult Day"), - (outScene.adultNightHeader, "Adult Night"), - ] - - for i, csHeader in enumerate(outScene.cutsceneHeaders): - headers.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrs = "\n".join( - indent + f"{curHeader.sceneName()}_header{i:02}," - if curHeader is not None - else indent + "NULL," - if i < 4 - else "" - for i, (curHeader, headerDesc) in enumerate(headers, 1) - ) - - headers.insert(0, (outScene, "Child Day (Default)")) - for i, (curHeader, headerDesc) in enumerate(headers): - if curHeader is not None: - sceneC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - sceneC.append(getSceneCommandList(curHeader, i)) - - if i == 0: - if outScene.hasAlternateHeaders(): - declarationBase = f"SceneCmd* {outScene.alternateHeadersName()}[]" - sceneC.header += f"extern {declarationBase};\n" - sceneC.source += declarationBase + " = {\n" + altHeaderPtrs + "\n};\n\n" - - # Write the room segment list - sceneC.append(getRoomList(outScene)) - - sceneC.append(getHeaderData(curHeader, i)) - - return sceneC diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py b/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py deleted file mode 100644 index 48aad26bc..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py +++ /dev/null @@ -1,47 +0,0 @@ -from .....utility import CData, indent -from ....oot_spline import OOTPath -from ....oot_level_classes import OOTScene - - -def getPathPointData(path: OOTPath, headerIndex: int, pathIndex: int): - pathData = CData() - declarationBase = f"Vec3s {path.pathName(headerIndex, pathIndex)}" - - # .h - pathData.header = f"extern {declarationBase}[];\n" - - # .c - pathData.source = ( - f"{declarationBase}[]" - + " = {\n" - + "\n".join( - indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in path.points - ) - + "\n};\n\n" - ) - - return pathData - - -def getPathData(outScene: OOTScene, headerIndex: int): - pathData = CData() - pathListData = CData() - declarationBase = f"Path {outScene.pathListName(headerIndex)}[{len(outScene.pathList)}]" - - # .h - pathListData.header = f"extern {declarationBase};\n" - - # .c - pathListData.source = declarationBase + " = {\n" - - # Parse in alphabetical order of names - sortedPathList = sorted(outScene.pathList, key=lambda x: x.objName.lower()) - for i, curPath in enumerate(sortedPathList): - pathName = curPath.pathName(headerIndex, i) - pathListData.source += indent + "{ " + f"ARRAY_COUNTU({pathName}), {pathName}" + " },\n" - pathData.append(getPathPointData(curPath, headerIndex, i)) - - pathListData.source += "};\n\n" - pathData.append(pathListData) - - return pathData diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_table_c.py b/fast64_internal/oot/scene/exporter/to_c/scene_table_c.py deleted file mode 100644 index c0cee9909..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/scene_table_c.py +++ /dev/null @@ -1,322 +0,0 @@ -import os -import enum -import bpy - -from dataclasses import dataclass, field -from typing import Optional -from .....utility import PluginError, writeFile -from ....oot_constants import ootEnumSceneID, ootSceneNameToID -from ....oot_utility import getCustomProperty, ExportInfo -from ....oot_level_classes import OOTScene - - -ADDED_SCENES_COMMENT = "// Added scenes" - - -class SceneIndexType(enum.IntEnum): - """Used to figure out the value of ``selectedSceneIndex``""" - - # this is using negative numbers since this is used as a return type if the scene index wasn't found - CUSTOM = -1 # custom scene - VANILLA_REMOVED = -2 # vanilla scene that was removed, this is to know if it should insert an entry - - -@dataclass -class SceneTableEntry: - """Defines an entry of ``scene_table.h``""" - - index: int - original: Optional[str] # the original line from the parsed file - scene: Optional[OOTScene] = None - exportName: Optional[str] = None - isCustomScene: bool = False - prefix: Optional[str] = None # ifdefs, endifs, comments etc, everything before the current entry - suffix: Optional[str] = None # remaining data after the last entry - parsed: Optional[str] = None - - # macro parameters - specName: Optional[str] = None # name of the scene segment in spec - titleCardName: Optional[str] = None # name of the title card segment in spec, or `none` for no title card - enumValue: Optional[str] = None # enum value for this scene - drawConfigIdx: Optional[str] = None # scene draw config index - unk1: Optional[str] = None - unk2: Optional[str] = None - - def __post_init__(self): - # parse the entry parameters from file data or an ``OOTScene`` - macroStart = "DEFINE_SCENE(" - if self.original is not None and macroStart in self.original: - # remove the index and the macro's name with the parenthesis - index = self.original.index(macroStart) + len(macroStart) - self.parsed = self.original[index:].removesuffix(")\n") - - parameters = self.parsed.split(", ") - assert len(parameters) == 6 - self.setParameters(*parameters) - elif self.scene is not None: - self.setParametersFromScene() - - def setParameters( - self, specName: str, titleCardName: str, enumValue: str, drawConfigIdx: str, unk1: str = "0", unk2: str = "0" - ): - """Sets the entry's parameters""" - self.specName = specName - self.titleCardName = titleCardName - self.enumValue = enumValue - self.drawConfigIdx = drawConfigIdx - self.unk1 = unk1 - self.unk2 = unk2 - - def setParametersFromScene(self, scene: Optional[OOTScene] = None): - """Use the ``OOTScene`` data to set the entry's parameters""" - scene = self.scene if scene is None else scene - # TODO: Implement title cards - name = scene.name if scene is not None else self.exportName - self.setParameters( - f"{scene.name.lower() if self.isCustomScene else scene.name}_scene", - "none", - ootSceneNameToID.get(name, f"SCENE_{name.upper()}"), - getCustomProperty(scene.sceneTableEntry, "drawConfig"), - ) - - def to_c(self): - """Returns the entry as C code""" - return ( - (self.prefix if self.prefix is not None else "") - + f"/* 0x{self.index:02X} */ " - + f"DEFINE_SCENE({self.specName}, {self.titleCardName}, {self.enumValue}, " - + f"{self.drawConfigIdx}, {self.unk1}, {self.unk2})\n" - + (self.suffix if self.suffix is not None else "") - ) - - -@dataclass -class SceneTable: - """Defines a ``scene_table.h`` file data""" - - exportPath: str - exportName: Optional[str] - selectedSceneEnumValue: Optional[str] - entries: list[SceneTableEntry] = field(default_factory=list) - sceneEnumValues: list[str] = field(default_factory=list) # existing values in ``scene_table.h`` - isFirstCustom: bool = False # if true, adds the "Added Scenes" comment to the C data - selectedSceneIndex: int = 0 - customSceneIndex: Optional[int] = None # None if the selected custom scene isn't in the table yet - - def __post_init__(self): - # read the file's data - try: - with open(self.exportPath) as fileData: - data = fileData.read() - fileData.seek(0) - lines = fileData.readlines() - except FileNotFoundError: - raise PluginError("ERROR: Can't find scene_table.h!") - - # parse the entries and populate the list of entries (``self.entries``) - prefix = "" - self.isFirstCustom = ADDED_SCENES_COMMENT not in data - entryIndex = 0 # we don't use ``enumerate`` since not every line is an actual entry - assert len(lines) > 0 - for line in lines: - # skip the lines before an entry, create one from the file's data - # and add the skipped lines as a prefix of the current entry - if ( - not line.startswith("#") # ifdefs or endifs - and not line.startswith(" *") # multi-line comments - and "//" not in line # single line comments - and "/**" not in line # multi-line comments - and line != "\n" - and line != "" - ): - entry = SceneTableEntry(entryIndex, line, prefix=prefix) - self.entries.append(entry) - self.sceneEnumValues.append(entry.enumValue) - prefix = "" - entryIndex += 1 - else: - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" - prefix += line - - # add whatever's after the last entry - if len(prefix) > 0 and prefix != "\n": - self.entries[-1].suffix = prefix - - # get the scene index for the scene chosen by the user - if self.selectedSceneEnumValue is not None: - self.selectedSceneIndex = self.getIndexFromEnumValue() - - # dictionary of entries from spec names - self.entryBySpecName = {entry.specName: entry for entry in self.entries} - - # set the custom scene index - if self.selectedSceneIndex == SceneIndexType.CUSTOM: - entry = self.entryBySpecName.get(f"{self.exportName}_scene") - if entry is not None: - self.customSceneIndex = entry.index - - def getIndexFromEnumValue(self): - """Returns the index (int) of the chosen scene if vanilla and found, else return an enum value from ``SceneIndexType``""" - if self.selectedSceneEnumValue == "Custom": - return SceneIndexType.CUSTOM - for i in range(len(self.sceneEnumValues)): - if self.sceneEnumValues[i] == self.selectedSceneEnumValue: - return i - # if the index is not found and it's not a custom export it means it's a vanilla scene that was removed - return SceneIndexType.VANILLA_REMOVED - - def getOriginalIndex(self): - """ - Returns the index of a specific scene defined by which one the user chose - or by the ``sceneName`` parameter if it's not set to ``None`` - """ - i = 0 - if self.selectedSceneEnumValue != "Custom": - for elem in ootEnumSceneID: - if elem[0] == self.selectedSceneEnumValue: - # returns i - 1 because the first entry is the ``Custom`` option - return i - 1 - i += 1 - raise PluginError("ERROR: Scene Index not found!") - - def getInsertionIndex(self, index: Optional[int] = None) -> int: - """Returns the index to know where to insert data""" - # special case where the scene is "Inside the Great Deku Tree" - # since it's the first scene simply return 0 - if self.selectedSceneEnumValue == "SCENE_DEKU_TREE": - return 0 - - # if index is None this means this is looking for ``original_scene_index - 1`` - # else, this means the table is shifted - if index is None: - currentIndex = self.getOriginalIndex() - else: - currentIndex = index - - for i in range(len(self.sceneEnumValues)): - if self.sceneEnumValues[i] == ootEnumSceneID[currentIndex][0]: - return i + 1 - - # if the index hasn't been found yet, do it again but decrement the index - return self.getInsertionIndex(currentIndex - 1) - - def updateEntryIndex(self): - """Updates every entry index so they follow each other""" - for i, entry in enumerate(self.entries): - if entry.index != i: - entry.index = i - - def getIndex(self): - """Returns the selected scene index if it's a vanilla one, else returns the custom scene index""" - assert self.selectedSceneIndex != SceneIndexType.VANILLA_REMOVED - - # this function's usage makes ``customSceneIndex is None`` impossible - if self.selectedSceneIndex < 0 and self.customSceneIndex is None: - raise PluginError("ERROR: Custom Scene Index is None!") - - return self.selectedSceneIndex if self.selectedSceneIndex >= 0 else self.customSceneIndex - - def append(self, entry: SceneTableEntry): - """Appends an entry to the scene table, only used by custom scenes""" - # add the "added scenes" comment if it's not already there - if self.isFirstCustom: - entry.prefix = f"\n{ADDED_SCENES_COMMENT}\n" - self.isFirstCustom = False - - if entry not in self.entries: - if entry.index >= 0: - self.customSceneIndex = entry.index - self.entries.append(entry) - else: - raise PluginError(f"ERROR: (Append) The index is not valid! ({entry.index})") - else: - raise PluginError("ERROR: (Append) Entry already in the table!") - - def insert(self, entry: SceneTableEntry): - """Inserts an entry in the scene table, only used by non-custom scenes""" - if not entry in self.entries: - if entry.index >= 0: - if entry.index < len(self.entries): - nextEntry = self.entries[entry.index] # the next entry is at the insertion index - - # move the next entry's prefix to the one we're going to insert - if len(nextEntry.prefix) > 0 and not "INCLUDE_TEST_SCENES" in nextEntry.prefix: - entry.prefix = nextEntry.prefix - nextEntry.prefix = "" - - self.entries.insert(entry.index, entry) - else: - raise PluginError(f"ERROR: (Insert) The index is not valid! ({entry.index})") - else: - raise PluginError("ERROR: (Insert) Entry already in the table!") - - def remove(self, index: int): - """Removes an entry from the scene table""" - isCustom = index == SceneIndexType.CUSTOM - if index >= 0 or isCustom: - entry = self.entries[self.getIndex()] - - # move the prefix of the entry to remove to the next entry - # if there's no next entry this prefix becomes the suffix of the last entry - if len(entry.prefix) > 0: - nextIndex = index + 1 - if not isCustom and nextIndex < len(self.entries): - self.entries[nextIndex].prefix = entry.prefix - else: - previousIndex = entry.index - 1 - if entry.index == len(self.entries) - 1 and ADDED_SCENES_COMMENT in entry.prefix: - entry.prefix = entry.prefix.removesuffix(f"\n{ADDED_SCENES_COMMENT}\n") - self.entries[previousIndex].suffix = entry.prefix - - self.entries.remove(entry) - elif index == SceneIndexType.VANILLA_REMOVED: - raise PluginError("INFO: This scene was already removed.") - else: - raise PluginError("ERROR: Unexpected scene index value.") - - def to_c(self): - """Returns the scene table as C code""" - return "".join(entry.to_c() for entry in self.entries) - - -def getDrawConfig(sceneName: str): - """Read draw config from scene table""" - sceneTable = SceneTable( - os.path.join(bpy.path.abspath(bpy.context.scene.ootDecompPath), "include/tables/scene_table.h"), None, None - ) - - entry = sceneTable.entryBySpecName.get(f"{sceneName}_scene") - if entry is not None: - return entry.drawConfigIdx - - raise PluginError(f"ERROR: Scene name {sceneName} not found in scene table.") - - -def modifySceneTable(scene: Optional[OOTScene], exportInfo: ExportInfo): - """Remove, append, insert or update the scene table entry of the selected scene""" - sceneTable = SceneTable( - os.path.join(exportInfo.exportPath, "include/tables/scene_table.h"), - exportInfo.name if exportInfo.option == "Custom" else None, - exportInfo.option, - ) - - if scene is None: - # remove mode - sceneTable.remove(sceneTable.selectedSceneIndex) - elif sceneTable.selectedSceneIndex == SceneIndexType.CUSTOM and sceneTable.customSceneIndex is None: - # custom mode: new custom scene - sceneTable.append(SceneTableEntry(len(sceneTable.entries) - 1, None, scene, exportInfo.name, True)) - elif sceneTable.selectedSceneIndex == SceneIndexType.VANILLA_REMOVED: - # insert mode - sceneTable.insert(SceneTableEntry(sceneTable.getInsertionIndex(), None, scene, exportInfo.name, False)) - else: - # update mode (for both vanilla and custom scenes since they already exist in the table) - sceneTable.entries[sceneTable.getIndex()].setParametersFromScene(scene) - - # update the indices - sceneTable.updateEntryIndex() - - # write the file with the final data - writeFile(sceneTable.exportPath, sceneTable.to_c()) diff --git a/fast64_internal/oot/scene/exporter/to_c/spec.py b/fast64_internal/oot/scene/exporter/to_c/spec.py deleted file mode 100644 index 9c1600582..000000000 --- a/fast64_internal/oot/scene/exporter/to_c/spec.py +++ /dev/null @@ -1,303 +0,0 @@ -import os -import bpy -import enum - -from dataclasses import dataclass, field -from typing import List, Optional -from .....utility import PluginError, writeFile, indent -from ....oot_utility import ExportInfo, getSceneDirFromLevelName - - -# either "$(BUILD_DIR)", "$(BUILD)" or "build" -buildDirectory = None - - -class CommandType(enum.Enum): - """This class defines the different spec command types""" - - NAME = 0 - COMPRESS = 1 - AFTER = 2 - FLAGS = 3 - ALIGN = 4 - ADDRESS = 5 - ROMALIGN = 6 - INCLUDE = 7 - INCLUDE_DATA_WITH_RODATA = 8 - NUMBER = 9 - PAD_TEXT = 10 - - @staticmethod - def from_string(value: str): - """Returns one of the enum values from a string""" - - cmdType = CommandType._member_map_.get(value.upper()) - if cmdType is None: - raise PluginError(f"ERROR: Can't find value: ``{value}`` in the enum!") - return cmdType - - -@dataclass -class SpecEntryCommand: - """This class defines a single spec command""" - - type: CommandType - content: str = "" - prefix: str = "" - suffix: str = "" - - def to_c(self): - return self.prefix + indent + f"{self.type.name.lower()} {self.content}".strip() + self.suffix + "\n" - - -@dataclass -class SpecEntry: - """Defines an entry of ``spec``""" - - original: Optional[list[str]] = field(default_factory=list) # the original lines from the parsed file - commands: list[SpecEntryCommand] = field(default_factory=list) # list of the different spec commands - segmentName: str = "" # the name of the current segment - prefix: str = "" # data between two commands - suffix: str = "" # remaining data after the entry (used for the last entry) - contentSuffix: str = "" # remaining data after the last command in the current entry - - def __post_init__(self): - if self.original is not None: - global buildDirectory - # parse the commands from the existing data - prefix = "" - for line in self.original: - line = line.strip() - dontHaveComments = ( - not line.startswith("// ") and not line.startswith("/* ") and not line.startswith(" */") - ) - - if line != "\n": - if not line.startswith("#") and dontHaveComments: - split = line.split(" ") - command = split[0] - if len(split) > 2: - content = " ".join(elem for i, elem in enumerate(split) if i > 0) - elif len(split) > 1: - content = split[1] - elif command == "name": - content = self.segmentName - else: - content = "" - - if buildDirectory is None and (content.startswith('"build') or content.startswith('"$(BUILD')): - buildDirectory = content.split("/")[0].removeprefix('"') - - self.commands.append( - SpecEntryCommand( - CommandType.from_string(command), - content, - (prefix + ("\n" if len(prefix) > 0 else "")) if prefix != "\n" else "", - ) - ) - prefix = "" - else: - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" - prefix += (f"\n{indent}" if not dontHaveComments else "") + line - # if there's a prefix it's the remaining data after the last entry - if len(prefix) > 0: - self.contentSuffix = prefix - - if len(self.segmentName) == 0 and len(self.commands[0].content) > 0: - self.segmentName = self.commands[0].content - else: - raise PluginError("ERROR: The segment name can't be set!") - - def to_c(self): - return ( - (self.prefix if len(self.prefix) > 0 else "\n") - + "beginseg\n" - + "".join(cmd.to_c() for cmd in self.commands) - + (f"{self.contentSuffix}\n" if len(self.contentSuffix) > 0 else "") - + "endseg" - + (self.suffix if self.suffix == "\n" else f"\n{self.suffix}\n" if len(self.suffix) > 0 else "") - ) - - -@dataclass -class SpecFile: - """This class defines the spec's file data""" - - exportPath: str # path to the spec file - entries: list[SpecEntry] = field(default_factory=list) # list of the different spec entries - - def __post_init__(self): - # read the file's data - try: - with open(self.exportPath, "r") as fileData: - lines = fileData.readlines() - except FileNotFoundError: - raise PluginError("ERROR: Can't find spec!") - - prefix = "" - parsedLines = [] - assert len(lines) > 0 - for line in lines: - # if we're inside a spec entry or if the lines between two entries do not contains these characters - # fill the ``parsedLine`` list if it's inside a segment - # when we reach the end of the current segment add a new ``SpecEntry`` to ``self.entries`` - isNotEmptyOrNewline = len(line) > 0 and line != "\n" - if ( - len(parsedLines) > 0 - or not line.startswith(" *") - and "/*\n" not in line - and not line.startswith("#") - and isNotEmptyOrNewline - ): - if "beginseg" not in line and "endseg" not in line: - # if inside a segment, between beginseg and endseg - parsedLines.append(line) - elif "endseg" in line: - # else, if the line has endseg in it (> if we reached the end of the current segment) - entry = SpecEntry(parsedLines, prefix=prefix) - self.entries.append(entry) - prefix = "" - parsedLines = [] - else: - # else, if between 2 segments and the line is something we don't need - if prefix.startswith("#") and line.startswith("#"): - # add newline if there's two consecutive preprocessor directives - prefix += "\n" - prefix += line - # set the last's entry's suffix to the remaining prefix - self.entries[-1].suffix = prefix.removesuffix("\n") - - def find(self, segmentName: str): - """Returns an entry from a segment name, returns ``None`` if nothing was found""" - - for i, entry in enumerate(self.entries): - if entry.segmentName == segmentName: - return self.entries[i] - return None - - def append(self, entry: SpecEntry): - """Appends an entry to the list""" - - # prefix/suffix shenanigans - lastEntry = self.entries[-1] - if len(lastEntry.suffix) > 0: - entry.prefix = f"{lastEntry.suffix}\n\n" - lastEntry.suffix = "" - self.entries.append(entry) - - def remove(self, segmentName: str): - """Removes an entry from a segment name""" - - # prefix/suffix shenanigans - entry = self.find(segmentName) - if entry is not None: - if len(entry.prefix) > 0 and entry.prefix != "\n": - lastEntry = self.entries[self.entries.index(entry) - 1] - lastEntry.suffix = (lastEntry.suffix if lastEntry.suffix is not None else "") + entry.prefix[:-2] - self.entries.remove(entry) - - def to_c(self): - return "\n".join(entry.to_c() for entry in self.entries) - - -def editSpecFile( - isScene: bool, - exportInfo: ExportInfo, - hasSceneTex: bool, - hasSceneCS: bool, - roomTotal: int, - csTotal: int, - roomIndexHasOcclusion: List[bool], -): - global buildDirectory - - # get the spec's data - specFile = SpecFile(os.path.join(exportInfo.exportPath, "spec")) - - # get the scene and current segment name and remove the scene - sceneName = exportInfo.name - sceneSegmentName = f"{sceneName}_scene" - specFile.remove(f'"{sceneSegmentName}"') - - # mark the other scene elements to remove (like rooms) - segmentsToRemove: list[str] = [] - for entry in specFile.entries: - if entry.segmentName.startswith(f'"{sceneName}_'): - segmentsToRemove.append(entry.segmentName) - - # remove the segments - for segmentName in segmentsToRemove: - specFile.remove(segmentName) - - if isScene: - assert buildDirectory is not None - isSingleFile = bpy.context.scene.ootSceneExportSettings.singleFile - includeDir = f"{buildDirectory}/" - if exportInfo.customSubPath is not None: - includeDir += f"{exportInfo.customSubPath + sceneName}" - else: - includeDir += f"{getSceneDirFromLevelName(sceneName)}" - - sceneCmds = [ - SpecEntryCommand(CommandType.NAME, f'"{sceneSegmentName}"'), - SpecEntryCommand(CommandType.COMPRESS), - SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), - ] - - # scene - if isSingleFile: - sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}.o"')) - else: - sceneCmds.extend( - [ - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_main.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_col.o"'), - ] - ) - - if hasSceneTex: - sceneCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_tex.o"')) - - if hasSceneCS: - for i in range(csTotal): - sceneCmds.append( - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{sceneSegmentName}_cs_{i}.o"') - ) - - sceneCmds.append(SpecEntryCommand(CommandType.NUMBER, "2")) - specFile.append(SpecEntry(None, sceneCmds)) - - # rooms - for i in range(roomTotal): - roomSegmentName = f"{sceneName}_room_{i}" - - roomCmds = [ - SpecEntryCommand(CommandType.NAME, f'"{roomSegmentName}"'), - SpecEntryCommand(CommandType.COMPRESS), - SpecEntryCommand(CommandType.ROMALIGN, "0x1000"), - ] - - if isSingleFile: - roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}.o"')) - else: - roomCmds.extend( - [ - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_main.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model_info.o"'), - SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_model.o"'), - ] - ) - if roomIndexHasOcclusion[i]: - roomCmds.append(SpecEntryCommand(CommandType.INCLUDE, f'"{includeDir}/{roomSegmentName}_occ.o"')) - - roomCmds.append(SpecEntryCommand(CommandType.NUMBER, "3")) - specFile.append(SpecEntry(None, roomCmds)) - specFile.entries[-1].suffix = "\n" - - # finally, write the spec file - writeFile(specFile.exportPath, specFile.to_c()) - - # reset build directory name so it can update properly on the next run - buildDirectory = None diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index dd6c02367..845283235 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -10,10 +10,9 @@ from ...f3d.f3d_gbi import TextureExportSettings, DLFormat from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader from ..oot_utility import ExportInfo, RemoveInfo, sceneNameFromID -from ..oot_level_writer import ootExportSceneToC from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene -from .exporter.to_c import clearBootupScene, modifySceneTable, editSpecFile, deleteSceneFiles +from ..exporter.decomp_edit.config import Config from ..exporter import SceneExport, Files @@ -94,7 +93,7 @@ class OOT_ClearBootupScene(Operator): bl_options = {"REGISTER", "UNDO", "PRESET"} def execute(self, context): - clearBootupScene(os.path.join(abspath(context.scene.ootDecompPath), "include/config/config_debug.h")) + Config.clearBootupScene(os.path.join(abspath(context.scene.ootDecompPath), "include/config/config_debug.h")) self.report({"INFO"}, "Success!") return {"FINISHED"} From c95f72c3f56c423488bb5dc99e1775337ffbf0a0 Mon Sep 17 00:00:00 2001 From: kurethedead <59840896+kurethedead@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:20:35 -0700 Subject: [PATCH 96/98] Refactor Room Shape (#5) * Deleted scene\exporter, fixed missing code references * Removed unused code from oot_level_writer and oot_level_classes * Removed oot_level_writer, contents moved into appropriate files * Remove unused OOTRoom * Black format * Create shape2.py * Refactor room shape * Remove old files * Remove mesh.py * Remove old comments * Delete old code --- fast64_internal/oot/exporter/__init__.py | 2 +- fast64_internal/oot/exporter/room/__init__.py | 230 ++++- fast64_internal/oot/exporter/room/main.py | 249 ------ fast64_internal/oot/exporter/room/mesh.py | 187 ---- fast64_internal/oot/exporter/room/shape.py | 841 +++++++++++++----- fast64_internal/oot/exporter/scene/rooms.py | 4 + fast64_internal/oot/oot_level_classes.py | 151 ---- 7 files changed, 844 insertions(+), 820 deletions(-) delete mode 100644 fast64_internal/oot/exporter/room/main.py delete mode 100644 fast64_internal/oot/exporter/room/mesh.py delete mode 100644 fast64_internal/oot/oot_level_classes.py diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 8450cc8c7..290e15b38 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -125,7 +125,7 @@ def export(originalSceneObj: Object, transform: Matrix, exportInfo: ExportInfo): sceneFile.write() for room in scene.rooms.entries: - room.mesh.copyBgImages(path) + room.roomShape.copy_bg_images(path) if not isCustomExport: Files.add_scene_edits(exportInfo, scene, sceneFile) diff --git a/fast64_internal/oot/exporter/room/__init__.py b/fast64_internal/oot/exporter/room/__init__.py index db91d7961..11e1dd284 100644 --- a/fast64_internal/oot/exporter/room/__init__.py +++ b/fast64_internal/oot/exporter/room/__init__.py @@ -1,2 +1,228 @@ -from .header import RoomAlternateHeader -from .main import Room +from dataclasses import dataclass +from typing import Optional +from mathutils import Matrix +from bpy.types import Object +from ....utility import PluginError, CData, indent +from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings +from ...room.properties import OOTRoomHeaderProperty +from ...oot_object import addMissingObjectsToAllRoomHeaders +from ...oot_model_classes import OOTModel, OOTGfxFormatter +from ..file import RoomFile +from ..utility import Utility, altHeaderList +from .header import RoomAlternateHeader, RoomHeader +from .shape import RoomShapeUtility, RoomShape, RoomShapeImageMulti, RoomShapeImageBase + + +@dataclass +class Room: + """This class defines a room""" + + name: str + roomIndex: int + mainHeader: Optional[RoomHeader] + altHeader: Optional[RoomAlternateHeader] + roomShape: Optional[RoomShape] + hasAlternateHeaders: bool + + @staticmethod + def new( + name: str, + transform: Matrix, + sceneObj: Object, + roomObj: Object, + roomShapeType: str, + model: OOTModel, + roomIndex: int, + sceneName: str, + saveTexturesAsPNG: bool, + ): + i = 0 + mainHeaderProps = roomObj.ootRoomHeader + altHeader = RoomAlternateHeader(f"{name}_alternateHeaders") + altProp = roomObj.ootAlternateRoomHeaders + + if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(mainHeaderProps.bgImageList) == 0: + raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') + + if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and roomIndex >= 1: + raise PluginError(f'Room shape "Image" can only have one room in the scene.') + + mainHeader = RoomHeader.new( + f"{name}_header{i:02}", + mainHeaderProps, + sceneObj, + roomObj, + transform, + i, + ) + hasAlternateHeaders = False + + for i, header in enumerate(altHeaderList, 1): + altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") + if not altP.usePreviousHeader: + hasAlternateHeaders = True + newRoomHeader = RoomHeader.new( + f"{name}_header{i:02}", + altP, + sceneObj, + roomObj, + transform, + i, + ) + setattr(altHeader, header, newRoomHeader) + + altHeader.cutscenes = [ + RoomHeader.new( + f"{name}_header{i:02}", + csHeader, + sceneObj, + roomObj, + transform, + i, + ) + for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) + ] + + hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else hasAlternateHeaders + altHeader = altHeader if hasAlternateHeaders else None + headers: list[RoomHeader] = [mainHeader] + if altHeader is not None: + headers.extend([altHeader.childNight, altHeader.adultDay, altHeader.adultNight]) + if len(altHeader.cutscenes) > 0: + headers.extend(altHeader.cutscenes) + addMissingObjectsToAllRoomHeaders(roomObj, headers) + + roomShape = RoomShapeUtility.create_shape( + sceneName, name, roomShapeType, model, transform, sceneObj, roomObj, saveTexturesAsPNG, mainHeaderProps + ) + return Room(name, roomIndex, mainHeader, altHeader, roomShape, hasAlternateHeaders) + + def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: + """Returns the current room header based on the header index""" + + if headerIndex == 0: + return self.mainHeader + + for i, header in enumerate(altHeaderList, 1): + if headerIndex == i: + return getattr(self.altHeader, header) + + for i, csHeader in enumerate(self.altHeader.cutscenes, 4): + if headerIndex == i: + return csHeader + + return None + + def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): + """Returns the room commands list""" + + cmdListData = CData() + listName = f"SceneCmd {curHeader.name}" + + # .h + cmdListData.header = f"extern {listName}[];\n" + + # .c + cmdListData.source = ( + (f"{listName}[]" + " = {\n") + + (Utility.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") + + self.roomShape.get_cmd() + + curHeader.infos.getCmds() + + (curHeader.objects.getCmd() if len(curHeader.objects.objectList) > 0 else "") + + (curHeader.actors.getCmd() if len(curHeader.actors.actorList) > 0 else "") + + Utility.getEndCmd() + + "};\n\n" + ) + + return cmdListData + + def getRoomMainC(self): + """Returns the C data of the main informations of a room""" + + roomC = CData() + roomHeaders: list[tuple[RoomHeader, str]] = [] + altHeaderPtrList = None + + if self.hasAlternateHeaders: + roomHeaders: list[tuple[RoomHeader, str]] = [ + (self.altHeader.childNight, "Child Night"), + (self.altHeader.adultDay, "Adult Day"), + (self.altHeader.adultNight, "Adult Night"), + ] + + for i, csHeader in enumerate(self.altHeader.cutscenes): + roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) + + altHeaderPtrListName = f"SceneCmd* {self.altHeader.name}" + + # .h + roomC.header = f"extern {altHeaderPtrListName}[];\n" + + # .c + altHeaderPtrList = ( + f"{altHeaderPtrListName}[]" + + " = {\n" + + "\n".join( + indent + f"{curHeader.name}," if curHeader is not None else indent + "NULL," + for (curHeader, _) in roomHeaders + ) + + "\n};\n\n" + ) + + roomHeaders.insert(0, (self.mainHeader, "Child Day (Default)")) + for i, (curHeader, headerDesc) in enumerate(roomHeaders): + if curHeader is not None: + roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" + roomC.source += curHeader.getHeaderDefines() + roomC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) + + if i == 0 and self.hasAlternateHeaders and altHeaderPtrList is not None: + roomC.source += altHeaderPtrList + + if len(curHeader.objects.objectList) > 0: + roomC.append(curHeader.objects.getC()) + + if len(curHeader.actors.actorList) > 0: + roomC.append(curHeader.actors.getC()) + + return roomC + + def getRoomShapeModelC(self, textureSettings: TextureExportSettings): + """Returns the C data of the room model""" + roomModel = CData() + + for i, entry in enumerate(self.roomShape.dl_entries): + if entry.opaque is not None: + roomModel.append(entry.opaque.to_c(self.roomShape.model.f3d)) + + if entry.transparent is not None: + roomModel.append(entry.transparent.to_c(self.roomShape.model.f3d)) + + # type ``ROOM_SHAPE_TYPE_IMAGE`` only allows 1 room + if i == 0 and isinstance(self.roomShape, RoomShapeImageBase): + break + + roomModel.append(self.roomShape.model.to_c(textureSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all()) + + if isinstance(self.roomShape, RoomShapeImageMulti): + # roomModel.append(self.roomShape.multiImg.getC()) # Error? double call in getRoomShapeC()? + roomModel.append(self.roomShape.to_c_img(textureSettings.includeDir)) + + return roomModel + + def getNewRoomFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): + """Returns a new ``RoomFile`` element""" + + roomMainData = self.getRoomMainC() + roomModelData = self.getRoomShapeModelC(textureExportSettings) + roomModelInfoData = self.roomShape.to_c() + + return RoomFile( + self.name, + roomMainData.source, + roomModelData.source, + roomModelInfoData.source, + isSingleFile, + path, + roomMainData.header + roomModelData.header + roomModelInfoData.header, + ) diff --git a/fast64_internal/oot/exporter/room/main.py b/fast64_internal/oot/exporter/room/main.py deleted file mode 100644 index e9a169c80..000000000 --- a/fast64_internal/oot/exporter/room/main.py +++ /dev/null @@ -1,249 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from mathutils import Matrix -from bpy.types import Object -from ....utility import PluginError, CData, indent, readFile, writeFile -from ....f3d.f3d_gbi import ScrollMethod, TextureExportSettings -from ...room.properties import OOTRoomHeaderProperty -from ...oot_object import addMissingObjectsToAllRoomHeaders -from ...oot_level_classes import OOTRoomMesh -from ...oot_model_classes import OOTModel, OOTGfxFormatter -from ...oot_utility import CullGroup -from ..file import RoomFile -from ..utility import Utility, altHeaderList -from .header import RoomAlternateHeader, RoomHeader -from .shape import RoomShape -from .mesh import ootProcessMesh, BoundingBox - - -@dataclass -class Room: - """This class defines a room""" - - name: str - roomIndex: int - mainHeader: Optional[RoomHeader] - altHeader: Optional[RoomAlternateHeader] - mesh: Optional[OOTRoomMesh] - roomShape: Optional[RoomShape] - hasAlternateHeaders: bool - - @staticmethod - def new( - name: str, - transform: Matrix, - sceneObj: Object, - roomObj: Object, - roomShapeType: str, - model: OOTModel, - roomIndex: int, - sceneName: str, - saveTexturesAsPNG: bool, - ): - i = 0 - mainHeaderProps = roomObj.ootRoomHeader - altHeader = RoomAlternateHeader(f"{name}_alternateHeaders") - altProp = roomObj.ootAlternateRoomHeaders - - if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and len(mainHeaderProps.bgImageList) == 0: - raise PluginError(f'Room {roomObj.name} uses room shape "Image" but doesn\'t have any BG images.') - - if mainHeaderProps.roomShape == "ROOM_SHAPE_TYPE_IMAGE" and roomIndex >= 1: - raise PluginError(f'Room shape "Image" can only have one room in the scene.') - - mainHeader = RoomHeader.new( - f"{name}_header{i:02}", - mainHeaderProps, - sceneObj, - roomObj, - transform, - i, - ) - hasAlternateHeaders = False - - for i, header in enumerate(altHeaderList, 1): - altP: OOTRoomHeaderProperty = getattr(altProp, f"{header}Header") - if not altP.usePreviousHeader: - hasAlternateHeaders = True - newRoomHeader = RoomHeader.new( - f"{name}_header{i:02}", - altP, - sceneObj, - roomObj, - transform, - i, - ) - setattr(altHeader, header, newRoomHeader) - - altHeader.cutscenes = [ - RoomHeader.new( - f"{name}_header{i:02}", - csHeader, - sceneObj, - roomObj, - transform, - i, - ) - for i, csHeader in enumerate(altProp.cutsceneHeaders, 4) - ] - - hasAlternateHeaders = True if len(altHeader.cutscenes) > 0 else hasAlternateHeaders - altHeader = altHeader if hasAlternateHeaders else None - headers: list[RoomHeader] = [mainHeader] - if altHeader is not None: - headers.extend([altHeader.childNight, altHeader.adultDay, altHeader.adultNight]) - if len(altHeader.cutscenes) > 0: - headers.extend(altHeader.cutscenes) - addMissingObjectsToAllRoomHeaders(roomObj, headers) - - # Mesh stuff - mesh = OOTRoomMesh(name, roomShapeType, model) - pos, _, scale, _ = Utility.getConvertedTransform(transform, sceneObj, roomObj, True) - cullGroup = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) - DLGroup = mesh.addMeshGroup(cullGroup).DLGroup - boundingBox = BoundingBox() - ootProcessMesh( - mesh, - DLGroup, - sceneObj, - roomObj, - transform, - not saveTexturesAsPNG, - None, - boundingBox, - ) - cullGroup.position, cullGroup.cullDepth = boundingBox.getEnclosingSphere() - mesh.terminateDLs() - mesh.removeUnusedEntries() - roomShape = RoomShape.new(roomShapeType, mainHeaderProps, mesh, sceneName, name) - return Room(name, roomIndex, mainHeader, altHeader, mesh, roomShape, hasAlternateHeaders) - - def getRoomHeaderFromIndex(self, headerIndex: int) -> RoomHeader | None: - """Returns the current room header based on the header index""" - - if headerIndex == 0: - return self.mainHeader - - for i, header in enumerate(altHeaderList, 1): - if headerIndex == i: - return getattr(self.altHeader, header) - - for i, csHeader in enumerate(self.altHeader.cutscenes, 4): - if headerIndex == i: - return csHeader - - return None - - def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): - """Returns the room commands list""" - - cmdListData = CData() - listName = f"SceneCmd {curHeader.name}" - - # .h - cmdListData.header = f"extern {listName}[];\n" - - # .c - cmdListData.source = ( - (f"{listName}[]" + " = {\n") - + (Utility.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") - + self.roomShape.getCmd() - + curHeader.infos.getCmds() - + (curHeader.objects.getCmd() if len(curHeader.objects.objectList) > 0 else "") - + (curHeader.actors.getCmd() if len(curHeader.actors.actorList) > 0 else "") - + Utility.getEndCmd() - + "};\n\n" - ) - - return cmdListData - - def getRoomMainC(self): - """Returns the C data of the main informations of a room""" - - roomC = CData() - roomHeaders: list[tuple[RoomHeader, str]] = [] - altHeaderPtrList = None - - if self.hasAlternateHeaders: - roomHeaders: list[tuple[RoomHeader, str]] = [ - (self.altHeader.childNight, "Child Night"), - (self.altHeader.adultDay, "Adult Day"), - (self.altHeader.adultNight, "Adult Night"), - ] - - for i, csHeader in enumerate(self.altHeader.cutscenes): - roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - - altHeaderPtrListName = f"SceneCmd* {self.altHeader.name}" - - # .h - roomC.header = f"extern {altHeaderPtrListName}[];\n" - - # .c - altHeaderPtrList = ( - f"{altHeaderPtrListName}[]" - + " = {\n" - + "\n".join( - indent + f"{curHeader.name}," if curHeader is not None else indent + "NULL," - for (curHeader, _) in roomHeaders - ) - + "\n};\n\n" - ) - - roomHeaders.insert(0, (self.mainHeader, "Child Day (Default)")) - for i, (curHeader, headerDesc) in enumerate(roomHeaders): - if curHeader is not None: - roomC.source += "/**\n * " + f"Header {headerDesc}\n" + "*/\n" - roomC.source += curHeader.getHeaderDefines() - roomC.append(self.getCmdList(curHeader, i == 0 and self.hasAlternateHeaders)) - - if i == 0 and self.hasAlternateHeaders and altHeaderPtrList is not None: - roomC.source += altHeaderPtrList - - if len(curHeader.objects.objectList) > 0: - roomC.append(curHeader.objects.getC()) - - if len(curHeader.actors.actorList) > 0: - roomC.append(curHeader.actors.getC()) - - return roomC - - def getRoomShapeModelC(self, textureSettings: TextureExportSettings): - """Returns the C data of the room model""" - roomModel = CData() - - for i, entry in enumerate(self.mesh.meshEntries): - if entry.DLGroup.opaque is not None: - roomModel.append(entry.DLGroup.opaque.to_c(self.mesh.model.f3d)) - - if entry.DLGroup.transparent is not None: - roomModel.append(entry.DLGroup.transparent.to_c(self.mesh.model.f3d)) - - # type ``ROOM_SHAPE_TYPE_IMAGE`` only allows 1 room - if i == 0 and self.mesh.roomShape == "ROOM_SHAPE_TYPE_IMAGE": - break - - roomModel.append(self.mesh.model.to_c(textureSettings, OOTGfxFormatter(ScrollMethod.Vertex)).all()) - - if self.roomShape.multiImg is not None: - roomModel.append(self.roomShape.multiImg.getC()) - roomModel.append(self.roomShape.getRoomShapeBgImgDataC(self.mesh, textureSettings)) - - return roomModel - - def getNewRoomFile(self, path: str, isSingleFile: bool, textureExportSettings: TextureExportSettings): - """Returns a new ``RoomFile`` element""" - - roomMainData = self.getRoomMainC() - roomModelData = self.getRoomShapeModelC(textureExportSettings) - roomModelInfoData = self.roomShape.getRoomShapeC() - - return RoomFile( - self.name, - roomMainData.source, - roomModelData.source, - roomModelInfoData.source, - isSingleFile, - path, - roomMainData.header + roomModelData.header + roomModelInfoData.header, - ) diff --git a/fast64_internal/oot/exporter/room/mesh.py b/fast64_internal/oot/exporter/room/mesh.py deleted file mode 100644 index abd7e8726..000000000 --- a/fast64_internal/oot/exporter/room/mesh.py +++ /dev/null @@ -1,187 +0,0 @@ -import os, bpy, mathutils -from ....f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict - -from ....utility import ( - PluginError, - toAlnum, -) - -from ...oot_utility import ( - CullGroup, - checkUniformScale, - ootConvertTranslation, -) - -from ...oot_level_classes import OOTDLGroup - - -class BoundingBox: - def __init__(self): - self.minPoint = None - self.maxPoint = None - self.points = [] - - def addPoint(self, point: tuple[float, float, float]): - if self.minPoint is None: - self.minPoint = list(point[:]) - else: - for i in range(3): - if point[i] < self.minPoint[i]: - self.minPoint[i] = point[i] - if self.maxPoint is None: - self.maxPoint = list(point[:]) - else: - for i in range(3): - if point[i] > self.maxPoint[i]: - self.maxPoint[i] = point[i] - self.points.append(point) - - def addMeshObj(self, obj: bpy.types.Object, transform: mathutils.Matrix): - mesh = obj.data - for vertex in mesh.vertices: - self.addPoint(transform @ vertex.co) - - def getEnclosingSphere(self) -> tuple[float, float]: - centroid = (mathutils.Vector(self.minPoint) + mathutils.Vector(self.maxPoint)) / 2 - radius = 0 - for point in self.points: - distance = (mathutils.Vector(point) - centroid).length - if distance > radius: - radius = distance - - # print(f"Radius: {radius}, Centroid: {centroid}") - - transformedCentroid = [round(value) for value in centroid] - transformedRadius = round(radius) - return transformedCentroid, transformedRadius - - -# This function should be called on a copy of an object -# The copy will have modifiers / scale applied and will be made single user -# When we duplicated obj hierarchy we stripped all ignore_renders from hierarchy. -def ootProcessMesh( - roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox -): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - translation, rotation, scale = relativeTransform.decompose() - - if obj.type == "EMPTY" and obj.ootEmptyType == "Cull Group": - if LODHierarchyObject is not None: - raise PluginError( - obj.name - + " cannot be used as a cull group because it is " - + "in the sub-hierarchy of the LOD group empty " - + LODHierarchyObject.name - ) - - cullProp = obj.ootCullGroupProperty - checkUniformScale(scale, obj) - DLGroup = roomMesh.addMeshGroup( - CullGroup( - ootConvertTranslation(translation), - scale if cullProp.sizeControlsCull else [cullProp.manualRadius], - obj.empty_display_size if cullProp.sizeControlsCull else 1, - ) - ).DLGroup - - elif obj.type == "MESH" and not obj.ignore_render: - triConverterInfo = TriangleConverterInfo(obj, None, roomMesh.model.f3d, relativeTransform, getInfoDict(obj)) - fMeshes = saveStaticModel( - triConverterInfo, - roomMesh.model, - obj, - relativeTransform, - roomMesh.model.name, - convertTextureData, - False, - "oot", - ) - if fMeshes is not None: - for drawLayer, fMesh in fMeshes.items(): - DLGroup.addDLCall(fMesh.draw, drawLayer) - - boundingBox.addMeshObj(obj, relativeTransform) - - alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) - for childObj in alphabeticalChildren: - if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": - ootProcessLOD( - roomMesh, - DLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - else: - ootProcessMesh( - roomMesh, - DLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - - -def ootProcessLOD( - roomMesh, DLGroup, sceneObj, obj, transformMatrix, convertTextureData, LODHierarchyObject, boundingBox: BoundingBox -): - relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world - translation, rotation, scale = relativeTransform.decompose() - ootTranslation = ootConvertTranslation(translation) - - LODHierarchyObject = obj - name = toAlnum(roomMesh.model.name + "_" + obj.name + "_lod") - opaqueLOD = roomMesh.model.addLODGroup(name + "_opaque", ootTranslation, obj.f3d_lod_always_render_farthest) - transparentLOD = roomMesh.model.addLODGroup( - name + "_transparent", ootTranslation, obj.f3d_lod_always_render_farthest - ) - - index = 0 - for childObj in obj.children: - # This group will not be converted to C directly, but its display lists will be converted through the FLODGroup. - childDLGroup = OOTDLGroup(name + str(index), roomMesh.model.DLFormat) - index += 1 - - if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": - ootProcessLOD( - roomMesh, - childDLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - else: - ootProcessMesh( - roomMesh, - childDLGroup, - sceneObj, - childObj, - transformMatrix, - convertTextureData, - LODHierarchyObject, - boundingBox, - ) - - # We handle case with no geometry, for the cases where we have "gaps" in the LOD hierarchy. - # This can happen if a LOD does not use transparency while the levels above and below it does. - childDLGroup.createDLs() - childDLGroup.terminateDLs() - - # Add lod AFTER processing hierarchy, so that DLs will be built by then - opaqueLOD.add_lod(childDLGroup.opaque, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) - transparentLOD.add_lod(childDLGroup.transparent, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) - - opaqueLOD.create_data() - transparentLOD.create_data() - - DLGroup.addDLCall(opaqueLOD.draw, "Opaque") - DLGroup.addDLCall(transparentLOD.draw, "Transparent") diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 1f8f8bdec..77aa65178 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -1,345 +1,726 @@ from dataclasses import dataclass, field from typing import Optional from ....utility import PluginError, CData, toAlnum, indent -from ....f3d.f3d_gbi import TextureExportSettings -from ...oot_level_classes import OOTRoomMesh, OOTBGImage -from ...room.properties import OOTRoomHeaderProperty +from ....f3d.f3d_gbi import SPDisplayList, SPEndDisplayList, GfxListTag, GfxList, DLFormat +from ....f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict +from ...room.properties import OOTRoomHeaderProperty, OOTBGProperty +from ...oot_model_classes import OOTModel +import bpy, shutil, os +from ..utility import Utility +from bpy.types import Object +from mathutils import Matrix, Vector + +from ...oot_utility import ( + CullGroup, + checkUniformScale, + ootConvertTranslation, +) @dataclass -class RoomShapeImageBase: - """This class defines the basic informations shared by other image classes""" - +class RoomShapeDListsEntry: # previously OOTDLGroup + OOTRoomMeshGroup name: str - type: str - amountType: str # ROOM_SHAPE_IMAGE_AMOUNT_SINGLE/_MULTI - entryArrayName: str + opaque: Optional[GfxList] = field(init=False, default=None) + transparent: Optional[GfxList] = field(init=False, default=None) + + def __post_init__(self): + self.name = toAlnum(self.name) + + def to_c(self): + opaque = self.opaque.name if self.opaque else "NULL" + transparent = self.transparent.name if self.transparent else "NULL" + return f"{opaque}, {transparent}" + + def add_dl_call(self, display_list: GfxList, draw_layer: str): + if draw_layer == "Opaque": + if self.opaque is None: + self.opaque = GfxList(self.name + "_opaque", GfxListTag.Draw, DLFormat.Static) + self.opaque.commands.append(SPDisplayList(display_list)) + elif draw_layer == "Transparent": + if self.transparent is None: + self.transparent = GfxList(self.name + "_transparent", GfxListTag.Draw, DLFormat.Static) + self.transparent.commands.append(SPDisplayList(display_list)) + else: + raise PluginError("Unhandled draw layer: " + str(draw_layer)) + + def terminate_dls(self): + if self.opaque is not None: + self.opaque.commands.append(SPEndDisplayList()) + + if self.transparent is not None: + self.transparent.commands.append(SPEndDisplayList()) + + def create_dls(self): + if self.opaque is None: + self.opaque = GfxList(self.name + "_opaque", GfxListTag.Draw, DLFormat.Static) + if self.transparent is None: + self.transparent = GfxList(self.name + "_transparent", GfxListTag.Draw, DLFormat.Static) + + def is_empty(self): + return self.opaque is None and self.transparent is None @dataclass -class RoomShapeDListsEntry: - """This class defines a display list pointer entry""" +class RoomShape: # previously OOTRoomMesh + """This class is the base class for all room shapes.""" + + name: str + """Name of struct itself""" + + model: OOTModel + """Stores all graphical data""" + + dl_entry_array_name: str + """Name of RoomShapeDListsEntry list""" + + dl_entries: list[RoomShapeDListsEntry] = field(init=False, default_factory=list) + + def to_c_dl_entries(self): + """Converts list of dl entries to c. This is usually appended to end of CData in to_c().""" + info_data = CData() + list_name = f"RoomShapeDListsEntry {self.dl_entry_array_name}" + f"[{len(self.dl_entries)}]" + + # .h + info_data.header = f"extern {list_name};\n" + + # .c + info_data.source = ( + (list_name + " = {\n") + + (indent + f",\n{indent}".join("{ " + elem.to_c() + " }" for elem in self.dl_entries)) + + "\n};\n\n" + ) - opaPtr: str - xluPtr: str + return info_data - def getEntryC(self): - return f"{self.opaPtr}, {self.xluPtr}" + def to_c(self) -> CData: + raise PluginError("to_c() not implemented.") + + def to_c_img(self, include_dir: str): + """Returns C representation of image data in room shape""" + return CData() + + def get_type(self) -> str: + """Returns value in oot_constants.ootEnumRoomShapeType""" + raise PluginError("get_type() not implemented.") + + def add_dl_entry(self, cull_group: Optional[CullGroup] = None) -> RoomShapeDListsEntry: + entry = RoomShapeDListsEntry(f"{self.name}_entry_{len(self.dl_entries)}") + self.dl_entries.append(entry) + return entry + + def remove_unused_entries(self): + new_list = [] + for entry in self.dl_entries: + if not entry.is_empty(): + new_list.append(entry) + self.dl_entries = new_list + + def terminate_dls(self): + for entry in self.dl_entries: + entry.terminate_dls() + + def get_cmd(self) -> str: + """Returns the room shape room command""" + + return indent + f"SCENE_CMD_ROOM_SHAPE(&{self.name}),\n" + + def copy_bg_images(self, export_path: str): + return # by default, do nothing @dataclass -class RoomShapeImageMultiBgEntry: - """This class defines an image entry for the multiple image mode""" +class RoomShapeNormal(RoomShape): + """This class defines the basic informations shared by other image classes""" + + def to_c(self): + """Returns the C data for the room shape""" - bgCamIndex: int - imgName: str - width: int - height: int - otherModeFlags: str + info_data = CData() + list_name = f"RoomShapeNormal {self.name}" + + # .h + info_data.header = f"extern {list_name};\n" - unk_00: int = field(init=False, default=130) + # .c + num_entries = f"ARRAY_COUNT({self.dl_entry_array_name})" + info_data.source = ( + (list_name + " = {\n" + indent) + + f",\n{indent}".join( + [ + f"{self.get_type()}", + num_entries, + f"{self.dl_entry_array_name}", + f"{self.dl_entry_array_name} + {num_entries}", + ] + ) + + "\n};\n\n" + ) + + info_data.append(self.to_c_dl_entries()) + + return info_data + + def get_type(self): + return "ROOM_SHAPE_TYPE_NORMAL" + + +@dataclass +class RoomShapeImageEntry: # OOTBGImage + name: str # source + image: bpy.types.Image + + # width: str + # height: str + format: str # fmt + size: str # siz + other_mode_flags: str # tlutMode + # bg_cam_index: int = 0 # bgCamIndex: for which bg cam index is this entry for + + unk_00: int = field(init=False, default=130) # for multi images only unk_0C: int = field(init=False, default=0) tlut: str = field(init=False, default="NULL") format: str = field(init=False, default="G_IM_FMT_RGBA") size: str = field(init=False, default="G_IM_SIZ_16b") - tlutCount: int = field(init=False, default=0) + tlut_count: int = field(init=False, default=0) # tlutCount - def getEntryC(self): + def get_width(self) -> int: + return self.image.size[0] if self.image else 0 + + def get_height(self) -> int: + return self.image.size[1] if self.image else 0 + + @staticmethod + def new(name: str, prop: OOTBGProperty): + if prop.image is None: + raise PluginError( + 'A room is has room shape "Image" but does not have an image set in one of its BG images.' + ) + return RoomShapeImageEntry( + toAlnum(f"{name}_bg_{prop.image.name}"), + prop.image, + prop.otherModeFlags, + ) + + def get_filename(self) -> str: + return f"{self.name}.jpg" + + def to_c_multi(self, bg_cam_index: int): return ( indent + "{\n" + + indent * 2 + f",\n{indent * 2}".join( [ - f"0x{self.unk_00:04X}, {self.bgCamIndex}", - f"{self.imgName}", + f"0x{self.unk_00:04X}, {bg_cam_index}", + f"{self.name}", f"0x{self.unk_0C:08X}", f"{self.tlut}", - f"{self.width}, {self.height}", + f"{self.get_width()}, {self.get_height()}", f"{self.format}, {self.size}", - f"{self.otherModeFlags}, 0x{self.tlutCount:04X},", + f"{self.other_mode_flags}, 0x{self.tlut_count:04X},", ] ) + + "\n" + indent - + " },\n" + + "},\n" + ) + + def to_c_single(self) -> str: + return indent + f",\n{indent}".join( + [ + f"{self.name}", + f"0x{self.unk_0C:08X}", + f"{self.tlut}", + f"{self.get_width()}, {self.get_height()}", + f"{self.format}, {self.size}", + f"{self.other_mode_flags}, 0x{self.tlut_count:04X},", + ] ) @dataclass -class RoomShapeImageMultiBg: - """This class defines the multiple background image array""" +class RoomShapeImageBase(RoomShape): + """This class defines the basic informations shared by other image classes""" - name: str - entries: list[RoomShapeImageMultiBgEntry] + def get_amount_type(self): + raise PluginError("get_amount_type() not implemented.") - @staticmethod - def new(name: str, mesh: OOTRoomMesh): - entries: list[RoomShapeImageMultiBgEntry] = [] - for i, bgImg in enumerate(mesh.bgImages): - entries.append( - RoomShapeImageMultiBgEntry( - i, bgImg.name, bgImg.image.size[0], bgImg.image.size[1], bgImg.otherModeFlags - ) - ) - return RoomShapeImageMultiBg(name, entries) + def get_type(self): + return "ROOM_SHAPE_TYPE_IMAGE" + + def to_c_dl_entries(self): + if len(self.dl_entries) > 1: + raise PluginError("RoomShapeImage only allows one one dl entry, but multiple found") - def getC(self): - infoData = CData() - listName = f"RoomShapeImageMultiBgEntry {self.name}[{len(self.entries)}]" + info_data = CData() + list_name = f"RoomShapeDListsEntry {self.dl_entry_array_name}" # .h - infoData.header = f"extern {listName};\n" + info_data.header = f"extern {list_name};\n" # .c - infoData.source = listName + " = {\n" + f"".join(elem.getEntryC() for elem in self.entries) + "};\n\n" - - return infoData + info_data.source = (list_name + " = {\n") + (indent + self.dl_entries[0].to_c()) + "\n};\n\n" + return info_data -@dataclass -class RoomShapeDLists: - """This class defines the display list pointer array (or variable)""" - - name: str - isArray: bool - entries: list[RoomShapeDListsEntry] - - @staticmethod - def new(name: str, isArray: bool, mesh: OOTRoomMesh): - entries: list[RoomShapeDListsEntry] = [] - for meshGrp in mesh.meshEntries: - entries.append( - RoomShapeDListsEntry( - meshGrp.DLGroup.opaque.name if meshGrp.DLGroup.opaque is not None else "NULL", - meshGrp.DLGroup.transparent.name if meshGrp.DLGroup.transparent is not None else "NULL", - ) - ) - return RoomShapeDLists(name, isArray, entries) + def to_c_img_single(self, bg_entry: RoomShapeImageEntry, include_dir: str): + """Gets C representation of image data""" + bits_per_value = 64 - def getC(self): - infoData = CData() - listName = f"RoomShapeDListsEntry {self.name}" + f"[{len(self.entries)}]" if self.isArray else "" + data = CData() # .h - infoData.header = f"extern {listName};\n" + data.header = f"extern u{bits_per_value} {bg_entry.name}[];\n" # .c - infoData.source = ( - (listName + " = {\n") - + ( - indent + f",\n{indent}".join("{ " + elem.getEntryC() + " }" for elem in self.entries) - if self.isArray - else indent + self.entries[0].getEntryC() - ) + data.source = ( + # This is to force 8 byte alignment + (f"Gfx {bg_entry.name}_aligner[] = " + "{ gsSPEndDisplayList() };\n" if bits_per_value != 64 else "") + + (f"u{bits_per_value} {bg_entry.name}[SCREEN_WIDTH * SCREEN_HEIGHT / 4]" + " = {\n") + + f'#include "{include_dir + bg_entry.get_filename()}.inc.c"' + "\n};\n\n" ) + return data + + def copy_bg_image(self, entry: RoomShapeImageEntry, export_path: str): + jpeg_compatibility = False + image = entry.image + image_filename = entry.get_filename() + if jpeg_compatibility: + is_packed = image.packed_file is not None + if not is_packed: + image.pack() + oldpath = image.filepath + old_format = image.file_format + try: + image.filepath = bpy.path.abspath(os.path.join(export_path, image_filename)) + image.file_format = "JPEG" + image.save() + if not is_packed: + image.unpack() + image.filepath = oldpath + image.file_format = old_format + except Exception as e: + image.filepath = oldpath + image.file_format = old_format + raise Exception(str(e)) + else: + filepath = bpy.path.abspath(os.path.join(export_path, image_filename)) + shutil.copy(bpy.path.abspath(image.filepath), filepath) - return infoData + def copy_bg_images(self, export_path: str): + raise PluginError("BG image copying not handled!") @dataclass class RoomShapeImageSingle(RoomShapeImageBase): - """This class defines a room shape using only one image""" + bg_entry: RoomShapeImageEntry - imgName: str - width: int - height: int - otherModeFlags: str - - unk_0C: int = field(init=False, default=0) - tlut: str = field(init=False, default="NULL") - format: str = field(init=False, default="G_IM_FMT_RGBA") - size: str = field(init=False, default="G_IM_SIZ_16b") - tlutCount: int = field(init=False, default=0) + def get_amount_type(self): + return "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE" - def getC(self): + def to_c(self): """Returns the single background image mode variable""" - infoData = CData() - listName = f"RoomShapeImageSingle {self.name}" + info_data = CData() + list_name = f"RoomShapeImageSingle {self.name}" # .h - infoData.header = f"extern {listName};\n" + info_data.header = f"extern {list_name};\n" # .c - infoData.source = (listName + " = {\n") + f",\n{indent}".join( - [ - "{ " + f"{self.type}, {self.amountType}, &{self.entryArrayName}" + " }", - f"{self.imgName}", - f"0x{self.unk_0C:08X}", - f"{self.tlut}", - f"{self.width}, {self.height}", - f"{self.format}, {self.size}", - f"{self.otherModeFlags}, 0x{self.tlutCount:04X}", - ] + info_data.source = ( + f"{list_name} = {{\n" + + f"{indent}{{ {self.get_type()}, {self.get_amount_type()}, &{self.dl_entry_array_name}, }},\n" + + self.bg_entry.to_c_single() + + f"\n}};\n\n" ) - return infoData + info_data.append(self.to_c_dl_entries()) + + return info_data + + def to_c_img(self, include_dir: str): + """Returns the image data for image room shapes""" + + return self.to_c_img_single(self.bg_entry, include_dir) + + def copy_bg_images(self, export_path: str): + self.copy_bg_image(self.bg_entry, export_path) @dataclass class RoomShapeImageMulti(RoomShapeImageBase): - """This class defines a room shape using multiple images""" + bg_entry_array_name: str + bg_entries: list[RoomShapeImageEntry] = field(init=False, default_factory=list) + + def get_amount_type(self): + return "ROOM_SHAPE_IMAGE_AMOUNT_MULTI" - bgEntryArrayName: str + def to_c_bg_entries(self) -> CData: + info_data = CData() + list_name = f"RoomShapeImageMultiBgEntry {self.bg_entry_array_name}[{len(self.bg_entries)}]" - def getC(self): + # .h + info_data.header = f"extern {list_name};\n" + + # .c + info_data.source = ( + list_name + " = {\n" + f"".join(elem.to_c_multi(i) for i, elem in enumerate(self.bg_entries)) + "};\n\n" + ) + + return info_data + + def to_c(self) -> CData: """Returns the multiple background image mode variable""" - infoData = CData() - listName = f"RoomShapeImageSingle {self.name}" + info_data = CData() + list_name = f"RoomShapeImageMulti {self.name}" # .h - infoData.header = f"extern {listName};\n" + info_data.header = f"extern {list_name};\n" # .c - infoData.source = (listName + " = {\n") + f",\n{indent}".join( - [ - "{ " + f"{self.type}, {self.amountType}, &{self.entryArrayName}" + " }", - f"ARRAY_COUNT({self.bgEntryArrayName})", - f"{self.bgEntryArrayName}", - ] + info_data.source = ( + (list_name + " = {\n" + indent) + + f",\n{indent}".join( + [ + "{ " + f"{self.get_type()}, {self.get_amount_type()}, &{self.dl_entry_array_name}" + " }", + f"ARRAY_COUNT({self.bg_entry_array_name})", + f"{self.bg_entry_array_name}", + ] + ) + + ",\n};\n\n" ) - return infoData + info_data.append(self.to_c_bg_entries()) + info_data.append(self.to_c_dl_entries()) + + return info_data + + def to_c_img(self, include_dir: str): + """Returns the image data for image room shapes""" + + data = CData() + + for entry in self.bg_entries: + data.append(self.to_c_img_single(entry, include_dir)) + + return data + + def copy_bg_images(self, export_path: str): + for bg_entry in self.bg_entries: + self.copy_bg_image(bg_entry, export_path) @dataclass -class RoomShapeNormal: - """This class defines a normal room shape""" +class RoomShapeCullableEntry( + RoomShapeDListsEntry +): # inheritance is due to functional relation, previously OOTRoomMeshGroup + bounds_sphere_center: tuple[float, float, float] + bounds_sphere_radius: float - name: str - type: str - entryArrayName: str + def to_c(self): + center = ", ".join([f"{n}" for n in self.bounds_sphere_center]) + opaque = self.opaque.name if self.opaque else "NULL" + transparent = self.transparent.name if self.transparent else "NULL" + return f" {{ {center} }}, {self.bounds_sphere_radius}, {opaque}, {transparent}" + + +@dataclass +class RoomShapeCullable(RoomShape): + def get_type(self): + return "ROOM_SHAPE_TYPE_CULLABLE" + + def add_dl_entry(self, cull_group: Optional[CullGroup] = None) -> RoomShapeDListsEntry: + if cull_group is None: + raise PluginError("RoomShapeCullable should always be provided a cull group.") + entry = RoomShapeCullableEntry( + f"{self.name}_entry_{len(self.dl_entries)}", cull_group.position, cull_group.cullDepth + ) + self.dl_entries.append(entry) + return entry + + def to_c_dl_entries(self): + info_data = CData() + list_name = f"RoomShapeCullableEntry {self.dl_entry_array_name}[{len(self.dl_entries)}]" + + # .h + info_data.header = f"extern {list_name};\n" - def getC(self): + # .c + info_data.source = ( + (list_name + " = {\n") + + (indent + f",\n{indent}".join("{ " + elem.to_c() + " }" for elem in self.dl_entries)) + + "\n};\n\n" + ) + + return info_data + + def to_c(self): """Returns the C data for the room shape""" - infoData = CData() - listName = f"RoomShapeNormal {self.name}" + info_data = CData() + list_name = f"RoomShapeCullable {self.name}" # .h - infoData.header = f"extern {listName};\n" + info_data.header = f"extern {list_name};\n" # .c - numEntries = f"ARRAY_COUNT({self.entryArrayName})" - infoData.source = ( - (listName + " = {\n" + indent) + num_entries = f"ARRAY_COUNTU({self.dl_entry_array_name})" # U? see ddan_room_0 + info_data.source = ( + (list_name + " = {\n" + indent) + f",\n{indent}".join( - [f"{self.type}", numEntries, f"{self.entryArrayName}", f"{self.entryArrayName} + {numEntries}"] + [ + f"{self.get_type()}", + num_entries, + f"{self.dl_entry_array_name}", + f"{self.dl_entry_array_name} + {num_entries}", + ] ) + "\n};\n\n" ) - return infoData + info_data.append(self.to_c_dl_entries()) + return info_data -@dataclass -class RoomShape: - """This class hosts every type of room shape""" - - dl: Optional[RoomShapeDLists] - normal: Optional[RoomShapeNormal] - single: Optional[RoomShapeImageSingle] - multiImg: Optional[RoomShapeImageMultiBg] - multi: Optional[RoomShapeImageMulti] +class RoomShapeUtility: @staticmethod - def new(type: str, props: OOTRoomHeaderProperty, mesh: OOTRoomMesh, sceneName: str, roomName: str): - name = f"{roomName}_shapeHeader" - dlName = f"{roomName}_shapeDListEntry" - normal = None - single = None - multiImg = None - multi = None - - match type: - case "ROOM_SHAPE_TYPE_NORMAL": - normal = RoomShapeNormal(name, type, dlName) - case "ROOM_SHAPE_TYPE_IMAGE": - for bgImage in props.bgImageList: - if bgImage.image is None: - raise PluginError( - 'A room is has room shape "Image" but does not have an image set in one of its BG images.' - ) - mesh.bgImages.append( - OOTBGImage( - toAlnum(sceneName + "_bg_" + bgImage.image.name), - bgImage.image, - bgImage.otherModeFlags, - ) - ) - - if len(mesh.bgImages) > 1: - multiImg = RoomShapeImageMultiBg.new(f"{roomName}_shapeMultiBg", mesh) - multi = RoomShapeImageMulti(name, type, "ROOM_SHAPE_IMAGE_AMOUNT_MULTI", dlName, multiImg.name) - else: - bgImg = mesh.bgImages[0] - single = RoomShapeImageSingle( - name, - type, - "ROOM_SHAPE_IMAGE_AMOUNT_SINGLE", - dlName, - bgImg.name, - bgImg.image.size[0], - bgImg.image.size[1], - bgImg.otherModeFlags, - ) - case _: - raise PluginError(f"ERROR: Room Shape not supported: {type}") - return RoomShape(RoomShapeDLists.new(dlName, normal is not None, mesh), normal, single, multiImg, multi) - - def getName(self): - """Returns the correct room shape name based on the type used""" - - if self.normal is not None: - return self.normal.name - elif self.single is not None: - return self.single.name - elif self.multi is not None and self.multiImg is not None: - return self.multi.name - else: - raise PluginError("ERROR: Name not found!") + def create_shape( + scene_name: str, + room_name: str, + room_shape_type: str, + model: OOTModel, + transform: Matrix, + sceneObj: Object, + roomObj: Object, + saveTexturesAsPNG: bool, + props: OOTRoomHeaderProperty, + ): + name = f"{room_name}_shapeHeader" + dl_name = f"{room_name}_shapeDListsEntry" + room_shape = None + + if room_shape_type == "ROOM_SHAPE_TYPE_CULLABLE": + room_shape = RoomShapeCullable(name, model, dl_name) + elif room_shape_type == "ROOM_SHAPE_TYPE_NORMAL": + room_shape = RoomShapeNormal(name, model, dl_name) + elif room_shape_type == "ROOM_SHAPE_TYPE_IMAGE": + if len(props.bgImageList) == 0: + raise PluginError("Cannot create room shape of type image without any images.") + elif len(props.bgImageList) == 1: + room_shape = RoomShapeImageSingle( + name, model, dl_name, RoomShapeImageEntry.new(scene_name, props.bgImageList[0]) + ) + else: + bg_name = f"{room_name}_shapeMultiBg" + room_shape = RoomShapeImageMulti(name, model, dl_name, bg_name) + for bg_image in props.bgImageList: + room_shape.bg_entries.append(RoomShapeImageEntry.new(scene_name, bg_image)) + + pos, _, scale, _ = Utility.getConvertedTransform(transform, sceneObj, roomObj, True) + cull_group = CullGroup(pos, scale, roomObj.ootRoomHeader.defaultCullDistance) + dl_entry = room_shape.add_dl_entry(cull_group) + boundingBox = BoundingBox() + ootProcessMesh( + room_shape, + dl_entry, + sceneObj, + roomObj, + transform, + not saveTexturesAsPNG, + None, + boundingBox, + ) + if isinstance(dl_entry, RoomShapeCullableEntry): + dl_entry.bounds_sphere_center, dl_entry.bounds_sphere_radius = boundingBox.getEnclosingSphere() - def getCmd(self): - """Returns the room shape room command""" + room_shape.terminate_dls() + room_shape.remove_unused_entries() + return room_shape - return indent + f"SCENE_CMD_ROOM_SHAPE(&{self.getName()}),\n" - def getRoomShapeBgImgDataC(self, roomMesh: OOTRoomMesh, textureSettings: TextureExportSettings): - """Returns the image data for image room shapes""" +class BoundingBox: + def __init__(self): + self.minPoint = None + self.maxPoint = None + self.points = [] - dlData = CData() - - bitsPerValue = 64 - for bgImage in roomMesh.bgImages: - # .h - dlData.header += f"extern u{bitsPerValue} {bgImage.name}[];\n" + def addPoint(self, point: tuple[float, float, float]): + if self.minPoint is None: + self.minPoint = list(point[:]) + else: + for i in range(3): + if point[i] < self.minPoint[i]: + self.minPoint[i] = point[i] + if self.maxPoint is None: + self.maxPoint = list(point[:]) + else: + for i in range(3): + if point[i] > self.maxPoint[i]: + self.maxPoint[i] = point[i] + self.points.append(point) + + def addMeshObj(self, obj: bpy.types.Object, transform: Matrix): + mesh = obj.data + for vertex in mesh.vertices: + self.addPoint(transform @ vertex.co) + + def getEnclosingSphere(self) -> tuple[float, float]: + centroid = (Vector(self.minPoint) + Vector(self.maxPoint)) / 2 + radius = 0 + for point in self.points: + distance = (Vector(point) - centroid).length + if distance > radius: + radius = distance + + transformedCentroid = [round(value) for value in centroid] + transformedRadius = round(radius) + return transformedCentroid, transformedRadius + + +# This function should be called on a copy of an object +# The copy will have modifiers / scale applied and will be made single user +# When we duplicated obj hierarchy we stripped all ignore_renders from hierarchy. +def ootProcessMesh( + roomShape: RoomShape, + dlEntry: RoomShapeDListsEntry, + sceneObj, + obj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox: BoundingBox, +): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + translation, rotation, scale = relativeTransform.decompose() + + if obj.type == "EMPTY" and obj.ootEmptyType == "Cull Group": + if LODHierarchyObject is not None: + raise PluginError( + obj.name + + " cannot be used as a cull group because it is " + + "in the sub-hierarchy of the LOD group empty " + + LODHierarchyObject.name + ) - # .c - dlData.source += ( - # This is to force 8 byte alignment - (f"Gfx {bgImage.name}_aligner[] = " + "{ gsSPEndDisplayList() };\n" if bitsPerValue != 64 else "") - + (f"u{bitsPerValue} {bgImage.name}[SCREEN_WIDTH * SCREEN_HEIGHT / 4]" + " = {\n") - + f'#include "{textureSettings.includeDir + bgImage.getFilename()}.inc.c"' - + "\n};\n\n" + cullProp = obj.ootCullGroupProperty + checkUniformScale(scale, obj) + dlEntry = roomShape.add_dl_entry( + CullGroup( + ootConvertTranslation(translation), + scale if cullProp.sizeControlsCull else [cullProp.manualRadius], + obj.empty_display_size if cullProp.sizeControlsCull else 1, ) + ) - return dlData + elif obj.type == "MESH" and not obj.ignore_render: + triConverterInfo = TriangleConverterInfo(obj, None, roomShape.model.f3d, relativeTransform, getInfoDict(obj)) + fMeshes = saveStaticModel( + triConverterInfo, + roomShape.model, + obj, + relativeTransform, + roomShape.model.name, + convertTextureData, + False, + "oot", + ) + if fMeshes is not None: + for drawLayer, fMesh in fMeshes.items(): + dlEntry.add_dl_call(fMesh.draw, drawLayer) + + boundingBox.addMeshObj(obj, relativeTransform) + + alphabeticalChildren = sorted(obj.children, key=lambda childObj: childObj.original_name.lower()) + for childObj in alphabeticalChildren: + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": + ootProcessLOD( + roomShape, + dlEntry, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + else: + ootProcessMesh( + roomShape, + dlEntry, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) - def getRoomShapeC(self): - """Returns the C data for the room shape""" - shapeData = CData() +def ootProcessLOD( + roomShape: RoomShape, + dlEntry: RoomShapeDListsEntry, + sceneObj, + obj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox: BoundingBox, +): + relativeTransform = transformMatrix @ sceneObj.matrix_world.inverted() @ obj.matrix_world + translation, rotation, scale = relativeTransform.decompose() + ootTranslation = ootConvertTranslation(translation) + + LODHierarchyObject = obj + name = toAlnum(roomShape.model.name + "_" + obj.name + "_lod") + opaqueLOD = roomShape.model.addLODGroup(name + "_opaque", ootTranslation, obj.f3d_lod_always_render_farthest) + transparentLOD = roomShape.model.addLODGroup( + name + "_transparent", ootTranslation, obj.f3d_lod_always_render_farthest + ) + + index = 0 + for childObj in obj.children: + # This group will not be converted to C directly, but its display lists will be converted through the FLODGroup. + childDLEntry = RoomShapeDListsEntry(f"{name}{str(index)}") + index += 1 + + if childObj.type == "EMPTY" and childObj.ootEmptyType == "LOD": + ootProcessLOD( + roomShape, + childDLEntry, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) + else: + ootProcessMesh( + roomShape, + childDLEntry, + sceneObj, + childObj, + transformMatrix, + convertTextureData, + LODHierarchyObject, + boundingBox, + ) - if self.normal is not None: - shapeData.append(self.normal.getC()) + # We handle case with no geometry, for the cases where we have "gaps" in the LOD hierarchy. + # This can happen if a LOD does not use transparency while the levels above and below it does. + childDLEntry.create_dls() + childDLEntry.terminate_dls() - if self.single is not None: - shapeData.append(self.single.getC()) + # Add lod AFTER processing hierarchy, so that DLs will be built by then + opaqueLOD.add_lod(childDLEntry.opaque, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) + transparentLOD.add_lod(childDLEntry.transparent, childObj.f3d_lod_z * bpy.context.scene.ootBlenderScale) - if self.multi is not None and self.multiImg is not None: - shapeData.append(self.multi.getC()) - shapeData.append(self.multiImg.getC()) + opaqueLOD.create_data() + transparentLOD.create_data() - shapeData.append(self.dl.getC()) - return shapeData + dlEntry.add_dl_call(opaqueLOD.draw, "Opaque") + dlEntry.add_dl_call(transparentLOD.draw, "Transparent") diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index a91585b88..2f4591f7a 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -52,6 +52,10 @@ def new(name: str, sceneName: str, model: OOTModel, sceneObj: Object, transform: saveTexturesAsPNG, ) + for i in range(min(roomDict.keys()), len(roomDict)): + if i not in roomDict: + raise PluginError(f"Room indices are not consecutive. Missing room index: {i}") + return RoomEntries(name, [roomDict[i] for i in range(min(roomDict.keys()), len(roomDict))]) def getCmd(self): diff --git a/fast64_internal/oot/oot_level_classes.py b/fast64_internal/oot/oot_level_classes.py deleted file mode 100644 index c5b27d85b..000000000 --- a/fast64_internal/oot/oot_level_classes.py +++ /dev/null @@ -1,151 +0,0 @@ -import bpy -import os -import shutil - -from ..utility import PluginError, toAlnum, indent -from ..f3d.f3d_gbi import ( - SPDisplayList, - SPEndDisplayList, - GfxListTag, - GfxList, -) -from ..f3d.occlusion_planes.exporter import OcclusionPlaneCandidatesList - - -class OOTBGImage: - def __init__(self, name: str, image: bpy.types.Image, otherModeFlags: str): - self.name = name - self.image = image - self.otherModeFlags = otherModeFlags - - def getFilename(self) -> str: - return f"{self.name}.jpg" - - def singlePropertiesC(self, tabDepth: int) -> str: - return (indent * tabDepth) + (indent * tabDepth).join( - ( - f"{self.name},\n", - f"0x00000000,\n", # (``unk_0C`` in decomp) - "NULL,\n", - f"{self.image.size[0]}, {self.image.size[1]},\n", - f"G_IM_FMT_RGBA, G_IM_SIZ_16b,\n", # RGBA16 - f"{self.otherModeFlags}, 0x0000", - ) - ) - - def multiPropertiesC(self, tabDepth: int, cameraIndex: int) -> str: - return (indent * tabDepth) + f"0x0082, {cameraIndex},\n" + self.singlePropertiesC(tabDepth) + "\n" - - -class OOTRoomMesh: - def __init__(self, roomName, roomShape, model): - self.roomName = roomName - self.roomShape = roomShape - self.meshEntries: list[OOTRoomMeshGroup] = [] - self.model = model - self.bgImages: list[OOTBGImage] = [] - - def terminateDLs(self): - for entry in self.meshEntries: - entry.DLGroup.terminateDLs() - - def headerName(self): - return str(self.roomName) + "_shapeHeader" - - def entriesName(self): - return str(self.roomName) + ( - "_shapeDListEntry" if self.roomShape != "ROOM_SHAPE_TYPE_CULLABLE" else "_shapeCullableEntry" - ) - - def addMeshGroup(self, cullGroup): - meshGroup = OOTRoomMeshGroup(cullGroup, self.model.DLFormat, self.roomName, len(self.meshEntries)) - self.meshEntries.append(meshGroup) - return meshGroup - - def currentMeshGroup(self): - return self.meshEntries[-1] - - def removeUnusedEntries(self): - newList = [] - for meshEntry in self.meshEntries: - if not meshEntry.DLGroup.isEmpty(): - newList.append(meshEntry) - self.meshEntries = newList - - def copyBgImages(self, exportPath: str): - jpegCompatibility = False # maybe delete some code later if jpeg compatibility improves - for bgImage in self.bgImages: - image = bgImage.image - imageFileName = bgImage.getFilename() - if jpegCompatibility: - isPacked = image.packed_file is not None - if not isPacked: - image.pack() - oldpath = image.filepath - oldFormat = image.file_format - try: - image.filepath = bpy.path.abspath(os.path.join(exportPath, imageFileName)) - image.file_format = "JPEG" - image.save() - if not isPacked: - image.unpack() - image.filepath = oldpath - image.file_format = oldFormat - except Exception as e: - image.filepath = oldpath - image.file_format = oldFormat - raise Exception(str(e)) - else: - filepath = bpy.path.abspath(os.path.join(exportPath, imageFileName)) - shutil.copy(bpy.path.abspath(image.filepath), filepath) - - def getMultiBgStructName(self): - return self.roomName + "BgImage" - - -class OOTDLGroup: - def __init__(self, name, DLFormat): - self.opaque = None - self.transparent = None - self.DLFormat = DLFormat - self.name = toAlnum(name) - - def addDLCall(self, displayList, drawLayer): - if drawLayer == "Opaque": - if self.opaque is None: - self.opaque = GfxList(self.name + "_opaque", GfxListTag.Draw, self.DLFormat) - self.opaque.commands.append(SPDisplayList(displayList)) - elif drawLayer == "Transparent": - if self.transparent is None: - self.transparent = GfxList(self.name + "_transparent", GfxListTag.Draw, self.DLFormat) - self.transparent.commands.append(SPDisplayList(displayList)) - else: - raise PluginError("Unhandled draw layer: " + str(drawLayer)) - - def terminateDLs(self): - if self.opaque is not None: - self.opaque.commands.append(SPEndDisplayList()) - - if self.transparent is not None: - self.transparent.commands.append(SPEndDisplayList()) - - def createDLs(self): - if self.opaque is None: - self.opaque = GfxList(self.name + "_opaque", GfxListTag.Draw, self.DLFormat) - if self.transparent is None: - self.transparent = GfxList(self.name + "_transparent", GfxListTag.Draw, self.DLFormat) - - def isEmpty(self): - return self.opaque is None and self.transparent is None - - -class OOTRoomMeshGroup: - def __init__(self, cullGroup, DLFormat, roomName, entryIndex): - self.cullGroup = cullGroup - self.roomName = roomName - self.entryIndex = entryIndex - - self.DLGroup = OOTDLGroup(self.entryName(), DLFormat) - - def entryName(self): - return self.roomName + "_entry_" + str(self.entryIndex) From 0dbad53d520838b7507e3d3f33de9948dd5428f9 Mon Sep 17 00:00:00 2001 From: kurethedead <59840896+kurethedead@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:01:09 -0700 Subject: [PATCH 97/98] Added occlusion plane system to new exporter (#6) * Deleted scene\exporter, fixed missing code references * Removed unused code from oot_level_writer and oot_level_classes * Removed oot_level_writer, contents moved into appropriate files * Remove unused OOTRoom * Black format * Create shape2.py * Refactor room shape * Remove old files * Remove mesh.py * Remove old comments * Delete old code * Add occlusion plane system to new exporter --- fast64_internal/oot/exporter/room/__init__.py | 4 +-- fast64_internal/oot/exporter/room/header.py | 1 + fast64_internal/oot/exporter/room/shape.py | 29 +++++++++++++++++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/fast64_internal/oot/exporter/room/__init__.py b/fast64_internal/oot/exporter/room/__init__.py index 11e1dd284..390edee6d 100644 --- a/fast64_internal/oot/exporter/room/__init__.py +++ b/fast64_internal/oot/exporter/room/__init__.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional from mathutils import Matrix from bpy.types import Object @@ -126,7 +126,7 @@ def getCmdList(self, curHeader: RoomHeader, hasAltHeaders: bool): cmdListData.source = ( (f"{listName}[]" + " = {\n") + (Utility.getAltHeaderListCmd(self.altHeader.name) if hasAltHeaders else "") - + self.roomShape.get_cmd() + + self.roomShape.get_cmds() + curHeader.infos.getCmds() + (curHeader.objects.getCmd() if len(curHeader.objects.objectList) > 0 else "") + (curHeader.actors.getCmd() if len(curHeader.actors.actorList) > 0 else "") diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 433659b3b..59bea7c22 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -8,6 +8,7 @@ from ...room.properties import OOTRoomHeaderProperty from ..utility import Utility from ..actor import Actor +from .shape import RoomShape @dataclass diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 77aa65178..4d50d59ed 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -9,6 +9,7 @@ from ..utility import Utility from bpy.types import Object from mathutils import Matrix, Vector +from ....f3d.occlusion_planes.exporter import addOcclusionQuads, OcclusionPlaneCandidatesList from ...oot_utility import ( CullGroup, @@ -74,6 +75,13 @@ class RoomShape: # previously OOTRoomMesh """Name of RoomShapeDListsEntry list""" dl_entries: list[RoomShapeDListsEntry] = field(init=False, default_factory=list) + """List of DL entries""" + + occlusion_planes: OcclusionPlaneCandidatesList = field(init=False) + """F3DEX3 occlusion planes""" + + def __post_init__(self): + self.occlusion_planes = OcclusionPlaneCandidatesList(self.name) def to_c_dl_entries(self): """Converts list of dl entries to c. This is usually appended to end of CData in to_c().""" @@ -119,10 +127,18 @@ def terminate_dls(self): for entry in self.dl_entries: entry.terminate_dls() - def get_cmd(self) -> str: - """Returns the room shape room command""" + def get_occlusion_planes_cmd(self): + return ( + indent + + f"SCENE_CMD_OCCLUSION_PLANE_CANDIDATES_LIST({len(self.occlusion_planes.planes)}, {self.occlusion_planes.name}),\n" + ) - return indent + f"SCENE_CMD_ROOM_SHAPE(&{self.name}),\n" + def get_cmds(self) -> str: + """Returns the room shape room commands""" + cmds = indent + f"SCENE_CMD_ROOM_SHAPE(&{self.name}),\n" + if len(self.occlusion_planes.planes) > 0: + cmds += self.get_occlusion_planes_cmd() + return cmds def copy_bg_images(self, export_path: str): return # by default, do nothing @@ -156,6 +172,7 @@ def to_c(self): + "\n};\n\n" ) + info_data.append(self.occlusion_planes.to_c()) info_data.append(self.to_c_dl_entries()) return info_data @@ -336,6 +353,7 @@ def to_c(self): + f"\n}};\n\n" ) + info_data.append(self.occlusion_planes.to_c()) info_data.append(self.to_c_dl_entries()) return info_data @@ -393,6 +411,7 @@ def to_c(self) -> CData: + ",\n};\n\n" ) + info_data.append(self.occlusion_planes.to_c()) info_data.append(self.to_c_bg_entries()) info_data.append(self.to_c_dl_entries()) @@ -481,6 +500,7 @@ def to_c(self): + "\n};\n\n" ) + info_data.append(self.occlusion_planes.to_c()) info_data.append(self.to_c_dl_entries()) return info_data @@ -537,6 +557,9 @@ def create_shape( if isinstance(dl_entry, RoomShapeCullableEntry): dl_entry.bounds_sphere_center, dl_entry.bounds_sphere_radius = boundingBox.getEnclosingSphere() + if bpy.context.scene.f3d_type == "F3DEX3": + addOcclusionQuads(roomObj, room_shape.occlusion_planes, True, transform @ sceneObj.matrix_world.inverted()) + room_shape.terminate_dls() room_shape.remove_unused_entries() return room_shape From 1181cb40a3398a5aa312d1e249a06cc999a43dcf Mon Sep 17 00:00:00 2001 From: Yanis42 <35189056+Yanis42@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:12:37 +0200 Subject: [PATCH 98/98] import cleanup --- fast64_internal/oot/exporter/__init__.py | 4 ---- fast64_internal/oot/exporter/collision/surface.py | 3 +-- fast64_internal/oot/exporter/cutscene/actor_cue.py | 1 - fast64_internal/oot/exporter/cutscene/text.py | 1 - fast64_internal/oot/exporter/decomp_edit/__init__.py | 8 +++----- fast64_internal/oot/exporter/decomp_edit/config.py | 4 +++- fast64_internal/oot/exporter/decomp_edit/spec.py | 9 ++++----- fast64_internal/oot/exporter/room/__init__.py | 2 +- fast64_internal/oot/exporter/room/header.py | 1 - fast64_internal/oot/exporter/room/shape.py | 5 ++++- fast64_internal/oot/exporter/scene/rooms.py | 6 +----- fast64_internal/oot/oot_utility.py | 5 ++++- 12 files changed, 21 insertions(+), 28 deletions(-) diff --git a/fast64_internal/oot/exporter/__init__.py b/fast64_internal/oot/exporter/__init__.py index 290e15b38..e85ef12fc 100644 --- a/fast64_internal/oot/exporter/__init__.py +++ b/fast64_internal/oot/exporter/__init__.py @@ -1,17 +1,13 @@ import bpy import os -import traceback from mathutils import Matrix from bpy.types import Object -from typing import Optional from ...f3d.f3d_gbi import DLFormat, TextureExportSettings -from ..scene.properties import OOTBootupSceneOptions from ..oot_model_classes import OOTModel from ..oot_f3d_writer import writeTextureArraysNew, writeTextureArraysExisting1D from .scene import Scene from .decomp_edit import Files -from .file import SceneFile from ...utility import ( PluginError, diff --git a/fast64_internal/oot/exporter/collision/surface.py b/fast64_internal/oot/exporter/collision/surface.py index ae1c2bfc8..67fc810bf 100644 --- a/fast64_internal/oot/exporter/collision/surface.py +++ b/fast64_internal/oot/exporter/collision/surface.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass, field -from typing import Optional +from dataclasses import dataclass from ....utility import CData, indent diff --git a/fast64_internal/oot/exporter/cutscene/actor_cue.py b/fast64_internal/oot/exporter/cutscene/actor_cue.py index c81ffd32e..7a38c3f0d 100644 --- a/fast64_internal/oot/exporter/cutscene/actor_cue.py +++ b/fast64_internal/oot/exporter/cutscene/actor_cue.py @@ -1,5 +1,4 @@ from dataclasses import dataclass, field -from typing import Optional from ....utility import PluginError, indent from ...oot_constants import ootData from ...cutscene.motion.utility import getRotation, getInteger diff --git a/fast64_internal/oot/exporter/cutscene/text.py b/fast64_internal/oot/exporter/cutscene/text.py index cf085d435..a19efb96e 100644 --- a/fast64_internal/oot/exporter/cutscene/text.py +++ b/fast64_internal/oot/exporter/cutscene/text.py @@ -1,5 +1,4 @@ from dataclasses import dataclass, field -from typing import Optional from ....utility import PluginError, indent from ...cutscene.motion.utility import getInteger from .common import CutsceneCmdBase diff --git a/fast64_internal/oot/exporter/decomp_edit/__init__.py b/fast64_internal/oot/exporter/decomp_edit/__init__.py index 9502e69e8..4f0693f15 100644 --- a/fast64_internal/oot/exporter/decomp_edit/__init__.py +++ b/fast64_internal/oot/exporter/decomp_edit/__init__.py @@ -2,14 +2,12 @@ import re import shutil -from typing import TYPE_CHECKING -from ...oot_utility import getSceneDirFromLevelName +from ...oot_utility import ExportInfo, RemoveInfo, getSceneDirFromLevelName +from ..scene import Scene +from ..file import SceneFile from .scene_table import SceneTableUtility from .spec import SpecUtility -if TYPE_CHECKING: - from ...exporter import SceneExport, ExportInfo, RemoveInfo, Scene, SceneFile - class Files: # TODO: find a better name """This class handles editing decomp files""" diff --git a/fast64_internal/oot/exporter/decomp_edit/config.py b/fast64_internal/oot/exporter/decomp_edit/config.py index 14f2156ee..a11eec82e 100644 --- a/fast64_internal/oot/exporter/decomp_edit/config.py +++ b/fast64_internal/oot/exporter/decomp_edit/config.py @@ -1,4 +1,6 @@ -import os, re +import os +import re + from ....utility import PluginError, readFile, writeFile from ...scene.properties import OOTBootupSceneOptions diff --git a/fast64_internal/oot/exporter/decomp_edit/spec.py b/fast64_internal/oot/exporter/decomp_edit/spec.py index 3387f1ef2..2bc053178 100644 --- a/fast64_internal/oot/exporter/decomp_edit/spec.py +++ b/fast64_internal/oot/exporter/decomp_edit/spec.py @@ -3,12 +3,11 @@ import re from dataclasses import dataclass, field -from typing import Optional, TYPE_CHECKING +from typing import Optional from ....utility import PluginError, writeFile, indent -from ...oot_utility import getSceneDirFromLevelName - -if TYPE_CHECKING: - from ...exporter import ExportInfo, Scene, SceneFile +from ...oot_utility import ExportInfo, getSceneDirFromLevelName +from ..scene import Scene +from ..file import SceneFile @dataclass diff --git a/fast64_internal/oot/exporter/room/__init__.py b/fast64_internal/oot/exporter/room/__init__.py index 390edee6d..3fe3d6c2e 100644 --- a/fast64_internal/oot/exporter/room/__init__.py +++ b/fast64_internal/oot/exporter/room/__init__.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Optional from mathutils import Matrix from bpy.types import Object diff --git a/fast64_internal/oot/exporter/room/header.py b/fast64_internal/oot/exporter/room/header.py index 59bea7c22..433659b3b 100644 --- a/fast64_internal/oot/exporter/room/header.py +++ b/fast64_internal/oot/exporter/room/header.py @@ -8,7 +8,6 @@ from ...room.properties import OOTRoomHeaderProperty from ..utility import Utility from ..actor import Actor -from .shape import RoomShape @dataclass diff --git a/fast64_internal/oot/exporter/room/shape.py b/fast64_internal/oot/exporter/room/shape.py index 4d50d59ed..96278f917 100644 --- a/fast64_internal/oot/exporter/room/shape.py +++ b/fast64_internal/oot/exporter/room/shape.py @@ -1,3 +1,7 @@ +import bpy +import shutil +import os + from dataclasses import dataclass, field from typing import Optional from ....utility import PluginError, CData, toAlnum, indent @@ -5,7 +9,6 @@ from ....f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict from ...room.properties import OOTRoomHeaderProperty, OOTBGProperty from ...oot_model_classes import OOTModel -import bpy, shutil, os from ..utility import Utility from bpy.types import Object from mathutils import Matrix, Vector diff --git a/fast64_internal/oot/exporter/scene/rooms.py b/fast64_internal/oot/exporter/scene/rooms.py index 2f4591f7a..6948ef6bb 100644 --- a/fast64_internal/oot/exporter/scene/rooms.py +++ b/fast64_internal/oot/exporter/scene/rooms.py @@ -1,5 +1,4 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING +from dataclasses import dataclass from mathutils import Matrix from bpy.types import Object from ....utility import PluginError, CData, indent @@ -7,9 +6,6 @@ from ...oot_model_classes import OOTModel from ..room import Room -if TYPE_CHECKING: - from . import Scene - @dataclass class RoomEntries: diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 1b044d21b..f8febd130 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -8,7 +8,7 @@ from bpy.types import Object from bpy.utils import register_class, unregister_class from bpy.types import Object -from typing import Callable, Optional +from typing import Callable, Optional, TYPE_CHECKING from .oot_constants import ootSceneIDToName from dataclasses import dataclass @@ -26,6 +26,9 @@ binOps, ) +if TYPE_CHECKING: + from .scene.properties import OOTBootupSceneOptions + def isPathObject(obj: bpy.types.Object) -> bool: return obj.type == "CURVE" and obj.ootSplineProperty.splineType == "Path"