diff --git a/README.md b/README.md index b7919c7..6302e18 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ Plug these values into the SM64 Geolayout Exporter/Importer panels. ### Replacing Existing SM64 Geolayout Geometry SM64 geolayouts are often in strange rest poses, which makes it hard to modify their geometry. It often helps to import an animation belonging to that geolayout to see what the idle pose of a geolayout should be. Once you know, you can rotate the bones of the armature in pose mode to a usable position and then use the 'Apply as Rest Pose' operator under the SM64 Armature Tools header. Skin your new mesh to that armature, then rotate the bones back to the original position and use 'Apply as Rest Pose' again. You can now export the geolayout to SM64 and it will be able to use existing animations. +For example, for Mario you would rotate the four limb joints around the Y-axis 180 degrees, then just the arms 90/-90 degrees as such: + +![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_t_pose.gif) + +Then after applying the rest pose and skinning, you would apply those operations in reverse order then apply rest pose again. + ### Importing/Exporting SM64 Animations (Not Mario) - Download Quad64, open the desired level, and go to Misc -> Script Dumps. diff --git a/__init__.py b/__init__.py index 4bb3eb9..0965665 100644 --- a/__init__.py +++ b/__init__.py @@ -1119,7 +1119,8 @@ class SM64_ExportLevel(bpy.types.Operator): exportLevelC(obj, finalTransform, context.scene.f3d_type, context.scene.isHWv1, context.scene.levelName, bpy.path.abspath(context.scene.levelExportPath), - context.scene.levelSaveTextures, context.scene.levelWriteScript) + context.scene.levelSaveTextures, context.scene.levelWriteScript, + context.scene.levelExportRooms) self.report({'INFO'}, 'Success! Level at ' + \ context.scene.levelExportPath) @@ -1160,6 +1161,7 @@ class SM64_ExportLevelPanel(bpy.types.Panel): prop_split(col, context.scene, 'levelName', 'Name') col.prop(context.scene, 'levelSaveTextures') col.prop(context.scene, 'levelWriteScript') + col.prop(context.scene, 'levelExportRooms') extendedRAMLabel(col) #prop_split(col, context.scene, 'levelCamera', 'Camera') for i in range(panelSeparatorSize): @@ -1434,8 +1436,8 @@ class SM64_ExportCollision(bpy.types.Operator): if len(context.selected_objects) == 0: raise PluginError("Object not selected.") obj = context.active_object - if type(obj.data) is not bpy.types.Mesh: - raise PluginError("Mesh not selected.") + #if type(obj.data) is not bpy.types.Mesh: + # raise PluginError("Mesh not selected.") #T, R, S = obj.matrix_world.decompose() #objTransform = R.to_matrix().to_4x4() @ \ @@ -1457,7 +1459,7 @@ class SM64_ExportCollision(bpy.types.Operator): exportCollisionC(obj, finalTransform, bpy.path.abspath(context.scene.colExportPath), False, context.scene.colIncludeChildren, - obj.name, True) + obj.name, True, context.scene.colExportRooms) self.report({'INFO'}, 'Success! Collision at ' + \ context.scene.colExportPath) elif context.scene.colExportType == 'Insertable Binary': @@ -1546,6 +1548,7 @@ class SM64_ExportCollisionPanel(bpy.types.Panel): col.prop(context.scene, 'colIncludeChildren') if context.scene.colExportType == 'C': col.prop(context.scene, 'colExportPath') + col.prop(context.scene, 'colExportRooms') elif context.scene.colExportType == 'Insertable Binary': col.prop(context.scene, 'colInsertableBinaryPath') else: @@ -1853,6 +1856,8 @@ def register(): name = 'Include child objects', default = True) bpy.types.Scene.colInsertableBinaryPath = bpy.props.StringProperty( name = 'Filepath', subtype = 'FILE_PATH') + bpy.types.Scene.colExportRooms = bpy.props.BoolProperty( + name = 'Export Rooms', default = False) # Objects #bpy.types.Scene.levelCamera = bpy.props.PointerProperty(type = bpy.types.Camera) @@ -1863,6 +1868,8 @@ def register(): name = 'Save Textures As PNGs', default = True) bpy.types.Scene.levelWriteScript = bpy.props.BoolProperty( name = 'Write to script file', default = True) + bpy.types.Scene.levelExportRooms = bpy.props.BoolProperty( + name = 'Export Rooms', default = False) # ROM bpy.types.Scene.importRom = bpy.props.StringProperty( @@ -1994,6 +2001,7 @@ def unregister(): del bpy.types.Scene.levelSaveTextures del bpy.types.Scene.levelWriteScript #del bpy.types.Scene.levelCamera + del bpy.types.Scene.levelExportRooms # Collision @@ -2005,6 +2013,7 @@ def unregister(): del bpy.types.Scene.colStartAddr del bpy.types.Scene.colEndAddr del bpy.types.Scene.colInsertableBinaryPath + del bpy.types.Scene.colExportRooms # ROM del bpy.types.Scene.importRom diff --git a/fast64_internal/sm64_collision.py b/fast64_internal/sm64_collision.py index e347ac6..a50a5c8 100644 --- a/fast64_internal/sm64_collision.py +++ b/fast64_internal/sm64_collision.py @@ -27,9 +27,10 @@ class CollisionVertex: str(int(round(self.position[2]))) + '),\n' class CollisionTriangle: - def __init__(self, indices, specialParam): + def __init__(self, indices, specialParam, room): self.indices = indices self.specialParam = specialParam + self.room = room def to_binary(self): data = bytearray(0) @@ -104,6 +105,25 @@ class Collision: data += '\t' + waterBox.to_c() data += '\tCOL_END()\n' + '};\n' return data + + def rooms_name(self): + return self.name + '_rooms' + + def to_c_rooms(self): + data = 'const u8 ' + self.rooms_name() + '[] = {\n\t' + newlineCount = 0 + for collisionType, triangles, in self.triangles.items(): + for triangle in triangles: + data += str(triangle.room) + ', ' + newlineCount += 1 + if newlineCount >= 8: + newlineCount = 0 + data += '\n\t' + data += '\n};\n' + return data + + def to_c_rooms_def(self): + return 'extern const u8 ' + self.rooms_name() + '[];\n' def to_binary(self): colTypeDef = CollisionTypeDefinition() @@ -220,7 +240,7 @@ def exportCollisionBinary(obj, transformMatrix, romfile, startAddress, return start, end def exportCollisionC(obj, transformMatrix, dirPath, includeSpecials, - includeChildren, name, writeDefinitionsFile): + includeChildren, name, writeDefinitionsFile, writeRoomsFile): colDirPath = os.path.join(dirPath, toAlnum(name)) if not os.path.exists(colDirPath): @@ -235,11 +255,19 @@ def exportCollisionC(obj, transformMatrix, dirPath, includeSpecials, fileObj.close() cDefine = collision.to_c_def() + if writeRoomsFile: + cDefine += collision.to_c_rooms_def() + roomsPath = os.path.join(colDirPath, 'rooms.inc.c') + roomsFile = open(roomsPath, 'w') + roomsFile.write(collision.to_c_rooms()) + roomsFile.close() + if writeDefinitionsFile: - headerPath = os.path.join(dirPath, 'collision_declarations.h') + headerPath = os.path.join(colDirPath, 'collision_declarations.h') cDefFile = open(headerPath, 'w') cDefFile.write(cDefine) cDefFile.close() + return cDefine def exportCollisionInsertableBinary(obj, transformMatrix, filepath, @@ -268,12 +296,24 @@ def exportCollisionCommon(obj, transformMatrix, includeSpecials, includeChildren # dict of collisionType : faces collisionDict = {} - addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, areaIndex) - + #addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, areaIndex) + tempObj, allObjs = \ + duplicateHierarchy(obj, None, True, areaIndex) + try: + addCollisionTriangles(tempObj, collisionDict, includeChildren, transformMatrix, areaIndex) + cleanupDuplicatedObjects(allObjs) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + except Exception as e: + cleanupDuplicatedObjects(allObjs) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + raise Exception(str(e)) + collision = Collision(toAlnum(name + '_' + obj.name) + '_collision') for collisionType, faces in collisionDict.items(): collision.triangles[collisionType] = [] - for (faceVerts, specialParam) in faces: + for (faceVerts, specialParam, room) in faces: indices = [] for vert in faceVerts: roundedPosition = roundPosition(vert) @@ -283,8 +323,7 @@ def exportCollisionCommon(obj, transformMatrix, includeSpecials, includeChildren indices.append(len(collision.vertices) - 1) else: indices.append(index) - collision.triangles[collisionType].append(CollisionTriangle(indices, specialParam)) - + collision.triangles[collisionType].append(CollisionTriangle(indices, specialParam, room)) if includeSpecials: area = SM64_Area(areaIndex, '', '', '', None, None, [], obj.name) process_sm64_objects(obj, area, obj.matrix_world, transformMatrix, True) @@ -294,32 +333,26 @@ def exportCollisionCommon(obj, transformMatrix, includeSpecials, includeChildren return collision def addCollisionTriangles(obj, collisionDict, includeChildren, transformMatrix, areaIndex): - tempObj, meshList = combineObjects(obj, includeChildren, 'ignore_collision', areaIndex) - if tempObj is None: - return - try: - if len(tempObj.data.materials) == 0: + if isinstance(obj.data, bpy.types.Mesh) and not obj.ignore_collision: + if len(obj.data.materials) == 0: raise PluginError(obj.name + " must have a material associated with it.") - tempObj.data.calc_loop_triangles() - for face in tempObj.data.loop_triangles: - material = tempObj.data.materials[face.material_index] + obj.data.calc_loop_triangles() + for face in obj.data.loop_triangles: + material = obj.data.materials[face.material_index] colType = material.collision_type if material.collision_all_options\ else material.collision_type_simple specialParam = material.collision_param if colType in specialSurfaces else None if colType not in collisionDict: collisionDict[colType] = [] collisionDict[colType].append((( - transformMatrix @ tempObj.data.vertices[face.vertices[0]].co, - transformMatrix @ tempObj.data.vertices[face.vertices[1]].co, - transformMatrix @ tempObj.data.vertices[face.vertices[2]].co), specialParam)) - cleanupCombineObj(tempObj, meshList) - obj.select_set(True) - bpy.context.view_layer.objects.active = obj - except Exception as e: - cleanupCombineObj(tempObj, meshList) - obj.select_set(True) - bpy.context.view_layer.objects.active = obj - raise Exception(str(e)) + transformMatrix @ obj.data.vertices[face.vertices[0]].co, + transformMatrix @ obj.data.vertices[face.vertices[1]].co, + transformMatrix @ obj.data.vertices[face.vertices[2]].co), specialParam, obj.room_num)) + + if includeChildren: + for child in obj.children: + addCollisionTriangles(child, collisionDict, includeChildren, transformMatrix @ child.matrix_local, areaIndex) + def roundPosition(position): return (int(round(position[0])), @@ -367,6 +400,9 @@ def col_register(): name = 'SM64 Special', items = enumSpecialType, default = 'special_yellow_coin') + bpy.types.Object.room_num = bpy.props.IntProperty( + name = 'Room', default = 0, min = 0) + def col_unregister(): del bpy.types.Material.collision_type del bpy.types.Material.collision_type_simple @@ -377,6 +413,8 @@ def col_unregister(): del bpy.types.Object.sm64_water_box del bpy.types.Object.sm64_special_preset + del bpy.types.Object.room_num + for cls in reversed(col_classes): unregister_class(cls) diff --git a/fast64_internal/sm64_geolayout_bone.py b/fast64_internal/sm64_geolayout_bone.py index f2e43f0..0e3b6b6 100644 --- a/fast64_internal/sm64_geolayout_bone.py +++ b/fast64_internal/sm64_geolayout_bone.py @@ -176,6 +176,7 @@ class GeolayoutStaticPanel(bpy.types.Panel): prop_split(col, obj, 'culling_radius', 'Culling Radius') col.prop(obj, 'ignore_render') col.prop(obj, 'ignore_collision') + prop_split(col, obj, 'room_num', 'Room') class MaterialPointerProperty(bpy.types.PropertyGroup): material : bpy.props.PointerProperty(type = bpy.types.Material) diff --git a/fast64_internal/sm64_geolayout_writer.py b/fast64_internal/sm64_geolayout_writer.py index c81cf7f..d58461c 100644 --- a/fast64_internal/sm64_geolayout_writer.py +++ b/fast64_internal/sm64_geolayout_writer.py @@ -143,32 +143,9 @@ def convertObjectToGeolayout(obj, convertTransformMatrix, meshGeolayout = geolayoutGraph.startGeolayout # Duplicate objects to apply scale / modifiers / linked data - bpy.ops.object.select_all(action = 'DESELECT') - selectMeshChildrenOnly(obj, None, True, None if areaObj is None else areaObj.areaIndex) - obj.select_set(True) - bpy.context.view_layer.objects.active = obj - bpy.ops.object.duplicate() + tempObj, allObjs = \ + duplicateHierarchy(obj, 'ignore_render', True, None if areaObj is None else areaObj.areaIndex) try: - tempObj = bpy.context.view_layer.objects.active - allObjs = bpy.context.selected_objects - bpy.ops.object.make_single_user(obdata = True) - bpy.ops.object.transform_apply(location = False, - rotation = True, scale = True, properties = False) - for selectedObj in allObjs: - bpy.ops.object.select_all(action = 'DESELECT') - selectedObj.select_set(True) - for modifier in selectedObj.modifiers: - bpy.ops.object.modifier_apply(apply_as='DATA', - modifier=modifier.name) - for selectedObj in allObjs: - if selectedObj.ignore_render: - for child in selectedObj.children: - bpy.ops.object.select_all(action = 'DESELECT') - child.select_set(True) - bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') - selectedObj.parent.select_set(True) - bpy.ops.object.parent_set(keep_transform = True) - selectedObj.parent = None processMesh(fModel, tempObj, convertTransformMatrix, meshGeolayout.nodes[0], True, geolayoutGraph.startGeolayout, geolayoutGraph) diff --git a/fast64_internal/sm64_level_writer.py b/fast64_internal/sm64_level_writer.py index 557ca01..c9d34c1 100644 --- a/fast64_internal/sm64_level_writer.py +++ b/fast64_internal/sm64_level_writer.py @@ -12,7 +12,7 @@ import re import shutil def exportLevelC(obj, transformMatrix, f3dType, isHWv1, levelName, exportDir, - savePNG, writeScriptFile): + savePNG, writeScriptFile, exportRooms): levelDir = os.path.join(exportDir, levelName) if not os.path.exists(levelDir): @@ -65,10 +65,18 @@ def exportLevelC(obj, transformMatrix, f3dType, isHWv1, levelName, exportDir, levelDataString += '#include "levels/' + levelName + '/' + areaName + '/collision.inc.c"\n' headerString += collision.to_c_def() + # Write rooms + if exportRooms: + roomFile = open(os.path.join(areaDir, 'room.inc.c'), 'w') + roomFile.write(collision.to_c_rooms()) + roomFile.close() + levelDataString += '#include "levels/' + levelName + '/' + areaName + '/room.inc.c"\n' + headerString += collision.to_c_rooms_def() + # Get area area = exportAreaCommon(obj, child, transformMatrix, geolayoutGraph.startGeolayout, collision, levelName + '_' + areaName) - areaString += area.to_c_script() + areaString += area.to_c_script(exportRooms) # Write macros macroFile = open(os.path.join(areaDir, 'macro.inc.c'), 'w') @@ -126,7 +134,6 @@ def exportLevelC(obj, transformMatrix, f3dType, isHWv1, levelName, exportDir, '#include "levels/' + levelName + '/leveldata.inc.c"\n', False) writeIfNotFound(os.path.join(levelDir, 'header.h'), '#include "levels/' + levelName + '/header.inc.h"\n', True) - if savePNG: writeIfNotFound(os.path.join(levelDir, 'texture.inc.c'), diff --git a/fast64_internal/sm64_objects.py b/fast64_internal/sm64_objects.py index 70c8e95..226b178 100644 --- a/fast64_internal/sm64_objects.py +++ b/fast64_internal/sm64_objects.py @@ -140,7 +140,7 @@ class SM64_Area: def macros_name(self): return self.name + '_macro_objs' - def to_c_script(self): + def to_c_script(self, includeRooms): data = '' data += '\tAREA(' + str(self.index) + ', ' + self.geolayout.name + '),\n' for warpNode in self.warpNodes: @@ -148,6 +148,8 @@ class SM64_Area: for obj in self.objects: data += '\t\t' + obj.to_c() + ',\n' data += '\t\tTERRAIN(' + self.collision.name + '),\n' + if includeRooms: + data += '\t\tROOMS(' + self.collision.rooms_name() + '),\n' data += '\t\tMACRO_OBJECTS(' + self.macros_name() + '),\n' if self.music_seq is None: data += '\t\tSTOP_MUSIC(0),\n' diff --git a/fast64_internal/utility.py b/fast64_internal/utility.py index b74ac10..036c91c 100644 --- a/fast64_internal/utility.py +++ b/fast64_internal/utility.py @@ -49,6 +49,41 @@ def deleteIfFound(filePath, stringValue): fileData.write(stringData) fileData.close() +def duplicateHierarchy(obj, ignoreAttr, includeEmpties, areaIndex): + # Duplicate objects to apply scale / modifiers / linked data + bpy.ops.object.select_all(action = 'DESELECT') + selectMeshChildrenOnly(obj, None, includeEmpties, areaIndex) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.duplicate() + try: + tempObj = bpy.context.view_layer.objects.active + allObjs = bpy.context.selected_objects + bpy.ops.object.make_single_user(obdata = True) + bpy.ops.object.transform_apply(location = False, + rotation = True, scale = True, properties = False) + for selectedObj in allObjs: + bpy.ops.object.select_all(action = 'DESELECT') + selectedObj.select_set(True) + for modifier in selectedObj.modifiers: + bpy.ops.object.modifier_apply(apply_as='DATA', + modifier=modifier.name) + for selectedObj in allObjs: + if ignoreAttr is not None and getattr(selectedObj, ignoreAttr): + for child in selectedObj.children: + bpy.ops.object.select_all(action = 'DESELECT') + child.select_set(True) + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + selectedObj.parent.select_set(True) + bpy.ops.object.parent_set(keep_transform = True) + selectedObj.parent = None + return tempObj, allObjs + except Exception as e: + cleanupDuplicatedObjects(allObjs) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + raise Exception(str(e)) + def selectMeshChildrenOnly(obj, ignoreAttr, includeEmpties, areaIndex): checkArea = areaIndex is not None and obj.data is None if checkArea and obj.sm64_obj_type == 'Area Root' and obj.areaIndex != areaIndex: diff --git a/images/mario_t_pose.gif b/images/mario_t_pose.gif new file mode 100644 index 0000000..eed7c14 Binary files /dev/null and b/images/mario_t_pose.gif differ