view: add support for non-square, non-power-of-2 sprites
This commit is contained in:
parent
fa3dad090c
commit
c9000b7c38
6 changed files with 57 additions and 14 deletions
3
dist/resources/entity/red_ellipse.json
vendored
Normal file
3
dist/resources/entity/red_ellipse.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"sprite": "red_ellipse"
|
||||
}
|
BIN
dist/resources/sprite/entity/red_ellipse.png
vendored
Normal file
BIN
dist/resources/sprite/entity/red_ellipse.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 255 B |
|
@ -1,18 +1,28 @@
|
|||
import { EntityData } from '../model/data/entity';
|
||||
import { Renderer } from './renderer/renderer';
|
||||
import { SpriteView, SpriteViewBuilder } from './sprite';
|
||||
import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite';
|
||||
import { loadImage, mkTexture } from './util/image';
|
||||
|
||||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
export async function loadEntity(
|
||||
r: Renderer,
|
||||
data: EntityData,
|
||||
): Promise<SpriteView> {
|
||||
const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`);
|
||||
const texture = mkTexture(r.getContext(), tile);
|
||||
const [texture, size, coords] = mkTexture(r, tile);
|
||||
|
||||
const [x, y] = data.anchor;
|
||||
const offset = vec2.mul(vec2.create(), data.anchor, size);
|
||||
r.snapToGrid(offset, offset);
|
||||
|
||||
const anchorCoords: SpriteCoords = [
|
||||
coords[0] - offset[0],
|
||||
coords[1] - offset[1],
|
||||
coords[2] - offset[0],
|
||||
coords[3] - offset[1],
|
||||
];
|
||||
|
||||
const builder = new SpriteViewBuilder(r, texture);
|
||||
builder.addSprite([-x, -y, -x + 1, -y + 1], [0, 0, 1, 1]);
|
||||
builder.addSprite(anchorCoords, [0, 0, 1, 1]);
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ function loadTiles(tiles: string[]): Promise<HTMLImageElement[]> {
|
|||
}
|
||||
|
||||
function mkTileset(
|
||||
gl: WebGLRenderingContext,
|
||||
r: Renderer,
|
||||
tiles: HTMLImageElement[],
|
||||
): Tileset {
|
||||
const tileSize = 32;
|
||||
|
@ -42,8 +42,10 @@ function mkTileset(
|
|||
}
|
||||
}
|
||||
|
||||
const [texture] = mkTexture(r, canvas);
|
||||
|
||||
return {
|
||||
texture: mkTexture(gl, canvas),
|
||||
texture,
|
||||
tiles: tileCoords,
|
||||
};
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ function addSprite(builder: SpriteViewBuilder, tileset: Tileset, x: number, y: n
|
|||
|
||||
export async function loadMap(r: Renderer, map: MapData): Promise<SpriteView> {
|
||||
const tiles = await loadTiles(map.tiles);
|
||||
const tileset = mkTileset(r.getContext(), tiles);
|
||||
const tileset = mkTileset(r, tiles);
|
||||
|
||||
const builder = new SpriteViewBuilder(r, tileset.texture);
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ import { Shaders } from './shaders';
|
|||
import { mat4, vec2 } from 'gl-matrix';
|
||||
|
||||
export class Renderer {
|
||||
private readonly viewScale = 64;
|
||||
public readonly coordScale = 32;
|
||||
private readonly viewScale = 2;
|
||||
|
||||
private readonly gl: WebGLRenderingContext;
|
||||
private readonly shaders: Shaders;
|
||||
|
@ -67,9 +68,9 @@ export class Renderer {
|
|||
}
|
||||
|
||||
public snapToGrid(out: vec2, a: vec2|number[]): void {
|
||||
vec2.scale(out, a, this.viewScale);
|
||||
vec2.scale(out, a, this.coordScale);
|
||||
vec2.round(out, out);
|
||||
vec2.scale(out, out, 1 / this.viewScale);
|
||||
vec2.scale(out, out, 1 / this.coordScale);
|
||||
}
|
||||
|
||||
private mkContext(): WebGLRenderingContext {
|
||||
|
@ -89,8 +90,10 @@ export class Renderer {
|
|||
this.gl.viewport(0, 0, w, h);
|
||||
this.clear();
|
||||
|
||||
const scale = this.viewScale * this.coordScale;
|
||||
|
||||
mat4.identity(this.viewport);
|
||||
mat4.scale(this.viewport, this.viewport, [2 * this.viewScale / w, -2 * this.viewScale / h, 1.0]);
|
||||
mat4.scale(this.viewport, this.viewport, [2 * scale / w, -2 * scale / h, 1.0]);
|
||||
this.gl.uniformMatrix4fv(this.shaders.viewportLoc, false, this.viewport);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { nextPowerOf2 } from '../../util';
|
||||
import { Renderer } from '../renderer/renderer';
|
||||
import { SpriteCoords } from '../sprite';
|
||||
|
||||
export function loadImage(url: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
@ -7,17 +11,38 @@ export function loadImage(url: string): Promise<HTMLImageElement> {
|
|||
});
|
||||
}
|
||||
|
||||
export function mkTexture(gl: WebGLRenderingContext, src: HTMLCanvasElement|HTMLImageElement): WebGLTexture {
|
||||
export function mkTexture(
|
||||
r: Renderer,
|
||||
src: HTMLCanvasElement|HTMLImageElement,
|
||||
): [WebGLTexture, [number, number], SpriteCoords] {
|
||||
const gl = r.getContext();
|
||||
const texture = gl.createTexture();
|
||||
if (!texture)
|
||||
throw new Error('unable to create texture');
|
||||
|
||||
const w = src.width, h = src.height;
|
||||
const w2 = nextPowerOf2(w), h2 = nextPowerOf2(h);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = w2;
|
||||
canvas.height = h2;
|
||||
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
ctx.drawImage(src, 0, 0);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
|
||||
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;
|
||||
const size: [number, number] = [
|
||||
w / r.coordScale, h / r.coordScale,
|
||||
];
|
||||
const coords: SpriteCoords = [
|
||||
0, 0, w2 / r.coordScale, h2 / r.coordScale,
|
||||
];
|
||||
|
||||
return [texture, size, coords];
|
||||
}
|
||||
|
|
Reference in a new issue