summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2017-09-12 09:20:19 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2017-09-12 09:20:19 +0200
commit02758a69ac49cc437ed27628b64e08fd443758b8 (patch)
tree470d9980b9c2ec710f85a7c5b872d4b529e36a9e /src
parenta5e69edc5a6f1a95618c04e214d39b397577d796 (diff)
downloadrpgedit-02758a69ac49cc437ed27628b64e08fd443758b8.tar
rpgedit-02758a69ac49cc437ed27628b64e08fd443758b8.zip
Implement simple map renderer
Diffstat (limited to 'src')
-rw-r--r--src/index.ts23
-rw-r--r--src/model/MapData.ts26
-rw-r--r--src/util.ts33
-rw-r--r--src/view/MapLoader.ts59
-rw-r--r--src/view/MapView.ts81
-rw-r--r--src/view/Renderer.ts29
-rw-r--r--src/view/Scene.ts41
-rw-r--r--src/view/default.fs7
-rw-r--r--src/view/default.vs13
9 files changed, 245 insertions, 67 deletions
diff --git a/src/index.ts b/src/index.ts
index 718a321..6390017 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,13 +1,24 @@
-import Renderer from './view/Renderer';
-import Scene from './view/Scene';
+import MapData from './model/MapData';
+import Renderer from './view/Renderer';
+import {loadMap} from './view/MapLoader';
-(() => {
+window.onload = () => {
const canvas = document.getElementById('rpgedit') as HTMLCanvasElement;
if (!canvas)
return;
const renderer = new Renderer(canvas);
- const scene = new Scene(renderer);
- scene.draw();
-})();
+
+ let xhr = new XMLHttpRequest();
+
+ xhr.addEventListener('load', async function() {
+ let mapDef = new MapData(JSON.parse(this.responseText));
+
+ let mapView = await loadMap(renderer, mapDef);
+ mapView.draw();
+ });
+
+ xhr.open('GET', 'resources/map/test.json', true);
+ xhr.send();
+};
diff --git a/src/model/MapData.ts b/src/model/MapData.ts
new file mode 100644
index 0000000..b83e146
--- /dev/null
+++ b/src/model/MapData.ts
@@ -0,0 +1,26 @@
+import {mapFromObject} from '../util';
+
+
+interface Input {
+ tiles: {[key: string]: string};
+ collision: string[];
+ layers: string[][][];
+}
+
+export default class MapData {
+ tiles: Map<string, string>;
+ collision: string[];
+ layers: string[][][];
+
+ width: number;
+ height: number;
+
+ constructor(data: Input) {
+ this.tiles = mapFromObject(data.tiles);
+ this.collision = data.collision;
+ this.layers = data.layers;
+
+ this.height = this.collision.length;
+ this.width = this.collision[0].length;
+ }
+}
diff --git a/src/util.ts b/src/util.ts
new file mode 100644
index 0000000..337bf05
--- /dev/null
+++ b/src/util.ts
@@ -0,0 +1,33 @@
+import * as _ from 'lodash';
+
+
+export function mapFromObject<T>(obj: {[key: string]: T}): Map<string, T> {
+ return new Map(_.toPairs(obj));
+}
+
+export function mapValues<K, V1, V2>(f: (v: V1) => V2, map: Map<K, V1>): Map<K, V2> {
+ let ret: Map<K, V2> = new Map();
+
+ for (let [k, v] of map)
+ ret.set(k, f(v));
+
+ return ret;
+}
+
+export async function mapValuesAsync<K, V1, V2>(f: (v: V1) => Promise<V2>, map: Map<K, V1>): Promise<Map<K, V2>> {
+ let ret: Map<K, V2> = new Map();
+
+ for (let [k, v] of mapValues(f, map))
+ ret.set(k, await v);
+
+ return ret;
+}
+
+export function nextPowerOf2(n: number): number {
+ let i = 1;
+
+ while (i < n)
+ i *= 2;
+
+ return i;
+}
diff --git a/src/view/MapLoader.ts b/src/view/MapLoader.ts
new file mode 100644
index 0000000..eca8b75
--- /dev/null
+++ b/src/view/MapLoader.ts
@@ -0,0 +1,59 @@
+import {mapValues, mapValuesAsync, nextPowerOf2} from '../util';
+
+import Renderer from './Renderer';
+import MapView from './MapView';
+import MapData from '../model/MapData';
+
+
+function loadImage(url: string): Promise<HTMLImageElement> {
+ return new Promise(function(resolve, reject) {
+ let img = new Image();
+ img.addEventListener('load', () => { resolve(img); });
+ img.addEventListener('error', () => { reject(Error('failed to load ' + url)); });
+ img.src = url;
+ });
+}
+
+function loadImages(urls: Map<string, string>): Promise<Map<string, HTMLImageElement>> {
+ return mapValuesAsync(loadImage, urls);
+}
+
+function loadTiles(tiles: Map<string, string>): Promise<Map<string, HTMLImageElement>> {
+ return loadImages(mapValues(t => `resources/sprite/tile/${t}.png`, tiles));
+}
+
+function mkTileTexture(gl: WebGLRenderingContext, tiles: Map<string, HTMLImageElement>): [WebGLTexture, Map<string, number>] {
+ let canvas = document.createElement('canvas');
+ canvas.width = nextPowerOf2(tiles.size) * MapView.tileSize;
+ canvas.height = MapView.tileSize;
+
+ let i = 0;
+ let ret: Map<string, number> = new Map();
+ let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
+
+ for (let [k, tile] of tiles) {
+ ctx.drawImage(tile, i * MapView.tileSize, 0);
+ ret.set(k, i++);
+ }
+
+ let texture = gl.createTexture();
+ if (!texture)
+ throw new Error('unable to create texture');
+
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+
+ return [texture, ret];
+}
+
+
+export async function loadMap(r: Renderer, mapData: MapData): Promise<MapView> {
+ let tiles = await loadTiles(mapData.tiles);
+ let [tileTexture, tileMap] = mkTileTexture(r.gl, tiles);
+
+ return new MapView(r, mapData, tileTexture, tileMap);
+}
diff --git a/src/view/MapView.ts b/src/view/MapView.ts
new file mode 100644
index 0000000..61b8336
--- /dev/null
+++ b/src/view/MapView.ts
@@ -0,0 +1,81 @@
+import * as _ from 'lodash';
+
+import {nextPowerOf2} from '../util';
+
+import Renderer from './Renderer';
+import MapData from '../model/MapData';
+
+
+class MapView {
+ private redrawPending: boolean = false;
+
+ private vertexBuffer: WebGLBuffer;
+ private textureBuffer: WebGLBuffer;
+
+
+ private addTile(vertexData: number[], textureData: number[], x: number, y: number, tile: string, tileCount: number) {
+ let tileID = this.tileMap.get(tile);
+ if (tileID === undefined)
+ 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);
+ }
+
+ constructor(private r: Renderer, private map: MapData, private tileTexture: WebGLTexture, private tileMap: Map<string, number>) {
+ let vertexData: number[] = [];
+ let textureData: number[] = [];
+
+ let tileCount = nextPowerOf2(tileMap.size);
+
+ for (let x = 0; x < map.width; x++) {
+ for (let y = 0; y < map.height; y++) {
+ this.addTile(vertexData, textureData, x, y, map.layers[0][y][x], tileCount);
+ }
+ }
+
+ this.vertexBuffer = r.createBuffer();
+ r.gl.bindBuffer(r.gl.ARRAY_BUFFER, this.vertexBuffer);
+ r.gl.bufferData(r.gl.ARRAY_BUFFER, new Float32Array(vertexData), r.gl.STATIC_DRAW);
+
+ this.textureBuffer = r.createBuffer();
+ r.gl.bindBuffer(r.gl.ARRAY_BUFFER, this.textureBuffer);
+ r.gl.bufferData(r.gl.ARRAY_BUFFER, new Float32Array(textureData), r.gl.STATIC_DRAW);
+ }
+
+ draw(): void {
+ this.r.gl.clear(this.r.gl.COLOR_BUFFER_BIT);
+
+ this.r.gl.activeTexture(this.r.gl.TEXTURE0);
+ this.r.gl.bindTexture(this.r.gl.TEXTURE_2D, this.tileTexture);
+ this.r.gl.uniform1i(this.r.samplerLoc, 0);
+
+ this.r.gl.bindBuffer(this.r.gl.ARRAY_BUFFER, this.vertexBuffer);
+ this.r.gl.vertexAttribPointer(this.r.vertexPosLoc, 2, this.r.gl.FLOAT, false, 0, 0);
+
+ this.r.gl.bindBuffer(this.r.gl.ARRAY_BUFFER, this.textureBuffer);
+ this.r.gl.vertexAttribPointer(this.r.textureCoordLoc, 2, this.r.gl.FLOAT, false, 0, 0);
+
+ this.r.gl.drawArrays(this.r.gl.TRIANGLES, 0, 6 * this.map.width * this.map.height);
+ }
+}
+
+module MapView {
+ export const tileSize = 32;
+}
+
+
+export default MapView;
diff --git a/src/view/Renderer.ts b/src/view/Renderer.ts
index 556cfe3..1b9dd84 100644
--- a/src/view/Renderer.ts
+++ b/src/view/Renderer.ts
@@ -1,15 +1,16 @@
import {mat4} from 'gl-matrix';
-class Renderer {
+export default class Renderer {
public gl: WebGLRenderingContext;
public vertexPosLoc: number;
+ public textureCoordLoc: number;
private viewportLoc: WebGLUniformLocation;
private translateLoc: WebGLUniformLocation;
+ public samplerLoc: WebGLUniformLocation;
private viewport: mat4 = mat4.create();
- private translate: mat4 = mat4.create();
private mkContext(): WebGLRenderingContext {
let gl = (this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl')) as WebGLRenderingContext|null;
@@ -45,9 +46,8 @@ class Renderer {
compileShader(type: number, src: string): WebGLShader {
let shader = this.gl.createShader(type);
- if (!shader) {
+ if (!shader)
throw new Error('Unable to create shader');
- }
this.gl.shaderSource(shader, src);
this.gl.compileShader(shader);
@@ -63,9 +63,8 @@ class Renderer {
private initShaders(): void {
let shaderProgram = this.gl.createProgram();
- if (!shaderProgram) {
+ if (!shaderProgram)
throw new Error('Unable to create shader program');
- }
let vertexShader = this.compileShader(this.gl.VERTEX_SHADER, require('./default.vs'));
let fragmentShader = this.compileShader(this.gl.FRAGMENT_SHADER, require('./default.fs'));
@@ -87,11 +86,15 @@ class Renderer {
this.gl.useProgram(shaderProgram);
- this.vertexPosLoc = this.getAttribLocation(shaderProgram, 'vertexPos');
+ this.vertexPosLoc = this.getAttribLocation(shaderProgram, 'aVertexPos');
this.gl.enableVertexAttribArray(this.vertexPosLoc);
- this.viewportLoc = this.getUniformLocation(shaderProgram, 'viewport');
- this.translateLoc = this.getUniformLocation(shaderProgram, 'translate');
+ this.textureCoordLoc = this.getAttribLocation(shaderProgram, 'aTextureCoord');
+ this.gl.enableVertexAttribArray(this.textureCoordLoc);
+
+ this.viewportLoc = this.getUniformLocation(shaderProgram, 'uViewport');
+ this.translateLoc = this.getUniformLocation(shaderProgram, 'uTranslate');
+ this.samplerLoc = this.getUniformLocation(shaderProgram, 'uSampler');
}
private setSize(): void {
@@ -102,11 +105,10 @@ class Renderer {
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
mat4.identity(this.viewport);
- mat4.scale(this.viewport, this.viewport, [64 / w, 64 / h, 1.0]);
+ mat4.scale(this.viewport, this.viewport, [2 * 64 / w, -2 * 64 / h, 1.0]);
this.gl.uniformMatrix4fv(this.viewportLoc, false, this.viewport);
- mat4.identity(this.translate);
- this.gl.uniformMatrix4fv(this.translateLoc, false, this.translate);
+ this.gl.uniform2f(this.translateLoc, -5.0, -5.0);
}
constructor(private canvas: HTMLCanvasElement) {
@@ -115,10 +117,7 @@ class Renderer {
this.initShaders();
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
- this.gl.enable(this.gl.DEPTH_TEST);
this.setSize();
}
}
-
-export default Renderer;
diff --git a/src/view/Scene.ts b/src/view/Scene.ts
deleted file mode 100644
index 0de7546..0000000
--- a/src/view/Scene.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import Renderer from './Renderer';
-
-class Scene {
- private triangleVertexPositionBuffer: WebGLBuffer;
- private squareVertexPositionBuffer: WebGLBuffer;
-
- constructor(private r: Renderer) {
- this.triangleVertexPositionBuffer = r.createBuffer();
- r.gl.bindBuffer(r.gl.ARRAY_BUFFER, this.triangleVertexPositionBuffer);
- const triangleVertices = [
- -1.5, 1.0,
- -2.5, -1.0,
- -0.5, -1.0,
- ];
- r.gl.bufferData(r.gl.ARRAY_BUFFER, new Float32Array(triangleVertices), r.gl.STATIC_DRAW);
-
- this.squareVertexPositionBuffer = r.createBuffer();
- r.gl.bindBuffer(r.gl.ARRAY_BUFFER, this.squareVertexPositionBuffer);
- const squareVertices = [
- 2.5, 1.0,
- 0.5, 1.0,
- 2.5, -1.0,
- 0.5, -1.0,
- ];
- r.gl.bufferData(r.gl.ARRAY_BUFFER, new Float32Array(squareVertices), r.gl.STATIC_DRAW);
- }
-
- draw(): void {
- this.r.gl.clear(this.r.gl.COLOR_BUFFER_BIT | this.r.gl.DEPTH_BUFFER_BIT);
-
- this.r.gl.bindBuffer(this.r.gl.ARRAY_BUFFER, this.triangleVertexPositionBuffer);
- this.r.gl.vertexAttribPointer(this.r.vertexPosLoc, 2, this.r.gl.FLOAT, false, 0, 0);
- this.r.gl.drawArrays(this.r.gl.TRIANGLES, 0, 3);
-
- this.r.gl.bindBuffer(this.r.gl.ARRAY_BUFFER, this.squareVertexPositionBuffer);
- this.r.gl.vertexAttribPointer(this.r.vertexPosLoc, 2, this.r.gl.FLOAT, false, 0, 0);
- this.r.gl.drawArrays(this.r.gl.TRIANGLE_STRIP, 0, 4);
- }
-}
-
-export default Scene;
diff --git a/src/view/default.fs b/src/view/default.fs
index 7a085b6..351fed7 100644
--- a/src/view/default.fs
+++ b/src/view/default.fs
@@ -1,3 +1,8 @@
+varying highp vec2 vTextureCoord;
+
+uniform sampler2D uSampler;
+
+
void main(void) {
- gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
+ gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y));
}
diff --git a/src/view/default.vs b/src/view/default.vs
index 7c4eaeb..4715a17 100644
--- a/src/view/default.vs
+++ b/src/view/default.vs
@@ -1,8 +1,13 @@
-attribute vec2 vertexPos;
+attribute vec2 aVertexPos;
+attribute vec2 aTextureCoord;
+
+uniform mat4 uViewport;
+uniform vec2 uTranslate;
+
+varying highp vec2 vTextureCoord;
-uniform mat4 viewport;
-uniform mat4 translate;
void main(void) {
- gl_Position = viewport * translate * vec4(vertexPos, 0.0, 1.0);
+ gl_Position = uViewport * vec4(aVertexPos + uTranslate, 0.0, 1.0);
+ vTextureCoord = aTextureCoord;
}