Added Sprite, Spritesheet and made batching actually do shit

This commit is contained in:
Maciej Samborski 2025-01-05 23:52:15 +01:00
parent 2e9ec2565b
commit 37a91de848
10 changed files with 373 additions and 343 deletions

View File

@ -1,36 +1,26 @@
import { Texture } from "./graphics.js";
import { Vec2 } from "./common.js";
import { Texture, Spritesheet } from "./graphics.js";
export const Colors = {
Red: [1, 0, 0, 1],
Green: [0, 1, 0, 1],
Blue: [0, 0, 1, 1],
Brown: [0.341, 0.337, 0.204, 1],
};
export function AssetToTileFill(name) {
let asset = assets.get(name);
return {
left: asset,
top: asset,
right: asset,
};
}
export class Assets {
assets = new Map();
loaded = false;
push(name, asset) {
if (this.assets.get(name) !== undefined)
throw new Error("Asset name occupied!");
this.assets.set(name, asset);
}
get(name) {
if (!this.loaded)
throw new Error("Tried to assess assets without loading them!");
return this.assets.get(name);
let a = this.assets.get(name);
if (a === undefined)
throw new Error("Couldn't find asset: " + name);
return a;
}
async load(gfx) {
assets.push("grass", await Texture.load(gfx, "../../assets/grass2.png"));
assets.push("leaves", await Texture.load(gfx, "../../assets/greenary.png"));
assets.push("log", await Texture.load(gfx, "../../assets/log.png"));
this.loaded = true;
assets.push("sprites", new Spritesheet(await Texture.load(gfx, "../../assets/sprites.png"), new Vec2(16, 16)));
}
}
export const assets = new Assets();

View File

