From 90fa352a508e0d9ccbab7046cdcc33c30b22e828 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Sun, 3 Jan 2021 18:03:58 -0700 Subject: [PATCH] Split out editor drawing and slightly speed up normal drawing --- js/main-editor.js | 30 +++++++--- js/renderer-canvas.js | 134 +++++++++++++++++++++++++++--------------- 2 files changed, 106 insertions(+), 58 deletions(-) diff --git a/js/main-editor.js b/js/main-editor.js index fc25df0..e2eff09 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -282,7 +282,7 @@ class EditorLevelBrowserOverlay extends DialogOverlay { let stored_level = this.conductor.stored_game.load_level(index); this.renderer.set_level(stored_level); this.renderer.set_viewport_size(stored_level.size_x, stored_level.size_y); - this.renderer.draw(); + this.renderer.draw_static_region(0, 0, stored_level.size_x, stored_level.size_y); let canvas = mk('canvas', { width: stored_level.size_x * this.conductor.tileset.size_x / 4, height: stored_level.size_y * this.conductor.tileset.size_y / 4, @@ -2424,7 +2424,7 @@ export class Editor extends PrimaryView { ev.stopPropagation(); // FIXME eventually this should be automatic - this.renderer.draw(); + this.redraw_entire_level(); } else if (ev.button === 1) { // Middle button: always pan @@ -2443,7 +2443,7 @@ export class Editor extends PrimaryView { ev.preventDefault(); ev.stopPropagation(); - this.renderer.draw(); + this.redraw_entire_level(); } }); // Once the mouse is down, we should accept mouse movement anywhere @@ -2471,7 +2471,7 @@ export class Editor extends PrimaryView { this.mouse_op.do_mousemove(ev); // FIXME !!! - this.renderer.draw(); + this.redraw_entire_level(); }); // TODO should this happen for a mouseup anywhere? this.viewport_el.addEventListener('mouseup', ev => { @@ -2720,7 +2720,7 @@ export class Editor extends PrimaryView { activate() { super.activate(); - this.renderer.draw(); + this.redraw_entire_level(); } // Level creation, management, and saving @@ -2934,7 +2934,7 @@ export class Editor extends PrimaryView { this.renderer.set_level(stored_level); if (this.active) { - this.renderer.draw(); + this.redraw_entire_level(); } if (this.save_button) { @@ -3073,6 +3073,8 @@ export class Editor extends PrimaryView { this.palette_actor_direction = DIRECTIONS[this.palette_actor_direction].left; } + // -- Drawing -- + mark_tile_dirty(tile) { // TODO partial redraws! until then, redraw everything if (tile === this.palette_selection) { @@ -3081,14 +3083,20 @@ export class Editor extends PrimaryView { this.selected_tile_el.append(this.renderer.create_tile_type_canvas(tile.type.name, tile)); } else { - this.renderer.draw(); + this.redraw_entire_level(); } } mark_cell_dirty(cell) { - this.renderer.draw(); + this.redraw_entire_level(); } + redraw_entire_level() { + this.renderer.draw_static_region(0, 0, this.stored_level.size_x, this.stored_level.size_y); + } + + // -- Utility/inspection -- + is_in_bounds(x, y) { return 0 <= x && x < this.stored_level.size_x && 0 <= y && y < this.stored_level.size_y; } @@ -3102,6 +3110,8 @@ export class Editor extends PrimaryView { } } + // -- Mutation -- + place_in_cell(x, y, tile) { // TODO weird api? if (! tile) @@ -3167,6 +3177,8 @@ export class Editor extends PrimaryView { this.mark_cell_dirty(cell); } + // -- Misc?? -- + open_tile_prop_overlay(tile, x0, y0) { this.cancel_mouse_operation(); // FIXME keep these around, don't recreate them constantly @@ -3222,6 +3234,6 @@ export class Editor extends PrimaryView { this.stored_level.size_x = size_x; this.stored_level.size_y = size_y; this.update_viewport_size(); - this.renderer.draw(); + this.redraw_entire_level(); } } diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index b74b183..f148ed4 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -105,21 +105,11 @@ export class CanvasRenderer { // TODO what about levels smaller than the viewport...? shrink the canvas in set_level? let xmargin = (this.viewport_size_x - 1) / 2; let ymargin = (this.viewport_size_y - 1) / 2; - let px, py; - // FIXME editor vs player - if (this.level.player) { - [px, py] = this.level.player.visual_position(tic_offset); - } - else { - [px, py] = [0, 0]; - } + let [px, py] = this.level.player.visual_position(tic_offset); // Figure out where to start drawing // TODO support overlapping regions better let x0 = px - xmargin; let y0 = py - ymargin; - // FIXME editor vs player again ugh, which is goofy since none of this is even relevant; - // maybe need to have a separate positioning method - if (this.level.stored_level) { for (let region of this.level.stored_level.camera_regions) { if (px >= region.left && px < region.right && py >= region.top && py < region.bottom) @@ -128,7 +118,6 @@ export class CanvasRenderer { y0 = Math.max(region.top, Math.min(region.bottom - this.viewport_size_y, y0)); } } - } // Always keep us within the map bounds x0 = Math.max(0, Math.min(this.level.size_x - this.viewport_size_x, x0)); y0 = Math.max(0, Math.min(this.level.size_y - this.viewport_size_y, y0)); @@ -152,55 +141,67 @@ export class CanvasRenderer { // include the tiles just outside it, so we allow this fencepost problem to fly let x1 = Math.min(this.level.size_x - 1, Math.ceil(x0 + this.viewport_size_x)); let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y)); - // Draw one layer at a time, so animated objects aren't overdrawn by + // Tiles in motion (i.e., actors) don't want to be overdrawn by neighboring tiles' terrain, + // so draw in three passes: everything below actors, actors, and everything above actors // neighboring terrain - // FIXME this is a bit inefficient when there are a lot of rarely-used layers; consider - // instead drawing everything under actors, then actors, then everything above actors? - // (note: will need to first fix the game to ensure everything is stacked correctly!) - for (let layer = 0; layer < LAYERS.MAX; layer++) { - for (let x = xf0; x <= x1; x++) { - for (let y = yf0; y <= y1; y++) { - let cell = this.level.cell(x, y); + for (let x = xf0; x <= x1; x++) { + for (let y = yf0; y <= y1; y++) { + let cell = this.level.cell(x, y); + for (let layer = 0; layer < LAYERS.actor; layer++) { let tile = cell[layer]; if (! tile) continue; - let vx, vy; - if (tile.type.is_actor && - // FIXME kind of a hack for the editor, which uses bare tile objects - tile.visual_position) - { - // Handle smooth scrolling - [vx, vy] = tile.visual_position(tic_offset); - // Round this to the pixel grid too! - vx = Math.floor(vx * tw + 0.5) / tw; - vy = Math.floor(vy * th + 0.5) / th; - } - else { - // Non-actors can't move - vx = x; - vy = y; - } + this.tileset.draw( + tile, tic, this.perception, + this._make_tileset_blitter(this.ctx, x - x0, y - y0)); + } + } + } + for (let x = xf0; x <= x1; x++) { + for (let y = yf0; y <= y1; y++) { + let cell = this.level.cell(x, y); + let actor = cell[LAYERS.actor]; + if (! actor) + continue; - // For actors (i.e., blocks), perception only applies if there's something - // of potential interest underneath - let perception = this.perception; - if (perception !== 'normal' && tile.type.is_actor && - ! cell.some(t => - t && t.type.layer < layer && - ! (t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) - { - perception = 'normal'; - } + // Handle smooth scrolling + let [vx, vy] = actor.visual_position(tic_offset); + // Round this to the pixel grid too! + vx = Math.floor(vx * tw + 0.5) / tw; + vy = Math.floor(vy * th + 0.5) / th; + + // For actors (i.e., blocks), perception only applies if there's something of + // potential interest underneath + let perception = this.perception; + if (perception !== 'normal' && + ! cell.some(t => t && t.type.layer < layer && ! ( + t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) + { + perception = 'normal'; + } + + this.tileset.draw( + actor, tic, perception, + this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); + } + } + for (let x = xf0; x <= x1; x++) { + for (let y = yf0; y <= y1; y++) { + let cell = this.level.cell(x, y); + for (let layer = LAYERS.actor + 1; layer < LAYERS.MAX; layer++) { + let tile = cell[layer]; + if (! tile) + continue; this.tileset.draw( - tile, tic, perception, - this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); + tile, tic, this.perception, + this._make_tileset_blitter(this.ctx, x - x0, y - y0)); } } } - if (this.show_actor_bboxes && this.level.constructor.name === 'Level') { // FIXME dumb hack so this doesn't happen in editor + if (this.show_actor_bboxes) { this.ctx.fillStyle = '#f004'; for (let x = xf0; x <= x1; x++) { for (let y = yf0; y <= y1; y++) { @@ -233,6 +234,41 @@ export class CanvasRenderer { } } + // Used by the editor and map previews. Draws a region of the level (probably a StoredLevel), + // assuming nothing is moving. + draw_static_region(x0, y0, x1, y1, destx = x0, desty = y0) { + for (let x = x0; x <= x1; x++) { + for (let y = y0; y <= y1; y++) { + let cell = this.level.cell(x, y); + if (! cell) + continue; + + let seen_anything_interesting; + for (let tile of cell) { + if (! tile) + continue; + + // For actors (i.e., blocks), perception only applies if there's something + // of potential interest underneath + let perception = this.perception; + if (perception !== 'normal' && tile.type.is_actor && ! seen_anything_interesting) { + perception = 'normal'; + } + + if (tile.type.layer < LAYERS.actor && ! ( + tile.type.name === 'floor' && (tile.wire_directions | tile.wire_tunnel_directions) === 0)) + { + seen_anything_interesting = true; + } + + this.tileset.draw( + tile, 0, perception, + this._make_tileset_blitter(this.ctx, destx + x - x0, desty + y - y0)); + } + } + } + } + create_tile_type_canvas(name, tile = null) { let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y}); let ctx = canvas.getContext('2d');