MapLoader: use square tile texture rather than long Nx1 rectangle

By placing the tiles in a square texture, the dimensions are bounded by the
square root of the dimension in the old solution. This way we can fit a
significantly higher number of tiles into it without using up all the
accuracy of the coordinates.
This commit is contained in:
Matthias Schiffer 2018-10-26 23:11:14 +02:00
parent bc6d79b088
commit 4b680776a3
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
3 changed files with 55 additions and 38 deletions

View file

@ -4,6 +4,11 @@ import MapState from '../model/state/MapState';
import MapView from './MapView'; import MapView from './MapView';
import Renderer from './renderer/Renderer'; import Renderer from './renderer/Renderer';
export interface TileMap {
texture: WebGLTexture;
tiles: Map<string, [number, number, number, number]>;
}
function loadImage(url: string): Promise<HTMLImageElement> { function loadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); const img = new Image();
@ -36,27 +41,42 @@ function mkTexture(gl: WebGLRenderingContext, src: HTMLCanvasElement|HTMLImageEl
return texture; return texture;
} }
function mkTileTexture(gl: WebGLRenderingContext, tiles: Map<string, HTMLImageElement>): function mkTileMap(
[WebGLTexture, Map<string, number>] { gl: WebGLRenderingContext,
const canvas = document.createElement('canvas'); tiles: Map<string, HTMLImageElement>,
canvas.width = nextPowerOf2(tiles.size) * MapView.tileSize; ): TileMap {
canvas.height = MapView.tileSize; const tileSize = MapView.tileSize;
let i = 0; const canvasDim = nextPowerOf2(Math.sqrt(tiles.size));
const ret: Map<string, number> = new Map(); const canvasSize = canvasDim * tileSize;
const canvas = document.createElement('canvas');
canvas.width = canvas.height = canvasSize;
let x = 0, y = 0;
const map: Map<string, [number, number, number, number]> = new Map();
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
for (const [k, tile] of tiles) { for (const [k, tile] of tiles) {
ctx.drawImage(tile, i * MapView.tileSize, 0); ctx.drawImage(tile, x * tileSize, y * tileSize);
ret.set(k, i++); map.set(k, [x / canvasDim, y / canvasDim, (x + 1) / canvasDim, (y + 1) / canvasDim]);
x++;
if (x === canvasDim) {
x = 0;
y++;
}
} }
return [mkTexture(gl, canvas), ret]; return {
texture: mkTexture(gl, canvas),
tiles: map,
};
} }
export async function loadMap(r: Renderer, map: MapState): Promise<MapView> { export async function loadMap(r: Renderer, map: MapState): Promise<MapView> {
const tiles = await loadTiles(map.data.tiles); const tiles = await loadTiles(map.data.tiles);
const [tileTexture, tileMap] = mkTileTexture(r.getContext(), tiles); const tileMap = mkTileMap(r.getContext(), tiles);
return new MapView(r, map, tileTexture, tileMap); return new MapView(r, map, tileMap);
} }

View file

@ -1,6 +1,5 @@
import {nextPowerOf2} from '../util';
import MapState from '../model/state/MapState'; import MapState from '../model/state/MapState';
import {TileMap} from './MapLoader';
import Renderer from './renderer/Renderer'; import Renderer from './renderer/Renderer';
export default class MapView { export default class MapView {
@ -14,17 +13,14 @@ export default class MapView {
constructor( constructor(
private readonly r: Renderer, private readonly r: Renderer,
private readonly map: MapState, private readonly map: MapState,
private readonly tileTexture: WebGLTexture, private readonly tileMap: TileMap,
private readonly tileMap: Map<string, number>,
) { ) {
const vertexData: number[] = []; const vertexData: number[] = [];
const textureData: number[] = []; const textureData: number[] = [];
const tileCount = nextPowerOf2(tileMap.size);
for (let x = 0; x < map.data.width; x++) for (let x = 0; x < map.data.width; x++)
for (let y = 0; y < map.data.height; y++) for (let y = 0; y < map.data.height; y++)
this.addTile(vertexData, textureData, x, y, map.data.layers[0][y][x], tileCount); this.addTile(vertexData, textureData, x, y, map.data.layers[0][y][x]);
const gl = r.getContext(); const gl = r.getContext();
@ -43,7 +39,7 @@ export default class MapView {
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.tileTexture); gl.bindTexture(gl.TEXTURE_2D, this.tileMap.texture);
gl.uniform1i(this.r.getSamplerLoc(), 0); gl.uniform1i(this.r.getSamplerLoc(), 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
@ -55,25 +51,24 @@ export default class MapView {
gl.drawArrays(gl.TRIANGLES, 0, 6 * this.map.data.width * this.map.data.height); gl.drawArrays(gl.TRIANGLES, 0, 6 * this.map.data.width * this.map.data.height);
} }
private addTile(vertexData: number[], textureData: number[], x: number, y: number, tile: string, tileCount: number) { private pushTile(buf: number[], coords: [number, number, number, number]) {
const tileID = this.tileMap.get(tile); const [x1, y1, x2, y2] = coords;
if (tileID === undefined)
buf.push(x1); buf.push(y1);
buf.push(x2); buf.push(y1);
buf.push(x1); buf.push(y2);
buf.push(x1); buf.push(y2);
buf.push(x2); buf.push(y1);
buf.push(x2); buf.push(y2);
}
private addTile(vertexData: number[], textureData: number[], x: number, y: number, tile: string) {
const tilePos = this.tileMap.tiles.get(tile);
if (!tilePos)
throw new Error('invalid tile specifier in map data'); throw new Error('invalid tile specifier in map data');
vertexData.push(x); vertexData.push(y); this.pushTile(vertexData, [x, y, x + 1, y + 1]);
vertexData.push(x + 1); vertexData.push(y); this.pushTile(textureData, tilePos);
vertexData.push(x); vertexData.push(y + 1);
vertexData.push(x); vertexData.push(y + 1);
vertexData.push(x + 1); vertexData.push(y);
vertexData.push(x + 1); vertexData.push(y + 1);
textureData.push(tileID / tileCount); textureData.push(0);
textureData.push((tileID + 1) / tileCount); textureData.push(0);
textureData.push(tileID / tileCount); textureData.push(1);
textureData.push(tileID / tileCount); textureData.push(1);
textureData.push((tileID + 1) / tileCount); textureData.push(0);
textureData.push((tileID + 1) / tileCount); textureData.push(1);
} }
} }

View file

@ -7,7 +7,9 @@
"rules": { "rules": {
"curly": [true, "as-needed"], "curly": [true, "as-needed"],
"indent": [true, "tabs"], "indent": [true, "tabs"],
"interface-name": false,
"no-bitwise": false, "no-bitwise": false,
"one-variable-per-declaration": false,
"quotemark": [true, "single", "avoid-escape", "avoid-template"] "quotemark": [true, "single", "avoid-escape", "avoid-template"]
}, },
"rulesDirectory": [] "rulesDirectory": []