def _cache_vao(self, ref_sprites, total_width, max_height, atlas, tex_map): sprite_count = len(ref_sprites) sprite_positions = [] sprite_colors = [] sprite_angles = [] sprite_sizes = [] for sprite in ref_sprites: sprite_positions.append([sprite.center_x, sprite.center_y]) sprite_colors.append(sprite.color + (sprite.alpha, )) sprite_angles.append(math.radians(sprite.angle)) sprite_sizes.append([sprite.width / 2, sprite.height / 2]) sprite_coords = self._map_textures_to_atlas(total_width, max_height, atlas, tex_map) # # Creating OpenGL data buffer # sprite_data = np.zeros(sprite_count, dtype=np.dtype([('position', '2f4'), ('angle', 'f4'), ('size', '2f4'), ('sub_tex_coords', '4f4'), ('color', '4B')])) sprite_data['position'] = sprite_positions sprite_data['angle'] = sprite_angles sprite_data['size'] = sprite_sizes sprite_data['sub_tex_coords'] = sprite_coords sprite_data['color'] = sprite_colors self.sprite_data = sprite_data.tobytes() self.gl_data_buf = shader.buffer(self.sprite_data, usage='stream') # # # self.vbo_buf = shader.buffer( GLShader.get_triangle_strip_vertices().tobytes()) vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f', ('in_vert', 'in_texture')) pos_angle_scale_buf_desc = shader.BufferDescription( self.gl_data_buf, '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale', 'in_sub_tex_coords', 'in_color'), normalized=['in_color'], instanced=True) vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc] self.vao = shader.vertex_array(self.program, vao_content)
def _generic_draw_line_strip(point_list: PointList, color: Color, mode: int = gl.GL_LINE_STRIP): """ Draw a line strip. A line strip is a set of continuously connected line segments. :param point_list: List of points making up the line. Each point is in a list. So it is a list of lists. :param Color color: color, specified in a list of 3 or 4 bytes in RGB or RGBA format. """ # Cache the program. But not on linux because it fails unit tests for some reason. # if not _generic_draw_line_strip.program or sys.platform == "linux": _generic_draw_line_strip.program = shader.program( vertex_shader=_line_vertex_shader, fragment_shader=_line_fragment_shader, ) c4 = get_four_byte_color(color) c4e = c4 * len(point_list) a = array.array('B', c4e) color_buf = shader.buffer(a.tobytes()) color_buf_desc = shader.BufferDescription( color_buf, '4B', ['in_color'], normalized=['in_color'], ) def gen_flatten(my_list): return [item for sublist in my_list for item in sublist] vertices = array.array('f', gen_flatten(point_list)) vbo_buf = shader.buffer(vertices.tobytes()) vbo_buf_desc = shader.BufferDescription(vbo_buf, '2f', ['in_vert']) vao_content = [vbo_buf_desc, color_buf_desc] vao = shader.vertex_array(_generic_draw_line_strip.program, vao_content) with vao: _generic_draw_line_strip.program['Projection'] = get_projection( ).flatten() vao.render(mode=mode)
def create_line(start_x: float, start_y: float, end_x: float, end_y: float, color: Color, line_width: float = 1) -> Shape: """ Create a line to be rendered later. This works faster than draw_line because the vertexes are only loaded to the graphics card once, rather than each frame. """ program = shader.program( vertex_shader=''' #version 330 uniform mat4 Projection; in vec2 in_vert; in vec4 in_color; out vec4 v_color; void main() { gl_Position = Projection * vec4(in_vert, 0.0, 1.0); v_color = in_color; } ''', fragment_shader=''' #version 330 in vec4 v_color; out vec4 f_color; void main() { f_color = v_color; } ''', ) buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')]) data = np.zeros(2, dtype=buffer_type) data['vertex'] = (start_x, start_y), (end_x, end_y) data['color'] = get_four_byte_color(color) vbo = shader.buffer(data.tobytes()) vao_content = [ shader.BufferDescription(vbo, '2f 4B', ('in_vert', 'in_color'), normalized=['in_color']) ] vao = shader.vertex_array(program, vao_content) with vao: program['Projection'] = get_projection().flatten() shape = Shape() shape.vao = vao shape.vbo = vbo shape.program = program shape.mode = gl.GL_LINE_STRIP shape.line_width = line_width return shape
def create_line_generic_with_colors(point_list: PointList, color_list: Iterable[Color], shape_mode: int, line_width: float=1) -> Shape: """ This function is used by ``create_line_strip`` and ``create_line_loop``, just changing the OpenGL type for the line drawing. """ program = shader.program( vertex_shader=''' #version 330 uniform mat4 Projection; in vec2 in_vert; in vec4 in_color; out vec4 v_color; void main() { gl_Position = Projection * vec4(in_vert, 0.0, 1.0); v_color = in_color; } ''', fragment_shader=''' #version 330 in vec4 v_color; out vec4 f_color; void main() { f_color = v_color; } ''', ) buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')]) data = np.zeros(len(point_list), dtype=buffer_type) data['vertex'] = point_list data['color'] = [get_four_byte_color(color) for color in color_list] vbo = shader.buffer(data.tobytes()) vao_content = [ shader.BufferDescription( vbo, '2f 4B', ('in_vert', 'in_color'), normalized=['in_color'] ) ] vao = shader.vertex_array(program, vao_content) with vao: program['Projection'] = get_projection().flatten() shape = Shape() shape.vao = vao shape.vbo = vbo shape.program = program shape.mode = shape_mode shape.line_width = line_width return shape
def _generic_draw_line_strip(point_list: PointList, color: Color, line_width: float=1, mode: int=moderngl.LINE_STRIP): """ Draw a line strip. A line strip is a set of continuously connected line segments. Args: :point_list: List of points making up the line. Each point is in a list. So it is a list of lists. :color: color, specified in a list of 3 or 4 bytes in RGB or RGBA format. :border_width: Width of the line in pixels. Returns: None Raises: None """ program = shader.program( vertex_shader=line_vertex_shader, fragment_shader=line_fragment_shader, ) buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')]) data = np.zeros(len(point_list), dtype=buffer_type) data['vertex'] = point_list color = get_four_byte_color(color) data['color'] = color vbo = shader.buffer(data.tobytes()) vbo_desc = shader.BufferDescription( vbo, '2f 4B', ('in_vert', 'in_color'), normalized=['in_color'] ) vao_content = [vbo_desc] vao = shader.vertex_array(program, vao_content) with vao: program['Projection'] = get_projection().flatten() gl.glLineWidth(line_width) gl.glPointSize(line_width) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glEnable(gl.GL_LINE_SMOOTH) gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) gl.glHint(gl.GL_POLYGON_SMOOTH_HINT, gl.GL_NICEST) vao.render(mode=mode)
def _calculate_angle_buffer(): self._sprite_angle_data = array.array('f') for sprite in self.sprite_list: self._sprite_angle_data.append(math.radians(sprite.angle)) self._sprite_angle_buf = shader.buffer( self._sprite_angle_data.tobytes(), usage=usage) variables = ['in_angle'] self._sprite_angle_desc = shader.BufferDescription( self._sprite_angle_buf, '1f', variables, instanced=True) self._sprite_angle_changed = False
def _calculate_size_buffer(): self._sprite_size_data = array.array('f') for sprite in self.sprite_list: self._sprite_size_data.append(sprite.width) self._sprite_size_data.append(sprite.height) self._sprite_size_buf = shader.buffer( self._sprite_size_data.tobytes(), usage=usage) variables = ['in_size'] self._sprite_size_desc = shader.BufferDescription( self._sprite_size_buf, '2f', variables, instanced=True) self._sprite_size_changed = False
def _calculate_pos_buffer(): self._sprite_pos_data = array.array('f') # print("A") for sprite in self.sprite_list: self._sprite_pos_data.append(sprite.center_x) self._sprite_pos_data.append(sprite.center_y) self._sprite_pos_buf = shader.buffer( self._sprite_pos_data.tobytes(), usage=usage) variables = ['in_pos'] self._sprite_pos_desc = shader.BufferDescription( self._sprite_pos_buf, '2f', variables, instanced=True) self._sprite_pos_changed = False
def _calculate_colors(): self._sprite_color_data = array.array('B') for sprite in self.sprite_list: self._sprite_color_data.append(int(sprite.color[0])) self._sprite_color_data.append(int(sprite.color[1])) self._sprite_color_data.append(int(sprite.color[2])) self._sprite_color_data.append(int(sprite.alpha)) self._sprite_color_buf = shader.buffer( self._sprite_color_data.tobytes(), usage=usage) variables = ['in_color'] self._sprite_color_desc = shader.BufferDescription( self._sprite_color_buf, '4B', variables, normalized=['in_color'], instanced=True) self._sprite_color_changed = False
def _generic_draw_line_strip(point_list: PointList, color: Color, mode: int = gl.GL_LINE_STRIP): """ Draw a line strip. A line strip is a set of continuously connected line segments. :param point_list: List of points making up the line. Each point is in a list. So it is a list of lists. :param Color color: color, specified in a list of 3 or 4 bytes in RGB or RGBA format. """ program = shader.program( vertex_shader=line_vertex_shader, fragment_shader=line_fragment_shader, ) buffer_type = np.dtype([('vertex', '2f4'), ('color', '4B')]) data = np.zeros(len(point_list), dtype=buffer_type) data['vertex'] = point_list color = get_four_byte_color(color) data['color'] = color vbo = shader.buffer(data.tobytes()) vbo_desc = shader.BufferDescription( vbo, '2f 4B', ('in_vert', 'in_color'), normalized=['in_color'] ) vao_content = [vbo_desc] vao = shader.vertex_array(program, vao_content) with vao: program['Projection'] = get_projection().flatten() vao.render(mode=mode)
def _calculate_sprite_buffer(self): if len(self.sprite_list) == 0: return # Loop through each sprite and grab its position, and the texture it will be using. array_of_positions = [] array_of_sizes = [] array_of_colors = [] array_of_angles = [] for sprite in self.sprite_list: array_of_positions.append([sprite.center_x, sprite.center_y]) array_of_angles.append(math.radians(sprite.angle)) size_h = sprite.height / 2 size_w = sprite.width / 2 array_of_sizes.append([size_w, size_h]) array_of_colors.append(sprite.color + (sprite.alpha, )) new_array_of_texture_names = [] new_array_of_images = [] new_texture = False if self.array_of_images is None: new_texture = True # print() # print("New texture start: ", new_texture) for sprite in self.sprite_list: if sprite._texture is None: raise Exception( "Error: Attempt to draw a sprite without a texture set.") name_of_texture_to_check = sprite._texture.name if name_of_texture_to_check not in self.array_of_texture_names: new_texture = True # print("New because of ", name_of_texture_to_check) if name_of_texture_to_check not in new_array_of_texture_names: new_array_of_texture_names.append(name_of_texture_to_check) image = sprite._texture.image new_array_of_images.append(image) # print("New texture end: ", new_texture) # print(new_array_of_texture_names) # print(self.array_of_texture_names) # print() if new_texture: # Add back in any old textures. Chances are we'll need them. for index, old_texture_name in enumerate( self.array_of_texture_names): if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None: new_array_of_texture_names.append(old_texture_name) image = self.array_of_images[index] new_array_of_images.append(image) self.array_of_texture_names = new_array_of_texture_names self.array_of_images = new_array_of_images # print(f"New Texture Atlas with names {self.array_of_texture_names}") # Get their sizes widths, heights = zip(*(i.size for i in self.array_of_images)) # Figure out what size a composite would be total_width = sum(widths) max_height = max(heights) if new_texture: # TODO: This code isn't valid, but I think some releasing might be in order. # if self.texture is not None: # shader.Texture.release(self.texture_id) # Make the composite image new_image = Image.new('RGBA', (total_width, max_height)) x_offset = 0 for image in self.array_of_images: new_image.paste(image, (x_offset, 0)) x_offset += image.size[0] # Create a texture out the composite image self._texture = shader.texture((new_image.width, new_image.height), 4, np.asarray(new_image)) if self.texture_id is None: self.texture_id = SpriteList.next_texture_id # Create a list with the coordinates of all the unique textures tex_coords = [] start_x = 0.0 for image in self.array_of_images: end_x = start_x + (image.width / total_width) normalized_width = image.width / total_width start_height = 1 - (image.height / max_height) normalized_height = image.height / max_height tex_coords.append( [start_x, start_height, normalized_width, normalized_height]) start_x = end_x # Go through each sprite and pull from the coordinate list, the proper # coordinates for that sprite's image. array_of_sub_tex_coords = [] for sprite in self.sprite_list: index = self.array_of_texture_names.index(sprite._texture.name) array_of_sub_tex_coords.append(tex_coords[index]) # Create numpy array with info on location and such buffer_type = np.dtype([('position', '2f4'), ('angle', 'f4'), ('size', '2f4'), ('sub_tex_coords', '4f4'), ('color', '4B')]) self.sprite_data = np.zeros(len(self.sprite_list), dtype=buffer_type) self.sprite_data['position'] = array_of_positions self.sprite_data['angle'] = array_of_angles self.sprite_data['size'] = array_of_sizes self.sprite_data['sub_tex_coords'] = array_of_sub_tex_coords self.sprite_data['color'] = array_of_colors if self.is_static: usage = 'static' else: usage = 'stream' self.sprite_data_buf = shader.buffer(self.sprite_data.tobytes(), usage=usage) vertices = np.array( [ # x, y, u, v -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, ], dtype=np.float32) self.vbo_buf = shader.buffer(vertices.tobytes()) vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f', ('in_vert', 'in_texture')) pos_angle_scale_buf_desc = shader.BufferDescription( self.sprite_data_buf, '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale', 'in_sub_tex_coords', 'in_color'), normalized=['in_color'], instanced=True) vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc] # Can add buffer to index vertices self.vao = shader.vertex_array(self.program, vao_content)
def _calculate_sub_tex_coords(): new_array_of_texture_names = [] new_array_of_images = [] new_texture = False if self.array_of_images is None: new_texture = True # print() # print("New texture start: ", new_texture) for sprite in self.sprite_list: # noinspection PyProtectedMember if sprite.texture is None: raise Exception( "Error: Attempt to draw a sprite without a texture set." ) name_of_texture_to_check = sprite.texture.name if name_of_texture_to_check not in self.array_of_texture_names: new_texture = True # print("New because of ", name_of_texture_to_check) if name_of_texture_to_check not in new_array_of_texture_names: new_array_of_texture_names.append(name_of_texture_to_check) if sprite.texture is None: raise ValueError(f"Sprite has no texture.") if sprite.texture.image is None: raise ValueError( f"Sprite texture {sprite.texture.name} has no image." ) image = sprite.texture.image new_array_of_images.append(image) # print("New texture end: ", new_texture) # print(new_array_of_texture_names) # print(self.array_of_texture_names) # print() if new_texture: # Add back in any old textures. Chances are we'll need them. for index, old_texture_name in enumerate( self.array_of_texture_names): if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None: new_array_of_texture_names.append(old_texture_name) image = self.array_of_images[index] new_array_of_images.append(image) self.array_of_texture_names = new_array_of_texture_names self.array_of_images = new_array_of_images # print(f"New Texture Atlas with names {self.array_of_texture_names}") # Get their sizes widths, heights = zip(*(i.size for i in self.array_of_images)) # Figure out what size a composite would be total_width = sum(widths) max_height = max(heights) if new_texture: # TODO: This code isn't valid, but I think some releasing might be in order. # if self.texture is not None: # shader.Texture.release(self.texture_id) # Make the composite image new_image = Image.new('RGBA', (total_width, max_height)) x_offset = 0 for image in self.array_of_images: new_image.paste(image, (x_offset, 0)) x_offset += image.size[0] # Create a texture out the composite image texture_bytes = new_image.tobytes() self._texture = shader.texture( (new_image.width, new_image.height), 4, texture_bytes) if self.texture_id is None: self.texture_id = SpriteList.next_texture_id # Create a list with the coordinates of all the unique textures tex_coords = [] start_x = 0.0 for image in self.array_of_images: end_x = start_x + (image.width / total_width) normalized_width = image.width / total_width start_height = 1 - (image.height / max_height) normalized_height = image.height / max_height tex_coords.append([ start_x, start_height, normalized_width, normalized_height ]) start_x = end_x # Go through each sprite and pull from the coordinate list, the proper # coordinates for that sprite's image. array_of_sub_tex_coords = array.array('f') for sprite in self.sprite_list: index = self.array_of_texture_names.index(sprite.texture.name) for coord in tex_coords[index]: array_of_sub_tex_coords.append(coord) self._sprite_sub_tex_buf = shader.buffer( array_of_sub_tex_coords.tobytes(), usage=usage) self._sprite_sub_tex_desc = shader.BufferDescription( self._sprite_sub_tex_buf, '4f', ['in_sub_tex_coords'], instanced=True) self._sprite_sub_tex_changed = False
def _calculate_sprite_buffer(self): if self.is_static: usage = 'static' else: usage = 'stream' def _calculate_pos_buffer(): self._sprite_pos_data = array.array('f') # print("A") for sprite in self.sprite_list: self._sprite_pos_data.append(sprite.center_x) self._sprite_pos_data.append(sprite.center_y) self._sprite_pos_buf = shader.buffer( self._sprite_pos_data.tobytes(), usage=usage) variables = ['in_pos'] self._sprite_pos_desc = shader.BufferDescription( self._sprite_pos_buf, '2f', variables, instanced=True) self._sprite_pos_changed = False def _calculate_size_buffer(): self._sprite_size_data = array.array('f') for sprite in self.sprite_list: self._sprite_size_data.append(sprite.width) self._sprite_size_data.append(sprite.height) self._sprite_size_buf = shader.buffer( self._sprite_size_data.tobytes(), usage=usage) variables = ['in_size'] self._sprite_size_desc = shader.BufferDescription( self._sprite_size_buf, '2f', variables, instanced=True) self._sprite_size_changed = False def _calculate_angle_buffer(): self._sprite_angle_data = array.array('f') for sprite in self.sprite_list: self._sprite_angle_data.append(math.radians(sprite.angle)) self._sprite_angle_buf = shader.buffer( self._sprite_angle_data.tobytes(), usage=usage) variables = ['in_angle'] self._sprite_angle_desc = shader.BufferDescription( self._sprite_angle_buf, '1f', variables, instanced=True) self._sprite_angle_changed = False def _calculate_colors(): self._sprite_color_data = array.array('B') for sprite in self.sprite_list: self._sprite_color_data.append(int(sprite.color[0])) self._sprite_color_data.append(int(sprite.color[1])) self._sprite_color_data.append(int(sprite.color[2])) self._sprite_color_data.append(int(sprite.alpha)) self._sprite_color_buf = shader.buffer( self._sprite_color_data.tobytes(), usage=usage) variables = ['in_color'] self._sprite_color_desc = shader.BufferDescription( self._sprite_color_buf, '4B', variables, normalized=['in_color'], instanced=True) self._sprite_color_changed = False def _calculate_sub_tex_coords(): new_array_of_texture_names = [] new_array_of_images = [] new_texture = False if self.array_of_images is None: new_texture = True # print() # print("New texture start: ", new_texture) for sprite in self.sprite_list: # noinspection PyProtectedMember if sprite.texture is None: raise Exception( "Error: Attempt to draw a sprite without a texture set." ) name_of_texture_to_check = sprite.texture.name if name_of_texture_to_check not in self.array_of_texture_names: new_texture = True # print("New because of ", name_of_texture_to_check) if name_of_texture_to_check not in new_array_of_texture_names: new_array_of_texture_names.append(name_of_texture_to_check) if sprite.texture is None: raise ValueError(f"Sprite has no texture.") if sprite.texture.image is None: raise ValueError( f"Sprite texture {sprite.texture.name} has no image." ) image = sprite.texture.image new_array_of_images.append(image) # print("New texture end: ", new_texture) # print(new_array_of_texture_names) # print(self.array_of_texture_names) # print() if new_texture: # Add back in any old textures. Chances are we'll need them. for index, old_texture_name in enumerate( self.array_of_texture_names): if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None: new_array_of_texture_names.append(old_texture_name) image = self.array_of_images[index] new_array_of_images.append(image) self.array_of_texture_names = new_array_of_texture_names self.array_of_images = new_array_of_images # print(f"New Texture Atlas with names {self.array_of_texture_names}") # Get their sizes widths, heights = zip(*(i.size for i in self.array_of_images)) # Figure out what size a composite would be total_width = sum(widths) max_height = max(heights) if new_texture: # TODO: This code isn't valid, but I think some releasing might be in order. # if self.texture is not None: # shader.Texture.release(self.texture_id) # Make the composite image new_image = Image.new('RGBA', (total_width, max_height)) x_offset = 0 for image in self.array_of_images: new_image.paste(image, (x_offset, 0)) x_offset += image.size[0] # Create a texture out the composite image texture_bytes = new_image.tobytes() self._texture = shader.texture( (new_image.width, new_image.height), 4, texture_bytes) if self.texture_id is None: self.texture_id = SpriteList.next_texture_id # Create a list with the coordinates of all the unique textures tex_coords = [] start_x = 0.0 for image in self.array_of_images: end_x = start_x + (image.width / total_width) normalized_width = image.width / total_width start_height = 1 - (image.height / max_height) normalized_height = image.height / max_height tex_coords.append([ start_x, start_height, normalized_width, normalized_height ]) start_x = end_x # Go through each sprite and pull from the coordinate list, the proper # coordinates for that sprite's image. array_of_sub_tex_coords = array.array('f') for sprite in self.sprite_list: index = self.array_of_texture_names.index(sprite.texture.name) for coord in tex_coords[index]: array_of_sub_tex_coords.append(coord) self._sprite_sub_tex_buf = shader.buffer( array_of_sub_tex_coords.tobytes(), usage=usage) self._sprite_sub_tex_desc = shader.BufferDescription( self._sprite_sub_tex_buf, '4f', ['in_sub_tex_coords'], instanced=True) self._sprite_sub_tex_changed = False if len(self.sprite_list) == 0: return _calculate_pos_buffer() _calculate_size_buffer() _calculate_angle_buffer() _calculate_sub_tex_coords() _calculate_colors() vertices = array.array( 'f', [ # x, y, u, v -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, ]) self.vbo_buf = shader.buffer(vertices.tobytes()) vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f', ('in_vert', 'in_texture')) # Can add buffer to index vertices vao_content = [ vbo_buf_desc, self._sprite_pos_desc, self._sprite_size_desc, self._sprite_angle_desc, self._sprite_sub_tex_desc, self._sprite_color_desc ] self._vao1 = shader.vertex_array(self.program, vao_content)
def _calculate_sprite_buffer(self): if len(self._queue) == 0: return # # Initialize arrays # array_of_positions = [] array_of_sizes = [] array_of_colors = [] array_of_angles = [] self.array_of_layers.clear() num_sprites = 0 # # # for layer in self._queue: for sprite in layer._queue: array_of_positions.append([sprite.center_x, sprite.center_y]) array_of_angles.append(math.radians(sprite.angle)) size_h = sprite.height / 2 size_w = sprite.width / 2 array_of_sizes.append([size_w, size_h]) array_of_colors.append(sprite.color + (sprite.alpha, )) num_sprites += len(layer._queue) new_array_of_texture_names = [] new_array_of_images = [] new_texture = False if self.array_of_images is None: new_texture = True for layer in self._queue: for sprite in layer._queue: if sprite._texture is None: raise Exception( "Error: Attempt to draw a sprite without a texture set." ) name_of_texture_to_check = sprite._texture.name if name_of_texture_to_check not in self.array_of_texture_names: new_texture = True if name_of_texture_to_check not in new_array_of_texture_names: new_array_of_texture_names.append(name_of_texture_to_check) image = sprite._texture.image new_array_of_images.append(image) if new_texture: # Add back in any old textures. Chances are we'll need them. for index, old_texture_name in enumerate( self.array_of_texture_names): if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None: new_array_of_texture_names.append(old_texture_name) image = self.array_of_images[index] new_array_of_images.append(image) self.array_of_texture_names = new_array_of_texture_names self.array_of_images = new_array_of_images # print(f"New Texture Atlas with names {self.array_of_texture_names}") # Get their sizes widths, heights = zip(*(i.size for i in self.array_of_images)) # Figure out what size a composite would be total_width = sum(widths) max_height = max(heights) if new_texture: new_image = self._merge_down_to_image(total_width, max_height) # Create a texture out the composite image self._texture = shader.texture((new_image.width, new_image.height), 4, np.asarray(new_image)) if self.texture_id is None: self.texture_id = SpriteList.next_texture_id # Create a list with the coordinates of all the unique textures tex_coords = [] start_x = 0.0 for image in self.array_of_images: end_x = start_x + (image.width / total_width) normalized_width = image.width / total_width start_height = 1 - (image.height / max_height) normalized_height = image.height / max_height tex_coords.append( [start_x, start_height, normalized_width, normalized_height]) start_x = end_x # Go through each sprite and pull from the coordinate list, the proper # coordinates for that sprite's image. array_of_sub_tex_coords = [] for layer in self._queue: for sprite in layer._queue: index = self.array_of_texture_names.index(sprite._texture.name) array_of_sub_tex_coords.append(tex_coords[index]) # Create numpy array with info on location and such buffer_type = np.dtype([('position', '2f4'), ('angle', 'f4'), ('size', '2f4'), ('sub_tex_coords', '4f4'), ('color', '4B')]) self.sprite_data = np.zeros(num_sprites, dtype=buffer_type) self.sprite_data['position'] = array_of_positions self.sprite_data['angle'] = array_of_angles self.sprite_data['size'] = array_of_sizes self.sprite_data['sub_tex_coords'] = array_of_sub_tex_coords self.sprite_data['color'] = array_of_colors if self.is_static: usage = 'static' else: usage = 'stream' self.sprite_data_buf = shader.buffer(self.sprite_data.tobytes(), usage=usage) self.vbo_buf = shader.buffer(self.vertices.tobytes()) vbo_buf_desc = shader.BufferDescription(self.vbo_buf, '2f 2f', ('in_vert', 'in_texture')) pos_angle_scale_buf_desc = shader.BufferDescription( self.sprite_data_buf, '2f 1f 2f 4f 4B', ('in_pos', 'in_angle', 'in_scale', 'in_sub_tex_coords', 'in_color'), normalized=['in_color'], instanced=True) vao_content = [vbo_buf_desc, pos_angle_scale_buf_desc] # Can add buffer to index vertices self.vao = shader.vertex_array(self.program, vao_content)
def _calculate_sub_tex_coords(): """ Create a sprite sheet, and set up subtexture coordinates to point to images in that sheet. """ new_array_of_texture_names = [] new_array_of_images = [] new_texture = False if self.array_of_images is None: new_texture = True # print() # print("New texture start: ", new_texture) for sprite in self.sprite_list: # noinspection PyProtectedMember if sprite.texture is None: raise Exception( "Error: Attempt to draw a sprite without a texture set." ) name_of_texture_to_check = sprite.texture.name if name_of_texture_to_check not in self.array_of_texture_names: new_texture = True # print("New because of ", name_of_texture_to_check) if name_of_texture_to_check not in new_array_of_texture_names: new_array_of_texture_names.append(name_of_texture_to_check) if sprite.texture is None: raise ValueError(f"Sprite has no texture.") if sprite.texture.image is None: raise ValueError( f"Sprite texture {sprite.texture.name} has no image." ) image = sprite.texture.image new_array_of_images.append(image) # print("New texture end: ", new_texture) # print(new_array_of_texture_names) # print(self.array_of_texture_names) # print() if new_texture: # Add back in any old textures. Chances are we'll need them. for index, old_texture_name in enumerate( self.array_of_texture_names): if old_texture_name not in new_array_of_texture_names and self.array_of_images is not None: new_array_of_texture_names.append(old_texture_name) image = self.array_of_images[index] new_array_of_images.append(image) self.array_of_texture_names = new_array_of_texture_names self.array_of_images = new_array_of_images # print(f"New Texture Atlas with names {self.array_of_texture_names}") # Get their sizes widths, heights = zip(*(i.size for i in self.array_of_images)) grid_item_width, grid_item_height = max(widths), max(heights) image_count = len(self.array_of_images) root = math.sqrt(image_count) grid_width = int(math.sqrt(image_count)) # print(f"\nimage_count={image_count}, root={root}") if root == grid_width: # Perfect square grid_height = grid_width # print("\nA") else: grid_height = grid_width grid_width += 1 if grid_width * grid_height < image_count: grid_height += 1 # print("\nB") # Figure out sprite sheet size MARGIN = 1 sprite_sheet_width = (grid_item_width + MARGIN) * grid_width sprite_sheet_height = (grid_item_height + MARGIN) * grid_height if new_texture: # TODO: This code isn't valid, but I think some releasing might be in order. # if self.texture is not None: # shader.Texture.release(self.texture_id) # Make the composite image new_image2 = Image.new( 'RGBA', (sprite_sheet_width, sprite_sheet_height)) x_offset = 0 for index, image in enumerate(self.array_of_images): x = (index % grid_width) * (grid_item_width + MARGIN) y = (index // grid_width) * (grid_item_height + MARGIN) # print(f"Pasting {new_array_of_texture_names[index]} at {x, y}") new_image2.paste(image, (x, y)) x_offset += image.size[0] # Create a texture out the composite image texture_bytes2 = new_image2.tobytes() self._texture = shader.texture( (new_image2.width, new_image2.height), 4, texture_bytes2) if self.texture_id is None: self.texture_id = SpriteList.next_texture_id # new_image2.save("sprites.png") # Create a list with the coordinates of all the unique textures tex_coords = [] for index, image in enumerate(self.array_of_images): column = index % grid_width row = index // grid_width # Texture coordinates are reversed in y axis row = grid_height - row - 1 x = column * (grid_item_width + MARGIN) y = row * (grid_item_height + MARGIN) # Because, coordinates are reversed y += (grid_item_height - (image.height - MARGIN)) normalized_x = x / sprite_sheet_width normalized_y = y / sprite_sheet_height start_x = normalized_x start_y = normalized_y normalized_width = image.width / sprite_sheet_width normalized_height = image.height / sprite_sheet_height # print(f"Fetching {new_array_of_texture_names[index]} at {row}, {column} => {x}, {y} normalized to {start_x:.2}, {start_y:.2} size {normalized_width}, {normalized_height}") tex_coords.append( [start_x, start_y, normalized_width, normalized_height]) # Go through each sprite and pull from the coordinate list, the proper # coordinates for that sprite's image. array_of_sub_tex_coords = array.array('f') for sprite in self.sprite_list: index = self.array_of_texture_names.index(sprite.texture.name) for coord in tex_coords[index]: array_of_sub_tex_coords.append(coord) self._sprite_sub_tex_buf = shader.buffer( array_of_sub_tex_coords.tobytes(), usage=usage) self._sprite_sub_tex_desc = shader.BufferDescription( self._sprite_sub_tex_buf, '4f', ['in_sub_tex_coords'], instanced=True) self._sprite_sub_tex_changed = False