Added room exporting support and updated README.

This commit is contained in:
kurethedead
2020-02-22 01:23:41 -08:00
parent 0d7c2321a0
commit bc3d37d913
9 changed files with 135 additions and 60 deletions
+6
View File
@@ -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.
+13 -4
View File
@@ -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
+65 -27
View File
@@ -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)
+1
View File
@@ -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)
+2 -25
View File
@@ -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)
+10 -3
View File
@@ -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'),
+3 -1
View File
@@ -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'
+35
View File
@@ -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:
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB