diff --git a/fast64_internal/sm64/sm64_geolayout_writer.py b/fast64_internal/sm64/sm64_geolayout_writer.py index df0c30cd7..ba7c73628 100644 --- a/fast64_internal/sm64/sm64_geolayout_writer.py +++ b/fast64_internal/sm64/sm64_geolayout_writer.py @@ -415,16 +415,11 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, geolayoutGraph = GeolayoutGraph(name + "_geo") if armatureObj.use_render_area: rootNode = TransformNode(StartRenderAreaNode(armatureObj.culling_radius)) - else: - rootNode = TransformNode(StartNode()) - geolayoutGraph.startGeolayout.nodes.append(rootNode) + geolayoutGraph.startGeolayout.nodes.append(rootNode) meshGeolayout = geolayoutGraph.startGeolayout - for i in range(len(startBoneNames)): - startBoneName = startBoneNames[i] - if i > 0: - meshGeolayout.nodes.append(TransformNode(StartNode())) - processBone( + for i, startBoneName in enumerate(startBoneNames): + materialOverrides = processBone( fModel, startBoneName, obj, @@ -433,7 +428,7 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, None, None, None, - meshGeolayout.nodes[i], + i, [], name, meshGeolayout, @@ -441,14 +436,41 @@ def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, infoDict, convertTextureData, ) + + def walk(node: TransformNode, fMeshes: dict[int, FMesh]): + base_node = node.node + if isinstance(base_node, JumpNode): + if base_node.geolayout is not None: + for node in base_node.geolayout.nodes: + fMeshes = walk(node, fMeshes) + fMesh = getattr(base_node, "fMesh", None) + if fMesh is not None: + if fMeshes.get(base_node.drawLayer, None): + fMeshes[base_node.drawLayer].append(fMesh) + else: + fMeshes[base_node.drawLayer] = [fMesh] + for child in node.children: + fMeshes = walk(child, fMeshes) + return fMeshes + + fMeshes = dict() + for node in geolayoutGraph.startGeolayout.nodes: + fMeshes = walk(node, fMeshes) + + # Must be done after all geometry saved and skinned meshes parented + for material, specificMat, overrideType in materialOverrides: + for drawLayer, fMesh_list in fMeshes.items(): + [ + saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, drawLayer, convertTextureData) + for fMesh in fMesh_list + ] + generateSwitchOptions(meshGeolayout.nodes[0], meshGeolayout, geolayoutGraph, name) appendRevertToGeolayout(geolayoutGraph, fModel) geolayoutGraph.generateSortedList() if inline: bleed_gfx = GeoLayoutBleed() bleed_gfx.bleed_geo_layout_graph(fModel, geolayoutGraph) - # if DLFormat == DLFormat.GameSpecific: - # geolayoutGraph.convertToDynamic() return geolayoutGraph, fModel @@ -1037,179 +1059,145 @@ def geoWriteTextDump(textDumpFilePath, geolayoutGraph, levelData): # Afterward, the node hierarchy is traversed again, and any SwitchOverride # nodes are converted to actual geolayout node hierarchies. -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) +def generateSwitchOptions(transform_node, geolayout, geolayoutGraph, prefix): + if isinstance(transform_node.node, JumpNode): + for node in transform_node.node.geolayout.nodes: + generateSwitchOptions(node, transform_node.node.geolayout, geolayoutGraph, prefix) + overrideNodes = [] - if isinstance(transformNode.node, SwitchNode): - switchName = transformNode.node.name - prefix += "_" + switchName - # prefix = switchName + if isinstance(transform_node.node, SwitchNode): + switchName = transform_node.node.name + prefix = f"{prefix}_{switchName}" materialOverrideTexDimensions = None - 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 - material = childNode.node.material - specificMat = childNode.node.specificMat - overrideType = childNode.node.overrideType - texDimensions = childNode.node.texDimensions - if ( - texDimensions is not None - and materialOverrideTexDimensions is not None - and materialOverrideTexDimensions != tuple(texDimensions) - ): - raise PluginError( - 'In switch bone "' - + switchName - + '", some material ' - + "overrides \nhave textures with dimensions differing from the original material.\n" - + "UV coordinates are in pixel units, so there will be UV errors in those overrides.\n " - + "Make sure that all overrides have the same texture dimensions as the original material.\n" - + "Note that materials with no textures default to dimensions of 32x32." - ) + for child_index, child_node in enumerate(transform_node.children): + # first child + if not isinstance(child_node.node, SwitchOverrideNode): + continue - if texDimensions is not None: - materialOverrideTexDimensions = tuple(texDimensions) - - # This should be a 0xB node - # copyNode = duplicateNode(transformNode.children[0], - # transformNode, transformNode.children.index(childNode)) - index = transformNode.children.index(childNode) - transformNode.children.remove(childNode) - - # 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 first child is a start node, where option 0 is - # assumes overrideChild starts with a Start node - option0Nodes = [transformNode.children[0]] - 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 i in range(len(transformNode.children)): - childNode = transformNode.children[i] - if isinstance(transformNode.node, SwitchNode): - prefixName = prefix + "_opt" + str(i) + prefix_name = f"{prefix}_opt{child_index}" + child_node_tex_dimensions = child_node.node.texDimensions + if ( + child_node_tex_dimensions is not None + and materialOverrideTexDimensions is not None + and materialOverrideTexDimensions != tuple(child_node_tex_dimensions) + ): + raise PluginError( + 'In switch bone "' + + switchName + + '", some material ' + + "overrides \nhave textures with dimensions differing from the original material.\n" + + "UV coordinates are in pixel units, so there will be UV errors in those overrides.\n " + + "Make sure that all overrides have the same texture dimensions as the original material.\n" + + "Note that materials with no textures default to dimensions of 32x32." + ) + + if child_node_tex_dimensions is not None: + materialOverrideTexDimensions = tuple(child_node_tex_dimensions) + + # Switch option bones should have unique names across all armatures. + transform_node.children.remove(child_node) # remove child node aka switch override node + option_geo_layout = geolayoutGraph.addGeolayout( + child_node, prefix_name + ) # add geo layout starting from child node + geolayoutGraph.addJumpNode( + transform_node, geolayout, option_geo_layout, child_index + ) # replace switch override with jump node to override GL + + # for switch overrides, each one is a copy of the first child, which is the default option, so iterate the first childs + # hierarchy and create a copy of that and place it in the option_geo_layout + generateOverrideHierarchy( + None, + transform_node.children[0], + child_node.node.material, + child_node.node.specificMat, + child_node.node.overrideType, + child_node.node.drawLayer, + 0, + option_geo_layout, + geolayoutGraph, + option_geo_layout.name, + ) + + for i, child_node in enumerate(transform_node.children): + if isinstance(transform_node.node, SwitchNode): + prefix_name = f"{prefix}_opt{i}" else: - prefixName = prefix + prefix_name = prefix - if childNode not in overrideNodes: - generateSwitchOptions(childNode, geolayout, geolayoutGraph, prefixName) + if child_node not in overrideNodes: + generateSwitchOptions(child_node, geolayout, geolayoutGraph, prefix_name) def generateOverrideHierarchy( - parentCopyNode, - transformNode, + parent_node, + transform_node, material, specificMat, overrideType, drawLayer, index, geolayout, - geolayoutGraph, + geolayout_graph, switchOptionName, ): - # print(transformNode.node) - if isinstance(transformNode.node, SwitchOverrideNode) and material is not None: + if isinstance(transform_node.node, SwitchOverrideNode) and material is not None: return - copyNode = TransformNode(copy.copy(transformNode.node)) - copyNode.parent = parentCopyNode - parentCopyNode.children.insert(index, copyNode) - 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, - ) + # copy the node hierarchy, append transform_node to root if no parent + if parent_node: + copy_node = TransformNode(copy.copy(transform_node.node)) + copy_node.parent = parent_node + parent_node.children.insert(index, copy_node) + else: + copy_node = TransformNode(copy.copy(transform_node.node)) + geolayout.nodes.append(copy_node) + + # if you have a jump node, create a full copy by parsing through that geo layout + if isinstance(transform_node.node, JumpNode): + jump_name = f"{switchOptionName}_jump_{transform_node.node.geolayout.name}" + jump_geo_layout = geolayout_graph.addGeolayout(transform_node, jump_name) + original_geo_layout = copy_node.node.geolayout + copy_node.node.geolayout = jump_geo_layout + geolayout_graph.addGeolayoutCall(geolayout, jump_geo_layout) + + # iterate starting with the first nodes children + for index, node in enumerate(original_geo_layout.nodes): + print("jump geo", type(node.node)) + generateOverrideHierarchy( + None, + node, + material, + specificMat, + overrideType, + drawLayer, + index, + jump_geo_layout, + geolayout_graph, + jump_name, + ) - elif not isinstance(copyNode.node, SwitchOverrideNode) and copyNode.node.hasDL: + # replace the DL with the override DL + elif not isinstance(copy_node.node, SwitchOverrideNode) and copy_node.node.hasDL: if material is not None: - copyNode.node.DLmicrocode = copyNode.node.fMesh.drawMatOverrides[(material, specificMat, overrideType)] - copyNode.node.override_hash = (material, specificMat, overrideType) + copy_node.node.DLmicrocode = copy_node.node.fMesh.drawMatOverrides[(material, specificMat, overrideType)] + copy_node.node.override_hash = (material, specificMat, overrideType) if drawLayer is not None: - copyNode.node.drawLayer = drawLayer + copy_node.node.drawLayer = drawLayer - for child in transformNode.children: + # recurse + for index, child in enumerate(transform_node.children): generateOverrideHierarchy( - copyNode, + copy_node, child, material, specificMat, overrideType, drawLayer, - transformNode.children.index(child), + index, geolayout, - geolayoutGraph, + geolayout_graph, switchOptionName, ) @@ -1632,7 +1620,7 @@ def processBone( lastTranslateName, lastRotateName, lastDeformName, - parentTransformNode, + parentTransformNode: Union[int, TransformNode], materialOverrides, namePrefix, geolayout, @@ -1646,7 +1634,7 @@ def processBone( materialOverrides = copy.copy(materialOverrides) if bone.geo_cmd == "Ignore": - return + return materialOverrides # Get translate if lastTranslateName is not None: @@ -1669,7 +1657,14 @@ def processBone( zeroTranslation = isZeroTranslation(translate) zeroRotation = isZeroRotation(rotate) - # hasDL = bone.use_deform + # true when this is the start of the geo layout + # if there is no parent, then instead set the node to be the root of our geo layout + if isinstance(parentTransformNode, int): + if len(geolayout.nodes) > parentTransformNode: + parentTransformNode = geolayout.nodes[parentTransformNode] + else: + parentTransformNode = None + hasDL = True if bone.geo_cmd in animatableBoneTypes: if bone.geo_cmd == "CustomAnimated": @@ -1682,9 +1677,12 @@ def processBone( if not zeroRotation: node = DisplayListWithOffsetNode(int(bone.draw_layer), hasDL, mathutils.Vector((0, 0, 0))) - parentTransformNode = addParentNode( - parentTransformNode, TranslateRotateNode(1, 0, False, translate, rotate) - ) + trans_rotate_node = TranslateRotateNode(1, 0, False, translate, rotate) + if parentTransformNode is None: + parentTransformNode = TransformNode(trans_rotate_node) + geolayout.nodes.append(parentTransformNode) + else: + parentTransformNode = addParentNode(parentTransformNode, trans_rotate_node) lastTranslateName = boneName lastRotateName = boneName @@ -1807,8 +1805,12 @@ def processBone( # bone.use_deform = False if usedDrawLayers is not None: lastDeformName = boneName - parentTransformNode.children.append(transformNode) - transformNode.parent = parentTransformNode + + if parentTransformNode is not None: + parentTransformNode.children.append(transformNode) + transformNode.parent = parentTransformNode + else: + geolayout.nodes.append(transformNode) else: lastDeformName = boneName if not bone.use_deform: @@ -1836,8 +1838,11 @@ def processBone( node.DLmicrocode = fMesh.draw node.fMesh = fMesh # Used for material override switches - parentTransformNode.children.append(transformNode) - transformNode.parent = parentTransformNode + if parentTransformNode is not None: + parentTransformNode.children.append(transformNode) + transformNode.parent = parentTransformNode + else: + geolayout.nodes.append(transformNode) if ( lastDeformName is not None @@ -1857,15 +1862,16 @@ def processBone( for additionalTransformNode in additionalNodes: transformNode.children.append(additionalTransformNode) additionalTransformNode.parent = transformNode - # print(boneName) + else: - parentTransformNode.children.append(transformNode) - transformNode.parent = parentTransformNode + if parentTransformNode is not None: + parentTransformNode.children.append(transformNode) + transformNode.parent = parentTransformNode + else: + geolayout.nodes.append(transformNode) 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 bone.geo_cmd == "Function": raise PluginError( "Function bones cannot have children. They instead affect the next sibling bone in alphabetical order." @@ -1877,7 +1883,7 @@ def processBone( # This is so it can be used for siblings. childrenNames = sorted([bone.name for bone in bone.children]) for name in childrenNames: - processBone( + materialOverrides = processBone( fModel, name, obj, @@ -1894,26 +1900,15 @@ def processBone( infoDict, convertTextureData, ) - # transformNode.children.append(childNode) - # childNode.parent = transformNode # see generateSwitchOptions() for explanation. 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())) - nextStartNode = TransformNode(StartNode()) - transformNode.children.append(nextStartNode) - nextStartNode.parent = transformNode + nextStartNode = transformNode childrenNames = sorted([bone.name for bone in bone.children]) for name in childrenNames: - processBone( + materialOverrides = processBone( fModel, name, obj, @@ -1930,14 +1925,11 @@ def processBone( infoDict, convertTextureData, ) - # transformNode.children.append(childNode) - # childNode.parent = transformNode else: raise PluginError('Switch bone "' + bone.name + '" must have child bones with geometry attached.') bone = armatureObj.data.bones[boneName] - for switchIndex in range(len(bone.switch_options)): - switchOption = bone.switch_options[switchIndex] + for switchIndex, switchOption in enumerate(bone.switch_options): if switchOption.switchType == "Mesh": optionArmature = switchOption.optionArmature if optionArmature is None: @@ -1961,8 +1953,6 @@ def processBone( geolayoutGraph.addJumpNode(transformNode, geolayout, optionGeolayout) continue - # optionNode = addParentNode(switchTransformNode, StartNode()) - optionBoneName = getSwitchOptionBone(optionArmature) optionBone = optionArmature.data.bones[optionBoneName] @@ -1972,9 +1962,9 @@ def processBone( if not zeroRotation or not zeroTranslation: startNode = TransformNode(TranslateRotateNode(1, 0, False, translate, rotate)) + optionGeolayout.nodes.append(startNode) else: - startNode = TransformNode(StartNode()) - optionGeolayout.nodes.append(startNode) + startNode = switchIndex childrenNames = sorted([bone.name for bone in optionBone.children]) for name in childrenNames: @@ -2003,7 +1993,7 @@ def processBone( ) optionObj = optionObjs[0] optionInfoDict = getInfoDict(optionObj) - processBone( + materialOverrides = processBone( fModel, name, optionObj, @@ -2045,6 +2035,8 @@ def processBone( overrideNode.parent = transformNode transformNode.children.append(overrideNode) + return materialOverrides + def processSwitchBoneMatOverrides(materialOverrides, switchBone): for switchOption in switchBone.switch_options: @@ -2160,10 +2152,8 @@ def checkIfFirstNonASMNode(childNode): # they precede them def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parentNode, drawLayer): # Add node to its immediate parent - # print(str(type(parentNode.node)) + str(type(transformNode.node))) transformNode.skinned = True - # print("Skinned mesh exists.") # Get skinned node bone = armatureObj.data.bones[boneName] @@ -2183,7 +2173,7 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent acrossSwitchNode = False while highestChildNode.parent is not None and not ( highestChildNode.parent.node.hasDL or highestChildNode.parent.skinnedWithoutDL - ): # empty 0x13 command? + ): isFirstChild &= checkIfFirstNonASMNode(highestChildNode) hasNonDeform0x13Command |= isinstance(highestChildNode.parent.node, DisplayListWithOffsetNode) @@ -2193,12 +2183,9 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent highestChildCopyParent = TransformNode(copy.copy(highestChildNode.node)) highestChildCopyParent.children = [highestChildCopy] highestChildCopy.parent = highestChildCopyParent - # print(str(highestChildCopy.node) + " " + str(isFirstChild)) highestChildCopy = highestChildCopyParent - # isFirstChild &= checkIfFirstNonASMNode(highestChildNode) if highestChildNode.parent is None: raise PluginError('Issue with "' + boneName + '": Deform parent bone not found for skinning.') - # raise PluginError("There shouldn't be a skinned mesh section if there is no deform parent. This error may have ocurred if a switch option node is trying to skin to a parent but no deform parent exists.") # Otherwise, remove the transformNode from the parent and # duplicate the node heirarchy up to the last deform parent. @@ -2206,7 +2193,6 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent # then add the duplicated node hierarchy afterward. if highestChildNode != transformNode: if not isFirstChild: - # print("Hierarchy but not first child.") if hasNonDeform0x13Command: raise PluginError( "Error with " @@ -2232,7 +2218,7 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent ): precedingFunctionCmds.insert(0, copy.deepcopy(highestChildNode.parent.children[highestChildIndex - 1])) highestChildIndex -= 1 - # _____________ + # add skinned mesh node highestChildCopy.parent = highestChildNode.parent highestChildCopy.parent.children.append(skinnedTransformNode) @@ -2247,21 +2233,41 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent transformNode = transformNodeCopy else: - # print("Hierarchy with first child.") nodeIndex = highestChildNode.parent.children.index(highestChildNode) while nodeIndex > 0 and type(highestChildNode.parent.children[nodeIndex - 1].node) is FunctionNode: nodeIndex -= 1 highestChildNode.parent.children.insert(nodeIndex, skinnedTransformNode) skinnedTransformNode.parent = highestChildNode.parent else: - # print("Immediate child.") - nodeIndex = parentNode.children.index(transformNode) - parentNode.children.insert(nodeIndex, skinnedTransformNode) - skinnedTransformNode.parent = parentNode + # if the skinned mesh is the first child, you can append that DL to that parent + # instead of requiring a new node, because there will be nothing in between them + # to interrupt the skinning + add_skinned_mesh_to_parent_fMesh(skinnedTransformNode, highestChildNode.parent) return transformNode +def add_skinned_mesh_to_parent_fMesh(skinned_transform_node, parent_node): + parent_fMesh = parent_node.node.fMesh + skinned_fMesh = skinned_transform_node.node.fMesh + if not parent_fMesh: + parent_node.node.fMesh = skinned_transform_node.node.fMesh + else: + while SPEndDisplayList() in parent_fMesh.draw.commands: + parent_fMesh.draw.commands.remove(SPEndDisplayList()) + # remove repeat material calls + first_mat_call = 0 + first_mat_index = None + for index, command in enumerate(skinned_fMesh.draw.commands): + if isinstance(command, SPDisplayList) and command.displayList.tag == GfxListTag.Material: + first_mat_call = command + first_mat_index = index + break + if first_mat_call.displayList == parent_fMesh.currentFMaterial.material: + skinned_fMesh.draw.commands.pop(first_mat_index) + parent_fMesh.draw.commands.extend(skinned_fMesh.draw.commands) + + def getAncestorGroups(parentGroup, vertexGroup, armatureObj, obj): if parentGroup is None: return [] @@ -2480,13 +2486,6 @@ def saveModelGivenVertexGroup( ] ) - # Must be done after all geometry saved - for material, specificMat, overrideType in materialOverrides: - for drawLayer, fMesh in fMeshes.items(): - saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, drawLayer, convertTextureData) - for drawLayer, fMesh in fSkinnedMeshes.items(): - saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, drawLayer, convertTextureData) - return fMeshes, fSkinnedMeshes, usedDrawLayers