@ -1,5 +1,5 @@
import {Graphics, Texture} from "./graphics.js";
import {TileFill} from "./world.js";
import {Vec2} from "./common.js";
import {Graphics, Texture, Spritesheet} from "./graphics.js";
export type Color = [number, number, number, number]
@ -10,21 +10,10 @@ export const Colors = {
Brown : [0.341, 0.337, 0.204, 1] as Color,
}
export type Asset = Texture;
export function AssetToTileFill(name: string): TileFill {
let asset = assets.get(name);
return {
left: asset,
top: asset,
right: asset,
};
}
export type Asset = Texture | Spritesheet;
export class Assets {
assets: Map<string, Asset> = new Map();
loaded: boolean = false;
push(name: string, asset: Asset) {
if (this.assets.get(name) !== undefined)
@ -34,18 +23,15 @@ export class Assets {
}
get(name: string): Asset {
if (!this.loaded)
throw new Error("Tried to assess assets without loading them!");
return this.assets.get(name)!;
let a = this.assets.get(name);
if (a === undefined)
throw new Error("Couldn't find asset: " + name);
return a;
}
async load(gfx: Graphics) {
assets.push("grass", await Texture.load(gfx, "../../assets/grass2.png"));
assets.push("leaves", await Texture.load(gfx, "../../assets/greenary.png"));
assets.push("log", await Texture.load(gfx, "../../assets/log.png"));
this.loaded = true;
assets.push("sprites", new Spritesheet(await Texture.load(gfx, "../../assets/sprites.png"), new Vec2(16, 16)));
}
}

View File

@ -1,30 +1,20 @@
import { Vec3 } from "./common.js";
import { Texture, DrawTag } from "./graphics.js";
import { DrawTag, Sprite } from "./graphics.js";
import { TileEdge } from "./world.js";
// TODO: Don't assume tile size is same in all directions
function cull(point, screen, tileSize) {
if (point.x + tileSize < screen.x)
return true;
if (point.x + tileSize > screen.x + screen.z)
return true;
if (point.y - tileSize < screen.y)
return true;
if (point.y + tileSize > screen.y + screen.w)
return true;
return false;
}
import * as Assets from "./assets.js";
// Attrib format
// position color uv
// (3) (4) (2) => 3 + 4 + 2 = 9 <=> data.len % 9 == 0
// (3) (4) (3) => 3 + 4 + 3 = 10 <=> vertexSize == 10
export class Rectangle {
fill;
attribs = ["a_position", "a_color", "a_tex_position"];
attribs = ["a_position", "a_color", "a_tex"];
tags = [];
data = [];
stride = 0;
vertexStride = 0;
constructor(fill, tags, attribs) {
this.fill = fill;
vertexSize = 10;
sprites = Assets.assets.get("sprites");
constructor(tags, sprites, attribs) {
if (sprites !== undefined) {
this.sprites = sprites;
}
if (attribs !== undefined) {
this.attribs = attribs;
}
@ -32,61 +22,59 @@ export class Rectangle {
this.tags = tags;
}
}
overrideDraw(draw) {
this.draw = draw;
}
draw(gfx, corners) {
if (this.fill instanceof Texture) {
this.data = [
corners[0].x, corners[0].y, corners[0].z, 0, 0, 0, 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, -1, 0,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, 0, -1,
corners[2].x, corners[2].y, corners[2].z, 0, 0, 0, 0, -1, -1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, -1, 0,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, 0, -1,
];
}
else {
this.data = [
corners[0].x, corners[0].y, corners[0].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[1].x, corners[1].y, corners[1].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[3].x, corners[3].y, corners[3].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[2].x, corners[2].y, corners[2].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[1].x, corners[1].y, corners[1].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[3].x, corners[3].y, corners[3].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
];
}
this.vertexStride = 9;
this.stride = this.vertexStride * 4;
gfx.toRender.push({ ...this });
}
drawExts(gfx, position, exts) {
if (this.fill instanceof Texture) {
this.data = [
position.x, position.y, position.z, 0, 0, 0, 0, 0, 0,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, -1, 0,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, 0, -1,
position.x + exts.x, position.y + exts.y, position.z, 0, 0, 0, 0, -1, -1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, -1, 0,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, 0, -1,
];
}
else {
this.data = [
position.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
];
}
this.vertexStride = 9;
this.stride = this.vertexStride * 4;
commit(gfx) {
gfx.toRender.push(this);
}
draw(corners, fill) {
if (fill instanceof Sprite) {
let uvs = Assets.assets.get("sprites").getUVs(fill.id);
this.data.push([
corners[0].x, corners[0].y, corners[0].z, 0, 0, 0, 0, uvs[0].x, uvs[0].y, 1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
corners[2].x, corners[2].y, corners[2].z, 0, 0, 0, 0, uvs[2].x, uvs[2].y, 1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
]);
}
else {
let color = fill;
this.data.push([
corners[0].x, corners[0].y, corners[0].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[3].x, corners[3].y, corners[3].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[2].x, corners[2].y, corners[2].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[3].x, corners[3].y, corners[3].z, color[0], color[1], color[2], color[3], 0, 0, 0,
]);
}
}
drawExts(position, exts, fill) {
if (fill instanceof Sprite) {
let uvs = Assets.assets.get("sprites").getUVs(fill.id);
this.data.push([
position.x, position.y, position.z, 0, 0, 0, 0, uvs[0].x, uvs[0].y, 1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
position.x + exts.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[2].x, uvs[2].y, 1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
]);
}
else {
let color = fill;
this.data.push([
position.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
]);
}
}
}
export function drawIsometricCube(gfx, position, exts, color, edge) {
export function drawIsometricCube(position, exts, r, fill, edge) {
let points = [
// Left Top
position,
@ -101,41 +89,39 @@ export function drawIsometricCube(gfx, position, exts, color, edge) {
new Vec3(position.x + 1.5 * exts.x, position.y + 0.5 * exts.y, position.z + 0.25 * exts.z),
new Vec3(position.x + 0.5 * exts.x, position.y - 0.5 * exts.y, position.z + 0.25 * exts.z),
];
let r = new Rectangle(color.top, [
DrawTag.ISO,
]);
// Top
r.draw(gfx, [
r.draw([
points[0],
points[1],
points[3],
points[2],
]);
], fill.top);
// Right Edge
if (edge == TileEdge.Right || edge == TileEdge.Both) {
r.fill = color.right;
r.draw(gfx, [
r.draw([
points[3],
points[2],
points[4],
points[5],
]);
], fill.right);
}
// Left Edge
if (edge == TileEdge.Left || edge == TileEdge.Both) {
r.fill = color.left;
r.draw(gfx, [
r.draw([
points[0],
points[2],
points[4],
points[6],
]);
], fill.left);
}
}
export function drawIsometricGrid(gfx, grid) {
let position = { ...grid.position };
let exts = new Vec3(grid.tileSize, grid.tileSize, 0);
let tileCoord = new Vec3(0, 0, 0);
let rect = new Rectangle([
DrawTag.ISO,
]);
// TODO: Optimize this
// 1. Grid based occlusion culling
// 2. frustum culling
@ -152,7 +138,7 @@ export function drawIsometricGrid(gfx, grid) {
position.x += grid.tileSize;
continue;
}
drawIsometricCube(gfx, new Vec3(position.x - grid.tileSize / 2 * k, position.y + grid.tileSize / 2 * k, position.z), exts, tile.fill, tile.edge);
drawIsometricCube(new Vec3(position.x - grid.tileSize / 2 * k, position.y + grid.tileSize / 2 * k, position.z), exts, rect, tile.fill, tile.edge);
position.x += grid.tileSize;
}
position.y -= grid.tileSize;
@ -160,4 +146,5 @@ export function drawIsometricGrid(gfx, grid) {
}
position.y = grid.position.y;
}
rect.commit(gfx);
}

View File

@ -1,40 +1,25 @@
import { Vec3, Vec2, Vec4 } from "./common.js"
import { Graphics, Texture, Drawable, DrawTag } from "./graphics.js";
import { TileEdge, TileFill, Grid } from "./world.js";
import { Vec3, Vec2 } from "./common.js"
import { Graphics, Drawable, DrawTag, Sprite, Spritesheet } from "./graphics.js";
import { TileEdge, TileFill, TileFillament, Grid } from "./world.js";
import * as Assets from "./assets.js";
// TODO: Don't assume tile size is same in all directions
function cull(point: Vec3, screen: Vec4, tileSize: number): boolean {
if (point.x + tileSize < screen.x)
return true;
if (point.x + tileSize > screen.x + screen.z)
return true;
if (point.y - tileSize < screen.y)
return true;
if (point.y + tileSize > screen.y + screen.w)
return true;
return false;
}
// Attrib format
// position color uv
// (3) (4) (2) => 3 + 4 + 2 = 9 <=> data.len % 9 == 0
// (3) (4) (3) => 3 + 4 + 3 = 10 <=> vertexSize == 10
export class Rectangle implements Drawable {
fill: Texture | Assets.Color;
attribs: string[] = ["a_position", "a_color", "a_tex_position"];
attribs: string[] = ["a_position", "a_color", "a_tex"];
tags: Array<DrawTag> = [];
data: number[] = [];
stride: number = 0;
vertexStride: number = 0;
data: number[][] = [];
vertexSize: number = 10;
constructor(fill: Texture | Assets.Color, tags?: Array<DrawTag>, attribs?: string[]) {
this.fill = fill;
sprites: Spritesheet = Assets.assets.get("sprites") as Spritesheet;
constructor(tags?: Array<DrawTag>, sprites?: Spritesheet, attribs?: string[]) {
if (sprites !== undefined) {
this.sprites = sprites;
}
if (attribs !== undefined) {
this.attribs = attribs;
@ -45,68 +30,68 @@ export class Rectangle implements Drawable {
}
}
overrideDraw(draw: (...args: any[]) => void) {
this.draw = draw;
}
draw(gfx: Graphics, corners: [Vec3, Vec3, Vec3, Vec3]) {
if (this.fill instanceof Texture) {
this.data = [
corners[0].x, corners[0].y, corners[0].z, 0, 0, 0, 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, -1, 0,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, 0, -1,
corners[2].x, corners[2].y, corners[2].z, 0, 0, 0, 0, -1, -1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, -1, 0,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, 0, -1,
]
} else {
this.data = [
corners[0].x, corners[0].y, corners[0].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[1].x, corners[1].y, corners[1].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[3].x, corners[3].y, corners[3].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[2].x, corners[2].y, corners[2].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[1].x, corners[1].y, corners[1].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
corners[3].x, corners[3].y, corners[3].z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
]
}
this.vertexStride = 9;
this.stride = this.vertexStride * 4
gfx.toRender.push({...this});
}
drawExts(gfx: Graphics, position: Vec3, exts: Vec2) {
if (this.fill instanceof Texture) {
this.data = [
position.x, position.y, position.z, 0, 0, 0, 0, 0, 0,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, -1, 0,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, 0, -1,
position.x + exts.x, position.y + exts.y, position.z, 0, 0, 0, 0, -1, -1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, -1, 0,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, 0, -1,
]
} else {
this.data = [
position.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x + exts.x, position.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
position.x, position.y + exts.y, position.z, this.fill[0], this.fill[1], this.fill[2], this.fill[3], 0, 0,
]
}
this.vertexStride = 9;
this.stride = this.vertexStride * 4
commit(gfx: Graphics) {
gfx.toRender.push(this);
}
draw(corners: [Vec3, Vec3, Vec3, Vec3], fill: TileFillament) {
if (fill instanceof Sprite) {
let uvs = (Assets.assets.get("sprites") as Spritesheet).getUVs(fill.id);
this.data.push([
corners[0].x, corners[0].y, corners[0].z, 0, 0, 0, 0, uvs[0].x, uvs[0].y, 1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
corners[2].x, corners[2].y, corners[2].z, 0, 0, 0, 0, uvs[2].x, uvs[2].y, 1,
corners[1].x, corners[1].y, corners[1].z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
corners[3].x, corners[3].y, corners[3].z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
]);
} else {
let color = fill;
this.data.push([
corners[0].x, corners[0].y, corners[0].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[3].x, corners[3].y, corners[3].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[2].x, corners[2].y, corners[2].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[1].x, corners[1].y, corners[1].z, color[0], color[1], color[2], color[3], 0, 0, 0,
corners[3].x, corners[3].y, corners[3].z, color[0], color[1], color[2], color[3], 0, 0, 0,
]);
}
}
drawExts(position: Vec3, exts: Vec2, fill: TileFillament) {
if (fill instanceof Sprite) {
let uvs = (Assets.assets.get("sprites") as Spritesheet).getUVs(fill.id);
this.data.push([
position.x, position.y, position.z, 0, 0, 0, 0, uvs[0].x, uvs[0].y, 1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
position.x + exts.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[2].x, uvs[2].y, 1,
position.x + exts.x, position.y, position.z, 0, 0, 0, 0, uvs[1].x, uvs[1].y, 1,
position.x, position.y + exts.y, position.z, 0, 0, 0, 0, uvs[3].x, uvs[3].y, 1,
]);
} else {
let color = fill;
this.data.push([
position.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x + exts.x, position.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
position.x, position.y + exts.y, position.z, color[0], color[1], color[2], color[3], 0, 0,
]);
}
}
}
export function drawIsometricCube(gfx: Graphics, position: Vec3, exts: Vec3, color: TileFill, edge: TileEdge) {
export function drawIsometricCube(position: Vec3, exts: Vec3, r: Rectangle, fill: TileFill, edge: TileEdge) {
let points = [
// Left Top
position,
@ -122,49 +107,40 @@ export function drawIsometricCube(gfx: Graphics, position: Vec3, exts: Vec3, col
new Vec3(position.x + 0.5 * exts.x, position.y - 0.5 * exts.y, position.z + 0.25 * exts.z),
];
let r = new Rectangle(color.top, [
DrawTag.ISO,
]);
// Top
r.draw(
gfx,
[
points[0],
points[1],
points[3],
points[2],
],
fill.top
);
// Right Edge
if (edge == TileEdge.Right || edge == TileEdge.Both) {
r.fill = color.right;
r.draw(
gfx,
[
points[3],
points[2],
points[4],
points[5],
]
],
fill.right
);
}
// Left Edge
if (edge == TileEdge.Left || edge == TileEdge.Both) {
r.fill = color.left;
r.draw(
gfx,
[
points[0],
points[2],
points[4],
points[6],
]
],
fill.left
);
}
}
@ -173,6 +149,9 @@ export function drawIsometricGrid(gfx: Graphics, grid: Grid) {
let position = {...grid.position} as Vec3;
let exts = new Vec3(grid.tileSize, grid.tileSize, 0);
let tileCoord = new Vec3(0, 0, 0);
let rect = new Rectangle([
DrawTag.ISO,
]);
// TODO: Optimize this
// 1. Grid based occlusion culling
@ -193,7 +172,13 @@ export function drawIsometricGrid(gfx: Graphics, grid: Grid) {
continue;
}
drawIsometricCube(gfx, new Vec3(position.x - grid.tileSize / 2 * k, position.y + grid.tileSize / 2 * k, position.z), exts, tile.fill, tile.edge);
drawIsometricCube(
new Vec3(position.x - grid.tileSize / 2 * k, position.y + grid.tileSize / 2 * k, position.z),
exts,
rect,
tile.fill,
tile.edge
);
position.x += grid.tileSize;
}
@ -202,4 +187,6 @@ export function drawIsometricGrid(gfx: Graphics, grid: Grid) {
}
position.y = grid.position.y;
}
rect.commit(gfx);
}

View File

@ -92,15 +92,16 @@ export class Graphics {
}
draw() {
for (let o of this.toRender) {
const data = o.data.flat();
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vbo);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(o.data), this.ctx.STATIC_DRAW);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(data), this.ctx.STATIC_DRAW);
let aid = 0;
for (let a of o.attribs) {
let attr = this.getAttribute(a);
if (!attr.formatted)
throw new Error("Tried to use unformatted attribute!");
this.ctx.enableVertexAttribArray(attr.loc);
this.ctx.vertexAttribPointer(attr.loc, attr.size, attr.type, attr.normalized, o.stride, aid * 4);
this.ctx.vertexAttribPointer(attr.loc, attr.size, attr.type, attr.normalized, o.vertexSize * 4, aid * 4);
aid += attr.size;
}
// Generalize the tag uniforms aka. don't hard code them
@ -112,15 +113,9 @@ export class Graphics {
}
}
}
if (o.fill instanceof Texture) {
this.ctx.uniform1i(this.getUniform("u_isTex"), 1);
o.fill.bind(this);
}
this.ctx.drawArrays(this.ctx.TRIANGLES, 0, o.data.length / o.vertexStride);
if (o.fill instanceof Texture) {
this.ctx.uniform1i(this.getUniform("u_isTex"), 0);
o.fill.unbind(this);
}
o.sprites?.bind(this);
this.ctx.drawArrays(this.ctx.TRIANGLES, 0, data.length / o.vertexSize);
o.sprites?.unbind(this);
for (let t of o.tags) {
switch (t) {
case DrawTag.ISO: {
@ -183,6 +178,48 @@ export class Texture {
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, null);
}
}
export class Sprite {
id = 0;
constructor(id) {
this.id = id;
}
static id(id) {
return new Sprite(id);
}
static tile(id) {
let s = new Sprite(id);
return {
left: s,
top: s,
right: s,
};
}
}
;
export class Spritesheet {
texture; // Texture is a horizontal spritesheet
spriteSize; // width and height of one sprite
spriteCount; // number of sprites
constructor(texture, spriteSize) {
this.texture = texture;
this.spriteSize = spriteSize;
this.spriteCount = texture.width / spriteSize.x;
}
getUVs(id) {
return [
new Vec2((this.spriteSize.x * id) / this.texture.width, 0),
new Vec2((this.spriteSize.x * (id + 1)) / this.texture.width, 0),
new Vec2((this.spriteSize.x * (id + 1)) / this.texture.width, -1),
new Vec2((this.spriteSize.x * id) / this.texture.width, -1),
];
}
bind(gfx) {
this.texture.bind(gfx);
}
unbind(gfx) {
this.texture.unbind(gfx);
}
}
async function loadTexture(path) {
return new Promise(resolve => {
const img = new Image();

View File

@ -1,5 +1,5 @@
import * as Assets from "./assets.js";
import {Vec2, Vec3, Vec4} from "./common.js";
import { TileFillament, TileFill } from "./world.js";
export function fullscreenCanvas(gfx: Graphics, id: string) {
const canvas = document.getElementById(id) as HTMLCanvasElement;
@ -57,13 +57,13 @@ export enum DrawTag {
}
export interface Drawable {
fill: Texture | Assets.Color;
attribs: string[];
tags: Array<DrawTag>;
data: Array<number>;
vertexStride: number;
stride: number;
data: number[][];
vertexSize: number;
sprites: Spritesheet | undefined;
}
export class Graphics {
@ -131,8 +131,10 @@ export class Graphics {
draw() {
for (let o of this.toRender) {
const data = o.data.flat();
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vbo);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(o.data), this.ctx.STATIC_DRAW);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(data), this.ctx.STATIC_DRAW);
let aid = 0;
for (let a of o.attribs) {
@ -141,7 +143,7 @@ export class Graphics {
throw new Error("Tried to use unformatted attribute!");
this.ctx.enableVertexAttribArray(attr.loc);
this.ctx.vertexAttribPointer(attr.loc, attr.size, attr.type, attr.normalized, o.stride, aid * 4);
this.ctx.vertexAttribPointer(attr.loc, attr.size, attr.type, attr.normalized, o.vertexSize * 4, aid * 4);
aid += attr.size;
}
@ -154,20 +156,12 @@ export class Graphics {
}
}
}
o.sprites?.bind(this);
this.ctx.drawArrays(this.ctx.TRIANGLES, 0, data.length / o.vertexSize);
if (o.fill instanceof Texture)
{
this.ctx.uniform1i(this.getUniform("u_isTex"), 1);
o.fill.bind(this);
}
this.ctx.drawArrays(this.ctx.TRIANGLES, 0, o.data.length / o.vertexStride);
if (o.fill instanceof Texture)
{
this.ctx.uniform1i(this.getUniform("u_isTex"), 0);
o.fill.unbind(this);
}
o.sprites?.unbind(this);
for (let t of o.tags) {
switch (t) {
@ -177,7 +171,6 @@ export class Graphics {
}
}
}
}
// TODO: Maybe add persistent rendering?
@ -251,6 +244,59 @@ export class Texture {
}
}
export type SpriteId = number;
export class Sprite {
id: SpriteId = 0;
private constructor(id: SpriteId) {
this.id = id;
}
static id(id: SpriteId): Sprite {
return new Sprite(id);
}
static tile(id: SpriteId): TileFill {
let s = new Sprite(id);
return {
left: s,
top: s,
right: s,
}
}
};
export class Spritesheet {
texture: Texture; // Texture is a horizontal spritesheet
spriteSize: Vec2; // width and height of one sprite
spriteCount: number; // number of sprites
constructor(texture: Texture, spriteSize: Vec2) {
this.texture = texture;
this.spriteSize = spriteSize;
this.spriteCount = texture.width / spriteSize.x;
}
getUVs(id: number): [Vec2, Vec2, Vec2, Vec2] {
return [
new Vec2((this.spriteSize.x * id) / this.texture.width, 0),
new Vec2((this.spriteSize.x * (id + 1)) / this.texture.width, 0),
new Vec2((this.spriteSize.x * (id + 1)) / this.texture.width, -1),
new Vec2((this.spriteSize.x * id) / this.texture.width, -1),
];
}
bind(gfx: Graphics) {
this.texture.bind(gfx);
}
unbind(gfx: Graphics) {
this.texture.unbind(gfx);
}
}
async function loadTexture(path: string): Promise<HTMLImageElement> {
return new Promise(resolve => {
const img = new Image();

View File

@ -1,22 +1,21 @@
import { initializeContext, Vec3, Mat4, Vec4, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas, Camera } from "./graphics.js";
import { Graphics, fullscreenCanvas, Camera, Sprite } from "./graphics.js";
import * as drawing from "./draw.js";
import * as wasm from "./wasm.js";
import { Input } from "./input.js";
import { bush, Grid, Tile, tree } from "./world.js";
import { Grid, Tile, tree } from "./world.js";
import * as Assets from "./assets.js";
const vertexShader = `#version 300 es
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_tex_position;
layout(location = 1) in vec3 a_tex;
layout(location = 2) in vec4 a_color;
out vec4 v_color;
out vec2 v_tex_position;
out vec3 v_tex;
uniform mat4 u_matrix;
uniform bool u_isIso;
uniform bool u_isTex;
mat4 Iso = mat4(
1, -1, 0, 0,
@ -38,11 +37,8 @@ const vertexShader = `#version 300 es
gl_Position = orthographic;
if (u_isTex) {
v_tex_position = a_tex_position;
} else {
v_color = a_color;
}
v_tex = a_tex;
v_color = a_color;
}
`;
const fragmentShader = `#version 300 es
@ -50,15 +46,14 @@ const fragmentShader = `#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_tex_position;
in vec3 v_tex;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
if (v_tex.z == 1.0) {
outColor = texture(u_texture, v_tex.xy);
} else {
outColor = v_color;
}
@ -131,23 +126,21 @@ function addDefaultKeybinds(input, camera) {
a.format(3, ctx.FLOAT, false, 0);
a = gfx.createAttribute("a_color");
a.format(4, ctx.FLOAT, false, 0);
a = gfx.createAttribute("a_tex_position");
a.format(2, ctx.FLOAT, false, 0);
a = gfx.createAttribute("a_tex");
a.format(3, ctx.FLOAT, false, 0);
gfx.createUniform("u_matrix");
gfx.createUniform("u_isIso");
gfx.createUniform("u_isTex");
let camera = new Camera(new Vec3(0, 0, -1));
await Assets.assets.load(gfx);
let m = Mat4.isometric();
let size = 100;
let grid = new Grid(m.transformNew(new Vec4(ctx.canvas.width / 4, ctx.canvas.height / 2, 0, 1)).reduce(), 24, size, size, 10);
grid.fillLayer(new Tile({
top: Assets.assets.get("grass"),
top: Sprite.id(0),
right: Assets.Colors.Brown,
left: Assets.Colors.Brown,
}), 0);
tree(grid, new Vec2(size / 2, size / 2));
bush(grid, new Vec2(size / 2 + 4, size / 2 + 4));
tree(grid, new Vec2(5, 5));
//for (let i = 0; i < 10; i++) {
// tree(grid, new Vec2(Math.floor(Math.random() * size - 1), Math.floor(Math.random() * size - 1)));
//}

View File

@ -1,24 +1,23 @@
import { initializeContext, Vec3, Mat4, Vec4, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas, Camera, DrawTag } from "./graphics.js";
import { Graphics, fullscreenCanvas, Camera, Sprite } from "./graphics.js";
import * as drawing from "./draw.js";
import * as wasm from "./wasm.js";
import { Input } from "./input.js";
import {bush, Grid, Tile, TileEdge, tree} from "./world.js";
import {Grid, Tile, TileEdge, tree} from "./world.js";
import * as Assets from "./assets.js";
const vertexShader =
`#version 300 es
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_tex_position;
layout(location = 1) in vec3 a_tex;
layout(location = 2) in vec4 a_color;
out vec4 v_color;
out vec2 v_tex_position;
out vec3 v_tex;
uniform mat4 u_matrix;
uniform bool u_isIso;
uniform bool u_isTex;
mat4 Iso = mat4(
1, -1, 0, 0,
@ -40,11 +39,8 @@ const vertexShader =
gl_Position = orthographic;
if (u_isTex) {
v_tex_position = a_tex_position;
} else {
v_color = a_color;
}
v_tex = a_tex;
v_color = a_color;
}
`;
@ -54,15 +50,14 @@ const fragmentShader =
precision highp float;
in vec4 v_color;
in vec2 v_tex_position;
in vec3 v_tex;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
if (v_tex.z == 1.0) {
outColor = texture(u_texture, v_tex.xy);
} else {
outColor = v_color;
}
@ -178,12 +173,11 @@ function addDefaultKeybinds(input: Input, camera: Camera) {
a.format(3, ctx.FLOAT, false, 0)
a = gfx.createAttribute("a_color");
a.format(4, ctx.FLOAT, false, 0)
a = gfx.createAttribute("a_tex_position");
a.format(2, ctx.FLOAT, false, 0)
a = gfx.createAttribute("a_tex");
a.format(3, ctx.FLOAT, false, 0)
gfx.createUniform("u_matrix");
gfx.createUniform("u_isIso");
gfx.createUniform("u_isTex");
let camera = new Camera(new Vec3(0, 0, -1));
@ -194,13 +188,11 @@ function addDefaultKeybinds(input: Input, camera: Camera) {
let size = 100;
let grid = new Grid(m.transformNew(new Vec4(ctx.canvas.width / 4, ctx.canvas.height / 2, 0, 1)).reduce(), 24, size, size, 10);
grid.fillLayer(new Tile({
top: Assets.assets.get("grass"),
top: Sprite.id(0),
right: Assets.Colors.Brown,
left: Assets.Colors.Brown,
}), 0);
tree(grid, new Vec2(size / 2, size / 2));
bush(grid, new Vec2(size / 2 + 4, size / 2 + 4));
tree(grid, new Vec2(5, 5));
//for (let i = 0; i < 10; i++) {
// tree(grid, new Vec2(Math.floor(Math.random() * size - 1), Math.floor(Math.random() * size - 1)));
//}
@ -211,7 +203,7 @@ function addDefaultKeybinds(input: Input, camera: Camera) {
const frame = (timestamp: number) => {
const deltaTime = (timestamp - prevTimestamp)/1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, camera, deltaTime, grid);

View File

@ -1,5 +1,5 @@
import * as Assets from "./assets.js";
import { Vec3 } from "./common.js";
import { Sprite } from "./graphics.js";
export var TileEdge;
(function (TileEdge) {
TileEdge[TileEdge["None"] = 0] = "None";
@ -73,25 +73,28 @@ export class Grid {
}
}
export function tree(grid, position) {
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 2));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 3));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.None), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 5));
let log = Sprite.tile(2);
let leaves = Sprite.tile(1);
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 1));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 2));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 3));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(log, TileEdge.None), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 5));
}
export function bush(grid, position) {
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), position.extend(1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), position.extend(2));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 2));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 2));
let leaves = Sprite.tile(1);
grid.setTile(new Tile(leaves, TileEdge.Both), position.extend(1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), position.extend(2));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 2));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 2));
}

View File

@ -1,6 +1,6 @@
import * as Assets from "./assets.js";
import {Vec2, Vec3} from "./common.js";
import {Texture} from "./graphics.js";
import {Sprite} from "./graphics.js";
export enum TileEdge {
None,
@ -9,8 +9,12 @@ export enum TileEdge {
Both,
}
export type TileFillament = Texture | Assets.Color;
export type TileFill = { left: TileFillament, top: TileFillament, right: TileFillament };
export type TileFillament = Sprite | Assets.Color;
export type TileFill = {
right: TileFillament
left: TileFillament,
top: TileFillament,
};
export class Tile {
fill: TileFill = {
@ -31,8 +35,8 @@ export class Tile {
export function ColorToTile(c: Assets.Color) {
return {
left: c,
top: c,
left: c,
top: c,
right: c,
};
}
@ -96,27 +100,32 @@ export class Grid {
}
export function tree(grid: Grid, position: Vec2) {
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 2));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 3));
grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.None), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 5));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 4));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 5));
let log = Sprite.tile(2);
let leaves = Sprite.tile(1);
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 1));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 2));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 3));
grid.setTile(new Tile(log, TileEdge.Both), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(log, TileEdge.None), new Vec3(position.x, position.y, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 5));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 4));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 5));
}
export function bush(grid: Grid, position: Vec2) {
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), position.extend(1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 1));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 1));
let leaves = Sprite.tile(1);
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), position.extend(2));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x + 1, position.y, 2));
grid.setTile(new Tile(Assets.AssetToTileFill("leaves"), TileEdge.Both), new Vec3(position.x, position.y + 1, 2));
grid.setTile(new Tile(leaves, TileEdge.Both), position.extend(1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y + 1, 1));
grid.setTile(new Tile(leaves, TileEdge.Both), position.extend(2));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x + 1, position.y, 2));
grid.setTile(new Tile(leaves, TileEdge.Both), new Vec3(position.x, position.y + 1, 2));
}