renderable
View Source
import os, math, ctypes import numpy as np from OpenGL import GL from art import VERT_LENGTH from palette import MAX_COLORS # inactive layer alphas LAYER_VIS_FULL = 1 LAYER_VIS_DIM = 0.25 LAYER_VIS_NONE = 0 class TileRenderable: """ 3D visual representation of an Art. Each layer is rendered as grids of rectangular OpenGL triangle-pairs. Animation frames are uploaded into our buffers from source Art's numpy arrays. """ vert_shader_source = 'renderable_v.glsl' "vertex shader: includes view projection matrix, XYZ camera uniforms." frag_shader_source = 'renderable_f.glsl' "Pixel shader: handles FG/BG colors." log_create_destroy = False log_animation = False log_buffer_updates = False grain_strength = 0. alpha = 1. "Alpha (0 to 1) for entire Renderable." bg_alpha = 1. "Alpha (0 to 1) *only* for tile background colors." default_move_rate = 1 use_art_offset = True "Use game object's art_off_pct values." def __init__(self, app, art, game_object=None): "Create Renderable with given Art, optionally bound to given GameObject" self.app = app "Application that renders us." self.art = art "Art we get data from." self.art.renderables.append(self) self.go = game_object "GameObject we're attached to." self.exporting = False "Set True momentarily by image export process; users shouldn't touch." self.visible = True "Boolean for easy render / don't-render functionality." self.frame = self.art.active_frame or 0 "Frame of our art's animation we're currently on" self.animating = False self.anim_timer, self.last_frame_time = 0, 0 # world space position and scale self.x, self.y, self.z = 0, 0, 0 self.scale_x, self.scale_y, self.scale_z = 1, 1, 1 if self.go: self.scale_x = self.go.scale_x self.scale_y = self.go.scale_y self.scale_z = self.go.scale_z # width and height in XY render space self.width, self.height = 1, 1 self.reset_size() # TODO: object rotation matrix, if needed self.goal_x, self.goal_y, self.goal_z = 0, 0, 0 self.move_rate = self.default_move_rate # marked True when UI is interpolating it self.ui_moving = False self.camera = self.app.camera # bind VAO etc before doing shaders etc if self.app.use_vao: self.vao = GL.glGenVertexArrays(1) GL.glBindVertexArray(self.vao) self.shader = self.app.sl.new_shader(self.vert_shader_source, self.frag_shader_source) self.proj_matrix_uniform = self.shader.get_uniform_location('projection') self.view_matrix_uniform = self.shader.get_uniform_location('view') self.position_uniform = self.shader.get_uniform_location('objectPosition') self.scale_uniform = self.shader.get_uniform_location('objectScale') self.charset_width_uniform = self.shader.get_uniform_location('charMapWidth') self.charset_height_uniform = self.shader.get_uniform_location('charMapHeight') self.char_uv_width_uniform = self.shader.get_uniform_location('charUVWidth') self.char_uv_height_uniform = self.shader.get_uniform_location('charUVHeight') self.charset_tex_uniform = self.shader.get_uniform_location('charset') self.palette_tex_uniform = self.shader.get_uniform_location('palette') self.grain_tex_uniform = self.shader.get_uniform_location('grain') self.palette_width_uniform = self.shader.get_uniform_location('palTextureWidth') self.grain_strength_uniform = self.shader.get_uniform_location('grainStrength') self.alpha_uniform = self.shader.get_uniform_location('alpha') self.brightness_uniform = self.shader.get_uniform_location('brightness') self.bg_alpha_uniform = self.shader.get_uniform_location('bgColorAlpha') self.create_buffers() # finish if self.app.use_vao: GL.glBindVertexArray(0) if self.log_create_destroy: self.app.log('created: %s' % self) def __str__(self): "for debug purposes, return a concise unique name" for i,r in enumerate(self.art.renderables): if r is self: break return '%s %s %s' % (self.art.get_simple_name(), self.__class__.__name__, i) def create_buffers(self): # vertex positions and elements # determine vertex count needed for render self.vert_count = int(len(self.art.elem_array)) self.vert_buffer, self.elem_buffer = GL.glGenBuffers(2) self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, 'vertPosition', VERT_LENGTH) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # tile data buffers # use GL_DYNAMIC_DRAW given they change every time a char/color changes self.char_buffer, self.uv_buffer = GL.glGenBuffers(2) # character indices (which become vertex UVs) self.update_buffer(self.char_buffer, self.art.chars[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'charIndex', 1) # UV "mods" - modify UV derived from character index self.update_buffer(self.uv_buffer, self.art.uv_mods[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'uvMod', 2) self.fg_buffer, self.bg_buffer = GL.glGenBuffers(2) # foreground/background color indices (which become rgba colors) self.update_buffer(self.fg_buffer, self.art.fg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'fgColorIndex', 1) self.update_buffer(self.bg_buffer, self.art.bg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'bgColorIndex', 1) def update_geo_buffers(self): self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, None, None) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # total vertex count probably changed self.vert_count = int(len(self.art.elem_array)) def update_tile_buffers(self, update_chars, update_uvs, update_fg, update_bg): "Update GL data arrays for tile characters, fg/bg colors, transforms." updates = {} if update_chars: updates[self.char_buffer] = self.art.chars if update_uvs: updates[self.uv_buffer] = self.art.uv_mods if update_fg: updates[self.fg_buffer] = self.art.fg_colors if update_bg: updates[self.bg_buffer] = self.art.bg_colors for update in updates: self.update_buffer(update, updates[update][self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, None, None) def update_buffer(self, buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size): if self.log_buffer_updates: self.app.log('update_buffer: %s, %s, %s, %s, %s, %s, %s' % (buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size)) GL.glBindBuffer(target, buffer_index) GL.glBufferData(target, array.nbytes, array, buffer_type) if attrib_name: attrib = self.shader.get_attrib_location(attrib_name) GL.glEnableVertexAttribArray(attrib) GL.glVertexAttribPointer(attrib, attrib_size, data_type, GL.GL_FALSE, 0, ctypes.c_void_p(0)) # unbind each buffer before binding next GL.glBindBuffer(target, 0) def advance_frame(self): "Advance to our Art's next animation frame." self.set_frame(self.frame + 1) def rewind_frame(self): "Rewind to our Art's previous animation frame." self.set_frame(self.frame - 1) def set_frame(self, new_frame_index): "Set us to display our Art's given animation frame." if new_frame_index == self.frame: return old_frame = self.frame self.frame = new_frame_index % self.art.frames self.update_tile_buffers(True, True, True, True) if self.log_animation: self.app.log('%s animating from frames %s to %s' % (self, old_frame, self.frame)) def start_animating(self): "Start animation playback." self.animating = True self.anim_timer = 0 def stop_animating(self): "Pause animation playback on current frame (in game mode)." self.animating = False # restore to active frame if stopping if not self.app.game_mode: self.set_frame(self.art.active_frame) def set_art(self, new_art): "Display and bind to given Art." if self.art: self.art.renderables.remove(self) self.art = new_art self.reset_size() self.art.renderables.append(self) # make sure frame is valid self.frame %= self.art.frames self.update_geo_buffers() self.update_tile_buffers(True, True, True, True) #print('%s now uses Art %s' % (self, self.art.filename)) def reset_size(self): self.width = self.art.width * self.art.quad_width * abs(self.scale_x) self.height = self.art.height * self.art.quad_height * self.scale_y def move_to(self, x, y, z, travel_time=None): """ Start simple linear interpolation to given destination over given time. Not very useful in Game Mode, mainly used for document switch UI effect in Art Mode. """ # for fixed travel time, set move rate accordingly if travel_time: frames = (travel_time * 1000) / max(self.app.framerate, 30) dx = x - self.x dy = y - self.y dz = z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) self.move_rate = dist / frames else: self.move_rate = self.default_move_rate self.ui_moving = True self.goal_x, self.goal_y, self.goal_z = x, y, z if self.log_animation: self.app.log('%s will move to %s,%s' % (self.art.filename, self.goal_x, self.goal_y)) def snap_to(self, x, y, z): self.x, self.y, self.z = x, y, z self.goal_x, self.goal_y, self.goal_z = x, y, z self.ui_moving = False def update_transform_from_object(self, obj): "Update our position & scale based on that of given game object." self.z = obj.z if self.scale_x != obj.scale_x or self.scale_y != obj.scale_y: self.reset_size() self.x, self.y = obj.x, obj.y if self.use_art_offset: if obj.flip_x: self.x += self.width * obj.art_off_pct_x else: self.x -= self.width * obj.art_off_pct_x self.y += self.height * obj.art_off_pct_y self.scale_x, self.scale_y = obj.scale_x, obj.scale_y if obj.flip_x: self.scale_x *= -1 self.scale_z = obj.scale_z def update_loc(self): # TODO: probably time to bust out the ol' vector module for this stuff # get delta dx = self.goal_x - self.x dy = self.goal_y - self.y dz = self.goal_z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) # close enough? if dist <= self.move_rate: self.x = self.goal_x self.y = self.goal_y self.z = self.goal_z self.ui_moving = False return # normalize inv_dist = 1 / dist dir_x = dx * inv_dist dir_y = dy * inv_dist dir_z = dz * inv_dist self.x += self.move_rate * dir_x self.y += self.move_rate * dir_y self.z += self.move_rate * dir_z #self.app.log('%s moved to %s,%s' % (self, self.x, self.y)) def update(self): if self.go: self.update_transform_from_object(self.go) if self.ui_moving: self.update_loc() if not self.animating: return if self.app.game_mode and self.app.gw.paused: return elapsed = self.app.get_elapsed_time() - self.last_frame_time self.anim_timer += elapsed new_frame = self.frame this_frame_delay = self.art.frame_delays[new_frame] * 1000 while self.anim_timer >= this_frame_delay: self.anim_timer -= this_frame_delay # iterate through frames, but don't call set_frame until we're done new_frame += 1 new_frame %= self.art.frames this_frame_delay = self.art.frame_delays[new_frame] * 1000 # TODO: if new_frame < self.frame, count anim loop? self.set_frame(new_frame) self.last_frame_time = self.app.get_elapsed_time() def destroy(self): if self.app.use_vao: GL.glDeleteVertexArrays(1, [self.vao]) GL.glDeleteBuffers(6, [self.vert_buffer, self.elem_buffer, self.char_buffer, self.uv_buffer, self.fg_buffer, self.bg_buffer]) if self.art and self in self.art.renderables: self.art.renderables.remove(self) if self.log_create_destroy: self.app.log('destroyed: %s' % self) def get_projection_matrix(self): """ UIRenderable overrides this so it doesn't have to override Renderable.render and duplicate lots of code. """ return np.eye(4, 4) if self.exporting else self.camera.projection_matrix def get_view_matrix(self): return np.eye(4, 4) if self.exporting else self.camera.view_matrix def get_loc(self): "Returns world space location as (x, y, z) tuple." export_loc = (-1, 1, 0) return export_loc if self.exporting else (self.x, self.y, self.z) def get_scale(self): "Returns world space scale as (x, y, z) tuple." if not self.exporting: return (self.scale_x, self.scale_y, self.scale_z) x = 2 / (self.art.width * self.art.quad_width) y = 2 / (self.art.height * self.art.quad_height) return (x, y, 1) def render_frame_for_export(self, frame): self.exporting = True self.set_frame(frame) # cache "inactive layer visibility", restore after render ilv = self.art.app.inactive_layer_visibility self.art.app.inactive_layer_visibility = LAYER_VIS_FULL # cursor might be hovering, undo any preview changes for edit in self.art.app.cursor.preview_edits: edit.undo() # update art to commit changes to the renderable self.art.update() self.render() self.art.app.inactive_layer_visibility = ilv self.exporting = False def render(self, layers=None, z_override=None, brightness=1.0): """ Render given list of layers at given Z depth. If layers is None, render all layers. If layers is an int, just render that layer. If z_override is None, render each layer at Z defined in our Art. """ if not self.visible: return GL.glUseProgram(self.shader.program) # bind textures - character set, palette, UI grain GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_tex_uniform, 0) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.charset.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE1) GL.glUniform1i(self.palette_tex_uniform, 1) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.palette.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE2) GL.glUniform1i(self.grain_tex_uniform, 2) GL.glBindTexture(GL.GL_TEXTURE_2D, self.app.ui.grain_texture.gltex) # set active texture unit back after binding 2nd-Nth textures GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_width_uniform, self.art.charset.map_width) GL.glUniform1i(self.charset_height_uniform, self.art.charset.map_height) GL.glUniform1f(self.char_uv_width_uniform, self.art.charset.u_width) GL.glUniform1f(self.char_uv_height_uniform, self.art.charset.v_height) GL.glUniform1f(self.palette_width_uniform, MAX_COLORS) GL.glUniform1f(self.grain_strength_uniform, self.grain_strength) # camera uniforms GL.glUniformMatrix4fv(self.proj_matrix_uniform, 1, GL.GL_FALSE, self.get_projection_matrix()) GL.glUniformMatrix4fv(self.view_matrix_uniform, 1, GL.GL_FALSE, self.get_view_matrix()) # TODO: determine if cost of setting all above uniforms for each # Renderable is significant enough to warrant opti where they're set once GL.glUniform1f(self.bg_alpha_uniform, self.bg_alpha) GL.glUniform1f(self.brightness_uniform, brightness) GL.glUniform3f(self.scale_uniform, *self.get_scale()) # VAO vs non-VAO paths if self.app.use_vao: GL.glBindVertexArray(self.vao) else: attrib = self.shader.get_attrib_location # for brevity vp = ctypes.c_void_p(0) # bind each buffer and set its attrib: # verts GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_buffer) GL.glVertexAttribPointer(attrib('vertPosition'), VERT_LENGTH, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('vertPosition')) # chars GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.char_buffer) GL.glVertexAttribPointer(attrib('charIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('charIndex')) # uvs GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.uv_buffer) GL.glVertexAttribPointer(attrib('uvMod'), 2, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('uvMod')) # fg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.fg_buffer) GL.glVertexAttribPointer(attrib('fgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('fgColorIndex')) # bg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.bg_buffer) GL.glVertexAttribPointer(attrib('bgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('bgColorIndex')) # finally, bind element buffer GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.elem_buffer) GL.glEnable(GL.GL_BLEND) GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) # draw all specified layers if no list given if layers is None: # sort layers in Z depth layers = list(range(self.art.layers)) layers.sort(key=lambda i: self.art.layers_z[i], reverse=False) # handle a single int param elif type(layers) is int: layers = [layers] layer_size = int(len(self.art.elem_array) / self.art.layers) for i in layers: # skip game mode-hidden layers if not self.app.show_hidden_layers and not self.art.layers_visibility[i]: continue layer_start = i * layer_size layer_end = layer_start + layer_size # for active art, dim all but active layer based on UI setting if not self.app.game_mode and self.art is self.app.ui.active_art and i != self.art.active_layer: GL.glUniform1f(self.alpha_uniform, self.alpha * self.app.inactive_layer_visibility) else: GL.glUniform1f(self.alpha_uniform, self.alpha) # use position offset instead of baked-in Z for layers - this # way a layer's Z can change w/o rebuilding its vert array x, y, z = self.get_loc() # for export, render all layers at same Z if not self.exporting: z += self.art.layers_z[i] z = z_override if z_override else z GL.glUniform3f(self.position_uniform, x, y, z) GL.glDrawElements(GL.GL_TRIANGLES, layer_size, GL.GL_UNSIGNED_INT, ctypes.c_void_p(layer_start * ctypes.sizeof(ctypes.c_uint))) GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0) GL.glDisable(GL.GL_BLEND) if self.app.use_vao: GL.glBindVertexArray(0) GL.glUseProgram(0) class OnionTileRenderable(TileRenderable): "TileRenderable subclass used for onion skin display in Art Mode animation." # never animate def start_animating(self): pass def stop_animating(self): pass class GameObjectRenderable(TileRenderable): """ TileRenderable subclass used by GameObjects. Almost no custom logic for now. """ def get_loc(self): """ Returns world space location as (x, y, z) tuple, offset by our GameObject's location. """ x, y, z = self.x, self.y, self.z if self.go: off_x, off_y, off_z = self.go.get_render_offset() x += off_x y += off_y z += off_z return x, y, z
View Source
class TileRenderable: """ 3D visual representation of an Art. Each layer is rendered as grids of rectangular OpenGL triangle-pairs. Animation frames are uploaded into our buffers from source Art's numpy arrays. """ vert_shader_source = 'renderable_v.glsl' "vertex shader: includes view projection matrix, XYZ camera uniforms." frag_shader_source = 'renderable_f.glsl' "Pixel shader: handles FG/BG colors." log_create_destroy = False log_animation = False log_buffer_updates = False grain_strength = 0. alpha = 1. "Alpha (0 to 1) for entire Renderable." bg_alpha = 1. "Alpha (0 to 1) *only* for tile background colors." default_move_rate = 1 use_art_offset = True "Use game object's art_off_pct values." def __init__(self, app, art, game_object=None): "Create Renderable with given Art, optionally bound to given GameObject" self.app = app "Application that renders us." self.art = art "Art we get data from." self.art.renderables.append(self) self.go = game_object "GameObject we're attached to." self.exporting = False "Set True momentarily by image export process; users shouldn't touch." self.visible = True "Boolean for easy render / don't-render functionality." self.frame = self.art.active_frame or 0 "Frame of our art's animation we're currently on" self.animating = False self.anim_timer, self.last_frame_time = 0, 0 # world space position and scale self.x, self.y, self.z = 0, 0, 0 self.scale_x, self.scale_y, self.scale_z = 1, 1, 1 if self.go: self.scale_x = self.go.scale_x self.scale_y = self.go.scale_y self.scale_z = self.go.scale_z # width and height in XY render space self.width, self.height = 1, 1 self.reset_size() # TODO: object rotation matrix, if needed self.goal_x, self.goal_y, self.goal_z = 0, 0, 0 self.move_rate = self.default_move_rate # marked True when UI is interpolating it self.ui_moving = False self.camera = self.app.camera # bind VAO etc before doing shaders etc if self.app.use_vao: self.vao = GL.glGenVertexArrays(1) GL.glBindVertexArray(self.vao) self.shader = self.app.sl.new_shader(self.vert_shader_source, self.frag_shader_source) self.proj_matrix_uniform = self.shader.get_uniform_location('projection') self.view_matrix_uniform = self.shader.get_uniform_location('view') self.position_uniform = self.shader.get_uniform_location('objectPosition') self.scale_uniform = self.shader.get_uniform_location('objectScale') self.charset_width_uniform = self.shader.get_uniform_location('charMapWidth') self.charset_height_uniform = self.shader.get_uniform_location('charMapHeight') self.char_uv_width_uniform = self.shader.get_uniform_location('charUVWidth') self.char_uv_height_uniform = self.shader.get_uniform_location('charUVHeight') self.charset_tex_uniform = self.shader.get_uniform_location('charset') self.palette_tex_uniform = self.shader.get_uniform_location('palette') self.grain_tex_uniform = self.shader.get_uniform_location('grain') self.palette_width_uniform = self.shader.get_uniform_location('palTextureWidth') self.grain_strength_uniform = self.shader.get_uniform_location('grainStrength') self.alpha_uniform = self.shader.get_uniform_location('alpha') self.brightness_uniform = self.shader.get_uniform_location('brightness') self.bg_alpha_uniform = self.shader.get_uniform_location('bgColorAlpha') self.create_buffers() # finish if self.app.use_vao: GL.glBindVertexArray(0) if self.log_create_destroy: self.app.log('created: %s' % self) def __str__(self): "for debug purposes, return a concise unique name" for i,r in enumerate(self.art.renderables): if r is self: break return '%s %s %s' % (self.art.get_simple_name(), self.__class__.__name__, i) def create_buffers(self): # vertex positions and elements # determine vertex count needed for render self.vert_count = int(len(self.art.elem_array)) self.vert_buffer, self.elem_buffer = GL.glGenBuffers(2) self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, 'vertPosition', VERT_LENGTH) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # tile data buffers # use GL_DYNAMIC_DRAW given they change every time a char/color changes self.char_buffer, self.uv_buffer = GL.glGenBuffers(2) # character indices (which become vertex UVs) self.update_buffer(self.char_buffer, self.art.chars[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'charIndex', 1) # UV "mods" - modify UV derived from character index self.update_buffer(self.uv_buffer, self.art.uv_mods[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'uvMod', 2) self.fg_buffer, self.bg_buffer = GL.glGenBuffers(2) # foreground/background color indices (which become rgba colors) self.update_buffer(self.fg_buffer, self.art.fg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'fgColorIndex', 1) self.update_buffer(self.bg_buffer, self.art.bg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'bgColorIndex', 1) def update_geo_buffers(self): self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, None, None) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # total vertex count probably changed self.vert_count = int(len(self.art.elem_array)) def update_tile_buffers(self, update_chars, update_uvs, update_fg, update_bg): "Update GL data arrays for tile characters, fg/bg colors, transforms." updates = {} if update_chars: updates[self.char_buffer] = self.art.chars if update_uvs: updates[self.uv_buffer] = self.art.uv_mods if update_fg: updates[self.fg_buffer] = self.art.fg_colors if update_bg: updates[self.bg_buffer] = self.art.bg_colors for update in updates: self.update_buffer(update, updates[update][self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, None, None) def update_buffer(self, buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size): if self.log_buffer_updates: self.app.log('update_buffer: %s, %s, %s, %s, %s, %s, %s' % (buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size)) GL.glBindBuffer(target, buffer_index) GL.glBufferData(target, array.nbytes, array, buffer_type) if attrib_name: attrib = self.shader.get_attrib_location(attrib_name) GL.glEnableVertexAttribArray(attrib) GL.glVertexAttribPointer(attrib, attrib_size, data_type, GL.GL_FALSE, 0, ctypes.c_void_p(0)) # unbind each buffer before binding next GL.glBindBuffer(target, 0) def advance_frame(self): "Advance to our Art's next animation frame." self.set_frame(self.frame + 1) def rewind_frame(self): "Rewind to our Art's previous animation frame." self.set_frame(self.frame - 1) def set_frame(self, new_frame_index): "Set us to display our Art's given animation frame." if new_frame_index == self.frame: return old_frame = self.frame self.frame = new_frame_index % self.art.frames self.update_tile_buffers(True, True, True, True) if self.log_animation: self.app.log('%s animating from frames %s to %s' % (self, old_frame, self.frame)) def start_animating(self): "Start animation playback." self.animating = True self.anim_timer = 0 def stop_animating(self): "Pause animation playback on current frame (in game mode)." self.animating = False # restore to active frame if stopping if not self.app.game_mode: self.set_frame(self.art.active_frame) def set_art(self, new_art): "Display and bind to given Art." if self.art: self.art.renderables.remove(self) self.art = new_art self.reset_size() self.art.renderables.append(self) # make sure frame is valid self.frame %= self.art.frames self.update_geo_buffers() self.update_tile_buffers(True, True, True, True) #print('%s now uses Art %s' % (self, self.art.filename)) def reset_size(self): self.width = self.art.width * self.art.quad_width * abs(self.scale_x) self.height = self.art.height * self.art.quad_height * self.scale_y def move_to(self, x, y, z, travel_time=None): """ Start simple linear interpolation to given destination over given time. Not very useful in Game Mode, mainly used for document switch UI effect in Art Mode. """ # for fixed travel time, set move rate accordingly if travel_time: frames = (travel_time * 1000) / max(self.app.framerate, 30) dx = x - self.x dy = y - self.y dz = z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) self.move_rate = dist / frames else: self.move_rate = self.default_move_rate self.ui_moving = True self.goal_x, self.goal_y, self.goal_z = x, y, z if self.log_animation: self.app.log('%s will move to %s,%s' % (self.art.filename, self.goal_x, self.goal_y)) def snap_to(self, x, y, z): self.x, self.y, self.z = x, y, z self.goal_x, self.goal_y, self.goal_z = x, y, z self.ui_moving = False def update_transform_from_object(self, obj): "Update our position & scale based on that of given game object." self.z = obj.z if self.scale_x != obj.scale_x or self.scale_y != obj.scale_y: self.reset_size() self.x, self.y = obj.x, obj.y if self.use_art_offset: if obj.flip_x: self.x += self.width * obj.art_off_pct_x else: self.x -= self.width * obj.art_off_pct_x self.y += self.height * obj.art_off_pct_y self.scale_x, self.scale_y = obj.scale_x, obj.scale_y if obj.flip_x: self.scale_x *= -1 self.scale_z = obj.scale_z def update_loc(self): # TODO: probably time to bust out the ol' vector module for this stuff # get delta dx = self.goal_x - self.x dy = self.goal_y - self.y dz = self.goal_z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) # close enough? if dist <= self.move_rate: self.x = self.goal_x self.y = self.goal_y self.z = self.goal_z self.ui_moving = False return # normalize inv_dist = 1 / dist dir_x = dx * inv_dist dir_y = dy * inv_dist dir_z = dz * inv_dist self.x += self.move_rate * dir_x self.y += self.move_rate * dir_y self.z += self.move_rate * dir_z #self.app.log('%s moved to %s,%s' % (self, self.x, self.y)) def update(self): if self.go: self.update_transform_from_object(self.go) if self.ui_moving: self.update_loc() if not self.animating: return if self.app.game_mode and self.app.gw.paused: return elapsed = self.app.get_elapsed_time() - self.last_frame_time self.anim_timer += elapsed new_frame = self.frame this_frame_delay = self.art.frame_delays[new_frame] * 1000 while self.anim_timer >= this_frame_delay: self.anim_timer -= this_frame_delay # iterate through frames, but don't call set_frame until we're done new_frame += 1 new_frame %= self.art.frames this_frame_delay = self.art.frame_delays[new_frame] * 1000 # TODO: if new_frame < self.frame, count anim loop? self.set_frame(new_frame) self.last_frame_time = self.app.get_elapsed_time() def destroy(self): if self.app.use_vao: GL.glDeleteVertexArrays(1, [self.vao]) GL.glDeleteBuffers(6, [self.vert_buffer, self.elem_buffer, self.char_buffer, self.uv_buffer, self.fg_buffer, self.bg_buffer]) if self.art and self in self.art.renderables: self.art.renderables.remove(self) if self.log_create_destroy: self.app.log('destroyed: %s' % self) def get_projection_matrix(self): """ UIRenderable overrides this so it doesn't have to override Renderable.render and duplicate lots of code. """ return np.eye(4, 4) if self.exporting else self.camera.projection_matrix def get_view_matrix(self): return np.eye(4, 4) if self.exporting else self.camera.view_matrix def get_loc(self): "Returns world space location as (x, y, z) tuple." export_loc = (-1, 1, 0) return export_loc if self.exporting else (self.x, self.y, self.z) def get_scale(self): "Returns world space scale as (x, y, z) tuple." if not self.exporting: return (self.scale_x, self.scale_y, self.scale_z) x = 2 / (self.art.width * self.art.quad_width) y = 2 / (self.art.height * self.art.quad_height) return (x, y, 1) def render_frame_for_export(self, frame): self.exporting = True self.set_frame(frame) # cache "inactive layer visibility", restore after render ilv = self.art.app.inactive_layer_visibility self.art.app.inactive_layer_visibility = LAYER_VIS_FULL # cursor might be hovering, undo any preview changes for edit in self.art.app.cursor.preview_edits: edit.undo() # update art to commit changes to the renderable self.art.update() self.render() self.art.app.inactive_layer_visibility = ilv self.exporting = False def render(self, layers=None, z_override=None, brightness=1.0): """ Render given list of layers at given Z depth. If layers is None, render all layers. If layers is an int, just render that layer. If z_override is None, render each layer at Z defined in our Art. """ if not self.visible: return GL.glUseProgram(self.shader.program) # bind textures - character set, palette, UI grain GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_tex_uniform, 0) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.charset.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE1) GL.glUniform1i(self.palette_tex_uniform, 1) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.palette.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE2) GL.glUniform1i(self.grain_tex_uniform, 2) GL.glBindTexture(GL.GL_TEXTURE_2D, self.app.ui.grain_texture.gltex) # set active texture unit back after binding 2nd-Nth textures GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_width_uniform, self.art.charset.map_width) GL.glUniform1i(self.charset_height_uniform, self.art.charset.map_height) GL.glUniform1f(self.char_uv_width_uniform, self.art.charset.u_width) GL.glUniform1f(self.char_uv_height_uniform, self.art.charset.v_height) GL.glUniform1f(self.palette_width_uniform, MAX_COLORS) GL.glUniform1f(self.grain_strength_uniform, self.grain_strength) # camera uniforms GL.glUniformMatrix4fv(self.proj_matrix_uniform, 1, GL.GL_FALSE, self.get_projection_matrix()) GL.glUniformMatrix4fv(self.view_matrix_uniform, 1, GL.GL_FALSE, self.get_view_matrix()) # TODO: determine if cost of setting all above uniforms for each # Renderable is significant enough to warrant opti where they're set once GL.glUniform1f(self.bg_alpha_uniform, self.bg_alpha) GL.glUniform1f(self.brightness_uniform, brightness) GL.glUniform3f(self.scale_uniform, *self.get_scale()) # VAO vs non-VAO paths if self.app.use_vao: GL.glBindVertexArray(self.vao) else: attrib = self.shader.get_attrib_location # for brevity vp = ctypes.c_void_p(0) # bind each buffer and set its attrib: # verts GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_buffer) GL.glVertexAttribPointer(attrib('vertPosition'), VERT_LENGTH, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('vertPosition')) # chars GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.char_buffer) GL.glVertexAttribPointer(attrib('charIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('charIndex')) # uvs GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.uv_buffer) GL.glVertexAttribPointer(attrib('uvMod'), 2, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('uvMod')) # fg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.fg_buffer) GL.glVertexAttribPointer(attrib('fgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('fgColorIndex')) # bg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.bg_buffer) GL.glVertexAttribPointer(attrib('bgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('bgColorIndex')) # finally, bind element buffer GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.elem_buffer) GL.glEnable(GL.GL_BLEND) GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) # draw all specified layers if no list given if layers is None: # sort layers in Z depth layers = list(range(self.art.layers)) layers.sort(key=lambda i: self.art.layers_z[i], reverse=False) # handle a single int param elif type(layers) is int: layers = [layers] layer_size = int(len(self.art.elem_array) / self.art.layers) for i in layers: # skip game mode-hidden layers if not self.app.show_hidden_layers and not self.art.layers_visibility[i]: continue layer_start = i * layer_size layer_end = layer_start + layer_size # for active art, dim all but active layer based on UI setting if not self.app.game_mode and self.art is self.app.ui.active_art and i != self.art.active_layer: GL.glUniform1f(self.alpha_uniform, self.alpha * self.app.inactive_layer_visibility) else: GL.glUniform1f(self.alpha_uniform, self.alpha) # use position offset instead of baked-in Z for layers - this # way a layer's Z can change w/o rebuilding its vert array x, y, z = self.get_loc() # for export, render all layers at same Z if not self.exporting: z += self.art.layers_z[i] z = z_override if z_override else z GL.glUniform3f(self.position_uniform, x, y, z) GL.glDrawElements(GL.GL_TRIANGLES, layer_size, GL.GL_UNSIGNED_INT, ctypes.c_void_p(layer_start * ctypes.sizeof(ctypes.c_uint))) GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0) GL.glDisable(GL.GL_BLEND) if self.app.use_vao: GL.glBindVertexArray(0) GL.glUseProgram(0)
3D visual representation of an Art. Each layer is rendered as grids of rectangular OpenGL triangle-pairs. Animation frames are uploaded into our buffers from source Art's numpy arrays.
View Source
def __init__(self, app, art, game_object=None): "Create Renderable with given Art, optionally bound to given GameObject" self.app = app "Application that renders us." self.art = art "Art we get data from." self.art.renderables.append(self) self.go = game_object "GameObject we're attached to." self.exporting = False "Set True momentarily by image export process; users shouldn't touch." self.visible = True "Boolean for easy render / don't-render functionality." self.frame = self.art.active_frame or 0 "Frame of our art's animation we're currently on" self.animating = False self.anim_timer, self.last_frame_time = 0, 0 # world space position and scale self.x, self.y, self.z = 0, 0, 0 self.scale_x, self.scale_y, self.scale_z = 1, 1, 1 if self.go: self.scale_x = self.go.scale_x self.scale_y = self.go.scale_y self.scale_z = self.go.scale_z # width and height in XY render space self.width, self.height = 1, 1 self.reset_size() # TODO: object rotation matrix, if needed self.goal_x, self.goal_y, self.goal_z = 0, 0, 0 self.move_rate = self.default_move_rate # marked True when UI is interpolating it self.ui_moving = False self.camera = self.app.camera # bind VAO etc before doing shaders etc if self.app.use_vao: self.vao = GL.glGenVertexArrays(1) GL.glBindVertexArray(self.vao) self.shader = self.app.sl.new_shader(self.vert_shader_source, self.frag_shader_source) self.proj_matrix_uniform = self.shader.get_uniform_location('projection') self.view_matrix_uniform = self.shader.get_uniform_location('view') self.position_uniform = self.shader.get_uniform_location('objectPosition') self.scale_uniform = self.shader.get_uniform_location('objectScale') self.charset_width_uniform = self.shader.get_uniform_location('charMapWidth') self.charset_height_uniform = self.shader.get_uniform_location('charMapHeight') self.char_uv_width_uniform = self.shader.get_uniform_location('charUVWidth') self.char_uv_height_uniform = self.shader.get_uniform_location('charUVHeight') self.charset_tex_uniform = self.shader.get_uniform_location('charset') self.palette_tex_uniform = self.shader.get_uniform_location('palette') self.grain_tex_uniform = self.shader.get_uniform_location('grain') self.palette_width_uniform = self.shader.get_uniform_location('palTextureWidth') self.grain_strength_uniform = self.shader.get_uniform_location('grainStrength') self.alpha_uniform = self.shader.get_uniform_location('alpha') self.brightness_uniform = self.shader.get_uniform_location('brightness') self.bg_alpha_uniform = self.shader.get_uniform_location('bgColorAlpha') self.create_buffers() # finish if self.app.use_vao: GL.glBindVertexArray(0) if self.log_create_destroy: self.app.log('created: %s' % self)
Create Renderable with given Art, optionally bound to given GameObject
vertex shader: includes view projection matrix, XYZ camera uniforms.
Pixel shader: handles FG/BG colors.
Alpha (0 to 1) for entire Renderable.
Alpha (0 to 1) only for tile background colors.
Use game object's art_off_pct values.
Application that renders us.
Art we get data from.
GameObject we're attached to.
Set True momentarily by image export process; users shouldn't touch.
Boolean for easy render / don't-render functionality.
Frame of our art's animation we're currently on
View Source
def create_buffers(self): # vertex positions and elements # determine vertex count needed for render self.vert_count = int(len(self.art.elem_array)) self.vert_buffer, self.elem_buffer = GL.glGenBuffers(2) self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, 'vertPosition', VERT_LENGTH) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # tile data buffers # use GL_DYNAMIC_DRAW given they change every time a char/color changes self.char_buffer, self.uv_buffer = GL.glGenBuffers(2) # character indices (which become vertex UVs) self.update_buffer(self.char_buffer, self.art.chars[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'charIndex', 1) # UV "mods" - modify UV derived from character index self.update_buffer(self.uv_buffer, self.art.uv_mods[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'uvMod', 2) self.fg_buffer, self.bg_buffer = GL.glGenBuffers(2) # foreground/background color indices (which become rgba colors) self.update_buffer(self.fg_buffer, self.art.fg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'fgColorIndex', 1) self.update_buffer(self.bg_buffer, self.art.bg_colors[self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, 'bgColorIndex', 1)
View Source
def update_geo_buffers(self): self.update_buffer(self.vert_buffer, self.art.vert_array, GL.GL_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_FLOAT, None, None) self.update_buffer(self.elem_buffer, self.art.elem_array, GL.GL_ELEMENT_ARRAY_BUFFER, GL.GL_STATIC_DRAW, GL.GL_UNSIGNED_INT, None, None) # total vertex count probably changed self.vert_count = int(len(self.art.elem_array))
View Source
def update_tile_buffers(self, update_chars, update_uvs, update_fg, update_bg): "Update GL data arrays for tile characters, fg/bg colors, transforms." updates = {} if update_chars: updates[self.char_buffer] = self.art.chars if update_uvs: updates[self.uv_buffer] = self.art.uv_mods if update_fg: updates[self.fg_buffer] = self.art.fg_colors if update_bg: updates[self.bg_buffer] = self.art.bg_colors for update in updates: self.update_buffer(update, updates[update][self.frame], GL.GL_ARRAY_BUFFER, GL.GL_DYNAMIC_DRAW, GL.GL_FLOAT, None, None)
Update GL data arrays for tile characters, fg/bg colors, transforms.
View Source
def update_buffer(self, buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size): if self.log_buffer_updates: self.app.log('update_buffer: %s, %s, %s, %s, %s, %s, %s' % (buffer_index, array, target, buffer_type, data_type, attrib_name, attrib_size)) GL.glBindBuffer(target, buffer_index) GL.glBufferData(target, array.nbytes, array, buffer_type) if attrib_name: attrib = self.shader.get_attrib_location(attrib_name) GL.glEnableVertexAttribArray(attrib) GL.glVertexAttribPointer(attrib, attrib_size, data_type, GL.GL_FALSE, 0, ctypes.c_void_p(0)) # unbind each buffer before binding next GL.glBindBuffer(target, 0)
View Source
def advance_frame(self): "Advance to our Art's next animation frame." self.set_frame(self.frame + 1)
Advance to our Art's next animation frame.
View Source
def rewind_frame(self): "Rewind to our Art's previous animation frame." self.set_frame(self.frame - 1)
Rewind to our Art's previous animation frame.
View Source
def set_frame(self, new_frame_index): "Set us to display our Art's given animation frame." if new_frame_index == self.frame: return old_frame = self.frame self.frame = new_frame_index % self.art.frames self.update_tile_buffers(True, True, True, True) if self.log_animation: self.app.log('%s animating from frames %s to %s' % (self, old_frame, self.frame))
Set us to display our Art's given animation frame.
View Source
def start_animating(self): "Start animation playback." self.animating = True self.anim_timer = 0
Start animation playback.
View Source
def stop_animating(self): "Pause animation playback on current frame (in game mode)." self.animating = False # restore to active frame if stopping if not self.app.game_mode: self.set_frame(self.art.active_frame)
Pause animation playback on current frame (in game mode).
View Source
def set_art(self, new_art): "Display and bind to given Art." if self.art: self.art.renderables.remove(self) self.art = new_art self.reset_size() self.art.renderables.append(self) # make sure frame is valid self.frame %= self.art.frames self.update_geo_buffers() self.update_tile_buffers(True, True, True, True) #print('%s now uses Art %s' % (self, self.art.filename))
Display and bind to given Art.
View Source
def reset_size(self): self.width = self.art.width * self.art.quad_width * abs(self.scale_x) self.height = self.art.height * self.art.quad_height * self.scale_y
View Source
def move_to(self, x, y, z, travel_time=None): """ Start simple linear interpolation to given destination over given time. Not very useful in Game Mode, mainly used for document switch UI effect in Art Mode. """ # for fixed travel time, set move rate accordingly if travel_time: frames = (travel_time * 1000) / max(self.app.framerate, 30) dx = x - self.x dy = y - self.y dz = z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) self.move_rate = dist / frames else: self.move_rate = self.default_move_rate self.ui_moving = True self.goal_x, self.goal_y, self.goal_z = x, y, z if self.log_animation: self.app.log('%s will move to %s,%s' % (self.art.filename, self.goal_x, self.goal_y))
Start simple linear interpolation to given destination over given time. Not very useful in Game Mode, mainly used for document switch UI effect in Art Mode.
View Source
def snap_to(self, x, y, z): self.x, self.y, self.z = x, y, z self.goal_x, self.goal_y, self.goal_z = x, y, z self.ui_moving = False
View Source
def update_transform_from_object(self, obj): "Update our position & scale based on that of given game object." self.z = obj.z if self.scale_x != obj.scale_x or self.scale_y != obj.scale_y: self.reset_size() self.x, self.y = obj.x, obj.y if self.use_art_offset: if obj.flip_x: self.x += self.width * obj.art_off_pct_x else: self.x -= self.width * obj.art_off_pct_x self.y += self.height * obj.art_off_pct_y self.scale_x, self.scale_y = obj.scale_x, obj.scale_y if obj.flip_x: self.scale_x *= -1 self.scale_z = obj.scale_z
Update our position & scale based on that of given game object.
View Source
def update_loc(self): # TODO: probably time to bust out the ol' vector module for this stuff # get delta dx = self.goal_x - self.x dy = self.goal_y - self.y dz = self.goal_z - self.z dist = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) # close enough? if dist <= self.move_rate: self.x = self.goal_x self.y = self.goal_y self.z = self.goal_z self.ui_moving = False return # normalize inv_dist = 1 / dist dir_x = dx * inv_dist dir_y = dy * inv_dist dir_z = dz * inv_dist self.x += self.move_rate * dir_x self.y += self.move_rate * dir_y self.z += self.move_rate * dir_z #self.app.log('%s moved to %s,%s' % (self, self.x, self.y))
View Source
def update(self): if self.go: self.update_transform_from_object(self.go) if self.ui_moving: self.update_loc() if not self.animating: return if self.app.game_mode and self.app.gw.paused: return elapsed = self.app.get_elapsed_time() - self.last_frame_time self.anim_timer += elapsed new_frame = self.frame this_frame_delay = self.art.frame_delays[new_frame] * 1000 while self.anim_timer >= this_frame_delay: self.anim_timer -= this_frame_delay # iterate through frames, but don't call set_frame until we're done new_frame += 1 new_frame %= self.art.frames this_frame_delay = self.art.frame_delays[new_frame] * 1000 # TODO: if new_frame < self.frame, count anim loop? self.set_frame(new_frame) self.last_frame_time = self.app.get_elapsed_time()
View Source
def destroy(self): if self.app.use_vao: GL.glDeleteVertexArrays(1, [self.vao]) GL.glDeleteBuffers(6, [self.vert_buffer, self.elem_buffer, self.char_buffer, self.uv_buffer, self.fg_buffer, self.bg_buffer]) if self.art and self in self.art.renderables: self.art.renderables.remove(self) if self.log_create_destroy: self.app.log('destroyed: %s' % self)
View Source
def get_projection_matrix(self): """ UIRenderable overrides this so it doesn't have to override Renderable.render and duplicate lots of code. """ return np.eye(4, 4) if self.exporting else self.camera.projection_matrix
UIRenderable overrides this so it doesn't have to override Renderable.render and duplicate lots of code.
View Source
def get_view_matrix(self): return np.eye(4, 4) if self.exporting else self.camera.view_matrix
View Source
def get_loc(self): "Returns world space location as (x, y, z) tuple." export_loc = (-1, 1, 0) return export_loc if self.exporting else (self.x, self.y, self.z)
Returns world space location as (x, y, z) tuple.
View Source
def get_scale(self): "Returns world space scale as (x, y, z) tuple." if not self.exporting: return (self.scale_x, self.scale_y, self.scale_z) x = 2 / (self.art.width * self.art.quad_width) y = 2 / (self.art.height * self.art.quad_height) return (x, y, 1)
Returns world space scale as (x, y, z) tuple.
View Source
def render_frame_for_export(self, frame): self.exporting = True self.set_frame(frame) # cache "inactive layer visibility", restore after render ilv = self.art.app.inactive_layer_visibility self.art.app.inactive_layer_visibility = LAYER_VIS_FULL # cursor might be hovering, undo any preview changes for edit in self.art.app.cursor.preview_edits: edit.undo() # update art to commit changes to the renderable self.art.update() self.render() self.art.app.inactive_layer_visibility = ilv self.exporting = False
View Source
def render(self, layers=None, z_override=None, brightness=1.0): """ Render given list of layers at given Z depth. If layers is None, render all layers. If layers is an int, just render that layer. If z_override is None, render each layer at Z defined in our Art. """ if not self.visible: return GL.glUseProgram(self.shader.program) # bind textures - character set, palette, UI grain GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_tex_uniform, 0) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.charset.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE1) GL.glUniform1i(self.palette_tex_uniform, 1) GL.glBindTexture(GL.GL_TEXTURE_2D, self.art.palette.texture.gltex) GL.glActiveTexture(GL.GL_TEXTURE2) GL.glUniform1i(self.grain_tex_uniform, 2) GL.glBindTexture(GL.GL_TEXTURE_2D, self.app.ui.grain_texture.gltex) # set active texture unit back after binding 2nd-Nth textures GL.glActiveTexture(GL.GL_TEXTURE0) GL.glUniform1i(self.charset_width_uniform, self.art.charset.map_width) GL.glUniform1i(self.charset_height_uniform, self.art.charset.map_height) GL.glUniform1f(self.char_uv_width_uniform, self.art.charset.u_width) GL.glUniform1f(self.char_uv_height_uniform, self.art.charset.v_height) GL.glUniform1f(self.palette_width_uniform, MAX_COLORS) GL.glUniform1f(self.grain_strength_uniform, self.grain_strength) # camera uniforms GL.glUniformMatrix4fv(self.proj_matrix_uniform, 1, GL.GL_FALSE, self.get_projection_matrix()) GL.glUniformMatrix4fv(self.view_matrix_uniform, 1, GL.GL_FALSE, self.get_view_matrix()) # TODO: determine if cost of setting all above uniforms for each # Renderable is significant enough to warrant opti where they're set once GL.glUniform1f(self.bg_alpha_uniform, self.bg_alpha) GL.glUniform1f(self.brightness_uniform, brightness) GL.glUniform3f(self.scale_uniform, *self.get_scale()) # VAO vs non-VAO paths if self.app.use_vao: GL.glBindVertexArray(self.vao) else: attrib = self.shader.get_attrib_location # for brevity vp = ctypes.c_void_p(0) # bind each buffer and set its attrib: # verts GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vert_buffer) GL.glVertexAttribPointer(attrib('vertPosition'), VERT_LENGTH, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('vertPosition')) # chars GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.char_buffer) GL.glVertexAttribPointer(attrib('charIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('charIndex')) # uvs GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.uv_buffer) GL.glVertexAttribPointer(attrib('uvMod'), 2, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('uvMod')) # fg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.fg_buffer) GL.glVertexAttribPointer(attrib('fgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('fgColorIndex')) # bg colors GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.bg_buffer) GL.glVertexAttribPointer(attrib('bgColorIndex'), 1, GL.GL_FLOAT, GL.GL_FALSE, 0, vp) GL.glEnableVertexAttribArray(attrib('bgColorIndex')) # finally, bind element buffer GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, self.elem_buffer) GL.glEnable(GL.GL_BLEND) GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) # draw all specified layers if no list given if layers is None: # sort layers in Z depth layers = list(range(self.art.layers)) layers.sort(key=lambda i: self.art.layers_z[i], reverse=False) # handle a single int param elif type(layers) is int: layers = [layers] layer_size = int(len(self.art.elem_array) / self.art.layers) for i in layers: # skip game mode-hidden layers if not self.app.show_hidden_layers and not self.art.layers_visibility[i]: continue layer_start = i * layer_size layer_end = layer_start + layer_size # for active art, dim all but active layer based on UI setting if not self.app.game_mode and self.art is self.app.ui.active_art and i != self.art.active_layer: GL.glUniform1f(self.alpha_uniform, self.alpha * self.app.inactive_layer_visibility) else: GL.glUniform1f(self.alpha_uniform, self.alpha) # use position offset instead of baked-in Z for layers - this # way a layer's Z can change w/o rebuilding its vert array x, y, z = self.get_loc() # for export, render all layers at same Z if not self.exporting: z += self.art.layers_z[i] z = z_override if z_override else z GL.glUniform3f(self.position_uniform, x, y, z) GL.glDrawElements(GL.GL_TRIANGLES, layer_size, GL.GL_UNSIGNED_INT, ctypes.c_void_p(layer_start * ctypes.sizeof(ctypes.c_uint))) GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0) GL.glDisable(GL.GL_BLEND) if self.app.use_vao: GL.glBindVertexArray(0) GL.glUseProgram(0)
Render given list of layers at given Z depth. If layers is None, render all layers. If layers is an int, just render that layer. If z_override is None, render each layer at Z defined in our Art.
View Source
class OnionTileRenderable(TileRenderable): "TileRenderable subclass used for onion skin display in Art Mode animation." # never animate def start_animating(self): pass def stop_animating(self): pass
TileRenderable subclass used for onion skin display in Art Mode animation.
View Source
def start_animating(self): pass
Start animation playback.
View Source
def stop_animating(self): pass
Pause animation playback on current frame (in game mode).
Inherited Members
- renderable.TileRenderable
- TileRenderable
- vert_shader_source
- frag_shader_source
- log_create_destroy
- log_animation
- log_buffer_updates
- grain_strength
- alpha
- bg_alpha
- default_move_rate
- use_art_offset
- app
- art
- go
- exporting
- visible
- frame
- create_buffers
- update_geo_buffers
- update_tile_buffers
- update_buffer
- advance_frame
- rewind_frame
- set_frame
- set_art
- reset_size
- move_to
- snap_to
- update_transform_from_object
- update_loc
- update
- destroy
- get_projection_matrix
- get_view_matrix
- get_loc
- get_scale
- render_frame_for_export
- render
View Source
class GameObjectRenderable(TileRenderable): """ TileRenderable subclass used by GameObjects. Almost no custom logic for now. """ def get_loc(self): """ Returns world space location as (x, y, z) tuple, offset by our GameObject's location. """ x, y, z = self.x, self.y, self.z if self.go: off_x, off_y, off_z = self.go.get_render_offset() x += off_x y += off_y z += off_z return x, y, z
TileRenderable subclass used by GameObjects. Almost no custom logic for now.
View Source
def get_loc(self): """ Returns world space location as (x, y, z) tuple, offset by our GameObject's location. """ x, y, z = self.x, self.y, self.z if self.go: off_x, off_y, off_z = self.go.get_render_offset() x += off_x y += off_y z += off_z return x, y, z
Returns world space location as (x, y, z) tuple, offset by our GameObject's location.
Inherited Members
- renderable.TileRenderable
- TileRenderable
- vert_shader_source
- frag_shader_source
- log_create_destroy
- log_animation
- log_buffer_updates
- grain_strength
- alpha
- bg_alpha
- default_move_rate
- use_art_offset
- app
- art
- go
- exporting
- visible
- frame
- create_buffers
- update_geo_buffers
- update_tile_buffers
- update_buffer
- advance_frame
- rewind_frame
- set_frame
- start_animating
- stop_animating
- set_art
- reset_size
- move_to
- snap_to
- update_transform_from_object
- update_loc
- update
- destroy
- get_projection_matrix
- get_view_matrix
- get_scale
- render_frame_for_export
- render