def __init__(self, width: int, height: int): """Create a LightLayer The size of a layer should ideally be of the same size and the screen. :param Tuple[int, int] size: Width and height of light layer """ super().__init__(width, height) self._lights: List[Light] = [] self._prev_target = None self._rebuild = False self._stride = 28 self._buffer = self.ctx.buffer(reserve=self._stride * 100) self._vao = self.ctx.geometry([ gl.BufferDescription( self._buffer, '2f 1f 1f 3f', ['in_vert', 'in_radius', 'in_attenuation', 'in_color'], normalized=['in_color'], ), ]) self._light_program = self.ctx.load_program( vertex_shader=":resources:shaders/lights/point_lights_vs.glsl", geometry_shader=":resources:shaders/lights/point_lights_geo.glsl", fragment_shader=":resources:shaders/lights/point_lights_fs.glsl", ) self._combine_program = self.ctx.load_program( vertex_shader=":resources:shaders/lights/combine_vs.glsl", fragment_shader=":resources:shaders/lights/combine_fs.glsl", ) # NOTE: Diffuse buffer created in parent self._light_buffer = self.ctx.framebuffer( color_attachments=self.ctx.texture((width, height), components=3))
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 = self.ctx.buffer( data=self._sprite_size_data, usage=usage) variables = ['in_size'] self._sprite_size_desc = gl.BufferDescription( self._sprite_size_buf, '2f', variables) 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(sprite.angle) self._sprite_angle_buf = self.ctx.buffer( data=self._sprite_angle_data, usage=usage) variables = ['in_angle'] self._sprite_angle_desc = gl.BufferDescription( self._sprite_angle_buf, '1f', variables, ) 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.extend(sprite.color[:3]) self._sprite_color_data.append(int(sprite.alpha)) self._sprite_color_buf = self.ctx.buffer( data=self._sprite_color_data, usage=usage) variables = ['in_color'] self._sprite_color_desc = gl.BufferDescription( self._sprite_color_buf, '4f1', variables, normalized=['in_color']) self._sprite_color_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 = self.ctx.buffer(data=self._sprite_pos_data, usage=usage) variables = ['in_pos'] self._sprite_pos_desc = gl.BufferDescription( self._sprite_pos_buf, '2f', variables, ) self._sprite_pos_changed = False
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 or self._force_new_atlas_generation: new_texture = True self._force_new_atlas_generation = False # 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 # Do we already have this in our old texture atlas? if name_of_texture_to_check not in self.array_of_texture_names: # No, so flag that we'll have to create a new one. new_texture = True # print("New because of ", name_of_texture_to_check) # Do we already have this created because of a prior loop? if name_of_texture_to_check not in new_array_of_texture_names: # No, so make as a new image 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 # Create a new image with a transparent border around it to help prevent artifacts tmp = Image.new('RGBA', (image.width+2, image.height+2)) tmp.paste(image, (1, 1)) tmp.paste(tmp.crop((1 , 1 , image.width+1, 2 )), (1 , 0 )) tmp.paste(tmp.crop((1 , image.height, image.width+1, image.height+1)), (1 , image.height+1)) tmp.paste(tmp.crop((1 , 0 , 2, image.height+2)), (0 , 0 )) tmp.paste(tmp.crop((image.width, 0 , image.width+1, image.height+2)), (image.width+1, 0 )) # Put in our array of new images new_array_of_images.append(tmp) # 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 = 0 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: # .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 = self.ctx.texture( (new_image2.width, new_image2.height), components=4, data=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 self._tex_coords = [] offset = 1 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) + offset y = row * (grid_item_height + margin) + offset # 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-2*offset) / sprite_sheet_width normalized_height = (image.height-2*offset) / 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}") self._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. self._sprite_sub_tex_data = array.array('f') for sprite in self.sprite_list: index = self.array_of_texture_names.index(sprite.texture.name) for coord in self._tex_coords[index]: self._sprite_sub_tex_data.append(coord) self._sprite_sub_tex_buf = self.ctx.buffer( data=self._sprite_sub_tex_data, usage=usage ) self._sprite_sub_tex_desc = gl.BufferDescription( self._sprite_sub_tex_buf, '4f', ['in_sub_tex_coords'], ) self._sprite_sub_tex_changed = False
def __init__(self, width, height, title): super().__init__(width, height, title, resizable=True) self.set_vsync(True) self.size = self.width // 4, self.height // 4 # Two buffers on the gpu with positions self.buffer1 = self.ctx.buffer( data=array('f', gen_initial_data(self, *self.size))) self.buffer2 = self.ctx.buffer(reserve=self.buffer1.size) # Buffer with color for each point self.colors = self.ctx.buffer(data=array('f', gen_colors(*self.size))) # Geometry for drawing the two buffer variants. # We pad away the desired position and add the color data. self.geometry1 = self.ctx.geometry([ gl.BufferDescription(self.buffer1, '2f 2x4', ['in_pos']), gl.BufferDescription(self.colors, '3f', ['in_color']), ]) self.geometry2 = self.ctx.geometry([ gl.BufferDescription(self.buffer2, '2f 2x4', ['in_pos']), gl.BufferDescription(self.colors, '3f', ['in_color']), ]) # Transform geometry for the two buffers. This is used to move the points with a transform shader self.transform1 = self.ctx.geometry([ gl.BufferDescription(self.buffer1, '2f 2f', ['in_pos', 'in_dest']) ]) self.transform2 = self.ctx.geometry([ gl.BufferDescription(self.buffer2, '2f 2f', ['in_pos', 'in_dest']) ]) # Let's make the coordinate system match the viewport projection = arcade.create_orthogonal_projection( 0, self.width, 0, self.height, -100, 100).flatten() # Draw the points with the the supplied color self.points_program = self.ctx.program( vertex_shader=""" #version 330 uniform mat4 projection; in vec2 in_pos; in vec3 in_color; out vec3 color; void main() { gl_Position = projection * vec4(in_pos, 0.0, 1.0); color = in_color; } """, fragment_shader=""" #version 330 in vec3 color; out vec4 fragColor; void main() { fragColor = vec4(color, 1.0); } """, ) # Write the projection matrix to the program uniform self.points_program['projection'] = projection # Program altering the point location. # We constantly try to move the point to its desired location. # In addition we check the distance to the mouse pointer and move it if within a certain range. self.transform_program = self.ctx.program(vertex_shader=""" #version 330 uniform float dt; // time delta (between frames) uniform vec2 mouse_pos; in vec2 in_pos; in vec2 in_dest; out vec2 out_pos; out vec2 out_dest; void main() { out_dest = in_dest; // Slowly move the point towards the desired location vec2 dir = in_dest - in_pos; vec2 pos = in_pos + dir * dt; // Move the point away from the mouse position float dist = length(pos - mouse_pos); if (dist < 90.0) { pos += (pos - mouse_pos) * dt * 10; } out_pos = pos; } """, ) self.frame_time = 0 self.last_time = time.time() self.mouse_pos = -100, -100