You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tobspr_shapez.io/src/js/game/map_view.js

250 lines
9.0 KiB

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";
import { MapChunkAggregate } from "./map_chunk_aggregate";
/**
* 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;
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;
}
/**
* 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) {
if (G_IS_DEV && (globalConfig.debug.showAcceptorEjectors || globalConfig.debug.showEntityBounds)) {
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);
}
}
}
/**
* Calls a given method on all given chunks
* @param {DrawParameters} parameters
* @param {function} method
*/
drawVisibleAggregates(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 aggregateTiles = globalConfig.chunkAggregateSize * globalConfig.mapChunkSize;
const aggStartX = Math.floor(minX / aggregateTiles);
const aggStartY = Math.floor(minY / aggregateTiles);
const aggEndX = Math.floor(maxX / aggregateTiles);
const aggEndY = Math.floor(maxY / aggregateTiles);
// Render y from top down for proper blending
for (let aggX = aggStartX; aggX <= aggEndX; ++aggX) {
for (let aggY = aggStartY; aggY <= aggEndY; ++aggY) {
const aggregate = this.root.map.getAggregate(aggX, aggY, true);
method.call(aggregate, 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.drawVisibleAggregates(parameters, MapChunkAggregate.prototype.drawOverlay);
}
/**
* Draws the map background
* @param {DrawParameters} parameters
*/
drawBackground(parameters) {
// Render tile grid
if (!this.root.app.settings.getAllSettings().disableTileGrid || !this.root.gameMode.hasResources()) {
const dpi = this.backgroundCacheDPI;
parameters.context.scale(1 / dpi, 1 / dpi);
// @ts-ignore
if (this.cachedBackgroundCanvas._contextLost) {
freeCanvas(this.cachedBackgroundCanvas);
this.internalInitializeCachedBackgroundCanvases();
}
parameters.context.fillStyle = parameters.context.createPattern(
this.cachedBackgroundCanvas,
"repeat"
);
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);
}
}