summaryrefslogtreecommitdiffstats
path: root/src/renderer/runtime/view/map.ts
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2019-12-24 13:53:16 +0100
committerMatthias Schiffer <mschiffer@universe-factory.net>2019-12-24 13:53:16 +0100
commit3c51a1994f41b625823c4f15e92396b5498ce23c (patch)
tree7a96310ec32df82ac87039ea555300bcab510a5e /src/renderer/runtime/view/map.ts
parent33926af829848050c54c698ed22da9fe2b912aea (diff)
downloadrpgedit-3c51a1994f41b625823c4f15e92396b5498ce23c.tar
rpgedit-3c51a1994f41b625823c4f15e92396b5498ce23c.zip
Move renderer into "runtime" subdirectory
Diffstat (limited to 'src/renderer/runtime/view/map.ts')
-rw-r--r--src/renderer/runtime/view/map.ts173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/renderer/runtime/view/map.ts b/src/renderer/runtime/view/map.ts
new file mode 100644
index 0000000..18def05
--- /dev/null
+++ b/src/renderer/runtime/view/map.ts
@@ -0,0 +1,173 @@
+import { EntityView } from './entity';
+import { Renderer } from './renderer/renderer';
+import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite';
+import { loadImage, mkTexture } from './util/image';
+
+import { MapData } from '../model/data/map';
+
+import { nextPowerOf2 } from '../util';
+
+interface StaticMapTile {
+ type: 'static';
+ image: HTMLImageElement;
+}
+
+interface EntityTile {
+ type: 'entity';
+ entity: EntityView;
+}
+
+type MapTile = StaticMapTile | EntityTile;
+
+interface StaticTilesetTile {
+ type: 'static';
+ coords: SpriteCoords;
+}
+
+type TilesetTile = StaticTilesetTile | EntityTile;
+
+interface Tileset {
+ texture: WebGLTexture;
+ tiles: TilesetTile[];
+}
+
+async function loadTile(r: Renderer, tile: string): Promise<MapTile> {
+ const name = tile.substr(1);
+ switch (tile[0]) {
+ case '-':
+ return {
+ type: 'static',
+ image: await loadImage(`resources/sprite/tile/${name}.png`),
+ };
+
+ case '@':
+ return {
+ type: 'entity',
+ entity: await EntityView.load(r, name),
+ };
+
+ default:
+ throw new Error('invalid tile specifier');
+ }
+}
+
+function loadTiles(r: Renderer, tiles: string[]): Promise<MapTile[]> {
+ return Promise.all(tiles.map((tile) => loadTile(r, tile)));
+}
+
+function mkTileset(
+ r: Renderer,
+ mapTiles: MapTile[],
+): Tileset {
+ const tileSize = 32;
+
+ const canvasDim = nextPowerOf2(Math.sqrt(mapTiles.length));
+ const canvasSize = canvasDim * tileSize;
+
+ const canvas = document.createElement('canvas');
+ canvas.width = canvas.height = canvasSize;
+
+ let x = 0, y = 0;
+ const tiles: TilesetTile[] = [];
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
+
+ for (const tile of mapTiles) {
+ switch (tile.type) {
+ case 'static':
+ ctx.drawImage(tile.image, x * tileSize, y * tileSize);
+ tiles.push({
+ type: 'static',
+ coords: [x / canvasDim, y / canvasDim, (x + 1) / canvasDim, (y + 1) / canvasDim],
+ });
+
+ x++;
+ if (x === canvasDim) {
+ x = 0;
+ y++;
+ }
+ break;
+
+ case 'entity':
+ tiles.push(tile);
+ break;
+ }
+ }
+
+ const [texture] = mkTexture(r, canvas);
+
+ return {
+ texture,
+ tiles,
+ };
+}
+
+function addSprite(
+ builder: SpriteViewBuilder,
+ entityTiles: Array<[[number, number], EntityView]>,
+ tileset: Tileset,
+ x: number,
+ y: number,
+ tile: number) {
+ if (!tile)
+ return;
+
+ const tilesetTile = tileset.tiles[tile - 1];
+
+ switch (tilesetTile.type) {
+ case 'static':
+ builder.addSprite([x, y, x + 1, y + 1], tilesetTile.coords);
+ break;
+
+ case 'entity':
+ entityTiles.push([[x + 0.5, y + 0.5], tilesetTile.entity]);
+ break;
+ }
+}
+
+function buildMapLayer(r: Renderer, tileset: Tileset, layer: number[][]): MapLayerView {
+ const builder = new SpriteViewBuilder(r, tileset.texture);
+ const entityTiles: Array<[[number, number], EntityView]> = [];
+
+ for (let x = 0; x < layer[0].length; x++)
+ for (let y = 0; y < layer.length; y++)
+ addSprite(builder, entityTiles, tileset, x, y, layer[y][x]);
+
+ return new MapLayerView(r, builder.build(), entityTiles);
+}
+
+class MapLayerView {
+ public constructor(
+ private r: Renderer,
+ private staticTiles: SpriteView,
+ private entityTiles: Array<[[number, number], EntityView]>,
+ ) {
+ }
+
+ public render(time: number): void {
+ this.r.setTranslation([0, 0]);
+ this.staticTiles.render();
+
+ for (const [coords, entity] of this.entityTiles) {
+ this.r.setTranslation(coords);
+ entity.renderByTime(time);
+ }
+ }
+}
+
+export class MapView {
+ public static async load(r: Renderer, map: MapData): Promise<MapView> {
+ const tiles = await loadTiles(r, map.tiles);
+ const tileset = mkTileset(r, tiles);
+
+ const layers = map.layers.map((layer) => buildMapLayer(r, tileset, layer.tiles));
+ return new MapView(layers);
+ }
+
+ private constructor(private layers: MapLayerView[]) {
+ }
+
+ public render(time: number): void {
+ for (const layer of this.layers)
+ layer.render(time);
+ }
+}