From cda31732b1a60eb4619281c6845b3c6f635a178f Mon Sep 17 00:00:00 2001 From: tobspr Date: Fri, 28 Aug 2020 20:56:02 +0200 Subject: [PATCH] Fix rendering bug --- res_raw/atlas.tps | 10 + src/js/game/map_chunk_view.js | 555 +++++++++++++++++----------------- src/js/game/map_view.js | 505 ++++++++++++++++--------------- 3 files changed, 544 insertions(+), 526 deletions(-) diff --git a/res_raw/atlas.tps b/res_raw/atlas.tps index 95fe82bf..d2332b2d 100644 --- a/res_raw/atlas.tps +++ b/res_raw/atlas.tps @@ -277,6 +277,11 @@ sprites/blueprints/underground_belt_entry.png sprites/blueprints/underground_belt_exit-tier2.png sprites/blueprints/underground_belt_exit.png + sprites/blueprints/virtual_processor-analyzer.png + sprites/blueprints/virtual_processor-rotater.png + sprites/blueprints/virtual_processor-shapecompare.png + sprites/blueprints/virtual_processor-unstacker.png + sprites/blueprints/virtual_processor.png sprites/blueprints/wire_tunnel-coating.png sprites/blueprints/wire_tunnel.png sprites/buildings/constant_signal.png @@ -296,6 +301,11 @@ sprites/buildings/underground_belt_entry.png sprites/buildings/underground_belt_exit-tier2.png sprites/buildings/underground_belt_exit.png + sprites/buildings/virtual_processor-analyzer.png + sprites/buildings/virtual_processor-rotater.png + sprites/buildings/virtual_processor-shapecompare.png + sprites/buildings/virtual_processor-unstacker.png + sprites/buildings/virtual_processor.png sprites/buildings/wire_tunnel-coating.png sprites/buildings/wire_tunnel.png sprites/wires/lever_on.png diff --git a/src/js/game/map_chunk_view.js b/src/js/game/map_chunk_view.js index 0918e7af..1ea04955 100644 --- a/src/js/game/map_chunk_view.js +++ b/src/js/game/map_chunk_view.js @@ -1,274 +1,281 @@ -import { globalConfig } from "../core/config"; -import { DrawParameters } from "../core/draw_parameters"; -import { getBuildingDataFromCode } from "./building_codes"; -import { Entity } from "./entity"; -import { MapChunk } from "./map_chunk"; -import { GameRoot } from "./root"; -import { THEME } from "./theme"; -import { drawSpriteClipped } from "../core/draw_utils"; - -export const CHUNK_OVERLAY_RES = 3; - -export class MapChunkView extends MapChunk { - /** - * - * @param {GameRoot} root - * @param {number} x - * @param {number} y - */ - constructor(root, x, y) { - super(root, x, y); - - /** - * Whenever something changes, we increase this number - so we know we need to redraw - */ - this.renderIteration = 0; - - this.markDirty(); - } - - /** - * Marks this chunk as dirty, rerendering all caches - */ - markDirty() { - ++this.renderIteration; - this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration; - } - - /** - * Draws the background layer - * @param {DrawParameters} parameters - */ - drawBackgroundLayer(parameters) { - const systems = this.root.systemMgr.systems; - systems.mapResources.drawChunk(parameters, this); - systems.beltUnderlays.drawChunk(parameters, this); - systems.belt.drawChunk(parameters, this); - } - - /** - * Draws the foreground layer - * @param {DrawParameters} parameters - */ - drawForegroundLayer(parameters) { - const systems = this.root.systemMgr.systems; - - systems.itemEjector.drawChunk(parameters, this); - systems.itemAcceptor.drawChunk(parameters, this); - - systems.miner.drawChunk(parameters, this); - - systems.staticMapEntities.drawChunk(parameters, this); - systems.lever.drawChunk(parameters, this); - systems.display.drawChunk(parameters, this); - systems.storage.drawChunk(parameters, this); - } - - /** - * Overlay - * @param {DrawParameters} parameters - */ - drawOverlay(parameters) { - const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES; - const sprite = this.root.buffers.getForKey({ - key: "chunk@" + this.root.currentLayer, - subKey: this.renderKey, - w: overlaySize, - h: overlaySize, - dpi: 1, - redrawMethod: this.generateOverlayBuffer.bind(this), - }); - - const dims = globalConfig.mapChunkWorldSize; - - // Draw chunk "pixel" art - parameters.context.imageSmoothingEnabled = false; - drawSpriteClipped({ - parameters, - sprite, - x: this.x * dims, - y: this.y * dims, - w: dims, - h: dims, - originalW: overlaySize, - originalH: overlaySize, - }); - - parameters.context.imageSmoothingEnabled = true; - - // Draw patch items - if (this.root.currentLayer === "regular") { - for (let i = 0; i < this.patches.length; ++i) { - const patch = this.patches[i]; - - const destX = this.x * dims + patch.pos.x * globalConfig.tileSize; - const destY = this.y * dims + patch.pos.y * globalConfig.tileSize; - const diameter = Math.min(80, 30 / parameters.zoomLevel); - - patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter); - } - } - } - - /** - * - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} context - * @param {number} w - * @param {number} h - * @param {number} dpi - */ - generateOverlayBuffer(canvas, context, w, h, dpi) { - context.fillStyle = - this.containedEntities.length > 0 - ? THEME.map.chunkOverview.filled - : THEME.map.chunkOverview.empty; - context.fillRect(0, 0, w, h); - - for (let x = 0; x < globalConfig.mapChunkSize; ++x) { - const lowerArray = this.lowerLayer[x]; - const upperArray = this.contents[x]; - for (let y = 0; y < globalConfig.mapChunkSize; ++y) { - const upperContent = upperArray[y]; - if (upperContent) { - const staticComp = upperContent.components.StaticMapEntity; - const data = getBuildingDataFromCode(staticComp.code); - const metaBuilding = data.metaInstance; - - const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix( - staticComp.rotation, - data.rotationVariant, - data.variant, - upperContent - ); - - if (overlayMatrix) { - // Draw lower content first since it "shines" through - const lowerContent = lowerArray[y]; - if (lowerContent) { - context.fillStyle = lowerContent.getBackgroundColorAsResource(); - context.fillRect( - x * CHUNK_OVERLAY_RES, - y * CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES - ); - } - - context.fillStyle = metaBuilding.getSilhouetteColor(); - for (let dx = 0; dx < 3; ++dx) { - for (let dy = 0; dy < 3; ++dy) { - const isFilled = overlayMatrix[dx + dy * 3]; - if (isFilled) { - context.fillRect( - x * CHUNK_OVERLAY_RES + dx, - y * CHUNK_OVERLAY_RES + dy, - 1, - 1 - ); - } - } - } - - continue; - } else { - context.fillStyle = metaBuilding.getSilhouetteColor(); - context.fillRect( - x * CHUNK_OVERLAY_RES, - y * CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES - ); - - continue; - } - } - - const lowerContent = lowerArray[y]; - if (lowerContent) { - context.fillStyle = lowerContent.getBackgroundColorAsResource(); - context.fillRect( - x * CHUNK_OVERLAY_RES, - y * CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES, - CHUNK_OVERLAY_RES - ); - } - } - } - - if (this.root.currentLayer === "wires") { - // Draw wires overlay - - context.fillStyle = THEME.map.wires.overlayColor; - context.fillRect(0, 0, w, h); - - for (let x = 0; x < globalConfig.mapChunkSize; ++x) { - const wiresArray = this.wireContents[x]; - for (let y = 0; y < globalConfig.mapChunkSize; ++y) { - const content = wiresArray[y]; - if (!content) { - continue; - } - MapChunkView.drawSingleWiresOverviewTile({ - context, - x: x * CHUNK_OVERLAY_RES, - y: y * CHUNK_OVERLAY_RES, - entity: content, - tileSizePixels: CHUNK_OVERLAY_RES, - }); - } - } - } - } - - /** - * @param {object} param0 - * @param {CanvasRenderingContext2D} param0.context - * @param {number} param0.x - * @param {number} param0.y - * @param {Entity} param0.entity - * @param {number} param0.tileSizePixels - * @param {string=} param0.overrideColor Optionally override the color to be rendered - */ - static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) { - const staticComp = entity.components.StaticMapEntity; - const data = getBuildingDataFromCode(staticComp.code); - const metaBuilding = data.metaInstance; - const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix( - staticComp.rotation, - data.rotationVariant, - data.variant, - entity - ); - context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor(); - if (overlayMatrix) { - for (let dx = 0; dx < 3; ++dx) { - for (let dy = 0; dy < 3; ++dy) { - const isFilled = overlayMatrix[dx + dy * 3]; - if (isFilled) { - context.fillRect( - x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES, - y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES, - tileSizePixels / CHUNK_OVERLAY_RES, - tileSizePixels / CHUNK_OVERLAY_RES - ); - } - } - } - } else { - context.fillRect(x, y, tileSizePixels, tileSizePixels); - } - } - - /** - * Draws the wires layer - * @param {DrawParameters} parameters - */ - drawWiresForegroundLayer(parameters) { - const systems = this.root.systemMgr.systems; - systems.wire.drawChunk(parameters, this); - systems.staticMapEntities.drawWiresChunk(parameters, this); - systems.wiredPins.drawChunk(parameters, this); - } -} +import { globalConfig } from "../core/config"; +import { DrawParameters } from "../core/draw_parameters"; +import { getBuildingDataFromCode } from "./building_codes"; +import { Entity } from "./entity"; +import { MapChunk } from "./map_chunk"; +import { GameRoot } from "./root"; +import { THEME } from "./theme"; +import { drawSpriteClipped } from "../core/draw_utils"; + +export const CHUNK_OVERLAY_RES = 3; + +export class MapChunkView extends MapChunk { + /** + * + * @param {GameRoot} root + * @param {number} x + * @param {number} y + */ + constructor(root, x, y) { + super(root, x, y); + + /** + * Whenever something changes, we increase this number - so we know we need to redraw + */ + this.renderIteration = 0; + + this.markDirty(); + } + + /** + * Marks this chunk as dirty, rerendering all caches + */ + markDirty() { + ++this.renderIteration; + this.renderKey = this.x + "/" + this.y + "@" + this.renderIteration; + } + + /** + * Draws the background layer + * @param {DrawParameters} parameters + */ + drawBackgroundLayer(parameters) { + const systems = this.root.systemMgr.systems; + systems.mapResources.drawChunk(parameters, this); + systems.beltUnderlays.drawChunk(parameters, this); + systems.belt.drawChunk(parameters, this); + } + + /** + * Draws the dynamic foreground layer + * @param {DrawParameters} parameters + */ + drawForegroundDynamicLayer(parameters) { + const systems = this.root.systemMgr.systems; + + systems.itemEjector.drawChunk(parameters, this); + systems.itemAcceptor.drawChunk(parameters, this); + systems.miner.drawChunk(parameters, this); + } + + /** + * Draws the static foreground layer + * @param {DrawParameters} parameters + */ + drawForegroundStaticLayer(parameters) { + const systems = this.root.systemMgr.systems; + + systems.staticMapEntities.drawChunk(parameters, this); + systems.lever.drawChunk(parameters, this); + systems.display.drawChunk(parameters, this); + systems.storage.drawChunk(parameters, this); + } + + /** + * Overlay + * @param {DrawParameters} parameters + */ + drawOverlay(parameters) { + const overlaySize = globalConfig.mapChunkSize * CHUNK_OVERLAY_RES; + const sprite = this.root.buffers.getForKey({ + key: "chunk@" + this.root.currentLayer, + subKey: this.renderKey, + w: overlaySize, + h: overlaySize, + dpi: 1, + redrawMethod: this.generateOverlayBuffer.bind(this), + }); + + const dims = globalConfig.mapChunkWorldSize; + + // Draw chunk "pixel" art + parameters.context.imageSmoothingEnabled = false; + drawSpriteClipped({ + parameters, + sprite, + x: this.x * dims, + y: this.y * dims, + w: dims, + h: dims, + originalW: overlaySize, + originalH: overlaySize, + }); + + parameters.context.imageSmoothingEnabled = true; + + // Draw patch items + if (this.root.currentLayer === "regular") { + for (let i = 0; i < this.patches.length; ++i) { + const patch = this.patches[i]; + + const destX = this.x * dims + patch.pos.x * globalConfig.tileSize; + const destY = this.y * dims + patch.pos.y * globalConfig.tileSize; + const diameter = Math.min(80, 30 / parameters.zoomLevel); + + patch.item.drawItemCenteredClipped(destX, destY, parameters, diameter); + } + } + } + + /** + * + * @param {HTMLCanvasElement} canvas + * @param {CanvasRenderingContext2D} context + * @param {number} w + * @param {number} h + * @param {number} dpi + */ + generateOverlayBuffer(canvas, context, w, h, dpi) { + context.fillStyle = + this.containedEntities.length > 0 + ? THEME.map.chunkOverview.filled + : THEME.map.chunkOverview.empty; + context.fillRect(0, 0, w, h); + + for (let x = 0; x < globalConfig.mapChunkSize; ++x) { + const lowerArray = this.lowerLayer[x]; + const upperArray = this.contents[x]; + for (let y = 0; y < globalConfig.mapChunkSize; ++y) { + const upperContent = upperArray[y]; + if (upperContent) { + const staticComp = upperContent.components.StaticMapEntity; + const data = getBuildingDataFromCode(staticComp.code); + const metaBuilding = data.metaInstance; + + const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix( + staticComp.rotation, + data.rotationVariant, + data.variant, + upperContent + ); + + if (overlayMatrix) { + // Draw lower content first since it "shines" through + const lowerContent = lowerArray[y]; + if (lowerContent) { + context.fillStyle = lowerContent.getBackgroundColorAsResource(); + context.fillRect( + x * CHUNK_OVERLAY_RES, + y * CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES + ); + } + + context.fillStyle = metaBuilding.getSilhouetteColor(); + for (let dx = 0; dx < 3; ++dx) { + for (let dy = 0; dy < 3; ++dy) { + const isFilled = overlayMatrix[dx + dy * 3]; + if (isFilled) { + context.fillRect( + x * CHUNK_OVERLAY_RES + dx, + y * CHUNK_OVERLAY_RES + dy, + 1, + 1 + ); + } + } + } + + continue; + } else { + context.fillStyle = metaBuilding.getSilhouetteColor(); + context.fillRect( + x * CHUNK_OVERLAY_RES, + y * CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES + ); + + continue; + } + } + + const lowerContent = lowerArray[y]; + if (lowerContent) { + context.fillStyle = lowerContent.getBackgroundColorAsResource(); + context.fillRect( + x * CHUNK_OVERLAY_RES, + y * CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES, + CHUNK_OVERLAY_RES + ); + } + } + } + + if (this.root.currentLayer === "wires") { + // Draw wires overlay + + context.fillStyle = THEME.map.wires.overlayColor; + context.fillRect(0, 0, w, h); + + for (let x = 0; x < globalConfig.mapChunkSize; ++x) { + const wiresArray = this.wireContents[x]; + for (let y = 0; y < globalConfig.mapChunkSize; ++y) { + const content = wiresArray[y]; + if (!content) { + continue; + } + MapChunkView.drawSingleWiresOverviewTile({ + context, + x: x * CHUNK_OVERLAY_RES, + y: y * CHUNK_OVERLAY_RES, + entity: content, + tileSizePixels: CHUNK_OVERLAY_RES, + }); + } + } + } + } + + /** + * @param {object} param0 + * @param {CanvasRenderingContext2D} param0.context + * @param {number} param0.x + * @param {number} param0.y + * @param {Entity} param0.entity + * @param {number} param0.tileSizePixels + * @param {string=} param0.overrideColor Optionally override the color to be rendered + */ + static drawSingleWiresOverviewTile({ context, x, y, entity, tileSizePixels, overrideColor = null }) { + const staticComp = entity.components.StaticMapEntity; + const data = getBuildingDataFromCode(staticComp.code); + const metaBuilding = data.metaInstance; + const overlayMatrix = metaBuilding.getSpecialOverlayRenderMatrix( + staticComp.rotation, + data.rotationVariant, + data.variant, + entity + ); + context.fillStyle = overrideColor || metaBuilding.getSilhouetteColor(); + if (overlayMatrix) { + for (let dx = 0; dx < 3; ++dx) { + for (let dy = 0; dy < 3; ++dy) { + const isFilled = overlayMatrix[dx + dy * 3]; + if (isFilled) { + context.fillRect( + x + (dx * tileSizePixels) / CHUNK_OVERLAY_RES, + y + (dy * tileSizePixels) / CHUNK_OVERLAY_RES, + tileSizePixels / CHUNK_OVERLAY_RES, + tileSizePixels / CHUNK_OVERLAY_RES + ); + } + } + } + } else { + context.fillRect(x, y, tileSizePixels, tileSizePixels); + } + } + + /** + * Draws the wires layer + * @param {DrawParameters} parameters + */ + drawWiresForegroundLayer(parameters) { + const systems = this.root.systemMgr.systems; + systems.wire.drawChunk(parameters, this); + systems.staticMapEntities.drawWiresChunk(parameters, this); + systems.wiredPins.drawChunk(parameters, this); + } +} diff --git a/src/js/game/map_view.js b/src/js/game/map_view.js index 178344a7..7eb864cd 100644 --- a/src/js/game/map_view.js +++ b/src/js/game/map_view.js @@ -1,252 +1,253 @@ -import { globalConfig } from "../core/config"; -import { DrawParameters } from "../core/draw_parameters"; -import { BaseMap } from "./map"; -import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils"; -import { Entity } from "./entity"; -import { THEME } from "./theme"; -import { MapChunkView } from "./map_chunk_view"; - -/** - * This is the view of the map, it extends the map which is the raw model and allows - * to draw it - */ -export class MapView extends BaseMap { - constructor(root) { - super(root); - - /** - * DPI of the background cache images, required in some places - */ - this.backgroundCacheDPI = 2; - - /** - * The cached background sprite, containing the flat background - * @type {HTMLCanvasElement} */ - this.cachedBackgroundCanvas = null; - - /** @type {CanvasRenderingContext2D} */ - this.cachedBackgroundContext = null; - /** - * Cached pattern of the stripes background - * @type {CanvasPattern} */ - this.cachedBackgroundPattern = null; - - this.internalInitializeCachedBackgroundCanvases(); - this.root.signals.aboutToDestruct.add(this.cleanup, this); - - this.root.signals.entityAdded.add(this.onEntityChanged, this); - this.root.signals.entityDestroyed.add(this.onEntityChanged, this); - this.root.signals.entityChanged.add(this.onEntityChanged, this); - } - - cleanup() { - freeCanvas(this.cachedBackgroundCanvas); - this.cachedBackgroundCanvas = null; - this.cachedBackgroundPattern = null; - } - - /** - * Called when an entity was added, removed or changed - * @param {Entity} entity - */ - onEntityChanged(entity) { - const staticComp = entity.components.StaticMapEntity; - if (staticComp) { - const rect = staticComp.getTileSpaceBounds(); - for (let x = rect.x; x <= rect.right(); ++x) { - for (let y = rect.y; y <= rect.bottom(); ++y) { - this.root.map.getOrCreateChunkAtTile(x, y).markDirty(); - } - } - } - } - - /** - * Draws all static entities like buildings etc. - * @param {DrawParameters} drawParameters - */ - drawStaticEntityDebugOverlays(drawParameters) { - const cullRange = drawParameters.visibleRect.toTileCullRectangle(); - const top = cullRange.top(); - const right = cullRange.right(); - const bottom = cullRange.bottom(); - const left = cullRange.left(); - - const border = 1; - - const minY = top - border; - const maxY = bottom + border; - const minX = left - border; - const maxX = right + border - 1; - - // Render y from top down for proper blending - for (let y = minY; y <= maxY; ++y) { - for (let x = minX; x <= maxX; ++x) { - // const content = this.tiles[x][y]; - const chunk = this.getChunkAtTileOrNull(x, y); - if (!chunk) { - continue; - } - const content = chunk.getTileContentFromWorldCoords(x, y); - if (content) { - let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1; - if (!isBorder) { - content.drawDebugOverlays(drawParameters); - } - } - } - } - } - - /** - * Initializes all canvases used for background rendering - */ - internalInitializeCachedBackgroundCanvases() { - // Background canvas - const dims = globalConfig.tileSize; - const dpi = this.backgroundCacheDPI; - const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, { - smooth: false, - label: "map-cached-bg", - }); - context.scale(dpi, dpi); - - context.fillStyle = THEME.map.background; - context.fillRect(0, 0, dims, dims); - - const borderWidth = THEME.map.gridLineWidth; - context.fillStyle = THEME.map.grid; - context.fillRect(0, 0, dims, borderWidth); - context.fillRect(0, borderWidth, borderWidth, dims); - - context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth); - context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth); - - this.cachedBackgroundCanvas = canvas; - this.cachedBackgroundContext = context; - } - - /** - * Draws the maps foreground - * @param {DrawParameters} parameters - */ - drawForeground(parameters) { - this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundLayer); - } - - /** - * Calls a given method on all given chunks - * @param {DrawParameters} parameters - * @param {function} method - */ - drawVisibleChunks(parameters, method) { - const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize); - const top = cullRange.top(); - const right = cullRange.right(); - const bottom = cullRange.bottom(); - const left = cullRange.left(); - - const border = 0; - const minY = top - border; - const maxY = bottom + border; - const minX = left - border; - const maxX = right + border; - - const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize); - const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize); - - const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize); - const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize); - - // Render y from top down for proper blending - for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) { - for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) { - const chunk = this.root.map.getChunk(chunkX, chunkY, true); - method.call(chunk, parameters); - } - } - } - - /** - * Draws the wires foreground - * @param {DrawParameters} parameters - */ - drawWiresForegroundLayer(parameters) { - this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer); - } - - /** - * Draws the map overlay - * @param {DrawParameters} parameters - */ - drawOverlay(parameters) { - this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay); - } - - /** - * Draws the map background - * @param {DrawParameters} parameters - */ - drawBackground(parameters) { - if (!this.cachedBackgroundPattern) { - this.cachedBackgroundPattern = parameters.context.createPattern( - this.cachedBackgroundCanvas, - "repeat" - ); - } - - if (!this.root.app.settings.getAllSettings().disableTileGrid) { - const dpi = this.backgroundCacheDPI; - parameters.context.scale(1 / dpi, 1 / dpi); - - parameters.context.fillStyle = this.cachedBackgroundPattern; - parameters.context.fillRect( - parameters.visibleRect.x * dpi, - parameters.visibleRect.y * dpi, - parameters.visibleRect.w * dpi, - parameters.visibleRect.h * dpi - ); - parameters.context.scale(dpi, dpi); - } - - this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer); - - if (G_IS_DEV && globalConfig.debug.showChunkBorders) { - const cullRange = parameters.visibleRect.toTileCullRectangle(); - const top = cullRange.top(); - const right = cullRange.right(); - const bottom = cullRange.bottom(); - const left = cullRange.left(); - - const border = 1; - const minY = top - border; - const maxY = bottom + border; - const minX = left - border; - const maxX = right + border - 1; - - const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize); - const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize); - - const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize); - const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize); - - for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) { - for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) { - parameters.context.fillStyle = "#ffaaaa"; - parameters.context.fillRect( - chunkX * globalConfig.mapChunkWorldSize, - chunkY * globalConfig.mapChunkWorldSize, - globalConfig.mapChunkWorldSize, - 3 - ); - parameters.context.fillRect( - chunkX * globalConfig.mapChunkWorldSize, - chunkY * globalConfig.mapChunkWorldSize, - 3, - globalConfig.mapChunkWorldSize - ); - } - } - } - } -} +import { globalConfig } from "../core/config"; +import { DrawParameters } from "../core/draw_parameters"; +import { BaseMap } from "./map"; +import { freeCanvas, makeOffscreenBuffer } from "../core/buffer_utils"; +import { Entity } from "./entity"; +import { THEME } from "./theme"; +import { MapChunkView } from "./map_chunk_view"; + +/** + * This is the view of the map, it extends the map which is the raw model and allows + * to draw it + */ +export class MapView extends BaseMap { + constructor(root) { + super(root); + + /** + * DPI of the background cache images, required in some places + */ + this.backgroundCacheDPI = 2; + + /** + * The cached background sprite, containing the flat background + * @type {HTMLCanvasElement} */ + this.cachedBackgroundCanvas = null; + + /** @type {CanvasRenderingContext2D} */ + this.cachedBackgroundContext = null; + /** + * Cached pattern of the stripes background + * @type {CanvasPattern} */ + this.cachedBackgroundPattern = null; + + this.internalInitializeCachedBackgroundCanvases(); + this.root.signals.aboutToDestruct.add(this.cleanup, this); + + this.root.signals.entityAdded.add(this.onEntityChanged, this); + this.root.signals.entityDestroyed.add(this.onEntityChanged, this); + this.root.signals.entityChanged.add(this.onEntityChanged, this); + } + + cleanup() { + freeCanvas(this.cachedBackgroundCanvas); + this.cachedBackgroundCanvas = null; + this.cachedBackgroundPattern = null; + } + + /** + * Called when an entity was added, removed or changed + * @param {Entity} entity + */ + onEntityChanged(entity) { + const staticComp = entity.components.StaticMapEntity; + if (staticComp) { + const rect = staticComp.getTileSpaceBounds(); + for (let x = rect.x; x <= rect.right(); ++x) { + for (let y = rect.y; y <= rect.bottom(); ++y) { + this.root.map.getOrCreateChunkAtTile(x, y).markDirty(); + } + } + } + } + + /** + * Draws all static entities like buildings etc. + * @param {DrawParameters} drawParameters + */ + drawStaticEntityDebugOverlays(drawParameters) { + const cullRange = drawParameters.visibleRect.toTileCullRectangle(); + const top = cullRange.top(); + const right = cullRange.right(); + const bottom = cullRange.bottom(); + const left = cullRange.left(); + + const border = 1; + + const minY = top - border; + const maxY = bottom + border; + const minX = left - border; + const maxX = right + border - 1; + + // Render y from top down for proper blending + for (let y = minY; y <= maxY; ++y) { + for (let x = minX; x <= maxX; ++x) { + // const content = this.tiles[x][y]; + const chunk = this.getChunkAtTileOrNull(x, y); + if (!chunk) { + continue; + } + const content = chunk.getTileContentFromWorldCoords(x, y); + if (content) { + let isBorder = x <= left - 1 || x >= right + 1 || y <= top - 1 || y >= bottom + 1; + if (!isBorder) { + content.drawDebugOverlays(drawParameters); + } + } + } + } + } + + /** + * Initializes all canvases used for background rendering + */ + internalInitializeCachedBackgroundCanvases() { + // Background canvas + const dims = globalConfig.tileSize; + const dpi = this.backgroundCacheDPI; + const [canvas, context] = makeOffscreenBuffer(dims * dpi, dims * dpi, { + smooth: false, + label: "map-cached-bg", + }); + context.scale(dpi, dpi); + + context.fillStyle = THEME.map.background; + context.fillRect(0, 0, dims, dims); + + const borderWidth = THEME.map.gridLineWidth; + context.fillStyle = THEME.map.grid; + context.fillRect(0, 0, dims, borderWidth); + context.fillRect(0, borderWidth, borderWidth, dims); + + context.fillRect(dims - borderWidth, borderWidth, borderWidth, dims - 2 * borderWidth); + context.fillRect(borderWidth, dims - borderWidth, dims, borderWidth); + + this.cachedBackgroundCanvas = canvas; + this.cachedBackgroundContext = context; + } + + /** + * Draws the maps foreground + * @param {DrawParameters} parameters + */ + drawForeground(parameters) { + this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundDynamicLayer); + this.drawVisibleChunks(parameters, MapChunkView.prototype.drawForegroundStaticLayer); + } + + /** + * Calls a given method on all given chunks + * @param {DrawParameters} parameters + * @param {function} method + */ + drawVisibleChunks(parameters, method) { + const cullRange = parameters.visibleRect.allScaled(1 / globalConfig.tileSize); + const top = cullRange.top(); + const right = cullRange.right(); + const bottom = cullRange.bottom(); + const left = cullRange.left(); + + const border = 0; + const minY = top - border; + const maxY = bottom + border; + const minX = left - border; + const maxX = right + border; + + const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize); + const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize); + + const chunkEndX = Math.floor(maxX / globalConfig.mapChunkSize); + const chunkEndY = Math.floor(maxY / globalConfig.mapChunkSize); + + // Render y from top down for proper blending + for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) { + for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) { + const chunk = this.root.map.getChunk(chunkX, chunkY, true); + method.call(chunk, parameters); + } + } + } + + /** + * Draws the wires foreground + * @param {DrawParameters} parameters + */ + drawWiresForegroundLayer(parameters) { + this.drawVisibleChunks(parameters, MapChunkView.prototype.drawWiresForegroundLayer); + } + + /** + * Draws the map overlay + * @param {DrawParameters} parameters + */ + drawOverlay(parameters) { + this.drawVisibleChunks(parameters, MapChunkView.prototype.drawOverlay); + } + + /** + * Draws the map background + * @param {DrawParameters} parameters + */ + drawBackground(parameters) { + if (!this.cachedBackgroundPattern) { + this.cachedBackgroundPattern = parameters.context.createPattern( + this.cachedBackgroundCanvas, + "repeat" + ); + } + + if (!this.root.app.settings.getAllSettings().disableTileGrid) { + const dpi = this.backgroundCacheDPI; + parameters.context.scale(1 / dpi, 1 / dpi); + + parameters.context.fillStyle = this.cachedBackgroundPattern; + parameters.context.fillRect( + parameters.visibleRect.x * dpi, + parameters.visibleRect.y * dpi, + parameters.visibleRect.w * dpi, + parameters.visibleRect.h * dpi + ); + parameters.context.scale(dpi, dpi); + } + + this.drawVisibleChunks(parameters, MapChunkView.prototype.drawBackgroundLayer); + + if (G_IS_DEV && globalConfig.debug.showChunkBorders) { + const cullRange = parameters.visibleRect.toTileCullRectangle(); + const top = cullRange.top(); + const right = cullRange.right(); + const bottom = cullRange.bottom(); + const left = cullRange.left(); + + const border = 1; + const minY = top - border; + const maxY = bottom + border; + const minX = left - border; + const maxX = right + border - 1; + + const chunkStartX = Math.floor(minX / globalConfig.mapChunkSize); + const chunkStartY = Math.floor(minY / globalConfig.mapChunkSize); + + const chunkEndX = Math.ceil(maxX / globalConfig.mapChunkSize); + const chunkEndY = Math.ceil(maxY / globalConfig.mapChunkSize); + + for (let chunkX = chunkStartX; chunkX <= chunkEndX; ++chunkX) { + for (let chunkY = chunkStartY; chunkY <= chunkEndY; ++chunkY) { + parameters.context.fillStyle = "#ffaaaa"; + parameters.context.fillRect( + chunkX * globalConfig.mapChunkWorldSize, + chunkY * globalConfig.mapChunkWorldSize, + globalConfig.mapChunkWorldSize, + 3 + ); + parameters.context.fillRect( + chunkX * globalConfig.mapChunkWorldSize, + chunkY * globalConfig.mapChunkWorldSize, + 3, + globalConfig.mapChunkWorldSize + ); + } + } + } + } +}