class SceneGraph(WrapperModel): unknown0 = _attribute() children = _attribute(_list(SceneGraphNode.create_node)) def all_nodes(self): for child in self.children: yield child yield from child.all_descendants()
class Material(WrapperModel): block_info = BlockInfo() shader_info = ShaderInfo() def __init__(self, wrapped_object): super().__init__(wrapped_object) self.gl_program_table = {} name = _attribute() unknown0 = _attribute() cull_mode = _attribute() channel_count = _attribute() channels = _attribute(_list(Channel)) texcoord_generator_count = _attribute() texcoord_generators = _attribute(_list(TexCoordGenerator)) texture_matrices = _attribute(_list(TextureMatrix)) textures = ReferenceAttribute() tev_stage_count = _attribute() tev_stages = _attribute(_list(TevStage)) tev_colors = _attribute(_list()) tev_color_previous = _attribute() kcolors = _attribute(_list()) swap_tables = _attribute(_list(SwapTable)) indirect_stage_count = _attribute() indirect_stages = _attribute(_list(IndirectStage)) indirect_matrices = _attribute(_list(IndirectMatrix)) alpha_test = _attribute(AlphaTest) fog = _attribute() depth_test_early = _attribute() depth_mode = _attribute(DepthMode) blend_mode = _attribute(BlendMode) dither = _attribute() @property def enabled_channels(self): for i in range(self.channel_count): yield self.channels[i] @property def enabled_texcoord_generators(self): for i in range(self.texcoord_generator_count): yield self.texcoord_generators[i] @property def enabled_tev_stages(self): for i in range(self.tev_stage_count): yield self.tev_stages[i] @property def enabled_indirect_stages(self): for i in range(self.indirect_stage_count): yield self.indirect_stages[i] def handle_event(self, event, path): if isinstance(event, ValueChangedEvent): if path in self.block_info.trigger_table: block_property = self.block_info.trigger_table[path] block_property.update_block(self.gl_block, self) if path in self.shader_info.triggers: self.gl_shader_invalidate() super().handle_event(event, path) @LazyProperty def gl_block(self): block = self.gl_create_resource(self.block_info.block_type, GL_DYNAMIC_DRAW) for block_property in self.block_info.properties: block_property.update_block(block, self) return block def gl_program(self, transformation_type): if transformation_type in self.gl_program_table: return self.gl_program_table[transformation_type] vertex_shader_string = models.vertex_shader.create_shader_string( self, transformation_type) fragment_shader_string = models.fragment_shader.create_shader_string( self) vertex_shader = self.gl_create_resource(gl.Shader, GL_VERTEX_SHADER, vertex_shader_string) fragment_shader = self.gl_create_resource(gl.Shader, GL_FRAGMENT_SHADER, fragment_shader_string) program = self.gl_create_resource(gl.Program, vertex_shader, fragment_shader) self.gl_delete_resource(vertex_shader) self.gl_delete_resource(fragment_shader) glUseProgram(program) matrix_block_index = glGetUniformBlockIndex(program, b'MatrixBlock') glUniformBlockBinding(program, matrix_block_index, MATRIX_BLOCK_BINDING_POINT) material_block_index = glGetUniformBlockIndex(program, b'MaterialBlock') if material_block_index != GL_INVALID_INDEX: glUniformBlockBinding(program, material_block_index, MATERIAL_BLOCK_BINDING_POINT) program.matrix_index_location = glGetUniformLocation( program, 'matrix_index') #<-? matrix_table_location = glGetUniformLocation(program, 'matrix_table') if matrix_table_location != -1: glUniform1i(matrix_table_location, MATRIX_TABLE_TEXTURE_UNIT) for i in range(8): location = glGetUniformLocation(program, 'texmap{}'.format(i)) if location == -1: continue glUniform1i(location, TEXTURE_UNITS[i]) self.gl_program_table[transformation_type] = program return program def gl_shader_invalidate(self): for program in self.gl_program_table.values(): self.gl_delete_resource(program) self.gl_program_table.clear() @property def gl_cull_mode(self): if self.cull_mode == gx.CULL_FRONT: return GL_FRONT if self.cull_mode == gx.CULL_BACK: return GL_BACK if self.cull_mode == gx.CULL_ALL: return GL_FRONT_AND_BACK raise ValueError('Invalid cull mode: {}'.format(self.cull_mode)) @property def gl_depth_function(self): if self.depth_mode.function == gx.NEVER: return GL_NEVER if self.depth_mode.function == gx.LESS: return GL_LESS if self.depth_mode.function == gx.EQUAL: return GL_EQUAL if self.depth_mode.function == gx.LEQUAL: return GL_LEQUAL if self.depth_mode.function == gx.GREATER: return GL_GREATER if self.depth_mode.function == gx.NEQUAL: return GL_NOTEQUAL if self.depth_mode.function == gx.GEQUAL: return GL_GEQUAL if self.depth_mode.function == gx.ALWAYS: return GL_ALWAYS raise ValueError('Invalid compare function: {}'.format( self.depth_mode.function)) @property def gl_blend_source_factor(self): if self.blend_mode.source_factor == gx.BL_ZERO: return GL_ZERO if self.blend_mode.source_factor == gx.BL_ONE: return GL_ONE if self.blend_mode.source_factor == gx.BL_SRCALPHA: return GL_SRC_ALPHA if self.blend_mode.source_factor == gx.BL_INVSRCALPHA: return GL_ONE_MINUS_SRC_ALPHA if self.blend_mode.source_factor == gx.BL_DSTALPHA: return GL_DST_ALPHA if self.blend_mode.source_factor == gx.BL_INVDSTALPHA: return GL_ONE_MINUS_DST_ALPHA if self.blend_mode.source_factor == gx.BL_DSTCLR: return GL_DST_COLOR if self.blend_mode.source_factor == gx.BL_INVDSTCLR: return GL_ONE_MINUS_DST_COLOR raise ValueError('Invalid blend source factor: {}'.format( self.blend_mode.source_factor)) @property def gl_blend_destination_factor(self): if self.blend_mode.destination_factor == gx.BL_ZERO: return GL_ZERO if self.blend_mode.destination_factor == gx.BL_ONE: return GL_ONE if self.blend_mode.destination_factor == gx.BL_SRCALPHA: return GL_SRC_ALPHA if self.blend_mode.destination_factor == gx.BL_INVSRCALPHA: return GL_ONE_MINUS_SRC_ALPHA if self.blend_mode.destination_factor == gx.BL_DSTALPHA: return GL_DST_ALPHA if self.blend_mode.destination_factor == gx.BL_INVDSTALPHA: return GL_ONE_MINUS_DST_ALPHA if self.blend_mode.destination_factor == gx.BL_SRCCLR: return GL_SRC_COLOR if self.blend_mode.destination_factor == gx.BL_INVSRCCLR: return GL_ONE_MINUS_SRC_COLOR raise ValueError('Invalid blend destination factor: {}'.format( self.blend_mode.destination_factor)) @property def gl_blend_logical_operation(self): if self.blend_mode.logical_operation == gx.LO_CLEAR: return GL_CLEAR if self.blend_mode.logical_operation == gx.LO_AND: return GL_AND if self.blend_mode.logical_operation == gx.LO_REVAND: return GL_AND_REVERSE if self.blend_mode.logical_operation == gx.LO_COPY: return GL_COPY if self.blend_mode.logical_operation == gx.LO_INVAND: return GL_AND_INVERTED if self.blend_mode.logical_operation == gx.LO_NOOP: return GL_NOOP if self.blend_mode.logical_operation == gx.LO_XOR: return GL_XOR if self.blend_mode.logical_operation == gx.LO_OR: return GL_OR if self.blend_mode.logical_operation == gx.LO_NOR: return GL_NOR if self.blend_mode.logical_operation == gx.LO_EQUIV: return GL_EQUIV if self.blend_mode.logical_operation == gx.LO_INV: return GL_INVERT if self.blend_mode.logical_operation == gx.LO_REVOR: return GL_OR_INVERTED if self.blend_mode.logical_operation == gx.LO_INVCOPY: return GL_COPY_INVERTED if self.blend_mode.logical_operation == gx.LO_INVOR: return GL_OR_INVERTED if self.blend_mode.logical_operation == gx.LO_INVNAND: return GL_NAND if self.blend_mode.logical_operation == gx.LO_SET: return GL_SET raise ValueError('Invalid logical operation: {}'.format( self.blend_mode.logical_operation)) def gl_bind(self, shape): self.gl_block.bind(MATERIAL_BLOCK_BINDING_POINT) for i, texture in enumerate(self.textures): if texture is None: continue texture.gl_bind(TEXTURE_UNITS[i]) if self.cull_mode != gx.CULL_NONE: glEnable(GL_CULL_FACE) glCullFace(self.gl_cull_mode) else: glDisable(GL_CULL_FACE) if self.depth_mode.enable: glEnable(GL_DEPTH_TEST) glDepthFunc(self.gl_depth_function) glDepthMask(self.depth_mode.update_enable) else: glDisable(GL_DEPTH_TEST) if self.blend_mode.function == gx.BM_BLEND: glEnable(GL_BLEND) glBlendEquation(GL_FUNC_ADD) glBlendFunc(self.gl_blend_source_factor, self.gl_blend_destination_factor) elif self.blend_mode.function == gx.BM_SUBTRACT: glEnable(GL_BLEND) glBlendEquation(GL_FUNC_REVERSE_SUBTRACT) glBlendFunc(GL_ONE, GL_ONE) else: glDisable(GL_BLEND) if self.blend_mode.function == gx.BM_LOGIC: glEnable(GL_COLOR_LOGIC_OP) glLogicOp(self.gl_blend_logical_operation) else: glDisable(GL_COLOR_LOGIC_OP) if self.dither: glEnable(GL_DITHER) else: glDisable(GL_DITHER) program = self.gl_program(shape.transformation_type) glUseProgram(program) if shape.transformation_type == 0: glUniform1i(program.matrix_index_location, shape.batches[0].matrix_table[0]) def gl_delete(self): super().gl_delete() try: del self.gl_block except AttributeError: pass self.gl_program_table.clear()
class Model(WrapperModel): def __init__(self, wrapped_object): super().__init__(wrapped_object) self.file_path = None self.init_references() file_type = _attribute() subversion = _attribute() scene_graph = _attribute(SceneGraph) position_array = _attribute() normal_array = _attribute() color_arrays = _attribute() texcoord_arrays = _attribute() influence_groups = _attribute() inverse_bind_matrices = _attribute() matrix_definitions = _attribute() joints = _attribute() shapes = _attribute(_list(models.shape.Shape)) materials = _attribute(_list(models.material.Material)) textures = _attribute(_list(models.texture.Texture)) def gl_init(self): array_table = {} array_table[gx.VA_PTNMTXIDX] = GLMatrixIndexArray() array_table.update({ attribute: GLDirectArray(attribute) for attribute in gx.VA_TEXMTXIDX }) array_table[gx.VA_POS] = gl_convert_array(self.position_array) array_table[gx.VA_NRM] = gl_convert_array(self.normal_array) array_table.update({ attribute: gl_convert_color_array(array) for attribute, array in zip(gx.VA_CLR, self.color_arrays) }) array_table.update({ attribute: gl_convert_array(array) for attribute, array in zip(gx.VA_TEX, self.texcoord_arrays) }) for shape in self.shapes: shape.gl_init(array_table) self.gl_joints = [copy.copy(joint) for joint in self.joints] self.gl_joint_matrices = numpy.empty((len(self.joints), 3, 4), numpy.float32) self.gl_matrix_table = self.gl_create_resource( gl.TextureBuffer, GL_DYNAMIC_DRAW, GL_RGBA32F, (len(self.matrix_definitions), 3, 4), numpy.float32) self.gl_update_matrix_table() def gl_update_joint_matrices(self, node, parent_joint=None, parent_joint_matrix=numpy.eye( 3, 4, dtype=numpy.float32)): for child in node.children: if child.node_type == NodeType.JOINT: joint = self.gl_joints[child.index] joint_matrix = self.gl_joint_matrices[child.index] joint_matrix[:] = joint.create_matrix(parent_joint, parent_joint_matrix) self.gl_update_joint_matrices(child, joint, joint_matrix) else: self.gl_update_joint_matrices(child, parent_joint, parent_joint_matrix) def gl_update_matrix_table(self): self.gl_update_joint_matrices(self.scene_graph) if self.inverse_bind_matrices is not None: influence_matrices = matrix3x4_array_multiply( self.gl_joint_matrices, self.inverse_bind_matrices) for matrix, matrix_definition in zip(self.gl_matrix_table, self.matrix_definitions): if matrix_definition.matrix_type == MatrixType.JOINT: matrix[:] = self.gl_joint_matrices[matrix_definition.index] elif matrix_definition.matrix_type == MatrixType.INFLUENCE_GROUP: influence_group = self.influence_groups[ matrix_definition.index] matrix[:] = sum(influence.weight * influence_matrices[influence.index] for influence in influence_group) else: ValueError('invalid matrix type') def gl_draw_shape(self, material, shape): if shape.gl_hide: return material.gl_bind(shape) shape.gl_bind() shape.gl_draw() def gl_draw_node(self, node, parent_material=None): for child in node.children: if child.node_type == NodeType.SHAPE: if parent_material.unknown0 == 1: self.gl_draw_shape(parent_material, self.shapes[child.index]) self.gl_draw_node(child, parent_material) if parent_material.unknown0 == 4: self.gl_draw_shape(parent_material, self.shapes[child.index]) elif child.node_type == NodeType.MATERIAL: self.gl_draw_node(child, child.material) else: self.gl_draw_node(child, parent_material) def gl_draw(self): self.gl_matrix_table.bind_texture( models.material.MATRIX_TABLE_TEXTURE_UNIT) self.gl_draw_node(self.scene_graph) @staticmethod def load(file_path): with open(file_path, 'rb') as stream: model = j3d.model.unpack(stream) model = Model(model) model.file_path = file_path return model def save(self, file_path): self.file_path = file_path self.sync_reference_indices() with open(file_path, 'wb') as stream: j3d.model.pack(stream, self.wrapped_object) def init_references(self): """Initialize references. Initialize references into the material and texture lists. """ for node in self.scene_graph.all_nodes(): if node.node_type == NodeType.MATERIAL: node.material = self.materials[node.wrapped_object.index] for material in self.materials: material.textures = ReferenceList( self. textures[texture_index] if texture_index is not None else None for texture_index in material.wrapped_object.texture_indices) def sync_reference_indices(self): """Synchronize reference indices. Indices used to reference into the material and texture lists are not automatically kept in sync. This method needs to be manually called to synchronize the reference indices. """ for node in self.scene_graph.all_nodes(): if node.node_type == NodeType.MATERIAL: node.wrapped_object.index = self.materials.index(node.material) for material in self.materials: for i, texture in enumerate(material.textures): texture_index = None if texture is not None: texture_index = self.textures.index(texture) material.wrapped_object.texture_indices[i] = texture_index def get_nodes_using_material(self, material_index): """Get scene graph nodes that use a given material. :param material_index: Index of the material in the material list. :return: List of the scene graph nodes that use the material. """ material = self.materials[material_index] nodes = [] for node in self.scene_graph.all_nodes(): if node.node_type == NodeType.MATERIAL and node.material == material: nodes.append(node) return nodes def get_materials_using_texture(self, texture_index): """Get materials that use a given texture. :param texture_index: Index of the texture in the texture list. :return: List of the materials that use the texture. """ texture = self.textures[texture_index] materials = [] for material in self.materials: if texture in material.textures: materials.append(material) continue return materials
class ShapeNode(SceneGraphNode): node_type = _attribute() index = _attribute() children = _attribute(_list(SceneGraphNode.create_node))
class MaterialNode(SceneGraphNode): node_type = _attribute() material = ReferenceAttribute() children = _attribute(_list(SceneGraphNode.create_node))