mirror of
https://github.com/ApfelTeeSaft/lightspeed64.git
synced 2026-08-26 19:33:24 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
|
||||
# Fast64
|
||||
|
||||
This requires Blender 2.81.
|
||||

|
||||
This is a Blender plugin that allows one to export display lists, geolayouts, and animations to Super Mario 64, either directly into a ROM or to C code. It supports custom color combiners / geometry modes, and different geolayout node types.
|
||||
|
||||
Make sure to save often, as this plugin is prone to crashing when undoing quickly.
|
||||
|
||||

|
||||

|
||||
### Features (import means import into Blender)
|
||||
- Geolayout import/export
|
||||
- Display List import/export
|
||||
- Animation import/export
|
||||
- Collision export
|
||||
- Skinned mesh support
|
||||
- Custom normals
|
||||
|
||||
### Restrictions
|
||||
- No YUV textures supported.
|
||||
- When importing display lists / geolayouts from SM64 to Blender, only the mesh is imported (no material)
|
||||
|
||||
### Credits
|
||||
Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing.
|
||||
|
||||
# Instructions
|
||||
### Installation
|
||||
Unzip and drag the entire folder into your blender addons folder (usually at C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons). Note that you must drag the ENTIRE folder, not just the 'fast64_internal' folder. Then in Blender, go to Edit -> Preferences -> Add-Ons and find/check the 'Fast64' plugin. If it does not show up, go to Edit -> Preferences -> Save&Load and make sure 'Auto Run Python Scripts' is enabled.
|
||||
|
||||
### Tool Locations
|
||||
The tools can be found in the properties sidebar under the 'Tools' tab (toggled by pressing N).
|
||||
The F3D material inspector can be found in the properties editor under the material tab.
|
||||
The geolayout inspector can be found in the properties editor under the bone tab in Pose Mode.
|
||||
The RDP default settings can be found in the properties editor under the world tab.
|
||||
|
||||
### ROM File Settings
|
||||
When importing from a ROM, the plugin will import from the ROM at filepath 'Import ROM', When exporting to a ROM, the plugin will make a copy of the file at 'Export ROM', modify it, and save the file to 'Output ROM'. The ROM must be expanded.
|
||||
|
||||
### Where Can I Export To?
|
||||
By default, when exporting to ROM, the plugin will copy the contents of bank 4 to the range (0x11A35B8 - 0x11FFF00). This will leave you with free space to export assets to range (0x11D8930 - 0x11FFF00).
|
||||
|
||||
### Vertex Colors
|
||||
To use vertex colors, select the "Vertex Colored Texture" preset and add two vertex color layers to your mesh named 'Col' and 'Alpha'. The alpha layer will use the greyscale value of the vertex color to determine alpha.
|
||||
|
||||
### F3D Materials
|
||||
Any exported meshes must have an F3D Material, which can be added by the 'Create F3D Material' button in the material inspector window.
|
||||
|
||||
### RDP State Optimization
|
||||
A material will not set every single geometry mode / other mode option. Instead, each property will be checked against the default settings located in the world tab in the properties sidebar. If the values are the same, then the code to change it is not added.
|
||||
|
||||
### Geolayouts
|
||||
There are many different geolayout node types. In Blender, each node type is represented by a bone group. Imported geolayouts will already have these bone groups, but if you want to add them to your own armature use the 'Add Bone Groups' operator in the SM64 Armature Tools header. All regular bones (aka 0x13 commands) and 0x15 command bones are placed on armature layer 0. All other geolayout commands are placed on layer 1. Metarig generated bones are placed on layers 3 and 4 (see 'Animating Existing Geolayouts'). If you change a bone's geolayout command type and it dissapears, it might have been moved to a different layer. Make sure to go to the armature properties editor and set both layers 0 and 1 to be visible. Geolayout command types can be editted in the properties editor under the bone tab in pose mode.
|
||||
|
||||
### Exporting Geolayouts and Skinned Meshes
|
||||
The N64 supports binary skinning, meaning each vertex will be influenced by one bone only. When skinning an exported geolayout, do NOT use automatic skinning, as this results in a smooth weight falloff. Instead, when weight painting set the weight to either 1 or 0, and set the brush Falloff to square. Skinning can only occur between an immediate parent and a child deform bone, not between siblings / across ancestors.
|
||||
|
||||
### 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.
|
||||
|
||||
### Importing An Animation To Blender
|
||||
To import an animation to blender, select an armature for the animation to be exported to, and press 'Import animation'. Note that the armature's root 0x13 (i.e. regular) bone must be named 'root'. Animations can be found by looking at behaviour scripts where 27 and 28 commands are.
|
||||
|
||||
### Exporting Mario Animations
|
||||
Mario animations use a DMA table, which contains 8 byte entries of (offset from table start, animation size). Documentation about this table is here:
|
||||
https://dudaw.webs.com/sm64docs/sm64_marios_animation_table.txt
|
||||
Basically, Mario's DMA table starts at 0x4EC000. There is an 8 byte header, and then the animation entries afterward. Thus the 'climb up ledge' DMA entry is at 0x4EC008. The first 8 bytes at that address indicate the offset from 0x4EC000 at which the actual animation exists. Using this table you can find animations you want to overwrite.
|
||||
|
||||
### Animating Existing Geolayouts
|
||||
Often times it is hard to rig an existing SM64 geolayout, as there are many intermediate non-deform bones and bones don't point to their children. To make this easier you can use the 'Create Animatable Metarig' operator in the SM64 Armature Tools header. This will generate a metarig which can be used with IK. The metarig bones will be placed on armature layers 3 and 4.
|
||||
|
||||
### Exporting Geolayouts to C
|
||||
When you export in C, there will be 3 paths. Geo Filepath is for geolayout data. DL Filepath is for f3d data. Definitions Filepath is for extern definitions of objects.
|
||||
To replace a model in decomp, replace its geo.inc.c and model.inc.c contents with the geolayout file and the dl file respectively. Use the contents of the define file to replace existing defines in one of the group header files (ex. mario is in group0.h). Make sure that the name of your geolayout is the same the name of the geolayout you're replacing.
|
||||
|
||||
### Switch Statements
|
||||
To add a switch mesh node, duplicate your switch bone and move it off to the side. Set the bone geolayout command to be Switch Option. Any nodes in this switch option must be children of this bone. Add any other bones to this bone. Add your switch geometry into your existing mesh in the correct position and skin it.
|
||||
|
||||
### Common Issues
|
||||
Inivisible Mesh : Bone is not set to deform, or geometry is skinned to more than one bone.
|
||||
Game crashes: Invalid function address for switch/function/held object bones.
|
||||
+1899
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
from .f3d_parser import *
|
||||
from .sm64_geolayout_writer import *
|
||||
from .sm64_geolayout_parser import *
|
||||
from .sm64_level_parser import *
|
||||
from .sm64_constants import *
|
||||
from .sm64_rom_tweaks import *
|
||||
from .sm64_anim import *
|
||||
from .f3d_material import *
|
||||
from .sm64_geolayout_bone import *
|
||||
from .sm64_collision import *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,287 @@
|
||||
|
||||
combiner_enums = {
|
||||
'Case A' : (
|
||||
('COMBINED', 'Combined Color', 'Combined Color'),
|
||||
('TEXEL0', 'Texture 0', 'Texture 0'),
|
||||
('TEXEL1', 'Texture 1', 'Texture 1'),
|
||||
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
|
||||
('SHADE', 'Shade Color', 'Shade Color'),
|
||||
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
|
||||
('1', '1', '1'),
|
||||
('NOISE', 'Noise', 'Noise'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
|
||||
'Case B' : (
|
||||
('COMBINED', 'Combined Color', 'Combined Color'),
|
||||
('TEXEL0', 'Texture 0', 'Texture 0'),
|
||||
('TEXEL1', 'Texture 1', 'Texture 1'),
|
||||
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
|
||||
('SHADE', 'Shade Color', 'Shade Color'),
|
||||
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
|
||||
('CENTER', 'Chroma Key Center', 'Chroma Key Center'),
|
||||
('K4', 'YUV Convert K4', 'YUV Convert K4'),
|
||||
('0', '0', '0')
|
||||
),
|
||||
|
||||
'Case C' : (
|
||||
('COMBINED' , 'Combined Color', 'Combined Color'),
|
||||
('TEXEL0' , 'Texture 0', 'Texture 0'),
|
||||
('TEXEL1' , 'Texture 1', 'Texture 1'),
|
||||
('PRIMITIVE' , 'Primitive Color', 'Primitive Color'),
|
||||
('SHADE' , 'Shade Color', 'Shade Color'),
|
||||
('ENVIRONMENT' , 'Environment Color', 'Environment Color'),
|
||||
('SCALE' , 'Chroma Key Scale', 'Chroma Key Scale'),
|
||||
('COMBINED_ALPHA' , 'Combined Color Alpha', 'Combined Color Alpha'),
|
||||
('TEXEL0_ALPHA' , 'Texture 0 Alpha', 'Texture 0 Alpha'),
|
||||
('TEXEL1_ALPHA' , 'Texture 1 Alpha', 'Texture 1 Alpha'),
|
||||
('PRIMITIVE_ALPHA' , 'Primitive Color Alpha', 'Primitive Color Alpha'),
|
||||
('SHADE_ALPHA' , 'Shade Color Alpha', 'Shade Color Alpha'),
|
||||
('ENV_ALPHA' , 'Environment Color Alpha', 'Environment Color Alpha'),
|
||||
('LOD_FRACTION' , 'LOD Fraction', 'LOD Fraction'),
|
||||
('PRIM_LOD_FRAC' , 'Primitive LOD Fraction', 'Primitive LOD Fraction'),
|
||||
('K5' , 'YUV Convert K5', 'YUV Convert K5'),
|
||||
('0' , '0', '0'),
|
||||
),
|
||||
|
||||
'Case D' : (
|
||||
('COMBINED', 'Combined Color', 'Combined Color'),
|
||||
('TEXEL0', 'Texture 0', 'Texture 0'),
|
||||
('TEXEL1', 'Texture 1', 'Texture 1'),
|
||||
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
|
||||
('SHADE', 'Shade Color', 'Shade Color'),
|
||||
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
|
||||
('1', '1', '1'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
|
||||
'Case A Alpha' : (
|
||||
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
|
||||
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
|
||||
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
|
||||
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
|
||||
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
|
||||
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
|
||||
('1', '1', '1'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
|
||||
'Case B Alpha' : (
|
||||
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
|
||||
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
|
||||
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
|
||||
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
|
||||
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
|
||||
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
|
||||
('1', '1', '1'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
|
||||
'Case C Alpha' : (
|
||||
('LOD_FRACTION', 'LOD Fraction', 'LOD Fraction'),
|
||||
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
|
||||
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
|
||||
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
|
||||
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
|
||||
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
|
||||
('PRIM_LOD_FRAC', 'Primitive LOD Fraction', 'Primitive LOD Fraction'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
|
||||
'Case D Alpha' : (
|
||||
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
|
||||
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
|
||||
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
|
||||
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
|
||||
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
|
||||
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
|
||||
('1', '1', '1'),
|
||||
('0', '0', '0'),
|
||||
),
|
||||
}
|
||||
|
||||
caseTemplateDict = {
|
||||
'Case A' : 'Fast3D_A',
|
||||
'Case B' : 'Fast3D_B',
|
||||
'Case C' : 'Fast3D_C',
|
||||
'Case D' : 'Fast3D_D',
|
||||
'Case A Alpha' : 'Fast3D_A_alpha',
|
||||
'Case B Alpha' : 'Fast3D_B_alpha',
|
||||
'Case C Alpha' : 'Fast3D_C_alpha',
|
||||
'Case D Alpha' : 'Fast3D_D_alpha',
|
||||
}
|
||||
|
||||
# hardware v2
|
||||
enumAlphaDither = [
|
||||
('G_AD_PATTERN', 'Pattern', 'Pattern'),
|
||||
('G_AD_NOTPATTERN', 'NOT Pattern', 'NOT Pattern'),
|
||||
('G_AD_NOISE', 'Noise', 'Noise'),
|
||||
('G_AD_DISABLE', 'Disable', 'Disable'),
|
||||
]
|
||||
|
||||
# hardware v2
|
||||
enumRGBDither = [
|
||||
('G_CD_MAGICSQ', 'Magic Square', 'Magic Square'),
|
||||
('G_CD_BAYER', 'Bayer', 'Bayer'),
|
||||
('G_CD_NOISE', 'Noise', 'Noise'),
|
||||
('G_CD_DISABLE', 'Disable', 'Disable'),
|
||||
('G_CD_ENABLE', 'Enable', 'Enable')
|
||||
]
|
||||
|
||||
enumCombKey = [
|
||||
('G_CK_NONE', 'None', 'None'),
|
||||
('G_CK_KEY', 'Key', 'Key'),
|
||||
]
|
||||
|
||||
enumTextConv = [
|
||||
('G_TC_CONV', 'Convert', 'Convert'),
|
||||
('G_TC_FILTCONV', 'Filter And Convert', 'Filter And Convert'),
|
||||
('G_TC_FILT', 'Filter', 'Filter')
|
||||
]
|
||||
|
||||
enumTextFilt = [
|
||||
('G_TF_POINT', 'Point', 'Point'),
|
||||
('G_TF_AVERAGE', 'Average', 'Average'),
|
||||
('G_TF_BILERP', 'Bilinear', 'Bilinear'),
|
||||
]
|
||||
|
||||
enumTextLUT = [
|
||||
('G_TT_NONE', 'None', 'None'),
|
||||
('G_TT_RGBA16', 'RGBA16', 'RGBA16'),
|
||||
('G_TT_IA16', 'IA16', 'IA16'),
|
||||
]
|
||||
|
||||
enumTextLOD = [
|
||||
('G_TL_TILE', 'Tile', 'Tile'),
|
||||
('G_TL_LOD', 'LOD', 'LOD'),
|
||||
]
|
||||
|
||||
enumTextDetail = [
|
||||
('G_TD_CLAMP', 'Clamp', 'Clamp'),
|
||||
('G_TD_SHARPEN', 'Sharpen', 'Sharpen'),
|
||||
('G_TD_DETAIL', 'Detail', 'Detail'),
|
||||
]
|
||||
|
||||
enumTextPersp = [
|
||||
('G_TP_NONE', 'None', 'None'),
|
||||
('G_TP_PERSP', 'Perspective', 'Perspective'),
|
||||
]
|
||||
|
||||
enumCycleType = [
|
||||
('G_CYC_1CYCLE', '1 Cycle', '1 Cycle'),
|
||||
('G_CYC_2CYCLE', '2 Cycle', '2 Cycle'),
|
||||
('G_CYC_COPY', 'Copy', 'Copy'),
|
||||
('G_CYC_FILL', 'Fill', 'Fill'),
|
||||
]
|
||||
|
||||
enumColorDither = [
|
||||
('G_CD_DISABLE', 'Disable', 'Disable'),
|
||||
('G_CD_ENABLE', 'Enable', 'Enable')
|
||||
]
|
||||
|
||||
enumPipelineMode = [
|
||||
('G_PM_1PRIMITIVE', '1 Primitive', '1 Primitive'),
|
||||
('G_PM_NPRIMITIVE', 'N Primitive', 'N Primitive')
|
||||
]
|
||||
|
||||
enumAlphaCompare = [
|
||||
('G_AC_NONE', 'None', 'None'),
|
||||
('G_AC_THRESHOLD', 'Threshold', 'Threshold'),
|
||||
('G_AC_DITHER', 'Dither', 'Dither'),
|
||||
]
|
||||
|
||||
enumDepthSource = [
|
||||
('G_ZS_PIXEL', 'Pixel', 'Pixel'),
|
||||
('G_ZS_PRIM', 'Primitive', 'Primitive'),
|
||||
]
|
||||
|
||||
enumCoverage = [
|
||||
('CVG_DST_CLAMP', 'Clamp', 'Clamp'),
|
||||
('CVG_DST_WRAP', 'Wrap', 'Wrap'),
|
||||
('CVG_DST_FULL', 'Full', 'Full'),
|
||||
('CVG_DST_SAVE', 'Save', 'Save')
|
||||
]
|
||||
|
||||
enumZMode = [
|
||||
('ZMODE_OPA', 'Opaque', 'Opaque'),
|
||||
('ZMODE_INTER', 'Interpenetrating', 'Interpenetrating'),
|
||||
('ZMODE_XLU', 'Transparent (XLU)', 'Transparent (XLU)'),
|
||||
('ZMODE_DEC', 'Decal', 'Decal')
|
||||
]
|
||||
|
||||
enumBlendColor = [
|
||||
('G_BL_CLR_IN', 'Input', 'Input'),
|
||||
('G_BL_CLR_MEM', 'Memory', 'Memory'),
|
||||
('G_BL_CLR_BL', 'Blend', 'Blend'),
|
||||
('G_BL_CLR_FOG', 'Fog', 'Fog')
|
||||
]
|
||||
|
||||
enumBlendAlpha = [
|
||||
('G_BL_A_IN', 'Input', 'Input'),
|
||||
('G_BL_A_FOG', 'Fog', 'Fog'),
|
||||
('G_BL_A_SHADE', 'Shade', 'Shade'),
|
||||
('G_BL_0', '0', '0')
|
||||
]
|
||||
|
||||
enumBlendMix = [
|
||||
('G_BL_1MA', '1 - Source Alpha', '1 - Source Alpha'),
|
||||
('G_BL_A_MEM', 'Memory Alpha', 'Memory Alpha'),
|
||||
('G_BL_1', '1', '1'),
|
||||
('G_BL_0', '0', '0')
|
||||
]
|
||||
|
||||
enumTexFormat = [
|
||||
('I4','Intensity 4-bit', 'Intensity 4-bit'),
|
||||
('I8','Intensity 8-bit', 'Intensity 8-bit'),
|
||||
('IA4','Intensity Alpha 4-bit', 'Intensity Alpha 4-bit'),
|
||||
('IA8','Intensity Alpha 8-bit', 'Intensity Alpha 8-bit'),
|
||||
('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'),
|
||||
('CI4','Color Index 4-bit', 'Color Index 4-bit'),
|
||||
('CI8','Color Index 8-bit', 'Color Index 8-bit'),
|
||||
('RGBA16','RGBA 16-bit', 'RGBA 16-bit'),
|
||||
('RGBA32','RGBA 32-bit', 'RGBA 32-bit'),
|
||||
#('YUV16','YUV 16-bit', 'YUV 16-bit'),
|
||||
]
|
||||
|
||||
enumCIFormat = [
|
||||
('RGBA16','RGBA 16-bit', 'RGBA 16-bit'),
|
||||
('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'),
|
||||
]
|
||||
|
||||
enumTexUV = [
|
||||
('TEXEL0', 'Texture 0', 'Texture 0'),
|
||||
('TEXEL1', 'Texture 1', 'Texture 1'),
|
||||
]
|
||||
|
||||
texBitSize = {
|
||||
'I4' : 4,
|
||||
'IA4' : 4,
|
||||
'CI4' : 4,
|
||||
'I8' : 8,
|
||||
'IA8' : 8,
|
||||
'CI8' : 8,
|
||||
'RGBA16' : 16,
|
||||
'IA16' : 16,
|
||||
'YUV16' : 16,
|
||||
'RGBA32' : 32,
|
||||
}
|
||||
|
||||
# 512 words * 64 bits / n bitSize
|
||||
maxTexelCount = {
|
||||
32 : 1024,
|
||||
16 : 2048,
|
||||
8 : 4096,
|
||||
4 : 8192,
|
||||
}
|
||||
|
||||
enumF3D = [
|
||||
#('F3D_GBI', 'Fast3D', "Fast3D"),
|
||||
#('F3DEX_GBI', 'F3DEX', "F3DEX"),
|
||||
#('F3DEX_GBI_2', 'F3DEX2', "F3DEX2"),
|
||||
('F3D', 'F3D', 'F3D'),
|
||||
('F3DEX/LX', 'F3DEX/LX', 'F3DEX/LX'),
|
||||
('F3DLX.Rej', 'F3DLX.Rej', 'F3DLX.Rej'),
|
||||
('F3DLP.Rej', 'F3DLP.Rej', 'F3DLP.Rej'),
|
||||
('F3DEX2/LX2', 'F3DEX2/LX2', 'F3DEX2/LX2'),
|
||||
('F3DEX2.Rej/LX2.Rej', 'F3DEX2.Rej/LX2.Rej', 'F3DEX2.Rej/LX2.Rej'),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,333 @@
|
||||
import bmesh
|
||||
import bpy
|
||||
import mathutils
|
||||
import pprint
|
||||
from .f3d_gbi import *
|
||||
from .utility import *
|
||||
from .sm64_constants import *
|
||||
|
||||
def getAxisVector(enumValue):
|
||||
sign = -1 if enumValue[0] == '-' else 1
|
||||
axis = enumValue[0] if sign == 1 else enumValue[1]
|
||||
return (
|
||||
sign if axis == 'X' else 0,
|
||||
sign if axis == 'Y' else 0,
|
||||
sign if axis == 'Z' else 0
|
||||
)
|
||||
|
||||
def getExportRotation(forwardAxisEnum, convertTransformMatrix):
|
||||
if 'Z' in forwardAxisEnum:
|
||||
print("Z axis reserved for verticals.")
|
||||
return None
|
||||
elif forwardAxisEnum == 'X':
|
||||
rightAxisEnum = '-Y'
|
||||
elif forwardAxisEnum == '-Y':
|
||||
rightAxisEnum = '-X'
|
||||
elif forwardAxisEnum == '-X':
|
||||
rightAxisEnum = 'Y'
|
||||
else:
|
||||
rightAxisEnum = 'X'
|
||||
|
||||
forwardAxis = getAxisVector(forwardAxisEnum)
|
||||
rightAxis = getAxisVector(rightAxisEnum)
|
||||
|
||||
upAxis = (0, 0, 1)
|
||||
|
||||
# Z assumed to be up
|
||||
columns = [rightAxis, forwardAxis, upAxis]
|
||||
localToBlenderRotation = mathutils.Matrix([
|
||||
[col[0] for col in columns],
|
||||
[col[1] for col in columns],
|
||||
[col[2] for col in columns]
|
||||
]).to_quaternion()
|
||||
|
||||
return convertTransformMatrix.to_quaternion() @ localToBlenderRotation
|
||||
|
||||
def F3DtoBlenderObject(romfile, startAddress, scene,
|
||||
newname, transformMatrix,
|
||||
segmentData, shadeSmooth):
|
||||
|
||||
mesh = bpy.data.meshes.new(newname + '-mesh')
|
||||
obj = bpy.data.objects.new(newname, mesh)
|
||||
scene.collection.objects.link(obj)
|
||||
createBlankMaterial(obj)
|
||||
|
||||
bMesh = bmesh.new()
|
||||
bMesh.from_mesh(mesh)
|
||||
|
||||
parseF3D(romfile, startAddress, scene, bMesh, obj, \
|
||||
transformMatrix, newname, segmentData, \
|
||||
[None] * 16 * 16)
|
||||
|
||||
#bmesh.ops.rotate(bMesh, cent = [0,0,0],
|
||||
# matrix = blenderToSM64Rotation,
|
||||
# verts = bMesh.verts)
|
||||
bMesh.to_mesh(mesh)
|
||||
bMesh.free()
|
||||
mesh.update()
|
||||
|
||||
if shadeSmooth:
|
||||
bpy.ops.object.select_all(action = 'DESELECT')
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.shade_smooth()
|
||||
|
||||
return obj
|
||||
|
||||
def getOrMakeVertexGroup(obj, groupName):
|
||||
for group in obj.vertex_groups:
|
||||
if group.name == groupName:
|
||||
return group
|
||||
return obj.vertex_groups.new(name = groupName)
|
||||
|
||||
def cmdToPositiveInt(cmd):
|
||||
return cmd if cmd >= 0 else 256 + cmd
|
||||
|
||||
def parseF3D(romfile, startAddress, scene,
|
||||
bMesh, obj, transformMatrix, groupName, segmentData, vertexBuffer):
|
||||
f3d = F3D('F3D', False)
|
||||
currentAddress = startAddress
|
||||
romfile.seek(currentAddress)
|
||||
command = romfile.read(8)
|
||||
|
||||
faceSeq = bMesh.faces
|
||||
vertSeq = bMesh.verts
|
||||
uv_layer = bMesh.loops.layers.uv.verify()
|
||||
deform_layer = bMesh.verts.layers.deform.verify()
|
||||
vertexGroup = getOrMakeVertexGroup(obj, groupName)
|
||||
groupIndex = vertexGroup.index
|
||||
|
||||
textureSize = [32, 32]
|
||||
|
||||
currentTextureAddr = -1
|
||||
jumps = [startAddress]
|
||||
|
||||
# Used for remove_double op at end
|
||||
vertList = []
|
||||
|
||||
while len(jumps) > 0:
|
||||
# FD, FC, B7 (tex, shader, geomode)
|
||||
#print(format(command[0], '#04x') + ' at ' + hex(currentAddress))
|
||||
if command[0] == cmdToPositiveInt(f3d.G_TRI1):
|
||||
try:
|
||||
newVerts = interpretDrawTriangle(command, vertexBuffer,
|
||||
faceSeq, vertSeq, uv_layer, deform_layer, groupIndex)
|
||||
vertList.extend(newVerts)
|
||||
except TypeError:
|
||||
print("Ignoring triangle from unloaded vertices.")
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_VTX):
|
||||
interpretLoadVertices(romfile, vertexBuffer, transformMatrix,
|
||||
command, segmentData)
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_SETTILESIZE):
|
||||
textureSize = interpretSetTileSize(
|
||||
int.from_bytes(command[4:8], 'big'))
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_DL):
|
||||
if command[1] == 0:
|
||||
jumps.append(currentAddress)
|
||||
currentAddress = decodeSegmentedAddr(command[4:8],
|
||||
segmentData = segmentData)
|
||||
romfile.seek(currentAddress)
|
||||
command = romfile.read(8)
|
||||
continue
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_ENDDL):
|
||||
currentAddress = jumps.pop()
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_SETGEOMETRYMODE):
|
||||
pass
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_SETCOMBINE):
|
||||
pass
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_SETTIMG):
|
||||
currentTextureAddr =\
|
||||
interpretSetTImage(command, segmentData)
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_LOADBLOCK):
|
||||
# for now only 16bit RGBA is supported.
|
||||
interpretLoadBlock(command, romfile, currentTextureAddr, textureSize,
|
||||
'RGBA', 16)
|
||||
|
||||
elif command[0] == cmdToPositiveInt(f3d.G_SETTILE):
|
||||
interpretSetTile(int.from_bytes(command[4:8], 'big'), None)
|
||||
|
||||
else:
|
||||
pass
|
||||
#print(format(command[0], '#04x') + ' at ' + hex(currentAddress))
|
||||
|
||||
currentAddress += 8
|
||||
romfile.seek(currentAddress)
|
||||
command = romfile.read(8)
|
||||
|
||||
bmesh.ops.remove_doubles(bMesh, verts = vertList, dist = 0.0001)
|
||||
return vertexBuffer
|
||||
|
||||
def getPosition(vertexBuffer, index):
|
||||
xStart = index * 16 + 0
|
||||
yStart = index * 16 + 2
|
||||
zStart = index * 16 + 4
|
||||
|
||||
xBytes = vertexBuffer[xStart : xStart + 2]
|
||||
yBytes = vertexBuffer[yStart : yStart + 2]
|
||||
zBytes = vertexBuffer[zStart : zStart + 2]
|
||||
|
||||
x = int.from_bytes(xBytes, 'big', signed=True) * sm64ToBlenderScale
|
||||
y = int.from_bytes(yBytes, 'big', signed=True) * sm64ToBlenderScale
|
||||
z = int.from_bytes(zBytes, 'big', signed=True) * sm64ToBlenderScale
|
||||
|
||||
return (x, y, z)
|
||||
|
||||
def getNormalorColor(vertexBuffer, index, isNormal = True):
|
||||
xByte = bytes([vertexBuffer[index * 16 + 12]])
|
||||
yByte = bytes([vertexBuffer[index * 16 + 13]])
|
||||
zByte = bytes([vertexBuffer[index * 16 + 14]])
|
||||
wByte = bytes([vertexBuffer[index * 16 + 15]])
|
||||
|
||||
if isNormal:
|
||||
x = int.from_bytes(xByte, 'big', signed=True)
|
||||
y = int.from_bytes(yByte, 'big', signed=True)
|
||||
z = int.from_bytes(zByte, 'big', signed=True)
|
||||
return (x,y,z)
|
||||
|
||||
else: # vertex color
|
||||
r = int.from_bytes(xByte, 'big') / 255
|
||||
g = int.from_bytes(yByte, 'big') / 255
|
||||
b = int.from_bytes(zByte, 'big') / 255
|
||||
a = int.from_bytes(wByte, 'big') / 255
|
||||
return (r,g,b,a)
|
||||
|
||||
def getUV(vertexBuffer, index, textureDimensions = [32,32]):
|
||||
uStart = index * 16 + 8
|
||||
vStart = index * 16 + 10
|
||||
|
||||
uBytes = vertexBuffer[uStart : uStart + 2]
|
||||
vBytes = vertexBuffer[vStart : vStart + 2]
|
||||
|
||||
u = int.from_bytes(uBytes, 'big', signed = True) / 32
|
||||
v = int.from_bytes(vBytes, 'big', signed = True) / 32
|
||||
|
||||
# We don't know texture size, so assume 32x32.
|
||||
u /= textureDimensions[0]
|
||||
v /= textureDimensions[1]
|
||||
v = 1 - v
|
||||
|
||||
return (u,v)
|
||||
|
||||
def interpretSetTile(data, texture):
|
||||
clampMirrorFlags = bitMask(data, 18, 2)
|
||||
|
||||
def interpretSetTileSize(data):
|
||||
hVal = bitMask(data, 0, 12)
|
||||
wVal = bitMask(data, 12, 12)
|
||||
|
||||
height = hVal >> 2 + 1
|
||||
width = wVal >> 2 + 1
|
||||
|
||||
return (width, height)
|
||||
|
||||
def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command,
|
||||
segmentData = None):
|
||||
command = int.from_bytes(command, 'big', signed=True)
|
||||
|
||||
numVerts = bitMask(command, 52, 4) + 1
|
||||
startIndex = bitMask(command, 48, 4)
|
||||
dataLength = bitMask(command, 32, 16)
|
||||
segmentedAddr = bitMask(command, 0, 32)
|
||||
|
||||
dataStartAddr = decodeSegmentedAddr(segmentedAddr.to_bytes(4, 'big'),
|
||||
segmentData = segmentData)
|
||||
|
||||
romfile.seek(dataStartAddr)
|
||||
data = romfile.read(dataLength)
|
||||
|
||||
for i in range(numVerts):
|
||||
vert = mathutils.Vector(readVectorFromShorts(data, i * 16))
|
||||
vert = transformMatrix @ vert
|
||||
transformedVert = bytearray(6)
|
||||
writeVectorToShorts(transformedVert, 0, vert)
|
||||
|
||||
start = (startIndex + i) * 16
|
||||
vertexBuffer[start: start + 6] = transformedVert
|
||||
vertexBuffer[start + 6: start + 16] = data[i * 16 + 6: i * 16 + 16]
|
||||
|
||||
|
||||
# Note the divided by 0x0A, which is due to the way BF command stores indices.
|
||||
# Without this the triangles are drawn incorrectly.
|
||||
def interpretDrawTriangle(command, vertexBuffer,
|
||||
faceSeq, vertSeq, uv_layer, deform_layer, groupIndex):
|
||||
|
||||
verts = [None, None, None]
|
||||
|
||||
index0 = int(command[5] / 0x0A)
|
||||
index1 = int(command[6] / 0x0A)
|
||||
index2 = int(command[7] / 0x0A)
|
||||
|
||||
vert0 = mathutils.Vector(getPosition(vertexBuffer, index0))
|
||||
vert1 = mathutils.Vector(getPosition(vertexBuffer, index1))
|
||||
vert2 = mathutils.Vector(getPosition(vertexBuffer, index2))
|
||||
|
||||
verts[0] = vertSeq.new(vert0)
|
||||
verts[1] = vertSeq.new(vert1)
|
||||
verts[2] = vertSeq.new(vert2)
|
||||
|
||||
tri = faceSeq.new(verts)
|
||||
|
||||
# Assign vertex group
|
||||
for vert in tri.verts:
|
||||
vert[deform_layer][groupIndex] = 1
|
||||
|
||||
loopIndex = 0
|
||||
for loop in tri.loops:
|
||||
loop[uv_layer].uv = mathutils.Vector(
|
||||
getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A)))
|
||||
loopIndex += 1
|
||||
|
||||
return verts
|
||||
|
||||
def interpretSetTImage(command, levelData):
|
||||
segmentedAddr = command[4:8]
|
||||
return decodeSegmentedAddr(segmentedAddr, levelData)
|
||||
|
||||
def interpretLoadBlock(command, romfile, textureStart, textureSize, colorFormat, colorDepth):
|
||||
numTexels = ((int.from_bytes(command[6:8], 'big')) >> 12) + 1
|
||||
|
||||
# This is currently broken.
|
||||
#createNewTextureMaterial(romfile, textureStart, textureSize, numTexels, colorFormat, colorDepth, obj)
|
||||
|
||||
def printvbuf(vertexBuffer):
|
||||
for i in range(0, int(len(vertexBuffer) / 16)):
|
||||
print(getPosition(vertexBuffer, i))
|
||||
print(getNormalorColor(vertexBuffer, i))
|
||||
print(getUV(vertexBuffer, i))
|
||||
|
||||
|
||||
def createBlankMaterial(obj):
|
||||
newMat = bpy.data.materials.new('sm64_material')
|
||||
obj.data.materials.append(newMat)
|
||||
|
||||
def createNewTextureMaterial(romfile, textureStart, textureSize, texelCount, colorFormat, colorDepth, obj):
|
||||
newMat = bpy.data.materials.new('sm64_material')
|
||||
newTex = bpy.data.textures.new('sm64_texture', 'IMAGE')
|
||||
newImg = bpy.data.images.new('sm64_image', *textureSize, True, True)
|
||||
|
||||
newTex.image = newImg
|
||||
newSlot = newMat.texture_slots.add()
|
||||
newSlot.texture = newTex
|
||||
|
||||
obj.data.materials.append(newMat)
|
||||
|
||||
romfile.seek(textureStart)
|
||||
texelSize = int(colorDepth / 8)
|
||||
dataLength = texelCount * texelSize
|
||||
textureData = romfile.read(dataLength)
|
||||
|
||||
if colorDepth != 16:
|
||||
print("Warning: Only 16bit RGBA supported, input was " + \
|
||||
str(colorDepth) + 'bit ' + colorFormat)
|
||||
else:
|
||||
print(str(texelSize) + " " + str(colorDepth))
|
||||
for n in range(0, dataLength, texelSize):
|
||||
oldPixel = textureData[n : n + texelSize]
|
||||
newImg.pixels[n : n+4] = read16bitRGBA(
|
||||
int.from_bytes(oldPixel, 'big'))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,501 @@
|
||||
import bpy
|
||||
import mathutils
|
||||
import math
|
||||
import re
|
||||
from .utility import *
|
||||
from .sm64_constants import *
|
||||
from math import pi
|
||||
|
||||
sm64_anim_types = {'ROTATE', 'TRANSLATE'}
|
||||
|
||||
class SM64_Animation:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.header = None
|
||||
self.indices = SM64_ShortArray(name + '_indices', False)
|
||||
self.values = SM64_ShortArray(name + '_values', False)
|
||||
|
||||
def to_binary(self, segmentData, isDMA, startAddress):
|
||||
return self.header.to_binary(segmentData, isDMA, startAddress) + \
|
||||
self.indices.to_binary() + \
|
||||
self.values.to_binary()
|
||||
|
||||
def to_c(self):
|
||||
return self.values.to_c() + '\n' +\
|
||||
self.indices.to_c() + '\n' +\
|
||||
self.header.to_c() + '\n'
|
||||
|
||||
class SM64_ShortArray:
|
||||
def __init__(self, name, signed):
|
||||
self.name = name
|
||||
self.shortData = []
|
||||
self.signed = signed
|
||||
|
||||
def to_binary(self):
|
||||
data = bytearray(0)
|
||||
for short in self.shortData:
|
||||
#print(self.name + str(short))
|
||||
data += short.to_bytes(2, 'big', signed = self.signed)
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
data = 'static const ' + ('s' if self.signed else 'u') + \
|
||||
'16 ' + self.name + '[] = {\n\t'
|
||||
wrapCounter = 0
|
||||
for short in self.shortData:
|
||||
data += '0x' + format(short, '04X') + ', '
|
||||
wrapCounter += 1
|
||||
if wrapCounter > 8:
|
||||
data += '\n\t'
|
||||
wrapCounter = 0
|
||||
data += '\n};\n'
|
||||
return data
|
||||
|
||||
class SM64_AnimationHeader:
|
||||
def __init__(self, name, repetitions, marioYOffset, frameInterval,
|
||||
nodeCount, transformValuesStart, transformIndicesStart, animSize):
|
||||
self.name = name
|
||||
self.repetitions = repetitions
|
||||
self.marioYOffset = marioYOffset
|
||||
self.frameInterval = frameInterval
|
||||
self.nodeCount = nodeCount
|
||||
self.transformValuesStart = transformValuesStart
|
||||
self.transformIndicesStart = transformIndicesStart
|
||||
self.animSize = animSize # DMA animations only
|
||||
|
||||
self.transformIndices = []
|
||||
|
||||
# presence of segmentData indicates DMA.
|
||||
def to_binary(self, segmentData, isDMA, startAddress):
|
||||
if isDMA:
|
||||
transformValuesStart = self.transformValuesStart
|
||||
transformIndicesStart = self.transformIndicesStart
|
||||
else:
|
||||
transformValuesStart = self.transformValuesStart + startAddress
|
||||
transformIndicesStart = self.transformIndicesStart + startAddress
|
||||
|
||||
data = bytearray(0)
|
||||
data.extend(self.repetitions.to_bytes(2, byteorder='big'))
|
||||
data.extend(self.marioYOffset.to_bytes(2, byteorder='big')) # y offset, only used for mario
|
||||
data.extend([0x00, 0x00]) # unknown, common with secondary anims, variable length animations?
|
||||
data.extend(int(round(self.frameInterval[0])).to_bytes(2, byteorder='big'))
|
||||
data.extend(int(round(self.frameInterval[1] - 1)).to_bytes(2, byteorder='big'))
|
||||
data.extend(self.nodeCount.to_bytes(2, byteorder='big'))
|
||||
if not isDMA:
|
||||
data.extend(encodeSegmentedAddr(transformValuesStart, segmentData))
|
||||
data.extend(encodeSegmentedAddr(transformIndicesStart, segmentData))
|
||||
data.extend(bytearray([0x00] * 6))
|
||||
else:
|
||||
data.extend(transformValuesStart.to_bytes(4, byteorder='big'))
|
||||
data.extend(transformIndicesStart.to_bytes(4, byteorder='big'))
|
||||
data.extend(self.animSize.to_bytes(4, byteorder='big'))
|
||||
data.extend(bytearray([0x00] * 2))
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
data = 'static const struct Animation ' + self.name + '[] = {\n' +\
|
||||
'\t' + str(self.repetitions) + ',\n' + \
|
||||
'\t' + str(self.marioYOffset) + ',\n' + \
|
||||
'\t0,\n' + \
|
||||
'\t' + str(int(round(self.frameInterval[0]))) + ',\n' + \
|
||||
'\t' + str(int(round(self.frameInterval[1] - 1))) + ',\n' + \
|
||||
'\tANIMINDEX_NUMPARTS(' + self.name + '_indices),\n' + \
|
||||
'\t' + self.name + '_values,\n' + \
|
||||
'\t' + self.name + '_indices,\n' + \
|
||||
'\t0,\n' + \
|
||||
'};\n'
|
||||
return data
|
||||
|
||||
class SM64_AnimIndexNode:
|
||||
def __init__(self, x, y, z):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
class SM64_AnimIndex:
|
||||
def __init__(self, numFrames, startOffset):
|
||||
self.startOffset = startOffset
|
||||
self.numFrames = numFrames
|
||||
|
||||
def getLastKeyframeTime(keyframes):
|
||||
last = keyframes[0].co[0]
|
||||
for keyframe in keyframes:
|
||||
if keyframe.co[0] > last:
|
||||
last = keyframe.co[0]
|
||||
return last
|
||||
|
||||
def exportAnimationC(armatureObj, loopAnim, filePath):
|
||||
sm64_anim = exportAnimationCommon(armatureObj, loopAnim)
|
||||
|
||||
data = sm64_anim.to_c()
|
||||
outFile = open(filePath, 'w')
|
||||
outFile.write(data)
|
||||
outFile.close()
|
||||
|
||||
def exportAnimationBinary(romfile, exportRange, armatureObj, DMAAddresses,
|
||||
segmentData, isDMA, loopAnim):
|
||||
|
||||
startAddress = get64bitAlignedAddr(exportRange[0])
|
||||
sm64_anim = exportAnimationCommon(armatureObj, loopAnim)
|
||||
|
||||
animData = sm64_anim.to_binary(segmentData, isDMA, startAddress)
|
||||
|
||||
if startAddress + len(animData) > exportRange[1]:
|
||||
raise ValueError('Size too big: Data ends at ' + \
|
||||
hex(startAddress + len(animData)) +\
|
||||
', which is larger than the specified range.')
|
||||
|
||||
romfile.seek(startAddress)
|
||||
romfile.write(animData)
|
||||
|
||||
addrRange = (startAddress, startAddress + len(animData))
|
||||
|
||||
if not isDMA:
|
||||
animTablePointer = get64bitAlignedAddr(startAddress + len(animData))
|
||||
romfile.seek(animTablePointer)
|
||||
romfile.write(encodeSegmentedAddr(startAddress, segmentData))
|
||||
return addrRange, animTablePointer
|
||||
else:
|
||||
if DMAAddresses is not None:
|
||||
romfile.seek(DMAAddresses['entry'])
|
||||
romfile.write((startAddress - DMAAddresses['start']).to_bytes(
|
||||
4, byteorder = 'big'))
|
||||
romfile.seek(DMAAddresses['entry'] + 4)
|
||||
romfile.write(len(animData).to_bytes(4, byteorder='big'))
|
||||
return addrRange, None
|
||||
|
||||
def exportAnimationCommon(armatureObj, loopAnim):
|
||||
if armatureObj.animation_data is None or \
|
||||
armatureObj.animation_data.action is None:
|
||||
raise ValueError("No active animation selected.")
|
||||
anim = armatureObj.animation_data.action
|
||||
sm64_anim = SM64_Animation(toAlnum(anim.name))
|
||||
|
||||
nodeCount = len(armatureObj.data.bones)
|
||||
translationData, armatureFrameData = convertAnimationData(anim, armatureObj)
|
||||
|
||||
repetitions = 0 if loopAnim else 1
|
||||
marioYOffset = 0x00 # ??? Seems to be this value for most animations
|
||||
frameInterval = [int(round(anim.frame_range[0])),
|
||||
int(round(anim.frame_range[1])) + 1]
|
||||
|
||||
transformValuesOffset = 0
|
||||
headerSize = 0x1A
|
||||
transformIndicesStart = headerSize #0x18 if including animSize?
|
||||
|
||||
# all node rotations + root translation
|
||||
# *3 for each property (xyz) and *4 for entry size
|
||||
# each keyframe stored as 2 bytes
|
||||
# transformValuesStart = transformIndicesStart + (nodeCount + 1) * 3 * 4
|
||||
transformValuesStart = transformIndicesStart
|
||||
|
||||
for translationFrameProperty in translationData:
|
||||
frameCount = len(translationFrameProperty)
|
||||
sm64_anim.indices.shortData.append(frameCount)
|
||||
sm64_anim.indices.shortData.append(transformValuesOffset)
|
||||
if(transformValuesOffset) > 2**16 - 1:
|
||||
raise ValueError('Animation is too large.')
|
||||
transformValuesOffset += frameCount
|
||||
transformValuesStart += 4
|
||||
for value in translationFrameProperty:
|
||||
sm64_anim.values.shortData.append(int.from_bytes(value.to_bytes(2,'big', signed = True), byteorder = 'big', signed = False))
|
||||
|
||||
for boneFrameData in armatureFrameData:
|
||||
for boneFrameDataProperty in boneFrameData:
|
||||
frameCount = len(boneFrameDataProperty)
|
||||
sm64_anim.indices.shortData.append(frameCount)
|
||||
sm64_anim.indices.shortData.append(transformValuesOffset)
|
||||
if(transformValuesOffset) > 2**16 - 1:
|
||||
raise ValueError('Animation is too large.')
|
||||
transformValuesOffset += frameCount
|
||||
transformValuesStart += 4
|
||||
for value in boneFrameDataProperty:
|
||||
sm64_anim.values.shortData.append(value)
|
||||
|
||||
animSize = headerSize + len(sm64_anim.indices.shortData) * 2 + \
|
||||
len(sm64_anim.values.shortData) * 2
|
||||
|
||||
sm64_anim.header = SM64_AnimationHeader(sm64_anim.name, repetitions,
|
||||
marioYOffset, frameInterval, nodeCount, transformValuesStart,
|
||||
transformIndicesStart, animSize)
|
||||
|
||||
return sm64_anim
|
||||
|
||||
def saveQuaternionFrame(frameData, rotation):
|
||||
for i in range(3):
|
||||
field = rotation.to_euler()[i]
|
||||
value = (math.degrees(field) % 360 ) / 360
|
||||
frameData[i].append(min(int(round(value * (2**16 - 1))), 2**16 - 1))
|
||||
|
||||
def removeTrailingFrames(frameData):
|
||||
for i in range(3):
|
||||
if len(frameData[i]) < 2:
|
||||
continue
|
||||
lastUniqueFrame = len(frameData[i]) - 1
|
||||
while lastUniqueFrame > 0:
|
||||
if frameData[i][lastUniqueFrame] == \
|
||||
frameData[i][lastUniqueFrame - 1]:
|
||||
lastUniqueFrame -= 1
|
||||
else:
|
||||
break
|
||||
frameData[i] = frameData[i][:lastUniqueFrame + 1]
|
||||
|
||||
def saveTranslationFrame(frameData, translation):
|
||||
for i in range(3):
|
||||
frameData[i].append(min(int(round(translation[i] / sm64ToBlenderScale)),
|
||||
2**16 - 1))
|
||||
|
||||
def convertAnimationData(anim, armatureObj):
|
||||
if 'root' not in armatureObj.data.bones:
|
||||
raise ValueError('Cannot find bone named "root". The first animatable' +\
|
||||
' (0x13) bone in the armature must be named "root."')
|
||||
|
||||
currentBone = armatureObj.data.bones["root"]
|
||||
bonesToProcess = [currentBone.name]
|
||||
animBones = []
|
||||
|
||||
# Get animation bones in order
|
||||
while len(bonesToProcess) > 0:
|
||||
boneName = bonesToProcess[0]
|
||||
currentBone = armatureObj.data.bones[boneName]
|
||||
currentPoseBone = armatureObj.pose.bones[boneName]
|
||||
bonesToProcess = bonesToProcess[1:]
|
||||
|
||||
# Only handle 0x13 bones for animation
|
||||
if currentPoseBone.bone_group is None:
|
||||
animBones.append(boneName)
|
||||
|
||||
# Traverse children in alphabetical order.
|
||||
childrenNames = sorted([bone.name for bone in currentBone.children])
|
||||
bonesToProcess = childrenNames + bonesToProcess
|
||||
|
||||
# list of boneFrameData, which is [[x frames], [y frames], [z frames]]
|
||||
translationData = [[],[],[]]
|
||||
armatureFrameData = []
|
||||
for i in range(len(animBones)):
|
||||
armatureFrameData.append([[],[],[]])
|
||||
for frame in range(int(round(anim.frame_range[1])) + 1):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
translation = armatureObj.pose.bones["root"].location
|
||||
saveTranslationFrame(translationData, translation)
|
||||
|
||||
for boneIndex in range(len(animBones)):
|
||||
boneName = animBones[boneIndex]
|
||||
currentBone = armatureObj.data.bones[boneName]
|
||||
currentPoseBone = armatureObj.pose.bones[boneName]
|
||||
|
||||
rotationValue = currentPoseBone.matrix.to_quaternion()
|
||||
if currentBone.parent is not None:
|
||||
rotationValue = (currentPoseBone.parent.matrix.inverted() @ \
|
||||
currentPoseBone.matrix).to_quaternion()
|
||||
|
||||
saveQuaternionFrame(armatureFrameData[boneIndex], rotationValue)
|
||||
|
||||
removeTrailingFrames(translationData)
|
||||
for frameData in armatureFrameData:
|
||||
removeTrailingFrames(frameData)
|
||||
|
||||
return translationData, armatureFrameData
|
||||
|
||||
def getNextBone(boneStack, armatureObj):
|
||||
if len(boneStack) == 0:
|
||||
raise ValueError("More bones in animation than on armature.")
|
||||
bone = armatureObj.data.bones[boneStack[0]]
|
||||
boneStack = boneStack[1:]
|
||||
boneStack = sorted([child.name for child in bone.children]) + boneStack
|
||||
|
||||
# Only return 0x13 bone
|
||||
while armatureObj.pose.bones[bone.name].bone_group is not None:
|
||||
if len(boneStack) == 0:
|
||||
raise ValueError("More bones in animation than on armature.")
|
||||
bone = armatureObj.data.bones[boneStack[0]]
|
||||
boneStack = boneStack[1:]
|
||||
boneStack = sorted([child.name for child in bone.children]) + boneStack
|
||||
|
||||
return bone, boneStack
|
||||
|
||||
def importAnimationToBlender(romfile, startAddress, armatureObj, segmentData, isDMA):
|
||||
if 'root' not in armatureObj.data.bones:
|
||||
raise ValueError('Cannot find bone named "root". The first animatable' +\
|
||||
'(0x13) bone in the armature must be named "root."')
|
||||
|
||||
animationHeader, armatureFrameData = \
|
||||
readAnimation('sm64_anim', romfile, startAddress, segmentData, isDMA)
|
||||
|
||||
if len(armatureFrameData) > len(armatureObj.data.bones) + 1:
|
||||
raise ValueError('More bones in animation than on armature.')
|
||||
|
||||
#bpy.context.scene.render.fps = 30
|
||||
bpy.context.scene.frame_end = animationHeader.frameInterval[1]
|
||||
anim = bpy.data.actions.new("sm64_anim")
|
||||
|
||||
boneStack = ['root']
|
||||
|
||||
isRootTranslation = True
|
||||
# boneFrameData = [[x keyframes], [y keyframes], [z keyframes]]
|
||||
# len(armatureFrameData) should be = number of bones
|
||||
# property index = 0,1,2 (aka x,y,z)
|
||||
for boneFrameData in armatureFrameData:
|
||||
if isRootTranslation:
|
||||
for propertyIndex in range(3):
|
||||
fcurve = anim.fcurves.new(
|
||||
data_path = 'pose.bones["root"].location',
|
||||
index = propertyIndex,
|
||||
action_group = 'root')
|
||||
for frame in range(len(boneFrameData[propertyIndex])):
|
||||
fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame])
|
||||
isRootTranslation = False
|
||||
else:
|
||||
bone, boneStack = getNextBone(boneStack, armatureObj)
|
||||
print(bone.name)
|
||||
for propertyIndex in range(3):
|
||||
fcurve = anim.fcurves.new(
|
||||
data_path = 'pose.bones["' + bone.name + '"].rotation_euler',
|
||||
index = propertyIndex,
|
||||
action_group = bone.name)
|
||||
for frame in range(len(boneFrameData[propertyIndex])):
|
||||
fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame])
|
||||
|
||||
if armatureObj.animation_data is None:
|
||||
armatureObj.animation_data_create()
|
||||
armatureObj.animation_data.action = anim
|
||||
|
||||
def readAnimation(name, romfile, startAddress, segmentData, isDMA):
|
||||
animationHeader = readAnimHeader(name, romfile, startAddress, segmentData, isDMA)
|
||||
|
||||
print("Frames: " + str(animationHeader.frameInterval[1]) + " / Nodes: " + str(animationHeader.nodeCount))
|
||||
|
||||
animationHeader.transformIndices = readAnimIndices(
|
||||
romfile, animationHeader.transformIndicesStart, animationHeader.nodeCount)
|
||||
|
||||
armatureFrameData = [] #list of list of frames
|
||||
|
||||
# sm64 space -> blender space -> pose space
|
||||
# BlenderToSM64: YZX (set rotation mode of bones)
|
||||
# SM64toBlender: ZXY (set anim keyframes and model armature)
|
||||
# new bones should extrude in +Y direction
|
||||
|
||||
# handle root translation
|
||||
boneFrameData = [[],[],[]]
|
||||
rootIndexNode = animationHeader.transformIndices[0]
|
||||
boneFrameData[0] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.x)]
|
||||
boneFrameData[1] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.y)]
|
||||
boneFrameData[2] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.z)]
|
||||
armatureFrameData.append(boneFrameData)
|
||||
|
||||
# handle rotations
|
||||
for boneIndexNode in animationHeader.transformIndices[1:]:
|
||||
boneFrameData = [[],[],[]]
|
||||
|
||||
# Transforming SM64 space to Blender space
|
||||
boneFrameData[0] = [n for n in \
|
||||
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.x)]
|
||||
boneFrameData[1] = [n for n in \
|
||||
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.y)]
|
||||
boneFrameData[2] = [n for n in \
|
||||
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.z)]
|
||||
|
||||
armatureFrameData.append(boneFrameData)
|
||||
|
||||
return (animationHeader, armatureFrameData)
|
||||
|
||||
def getKeyFramesRotation(romfile, transformValuesStart, boneIndex):
|
||||
ptrToValue = transformValuesStart + boneIndex.startOffset
|
||||
romfile.seek(ptrToValue)
|
||||
|
||||
keyframes = []
|
||||
for frame in range(boneIndex.numFrames):
|
||||
romfile.seek(ptrToValue + frame * 2)
|
||||
value = int.from_bytes(romfile.read(2), 'big') * 360 / (2**16)
|
||||
keyframes.append(math.radians(value))
|
||||
|
||||
return keyframes
|
||||
|
||||
def getKeyFramesTranslation(romfile, transformValuesStart, boneIndex):
|
||||
ptrToValue = transformValuesStart + boneIndex.startOffset
|
||||
romfile.seek(ptrToValue)
|
||||
|
||||
keyframes = []
|
||||
for frame in range(boneIndex.numFrames):
|
||||
romfile.seek(ptrToValue + frame * 2)
|
||||
keyframes.append(int.from_bytes(romfile.read(2), 'big', signed = True) *\
|
||||
sm64ToBlenderScale)
|
||||
|
||||
return keyframes
|
||||
|
||||
def readAnimHeader(name, romfile, startAddress, segmentData, isDMA):
|
||||
frameInterval = [0,0]
|
||||
|
||||
romfile.seek(startAddress + 0x00)
|
||||
numRepeats = int.from_bytes(romfile.read(2), 'big')
|
||||
|
||||
romfile.seek(startAddress + 0x02)
|
||||
marioYOffset = int.from_bytes(romfile.read(2), 'big')
|
||||
|
||||
romfile.seek(startAddress + 0x06)
|
||||
frameInterval[0] = int.from_bytes(romfile.read(2), 'big')
|
||||
|
||||
romfile.seek(startAddress + 0x08)
|
||||
frameInterval[1] = int.from_bytes(romfile.read(2), 'big')
|
||||
|
||||
romfile.seek(startAddress + 0x0A)
|
||||
numNodes = int.from_bytes(romfile.read(2), 'big')
|
||||
|
||||
romfile.seek(startAddress + 0x0C)
|
||||
transformValuesOffset = int.from_bytes(romfile.read(4), 'big')
|
||||
if isDMA:
|
||||
transformValuesStart = startAddress + transformValuesOffset
|
||||
else:
|
||||
transformValuesStart = decodeSegmentedAddr(
|
||||
transformValuesOffset.to_bytes(4, byteorder='big'), segmentData)
|
||||
|
||||
romfile.seek(startAddress + 0x10)
|
||||
transformIndicesOffset = int.from_bytes(romfile.read(4), 'big')
|
||||
if isDMA:
|
||||
transformIndicesStart = startAddress + transformIndicesOffset
|
||||
else:
|
||||
transformIndicesStart = decodeSegmentedAddr(
|
||||
transformIndicesOffset.to_bytes(4, byteorder='big'), segmentData)
|
||||
|
||||
romfile.seek(startAddress + 0x14)
|
||||
animSize = int.from_bytes(romfile.read(4), 'big')
|
||||
|
||||
return SM64_AnimationHeader(name, numRepeats, marioYOffset, frameInterval, numNodes,
|
||||
transformValuesStart, transformIndicesStart, animSize)
|
||||
|
||||
def readAnimIndices(romfile, ptrAddress, nodeCount):
|
||||
indices = []
|
||||
|
||||
# Handle root transform
|
||||
rootPosIndex = readTransformIndex(romfile, ptrAddress)
|
||||
indices.append(rootPosIndex)
|
||||
|
||||
# Handle rotations
|
||||
for i in range(nodeCount):
|
||||
rotationIndex = readTransformIndex(romfile, ptrAddress + (i+1) * 12)
|
||||
indices.append(rotationIndex)
|
||||
|
||||
return indices
|
||||
|
||||
def readTransformIndex(romfile, startAddress):
|
||||
x = readValueIndex(romfile, startAddress + 0)
|
||||
y = readValueIndex(romfile, startAddress + 4)
|
||||
z = readValueIndex(romfile, startAddress + 8)
|
||||
|
||||
return SM64_AnimIndexNode(x, y, z)
|
||||
|
||||
def readValueIndex(romfile, startAddress):
|
||||
romfile.seek(startAddress)
|
||||
numFrames = int.from_bytes(romfile.read(2), 'big')
|
||||
romfile.seek(startAddress + 2)
|
||||
|
||||
# multiply 2 because value is the index in array of shorts (???)
|
||||
startOffset = int.from_bytes(romfile.read(2), 'big') * 2
|
||||
print(str(hex(startAddress)) + ": " + str(numFrames) + " " + str(startOffset))
|
||||
return SM64_AnimIndex(numFrames, startOffset)
|
||||
|
||||
def writeAnimation(romfile, startAddress, segmentData):
|
||||
pass
|
||||
|
||||
def writeAnimHeader(romfile, startAddress, segmentData):
|
||||
pass
|
||||
@@ -0,0 +1,936 @@
|
||||
from .utility import *
|
||||
from .sm64_constants import *
|
||||
from bpy.utils import register_class, unregister_class
|
||||
import bpy, bmesh
|
||||
|
||||
class CollisionVertex:
|
||||
def __init__(self, position):
|
||||
self.position = position
|
||||
|
||||
def to_binary(self):
|
||||
data = bytearray(0)
|
||||
if len(self.position) > 3:
|
||||
raise ValueError("Vertex position should not be " + \
|
||||
str(len(self.position) + ' fields long.'))
|
||||
for field in self.position:
|
||||
data.extend(int(round(field)).to_bytes(2, 'big', signed = True))
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
return 'COL_VERTEX(' + \
|
||||
str(int(round(self.position[0]))) + ', ' + \
|
||||
str(int(round(self.position[1]))) + ', ' + \
|
||||
str(int(round(self.position[2]))) + '),\n'
|
||||
|
||||
class CollisionTriangle:
|
||||
def __init__(self, indices):
|
||||
self.indices = indices
|
||||
self.specialParam = None
|
||||
|
||||
def to_binary(self):
|
||||
data = bytearray(0)
|
||||
if len(self.indices) > 3:
|
||||
raise ValueError("Triangle indices should not be " + \
|
||||
str(len(self.indices) + ' fields long.'))
|
||||
for index in self.indices:
|
||||
data.extend(int(round(index)).to_bytes(2, 'big', signed = False))
|
||||
if self.specialParam is not None:
|
||||
data.extend(int(self.specialParam).to_bytes(2, 'big', signed=False))
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
if self.specialParam is None:
|
||||
return 'COL_TRI(' + \
|
||||
str(int(round(self.indices[0]))) + ', ' + \
|
||||
str(int(round(self.indices[1]))) + ', ' + \
|
||||
str(int(round(self.indices[2]))) + '),\n'
|
||||
else:
|
||||
return 'COL_TRI_SPECIAL(' + \
|
||||
str(int(round(self.indices[0]))) + ', ' + \
|
||||
str(int(round(self.indices[1]))) + ', ' + \
|
||||
str(int(round(self.indices[2]))) + ', ' + \
|
||||
str(int(self.specialParam)) + '),\n'
|
||||
|
||||
class CollisionSpecial:
|
||||
def __init__(self):
|
||||
self.preset = None
|
||||
self.position = [None] * 3
|
||||
self.yaw = None
|
||||
self.specialParam = None
|
||||
|
||||
def to_binary(self):
|
||||
data = int(self.preset).to_bytes(2, 'big')
|
||||
if len(self.position) > 3:
|
||||
raise ValueError("Object position should not be " + \
|
||||
str(len(self.position) + ' fields long.'))
|
||||
for index in self.position:
|
||||
data.extend(int(round(index)).to_bytes(2, 'big', signed = False))
|
||||
if self.yaw is not None:
|
||||
data.extend(int(self.yaw).to_bytes(2, 'big'))
|
||||
if self.specialParam is not None:
|
||||
data.extend(int(self.specialParam).to_bytes(2, 'big'))
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
if self.yaw is None:
|
||||
data = 'SPECIAL_OBJECT('
|
||||
elif self.specialParam is None:
|
||||
data = 'SPECIAL_OBJECT_WITH_YAW('
|
||||
else:
|
||||
data = 'SPECIAL_OBJECT_WITH_YAW_AND_PARAM('
|
||||
|
||||
data += str(self.preset) + ', ' +\
|
||||
str(int(round(self.position[0]))) + ', ' + \
|
||||
str(int(round(self.position[1]))) + ', ' + \
|
||||
str(int(round(self.position[2])))
|
||||
|
||||
if self.yaw is None:
|
||||
data += '),\n'
|
||||
elif self.specialParam is None:
|
||||
data += ', ' + str(self.yaw) + '),\n'
|
||||
else:
|
||||
data += ', ' + str(self.yaw) + ', ' + str(self.specialParam) + '),\n'
|
||||
return data
|
||||
|
||||
class CollisionWaterBox:
|
||||
def __init__(self):
|
||||
self.waterBoxType = 'Water'
|
||||
self.low = (0,0)
|
||||
self.high = (0,0)
|
||||
self.height = 0
|
||||
|
||||
def to_binary(self):
|
||||
data = bytearray([0x00, 0x00 if self.waterBoxType == 'Water' else 0x32])
|
||||
data.extend(int(round(self.low[0])).to_bytes(2, 'big', signed=True))
|
||||
data.extend(int(round(self.low[1])).to_bytes(2, 'big', signed=True))
|
||||
data.extend(int(round(self.high[0])).to_bytes(2, 'big', signed=True))
|
||||
data.extend(int(round(self.high[1])).to_bytes(2, 'big', signed=True))
|
||||
data.extend(int(round(self.height)).to_bytes(2, 'big', signed=True))
|
||||
return data
|
||||
|
||||
def to_c(self):
|
||||
data = 'COL_WATER_BOX(' + \
|
||||
('0x00' if self.waterBoxType == 'Water' else '0x32') + ', ' + \
|
||||
str(int(round(self.low[0]))) + ', ' + \
|
||||
str(int(round(self.low[1]))) + ', ' + \
|
||||
str(int(round(self.high[0]))) + ', ' + \
|
||||
str(int(round(self.high[1]))) + ', ' + \
|
||||
str(int(round(self.height))) + '),\n'
|
||||
return data
|
||||
|
||||
class Collision:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.startAddress = 0
|
||||
self.vertices = []
|
||||
# dict of collision type : triangle list
|
||||
self.triangles = {}
|
||||
self.specials = []
|
||||
self.waterBoxes = []
|
||||
|
||||
def set_addr(self, startAddress):
|
||||
startAddress = get64bitAlignedAddr(startAddress)
|
||||
self.startAddress = startAddress
|
||||
print('Collision ' + self.name + ': ' + str(startAddress) + \
|
||||
', ' + str(self.size()))
|
||||
return startAddress, startAddress + self.size()
|
||||
|
||||
def save_binary(self, romfile):
|
||||
romfile.seek(self.startAddress)
|
||||
romfile.write(self.to_binary())
|
||||
|
||||
def size(self):
|
||||
return len(self.to_binary())
|
||||
|
||||
def to_c(self):
|
||||
data = 'const Collision ' + self.name + '[] = {\n'
|
||||
data += '\tCOL_INIT(),\n'
|
||||
data += '\tCOL_VERTEX_INIT(' + str(len(self.vertices)) + '),\n'
|
||||
for vertex in self.vertices:
|
||||
data += '\t' + vertex.to_c()
|
||||
for collisionType, triangles in self.triangles.items():
|
||||
data += '\tCOL_TRI_INIT(' + collisionType + ', ' +\
|
||||
str(len(triangles)) + '),\n'
|
||||
for triangle in triangles:
|
||||
data += '\t' + triangle.to_c()
|
||||
data += '\tCOL_TRI_STOP(),\n'
|
||||
if len(self.specials) > 0:
|
||||
data += '\tCOL_SPECIAL_INIT(' + str(len(self.specials)) + '),\n'
|
||||
for special in self.specials:
|
||||
data += '\t' + special.to_c()
|
||||
if len(self.waterBoxes) > 0:
|
||||
data += '\tCOL_WATER_BOX_INIT(' + str(len(self.waterBoxes)) + '),\n'
|
||||
for waterBox in self.waterBoxes:
|
||||
data += '\t' + waterBox.to_c()
|
||||
data += '\tCOL_END()\n' + '};\n'
|
||||
return data
|
||||
|
||||
def to_binary(self):
|
||||
colTypeDef = CollisionTypeDefinition()
|
||||
data = bytearray([0x00, 0x40])
|
||||
data += len(self.vertices).to_bytes(2, 'big')
|
||||
for vertex in self.vertices:
|
||||
data += vertex.to_binary()
|
||||
for collisionType, triangles in self.triangles.items():
|
||||
data += getattr(colTypeDef, collisionType).to_bytes(2, 'big')
|
||||
data += len(triangles).to_bytes(2, 'big')
|
||||
for triangle in triangles:
|
||||
data += triangle.to_binary()
|
||||
data += bytearray([0x00, 0x41])
|
||||
if len(self.specials) > 0:
|
||||
data += bytearray([0x00, 0x43])
|
||||
data += len(self.specials).to_bytes(2, 'big')
|
||||
for special in self.specials:
|
||||
data += special.to_binary()
|
||||
if len(self.waterBoxes) > 0:
|
||||
data += bytearray([0x00, 0x44])
|
||||
data += len(self.waterBoxes).to_bytes(2, 'big')
|
||||
for waterBox in self.waterBoxes:
|
||||
data += waterBox.to_binary()
|
||||
data += bytearray([0x00, 0x42])
|
||||
return data
|
||||
|
||||
class CollisionPanel(bpy.types.Panel):
|
||||
bl_label = "Collision Inspector"
|
||||
bl_idname = "SM64_Collision_Inspector"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "material"
|
||||
bl_options = {'HIDE_HEADER'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.material is not None)
|
||||
|
||||
def draw(self, context):
|
||||
box = self.layout.box()
|
||||
#box.label(text = 'Collision Inspector')
|
||||
material = context.material
|
||||
if not material.collision_all_options:
|
||||
prop_split(box, material, 'collision_type_simple',
|
||||
'SM64 Collision Type')
|
||||
else:
|
||||
prop_split(box, material, 'collision_type',
|
||||
'SM64 Collision Type All')
|
||||
|
||||
split = box.split(factor = 0.5)
|
||||
split.label(text = '')
|
||||
split.prop(material, 'collision_all_options')
|
||||
#infoBox = box.box()
|
||||
#infoBox.label(text = \
|
||||
# 'For special params, make a vert color layer named "Collision."')
|
||||
#infoBox.label(text = 'The red value 0-1 will be converted to 0-65535.')
|
||||
|
||||
class SM64ObjectPanel(bpy.types.Panel):
|
||||
bl_label = "Object Inspector"
|
||||
bl_idname = "SM64_Object_Inspector"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
bl_options = {'HIDE_HEADER'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (context.object is not None)
|
||||
|
||||
def draw(self, context):
|
||||
box = self.layout.box()
|
||||
box.label(text = 'SM64 Object Inspector')
|
||||
obj = context.object
|
||||
prop_split(box, obj, 'sm64_obj_type', 'Special Preset')
|
||||
if obj.sm64_obj_type == 'Special':
|
||||
prop_split(box, obj, 'sm64_special_preset', 'Special Preset')
|
||||
elif obj.sm64_obj_type == 'Water Box':
|
||||
prop_split(box, obj, 'sm64_water_box', 'Water Box Type')
|
||||
|
||||
def exportCollisionBinary(obj, transformMatrix, romfile, startAddress,
|
||||
endAddress, addEntities):
|
||||
collision = exportCollisionCommon(obj, transformMatrix, addEntities)
|
||||
start, end = collision.set_addr(startAddress)
|
||||
if end > endAddress:
|
||||
raise ValueError('Size too big: Data ends at ' + hex(end) +\
|
||||
', which is larger than the specified range.')
|
||||
collision.save_binary(romfile)
|
||||
return start, end
|
||||
|
||||
def exportCollisionC(obj, transformMatrix, filePath, addEntities):
|
||||
fileObj = open(filePath, 'w')
|
||||
collision = exportCollisionCommon(obj, transformMatrix, addEntities)
|
||||
fileObj.write(collision.to_c())
|
||||
fileObj.close()
|
||||
|
||||
def exportCollisionCommon(obj, transformMatrix, addEntities):
|
||||
bpy.ops.object.select_all(action = 'DESELECT')
|
||||
obj.select_set(True)
|
||||
|
||||
bMesh = bmesh.new()
|
||||
bMesh.from_mesh(obj.data)
|
||||
bmesh.ops.triangulate(bMesh, faces = bMesh.faces[:])
|
||||
bMesh.verts.ensure_lookup_table()
|
||||
bMesh.edges.ensure_lookup_table()
|
||||
bMesh.verts.index_update()
|
||||
bMesh.faces.index_update()
|
||||
|
||||
if len(obj.data.materials) == 0:
|
||||
raise ValueError(obj.name + " must have a material associated with it.")
|
||||
|
||||
# dict of collisionType : faces
|
||||
collisionDict = {}
|
||||
for face in bMesh.faces:
|
||||
material = obj.data.materials[face.material_index]
|
||||
colType = material.collision_type if material.collision_all_options else \
|
||||
material.collision_type_simple
|
||||
if colType not in collisionDict:
|
||||
collisionDict[colType] = []
|
||||
collisionDict[colType].append(face)
|
||||
|
||||
collision = Collision(toAlnum(obj.name) + '_collision')
|
||||
for collisionType, faces in collisionDict.items():
|
||||
collision.triangles[collisionType] = []
|
||||
for face in faces:
|
||||
indices = []
|
||||
for vert in face.verts:
|
||||
roundedPosition = roundPosition(transformMatrix @ vert.co)
|
||||
index = collisionVertIndex(roundedPosition, collision.vertices)
|
||||
if index is None:
|
||||
collision.vertices.append(CollisionVertex(roundedPosition))
|
||||
indices.append(len(collision.vertices) - 1)
|
||||
else:
|
||||
indices.append(index)
|
||||
collision.triangles[collisionType].append(CollisionTriangle(indices))
|
||||
|
||||
if addEntities:
|
||||
childArray = obj.children
|
||||
while len(childArray) > 0:
|
||||
childObj = childArray[0]
|
||||
childArray = childArray[1:]
|
||||
if childObj.sm64_obj_type == 'Special':
|
||||
special = CollisionSpecial()
|
||||
special.preset = childObj.sm64_special_preset
|
||||
special.position = roundPosition(transformMatrix @ \
|
||||
childObj.location)
|
||||
collision.specials.append(special)
|
||||
elif childObj.sm64_obj_type == 'Water Box':
|
||||
pass
|
||||
childArray.extend(childObj.children)
|
||||
|
||||
return collision
|
||||
|
||||
def roundPosition(position):
|
||||
return (int(round(position[0])),
|
||||
int(round(position[1])),
|
||||
int(round(position[2])))
|
||||
|
||||
def collisionVertIndex(vert, vertArray):
|
||||
for i in range(len(vertArray)):
|
||||
colVert = vertArray[i]
|
||||
if colVert.position == vert:
|
||||
return i
|
||||
return None
|
||||
|
||||
classes = (
|
||||
CollisionPanel,
|
||||
#SM64ObjectPanel,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
bpy.types.Material.collision_type = bpy.props.EnumProperty(
|
||||
name = 'Collision Type', items = enumCollisionType,
|
||||
default = 'SURFACE_DEFAULT')
|
||||
|
||||
bpy.types.Material.collision_type_simple = bpy.props.EnumProperty(
|
||||
name = 'Collision Type', items = enumCollisionTypeSimple,
|
||||
default = 'SURFACE_DEFAULT')
|
||||
|
||||
bpy.types.Material.collision_all_options = bpy.props.BoolProperty(
|
||||
name = 'Show All Options')
|
||||
|
||||
bpy.types.Object.sm64_obj_type = bpy.props.EnumProperty(
|
||||
name = 'SM64 Object Type', items = enumObjectType, default = 'None')
|
||||
|
||||
bpy.types.Object.sm64_water_box = bpy.props.EnumProperty(
|
||||
name = 'SM64 Water Box', items = enumWaterBoxType, default = 'Water')
|
||||
|
||||
bpy.types.Object.sm64_special_preset = bpy.props.EnumProperty(
|
||||
name = 'SM64 Special', items = enumSpecialType,
|
||||
default = 'special_yellow_coin')
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
enumWaterBoxType = [
|
||||
("Water", 'Water', "Water"),
|
||||
('Toxic Haze', 'Toxic Haze', 'Toxic Haze')
|
||||
]
|
||||
|
||||
class CollisionTypeDefinition:
|
||||
def __init__(self):
|
||||
self.SURFACE_DEFAULT = 0x0000
|
||||
self.SURFACE_BURNING = 0x0001
|
||||
self.SURFACE_0004 = 0x0004
|
||||
self.SURFACE_HANGABLE = 0x0005
|
||||
self.SURFACE_SLOW = 0x0009
|
||||
self.SURFACE_DEATH_PLANE = 0x000A
|
||||
self.SURFACE_CLOSE_CAMERA = 0x000B
|
||||
self.SURFACE_WATER = 0x000D
|
||||
self.SURFACE_FLOWING_WATER = 0x000E
|
||||
self.SURFACE_INTANGIBLE = 0x0012
|
||||
self.SURFACE_VERY_SLIPPERY = 0x0013
|
||||
self.SURFACE_SLIPPERY = 0x0014
|
||||
self.SURFACE_NOT_SLIPPERY = 0x0015
|
||||
self.SURFACE_TTM_VINES = 0x0016
|
||||
self.SURFACE_MGR_MUSIC = 0x001A
|
||||
self.SURFACE_INSTANT_WARP_1B = 0x001B
|
||||
self.SURFACE_INSTANT_WARP_1C = 0x001C
|
||||
self.SURFACE_INSTANT_WARP_1D = 0x001D
|
||||
self.SURFACE_INSTANT_WARP_1E = 0x001E
|
||||
self.SURFACE_SHALLOW_QUICKSAND = 0x0021
|
||||
self.SURFACE_DEEP_QUICKSAND = 0x0022
|
||||
self.SURFACE_INSTANT_QUICKSAND = 0x0023
|
||||
self.SURFACE_DEEP_MOVING_QUICKSAND = 0x0024
|
||||
self.SURFACE_SHALLOW_MOVING_QUICKSAND = 0x0025
|
||||
self.SURFACE_QUICKSAND = 0x0026
|
||||
self.SURFACE_MOVING_QUICKSAND = 0x0027
|
||||
self.SURFACE_WALL_MISC = 0x0028
|
||||
self.SURFACE_NOISE_DEFAULT = 0x0029
|
||||
self.SURFACE_NOISE_SLIPPERY = 0x002A
|
||||
self.SURFACE_HORIZONTAL_WIND = 0x002C
|
||||
self.SURFACE_INSTANT_MOVING_QUICKSAND = 0x002D
|
||||
self.SURFACE_ICE = 0x002E
|
||||
self.SURFACE_LOOK_UP_WARP = 0x002F
|
||||
self.SURFACE_HARD = 0x0030
|
||||
self.SURFACE_WARP = 0x0032
|
||||
self.SURFACE_TIMER_START = 0x0033
|
||||
self.SURFACE_TIMER_END = 0x0034
|
||||
self.SURFACE_HARD_SLIPPERY = 0x0035
|
||||
self.SURFACE_HARD_VERY_SLIPPERY = 0x0036
|
||||
self.SURFACE_HARD_NOT_SLIPPERY = 0x0037
|
||||
self.SURFACE_VERTICAL_WIND = 0x0038
|
||||
self.SURFACE_BOSS_FIGHT_CAMERA = 0x0065
|
||||
self.SURFACE_CAMERA_FREE_ROAM = 0x0066
|
||||
self.SURFACE_THI3_WALLKICK = 0x0068
|
||||
self.SURFACE_CAMERA_PLATFORM = 0x0069
|
||||
self.SURFACE_CAMERA_MIDDLE = 0x006E
|
||||
self.SURFACE_CAMERA_ROTATE_RIGHT = 0x006F
|
||||
self.SURFACE_CAMERA_ROTATE_LEFT = 0x0070
|
||||
self.SURFACE_CAMERA_BOUNDARY = 0x0072
|
||||
self.SURFACE_NOISE_VERY_SLIPPERY_73 = 0x0073
|
||||
self.SURFACE_NOISE_VERY_SLIPPERY_74 = 0x0074
|
||||
self.SURFACE_NOISE_VERY_SLIPPERY = 0x0075
|
||||
self.SURFACE_NO_CAM_COLLISION = 0x0076
|
||||
self.SURFACE_NO_CAM_COLLISION_77 = 0x0077
|
||||
self.SURFACE_NO_CAM_COL_VERY_SLIPPERY = 0x0078
|
||||
self.SURFACE_NO_CAM_COL_SLIPPERY = 0x0079
|
||||
self.SURFACE_SWITCH = 0x007A
|
||||
self.SURFACE_VANISH_CAP_WALLS = 0x007B
|
||||
self.SURFACE_PAINTING_WOBBLE_A6 = 0x00A6
|
||||
self.SURFACE_PAINTING_WOBBLE_A7 = 0x00A7
|
||||
self.SURFACE_PAINTING_WOBBLE_A8 = 0x00A8
|
||||
self.SURFACE_PAINTING_WOBBLE_A9 = 0x00A9
|
||||
self.SURFACE_PAINTING_WOBBLE_AA = 0x00AA
|
||||
self.SURFACE_PAINTING_WOBBLE_AB = 0x00AB
|
||||
self.SURFACE_PAINTING_WOBBLE_AC = 0x00AC
|
||||
self.SURFACE_PAINTING_WOBBLE_AD = 0x00AD
|
||||
self.SURFACE_PAINTING_WOBBLE_AE = 0x00AE
|
||||
self.SURFACE_PAINTING_WOBBLE_AF = 0x00AF
|
||||
self.SURFACE_PAINTING_WOBBLE_B0 = 0x00B0
|
||||
self.SURFACE_PAINTING_WOBBLE_B1 = 0x00B1
|
||||
self.SURFACE_PAINTING_WOBBLE_B2 = 0x00B2
|
||||
self.SURFACE_PAINTING_WOBBLE_B3 = 0x00B3
|
||||
self.SURFACE_PAINTING_WOBBLE_B4 = 0x00B4
|
||||
self.SURFACE_PAINTING_WOBBLE_B5 = 0x00B5
|
||||
self.SURFACE_PAINTING_WOBBLE_B6 = 0x00B6
|
||||
self.SURFACE_PAINTING_WOBBLE_B7 = 0x00B7
|
||||
self.SURFACE_PAINTING_WOBBLE_B8 = 0x00B8
|
||||
self.SURFACE_PAINTING_WOBBLE_B9 = 0x00B9
|
||||
self.SURFACE_PAINTING_WOBBLE_BA = 0x00BA
|
||||
self.SURFACE_PAINTING_WOBBLE_BB = 0x00BB
|
||||
self.SURFACE_PAINTING_WOBBLE_BC = 0x00BC
|
||||
self.SURFACE_PAINTING_WOBBLE_BD = 0x00BD
|
||||
self.SURFACE_PAINTING_WOBBLE_BE = 0x00BE
|
||||
self.SURFACE_PAINTING_WOBBLE_BF = 0x00BF
|
||||
self.SURFACE_PAINTING_WOBBLE_C0 = 0x00C0
|
||||
self.SURFACE_PAINTING_WOBBLE_C1 = 0x00C1
|
||||
self.SURFACE_PAINTING_WOBBLE_C2 = 0x00C2
|
||||
self.SURFACE_PAINTING_WOBBLE_C3 = 0x00C3
|
||||
self.SURFACE_PAINTING_WOBBLE_C4 = 0x00C4
|
||||
self.SURFACE_PAINTING_WOBBLE_C5 = 0x00C5
|
||||
self.SURFACE_PAINTING_WOBBLE_C6 = 0x00C6
|
||||
self.SURFACE_PAINTING_WOBBLE_C7 = 0x00C7
|
||||
self.SURFACE_PAINTING_WOBBLE_C8 = 0x00C8
|
||||
self.SURFACE_PAINTING_WOBBLE_C9 = 0x00C9
|
||||
self.SURFACE_PAINTING_WOBBLE_CA = 0x00CA
|
||||
self.SURFACE_PAINTING_WOBBLE_CB = 0x00CB
|
||||
self.SURFACE_PAINTING_WOBBLE_CC = 0x00CC
|
||||
self.SURFACE_PAINTING_WOBBLE_CD = 0x00CD
|
||||
self.SURFACE_PAINTING_WOBBLE_CE = 0x00CE
|
||||
self.SURFACE_PAINTING_WOBBLE_CF = 0x00CF
|
||||
self.SURFACE_PAINTING_WOBBLE_D0 = 0x00D0
|
||||
self.SURFACE_PAINTING_WOBBLE_D1 = 0x00D1
|
||||
self.SURFACE_PAINTING_WOBBLE_D2 = 0x00D2
|
||||
self.SURFACE_PAINTING_WARP_D3 = 0x00D3
|
||||
self.SURFACE_PAINTING_WARP_D4 = 0x00D4
|
||||
self.SURFACE_PAINTING_WARP_D5 = 0x00D5
|
||||
self.SURFACE_PAINTING_WARP_D6 = 0x00D6
|
||||
self.SURFACE_PAINTING_WARP_D7 = 0x00D7
|
||||
self.SURFACE_PAINTING_WARP_D8 = 0x00D8
|
||||
self.SURFACE_PAINTING_WARP_D9 = 0x00D9
|
||||
self.SURFACE_PAINTING_WARP_DA = 0x00DA
|
||||
self.SURFACE_PAINTING_WARP_DB = 0x00DB
|
||||
self.SURFACE_PAINTING_WARP_DC = 0x00DC
|
||||
self.SURFACE_PAINTING_WARP_DD = 0x00DD
|
||||
self.SURFACE_PAINTING_WARP_DE = 0x00DE
|
||||
self.SURFACE_PAINTING_WARP_DF = 0x00DF
|
||||
self.SURFACE_PAINTING_WARP_E0 = 0x00E0
|
||||
self.SURFACE_PAINTING_WARP_E1 = 0x00E1
|
||||
self.SURFACE_PAINTING_WARP_E2 = 0x00E2
|
||||
self.SURFACE_PAINTING_WARP_E3 = 0x00E3
|
||||
self.SURFACE_PAINTING_WARP_E4 = 0x00E4
|
||||
self.SURFACE_PAINTING_WARP_E5 = 0x00E5
|
||||
self.SURFACE_PAINTING_WARP_E6 = 0x00E6
|
||||
self.SURFACE_PAINTING_WARP_E7 = 0x00E7
|
||||
self.SURFACE_PAINTING_WARP_E8 = 0x00E8
|
||||
self.SURFACE_PAINTING_WARP_E9 = 0x00E9
|
||||
self.SURFACE_PAINTING_WARP_EA = 0x00EA
|
||||
self.SURFACE_PAINTING_WARP_EB = 0x00EB
|
||||
self.SURFACE_PAINTING_WARP_EC = 0x00EC
|
||||
self.SURFACE_PAINTING_WARP_ED = 0x00ED
|
||||
self.SURFACE_PAINTING_WARP_EE = 0x00EE
|
||||
self.SURFACE_PAINTING_WARP_EF = 0x00EF
|
||||
self.SURFACE_PAINTING_WARP_F0 = 0x00F0
|
||||
self.SURFACE_PAINTING_WARP_F1 = 0x00F1
|
||||
self.SURFACE_PAINTING_WARP_F2 = 0x00F2
|
||||
self.SURFACE_PAINTING_WARP_F3 = 0x00F3
|
||||
self.SURFACE_TTC_PAINTING_1 = 0x00F4
|
||||
self.SURFACE_TTC_PAINTING_2 = 0x00F5
|
||||
self.SURFACE_TTC_PAINTING_3 = 0x00F6
|
||||
self.SURFACE_PAINTING_WARP_F7 = 0x00F7
|
||||
self.SURFACE_PAINTING_WARP_F8 = 0x00F8
|
||||
self.SURFACE_PAINTING_WARP_F9 = 0x00F9
|
||||
self.SURFACE_PAINTING_WARP_FA = 0x00FA
|
||||
self.SURFACE_PAINTING_WARP_FB = 0x00FB
|
||||
self.SURFACE_PAINTING_WARP_FC = 0x00FC
|
||||
self.SURFACE_WOBBLING_WARP = 0x00FD
|
||||
self.SURFACE_TRAPDOOR = 0x00FF
|
||||
|
||||
self.SURFACE_CLASS_DEFAULT = 0x0000
|
||||
self.SURFACE_CLASS_VERY_SLIPPERY = 0x0013
|
||||
self.SURFACE_CLASS_SLIPPERY = 0x0014
|
||||
self.SURFACE_CLASS_NOT_SLIPPERY = 0x0015
|
||||
|
||||
self.SURFACE_FLAG_DYNAMIC = (1 << 0)
|
||||
self.SURFACE_FLAG_NO_CAM_COLLISION = (1 << 1)
|
||||
self.SURFACE_FLAG_X_PROJECTION = (1 << 3)
|
||||
|
||||
self.TERRAIN_LOAD_VERTICES = 0x0040
|
||||
self.TERRAIN_LOAD_CONTINUE = 0x0041
|
||||
self.TERRAIN_LOAD_END = 0x0042
|
||||
self.TERRAIN_LOAD_OBJECTS = 0x0043
|
||||
self.TERRAIN_LOAD_ENVIRONMENT = 0x0044
|
||||
|
||||
self.TERRAIN_GRASS = 0x0000
|
||||
self.TERRAIN_STONE = 0x0001
|
||||
self.TERRAIN_SNOW = 0x0002
|
||||
self.TERRAIN_SAND = 0x0003
|
||||
self.TERRAIN_SPOOKY = 0x0004
|
||||
self.TERRAIN_WATER = 0x0005
|
||||
self.TERRAIN_SLIDE = 0x0006
|
||||
self.TERRAIN_MASK = 0x0007
|
||||
|
||||
def SURFACE_IS_QUICKSAND(self, cmd):
|
||||
return (cmd >= 0x21 and cmd <= 0x28)
|
||||
def SURFACE_IS_NOT_HARD(self, cmd):
|
||||
return (cmd != self.SURFACE_HARD and not (cmd >= 0x35 and cmd <= 0x37))
|
||||
def SURFACE_IS_PAINTING_WARP(self, cmd):
|
||||
return (cmd >= 0xD3 and cmd < 0xFD)
|
||||
def TERRAIN_LOAD_IS_SURFACE_TYPE_LOW(self, cmd) :
|
||||
return (cmd < 0x40)
|
||||
def TERRAIN_LOAD_IS_SURFACE_TYPE_HIGH(self, cmd) :
|
||||
return (cmd >= 0x65)
|
||||
|
||||
enumSpecialType = [
|
||||
('special_null_start', 'Null Start', 'Null Start'),
|
||||
('special_yellow_coin', 'Yellow Coin', 'Yellow Coin'),
|
||||
('special_yellow_coin_2', 'Yellow Coin 2', 'Yellow Coin 2'),
|
||||
('special_unknown_3', 'Unknown 3', 'Unknown 3'),
|
||||
('special_boo', 'Boo', 'Boo'),
|
||||
('special_unknown_5', 'Unknown 5', 'Unknown 5'),
|
||||
('special_lll_moving_octagonal_mesh_platform',
|
||||
'LLL Moving Octagonal Platform', 'LLL Moving Octagonal Platform'),
|
||||
('special_snow_ball', 'Snow Ball', 'Snow Ball'),
|
||||
('special_lll_drawbridge_spawner', 'LLL Drawbridge Spawner',
|
||||
'LLL Drawbridge Spawner'),
|
||||
('special_empty_9', 'Empty 9', 'Empty 9'),
|
||||
('special_lll_rotating_block_with_fire_bars',
|
||||
'LLL Rotating Block With Fire Bars', 'LLL Rotating Block With Fire Bars'),
|
||||
('special_lll_floating_wood_bridge',
|
||||
'LLL Floating Wood Bridge', 'LLL Floating Wood Bridge'),
|
||||
('special_tumbling_platform', 'Tumbling Platform', 'Tumbling Platform'),
|
||||
('special_lll_rotating_hexagonal_ring', 'LLL Rotating Hexagonal Ring',
|
||||
'LLL Rotating Hexagonal Ring'),
|
||||
('special_lll_sinking_rectangular_platform',
|
||||
'LLL Sinking Rectangular Platform', 'LLL Sinking Rectangular Platform'),
|
||||
('special_lll_sinking_square_platforms', 'LLL Sinking Square Platforms',
|
||||
'LLL Sinking Square Platforms'),
|
||||
('special_lll_tilting_square_platform', 'LLL Tilting Square Platform',
|
||||
'LLL Tilting Square Platform'),
|
||||
('special_lll_bowser_puzzle', 'LLL Bowser Puzzle', 'LLL Bowser Puzzle'),
|
||||
('special_mr_i', 'Mr. I', 'Mr. I'),
|
||||
('special_small_bully', 'Small Bully', 'Small Bully'),
|
||||
('special_big_bully', 'Big Bully', 'Big Bully'),
|
||||
('special_empty_21', 'Empty 21', 'Empty 21'),
|
||||
('special_empty_22', 'Empty 22', 'Empty 22'),
|
||||
('special_empty_23', 'Empty 23', 'Empty 23'),
|
||||
('special_empty_24', 'Empty 24', 'Empty 24'),
|
||||
('special_empty_25', 'Empty 25', 'Empty 25'),
|
||||
('special_moving_blue_coin', 'Moving Blue Coin', 'Moving Blue Coin'),
|
||||
('special_jrb_chest', 'JRB Chest', 'JRB Chest'),
|
||||
('special_water_ring', 'Water Ring', 'Water Ring'),
|
||||
('special_mine', 'Mine', 'Mine'),
|
||||
('special_empty_30', 'Empty 30', 'Empty 30'),
|
||||
('special_empty_31', 'Empty 31', 'Empty 31'),
|
||||
('special_butterfly', 'Butterfly', 'Butterfly'),
|
||||
('special_bowser', 'Bowser', 'Bowser'),
|
||||
('special_wf_rotating_wooden_platform', 'WF Rotating Wooden Platform',
|
||||
'WF Rotating Wooden Platform'),
|
||||
('special_small_bomp', 'Small Bomp', 'Small Bomp'),
|
||||
('special_wf_sliding_platform', 'WF Sliding Platform', 'WF Sliding Platform'),
|
||||
('special_tower_platform_group', 'Tower Platform Group',
|
||||
'Tower Platform Group'),
|
||||
('special_rotating_counter_clockwise', 'Rotating Counter Clockwise',
|
||||
'Rotating Counter Clockwise'),
|
||||
('special_wf_tumbling_bridge', 'WF Tumbling Bridge', 'WF Tumbling Bridge'),
|
||||
('special_large_bomp', 'Large Bomp', 'Large Bomp' ),
|
||||
('special_level_geo_03', 'Level Geo 03', 'Level Geo 03'),
|
||||
('special_level_geo_04', 'Level Geo 04', 'Level Geo 04'),
|
||||
('special_level_geo_05', 'Level Geo 05', 'Level Geo 05'),
|
||||
('special_level_geo_06', 'Level Geo 06', 'Level Geo 06'),
|
||||
('special_level_geo_07', 'Level Geo 07', 'Level Geo 07'),
|
||||
('special_level_geo_08', 'Level Geo 08', 'Level Geo 08'),
|
||||
('special_level_geo_09', 'Level Geo 09', 'Level Geo 09'),
|
||||
('special_level_geo_0A', 'Level Geo 0A', 'Level Geo 0A'),
|
||||
('special_level_geo_0B', 'Level Geo 0B', 'Level Geo 0B'),
|
||||
('special_level_geo_0C', 'Level Geo 0C', 'Level Geo 0C'),
|
||||
('special_level_geo_0D', 'Level Geo 0D', 'Level Geo 0D'),
|
||||
('special_level_geo_0E', 'Level Geo 0E', 'Level Geo 0E'),
|
||||
('special_level_geo_0F', 'Level Geo 0F', 'Level Geo 0F'),
|
||||
('special_level_geo_10', 'Level Geo 10', 'Level Geo 10'),
|
||||
('special_level_geo_11', 'Level Geo 11', 'Level Geo 11'),
|
||||
('special_level_geo_12', 'Level Geo 12', 'Level Geo 12'),
|
||||
('special_level_geo_13', 'Level Geo 13', 'Level Geo 13'),
|
||||
('special_level_geo_14', 'Level Geo 14', 'Level Geo 14'),
|
||||
('special_level_geo_15', 'Level Geo 15', 'Level Geo 15'),
|
||||
('special_level_geo_16', 'Level Geo 16', 'Level Geo 16'),
|
||||
('special_bubble_tree', 'bubble_tree', 'bubble_tree'),
|
||||
('special_spiky_tree', 'Spiky Tree', 'Spiky Tree'),
|
||||
('special_snow_tree', 'Snow Tree', 'Snow Tree'),
|
||||
('special_unknown_tree', 'Unknown Tree', 'Unknown Tree'),
|
||||
('special_palm_tree', 'Palm Tree', 'Palm Tree'),
|
||||
('special_wooden_door', 'Wooden Door', 'Wooden Door'),
|
||||
('special_haunted_door', 'Haunted Door', 'Haunted Door'),
|
||||
('special_unknown_door', 'Unknown Door', 'Unknown Door'),
|
||||
('special_metal_door', 'Metal Door', 'Metal Door'),
|
||||
('special_hmc_door', 'HMC Door', 'HMC Door'),
|
||||
('special_unknown2_door', 'Unknown 2 Door', 'Unknown 2 Door'),
|
||||
('special_wooden_door_warp', 'Wooden Door Warp', 'Wooden Door Warp'),
|
||||
('special_unknown1_door_warp', 'Unknown 1 Door Warp', 'Unknown 1 Door Warp'),
|
||||
('special_metal_door_warp', 'Metal Door Warp', 'Metal Door Warp'),
|
||||
('special_unknown2_door_warp', 'Unknown 2 Door Warp', 'Unknown 2 Door Warp'),
|
||||
('special_unknown3_door_warp', 'Unknown 3 Door Warp', 'Unknown 3 Door Warp'),
|
||||
('special_castle_door_warp', 'Castle Door Warp', 'Castle Door Warp'),
|
||||
('special_castle_door', 'Castle Door', 'Castle Door'),
|
||||
('special_0stars_door', '0 Stars Door', '0 Stars Door'),
|
||||
('special_1star_door', '1 Star Door', '1 Star Door'),
|
||||
('special_3star_door', '3 Star Door', '3 Star Door'),
|
||||
('special_key_door', 'Key Door', 'Key Door'),
|
||||
('special_null_end', 'Null End', 'Null End'),
|
||||
]
|
||||
|
||||
enumObjectType = [
|
||||
('None', 'None', 'None'),
|
||||
('Special', 'Special', 'Special'),
|
||||
('Water Box', 'Water Box', 'Water Box'),
|
||||
]
|
||||
|
||||
enumCollisionTypeSimple = [
|
||||
('SURFACE_DEFAULT','Default','Default'),
|
||||
('SURFACE_BURNING','Burning','Burning'),
|
||||
('SURFACE_HANGABLE','Hangable','Hangable'),
|
||||
('SURFACE_SLOW','Slow','Slow'),
|
||||
('SURFACE_DEATH_PLANE','Death Plane','Death Plane'),
|
||||
('SURFACE_WATER','Water','Water'),
|
||||
('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'),
|
||||
('SURFACE_INTANGIBLE','Intangible','Intangible'),
|
||||
('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'),
|
||||
('SURFACE_SLIPPERY','Slippery','Slippery'),
|
||||
('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'),
|
||||
('SURFACE_TTM_VINES','Vines','Vines'),
|
||||
('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'),
|
||||
('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'),
|
||||
('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'),
|
||||
('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'),
|
||||
('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'),
|
||||
('SURFACE_QUICKSAND','Quicksand','Quicksand'),
|
||||
('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'),
|
||||
('SURFACE_WALL_MISC','Wall Misc','Wall Misc'),
|
||||
('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'),
|
||||
('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'),
|
||||
('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'),
|
||||
('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'),
|
||||
('SURFACE_ICE','Ice','Ice'),
|
||||
('SURFACE_HARD','Hard','Hard'),
|
||||
('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'),
|
||||
('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'),
|
||||
('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'),
|
||||
('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'),
|
||||
('SURFACE_SWITCH','Switch','Switch'),
|
||||
('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'),
|
||||
]
|
||||
|
||||
enumCollisionType = [
|
||||
('SURFACE_DEFAULT','Default','Default'),
|
||||
('SURFACE_BURNING','Burning','Burning'),
|
||||
('SURFACE_0004','0004','0004'),
|
||||
('SURFACE_HANGABLE','Hangable','Hangable'),
|
||||
('SURFACE_SLOW','Slow','Slow'),
|
||||
('SURFACE_DEATH_PLANE','Death Plane','Death Plane'),
|
||||
('SURFACE_CLOSE_CAMERA','Close Camera','Close Camera'),
|
||||
('SURFACE_WATER','Water','Water'),
|
||||
('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'),
|
||||
('SURFACE_INTANGIBLE','Intangible','Intangible'),
|
||||
('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'),
|
||||
('SURFACE_SLIPPERY','Slippery','Slippery'),
|
||||
('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'),
|
||||
('SURFACE_TTM_VINES','Vines','Vines'),
|
||||
('SURFACE_MGR_MUSIC','Music','Music'),
|
||||
('SURFACE_INSTANT_WARP_1B','Instant Warp 1B','Instant Warp 1B'),
|
||||
('SURFACE_INSTANT_WARP_1C','Instant Warp 1C','Instant Warp 1C'),
|
||||
('SURFACE_INSTANT_WARP_1D','Instant Warp 1D','Instant Warp 1D'),
|
||||
('SURFACE_INSTANT_WARP_1E','Instant Warp 1E','Instant Warp 1E'),
|
||||
('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'),
|
||||
('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'),
|
||||
('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'),
|
||||
('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'),
|
||||
('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'),
|
||||
('SURFACE_QUICKSAND','Quicksand','Quicksand'),
|
||||
('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'),
|
||||
('SURFACE_WALL_MISC','Wall Misc','Wall Misc'),
|
||||
('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'),
|
||||
('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'),
|
||||
('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'),
|
||||
('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'),
|
||||
('SURFACE_ICE','Ice','Ice'),
|
||||
('SURFACE_LOOK_UP_WARP','Look Up Warp','Look Up Warp'),
|
||||
('SURFACE_HARD','Hard','Hard'),
|
||||
('SURFACE_WARP','Warp','Warp'),
|
||||
('SURFACE_TIMER_START','Timer Start','Timer Start'),
|
||||
('SURFACE_TIMER_END','Timer End','Timer End'),
|
||||
('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'),
|
||||
('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'),
|
||||
('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'),
|
||||
('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'),
|
||||
('SURFACE_BOSS_FIGHT_CAMERA','Boss Fight Camera','Boss Fight Camera'),
|
||||
('SURFACE_CAMERA_FREE_ROAM','Camera Free Roam','Camera Free Roam'),
|
||||
('SURFACE_THI3_WALLKICK','Wallkick','Wallkick'),
|
||||
('SURFACE_CAMERA_PLATFORM','Camera Platform','Camera Platform'),
|
||||
('SURFACE_CAMERA_MIDDLE','Camera Middle','Camera Middle'),
|
||||
('SURFACE_CAMERA_ROTATE_RIGHT','Camera Rotate Right','Camera Rotate Right'),
|
||||
('SURFACE_CAMERA_ROTATE_LEFT','Camera Rotate Left','Camera Rotate Left'),
|
||||
('SURFACE_CAMERA_BOUNDARY','Camera Boundary','Camera Boundary'),
|
||||
('SURFACE_NOISE_VERY_SLIPPERY_73','Noise Very Slippery 73','Noise Very Slippery 73'),
|
||||
('SURFACE_NOISE_VERY_SLIPPERY_74','Noise Very Slippery 74','Noise Very Slippery 74'),
|
||||
('SURFACE_NOISE_VERY_SLIPPERY','Noise Very Slippery','Noise Very Slippery'),
|
||||
('SURFACE_NO_CAM_COLLISION','No Cam Collision','No Cam Collision'),
|
||||
('SURFACE_NO_CAM_COLLISION_77','No Cam Collision 77','No Cam Collision 77'),
|
||||
('SURFACE_NO_CAM_COL_VERY_SLIPPERY','No Cam Collision Very Slippery','No Cam Collision Very Slippery'),
|
||||
('SURFACE_NO_CAM_COL_SLIPPERY','No Cam Collision Slippery','No Cam Collision Slippery'),
|
||||
('SURFACE_SWITCH','Switch','Switch'),
|
||||
('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'),
|
||||
('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'),
|
||||
('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'),
|
||||
('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'),
|
||||
('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'),
|
||||
('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'),
|
||||
('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'),
|
||||
('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'),
|
||||
('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'),
|
||||
('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'),
|
||||
('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'),
|
||||
('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'),
|
||||
('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'),
|
||||
('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'),
|
||||
('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'),
|
||||
('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'),
|
||||
('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'),
|
||||
('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'),
|
||||
('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'),
|
||||
('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'),
|
||||
('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'),
|
||||
('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'),
|
||||
('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'),
|
||||
('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'),
|
||||
('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'),
|
||||
('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'),
|
||||
('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'),
|
||||
('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'),
|
||||
('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'),
|
||||
('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'),
|
||||
('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'),
|
||||
('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'),
|
||||
('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'),
|
||||
('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'),
|
||||
('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'),
|
||||
('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'),
|
||||
('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'),
|
||||
('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'),
|
||||
('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'),
|
||||
('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'),
|
||||
('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'),
|
||||
('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'),
|
||||
('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'),
|
||||
('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'),
|
||||
('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'),
|
||||
('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'),
|
||||
('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'),
|
||||
('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'),
|
||||
('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'),
|
||||
('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'),
|
||||
('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'),
|
||||
('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'),
|
||||
('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'),
|
||||
('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'),
|
||||
('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'),
|
||||
('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'),
|
||||
('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'),
|
||||
('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'),
|
||||
('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'),
|
||||
('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'),
|
||||
('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'),
|
||||
('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'),
|
||||
('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'),
|
||||
('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'),
|
||||
('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'),
|
||||
('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'),
|
||||
('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'),
|
||||
('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'),
|
||||
('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'),
|
||||
('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'),
|
||||
('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'),
|
||||
('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'),
|
||||
('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'),
|
||||
('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'),
|
||||
('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'),
|
||||
('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'),
|
||||
('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'),
|
||||
('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'),
|
||||
('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'),
|
||||
('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'),
|
||||
('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'),
|
||||
('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'),
|
||||
('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'),
|
||||
('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'),
|
||||
('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'),
|
||||
('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'),
|
||||
('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'),
|
||||
('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'),
|
||||
('SURFACE_WOBBLING_WARP','Wobbling Warp','Wobbling Warp'),
|
||||
('SURFACE_TRAPDOOR','Trapdoor','Trapdoor'),
|
||||
]
|
||||
|
||||
enumPaintingCollisionType = [
|
||||
('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'),
|
||||
('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'),
|
||||
('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'),
|
||||
('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'),
|
||||
('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'),
|
||||
('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'),
|
||||
('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'),
|
||||
('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'),
|
||||
('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'),
|
||||
('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'),
|
||||
('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'),
|
||||
('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'),
|
||||
('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'),
|
||||
('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'),
|
||||
('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'),
|
||||
('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'),
|
||||
('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'),
|
||||
('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'),
|
||||
('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'),
|
||||
('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'),
|
||||
('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'),
|
||||
('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'),
|
||||
('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'),
|
||||
('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'),
|
||||
('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'),
|
||||
('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'),
|
||||
('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'),
|
||||
('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'),
|
||||
('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'),
|
||||
('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'),
|
||||
('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'),
|
||||
('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'),
|
||||
('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'),
|
||||
('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'),
|
||||
('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'),
|
||||
('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'),
|
||||
('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'),
|
||||
('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'),
|
||||
('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'),
|
||||
('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'),
|
||||
('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'),
|
||||
('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'),
|
||||
('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'),
|
||||
('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'),
|
||||
('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'),
|
||||
('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'),
|
||||
('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'),
|
||||
('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'),
|
||||
('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'),
|
||||
('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'),
|
||||
('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'),
|
||||
('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'),
|
||||
('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'),
|
||||
('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'),
|
||||
('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'),
|
||||
('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'),
|
||||
('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'),
|
||||
('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'),
|
||||
('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'),
|
||||
('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'),
|
||||
('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'),
|
||||
('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'),
|
||||
('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'),
|
||||
('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'),
|
||||
('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'),
|
||||
('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'),
|
||||
('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'),
|
||||
('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'),
|
||||
('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'),
|
||||
('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'),
|
||||
('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'),
|
||||
('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'),
|
||||
('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'),
|
||||
('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'),
|
||||
('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'),
|
||||
('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'),
|
||||
('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'),
|
||||
('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'),
|
||||
('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'),
|
||||
('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'),
|
||||
('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'),
|
||||
('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'),
|
||||
('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'),
|
||||
('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'),
|
||||
('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'),
|
||||
('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'),
|
||||
('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'),
|
||||
]
|
||||
|
||||
enumUnusedCollisionType = [
|
||||
('SURFACE_0004','0004','0004'),
|
||||
]
|
||||
@@ -0,0 +1,325 @@
|
||||
import mathutils
|
||||
|
||||
# RAM address used in evaluating switch for hatless Mario
|
||||
marioHatSwitch = 0x80277740
|
||||
marioLowPolySwitch = 0x80277150
|
||||
|
||||
marioFaceExpressionCount = 4
|
||||
|
||||
loadMarioMIO0 = 0x2ABCA0
|
||||
loadMarioGeo = 0x2ABCB8
|
||||
|
||||
# Full available rom interval for extended 0x04 bank (from SM64 Editor)
|
||||
marioFullRomInterval = (0x11D8930, 0x11FFF00)
|
||||
defaultExtendSegment4 = (0x11A35B8, 0x11FFF00)
|
||||
|
||||
mainLevelLoadScriptSegment = {
|
||||
0x15 : (0x2ABCA0, 0x2AC6B0)
|
||||
}
|
||||
|
||||
bank0Segment = {
|
||||
0x00 : (0x000000, 0x7FFFFF)
|
||||
}
|
||||
|
||||
# Segments for common geolayouts and Mario
|
||||
loadSegmentAddresses = {
|
||||
0x03 : 0x2ABCAC,
|
||||
0x04 : 0x2ABCA0,
|
||||
0x13 : 0x2ABCD0,
|
||||
0x16 : 0x2ABCC4,
|
||||
0x17 : 0x2ABCB8
|
||||
}
|
||||
|
||||
geoNodeRotateOrder = 'ZXY'
|
||||
|
||||
sm64ToBlenderScale = 0.0047
|
||||
marioScale = 0.25
|
||||
|
||||
sm64BoneUp = mathutils.Vector([1,0,0])
|
||||
|
||||
# Equivalent of euler(-90, -90, 0)
|
||||
blenderToSM64Rotation = mathutils.Matrix(
|
||||
[[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 0, 0]]
|
||||
)
|
||||
|
||||
blenderToSM64RotationLevel = mathutils.Matrix(
|
||||
[[1, 0, 0],
|
||||
[0, 0, 1],
|
||||
[0,-1, 0]]
|
||||
)
|
||||
|
||||
colorFormats = {
|
||||
'RGBA' : 0,
|
||||
'YUV' : 1,
|
||||
'CI' : 2,
|
||||
'IA' : 3,
|
||||
'I' : 4
|
||||
}
|
||||
|
||||
pixelBitSizes = {
|
||||
'4bit' : 0,
|
||||
'8bit' : 1,
|
||||
'16bit' : 2,
|
||||
'32bit' : 3
|
||||
}
|
||||
|
||||
lightIndices = [0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94]
|
||||
|
||||
# Color combinations
|
||||
# In SM64, environment alpha controls Mario's alpha.
|
||||
|
||||
# 2 cycle with specular lighting stored in primitive
|
||||
S_PHONG_TEX = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'ENVIRONMENT', '0',
|
||||
'PRIMITIVE', 'COMBINED', 'TEXEL0', 'COMBINED',
|
||||
'0', '0', '0', 'COMBINED']
|
||||
|
||||
# MULTIPLY TWO TEXTURES
|
||||
S_MULTIPLY = [
|
||||
'TEXEL0', '0', 'TEXEL1', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'TEXEL0', '0', 'TEXEL1', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT'
|
||||
]
|
||||
|
||||
S_PRIM_TRANSPARENT_SHADE = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'PRIMITIVE', '0',
|
||||
'TEXEL0', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'PRIMITIVE', '0']
|
||||
|
||||
# REGULAR SHADED TEXTURE
|
||||
S_SHADED_TEX = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'TEXEL0', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT']
|
||||
|
||||
# VERTEX COLOR
|
||||
S_VERTEX_COLORED_TEX = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'ENVIRONMENT', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'SHADE', '0',
|
||||
'ENVIRONMENT', '0', 'SHADE', '0']
|
||||
|
||||
S_SHADED_TEX_CUTOUT = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'ENVIRONMENT', '0',
|
||||
'TEXEL0', '0', 'SHADE', '0',
|
||||
'TEXEL0', '0', 'ENVIRONMENT', '0']
|
||||
|
||||
# UNLIT TEXTURE, USED FOR METAL MARIO
|
||||
S_UNLIT_TEX = ['0', '0', '0', 'TEXEL0',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'0', '0', '0', 'TEXEL0',
|
||||
'0', '0', '0', 'ENVIRONMENT']
|
||||
|
||||
# SM64 CUSTOM IMPORTER CC
|
||||
S_SHADED_TEX_NOALPHA = ['TEXEL0', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'SHADE',
|
||||
'TEXEL0', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'SHADE']
|
||||
|
||||
# SM64 BODY CC
|
||||
S_SHADED_SOLID = ['0', '0', '0', 'SHADE',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'0', '0', '0', 'SHADE',
|
||||
'0', '0', '0', 'ENVIRONMENT']
|
||||
|
||||
# SM64 BODY TEXTURES (FACE, SIDEBURNS, ETC.)
|
||||
S_UNLIT_DECAL_ON_SHADED_SOLID = ['TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE',
|
||||
'0', '0', '0', 'ENVIRONMENT']
|
||||
|
||||
# SHADED RANDOM NOISE
|
||||
S_SHADED_NOISE = ['NOISE', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT',
|
||||
'NOISE', '0', 'SHADE', '0',
|
||||
'0', '0', '0', 'ENVIRONMENT']
|
||||
|
||||
colorCombinationCommands = [
|
||||
0x03, #load lighting data
|
||||
0xB6, #clear geometry params
|
||||
0xB7, #set geometry params
|
||||
0xBB, #set texture scaling factor
|
||||
0xF3, #set texture size
|
||||
0xF5, #set texture properties
|
||||
0xF7, #set fill color
|
||||
0xF8, #set fog color
|
||||
0xFB, #set env color
|
||||
0xFC, #set color combination
|
||||
0xFD #load texture
|
||||
]
|
||||
|
||||
drawCommands = [
|
||||
0x04, #load vertex data
|
||||
0xBF #draw triangle
|
||||
]
|
||||
|
||||
originalMarioHeadROMInterval = (
|
||||
0x011B4F58,
|
||||
0x011B5710
|
||||
)
|
||||
|
||||
marioVanishOffsets = {
|
||||
"regular" : 0xB0C,
|
||||
"metal" : 0x9EC,
|
||||
}
|
||||
|
||||
commonGeolayoutPointers = {
|
||||
'Dorrie' : [2039136, 'HMC'],
|
||||
'Bowser' : [1809204, 'BFB'],
|
||||
'Bowser 2' : [1809328, 'BFB'],
|
||||
'Lakitu' : [1985520, 'CC']
|
||||
}
|
||||
|
||||
draw_layer_enums = [
|
||||
('1', 'Solid', '0x01'),
|
||||
('2', 'Solid Decal', '0x02'),
|
||||
('4', 'Transparent (No Blending)', '0x04'),
|
||||
('5', 'Transparent (Blending Front)', '0x05'),
|
||||
('6', 'Transparent (Blending Back)', '0x06'),
|
||||
]
|
||||
|
||||
level_enums = [
|
||||
("HH" , "Haunted House" , "HH" ),
|
||||
("CCM", "Cool Cool Mountain" , "CCM"),
|
||||
("IC" , "Inside Castle" , "IC" ),
|
||||
("HMC", "Hazy Maze Cave" , "HMC"),
|
||||
("SSL", "Shifting Sand Land" , "SSL"),
|
||||
("BOB", "Bob-Omb's Battlefield" , "BOB"),
|
||||
("SML", "Snow Man's land" , "SML"),
|
||||
("WDW", "Wet Dry World" , "WDW"),
|
||||
("JRB", "Jolly Roger Bay" , "JRB"),
|
||||
("THI", "Tiny Huge Island" , "THI"),
|
||||
("TTC", "Tick Tock Clock" , "TTC"),
|
||||
("RR" , "Rainbow Ride" , "RR" ),
|
||||
("CG" , "Castle Grounds" , "CG" ),
|
||||
("BFC", "Bowser First Course" , "BFC"),
|
||||
("VC" , "Vanish Cap" , "VC" ),
|
||||
("BFS", "Bowser's Fire Sea" , "BFS"),
|
||||
("SA" , "Secret Aquarium" , "SA" ),
|
||||
("BTC", "Bowser Third Course" , "BTC"),
|
||||
("LLL", "Lethal Lava Land" , "LLL"),
|
||||
("DDD", "Dire Dire Docks" , "DDD"),
|
||||
("WF" , "Whomp's Fortress" , "WF" ),
|
||||
("PIC", "Picture at the end" , "PIC"),
|
||||
("CC" , "Castle Courtyard" , "CC" ),
|
||||
("PSS", "Peach's Secret Slide" , "PSS"),
|
||||
("MC" , "Metal Cap" , "MC" ),
|
||||
("WC" , "Wing Cap" , "WC" ),
|
||||
("BFB", "Bowser First Battle" , "BFB"),
|
||||
("RC" , "Rainbow Clouds" , "RC" ),
|
||||
("BSB", "Bowser Second Battle" , "BSB"),
|
||||
("BTB", "Bowser Third Battle" , "BTB"),
|
||||
("TTM", "Tall Tall Mountain" , "TTM"),
|
||||
]
|
||||
|
||||
class SM64_CharacterImportData:
|
||||
def __init__(self, geoAddr, level, switchDict):
|
||||
self.geoAddr = geoAddr
|
||||
self.level = level
|
||||
self.switchDict = switchDict
|
||||
|
||||
character_enums = [
|
||||
("Mario" , "Mario" , "Mario" ),
|
||||
("Peach" , "Peach" , "Peach" ),
|
||||
#("Bowser" , "Bowser" , "Bowser" ),
|
||||
#("Koopa" , "Koopa" , "Koopa" ),
|
||||
#("Toad" , "Toad" , "Toad" )
|
||||
]
|
||||
|
||||
# switch index starts at 1.
|
||||
# switch option index starts at 0.
|
||||
sm64_character_data = {
|
||||
|
||||
"Mario" : SM64_CharacterImportData('12A784', 'CG', {
|
||||
1 : { # Low poly mario
|
||||
0 : 'Ignore'
|
||||
},
|
||||
2 : {
|
||||
1 : "Material", # metal mario
|
||||
2 : "Draw Layer", # vanish mario
|
||||
3 : "Material", # metal vanish mario
|
||||
},
|
||||
3 : { # cap vs no cap
|
||||
},
|
||||
4 : { # face animation (cap)
|
||||
0 : "Material",
|
||||
1 : "Material",
|
||||
2 : "Material",
|
||||
3 : "Material",
|
||||
4 : "Material",
|
||||
5 : "Material",
|
||||
6 : "Material",
|
||||
7 : "Material",
|
||||
},
|
||||
5 : { # face animation (no cap)
|
||||
0 : "Material",
|
||||
1 : "Material",
|
||||
2 : "Material",
|
||||
3 : "Material",
|
||||
4 : "Material",
|
||||
5 : "Material",
|
||||
6 : "Material",
|
||||
7 : "Material",
|
||||
},
|
||||
6 : { # right fist type
|
||||
# 0 = closed, 1 = open, 2 is something, 3-5 are same as 2
|
||||
3 : 'Ignore',
|
||||
4 : 'Ignore',
|
||||
5 : 'Ignore',
|
||||
},
|
||||
7 : { # left fist type
|
||||
# 0 = closed, 1 = open, 2 = peace, 3 = hold cap, 4 = hold cap wing
|
||||
},
|
||||
}),
|
||||
'Peach' : SM64_CharacterImportData('180950', 'CG', {
|
||||
1 : { # transparent peach
|
||||
1 : 'Draw Layer',
|
||||
},
|
||||
2 : { # 0-3 = face animation kissing, 4-7 = face animation normal
|
||||
0 : "Material",
|
||||
1 : "Material",
|
||||
2 : "Material",
|
||||
3 : "Material",
|
||||
4 : "Material",
|
||||
5 : "Material",
|
||||
6 : "Material",
|
||||
7 : "Material",
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
level_pointers = {
|
||||
"HH" : 0x2AC094,
|
||||
"CCM": 0x2AC0A8,
|
||||
"IC" : 0x2AC0BC,
|
||||
"HMC": 0x2AC0D0,
|
||||
"SSL": 0x2AC0E4,
|
||||
"BOB": 0x2AC0F8,
|
||||
"SML": 0x2AC10C,
|
||||
"WDW": 0x2AC120,
|
||||
"JRB": 0x2AC134,
|
||||
"THI": 0x2AC148,
|
||||
"TTC": 0x2AC15C,
|
||||
"RR" : 0x2AC170,
|
||||
"CG" : 0x2AC184,
|
||||
"BFC": 0x2AC198,
|
||||
"VC" : 0x2AC1AC,
|
||||
"BFS": 0x2AC1C0,
|
||||
"SA" : 0x2AC1D4,
|
||||
"BTC": 0x2AC1E8,
|
||||
"LLL": 0x2AC1FC,
|
||||
"DDD": 0x2AC210,
|
||||
"WF" : 0x2AC224,
|
||||
"PIC": 0x2AC238,
|
||||
"CC" : 0x2AC24C,
|
||||
"PSS": 0x2AC260,
|
||||
"MC" : 0x2AC274,
|
||||
"WC" : 0x2AC288,
|
||||
"BFB": 0x2AC29C,
|
||||
"RC" : 0x2AC2B0,
|
||||
"BSB": 0x2AC2C4,
|
||||
"BTB": 0x2AC2D8,
|
||||
"TTM": 0x2AC2EC,
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from .sm64_geolayout_utility import createBoneGroups, addBoneToGroup
|
||||
from .utility import prop_split
|
||||
|
||||
enumBoneType = [
|
||||
("Switch", "Switch (0x0E)", "Switch"),
|
||||
("Start", "Start (0x0B)", "Start"),
|
||||
("TranslateRotate", "Translate Rotate (0x10)", "Translate Rotate"),
|
||||
("Translate", "Translate (0x11)", "Translate"),
|
||||
("Rotate", "Rotate (0x12)", "Rotate"),
|
||||
("Billboard", "Billboard (0x14)", "Billboard"),
|
||||
("DisplayList", "Display List (0x15)", "Display List"),
|
||||
("Shadow", "Shadow (0x16)", "Shadow"),
|
||||
("Function", "Function (0x18)", "Function"),
|
||||
("HeldObject", "Held Object (0x1C)", "Held Object"),
|
||||
("Scale", "Scale (0x1D)", "Scale"),
|
||||
("StartRenderArea", "Start Render Area (0x20)", "Start Render Area"),
|
||||
("Ignore", "Ignore", "Ignore bones when exporting."),
|
||||
("SwitchOption", "Switch Option", "Switch Option"),
|
||||
("DisplayListWithOffset", "Display List With Offset (0x13)",
|
||||
"Display List With Offset"),
|
||||
]
|
||||
|
||||
enumDrawLayers = [
|
||||
('0', 'Background (0x00)', 'Background'),
|
||||
('1', 'Opaque (0x01)', 'Opaque'),
|
||||
('2', 'Opaque Decal (0x02)', 'Opaque Decal'),
|
||||
('3', 'Opaque Intersecting (0x03)', 'Opaque Intersecting'),
|
||||
('4', 'Cutout (0x04)', 'Cutout'),
|
||||
('5', 'Transparent (0x05)', 'Transparent'),
|
||||
('6', 'Transparent Decal (0x06)', 'Transparent Decal'),
|
||||
('7', 'Transparent Intersecting (0x07)', 'Transparent Intersecting'),
|
||||
]
|
||||
|
||||
enumFieldLayout = [
|
||||
('0', 'Translate And Rotate', 'Translate And Rotate'),
|
||||
('1', 'Translate', 'Translate'),
|
||||
('2', 'Rotate', 'Rotate'),
|
||||
#('3', 'Rotate Y', 'Rotate Y'),
|
||||
# Rotate Y complicates exporting code, so we treat it as Rotate.
|
||||
]
|
||||
|
||||
enumShadowType = [
|
||||
('0', 'Circle Scalable (9 verts)', 'Circle Scalable (9 verts)'),
|
||||
('1', 'Circle Scalable (4 verts)', 'Circle Scalable (4 verts)'),
|
||||
('2', 'Circle Permanent (4 verts)', 'Circle Permanent (4 verts)'),
|
||||
('10', 'Square Permanent', 'Square Permanent'),
|
||||
('11', 'Square Scalable', 'Square Scalable'),
|
||||
('12', 'Square Togglable', 'Square Togglable'),
|
||||
]
|
||||
|
||||
enumSwitchOptions = [
|
||||
('Mesh', 'Mesh Override', 'Switch to a different mesh hierarchy.'),
|
||||
('Material', 'Material Override', 'Use the same mesh hierarchy, but override material on ALL meshes. Optionally override draw layer.'),
|
||||
('Draw Layer', 'Draw Layer Override', 'Override draw layer only.'),
|
||||
]
|
||||
|
||||
enumMatOverrideOptions = [
|
||||
("All", 'All', 'Override every material with this one.'),
|
||||
("Specific", 'Specific', 'Only override instances of give material.'),
|
||||
]
|
||||
|
||||
def drawGeoInfo(panel, bone):
|
||||
|
||||
panel.layout.box().label(text = 'Geolayout Inspector')
|
||||
if bone is None:
|
||||
panel.layout.label(text = 'Edit geolayout properties in Pose mode.')
|
||||
return
|
||||
|
||||
col = panel.layout.column()
|
||||
|
||||
prop_split(col, bone, 'geo_cmd', 'Geolayout Command')
|
||||
|
||||
if bone.geo_cmd in ['TranslateRotate', 'Translate', 'Rotate',
|
||||
'Billboard', 'DisplayList', 'Scale', 'DisplayListWithOffset']:
|
||||
prop_split(col, bone, 'draw_layer', 'Draw layer')
|
||||
|
||||
if bone.geo_cmd == 'Scale':
|
||||
prop_split(col, bone, 'geo_scale', 'Scale')
|
||||
|
||||
if bone.geo_cmd == 'HeldObject':
|
||||
prop_split(col, bone, 'geo_func', 'Function')
|
||||
|
||||
if bone.geo_cmd == 'Switch':
|
||||
prop_split(col, bone, 'geo_func', 'Function')
|
||||
prop_split(col, bone, 'func_param', 'Parameter')
|
||||
col.label(text = 'Switch Option 0 is always this bone\'s children.')
|
||||
col.operator(AddSwitchOption.bl_idname).option = len(bone.switch_options)
|
||||
for i in range(len(bone.switch_options)):
|
||||
drawSwitchOptionProperty(col, bone.switch_options[i], i)
|
||||
|
||||
if bone.geo_cmd == 'Function':
|
||||
prop_split(col, bone, 'geo_func', 'Function')
|
||||
prop_split(col, bone, 'func_param', 'Parameter')
|
||||
infoBox2 = col.box()
|
||||
infoBox2.label(text = 'This affects the next sibling bone in ' +\
|
||||
'alphabetical order.')
|
||||
|
||||
'''
|
||||
if bone.geo_cmd == 'Function' or \
|
||||
bone.geo_cmd == 'Switch' or \
|
||||
bone.geo_cmd == 'HeldObject':
|
||||
infoBox = col.box()
|
||||
infoBox.label(text = 'For binary, this is a memory address.')
|
||||
infoBox.label(text = 'For C, this is a function name.')
|
||||
'''
|
||||
|
||||
if bone.geo_cmd == 'TranslateRotate':
|
||||
prop_split(col, bone, 'field_layout', 'Field Layout')
|
||||
|
||||
if bone.geo_cmd == 'Shadow':
|
||||
prop_split(col, bone, 'shadow_type', 'Type')
|
||||
prop_split(col, bone, 'shadow_solidity', 'Alpha')
|
||||
prop_split(col, bone, 'shadow_scale', 'Scale')
|
||||
|
||||
if bone.geo_cmd == 'StartRenderArea':
|
||||
prop_split(col, bone, 'culling_radius', 'Culling Radius')
|
||||
|
||||
#if bone.geo_cmd == 'SwitchOption':
|
||||
# prop_split(col, bone, 'switch_bone', 'Switch Bone')
|
||||
|
||||
layerInfoBox = panel.layout.box()
|
||||
layerInfoBox.label(text =
|
||||
'Regular bones (0x13) are on armature layer 0.')
|
||||
layerInfoBox.label(text =
|
||||
'Other bones are on armature layer 1.')
|
||||
layerInfoBox.label(text =
|
||||
"'Ignore' bones are on any layer.")
|
||||
|
||||
class MaterialPointerProperty(bpy.types.PropertyGroup):
|
||||
material : bpy.props.PointerProperty(type = bpy.types.Material)
|
||||
|
||||
class SwitchOptionProperty(bpy.types.PropertyGroup):
|
||||
switchType : bpy.props.EnumProperty(name = 'Option Type',
|
||||
items = enumSwitchOptions)
|
||||
optionArmature : bpy.props.PointerProperty(name = 'Option Armature',
|
||||
type = bpy.types.Object)
|
||||
materialOverride : bpy.props.PointerProperty(type = bpy.types.Material,
|
||||
name = 'Material Override')
|
||||
materialOverrideType : bpy.props.EnumProperty(name = 'Material Override Type',
|
||||
items = enumMatOverrideOptions)
|
||||
specificOverrideArray : bpy.props.CollectionProperty(type = MaterialPointerProperty,
|
||||
name = 'Specified Materials To Override')
|
||||
specificIgnoreArray : bpy.props.CollectionProperty(type = MaterialPointerProperty,
|
||||
name = 'Specified Materials To Ignore')
|
||||
overrideDrawLayer : bpy.props.BoolProperty()
|
||||
drawLayer : bpy.props.EnumProperty(items = enumDrawLayers, name = 'Draw Layer')
|
||||
collapse : bpy.props.BoolProperty()
|
||||
|
||||
def drawSwitchOptionProperty(layout, switchOption, index):
|
||||
box = layout.box()
|
||||
#box.box().label(text = 'Switch Option ' + str(index + 1))
|
||||
box.prop(switchOption, 'collapse', text = 'Switch Option ' + \
|
||||
str(index + 1), icon = 'TRIA_DOWN' if not switchOption.collapse else \
|
||||
'TRIA_RIGHT')
|
||||
if not switchOption.collapse:
|
||||
prop_split(box, switchOption, 'switchType', 'Type')
|
||||
if switchOption.switchType == 'Material':
|
||||
prop_split(box, switchOption, 'materialOverride', 'Material')
|
||||
prop_split(box, switchOption, 'materialOverrideType',
|
||||
"Material Override Type")
|
||||
if switchOption.materialOverrideType == 'Specific':
|
||||
matArrayBox = box.box()
|
||||
matArrayBox.label(text = "Specified Materials To Override")
|
||||
drawMatArray(matArrayBox, index, switchOption,
|
||||
switchOption.specificOverrideArray, True)
|
||||
else:
|
||||
matArrayBox = box.box()
|
||||
matArrayBox.label(text = "Specified Materials To Ignore")
|
||||
drawMatArray(matArrayBox, index, switchOption,
|
||||
switchOption.specificIgnoreArray, False)
|
||||
prop_split(box, switchOption, 'overrideDrawLayer',
|
||||
"Override Draw Layer")
|
||||
if switchOption.overrideDrawLayer:
|
||||
prop_split(box, switchOption, 'drawLayer', 'Draw Layer')
|
||||
elif switchOption.switchType == 'Draw Layer':
|
||||
prop_split(box, switchOption, 'drawLayer', "Draw Layer")
|
||||
else:
|
||||
prop_split(box, switchOption, 'optionArmature', 'Option Armature')
|
||||
buttons = box.row(align = True)
|
||||
buttons.operator(RemoveSwitchOption.bl_idname,
|
||||
text = 'Remove Option').option = index
|
||||
buttons.operator(AddSwitchOption.bl_idname,
|
||||
text = 'Add Option').option = index + 1
|
||||
|
||||
moveButtons = box.row(align = True)
|
||||
moveUp = moveButtons.operator(MoveSwitchOption.bl_idname,
|
||||
text = 'Move Up')
|
||||
moveUp.option = index
|
||||
moveUp.offset = -1
|
||||
moveDown = moveButtons.operator(MoveSwitchOption.bl_idname,
|
||||
text = 'Move Down')
|
||||
moveDown.option = index
|
||||
moveDown.offset = 1
|
||||
|
||||
def drawMatArray(layout, option, switchOption, matArray, isSpecific):
|
||||
addOp = layout.operator(AddSwitchOptionMat.bl_idname, text = 'Add Material')
|
||||
addOp.option = option
|
||||
addOp.isSpecific = isSpecific
|
||||
|
||||
for i in range(len(matArray)):
|
||||
drawMatArrayProperty(layout, matArray[i], option, i, isSpecific)
|
||||
|
||||
def drawMatArrayProperty(layout, materialPointer, option, index, isSpecific):
|
||||
row = layout.box().row()
|
||||
row.prop(materialPointer, 'material', text = '')
|
||||
removeOp = row.operator(RemoveSwitchOptionMat.bl_idname, text = 'Remove Material')
|
||||
removeOp.option = option
|
||||
removeOp.index = index
|
||||
removeOp.isSpecific = isSpecific
|
||||
|
||||
class AddSwitchOptionMat(bpy.types.Operator):
|
||||
bl_idname = 'bone.add_switch_option_mat'
|
||||
bl_label = 'Add Switch Option Material'
|
||||
option : bpy.props.IntProperty()
|
||||
isSpecific : bpy.props.BoolProperty()
|
||||
def execute(self, context):
|
||||
bone = context.bone
|
||||
if self.isSpecific:
|
||||
bone.switch_options[self.option].specificOverrideArray.add()
|
||||
else:
|
||||
bone.switch_options[self.option].specificIgnoreArray.add()
|
||||
self.report({'INFO'}, 'Success!')
|
||||
return {'FINISHED'}
|
||||
|
||||
class RemoveSwitchOptionMat(bpy.types.Operator):
|
||||
bl_idname = 'bone.remove_switch_option_mat'
|
||||
bl_label = 'Remove Switch Option Material'
|
||||
option : bpy.props.IntProperty()
|
||||
index : bpy.props.IntProperty()
|
||||
isSpecific : bpy.props.BoolProperty()
|
||||
def execute(self, context):
|
||||
if self.isSpecific:
|
||||
context.bone.switch_options[self.option].specificOverrideArray.remove(self.index)
|
||||
else:
|
||||
context.bone.switch_options[self.option].specificIgnoreArray.remove(self.index)
|
||||
self.report({'INFO'}, 'Success!')
|
||||
return {'FINISHED'}
|
||||
|
||||
class GeolayoutBonePanel(bpy.types.Panel):
|
||||
bl_label = "Geolayout Inspector"
|
||||
bl_idname = "SM64_Geolayout_Inspector"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "bone"
|
||||
bl_options = {'HIDE_HEADER'}
|
||||
|
||||
#@classmethod
|
||||
#def poll(cls, context):
|
||||
# return (context.bone is not None)
|
||||
|
||||
def draw(self, context):
|
||||
drawGeoInfo(self, context.bone)
|
||||
|
||||
class AddSwitchOption(bpy.types.Operator):
|
||||
bl_idname = 'bone.add_switch_option'
|
||||
bl_label = 'Add Switch Option'
|
||||
option : bpy.props.IntProperty()
|
||||
def execute(self, context):
|
||||
bone = context.bone
|
||||
bone.switch_options.add()
|
||||
bone.switch_options.move(len(bone.switch_options)-1, self.option)
|
||||
self.report({'INFO'}, 'Success!')
|
||||
return {'FINISHED'}
|
||||
|
||||
class RemoveSwitchOption(bpy.types.Operator):
|
||||
bl_idname = 'bone.remove_switch_option'
|
||||
bl_label = 'Remove Switch Option'
|
||||
option : bpy.props.IntProperty()
|
||||
def execute(self, context):
|
||||
context.bone.switch_options.remove(self.option)
|
||||
self.report({'INFO'}, 'Success!')
|
||||
return {'FINISHED'}
|
||||
|
||||
class MoveSwitchOption(bpy.types.Operator):
|
||||
bl_idname = 'bone.move_switch_option'
|
||||
bl_label = 'Move Switch Option'
|
||||
option : bpy.props.IntProperty()
|
||||
offset : bpy.props.IntProperty()
|
||||
def execute(self, context):
|
||||
bone = context.bone
|
||||
bone.switch_options.move(self.option, self.option + self.offset)
|
||||
self.report({'INFO'}, 'Success!')
|
||||
return {'FINISHED'}
|
||||
|
||||
'''
|
||||
class GeolayoutBoneSidePanel(bpy.types.Panel):
|
||||
bl_idname = "SM64_Geolayout_Inspector_Side"
|
||||
bl_label = "SM64 Geolayout Inspector"
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = 'Item'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_bones is not None and \
|
||||
len(context.selected_bones) > 0
|
||||
|
||||
def draw(self, context):
|
||||
drawGeoInfo(self, context.selected_bones[0])
|
||||
'''
|
||||
|
||||
def getSwitchOptionBone(switchArmature):
|
||||
optionBones = []
|
||||
for poseBone in switchArmature.pose.bones:
|
||||
if poseBone.bone_group is not None and \
|
||||
poseBone.bone_group.name == 'SwitchOption':
|
||||
optionBones.append(poseBone.name)
|
||||
if len(optionBones) > 1:
|
||||
raise ValueError("There should only be one switch option bone in " +\
|
||||
switchArmature.name + '.')
|
||||
elif len(optionBones) < 1:
|
||||
raise ValueError("Could not find a switch option bone in " +\
|
||||
switchArmature.name + ', which should be the root bone in the hierarchy.')
|
||||
return optionBones[0]
|
||||
|
||||
def updateBone(self, context):
|
||||
if not hasattr(context, 'bone'):
|
||||
print("No bone in context.")
|
||||
return
|
||||
armatureObj = context.object
|
||||
|
||||
createBoneGroups(armatureObj)
|
||||
if context.bone.geo_cmd != 'DisplayListWithOffset':
|
||||
addBoneToGroup(armatureObj, context.bone.name, context.bone.geo_cmd)
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
else:
|
||||
addBoneToGroup(armatureObj, context.bone.name, None)
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
|
||||
classes = (
|
||||
GeolayoutBonePanel,
|
||||
#GeolayoutBoneSidePanel
|
||||
AddSwitchOption,
|
||||
RemoveSwitchOption,
|
||||
AddSwitchOptionMat,
|
||||
RemoveSwitchOptionMat,
|
||||
MoveSwitchOption,
|
||||
MaterialPointerProperty,
|
||||
SwitchOptionProperty,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
bpy.types.Bone.geo_cmd = bpy.props.EnumProperty(
|
||||
name = 'Geolayout Command', items = enumBoneType,
|
||||
default = 'DisplayListWithOffset', update = updateBone)
|
||||
|
||||
bpy.types.Bone.draw_layer = bpy.props.EnumProperty(
|
||||
name = 'Draw Layer', items = enumDrawLayers, default = '1')
|
||||
|
||||
# Scale
|
||||
bpy.types.Bone.geo_scale = bpy.props.FloatProperty(
|
||||
name = 'Scale', min = 2**(-16), max = 2**(16), default = 1)
|
||||
|
||||
# Function, HeldObject, Switch
|
||||
# 8027795C for HeldObject
|
||||
bpy.types.Bone.geo_func = bpy.props.StringProperty(
|
||||
name = 'Function', default = '',
|
||||
description = 'Name of function for C, hex address for binary.')
|
||||
|
||||
# Function
|
||||
bpy.types.Bone.func_param = bpy.props.IntProperty(
|
||||
name = 'Function Parameter', min = -2**(15), max = 2**(15) - 1, default = 0)
|
||||
|
||||
# TranslateRotate
|
||||
bpy.types.Bone.field_layout = bpy.props.EnumProperty(
|
||||
name = 'Field Layout', items = enumFieldLayout, default = '0')
|
||||
|
||||
# Shadow
|
||||
bpy.types.Bone.shadow_type = bpy.props.EnumProperty(
|
||||
name = 'Shadow Type', items = enumShadowType, default = '1')
|
||||
|
||||
bpy.types.Bone.shadow_solidity = bpy.props.FloatProperty(
|
||||
name = 'Shadow Alpha', min = 0, max = 1, default = 1)
|
||||
|
||||
bpy.types.Bone.shadow_scale = bpy.props.IntProperty(
|
||||
name = 'Shadow Scale', min = -2**(15), max = 2**(15) - 1, default = 100)
|
||||
|
||||
# StartRenderArea
|
||||
bpy.types.Bone.culling_radius = bpy.props.IntProperty(
|
||||
name = 'Culling Radius', min=-2**(15), max=2**(15)-1, default=2000)
|
||||
|
||||
#bpy.types.Bone.switch_bone = bpy.props.StringProperty(
|
||||
# name = 'Switch Bone')
|
||||
|
||||
bpy.types.Bone.switch_options = bpy.props.CollectionProperty(
|
||||
type = SwitchOptionProperty)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
#del bpy.types.Bone.geo_cmd
|
||||
#del bpy.types.Bone.draw_layer
|
||||
#del bpy.types.Bone.geo_scale
|
||||
#del bpy.types.Bone.geo_func
|
||||
#del bpy.types.Bone.func_param
|
||||
#del bpy.types.Bone.field_layout
|
||||
#del bpy.types.Bone.shadow_type
|
||||
#del bpy.types.Bone.shadow_solidity
|
||||
#del bpy.types.Bone.shadow_scale
|
||||
#del bpy.types.Bone.culling_radius
|
||||
@@ -0,0 +1,104 @@
|
||||
drawLayers = {
|
||||
'Unused' : [0, 3],
|
||||
'Solid' : 1,
|
||||
'Decal' : 2,
|
||||
'AlphaTest' : 4,
|
||||
'Blend' : 5,
|
||||
'BlendBehind' : 6
|
||||
}
|
||||
|
||||
GEO_BRANCH_STORE = 0x00
|
||||
GEO_END = 0x01
|
||||
GEO_BRANCH = 0x02
|
||||
GEO_RETURN = 0x03
|
||||
GEO_NODE_OPEN = 0x04
|
||||
GEO_NODE_CLOSE = 0x05
|
||||
GEO_SET_RENDER_AREA = 0x08
|
||||
GEO_SET_CAMERA_FRUSTRUM = 0x0A
|
||||
GEO_START = 0x0B
|
||||
GEO_SET_Z_BUF = 0x0C
|
||||
GEO_SET_RENDER_RANGE = 0x0D
|
||||
GEO_SWITCH = 0x0E
|
||||
GEO_TRANSLATE_ROTATE = 0x10
|
||||
GEO_TRANSLATE = 0x11
|
||||
GEO_ROTATE = 0x12
|
||||
GEO_LOAD_DL_W_OFFSET = 0x13
|
||||
GEO_BILLBOARD = 0x14
|
||||
GEO_LOAD_DL = 0x15
|
||||
GEO_START_W_SHADOW = 0x16
|
||||
GEO_SETUP_OBJ_RENDER = 0x17
|
||||
GEO_CALL_ASM = 0x18
|
||||
GEO_SET_BG = 0x19
|
||||
GEO_HELD_OBJECT = 0x1C
|
||||
GEO_NOP = [0x1A, 0x1E, 0x1F]
|
||||
GEO_SCALE = 0x1D
|
||||
GEO_START_W_RENDERAREA = 0x20
|
||||
|
||||
startCommands = [
|
||||
GEO_START,
|
||||
GEO_START_W_SHADOW,
|
||||
GEO_START_W_RENDERAREA
|
||||
]
|
||||
|
||||
nodeGroupCmds = [
|
||||
GEO_START,
|
||||
GEO_SWITCH,
|
||||
GEO_TRANSLATE_ROTATE,
|
||||
GEO_TRANSLATE,
|
||||
GEO_ROTATE,
|
||||
GEO_LOAD_DL_W_OFFSET,
|
||||
GEO_BILLBOARD,
|
||||
GEO_START_W_SHADOW,
|
||||
GEO_SCALE,
|
||||
GEO_START_W_RENDERAREA
|
||||
]
|
||||
|
||||
nodeDeformCmds = [
|
||||
GEO_TRANSLATE_ROTATE,
|
||||
GEO_TRANSLATE,
|
||||
GEO_ROTATE,
|
||||
GEO_LOAD_DL_W_OFFSET,
|
||||
GEO_BILLBOARD,
|
||||
GEO_LOAD_DL,
|
||||
GEO_SCALE,
|
||||
]
|
||||
|
||||
nodeDeformCmdsBoneGroups = [
|
||||
'TranslateRotate',
|
||||
'Translate',
|
||||
'Rotate',
|
||||
'Billboard',
|
||||
'DisplayList',
|
||||
'Scale'
|
||||
]
|
||||
|
||||
nodeCmds = [
|
||||
GEO_NODE_OPEN,
|
||||
#GEO_START,
|
||||
#GEO_START_W_SHADOW,
|
||||
#GEO_START_W_RENDERAREA,
|
||||
GEO_LOAD_DL,
|
||||
GEO_LOAD_DL_W_OFFSET,
|
||||
GEO_BRANCH,
|
||||
#GEO_SWITCH,
|
||||
#GEO_SCALE,
|
||||
#GEO_TRANSLATE_ROTATE
|
||||
]
|
||||
|
||||
geoCmdStatic = {
|
||||
0x04: [0x00, 0x01, 0x03, 0x04, 0x05, 0x09, 0x0B, 0x0C, 0x17, 0x20],
|
||||
0x08: [0x02, 0x0D, 0x0E, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19],
|
||||
0x0C: [0x08, 0x13, 0x1C],
|
||||
0x14: [0x0F],
|
||||
}
|
||||
|
||||
def getGeoLayoutCmdLength(byte0, byte1):
|
||||
for length, cmdList in geoCmdStatic.items():
|
||||
if byte0 in cmdList:
|
||||
return length
|
||||
|
||||
# handle variable length
|
||||
if byte0 in [0x0A]:
|
||||
return 0x08 if byte1 == 0 else 0x0C
|
||||
else:
|
||||
raise ValueError("Unhandled geolayout command: " + hex(byte0))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
import bpy
|
||||
|
||||
def getBoneGroupByName(armatureObj, name):
|
||||
for boneGroup in armatureObj.pose.bone_groups:
|
||||
if boneGroup.name == name:
|
||||
return boneGroup
|
||||
return None
|
||||
|
||||
def getBoneGroupIndex(armatureObj, name):
|
||||
index = 0
|
||||
for boneGroup in armatureObj.pose.bone_groups:
|
||||
if boneGroup.name == name:
|
||||
return index
|
||||
else:
|
||||
index += 1
|
||||
return -1
|
||||
|
||||
class BoneNodeProperties:
|
||||
def __init__(self, deform, theme):
|
||||
self.deform = deform
|
||||
self.theme = theme
|
||||
|
||||
# Only 0x13 commands are keyframe animated.
|
||||
# We want to ignore/prevent animations on these other nodes.
|
||||
boneNodeProperties = {
|
||||
"Switch" : BoneNodeProperties(False, 'THEME01'), #0xE
|
||||
"Start" : BoneNodeProperties(True, 'THEME12'), #0x0B
|
||||
"TranslateRotate" : BoneNodeProperties(True, 'THEME02'), #0x10
|
||||
"Translate" : BoneNodeProperties(True, 'THEME03'), #0x11
|
||||
"Rotate" : BoneNodeProperties(True, 'THEME04'), #0x12
|
||||
"Billboard" : BoneNodeProperties(True, 'THEME14'), #0x14
|
||||
"DisplayList" : BoneNodeProperties(True, 'THEME06'), #0x15
|
||||
"Shadow" : BoneNodeProperties(False, 'THEME07'), #0x16
|
||||
"Function" : BoneNodeProperties(False, 'THEME05'), #0x18
|
||||
"HeldObject" : BoneNodeProperties(False, 'THEME09'), #0x1C
|
||||
"Scale" : BoneNodeProperties(True, 'THEME10'), #0x1D
|
||||
"StartRenderArea" : BoneNodeProperties(True, 'THEME13'), #0x20
|
||||
"Ignore" : BoneNodeProperties(False, 'THEME08'), # Used for rigging
|
||||
|
||||
"SwitchOption" : BoneNodeProperties(False, 'THEME11')
|
||||
}
|
||||
|
||||
boneLayers = {
|
||||
'anim' : 0,
|
||||
'other' : 1,
|
||||
'meta' : 2,
|
||||
'visual' : 3
|
||||
}
|
||||
|
||||
def createBoneLayerMask(values):
|
||||
mask = [False] * 32
|
||||
for value in values:
|
||||
mask[value] = True
|
||||
return mask
|
||||
|
||||
def createBoneGroups(armatureObj):
|
||||
for (groupName, properties) in boneNodeProperties.items():
|
||||
if getBoneGroupByName(armatureObj, groupName) is None:
|
||||
boneGroup = armatureObj.pose.bone_groups.new(name = groupName)
|
||||
boneGroup.color_set = properties.theme
|
||||
|
||||
def addBoneToGroup(armatureObj, boneName, groupName):
|
||||
if groupName is None:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
posebone = armatureObj.pose.bones[boneName]
|
||||
bone = armatureObj.data.bones[boneName]
|
||||
posebone.bone_group = None
|
||||
bone.use_deform = True
|
||||
bone.layers = createBoneLayerMask([boneLayers['anim']])
|
||||
posebone.lock_location = (False, False, False)
|
||||
posebone.lock_rotation = (False, False, False)
|
||||
posebone.lock_scale = (False, False, False)
|
||||
return
|
||||
|
||||
elif groupName not in boneNodeProperties:
|
||||
raise ValueError("Bone group " + groupName + " doesn't exist.")
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
posebone = armatureObj.pose.bones[boneName]
|
||||
bone = armatureObj.data.bones[boneName]
|
||||
posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName)
|
||||
if groupName != 'Ignore':
|
||||
bone.use_deform = boneNodeProperties[groupName].deform
|
||||
if groupName != "DisplayList":
|
||||
bone.layers = createBoneLayerMask([boneLayers['other']])
|
||||
if groupName != "SwitchOption":
|
||||
posebone.lock_location = (True, True, True)
|
||||
posebone.lock_rotation = (True, True, True)
|
||||
posebone.lock_scale = (True, True, True)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
terrain_types = {
|
||||
'Normal A' : 0x00,
|
||||
'Normal B' : 0x01,
|
||||
'Snow' : 0x02,
|
||||
'Sand' : 0x03,
|
||||
'Haunted House' : 0x04,
|
||||
'Water Level' : 0x05,
|
||||
'Slippery Slide': 0x06,
|
||||
}
|
||||
|
||||
default_dialog_ID = {
|
||||
'BOB' : 0x00,
|
||||
'WF' : 0x1E,
|
||||
'BFC' : 0x5A,
|
||||
'VC' : 0x81,
|
||||
'MC' : 0x82,
|
||||
'WC' : 0x83,
|
||||
}
|
||||
|
||||
L_LOAD_RAW_JUMP0 = 0x00
|
||||
L_LOAD_RAW_JUMP1 = 0x01
|
||||
L_END = 0x02
|
||||
L_DELAY1 = 0x03
|
||||
L_DELAY2 = 0x04
|
||||
L_JUMP = 0x05
|
||||
L_PUSH = 0x06
|
||||
L_POP = 0x07
|
||||
L_PUSH_16 = 0x08
|
||||
L_POP_16 = 0x09
|
||||
L_PUSH_00 = 0x0A
|
||||
L_COND_POP = 0x0B
|
||||
L_COND_JUMP = 0x0C
|
||||
L_COND_PUSH = 0x0D
|
||||
L_COND_SKIP = 0x0E
|
||||
L_SKIP = 0x0F
|
||||
L_NOOP = 0x10
|
||||
L_SET_ACC_ASM = 0x11
|
||||
L_ACTIVE_SET_ACC = 0x12
|
||||
L_SET_ACC = 0x13
|
||||
L_POOL_PUSH = 0x14
|
||||
L_POOL_POP = 0x15
|
||||
L_LOAD_ROM_RAM = 0x16
|
||||
L_LOAD_ROM_SEG = 0x17
|
||||
L_LOAD_MIO0_SEG = 0x18
|
||||
L_MARIO_DEMO = 0x19
|
||||
L_LOAD_MIO0_TEX = 0x1A
|
||||
L_LOAD_START = 0x1B
|
||||
L_MEM_CLEANUP = 0x1C
|
||||
L_LOAD_END = 0x1D
|
||||
L_POOL_ALLOC = 0x1E
|
||||
L_AREA_START = 0x1F
|
||||
L_AREA_END = 0x20
|
||||
L_LOAD_POLY_WO_GEO = 0x21
|
||||
L_LOAD_POLY_W_GEO = 0x22
|
||||
L_23 = 0x23
|
||||
L_PLACE_OBJECT = 0x24
|
||||
L_LOAD_MARIO = 0x25
|
||||
L_WARP_CONNECT = 0x26
|
||||
L_WARP_PAINTING = 0x27
|
||||
L_WARP_AREA = 0x28
|
||||
L_29 = 0x29
|
||||
L_2A = 0x2A
|
||||
L_SET_DEFAULT_MARIO_POS = 0x2B
|
||||
L_2C = 0x2C
|
||||
L_2D = 0x2D
|
||||
L_LOAD_COLLISION = 0x2E
|
||||
L_RENDER_AREA = 0x2F
|
||||
L_SHOW_DIALOG = 0x30
|
||||
L_SET_DEFAULT_TERRAIN = 0x31
|
||||
L_NOOP2 = 0x32
|
||||
L_FADE_OVERLAY = 0x33
|
||||
L_BLACKOUT_SCREEN = 0x34
|
||||
L_35 = 0x35
|
||||
L_SET_MUSIC_SCREEN = 0x36
|
||||
L_SET_MUSIC_LEVEL = 0x37
|
||||
L_38 = 0x38
|
||||
L_PLACE_MACRO_OBJECT = 0x39
|
||||
L_3A = 0x3A
|
||||
L_JET_STREAM = 0x3B
|
||||
L_3C = 0x3C
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
import bpy
|
||||
import mathutils
|
||||
import copy
|
||||
|
||||
from math import pi
|
||||
|
||||
from .sm64_level_constants import *
|
||||
from .sm64_geolayout_constants import *
|
||||
from .f3d_parser import *
|
||||
from .utility import *
|
||||
|
||||
def parseLevelAtPointer(romfile, pointerAddress):
|
||||
segmentData = parseCommonSegmentLoad(romfile)
|
||||
|
||||
romfile.seek(pointerAddress)
|
||||
command = romfile.read(16)
|
||||
segment = command[3]
|
||||
segmentStart = int.from_bytes(command[4:8], 'big')
|
||||
segmentEnd = int.from_bytes(command[8:12], 'big')
|
||||
|
||||
segmentData[segment] = (segmentStart, segmentEnd)
|
||||
|
||||
startAddress = decodeSegmentedAddr(
|
||||
command[12:16], segmentData)
|
||||
|
||||
parsedLevel = parseLevel(romfile, startAddress, segmentData)
|
||||
for segment, interval in parsedLevel.segmentData.items():
|
||||
print("Segment " + format(segment, '#04x') + ': ' + hex(interval[0])\
|
||||
+ " - " + hex(interval[1]))
|
||||
|
||||
return parsedLevel
|
||||
|
||||
def parseCommonSegmentLoad(romfile):
|
||||
segmentData = copy.deepcopy(mainLevelLoadScriptSegment)
|
||||
for segment, pointer in loadSegmentAddresses.items():
|
||||
romfile.seek(pointer)
|
||||
command = romfile.read(12)
|
||||
|
||||
segment = command[3]
|
||||
segmentStart = int.from_bytes(command[4:8], 'big')
|
||||
segmentEnd = int.from_bytes(command[8:12], 'big')
|
||||
|
||||
segmentData[segment] = (segmentStart, segmentEnd)
|
||||
|
||||
return segmentData
|
||||
|
||||
# second byte = command length
|
||||
def parseLevel(romfile, startAddress, segmentData):
|
||||
currentAddress = startAddress
|
||||
|
||||
romfile.seek(currentAddress)
|
||||
currentCmd = romfile.read(2)
|
||||
romfile.seek(currentAddress) # second seek is because reading moves read pointer forward
|
||||
currentCmd = romfile.read(currentCmd[1])
|
||||
#currentAddress += currentCmd[1]
|
||||
|
||||
scriptStack = [currentAddress]
|
||||
currentLevel = SM64_Level()
|
||||
currentLevel.segmentData = segmentData
|
||||
currentArea = currentLevel.nonArea
|
||||
|
||||
# Note that this is only applicable for specific levels,
|
||||
# accessed through a jump table in the main level script.
|
||||
while len(scriptStack) > 0 and currentCmd[0] is not L_END:
|
||||
#print(bytesToHex(currentCmd) + " at " + hex(currentAddress))
|
||||
|
||||
if currentCmd[0] == L_JUMP:
|
||||
currentAddress = decodeSegmentedAddr(
|
||||
currentCmd[4:8], segmentData)
|
||||
|
||||
elif currentCmd[0] == L_PUSH:
|
||||
scriptStack.append(currentAddress)
|
||||
#print([hex(value) for value in scriptStack])
|
||||
currentAddress = decodeSegmentedAddr(
|
||||
currentCmd[4:8], segmentData)
|
||||
|
||||
elif currentCmd[0] == L_POP:
|
||||
currentAddress = scriptStack.pop()
|
||||
romfile.seek(currentAddress)
|
||||
currentCmd = romfile.read(2)
|
||||
romfile.seek(currentAddress)
|
||||
currentCmd = romfile.read(currentCmd[1])
|
||||
currentAddress += currentCmd[1]
|
||||
#print([hex(value) for value in scriptStack])
|
||||
|
||||
elif currentCmd[0] == L_NOOP or \
|
||||
currentCmd[0] == L_NOOP2:
|
||||
pass
|
||||
|
||||
elif currentCmd[0] == L_LOAD_ROM_SEG or\
|
||||
currentCmd[0] == L_LOAD_MIO0_SEG or\
|
||||
currentCmd[0] == L_LOAD_MIO0_TEX:
|
||||
segmentData[currentCmd[3]] = [
|
||||
int.from_bytes(currentCmd[4:8], 'big'),
|
||||
int.from_bytes(currentCmd[8:12], 'big')
|
||||
]
|
||||
|
||||
elif currentCmd[0] == L_AREA_START:
|
||||
if currentArea is not currentLevel.nonArea:
|
||||
raise ValueError("Nested areas not supported.")
|
||||
else:
|
||||
currentArea = SM64_Area(currentAddress, currentCmd)
|
||||
|
||||
elif currentCmd[0] == L_AREA_END:
|
||||
currentLevel.areas.append(currentArea)
|
||||
currentArea = currentLevel.nonArea
|
||||
|
||||
elif currentCmd[0] == L_LOAD_POLY_W_GEO or\
|
||||
currentCmd[0] == L_LOAD_POLY_WO_GEO:
|
||||
polygonData = SM64_Geometry(currentCmd, currentAddress)
|
||||
currentLevel.geometry.append(polygonData)
|
||||
|
||||
elif currentCmd[0] == L_PLACE_OBJECT:
|
||||
newObj = SM64_Object(currentCmd, currentAddress)
|
||||
currentArea.objects.append(newObj)
|
||||
|
||||
elif currentCmd[0] == L_WARP_CONNECT or\
|
||||
currentCmd[0] == L_WARP_PAINTING:
|
||||
warp = SM64_Warp(currentCmd, currentAddress)
|
||||
currentArea.warps.append(warp)
|
||||
|
||||
elif currentCmd[0] == L_WARP_AREA:
|
||||
areaWarp = SM64_Area_Warp(currentCmd, currentAddress)
|
||||
currentArea.areaWarps.append(areaWarp)
|
||||
|
||||
elif currentCmd[0] == L_SET_DEFAULT_MARIO_POS:
|
||||
marioPos = SM64_MarioStart(currentCmd, currentAddress)
|
||||
currentLevel.marioStartPosition = marioPos
|
||||
|
||||
elif currentCmd[0] == L_LOAD_COLLISION:
|
||||
col = SM64_Collider(currentCmd, currentAddress)
|
||||
currentArea.collider = col
|
||||
|
||||
elif currentCmd[0] == L_SHOW_DIALOG:
|
||||
dialog = SM64_Dialog_ID(currentCmd, currentAddress)
|
||||
currentArea.startDialogID = dialog
|
||||
|
||||
elif currentCmd[0] == L_SET_DEFAULT_TERRAIN:
|
||||
terrain = SM64_Default_Terrain(currentCmd, currentAddress)
|
||||
currentArea.defaultTerrainType = terrain
|
||||
|
||||
elif currentCmd[0] == L_SET_MUSIC_LEVEL:
|
||||
music = SM64_Music_Level(currentCmd, currentAddress)
|
||||
currentArea.music = music
|
||||
|
||||
elif currentCmd[0] == L_PLACE_MACRO_OBJECT:
|
||||
macroObj = SM64_Macro_Object(currentCmd, currentAddress)
|
||||
currentArea.macroObjects.append(macroObj)
|
||||
|
||||
elif currentCmd[0] == L_JET_STREAM:
|
||||
jetStream = SM64_Jet_Stream(currentCmd, currentAddress)
|
||||
currentArea.jetStreams.append(jetStream)
|
||||
|
||||
else:
|
||||
print("Unhandled command: " + hex(currentCmd[0]))
|
||||
|
||||
if currentCmd[0] != L_PUSH and currentCmd[0] != L_JUMP and currentCmd[0] != L_POP:
|
||||
currentAddress += currentCmd[1]
|
||||
romfile.seek(currentAddress)
|
||||
currentCmd = romfile.read(2)
|
||||
romfile.seek(currentAddress)
|
||||
currentCmd = romfile.read(currentCmd[1])
|
||||
#currentAddress += currentCmd[1]
|
||||
|
||||
return currentLevel
|
||||
|
||||
class SM64_Level:
|
||||
def __init__(self):
|
||||
self.segmentData = {}
|
||||
self.geometry = []
|
||||
self.areas = []
|
||||
self.marioStartPosition = None
|
||||
self.nonArea = SM64_Area(0, [0x00, 0x00, 0x00, 0x00])
|
||||
|
||||
class SM64_Area:
|
||||
def __init__(self, startAddress, command):
|
||||
self.objects = []
|
||||
self.warps = []
|
||||
self.areaWarps = []
|
||||
self.collider = None
|
||||
self.macroObjects = []
|
||||
self.startDialogID = None
|
||||
self.music = None
|
||||
self.defaultTerrainType = None
|
||||
self.marioStart = None
|
||||
self.startAddress = startAddress
|
||||
self.areaNum = command[2]
|
||||
self.geoAddress = command[4:8]
|
||||
self.jetStreams = []
|
||||
|
||||
class SM64_Music_Screen:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.seqNum = None
|
||||
self.params = None
|
||||
else:
|
||||
self.seqNum = command[5]
|
||||
self.params = command[2:5]
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_SET_MUSIC_SCREEN
|
||||
command[1] = 8
|
||||
command[2:5] = self.params
|
||||
command[5] = seqNum
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Music_Level:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.seqNum = None
|
||||
else:
|
||||
self.seqNum = command[3]
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_SET_MUSIC_LEVEL
|
||||
command[1] = 4
|
||||
command[3] = seqNum
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Geometry:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.geoCmd = None
|
||||
self.drawLayer = None
|
||||
self.modelID = None
|
||||
self.geoAddress = None
|
||||
else:
|
||||
self.geoCmd = command[0]
|
||||
self.drawLayer = command[2] >> 4
|
||||
self.modelID = command[3]
|
||||
self.geoAddress = command[4:8]
|
||||
self.address = address
|
||||
|
||||
#print('Level geo: ' + hex(int.from_bytes(self.geoAddress, 'big')))
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = self.geoCmd
|
||||
command[1] = 8
|
||||
command[2] = self.drawLayer << 4
|
||||
command[3] = self.modelID
|
||||
command[4:8] = self.collisionAddress
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Collider:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.collisionAddress = None
|
||||
self.address = None
|
||||
else:
|
||||
self.collisionAddress = command[4:8]
|
||||
self.address = address
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_LOAD_COLLISION
|
||||
command[1] = 8
|
||||
command[4:8] = self.collisionAddress
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Dialog_ID:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.ID = None
|
||||
self.address = None
|
||||
else:
|
||||
self.ID = command[3]
|
||||
self.address = address
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_SHOW_DIALOG
|
||||
command[1] = 4
|
||||
command[3] = self.ID
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Default_Terrain:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.terrainType = None
|
||||
self.address = None
|
||||
else:
|
||||
self.terrainType = command[3]
|
||||
self.address = address
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_SET_DEFAULT_TERRAIN
|
||||
command[1] = 4
|
||||
command[3] = self.terrainType
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Object:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.mask = None
|
||||
self.modelID = None
|
||||
self.position = [0,0,0]
|
||||
self.rotation = [0,0,0]
|
||||
self.behaviourParams = None
|
||||
self.behaviourAddress = None
|
||||
else:
|
||||
self.mask = command[2]
|
||||
self.modelID = command[3]
|
||||
self.position = readVectorFromShorts(command, 4)
|
||||
self.rotation = readEulerVectorFromShorts(command, 10)
|
||||
self.behaviourParams = command[16:20]
|
||||
self.behaviourAddress = command[20:24]
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(24)
|
||||
command[0] = L_PLACE_OBJECT
|
||||
command[1] = 0x18
|
||||
command[2] = self.mask
|
||||
command[3] = self.modelID
|
||||
writeVectorToShorts(command, 4, self.position)
|
||||
writeEulerVectorToShorts(command, 10, self.rotation)
|
||||
command[16:20] = self.behaviourParams
|
||||
command[20:24] = self.behaviourAddress
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Macro_Object:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.objPlacementListPointer = None
|
||||
#self.objectID = None
|
||||
#self.position = [0,0,0]
|
||||
else:
|
||||
self.objPlacementListPointer = command[4:8]
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = L_PLACE_MACRO_OBJECT
|
||||
command[1] = 8
|
||||
command[4:8] = self.objPlacementListPointer
|
||||
|
||||
|
||||
class SM64_Jet_Stream:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.position = None
|
||||
self.intensity = 0
|
||||
else:
|
||||
self.position = readVectorFromShorts(command, 4)
|
||||
self.intensity = readFloatFromShort(command, 10)
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self, command = None, address = None):
|
||||
command = bytearray(12)
|
||||
command[0] = L_JET_STREAM
|
||||
command[1] = 12
|
||||
writeVectorToShorts(command, 4, self.position)
|
||||
writeFloatToShort(command, 10, self.intensity)
|
||||
self.address = address
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Warp:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.warpCmd = None
|
||||
self.curWarpID = None
|
||||
self.destCourseID = None
|
||||
self.destCourseArea = None
|
||||
self.destWarpID = None
|
||||
else:
|
||||
self.warpCmd = command[0]
|
||||
self.curWarpID = command[2]
|
||||
self.destCourseID = command[3]
|
||||
self.destCourseArea = command[4]
|
||||
self.destWarpID = command[5]
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(8)
|
||||
command[0] = self.warpCmd
|
||||
command[1] = 8
|
||||
command[2] = self.curWarpID
|
||||
command[3] = self.destCourseID
|
||||
command[4] = self.destCourseArea
|
||||
command[5] = self.destWarpID
|
||||
|
||||
return command
|
||||
|
||||
class SM64_Area_Warp:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.collisionType = None
|
||||
self.courseAreaID = None
|
||||
self.teleportPosition = [0,0,0]
|
||||
else:
|
||||
self.collisionType = command[2]
|
||||
self.courseAreaID = command[3]
|
||||
self.teleportPosition = readVectorFromShorts(command, 4)
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(12)
|
||||
command[0] = L_WARP_AREA
|
||||
command[1] = 12
|
||||
command[2] = self.collisionType
|
||||
command[3] = self.courseAreaID
|
||||
writeVectorToShorts(command, 4, self.teleportPosition)
|
||||
|
||||
return command
|
||||
|
||||
class SM64_MarioStart:
|
||||
def __init__(self, command = None, address = None):
|
||||
if command is None:
|
||||
self.areaID = None
|
||||
self.position = (0,0,0)
|
||||
self.yRotation = 0
|
||||
else:
|
||||
self.areaID = command[2]
|
||||
self.yRotation = readEulerFloatFromShort(command, 4)
|
||||
self.position = readVectorFromShorts(command, 6)
|
||||
self.address = address
|
||||
|
||||
def to_microcode(self):
|
||||
command = bytearray(12)
|
||||
command[0] = L_SET_DEFAULT_MARIO_POS
|
||||
command[1] = 12
|
||||
writeEulerFloatToShort(command, 4, self.yRotation)
|
||||
writeVectorToShorts(command, 6, self.position)
|
||||
|
||||
return command
|
||||
@@ -0,0 +1,39 @@
|
||||
from .sm64_constants import loadSegmentAddresses
|
||||
|
||||
def ExtendBank0x04(romfile, segmentData, segment4):
|
||||
|
||||
# Extend bank 0x04
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 4)
|
||||
oldStart = int.from_bytes(romfile.read(4), 'big')
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 4)
|
||||
romfile.write(int.to_bytes(segment4[0], 4, 'big'))
|
||||
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 8)
|
||||
oldEnd = int.from_bytes(romfile.read(4), 'big')
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 8)
|
||||
romfile.write(int.to_bytes(segment4[1], 4, 'big'))
|
||||
|
||||
romfile.seek(oldStart)
|
||||
oldData = romfile.read(oldEnd - oldStart)
|
||||
|
||||
if oldEnd - oldStart > segment4[1] - segment4[0]:
|
||||
print("Not enough space to copy old data.")
|
||||
raise ValueError("Not enough space to copy old data.")
|
||||
|
||||
romfile.seek(segment4[0])
|
||||
romfile.write(oldData)
|
||||
|
||||
segmentData[0x04] = (segment4[0], segment4[1])
|
||||
|
||||
def DisableLowPolyMario(romfile, geoStartAddress):
|
||||
# disable low poly mario
|
||||
romfile.seek(geoStartAddress + 0x42)
|
||||
romfile.write(bytearray.fromhex('2E 18'))
|
||||
|
||||
def readSegment4(romfile, segmentData):
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 0x4)
|
||||
seg4start = int.from_bytes(romfile.read(4), 'big')
|
||||
romfile.seek(loadSegmentAddresses[0x04] + 0x8)
|
||||
seg4end = int.from_bytes(romfile.read(4), 'big')
|
||||
segmentData[0x04] = (seg4start, seg4end)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import bpy
|
||||
from math import pi, ceil, degrees, radians
|
||||
from mathutils import *
|
||||
from .sm64_constants import *
|
||||
from .sm64_geolayout_constants import *
|
||||
import random
|
||||
import string
|
||||
|
||||
def getAddressFromRAMAddress(RAMAddress):
|
||||
addr = RAMAddress - 0x80000000
|
||||
if addr < 0:
|
||||
raise ValueError("Invalid RAM address.")
|
||||
return addr
|
||||
|
||||
def getObjectQuaternion(obj):
|
||||
if obj.rotation_mode == 'QUATERNION':
|
||||
rotation = mathutils.Quaternion(obj.rotation_quaternion)
|
||||
elif obj.rotation_mode == 'AXIS_ANGLE':
|
||||
rotation = mathutils.Quaternion(obj.rotation_axis_angle)
|
||||
else:
|
||||
rotation = mathutils.Euler(
|
||||
obj.rotation_euler, obj.rotation_mode).to_quaternion()
|
||||
return rotation
|
||||
|
||||
def tempName(name):
|
||||
letters = string.digits
|
||||
return name + '_temp' + "".join(random.choice(letters) for i in range(10))
|
||||
|
||||
def prop_split(layout, data, field, name):
|
||||
split = layout.split(factor = 0.5)
|
||||
split.label(text = name)
|
||||
split.prop(data, field, text = '')
|
||||
|
||||
def toAlnum(name):
|
||||
if name == '':
|
||||
return None
|
||||
for i in range(len(name)):
|
||||
if not name[i].isalnum():
|
||||
name = name[:i] + '_' + name[i+1:]
|
||||
if name[0].isdigit():
|
||||
name = '_' + name
|
||||
return name
|
||||
|
||||
def get64bitAlignedAddr(address):
|
||||
endNibble = hex(address)[-1]
|
||||
if endNibble != '0' and endNibble != '8':
|
||||
address = ceil(address / 8) * 8
|
||||
return address
|
||||
|
||||
def getNameFromPath(path):
|
||||
index = len(path) - 1
|
||||
n = path[index]
|
||||
while n != '/' and n != '\\' and index > 0:
|
||||
index -= 1
|
||||
n = path[index]
|
||||
if index == 0 and n != '/' and n != '\\':
|
||||
return toAlnum(path)
|
||||
else:
|
||||
return toAlnum(path[index + 1:])
|
||||
|
||||
def gammaCorrect(color):
|
||||
return [
|
||||
gammaCorrectValue(color[0]),
|
||||
gammaCorrectValue(color[1]),
|
||||
gammaCorrectValue(color[2])]
|
||||
|
||||
def gammaCorrectValue(u):
|
||||
if u < 0.0031308:
|
||||
y = u * 12.92
|
||||
else:
|
||||
y = 1.055 * pow(u, (1/2.4)) - 0.055
|
||||
|
||||
return min(max(y, 0), 1)
|
||||
|
||||
def gammaInverse(color):
|
||||
return [
|
||||
gammaInverseValue(color[0]),
|
||||
gammaInverseValue(color[1]),
|
||||
gammaInverseValue(color[2])]
|
||||
|
||||
def gammaInverseValue(u):
|
||||
if u < 0.04045:
|
||||
y = u / 12.92
|
||||
else:
|
||||
y = ((u + 0.055) / 1.055) ** 2.4
|
||||
|
||||
return min(max(y, 0), 1)
|
||||
|
||||
def printBlenderMessage(msgSet, message, blenderOp):
|
||||
if blenderOp is not None:
|
||||
blenderOp.report(msgSet, message)
|
||||
else:
|
||||
print(message)
|
||||
|
||||
def bytesToInt(value):
|
||||
return int.from_bytes(value, 'big')
|
||||
|
||||
def bytesToHex(value, byteSize = 4):
|
||||
return format(bytesToInt(value), '#0' + str(byteSize * 2 + 2) + 'x')
|
||||
|
||||
def bytesToHexClean(value, byteSize = 4):
|
||||
return format(bytesToInt(value), '0' + str(byteSize * 2) + 'x')
|
||||
|
||||
def intToHex(value, byteSize = 4):
|
||||
return format(value, '#0' + str(byteSize * 2 + 2) + 'x')
|
||||
|
||||
def intToBytes(value, byteSize):
|
||||
return bytes.fromhex(intToHex(value, byteSize)[2:])
|
||||
|
||||
# byte input
|
||||
# returns an integer, usually used for file seeking positions
|
||||
def decodeSegmentedAddr(address, segmentData):
|
||||
#print(bytesAsHex(address))
|
||||
segmentStart = segmentData[address[0]][0]
|
||||
return segmentStart + bytesToInt(address[1:4])
|
||||
|
||||
#int input
|
||||
# returns bytes, usually used for writing new segmented addresses
|
||||
def encodeSegmentedAddr(address, segmentData):
|
||||
segment = getSegment(address, segmentData)
|
||||
segmentStart = segmentData[segment][0]
|
||||
|
||||
segmentedAddr = address - segmentStart
|
||||
return intToBytes(segment, 1) + intToBytes(segmentedAddr, 3)
|
||||
|
||||
def getSegment(address, segmentData):
|
||||
for segment, interval in segmentData.items():
|
||||
if address in range(*interval):
|
||||
return segment
|
||||
|
||||
raise ValueError("Address " + hex(address) + \
|
||||
" is not found in any of the provided segments.")
|
||||
|
||||
# Position
|
||||
def readVectorFromShorts(command, offset):
|
||||
return [readFloatFromShort(command, valueOffset) for valueOffset
|
||||
in range(offset, offset + 6, 2)]
|
||||
|
||||
def readFloatFromShort(command, offset):
|
||||
return int.from_bytes(command[offset: offset + 2],
|
||||
'big', signed = True) * sm64ToBlenderScale
|
||||
|
||||
def writeVectorToShorts(command, offset, values):
|
||||
for i in range(3):
|
||||
valueOffset = offset + i * 2
|
||||
writeFloatToShort(command, valueOffset, values[i])
|
||||
|
||||
def writeFloatToShort(command, offset, value):
|
||||
command[offset : offset + 2] = \
|
||||
int(round(value / sm64ToBlenderScale)).to_bytes(
|
||||
2, 'big', signed = True)
|
||||
|
||||
def convertFloatToShort(value):
|
||||
return int(round((value / sm64ToBlenderScale)))
|
||||
|
||||
def convertEulerFloatToShort(value):
|
||||
return int(round(degrees(value)))
|
||||
|
||||
# Rotation
|
||||
|
||||
# Rotation is stored as a short.
|
||||
# Zero rotation starts at Z+ on an XZ plane and goes counterclockwise.
|
||||
# 2**16 - 1 is the last value before looping around again.
|
||||
def readEulerVectorFromShorts(command, offset):
|
||||
return [readEulerFloatFromShort(command, valueOffset) for valueOffset
|
||||
in range(offset, offset + 6, 2)]
|
||||
|
||||
def readEulerFloatFromShort(command, offset):
|
||||
return radians(int.from_bytes(command[offset: offset + 2],
|
||||
'big', signed = True))
|
||||
|
||||
def writeEulerVectorToShorts(command, offset, values):
|
||||
for i in range(3):
|
||||
valueOffset = offset + i * 2
|
||||
writeEulerFloatToShort(command, valueOffset, values[i])
|
||||
|
||||
def writeEulerFloatToShort(command, offset, value):
|
||||
command[offset : offset + 2] = int(round(degrees(value))).to_bytes(
|
||||
2, 'big', signed = True)
|
||||
|
||||
# convert 32 bit (8888) to 16 bit (5551) color
|
||||
def convert32to16bitRGBA(oldPixel):
|
||||
if oldPixel[3] > 127:
|
||||
alpha = 1
|
||||
else:
|
||||
alpha = 0
|
||||
newPixel = (oldPixel[0] >> 3) << 11 |\
|
||||
(oldPixel[1] >> 3) << 6 |\
|
||||
(oldPixel[2] >> 3) << 1 |\
|
||||
alpha
|
||||
return newPixel.to_bytes(2, 'big')
|
||||
|
||||
# convert normalized RGB values to bytes (0-255)
|
||||
def convertRGB(normalizedRGB):
|
||||
return bytearray([
|
||||
int(normalizedRGB[0] * 255),
|
||||
int(normalizedRGB[1] * 255),
|
||||
int(normalizedRGB[2] * 255)
|
||||
])
|
||||
# convert normalized RGB values to bytes (0-255)
|
||||
def convertRGBA(normalizedRGBA):
|
||||
return bytearray([
|
||||
int(normalizedRGBA[0] * 255),
|
||||
int(normalizedRGBA[1] * 255),
|
||||
int(normalizedRGBA[2] * 255),
|
||||
int(normalizedRGBA[3] * 255)
|
||||
])
|
||||
|
||||
def vector3ComponentMultiply(a, b):
|
||||
return mathutils.Vector(
|
||||
(a.x * b.x, a.y * b.y, a.z * b.z)
|
||||
)
|
||||
|
||||
# Position values are signed shorts.
|
||||
def convertPosition(position):
|
||||
positionShorts = [int(floatValue) for floatValue in position]
|
||||
F3DPosition = bytearray(0)
|
||||
for shortData in [shortValue.to_bytes(2, 'big', signed=True) for shortValue in positionShorts]:
|
||||
F3DPosition.extend(shortData)
|
||||
return F3DPosition
|
||||
|
||||
# UVs in F3D are a fixed point short: s10.5 (hence the 2**5)
|
||||
# fixed point is NOT exponent+mantissa, it is integer+fraction
|
||||
def convertUV(normalizedUVs, textureWidth, textureHeight):
|
||||
#print(str(normalizedUVs[0]) + " - " + str(normalizedUVs[1]))
|
||||
F3DUVs = convertFloatToFixed16Bytes(normalizedUVs[0] * textureWidth) +\
|
||||
convertFloatToFixed16Bytes(normalizedUVs[1] * textureHeight)
|
||||
return F3DUVs
|
||||
|
||||
def convertFloatToFixed16Bytes(value):
|
||||
value *= 2**5
|
||||
value = min(max(value, -2**15), 2**15 - 1)
|
||||
|
||||
return int(round(value)).to_bytes(2, 'big', signed = True)
|
||||
|
||||
def convertFloatToFixed16(value):
|
||||
value *= 2**5
|
||||
value = min(max(value, -2**15), 2**15 - 1)
|
||||
return int.from_bytes(
|
||||
int(round(value)).to_bytes(2, 'big', signed = True), 'big')
|
||||
|
||||
|
||||
# Normal values are signed bytes (-128 to 127)
|
||||
# Normalized magnitude = 127
|
||||
def convertNormal(normal):
|
||||
F3DNormal = bytearray(0)
|
||||
for axis in normal:
|
||||
F3DNormal.extend(int(axis * 127).to_bytes(1, 'big', signed=True))
|
||||
return F3DNormal
|
||||
|
||||
def byteMask(data, offset, amount):
|
||||
return bitMask(data, offset * 8, amount * 8)
|
||||
def bitMask(data, offset, amount):
|
||||
return (~(-1 << amount) << offset & data) >> offset
|
||||
|
||||
def read16bitRGBA(data):
|
||||
r = bitMask(data, 11, 5) / ((2**5) - 1)
|
||||
g = bitMask(data, 6, 5) / ((2**5) - 1)
|
||||
b = bitMask(data, 1, 5) / ((2**5) - 1)
|
||||
a = bitMask(data, 0, 1) / ((2**1) - 1)
|
||||
|
||||
return [r,g,b,a]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
BIN
Binary file not shown.
Reference in New Issue
Block a user