diff --git a/__init__.py b/__init__.py index 5bf3674..c6a72ee 100644 --- a/__init__.py +++ b/__init__.py @@ -341,7 +341,8 @@ class SM64_ExportGeolayoutObject(bpy.types.Operator): modelLoadInfo = (None, None) if context.scene.geoUseBank0: - addrRange, startRAM = exportGeolayoutObjectBinaryBank0( + addrRange, startRAM, geoStart = \ + exportGeolayoutObjectBinaryBank0( romfileOutput, obj, exportRange, finalTransform, *modelLoadInfo, textDumpFilePath, context.scene.f3d_type, context.scene.isHWv1, @@ -368,7 +369,8 @@ class SM64_ExportGeolayoutObject(bpy.types.Operator): if context.scene.geoUseBank0: self.report({'INFO'}, 'Success! Geolayout at (' + \ hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ - '), to write to RAM Address ' + hex(startRAM)) + '), to write to RAM Address ' + hex(startRAM) + \ + ', with geolayout starting at ' + hex(geoStart)) else: self.report({'INFO'}, 'Success! Geolayout at (' + \ hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ @@ -497,7 +499,8 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): modelLoadInfo = (None, None) if context.scene.geoUseBank0: - addrRange, startRAM = exportGeolayoutArmatureBinaryBank0( + addrRange, startRAM, geoStart = \ + exportGeolayoutArmatureBinaryBank0( romfileOutput, armatureObj, obj, exportRange, finalTransform, *modelLoadInfo, textDumpFilePath, context.scene.f3d_type, context.scene.isHWv1, @@ -523,7 +526,8 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator): if context.scene.geoUseBank0: self.report({'INFO'}, 'Success! Geolayout at (' + \ hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ - '), to write to RAM Address ' + hex(startRAM)) + '), to write to RAM Address ' + hex(startRAM) + \ + ', with geolayout starting at ' + hex(geoStart)) else: self.report({'INFO'}, 'Success! Geolayout at (' + \ hex(addrRange[0]) + ', ' + hex(addrRange[1]) + \ @@ -1457,7 +1461,7 @@ def register(): bpy.types.Scene.geoRAMAddr = bpy.props.StringProperty(name = 'RAM Address', default = '80000000') bpy.types.Scene.geoTexDir = bpy.props.StringProperty( - name ='Include Path', default = '/level/ddd/') + name ='Include Path', default = '/actors/mario/') bpy.types.Scene.geoSaveTextures = bpy.props.BoolProperty( name = 'Save Textures As PNGs') bpy.types.Scene.geoSeparateTextureDef = bpy.props.BoolProperty( diff --git a/fast64_internal/f3d_gbi.py b/fast64_internal/f3d_gbi.py index 55461a5..157690a 100644 --- a/fast64_internal/f3d_gbi.py +++ b/fast64_internal/f3d_gbi.py @@ -1677,6 +1677,8 @@ class FMesh: data = self.draw.to_c_def() for triangleList in self.triangleLists: data += triangleList.to_c_def() + for materialTuple, drawOverride in self.drawMatOverrides.items(): + data += drawOverride.to_c_def() return data class FMaterial: diff --git a/fast64_internal/sm64_camera.py b/fast64_internal/sm64_camera.py index 8c301cc..8ec9550 100644 --- a/fast64_internal/sm64_camera.py +++ b/fast64_internal/sm64_camera.py @@ -61,7 +61,8 @@ class CameraSettingsPanel(bpy.types.Panel): prop_split(layout, camera, 'camType', 'Camera Type') prop_split(layout, camera, 'envType', 'Environment Type') -def saveCameraSettingsToGeolayout(geolayout, cameraObj, rootObj): +def saveCameraSettingsToGeolayout(geolayoutGraph, cameraObj, rootObj): + geolayout = geolayoutGraph.startGeolayout camera = cameraObj.data screenAreaNode = TransformNode(ScreenAreaNode( camera.useDefaultScreenRect, 0xA, camera.screenPos, camera.screenSize)) @@ -93,14 +94,16 @@ def saveCameraSettingsToGeolayout(geolayout, cameraObj, rootObj): frustumNode.children.append(cameraNode) startDLNode = TransformNode(StartNode()) - cameraNode.children.append(startDLNode) + meshGeolayout = geolayoutGraph.addGeolayout(rootObj, rootObj.name) + meshGeolayout.nodes.append(startDLNode) + geolayoutGraph.addJumpNode(cameraNode, geolayout, meshGeolayout) # Moving textures here cameraNode.children.append(TransformNode(RenderObjNode())) cameraNode.children.append(TransformNode(EnvFunctionNode(camera.envType))) - return startDLNode + return meshGeolayout cam_classes = ( CameraSettingsPanel, diff --git a/fast64_internal/sm64_geolayout_classes.py b/fast64_internal/sm64_geolayout_classes.py index aafa356..63d1d1b 100644 --- a/fast64_internal/sm64_geolayout_classes.py +++ b/fast64_internal/sm64_geolayout_classes.py @@ -3,26 +3,147 @@ from .sm64_geolayout_utility import * from .utility import * from .sm64_constants import * import struct +import copy + +class GeolayoutGraph: + def __init__(self, name): + self.startGeolayout = Geolayout(name, True) + # dict of Object : Geolayout + self.secondaryGeolayouts = {} + # dict of Geolayout : Geolayout List (which geolayouts are called) + self.geolayoutCalls = {} + self.sortedList = [] + self.sortedListGenerated = False + + def checkListSorted(self): + if not self.sortedListGenerated: + raise ValueError("Must generate sorted geolayout list first " +\ + 'before calling this function.') + + def size(self): + self.checkListSorted() + size = 0 + for geolayout in self.sortedList: + size += geolayout.size() + + return size + + def addGeolayout(self, obj, name): + geolayout = Geolayout(name, False) + self.secondaryGeolayouts[obj] = geolayout + return geolayout + + def addJumpNode(self, parentNode, caller, callee, index = None): + if index is None: + parentNode.children.append(TransformNode(JumpNode( + True, callee))) + else: + parentNode.children.insert(index, TransformNode(JumpNode( + True, callee))) + self.addGeolayoutCall(caller, callee) + + def addGeolayoutCall(self, caller, callee): + if caller not in self.geolayoutCalls: + self.geolayoutCalls[caller] = [] + self.geolayoutCalls[caller].append(callee) + + def sortGeolayouts(self, geolayoutList, geolayout, callOrder): + if geolayout in self.geolayoutCalls: + for calledGeolayout in self.geolayoutCalls[geolayout]: + geoIndex = geolayoutList.index(geolayout) + if calledGeolayout in geolayoutList: + callIndex = geolayoutList.index(calledGeolayout) + if callIndex < geoIndex: + continue + else: + raise ValueError('Circular geolayout dependency.' +\ + str(callOrder)) + else: + geolayoutList.insert(geolayoutList.index(geolayout), + calledGeolayout) + callOrder = copy.copy(callOrder) + callOrder.append(calledGeolayout) + self.sortGeolayouts(geolayoutList, calledGeolayout, + callOrder) + return geolayoutList + + def generateSortedList(self): + self.sortedList = self.sortGeolayouts([self.startGeolayout], + self.startGeolayout, [self.startGeolayout]) + self.sortedListGenerated = True + + def set_addr(self, address): + self.checkListSorted() + for geolayout in self.sortedList: + geolayout.startAddress = address + address += geolayout.size() + print(geolayout.name + " - " + \ + str(geolayout.startAddress)) + return address + + def to_binary(self, segmentData): + self.checkListSorted() + data = bytearray(0) + for geolayout in self.sortedList: + data += geolayout.to_binary(segmentData) + return data + + def save_binary(self, romfile, segmentData): + for geolayout in self.sortedList: + geolayout.save_binary(romfile, segmentData) + + def to_c(self): + self.checkListSorted() + data = '' + for geolayout in self.sortedList: + data += geolayout.to_c() + return data + + def to_c_def(self): + self.checkListSorted() + data = '' + for geolayout in self.sortedList: + data += geolayout.to_c_def() + return data + + def toTextDump(self, segmentData): + self.checkListSorted() + data = '' + for geolayout in self.sortedList: + data += geolayout.toTextDump(segmentData) + '\n' + return data class Geolayout: - def __init__(self, name): + def __init__(self, name, isStartGeo): self.nodes = [] self.name = toAlnum(name) - # dict of Object : geolayout - self.secondaryGeolayouts = {} + self.startAddress = 0 + self.isStartGeo = isStartGeo + + def size(self): + size = 4 # end command + for node in self.nodes: + size += node.size() + return size def to_binary(self, segmentData): + endCmd = GEO_END if self.isStartGeo else GEO_RETURN data = bytearray(0) for node in self.nodes: data += node.to_binary(segmentData) - data += bytearray([GEO_END, 0x00, 0x00, 0x00]) + data += bytearray([endCmd, 0x00, 0x00, 0x00]) return data + def save_binary(self, romfile, segmentData): + romfile.seek(self.startAddress) + romfile.write(self.to_binary(segmentData)) + def to_c(self): + endCmd = 'GEO_END' if self.isStartGeo else 'GEO_RETURN' data = 'const GeoLayout ' + self.name + '[] = {\n' for node in self.nodes: data += node.to_c(1) - data += '\t' + 'GEO_END(),\n' + data += '\t' + endCmd + '(),\n' data += '};\n' return data @@ -30,10 +151,11 @@ class Geolayout: return 'extern const GeoLayout ' + self.name + '[];\n' def toTextDump(self, segmentData): + endCmd = '01' if self.isStartGeo else '03' data = '' for node in self.nodes: - data += node.toTextDump(1, segmentData) - data += '01 00 00 00' + data += node.toTextDump(0, segmentData) + data += endCmd + ' 00 00 00\n' return data class TransformNode: @@ -41,7 +163,16 @@ class TransformNode: self.node = node self.children = [] self.parent = None - + + def size(self): + size = self.node.size() if self.node is not None else 0 + if len(self.children) > 0 and type(self.node) in nodeGroupClasses: + size += 8 # node open/close + for child in self.children: + size += child.size() + + return size + # Function commands usually effect the following command, so it is similar # to a parent child relationship. def to_binary(self, segmentData): @@ -104,7 +235,6 @@ class TransformNode: raise ValueError("A switch bone must have at least one child bone.") return data - class SwitchOverrideNode: def __init__(self, material, specificMat, drawLayer, overrideType): self.material = material @@ -112,6 +242,30 @@ class SwitchOverrideNode: self.drawLayer = drawLayer self.overrideType = overrideType +class JumpNode: + def __init__(self, storeReturn, geolayout): + self.geolayout = geolayout + self.storeReturn = storeReturn + self.hasDL = False + + def size(self): + return 8 + + def to_binary(self, segmentData): + if segmentData is not None: + startAddress = encodeSegmentedAddr(self.geolayout.startAddress, + segmentData) + else: + startAddress = bytearray([0x00] * 4) + command = bytearray([GEO_BRANCH, + 0x01 if self.storeReturn else 0x00, 0x00, 0x00]) + command.extend(startAddress) + return command + + def to_c(self): + return "GEO_BRANCH(" + ('1, ' if self.storeReturn else '0, ') + \ + self.geolayout.name + '),' + # We add Function commands to nonDeformTransformData because any skinned # 0x15 commands should go before them, as they are usually preceding # an empty transform command (of which they modify?) @@ -120,6 +274,9 @@ class FunctionNode: self.geo_func = geo_func self.func_param = func_param self.hasDL = False + + def size(self): + return 8 def to_binary(self, segmentData): command = bytearray([GEO_CALL_ASM, 0x00]) @@ -136,6 +293,9 @@ class HeldObjectNode: self.geo_func = geo_func self.translate = translate self.hasDL = False + + def size(self): + return 12 def to_binary(self, segmentData): command = bytearray([GEO_HELD_OBJECT, 0x00]) @@ -155,6 +315,9 @@ class StartNode: def __init__(self): self.hasDL = False + def size(self): + return 4 + def to_binary(self, segmentData): command = bytearray([GEO_START, 0x00, 0x00, 0x00]) return command @@ -165,6 +328,9 @@ class StartNode: class EndNode: def __init__(self): self.hasDL = False + + def size(self): + return 4 def to_binary(self, segmentData): command = bytearray([GEO_END, 0x00, 0x00, 0x00]) @@ -178,10 +344,14 @@ class EndNode: # Afterward, for each switch node the node hierarchy is duplicated and # the correct diplsay lists are added. class SwitchNode: - def __init__(self, geo_func, func_param): + def __init__(self, geo_func, func_param, name): self.switchFunc = geo_func self.defaultCase = func_param self.hasDL = False + self.name = name + + def size(self): + return 8 def to_binary(self, segmentData): command = bytearray([GEO_SWITCH, 0x00]) @@ -206,6 +376,20 @@ class TranslateRotateNode: self.fMesh = None self.DLmicrocode = None + + def size(self): + if self.fieldLayout == 0: + size = 16 + elif self.fieldLayout == 1: + size = 8 + elif self.fieldLayout == 2: + size = 8 + elif self.fieldLayout == 3: + size = 4 + + if self.hasDL: + size += 4 + return size def to_binary(self, segmentData): params = ((1 if self.hasDL else 0) << 7) & \ @@ -284,6 +468,9 @@ class TranslateNode: self.translate = translate self.fMesh = None self.DLmicrocode = None + + def size(self): + return 12 if self.hasDL else 8 def to_binary(self, segmentData): params = ((1 if self.hasDL else 0) << 7) | self.drawLayer @@ -316,6 +503,9 @@ class RotateNode: self.rotate = rotate self.fMesh = None self.DLmicrocode = None + + def size(self): + return 12 if self.hasDL else 8 def to_binary(self, segmentData): params = ((1 if self.hasDL else 0) << 7) | self.drawLayer @@ -349,6 +539,9 @@ class BillboardNode: self.translate = translate self.fMesh = None self.DLmicrocode = None + + def size(self): + return 12 if self.hasDL else 8 def to_binary(self, segmentData): params = ((1 if self.hasDL else 0) << 7) | self.drawLayer @@ -377,6 +570,9 @@ class DisplayListNode: self.hasDL = True self.fMesh = None self.DLmicrocode = None + + def size(self): + return 8 def to_binary(self, segmentData): if self.DLmicrocode is None: @@ -400,6 +596,9 @@ class ShadowNode: self.shadowScale = shadow_scale self.hasDL = False + def size(self): + return 8 + def to_binary(self, segmentData): command = bytearray([GEO_START_W_SHADOW, 0x00]) command.extend(self.shadowType.to_bytes(2, 'big')) @@ -421,6 +620,9 @@ class ScaleNode: self.fMesh = None self.DLmicrocode = None + def size(self): + return 12 if self.hasDL else 8 + def to_binary(self, segmentData): params = ((1 if self.hasDL else 0) << 7) | self.drawLayer command = bytearray([GEO_SCALE, params, 0x00, 0x00]) @@ -442,6 +644,9 @@ class StartRenderAreaNode: def __init__(self, cullingRadius): self.cullingRadius = cullingRadius self.hasDL = False + + def size(self): + return 4 def to_binary(self, segmentData): command = bytearray([GEO_START_W_RENDERAREA, 0x00]) @@ -458,6 +663,9 @@ class DisplayListWithOffsetNode: self.translate = translate self.fMesh = None self.DLmicrocode = None + + def size(self): + return 12 def to_binary(self, segmentData): command = bytearray([GEO_LOAD_DL_W_OFFSET, self.drawLayer]) @@ -478,22 +686,25 @@ class DisplayListWithOffsetNode: (self.DLmicrocode.name if self.hasDL else 'NULL') + '),' class ScreenAreaNode: - def __init__(self, useDefaults, entryMinus2Count, position, size): + def __init__(self, useDefaults, entryMinus2Count, position, dimensions): self.useDefaults = useDefaults self.entryMinus2Count = entryMinus2Count self.position = position - self.size = size + self.dimensions = dimensions + + def size(self): + return 12 def to_binary(self, segmentData): position = [160, 120] if self.useDefaults else self.position - size = [160, 120] if self.useDefaults else self.size + dimensions = [160, 120] if self.useDefaults else self.dimensions entryMinus2Count = 0xA if self.useDefaults else self.entryMinus2Count command = bytearray([GEO_SET_RENDER_AREA, 0x00]) command.extend(entryMinus2Count.to_bytes(2, 'big', signed = False)) command.extend(position[0].to_bytes(2, 'big', signed = True)) command.extend(position[1].to_bytes(2, 'big', signed = True)) - command.extend(size[0].to_bytes(2, 'big', signed = True)) - command.extend(size[1].to_bytes(2, 'big', signed = True)) + command.extend(dimensions[0].to_bytes(2, 'big', signed = True)) + command.extend(dimensions[1].to_bytes(2, 'big', signed = True)) return command def to_c(self): @@ -504,11 +715,15 @@ class ScreenAreaNode: else: return 'GEO_NODE_SCREEN_AREA(' + str(self.entryMinus2Count) +\ ', ' + str(self.position[0]) + ', ' + str(self.position[1]) +\ - ', ' + str(self.size[0]) + ', ' + str(self.size[1]) + '),' + ', ' + str(self.dimensions[0]) + ', ' + \ + str(self.dimensions[1]) + '),' class OrthoNode: def __init__(self, scale): self.scale = scale + + def size(self): + return 4 def to_binary(self, segmentData): command = bytearray([GEO_SET_ORTHO, 0x00]) @@ -524,19 +739,24 @@ class FrustumNode: self.fov = fov self.near = int(round(100 * near)) self.far = int(round(100 * far)) + self.useFunc = True # Always use function? + + def size(self): + return 12 if self.useFunc else 8 def to_binary(self, segmentData): - command = bytearray([GEO_SET_CAMERA_FRUSTRUM, 0x00]) + command = bytearray([GEO_SET_CAMERA_FRUSTRUM, + 0x01 if self.useFunc else 0x00]) command.extend(bytearray(struct.pack(">f", self.fov))) command.extend(self.near.to_bytes(2, 'big', signed = True)) # Conversion? command.extend(self.far.to_bytes(2, 'big', signed = True)) # Conversion? - if True: # Always use function? + if self.useFunc: command.extend(bytes.fromhex('8029AA3C')) return command def to_c(self): - if False: # Always use function? + if not self.useFunc: return 'GEO_CAMERA_FRUSTUM(' + format(self.fov, '.4f') +\ ', ' + str(self.near) + ', ' + str(self.far) + '),' else: @@ -547,6 +767,9 @@ class FrustumNode: class ZBufferNode: def __init__(self, enable): self.enable = enable + + def size(self): + return 4 def to_binary(self, segmentData): command = bytearray([GEO_SET_Z_BUF, 0x01 if self.enable else 0x00, @@ -564,6 +787,9 @@ class CameraNode: self.lookAt = \ [int(round(value / sm64ToBlenderScale)) for value in lookAt] + def size(self): + return 20 + def to_binary(self, segmentData): command = bytearray([GEO_CAMERA, 0x00]) command.extend(self.camType.to_bytes(2, 'big', signed = True)) @@ -589,6 +815,9 @@ class CameraNode: class RenderObjNode: def __init__(self): pass + + def size(self): + return 4 def to_binary(self, segmentData): command = bytearray([GEO_SETUP_OBJ_RENDER, 0x00, 0x00, 0x00]) @@ -601,6 +830,9 @@ class BackgroundNode: def __init__(self, isColor, backgroundValue): self.isColor = isColor self.backgroundValue = backgroundValue + + def size(self): + return 8 def to_binary(self, segmentData): command = bytearray([GEO_SET_BG, 0x00]) @@ -623,6 +855,9 @@ class BackgroundNode: class EnvFunctionNode: def __init__(self, index): self.index = index + + def size(self): + return 8 def to_binary(self, segmentData): command = bytearray([GEO_CALL_ASM, 0x00]) diff --git a/fast64_internal/sm64_geolayout_writer.py b/fast64_internal/sm64_geolayout_writer.py index 0ca600c..ca274c5 100644 --- a/fast64_internal/sm64_geolayout_writer.py +++ b/fast64_internal/sm64_geolayout_writer.py @@ -26,7 +26,8 @@ def findStartBone(armatureObj): (poseBone.bone_group.name != "SwitchOption" and \ poseBone.bone_group.name != "Ignore"): return poseBone.name - raise ValueError("No non switch option start bone could be found.") + raise ValueError("No non switch option start bone could be found " +\ + 'in ' + armatureObj.name + '. Is this the root armature?') def prepareGeolayoutExport(armatureObj, obj): # Make object and armature space the same. @@ -89,19 +90,23 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, mathutils.Matrix.Diagonal(armatureObj.scale).to_4x4() # Start geolayout - geolayout = Geolayout(armatureObj.name) if camera is not None: + geolayoutGraph = GeolayoutGraph(armatureObj.name + '_level') cameraObj = getCameraObj(camera) - rootNode = saveCameraSettingsToGeolayout( - geolayout, cameraObj, armatureObj) + meshGeolayout = saveCameraSettingsToGeolayout( + geolayoutGraph, cameraObj, armatureObj) else: + geolayoutGraph = GeolayoutGraph(armatureObj.name) rootNode = TransformNode(StartNode()) - geolayout.nodes.append(rootNode) + geolayoutGraph.startGeolayout.nodes.append(rootNode) + meshGeolayout = geolayoutGraph.startGeolayout processBone(fModel, startBoneName, obj, armatureObj, - convertTransformMatrix, None, None, None, rootNode, [], '', - geolayout, infoDict) - generateSwitchOptions(geolayout.nodes[0]) - return geolayout, fModel + convertTransformMatrix, None, None, None, meshGeolayout.nodes[0], + [], '', meshGeolayout, geolayoutGraph, infoDict) + generateSwitchOptions(meshGeolayout.nodes[0], meshGeolayout, geolayoutGraph, + '') + geolayoutGraph.generateSortedList() + return geolayoutGraph, fModel def selectMeshChildrenOnly(obj): obj.select_set(True) @@ -119,15 +124,16 @@ def convertObjectToGeolayout(obj, convertTransformMatrix, # mathutils.Matrix.Diagonal(obj.scale).to_4x4() # Start geolayout - geolayout = Geolayout(obj.name) if camera is not None: + geolayoutGraph = GeolayoutGraph(obj.name + '_level') cameraObj = getCameraObj(camera) - rootNode = saveCameraSettingsToGeolayout( - geolayout, cameraObj, obj) + meshGeolayout = saveCameraSettingsToGeolayout( + geolayoutGraph, cameraObj, obj) else: + geolayoutGraph = GeolayoutGraph(obj.name) rootNode = TransformNode(StartNode()) - geolayout.nodes.append(rootNode) - geolayout.nodes.append(TransformNode(StartNode())) + geolayoutGraph.startGeolayout.nodes.append(rootNode) + meshGeolayout = geolayoutGraph.startGeolayout # Duplicate objects to apply scale / modifiers / linked data bpy.ops.object.select_all(action = 'DESELECT') @@ -148,7 +154,8 @@ def convertObjectToGeolayout(obj, convertTransformMatrix, bpy.ops.object.modifier_apply(apply_as='DATA', modifier=modifier.name) processMesh(fModel, tempObj, convertTransformMatrix, - rootNode, True) + meshGeolayout.nodes[0], True, geolayoutGraph.startGeolayout, + geolayoutGraph) cleanupDuplicatedObjects(allObjs) obj.select_set(True) bpy.context.view_layer.objects.active = obj @@ -158,26 +165,27 @@ def convertObjectToGeolayout(obj, convertTransformMatrix, bpy.context.view_layer.objects.active = obj raise Exception(str(e)) - return geolayout, fModel + geolayoutGraph.generateSortedList() + return geolayoutGraph, fModel # C Export def exportGeolayoutArmatureC(armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, dirPath, texDir, savePNG, texSeparate, camera): - geolayout, fModel = convertArmatureToGeolayout(armatureObj, obj, + geolayoutGraph, fModel = convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera) - saveGeolayoutC(armatureObj.name, geolayout, fModel, dirPath, texDir, + saveGeolayoutC(armatureObj.name, geolayoutGraph, fModel, dirPath, texDir, savePNG, texSeparate) def exportGeolayoutObjectC(obj, convertTransformMatrix, f3dType, isHWv1, dirPath, texDir, savePNG, texSeparate, camera): - geolayout, fModel = convertObjectToGeolayout(obj, + geolayoutGraph, fModel = convertObjectToGeolayout(obj, convertTransformMatrix, f3dType, isHWv1, camera) - saveGeolayoutC(obj.name, geolayout, fModel, dirPath, texDir, + saveGeolayoutC(obj.name, geolayoutGraph, fModel, dirPath, texDir, savePNG, texSeparate) -def saveGeolayoutC(dirName, geolayout, fModel, dirPath, texDir, savePNG, +def saveGeolayoutC(dirName, geolayoutGraph, fModel, dirPath, texDir, savePNG, texSeparate): geoDirPath = os.path.join(dirPath, toAlnum(dirName)) @@ -196,13 +204,13 @@ def saveGeolayoutC(dirName, geolayout, fModel, dirPath, texDir, savePNG, dlFile.close() geoPath = os.path.join(geoDirPath, 'geo.inc.c') - geoData = geolayout.to_c() + geoData = geolayoutGraph.to_c() geoFile = open(geoPath, 'w') geoFile.write(geoData) geoFile.close() headerPath = os.path.join(geoDirPath, 'header.h') - cDefine = geolayout.to_c_def() + fModel.to_c_def() + cDefine = geolayoutGraph.to_c_def() + fModel.to_c_def() cDefFile = open(headerPath, 'w') cDefFile.write(cDefine) cDefFile.close() @@ -213,29 +221,32 @@ def exportGeolayoutArmatureBinaryBank0(romfile, armatureObj, obj, exportRange, convertTransformMatrix, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, RAMAddr, camera): - geolayout, fModel = convertArmatureToGeolayout(armatureObj, obj, + geolayoutGraph, fModel = convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera) - return saveGeolayoutBinaryBank0(romfile, fModel, geolayout, exportRange, - levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, RAMAddr) + return saveGeolayoutBinaryBank0(romfile, fModel, geolayoutGraph, + exportRange, levelCommandPos, modelID, textDumpFilePath, + f3dType, isHWv1, RAMAddr) def exportGeolayoutObjectBinaryBank0(romfile, obj, exportRange, convertTransformMatrix, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, RAMAddr, camera): - geolayout, fModel = convertObjectToGeolayout(obj, + geolayoutGraph, fModel = convertObjectToGeolayout(obj, convertTransformMatrix, f3dType, isHWv1, camera) - return saveGeolayoutBinaryBank0(romfile, fModel, geolayout, exportRange, - levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, RAMAddr) + return saveGeolayoutBinaryBank0(romfile, fModel, geolayoutGraph, + exportRange, levelCommandPos, modelID, textDumpFilePath, + f3dType, isHWv1, RAMAddr) -def saveGeolayoutBinaryBank0(romfile, fModel, geolayout, exportRange, +def saveGeolayoutBinaryBank0(romfile, fModel, geolayoutGraph, exportRange, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, RAMAddr): fModel.freePalettes() segmentData = copy.copy(bank0Segment) startRAM = get64bitAlignedAddr(RAMAddr) - nonGeoStartAddr = startRAM + len(geolayout.to_binary(None)) + nonGeoStartAddr = startRAM + geolayoutGraph.size() + geolayoutGraph.set_addr(startRAM) addrRange = fModel.set_addr(nonGeoStartAddr) addrEndInROM = addrRange[1] - startRAM + exportRange[0] if addrEndInROM > exportRange[1]: @@ -243,8 +254,7 @@ def saveGeolayoutBinaryBank0(romfile, fModel, geolayout, exportRange, ', which is larger than the specified range.') bytesIO = BytesIO() #actualRAMAddr = get64bitAlignedAddr(RAMAddr) - bytesIO.seek(startRAM) - bytesIO.write(geolayout.to_binary(segmentData)) + geolayoutGraph.save_binary(bytesIO, segmentData) fModel.save_binary(bytesIO, segmentData) data = bytesIO.getvalue()[startRAM:] @@ -254,11 +264,13 @@ def saveGeolayoutBinaryBank0(romfile, fModel, geolayout, exportRange, romfile.seek(startAddress) romfile.write(data) - segPointerData = encodeSegmentedAddr(startRAM, segmentData) + geoStart = geolayoutGraph.startGeolayout.startAddress + segPointerData = encodeSegmentedAddr(geoStart, segmentData) geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID) - geoWriteTextDump(textDumpFilePath, geolayout, segmentData) + geoWriteTextDump(textDumpFilePath, geolayoutGraph, segmentData) - return ((startAddress, startAddress + len(data)), startRAM + 0x80000000) + return ((startAddress, startAddress + len(data)), startRAM + 0x80000000, + geoStart + 0x80000000) # Binary Export @@ -266,10 +278,10 @@ def exportGeolayoutArmatureBinary(romfile, armatureObj, obj, exportRange, convertTransformMatrix, levelData, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, camera): - geolayout, fModel = convertArmatureToGeolayout(armatureObj, obj, + geolayoutGraph, fModel = convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera) - return saveGeolayoutBinary(romfile, geolayout, fModel, exportRange, + return saveGeolayoutBinary(romfile, geolayoutGraph, fModel, exportRange, convertTransformMatrix, levelData, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1) @@ -277,14 +289,14 @@ def exportGeolayoutObjectBinary(romfile, obj, exportRange, convertTransformMatrix, levelData, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1, camera): - geolayout, fModel = convertObjectToGeolayout(obj, + geolayoutGraph, fModel = convertObjectToGeolayout(obj, convertTransformMatrix, f3dType, isHWv1, camera) - return saveGeolayoutBinary(romfile, geolayout, fModel, exportRange, + return saveGeolayoutBinary(romfile, geolayoutGraph, fModel, exportRange, convertTransformMatrix, levelData, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1) -def saveGeolayoutBinary(romfile, geolayout, fModel, exportRange, +def saveGeolayoutBinary(romfile, geolayoutGraph, fModel, exportRange, convertTransformMatrix, levelData, levelCommandPos, modelID, textDumpFilePath, f3dType, isHWv1): fModel.freePalettes() @@ -292,19 +304,20 @@ def saveGeolayoutBinary(romfile, geolayout, fModel, exportRange, # Get length of data, then actually write it after relative addresses # are found. startAddress = get64bitAlignedAddr(exportRange[0]) - nonGeoStartAddr = startAddress + len(geolayout.to_binary(None)) + nonGeoStartAddr = startAddress + geolayoutGraph.size() + geolayoutGraph.set_addr(startAddress) addrRange = fModel.set_addr(nonGeoStartAddr) if addrRange[1] > exportRange[1]: raise ValueError('Size too big: Data ends at ' + hex(addrRange[1]) +\ ', which is larger than the specified range.') - romfile.seek(startAddress) - romfile.write(geolayout.to_binary(levelData)) + geolayoutGraph.save_binary(romfile, levelData) fModel.save_binary(romfile, levelData) - segPointerData = encodeSegmentedAddr(startAddress, levelData) + geoStart = geolayoutGraph.startGeolayout.startAddress + segPointerData = encodeSegmentedAddr(geoStart, levelData) geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID) - geoWriteTextDump(textDumpFilePath, geolayout, levelData) + geoWriteTextDump(textDumpFilePath, geolayoutGraph, levelData) return (startAddress, addrRange[1]), bytesToHex(segPointerData) @@ -316,10 +329,10 @@ def geoWriteLevelCommand(romfile, segPointerData, levelCommandPos, modelID): romfile.seek(levelCommandPos + 4) romfile.write(segPointerData) -def geoWriteTextDump(textDumpFilePath, geolayout, levelData): +def geoWriteTextDump(textDumpFilePath, geolayoutGraph, levelData): if textDumpFilePath is not None: openfile = open(textDumpFilePath, 'w') - openfile.write(geolayout.toTextDump(levelData)) + openfile.write(geolayoutGraph.toTextDump(levelData)) openfile.close() # Switch Handling Process @@ -330,11 +343,19 @@ def geoWriteTextDump(textDumpFilePath, geolayout, levelData): # Afterward, the node hierarchy is traversed again, and any SwitchOverride # nodes are converted to actual geolayout node hierarchies. -def generateSwitchOptions(transformNode): +def generateSwitchOptions(transformNode, geolayout, geolayoutGraph, prefix): + if isinstance(transformNode.node, JumpNode): + for node in transformNode.node.geolayout.nodes: + generateSwitchOptions(node, transformNode.node.geolayout, + geolayoutGraph, prefix) overrideNodes = [] if isinstance(transformNode.node, SwitchNode): + switchName = transformNode.node.name + prefix += '_' + switchName + #prefix = switchName i = 0 while i < len(transformNode.children): + prefixName = prefix + '_opt' + str(i) childNode = transformNode.children[i] if isinstance(childNode.node, SwitchOverrideNode): drawLayer = childNode.node.drawLayer @@ -343,24 +364,55 @@ def generateSwitchOptions(transformNode): overrideType = childNode.node.overrideType # This should be a 0xB node - copyNode = duplicateNode(transformNode.children[0], - transformNode, transformNode.children.index(childNode)) + #copyNode = duplicateNode(transformNode.children[0], + # transformNode, transformNode.children.index(childNode)) index = transformNode.children.index(childNode) transformNode.children.remove(childNode) + + # Switch option bones should have unique names across all + # armatures. + optionGeolayout = geolayoutGraph.addGeolayout( + childNode, prefixName) + geolayoutGraph.addJumpNode(transformNode, geolayout, + optionGeolayout, index) + optionGeolayout.nodes.append(TransformNode(StartNode())) + copyNode = optionGeolayout.nodes[0] + #i -= 1 - # Assumes each switch child has only one child - for overrideChild in transformNode.children[0].children: - generateOverrideHierarchy(copyNode, overrideChild, - material, specificMat, overrideType, drawLayer, index) + # Assumes first child is a jump node to option 0 + # assumes overrideChild starts with a Start node + option0Nodes = transformNode.children[0].node.geolayout.nodes + if len(option0Nodes) == 1 and \ + isinstance(option0Nodes[0].node, StartNode): + for startChild in option0Nodes[0].children: + generateOverrideHierarchy(copyNode, startChild, + material, specificMat, overrideType, drawLayer, + option0Nodes[0].children.index(startChild), + optionGeolayout, geolayoutGraph, + optionGeolayout.name) + else: + for overrideChild in option0Nodes: + generateOverrideHierarchy(copyNode, overrideChild, + material, specificMat, overrideType, drawLayer, + option0Nodes.index(overrideChild), + optionGeolayout, geolayoutGraph, + optionGeolayout.name) if material is not None: overrideNodes.append(copyNode) i += 1 - for childNode in transformNode.children: + for i in range(len(transformNode.children)): + childNode = transformNode.children[i] + if isinstance(transformNode.node, SwitchNode): + prefixName = prefix + '_opt' + str(i) + else: + prefixName = prefix + if childNode not in overrideNodes: - generateSwitchOptions(childNode) + generateSwitchOptions(childNode, geolayout, geolayoutGraph, prefixName) def generateOverrideHierarchy(parentCopyNode, transformNode, - material, specificMat, overrideType, drawLayer, index): + material, specificMat, overrideType, drawLayer, index, geolayout, + geolayoutGraph, switchOptionName): #print(transformNode.node) if isinstance(transformNode.node, SwitchOverrideNode) and \ material is not None: @@ -369,7 +421,29 @@ def generateOverrideHierarchy(parentCopyNode, transformNode, copyNode = TransformNode(copy.copy(transformNode.node)) copyNode.parent = parentCopyNode parentCopyNode.children.insert(index, copyNode) - if not isinstance(copyNode.node, SwitchOverrideNode) and\ + if isinstance(transformNode.node, JumpNode): + jumpName = switchOptionName + '_jump_' +\ + transformNode.node.geolayout.name + jumpGeolayout = geolayoutGraph.addGeolayout(transformNode, jumpName) + oldGeolayout = copyNode.node.geolayout + copyNode.node.geolayout = jumpGeolayout + geolayoutGraph.addGeolayoutCall(geolayout, jumpGeolayout) + startNode = TransformNode(StartNode()) + jumpGeolayout.nodes.append(startNode) + if len(oldGeolayout.nodes) == 1 and \ + isinstance(oldGeolayout.nodes[0].node, StartNode): + for node in oldGeolayout.nodes[0].children: + generateOverrideHierarchy(startNode, node, material, specificMat, + overrideType, drawLayer, + oldGeolayout.nodes[0].children.index(node), + jumpGeolayout, geolayoutGraph, jumpName) + else: + for node in oldGeolayout.nodes: + generateOverrideHierarchy(startNode, node, material, specificMat, + overrideType, drawLayer, oldGeolayout.nodes.index(node), + jumpGeolayout, geolayoutGraph, jumpName) + + elif not isinstance(copyNode.node, SwitchOverrideNode) and\ copyNode.node.hasDL: if material is not None: copyNode.node.DLmicrocode = \ @@ -378,8 +452,9 @@ def generateOverrideHierarchy(parentCopyNode, transformNode, copyNode.node.drawLayer = drawLayer for child in transformNode.children: - generateOverrideHierarchy(copyNode, child, material, specificMat, overrideType, - drawLayer, transformNode.children.index(child)) + generateOverrideHierarchy(copyNode, child, material, specificMat, + overrideType, drawLayer, transformNode.children.index(child), + geolayout, geolayoutGraph, switchOptionName) def addStartNode(transformNode): optionNode = TransformNode(StartNode()) @@ -411,7 +486,8 @@ def addPreRenderAreaNode(parentTransformNode, cullingRadius): # This function should be called on a copy of an object # The copy will have modifiers / scale applied and will be made single user -def processMesh(fModel, obj, transformMatrix, parentTransformNode, isRoot): +def processMesh(fModel, obj, transformMatrix, parentTransformNode, + geolayout, geolayoutGraph, isRoot): #finalTransform = copy.deepcopy(transformMatrix) if not isinstance(obj.data, bpy.types.Mesh): @@ -465,7 +541,8 @@ def processMesh(fModel, obj, transformMatrix, parentTransformNode, isRoot): transformNode.parent = parentTransformNode for childObj in obj.children: - processMesh(fModel, childObj, transformMatrix, transformNode, False) + processMesh(fModel, childObj, transformMatrix, transformNode, + geolayout, geolayoutGraph, False) # need to remember last geometry holding parent bone. # to do skinning, add the 0x15 command before any non-geometry bone groups. @@ -484,7 +561,7 @@ def processMesh(fModel, obj, transformMatrix, parentTransformNode, isRoot): def processBone(fModel, boneName, obj, armatureObj, transformMatrix, lastTranslateName, lastRotateName, lastDeformName, parentTransformNode, - materialOverrides, namePrefix, geolayout, infoDict): + materialOverrides, namePrefix, geolayout, geolayoutGraph, infoDict): bone = armatureObj.data.bones[boneName] poseBone = armatureObj.pose.bones[boneName] boneGroup = poseBone.bone_group @@ -549,7 +626,7 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, if bone.geo_func == '': raise ValueError('Switch bone ' + boneName + \ ' function value is empty.') - node = SwitchNode(bone.geo_func, bone.func_param) + node = SwitchNode(bone.geo_func, bone.func_param, boneName) processSwitchBoneMatOverrides(materialOverrides, bone) elif boneGroup.name == 'Start': @@ -613,7 +690,6 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, transformNode = TransformNode(node) - # TODO: HERE!!! if node.hasDL: meshGroup = saveModelGivenVertexGroup( fModel, obj, bone.name, lastDeformName, @@ -646,36 +722,48 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, else: parentTransformNode.children.append(transformNode) transformNode.parent = parentTransformNode - - isSwitch = isinstance(transformNode.node, SwitchNode) - if isSwitch: - switchTransformNode = transformNode - option0Node = addStartNode(transformNode) - #option0Node = addPreTranslateRotateNode(armatureObj, transformNode, - # mathutils.Vector((0,0,0)), mathutils.Quaternion()) - transformNode = option0Node - #print(boneGroup.name if boneGroup is not None else "Offset") - if len(bone.children) > 0: - #print("\tHas Children") - if boneGroup is not None and boneGroup.name == 'Function': - raise ValueError("Function bones cannot have children. They instead affect the next sibling bone in alphabetical order.") + if not isinstance(transformNode.node, SwitchNode): + #print(boneGroup.name if boneGroup is not None else "Offset") + if len(bone.children) > 0: + #print("\tHas Children") + if boneGroup is not None and boneGroup.name == 'Function': + raise ValueError("Function bones cannot have children. They instead affect the next sibling bone in alphabetical order.") + + # Handle child nodes + # nonDeformTransformData should be modified to be sent to children, + # otherwise it should not be modified for parent. + # This is so it can be used for siblings. + childrenNames = sorted([bone.name for bone in bone.children]) + for name in childrenNames: + processBone(fModel, name, obj, armatureObj, + finalTransform, lastTranslateName, lastRotateName, + lastDeformName, transformNode, materialOverrides, + namePrefix, geolayout, + geolayoutGraph, infoDict) + #transformNode.children.append(childNode) + #childNode.parent = transformNode - # Handle child nodes - # nonDeformTransformData should be modified to be sent to children, - # otherwise it should not be modified for parent. - # This is so it can be used for siblings. - childrenNames = sorted([bone.name for bone in bone.children]) - for name in childrenNames: - processBone(fModel, name, obj, armatureObj, - finalTransform, lastTranslateName, lastRotateName, - lastDeformName, transformNode, materialOverrides, namePrefix, - geolayout, infoDict) - #transformNode.children.append(childNode) - #childNode.parent = transformNode - # see generateSwitchOptions() for explanation. - if isSwitch: + else: + #print(boneGroup.name if boneGroup is not None else "Offset") + if len(bone.children) > 0: + optionGeolayout = \ + geolayoutGraph.addGeolayout( + transformNode, boneName + '_opt0') + geolayoutGraph.addJumpNode(transformNode, geolayout, + optionGeolayout) + optionGeolayout.nodes.append(TransformNode(StartNode())) + childrenNames = sorted([bone.name for bone in bone.children]) + for name in childrenNames: + processBone(fModel, name, obj, armatureObj, + finalTransform, lastTranslateName, lastRotateName, + lastDeformName, optionGeolayout.nodes[0], materialOverrides, + namePrefix, optionGeolayout, + geolayoutGraph, infoDict) + #transformNode.children.append(childNode) + #childNode.parent = transformNode + bone = armatureObj.data.bones[boneName] for switchIndex in range(len( bone.switch_options)): switchOption = bone.switch_options[switchIndex] @@ -689,11 +777,12 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, raise ValueError('Error: In switch bone ' + boneName +\ ' for option ' + str(switchIndex) + \ ', the object provided is not an armature.') - elif optionArmature in geolayout.secondaryGeolayouts: - raise ValueError('Error: In switch bone ' + boneName +\ - ' for option ' + str(switchIndex) + \ - ', the armature provided cannot be used more than once' + \ - ' as a switch option in the entire geolayout.') + elif optionArmature in geolayoutGraph.secondaryGeolayouts: + optionGeolayout = geolayoutGraph.secondaryGeolayouts[ + optionArmature] + geolayoutGraph.addJumpNode( + transformNode, geolayout, optionGeolayout) + continue #optionNode = addStartNode(switchTransformNode) @@ -704,10 +793,21 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, switchOptionRotate = mathutils.Quaternion() # Armature doesn't matter here since node is not based off bone - translateRotateNode = addPreTranslateRotateNode( - switchTransformNode, translate, rotate @ switchOptionRotate) + optionGeolayout = \ + geolayoutGraph.addGeolayout( + optionArmature, optionArmature.name) + geolayoutGraph.addJumpNode(transformNode, geolayout, + optionGeolayout) - geolayout.secondaryGeolayouts[optionArmature] = translateRotateNode + rotAxis, rotAngle = (rotate @ switchOptionRotate + ).to_axis_angle() + if rotAngle > 0.00001 or translate.length > 0.0001: + startNode = TransformNode( + TranslateRotateNode(1, 0, False, translate, + rotate @ switchOptionRotate)) + else: + startNode = TransformNode(StartNode()) + optionGeolayout.nodes.append(startNode) childrenNames = sorted( [bone.name for bone in optionBone.children]) @@ -734,8 +834,9 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, processBone(fModel, name, optionObj, optionArmature, finalTransform, optionBone.name, optionBone.name, - optionBone.name, translateRotateNode, materialOverrides, - optionBone.name, geolayout, optionInfoDict) + optionBone.name, startNode, + materialOverrides, optionBone.name, + optionGeolayout, geolayoutGraph, optionInfoDict) else: if switchOption.switchType == 'Material': material = switchOption.materialOverride @@ -757,8 +858,8 @@ def processBone(fModel, boneName, obj, armatureObj, transformMatrix, overrideNode = TransformNode(SwitchOverrideNode( material, specificMat, drawLayer, switchOption.materialOverrideType)) - overrideNode.parent = switchTransformNode - switchTransformNode.children.append(overrideNode) + overrideNode.parent = transformNode + transformNode.children.append(overrideNode) def processSwitchBoneMatOverrides(materialOverrides, switchBone): for switchOption in switchBone.switch_options: @@ -1050,11 +1151,13 @@ def saveModelGivenVertexGroup(fModel, obj, vertexGroup, fMeshGroup = saveSkinnedMeshByMaterial(skinnedFaces, fModel, vertexGroup, obj, currentMatrix, parentMatrix, namePrefix, infoDict) else: - fMeshGroup = FMeshGroup(toAlnum(namePrefix + vertexGroup), + fMeshGroup = FMeshGroup(toAlnum(namePrefix + \ + ('_' if namePrefix != '' else '') + vertexGroup), FMesh(toAlnum(namePrefix + vertexGroup) + '_mesh'), None) # Save mesh group - checkUniqueBoneNames(fModel, toAlnum(namePrefix + vertexGroup), vertexGroup) + checkUniqueBoneNames(fModel, toAlnum(namePrefix + \ + ('_' if namePrefix != '' else '') + vertexGroup), vertexGroup) fModel.meshGroups[toAlnum(namePrefix + vertexGroup)] = fMeshGroup # Save unskinned mesh