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 Renderer from './renderer/Renderer';
export interface TileMap {
texture: WebGLTexture;
tiles: Map<string, [number, number, number, number]>;
}
function loadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
@ -36,27 +41,42 @@ function mkTexture(gl: WebGLRenderingContext, src: HTMLCanvasElement|HTMLImageEl
return texture;
}
function mkTileTexture(gl: WebGLRenderingContext, tiles: Map<string, HTMLImageElement>):
[WebGLTexture, Map<string, number>] {
const canvas = document.createElement('canvas');
canvas.width = nextPowerOf2(tiles.size) * MapView.tileSize;
canvas.height = MapView.tileSize;
function mkTileMap(
gl: WebGLRenderingContext,
tiles: Map<string, HTMLImageElement>,
): TileMap {
const tileSize = MapView.tileSize;
let i = 0;
const ret: Map<string, number> = new Map();
const canvasDim = nextPowerOf2(Math.sqrt(tiles.size));
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;
for (const [k, tile] of tiles) {
ctx.drawImage(tile, i * MapView.tileSize, 0);
ret.set(k, i++);
ctx.drawImage(tile, x * tileSize, y * tileSize);
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> {
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 {TileMap} from './MapLoader';
import Renderer from './renderer/Renderer';
export default class MapView {
@ -14,17 +13,14 @@ export default class MapView {
constructor(
private readonly r: Renderer,
private readonly map: MapState,
private readonly tileTexture: WebGLTexture,
private readonly tileMap: Map<string, number>,
private readonly tileMap: TileMap,
) {
const vertexData: number[] = [];
const textureData: number[] = [];
const tileCount = nextPowerOf2(tileMap.size);
for (let x = 0; x < map.data.width; x++)
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();
@ -43,7 +39,7 @@ export default class MapView {
gl.clear(gl.COLOR_BUFFER_BIT);
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.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);
}
private addTile(vertexData: number[], textureData: number[], x: number, y: number, tile: string, tileCount: number) {
const tileID = this.tileMap.get(tile);
if (tileID === undefined)
private pushTile(buf: number[], coords: [number, number, number, number]) {
const [x1, y1, x2, y2] = coords;
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');
vertexData.push(x); vertexData.push(y);
vertexData.push(x + 1); vertexData.push(y);
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);
this.pushTile(vertexData, [x, y, x + 1, y + 1]);
this.pushTile(textureData, tilePos);
}
}

View file

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