diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2017-09-12 09:20:19 +0200 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2017-09-12 09:20:19 +0200 |
commit | 02758a69ac49cc437ed27628b64e08fd443758b8 (patch) | |
tree | 470d9980b9c2ec710f85a7c5b872d4b529e36a9e /src/view | |
parent | a5e69edc5a6f1a95618c04e214d39b397577d796 (diff) | |
download | rpgedit-02758a69ac49cc437ed27628b64e08fd443758b8.tar rpgedit-02758a69ac49cc437ed27628b64e08fd443758b8.zip |
Implement simple map renderer
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/MapLoader.ts | 59 | ||||
-rw-r--r-- | src/view/MapView.ts | 81 | ||||
-rw-r--r-- | src/view/Renderer.ts | 29 | ||||
-rw-r--r-- | src/view/Scene.ts | 41 | ||||
-rw-r--r-- | src/view/default.fs | 7 | ||||
-rw-r--r-- | src/view/default.vs | 13 |
6 files changed, 169 insertions, 61 deletions
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; } |