diff --git a/Makefile b/Makefile index d3c364b..30f2353 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: nitrog3d nitrog3d: mkdir -p io_scene_g3d - cp __init__.py import_nsbmd.py utils.py io_scene_g3d + cp __init__.py import_nsbmd.py utils.py g3_commands.py io_scene_g3d zip -r nitrog3d.zip io_scene_g3d rm -rf io_scene_g3d diff --git a/g3_commands.py b/g3_commands.py new file mode 100644 index 0000000..2a32d8b --- /dev/null +++ b/g3_commands.py @@ -0,0 +1,191 @@ +from .utils import error, read32, np_fixed_to_float, fixed_to_float, to_rgb, vec10_to_vec +from enum import IntEnum +import numpy as np + +def parse_dl(data, size, report_func): + offset = 0 + display_list = [] + while offset < size: + commandData = read32(data, offset) + offset += 4 + commands, offset = parse_dl_command(data, offset, commandData, report_func) + display_list.append(commands) + return display_list + +def parse_dl_command(data, offset, commandData, report_func): + commands = [] + for i in range(4): + command = (commandData >> i * 8) & 0xFF + + if command == 0x00: + commands.append(DLCommandNoop()) + elif command == 0x10: + mode = MatrixMode(read32(data, offset)) + offset += 4 + commands.append(DLCommandMtxMode(mode)) + elif command == 0x11: + commands.append(DLCommandPushMtx()) + elif command == 0x12: + matrixId = read32(data, offset) + offset += 4 + commands.append(DLCommandPopMtx(matrixId)) + elif command == 0x13: + matrixId = read32(data, offset) + offset += 4 + commands.append(DLCommandStoreMtx(matrixId)) + elif command == 0x14: + matrixId = read32(data, offset) + offset += 4 + commands.append(DLCommandRestoreMtx(matrixId)) + elif command == 0x15: + commands.append(DLCommandIdentity()) + elif command == 0x16: + matrix = np_fixed_to_float(np.frombuffer(data[offset:offset + 64], dtype=np.dtype('uint32').newbyteorder('<')).reshape((4, 4))) + offset += 64 + commands.append(DLCommandLoadMtx44(matrix)) + elif command == 0x17: + matrix = np_fixed_to_float(np.frombuffer(data[offset:offset + 48], dtype=np.dtype('uint32').newbyteorder('<')).reshape((4, 3))) + offset += 48 + commands.append(DLCommandLoadMtx43(matrix)) + elif command == 0x18: + matrix = np_fixed_to_float(np.frombuffer(data[offset:offset + 64], dtype=np.dtype('uint32').newbyteorder('<')).reshape((4, 4))) + offset += 64 + commands.append(DLCommandMultMtx44(matrix)) + elif command == 0x19: + matrix = np_fixed_to_float(np.frombuffer(data[offset:offset + 48], dtype=np.dtype('uint32').newbyteorder('<')).reshape((4, 3))) + offset += 48 + commands.append(DLCommandMultMtx43(matrix)) + elif command == 0x1A: + matrix = np_fixed_to_float(np.frombuffer(data[offset:offset + 36], dtype=np.dtype('uint32').newbyteorder('<')).reshape((3, 3))) + offset += 36 + commands.append(DLCommandMultMtx33(matrix)) + elif command == 0x1B: + x = fixed_to_float(read32(data, offset)) + y = fixed_to_float(read32(data, offset + 4)) + z = fixed_to_float(read32(data, offset + 8)) + matrix = np.identity(4) + matrix[0, 0] = x + matrix[1, 1] = y + matrix[2, 2] = z + offset += 12 + commands.append(DLCommandScale(matrix)) + elif command == 0x1C: + x = fixed_to_float(read32(data, offset)) + y = fixed_to_float(read32(data, offset + 4)) + z = fixed_to_float(read32(data, offset + 8)) + matrix = np.identity(4) + matrix[0, 3] = x + matrix[1, 3] = y + matrix[2, 3] = z + offset += 12 + commands.append(DLCommandTranslate(matrix)) + elif command == 0x20: + color = read32(data, offset) + rgb = to_rgb(color) + offset += 4 + commands.append(DLCommandColor(rgb)) + elif command == 0x21: + normal = vec10_to_vec(read32(data, offset)) + offset += 4 + commands.append(DLCommandNormal(normal)) + elif command == 0x22: + texcoord = read32(data, offset) + s = fixed_to_float((texcoord & 0xFFFF) << 8) # unsure if this shift is correct + t = fixed_to_float(((texcoord >> 16) & 0xFFFF) << 8) + offset += 4 + commands.append(DLCommandTexcoord(s, t)) + #todo more commands + return commands, offset + +class MatrixMode(IntEnum): + PROJECTION = 0 + POSITION = 1 + POSITION_VECTOR = 2 + TEXTURE = 3 + +class DLCommand: + def __init__(self, commandId): + self.commandId = commandId + +class DLCommandNoop(DLCommand): + def __init__(self): + super().__init__(0x00) + +class DLCommandMtxMode(DLCommand): + def __init__(self, mode): + super().__init__(0x10) + self.mode = mode + +class DLCommandPushMtx(DLCommand): + def __init__(self): + super().__init__(0x11) + +class DLCommandPopMtx(DLCommand): + def __init__(self, matrixId): + super().__init__(0x12) + self.matrixId = matrixId + +class DLCommandStoreMtx(DLCommand): + def __init__(self, matrixId): + super().__init__(0x13) + self.matrixId = matrixId + +class DLCommandRestoreMtx(DLCommand): + def __init__(self, matrixId): + super().__init__(0x14) + self.matrixId = matrixId + +class DLCommandIdentity(DLCommand): + def __init__(self): + super().__init__(0x15) + +class DLCommandLoadMtx44(DLCommand): + def __init__(self, matrix): + super().__init__(0x16) + self.matrix = matrix + +class DLCommandLoadMtx43(DLCommand): + def __init__(self, matrix): + super().__init__(0x17) + self.matrix = matrix + +class DLCommandMultMtx44(DLCommand): + def __init__(self, matrix): + super().__init__(0x18) + self.matrix = matrix + +class DLCommandMultMtx43(DLCommand): + def __init__(self, matrix): + super().__init__(0x19) + self.matrix = matrix + +class DLCommandMultMtx33(DLCommand): + def __init__(self, matrix): + super().__init__(0x1A) + self.matrix = matrix + +class DLCommandScale(DLCommand): + def __init__(self, matrix): + super().__init__(0x1B) + self.matrix = matrix + +class DLCommandTranslate(DLCommand): + def __init__(self, matrix): + super().__init__(0x1C) + self.matrix = matrix + +class DLCommandColor(DLCommand): + def __init__(self, color): + super().__init__(0x20) + self.color = color + +class DLCommandNormal(DLCommand): + def __init__(self, normal): + super().__init__(0x21) + self.normal = normal + +class DLCommandTexcoord(DLCommand): + def __init__(self, s, t): + super().__init__(0x22) + self.s = s + self.t = t \ No newline at end of file diff --git a/import_nsbmd.py b/import_nsbmd.py index d662cb2..79252b7 100644 --- a/import_nsbmd.py +++ b/import_nsbmd.py @@ -1,6 +1,7 @@ from enum import IntEnum, IntFlag from os.path import isfile from .utils import read8, read16, read32, read_str, log, debug, parse_dictionary, fixed_to_float, to_rgb +from .g3_commands import parse_dl import numpy as np class ScalingRule(IntEnum): @@ -393,6 +394,33 @@ class NSBMDMaterial(): def add_palette_mat_data(self, palette_mat_data): self.paletteMatData.append(palette_mat_data) +class ShapeFlags(IntFlag): + USE_NORMAL = 0x0001 + USE_COLOR = 0x0002 + USE_TEXCOORD = 0x0004 + USE_RESTOREMTX = 0x0008 + +class NSBMDShape(): + def __init__(self, name): + self.name = name + self.useNormal = False + self.useColor = False + self.useTexCoord = False + self.useRestoreMtx = False + + def parse_flags(self, flags, report_func): + self.useNormal = (flags & ShapeFlags.USE_NORMAL) != 0 + log('Use normal: %s' % self.useNormal, report_func) + + self.useColor = (flags & ShapeFlags.USE_COLOR) != 0 + log('Use color: %s' % self.useColor, report_func) + + self.useTexCoord = (flags & ShapeFlags.USE_TEXCOORD) != 0 + log('Use tex coord: %s' % self.useTexCoord, report_func) + + self.useRestoreMtx = (flags & ShapeFlags.USE_RESTOREMTX) != 0 + log('Use restore mtx: %s' % self.useRestoreMtx, report_func) + class NSBMDModel(): def __init__(self, name): self.name = name @@ -541,11 +569,12 @@ class NSBMDImporter(): if material_value < matIdxDataEnd: matIdxDataEnd = material_value - dict_size = read16(materialset_data[offsetDictPlttToMat:], 2) + dict_size = read16(materialset_data[offsetDictPlttToMat:], 0x2) model.matIdxData = materialset_data[offsetDictPlttToMat + dict_size:matIdxDataEnd].tobytes() # no idea how this is used, but essential log('Material id data: %s' % model.matIdxData.hex(" "), self.report) for material_key, material_value in materialset_dictionary.items(): + log('%s: %08X' % (material_key, material_value), self.report) material = NSBMDMaterial(material_key) material_data = materialset_data[material_value:] diffAmb = read32(material_data, 0x04) @@ -646,8 +675,18 @@ class NSBMDImporter(): material_id = read8(pltt_mat_data, i) pltt_mat = NSBMDPaletteMaterialData(pltt_mat_key, material_id, pltt_mat_bound) model.materials[material_id].add_palette_mat_data(pltt_mat) - + shape_data = data[shape_offset:] + shape_dictionary = parse_dictionary(shape_data) + for shape_key, shape_value in shape_dictionary.items(): + log('%s: %08X' % (shape_key, shape_value), self.report) + shape = NSBMDShape(shape_key) + shape_item_data = shape_data[shape_value:] + shape_flags = read32(shape_item_data, 0x04) + shape.parse_flags(shape_flags, self.report) + shape_dl_offset = read32(shape_item_data, 0x08) + shape_dl_size = read32(shape_item_data, 0x0C) + shape.dlData = parse_dl(shape_data[shape_dl_offset:], shape_dl_size, self.report) #todo return nsbmd diff --git a/utils.py b/utils.py index 350047b..ac573b1 100644 --- a/utils.py +++ b/utils.py @@ -24,6 +24,9 @@ def read_dict_string(data, offset): def log(string, report_func): report_func(type={"INFO"}, message=string) +def error(string, report_func): + report_func(type={"ERROR"}, message=string) + def debug(string, report_func): report_func(type={"DEBUG"}, message=string) @@ -53,3 +56,9 @@ def fixed_to_float(value): def to_rgb(color): return (color & 0x1F, (color >> 5) & 0x1F, (color >> 10) & 0x1F) + +def np_fixed_to_float(value): + return (value / 4096.0).astype('float') + +def vec10_to_vec(value): + return (value & 0x3FF, (value >> 10) & 0x3FF, (value >> 20) & 0x3FF) \ No newline at end of file