Compare commits

...

11 Commits

Author SHA1 Message Date
Maciej Samborski 87ad6fbd9d Added camera scaling 2025-01-02 19:39:39 +01:00
Maciej Samborski 912487d264 Added basic Object Batching 2025-01-01 21:10:49 +01:00
Maciej Samborski 14d5db166e Fixed, camera translate, matrix transform and texture transparency 2024-12-31 16:03:15 +01:00
Maciej Samborski e936b626f3 Added Grid and grid based rendering 2024-12-27 22:50:07 +01:00
Maciej Samborski 95257a4ef3 Added ability to change between isometric and normal drawing 2024-12-27 14:29:07 +01:00
Maciej Samborski 2a1fe1d0a9 Added Input support 2024-12-27 14:05:20 +01:00
Maciej Samborski bdde494b5b Added isometric projection 2024-12-26 19:02:14 +01:00
Maciej Samborski ab532a2608 Improved switching between textures and colors 2024-12-24 21:12:41 +01:00
Maciej Samborski 2c81bc0684 Added support for Textures 2024-12-24 20:37:22 +01:00
Maciej Samborski 129cf05b90 Added Mat4, Vec4 and some of the mathematical operations on those 2024-12-24 16:30:00 +01:00
Maciej Samborski 3e9bdcc469 Started work on wasm interface 2024-12-19 19:07:59 +01:00
58 changed files with 2855 additions and 812 deletions

BIN
assets/character.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,3 @@
For a description of the contents of this pack, usage rights, and other useful details, go here:
http://blog.spiralgraphics.biz/2011/02/nine-cartoon-backdrops.html

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
assets/grass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

BIN
assets/grass2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
assets/greenary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

BIN
assets/log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

BIN
assets/sprites.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,74 +0,0 @@
function initializeContext(canvasId) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext("webgl2", { antialias: false });
return ctx;
}
class Vec2 {
x;
y;
constructor(x, y) {
this.x = x;
this.y = y;
}
add(other) {
this.x += other.x;
this.y += other.y;
}
addScalar(scalar) {
this.x += scalar;
this.y += scalar;
}
addNew(other) {
return new Vec2(this.x + other.x, this.y + other.y);
}
addScalarNew(scalar) {
return new Vec2(this.x + scalar, this.y + scalar);
}
sub(other) {
this.x -= other.x;
this.y -= other.y;
}
mult(scalar) {
this.x *= scalar;
this.y *= scalar;
}
multNew(scalar) {
return new Vec2(this.x * scalar, this.y * scalar);
}
splatToArray() {
return [this.x, this.y];
}
static angle(angle) {
const eps = 1e-6;
let x = Math.cos(angle);
let y = Math.sin(angle);
if ((x > 0 && x < eps)
|| (x < 0 && x > -eps))
x = 0;
if ((y > 0 && y < eps)
|| (y < 0 && y > -eps))
y = 0;
return new Vec2(x, y);
}
}
class Vec3 {
x;
y;
z;
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
add(other) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
}
sub(other) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
}
}
export { initializeContext, Vec2, Vec3 };

View File

@ -1,96 +0,0 @@
function initializeContext(canvasId: string): WebGL2RenderingContext | null {
const canvas = document.getElementById(canvasId) as HTMLCanvasElement;
const ctx = canvas.getContext("webgl2", {antialias: false});
return ctx;
}
type Color = [number, number, number, number]
class Vec2 {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
add(other: Vec2) {
this.x += other.x;
this.y += other.y;
}
addScalar(scalar: number) {
this.x += scalar;
this.y += scalar;
}
addNew(other: Vec2): Vec2 {
return new Vec2(this.x + other.x, this.y + other.y);
}
addScalarNew(scalar: number): Vec2 {
return new Vec2(this.x + scalar, this.y + scalar);
}
sub(other: Vec2) {
this.x -= other.x;
this.y -= other.y;
}
mult(scalar: number) {
this.x *= scalar;
this.y *= scalar;
}
multNew(scalar: number): Vec2 {
return new Vec2(this.x * scalar, this.y * scalar);
}
splatToArray(): Array<number> {
return [this.x, this.y];
}
static angle(angle: number): Vec2 {
const eps = 1e-6;
let x = Math.cos(angle);
let y = Math.sin(angle);
if ((x > 0 && x < eps)
|| (x < 0 && x > -eps))
x = 0;
if ((y > 0 && y < eps)
|| (y < 0 && y > -eps))
y = 0;
return new Vec2(x, y);
}
}
class Vec3 {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
add(other: Vec3) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
}
sub(other: Vec3) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
}
}
export { initializeContext, Vec2, Vec3, Color };

92
draw.js
View File

@ -1,92 +0,0 @@
import { Vec2 } from "./common.js";
function drawTriangle(gfx, positions, color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points = [
positions[0].x, positions[0].y,
positions[1].x, positions[1].y,
positions[2].x, positions[2].y,
];
const colors = [
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3);
}
function drawTriangleExts(gfx, position, exts, color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points = [
position.x, position.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
];
const colors = [
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3);
}
function drawRectangle(gfx, position, exts, color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points = [
position.x, position.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
position.x + exts.x, position.y + exts.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
];
const colors = [
color,
color,
color,
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 6);
}
function drawCircle(gfx, position, radius, color) {
const points = new Array();
const precision = 40;
const angle = 2.0 * Math.PI / precision;
let a = 0;
for (let i = 0; i < precision; ++i) {
var vec = Vec2.angle(a);
vec.mult(radius);
a += angle;
points.push(vec);
}
for (let i = 0; i < points.length; i++) {
const current = points[i];
const next = points[(i + 1) % points.length];
let center = position;
drawTriangle(gfx, [center, center.addNew(current), center.addNew(next)], color);
}
}
function drawLine(gfx, A, B, color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
let points = [
A.x, A.y,
B.x, B.y,
];
const colors = [
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.LINES, 0, 2);
}
export { drawTriangle, drawTriangleExts, drawRectangle, drawCircle, drawLine };

116
draw.ts
View File

@ -1,116 +0,0 @@
import { Vec2 } from "./common.js"
import { Graphics } from "./graphics.js";
import { Color } from "./common.js";
function drawTriangle(gfx: Graphics, positions: [Vec2, Vec2, Vec2], color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points: Array<number> = [
positions[0].x, positions[0].y,
positions[1].x, positions[1].y,
positions[2].x, positions[2].y,
]
const colors: Array<number[]> = [
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3);
}
function drawTriangleExts(gfx: Graphics, position: Vec2, exts: Vec2, color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points: Array<number> = [
position.x, position.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
]
const colors: Array<number[]> = [
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 3);
}
function drawRectangle(gfx: Graphics, position: Vec2, exts: Vec2, color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
const points: Array<number> = [
position.x, position.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
position.x + exts.x, position.y + exts.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
]
const colors: Array<number[]> = [
color,
color,
color,
color,
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.TRIANGLES, 0, 6);
}
function drawCircle(gfx: Graphics, position: Vec2, radius: number, color: Color) {
const points: Array<Vec2> = new Array<Vec2>();
const precision = 40;
const angle = 2.0*Math.PI/precision;
let a = 0;
for (let i = 0; i < precision; ++i) {
var vec = Vec2.angle(a);
vec.mult(radius);
a += angle;
points.push(vec);
}
for (let i = 0; i < points.length; i++) {
const current = points[i];
const next = points[(i + 1) % points.length];
let center = position;
drawTriangle(gfx, [center, center.addNew(current), center.addNew(next)], color);
}
}
function drawLine(gfx: Graphics, A: Vec2, B: Vec2, color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
let points: Array<number> = [
A.x, A.y,
B.x, B.y,
];
const colors: Array<number[]> = [
color,
color,
];
a_position.data(gfx.ctx, points, gfx.ctx.STATIC_DRAW);
a_color.data(gfx.ctx, colors.flat(), gfx.ctx.STATIC_DRAW);
gfx.ctx.drawArrays(gfx.ctx.LINES, 0, 2);
}
export { drawTriangle, drawTriangleExts, drawRectangle, drawCircle, drawLine }

View File

@ -1,110 +0,0 @@
function fullscreenCanvas(gfx, id) {
const canvas = document.getElementById(id);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gfx.ctx.viewport(0, 0, canvas.width, canvas.height);
}
function createShader(ctx, type, source) {
var shader = ctx.createShader(type);
if (!shader) {
throw new Error("Couldn't create shader: " + type);
}
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
var success = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
if (!success) {
console.log(ctx.getShaderInfoLog(shader));
ctx.deleteShader(shader);
throw new Error("Couldn't compile shader: " + type);
}
return shader;
}
function createProgram(ctx, vertexShaderSource, fragmentShaderSource) {
var program = ctx.createProgram();
if (vertexShaderSource !== null) {
const vs = createShader(ctx, ctx.VERTEX_SHADER, vertexShaderSource);
ctx.attachShader(program, vs);
}
if (fragmentShaderSource !== null) {
const fs = createShader(ctx, ctx.FRAGMENT_SHADER, fragmentShaderSource);
ctx.attachShader(program, fs);
}
ctx.linkProgram(program);
var success = ctx.getProgramParameter(program, ctx.LINK_STATUS);
if (!success) {
console.log(ctx.getProgramInfoLog(program));
ctx.deleteProgram(program);
throw new Error("Failed to create program!");
}
return program;
}
class Graphics {
ctx;
program;
attribs = new Map();
uniforms = new Map();
vao;
constructor(ctx, vs, fs) {
this.ctx = ctx;
this.program = createProgram(ctx, vs, fs);
this.vao = ctx.createVertexArray();
ctx.bindVertexArray(this.vao);
ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.useProgram(this.program);
}
clear(r, g, b, a) {
this.ctx.clearColor(r, g, b, a);
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);
}
createAttribute(name) {
const attrib = new Attribute(this.ctx, this.program, name);
this.attribs.set(name, attrib);
return attrib;
}
getAttribute(name) {
const attrib = this.attribs.get(name);
if (attrib === undefined)
throw new Error("Tried to get uninitialized attribute: " + name);
return attrib;
}
createUniform(name) {
const loc = this.ctx.getUniformLocation(this.program, name);
if (loc === null)
throw new Error("Couldn't get location for uniform: " + name);
this.uniforms.set(name, loc);
}
getUniform(name) {
const loc = this.uniforms.get(name);
if (loc === undefined)
throw new Error("Tried to get uninitialized uniform: " + name);
return loc;
}
}
class Attribute {
loc;
buffer;
// TODO: maybe use undefined as default value?
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
constructor(ctx, program, name) {
this.loc = ctx.getAttribLocation(program, name);
this.buffer = ctx.createBuffer();
ctx.enableVertexAttribArray(this.loc);
}
format(size, type, normalized, stride, offset) {
this.size = size;
this.type = type;
this.normalized = normalized;
this.stride = stride;
this.offset = offset;
}
data(ctx, data, usage) {
ctx.bindBuffer(ctx.ARRAY_BUFFER, this.buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(data), usage);
ctx.vertexAttribPointer(this.loc, this.size, this.type, this.normalized, this.stride, this.offset);
}
}
export { fullscreenCanvas, Graphics, Attribute };

View File

@ -1,148 +0,0 @@
import { Vec2 } from "./common.js"
function fullscreenCanvas(gfx: Graphics, id: string) {
const canvas = document.getElementById(id) as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gfx.ctx.viewport(0, 0, canvas.width, canvas.height);
}
function createShader(ctx: WebGL2RenderingContext, type: GLenum, source: string): WebGLShader {
var shader = ctx.createShader(type);
if (!shader) {
throw new Error("Couldn't create shader: " + type);
}
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
var success = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
if (!success) {
console.log(ctx.getShaderInfoLog(shader));
ctx.deleteShader(shader);
throw new Error("Couldn't compile shader: " + type);
}
return shader;
}
function createProgram(ctx: WebGL2RenderingContext, vertexShaderSource: string | null, fragmentShaderSource: string | null): WebGLProgram {
var program = ctx.createProgram();
if (vertexShaderSource !== null) {
const vs = createShader(ctx, ctx.VERTEX_SHADER, vertexShaderSource);
ctx.attachShader(program, vs);
}
if (fragmentShaderSource !== null) {
const fs = createShader(ctx, ctx.FRAGMENT_SHADER, fragmentShaderSource);
ctx.attachShader(program, fs);
}
ctx.linkProgram(program);
var success = ctx.getProgramParameter(program, ctx.LINK_STATUS);
if (!success) {
console.log(ctx.getProgramInfoLog(program));
ctx.deleteProgram(program);
throw new Error("Failed to create program!");
}
return program;
}
class Graphics {
ctx: WebGL2RenderingContext;
program: WebGLProgram;
attribs: Map<string, Attribute> = new Map();
uniforms: Map<string, WebGLUniformLocation> = new Map();
vao: WebGLVertexArrayObject;
constructor(ctx: WebGL2RenderingContext, vs: string, fs: string) {
this.ctx = ctx;
this.program = createProgram(ctx, vs, fs);
this.vao = ctx.createVertexArray();
ctx.bindVertexArray(this.vao);
ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.useProgram(this.program);
}
clear(r: number, g: number, b: number, a: number) {
this.ctx.clearColor(r, g, b, a);
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);
}
createAttribute(name: string): Attribute {
const attrib = new Attribute(this.ctx, this.program, name);
this.attribs.set(name, attrib);
return attrib;
}
getAttribute(name: string): Attribute {
const attrib = this.attribs.get(name);
if (attrib === undefined)
throw new Error("Tried to get uninitialized attribute: " + name);
return attrib;
}
createUniform(name: string) {
const loc = this.ctx.getUniformLocation(this.program, name);
if (loc === null)
throw new Error("Couldn't get location for uniform: " + name);
this.uniforms.set(name, loc);
}
getUniform(name: string): WebGLUniformLocation {
const loc = this.uniforms.get(name);
if (loc === undefined)
throw new Error("Tried to get uninitialized uniform: " + name);
return loc;
}
}
class Attribute {
loc: GLint;
buffer: WebGLBuffer;
// TODO: maybe use undefined as default value?
size: GLint = 0;
type: GLenum = 0;
normalized: GLboolean = false;
stride: GLsizei = 0;
offset: GLintptr = 0;
constructor(ctx: WebGL2RenderingContext, program: WebGLProgram, name: string) {
this.loc = ctx.getAttribLocation(program, name);
this.buffer = ctx.createBuffer();
ctx.enableVertexAttribArray(this.loc);
}
format(
size: GLint,
type: GLenum,
normalized: GLboolean,
stride: GLsizei,
offset: GLintptr)
{
this.size = size;
this.type = type;
this.normalized = normalized;
this.stride = stride;
this.offset = offset;
}
data(ctx: WebGL2RenderingContext, data: Array<number>, usage: GLenum) {
ctx.bindBuffer(ctx.ARRAY_BUFFER, this.buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(data), usage);
ctx.vertexAttribPointer(this.loc, this.size, this.type, this.normalized, this.stride, this.offset);
}
}
export { fullscreenCanvas, Graphics, Attribute }

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="" />
<link rel="stylesheet" href="style.css"></link>
<script type="module" src="script.js" defer> var exports = {}; </script>
<script type="module" src="src/js/script.js" defer> var exports = {}; </script>
</head>
<body>
<canvas id="game"></canvas>

View File

@ -1,77 +0,0 @@
import { initializeContext, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas } from "./graphics.js";
import * as drawing from "./draw.js";
const vertexShader = `#version 300 es
in vec2 a_position;
in vec4 a_color;
out vec4 color;
uniform vec2 u_resolution;
void main() {
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
color = a_color;
gl_Position = vec4(clipSpace.xy, 0.0, 1.0);
}
`;
const fragmentShader = `#version 300 es
precision highp float;
in vec4 color;
out vec4 outColor;
void main() {
outColor = color;
}
`;
function draw(gfx, dt, pos, velocity) {
gfx.clear(0, 0, 0, 0);
gfx.ctx.uniform2f(gfx.getUniform("u_resolution"), gfx.ctx.canvas.width, gfx.ctx.canvas.height);
drawing.drawCircle(gfx, pos, 200, [1, 0, 0, 1]);
if (pos.x + 200 >= gfx.ctx.canvas.width) {
pos.x = gfx.ctx.canvas.width - 200;
velocity.x *= -1;
}
else if (pos.x - 200 <= 0) {
pos.x = 200;
velocity.x *= -1;
}
if (pos.y + 200 >= gfx.ctx.canvas.height) {
pos.y = gfx.ctx.canvas.height - 200;
velocity.y *= -1;
}
else if (pos.y - 200 <= 0) {
pos.y = 200;
velocity.y *= -1;
}
pos.add(velocity.multNew(dt));
}
(() => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
if (ctx === null)
return;
const gfx = new Graphics(ctx, vertexShader, fragmentShader);
fullscreenCanvas(gfx, canvasId);
const a_position = gfx.createAttribute("a_position");
a_position.format(2, gfx.ctx.FLOAT, false, 0, 0);
const a_color = gfx.createAttribute("a_color");
a_color.format(4, gfx.ctx.FLOAT, false, 0, 0);
gfx.createUniform("u_resolution");
let pos = new Vec2(300, 300);
let velocity = new Vec2(200, 200);
let prevTimestamp = 0;
const frame = (timestamp) => {
const deltaTime = (timestamp - prevTimestamp) / 1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, deltaTime, pos, velocity);
window.requestAnimationFrame(frame);
};
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
})();

View File

@ -1,98 +0,0 @@
import { initializeContext, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas } from "./graphics.js";
import * as drawing from "./draw.js";
const vertexShader =
`#version 300 es
in vec2 a_position;
in vec4 a_color;
out vec4 color;
uniform vec2 u_resolution;
void main() {
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
color = a_color;
gl_Position = vec4(clipSpace.xy, 0.0, 1.0);
}
`;
const fragmentShader =
`#version 300 es
precision highp float;
in vec4 color;
out vec4 outColor;
void main() {
outColor = color;
}
`;
function draw(gfx: Graphics, dt: number, pos: Vec2, velocity: Vec2) {
gfx.clear(0, 0, 0, 0);
gfx.ctx.uniform2f(gfx.getUniform("u_resolution"), gfx.ctx.canvas.width, gfx.ctx.canvas.height);
drawing.drawCircle(gfx, pos, 200, [1, 0, 0, 1]);
if (pos.x + 200 >= gfx.ctx.canvas.width)
{
pos.x = gfx.ctx.canvas.width - 200;
velocity.x *= -1;
} else if (pos.x - 200 <= 0)
{
pos.x = 200;
velocity.x *= -1;
}
if (pos.y + 200 >= gfx.ctx.canvas.height)
{
pos.y = gfx.ctx.canvas.height - 200;
velocity.y *= -1;
} else if (pos.y - 200 <= 0)
{
pos.y = 200;
velocity.y *= -1;
}
pos.add(velocity.multNew(dt));
}
(() => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
if (ctx === null) return;
const gfx = new Graphics(ctx, vertexShader, fragmentShader);
fullscreenCanvas(gfx, canvasId);
const a_position = gfx.createAttribute("a_position");
a_position.format(2, gfx.ctx.FLOAT, false, 0, 0);
const a_color = gfx.createAttribute("a_color");
a_color.format(4, gfx.ctx.FLOAT, false, 0, 0);
gfx.createUniform("u_resolution");
let pos = new Vec2(300, 300);
let velocity = new Vec2(200, 200);
let prevTimestamp = 0;
const frame = (timestamp: number) => {
const deltaTime = (timestamp - prevTimestamp)/1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, deltaTime, pos, velocity);
window.requestAnimationFrame(frame);
}
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
})();

36
src/js/assets.js Normal file
View File

@ -0,0 +1,36 @@
import { Texture } 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);
}
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;
}
}
export const assets = new Assets();

52
src/js/assets.ts Normal file
View File

@ -0,0 +1,52 @@
import {Graphics, Texture} from "./graphics.js";
import {TileFill} from "./world.js";
export type Color = [number, number, number, number]
export const Colors = {
Red : [1, 0, 0, 1] as Color,
Green : [0, 1, 0, 1] as Color,
Blue : [0, 0, 1, 1] as Color,
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 class Assets {
assets: Map<string, Asset> = new Map();
loaded: boolean = false;
push(name: string, asset: Asset) {
if (this.assets.get(name) !== undefined)
throw new Error("Asset name occupied!");
this.assets.set(name, asset);
}
get(name: string): Asset {
if (!this.loaded)
throw new Error("Tried to assess assets without loading them!");
return this.assets.get(name)!;
}
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;
}
}
export const assets = new Assets();

412
src/js/common.js Normal file
View File

@ -0,0 +1,412 @@
function initializeContext(canvasId) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext("webgl2", { antialias: false });
return ctx;
}
class Vec2 {
x;
y;
constructor(x, y) {
this.x = x;
this.y = y;
}
copy() {
return new Vec2(this.x, this.y);
}
add(other) {
this.x += other.x;
this.y += other.y;
}
addScalar(scalar) {
this.x += scalar;
this.y += scalar;
}
addNew(other) {
return new Vec2(this.x + other.x, this.y + other.y);
}
addScalarNew(scalar) {
return new Vec2(this.x + scalar, this.y + scalar);
}
sub(other) {
this.x -= other.x;
this.y -= other.y;
}
subNew(other) {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
return vec;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
}
multScalarNew(scalar) {
return new Vec2(this.x * scalar, this.y * scalar);
}
div(other) {
if (other.x == 0 ||
other.y == 0) {
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
}
divNew(other) {
if (other.x == 0 ||
other.y == 0) {
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
return vec;
}
reduce() {
throw new Error("Can't reduce Vec2!");
}
extend(value) {
return new Vec3(this.x, this.y, value);
}
splatToArray() {
return [this.x, this.y];
}
static angle(angle) {
const eps = 1e-6;
let x = Math.cos(angle);
let y = Math.sin(angle);
if ((x > 0 && x < eps)
|| (x < 0 && x > -eps))
x = 0;
if ((y > 0 && y < eps)
|| (y < 0 && y > -eps))
y = 0;
return new Vec2(x, y);
}
}
class Vec3 {
x;
y;
z;
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
copy() {
return new Vec3(this.x, this.y, this.z);
}
add(other) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
}
addNew(other) {
let vec = this.copy();
vec.x += other.x;
vec.y += other.y;
vec.z += other.z;
return vec;
}
sub(other) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
}
subNew(other) {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
vec.z -= other.z;
return vec;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
}
multScalarNew(scalar) {
let vec = this.copy();
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
return vec;
}
div(other) {
if (other.x == 0 ||
other.y == 0 ||
other.z == 0) {
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
this.z /= other.z;
}
divNew(other) {
if (other.x == 0 ||
other.y == 0 ||
other.z == 0) {
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
vec.z /= other.z;
return vec;
}
reduce() {
return new Vec2(this.x, this.y);
}
extend(value) {
return new Vec4(this.x, this.y, this.z, value);
}
splatToArray() {
return [this.x, this.y, this.z];
}
}
class Vec4 {
x;
y;
z;
w;
constructor(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
copy() {
return new Vec4(this.x, this.y, this.z, this.w);
}
add(other) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
this.w += other.w;
}
addNew(other) {
let vec = this.copy();
vec.x += other.x;
vec.y += other.y;
vec.z += other.z;
vec.w += other.w;
return vec;
}
sub(other) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
this.w -= other.w;
}
subNew(other) {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
vec.z -= other.z;
vec.w -= other.w;
return vec;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
this.w *= scalar;
}
multScalarNew(scalar) {
let vec = this.copy();
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
vec.w *= scalar;
return vec;
}
div(other) {
if (other.x == 0 ||
other.y == 0 ||
other.z == 0 ||
other.w == 0) {
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
this.z /= other.z;
this.w /= other.w;
}
divNew(other) {
if (other.x == 0 ||
other.y == 0 ||
other.z == 0 ||
other.w == 0) {
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
vec.z /= other.z;
vec.w /= other.w;
return vec;
}
reduce() {
return new Vec3(this.x, this.y, this.z);
}
extend(_value) {
throw new Error("Can't extend Vec4");
}
}
class Mat4 {
data;
constructor(i) {
if (i instanceof Float32Array) {
console.assert(i.length == 16, "Mat4 has to have 16 elements");
this.data = i;
return;
}
else if (typeof (i) === 'number') {
this.data = new Float32Array(16).fill(i);
return;
}
this.data = new Float32Array(16);
}
static IDENTITY() {
return new Mat4(new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static orthographic(left, right, bottom, top, near, far) {
let data = new Float32Array([
2 / (right - left), 0, 0, 0,
0, 2 / (top - bottom), 0, 0,
0, 0, -2 / (far - near), 0,
-(right + left) / (right - left),
-(top + bottom) / (top - bottom),
-(far + near) / (far - near),
1,
]);
return new Mat4(data);
}
static isometric() {
return new Mat4(new Float32Array([
1, -1, 0, 0,
1, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static isometric_inverse() {
return new Mat4(new Float32Array([
0.5, 0.5, 0, 0,
-0.5, 0.5, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static rotation_x(angle) {
let data = new Float32Array([
1, 0, 0, 0,
0, Math.cos(angle), -Math.sin(angle), 0,
0, Math.sin(angle), Math.cos(angle), 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static rotation_y(angle) {
let data = new Float32Array([
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static rotation_z(angle) {
let data = new Float32Array([
Math.cos(angle), -Math.sin(angle), 0, 0,
Math.sin(angle), Math.cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static translate(t) {
return new Mat4(new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
t.x, t.y, t.z, 1,
]));
}
static scale(scales) {
return new Mat4(new Float32Array([
scales.x, 0, 0, 0,
0, scales.y, 0, 0,
0, 0, scales.z, 0,
0, 0, 0, 1,
]));
}
x(n) {
return this.data[n];
}
y(n) {
return this.data[n + 4];
}
z(n) {
return this.data[n + 8];
}
w(n) {
return this.data[n + 12];
}
splat() {
return Array.from(this.data);
}
row(row, data) {
for (let i = 0; i < data.length; ++i)
this.data[i + 4 * row] = data[i];
}
col(col, data) {
for (let i = 0; i < data.length; ++i)
this.data[i * 4 + col] = data[i];
}
transform(v) {
let x = v.x * this.x(0) + v.y * this.x(1) + v.z * this.x(2) + v.w * this.x(3);
let y = v.x * this.y(0) + v.y * this.y(1) + v.z * this.y(2) + v.w * this.y(3);
let z = v.x * this.z(0) + v.y * this.z(1) + v.z * this.z(2) + v.w * this.z(3);
let w = v.x * this.w(0) + v.y * this.w(1) + v.z * this.w(2) + v.w * this.w(3);
v.x = x;
v.y = y;
v.z = z;
v.w = w;
}
transformNew(v) {
let vec = v.copy();
let x = v.x * this.x(0) + v.y * this.x(1) + v.z * this.x(2) + v.w * this.x(3);
let y = v.x * this.y(0) + v.y * this.y(1) + v.z * this.y(2) + v.w * this.y(3);
let z = v.x * this.z(0) + v.y * this.z(1) + v.z * this.z(2) + v.w * this.z(3);
let w = v.x * this.w(0) + v.y * this.w(1) + v.z * this.w(2) + v.w * this.w(3);
vec.x = x;
vec.y = y;
vec.z = z;
vec.w = w;
return vec;
}
multNew(other) {
let m = new Mat4(0);
for (let i = 0; i < 4; ++i) {
for (let j = 0; j < 4; ++j) {
m.data[(i * 4) + j] += this.data[(i * 4) + 0] * other.data[j + 0];
m.data[(i * 4) + j] += this.data[(i * 4) + 1] * other.data[j + 4];
m.data[(i * 4) + j] += this.data[(i * 4) + 2] * other.data[j + 8];
m.data[(i * 4) + j] += this.data[(i * 4) + 3] * other.data[j + 12];
}
}
return m;
}
multScalar(scalar) {
for (let i = 0; i < this.data.length; ++i) {
this.data[i] *= scalar;
}
}
}
export { initializeContext, Mat4, Vec2, Vec3, Vec4 };

555
src/js/common.ts Normal file
View File

@ -0,0 +1,555 @@
function initializeContext(canvasId: string): WebGL2RenderingContext | null {
const canvas = document.getElementById(canvasId) as HTMLCanvasElement;
const ctx = canvas.getContext("webgl2", {antialias: false});
return ctx;
}
// TODO: Make all vectors follow one interface
interface Vector<P, T, N> {
copy(): T;
add(other: T): void;
addNew(other: T): T;
sub(other: T): void;
subNew(other: T): T;
multScalar(scalar: number): void;
multScalarNew(scalar: number): T;
div(other: T): void;
divNew(other: T): T;
reduce(): P;
extend(value: number): N;
}
class Vec2 implements Vector<void, Vec2, Vec3> {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
copy(): Vec2 {
return new Vec2(this.x, this.y);
}
add(other: Vec2) {
this.x += other.x;
this.y += other.y;
}
addScalar(scalar: number) {
this.x += scalar;
this.y += scalar;
}
addNew(other: Vec2): Vec2 {
return new Vec2(this.x + other.x, this.y + other.y);
}
addScalarNew(scalar: number): Vec2 {
return new Vec2(this.x + scalar, this.y + scalar);
}
sub(other: Vec2) {
this.x -= other.x;
this.y -= other.y;
}
subNew(other: Vec2): Vec2 {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
return vec;
}
multScalar(scalar: number) {
this.x *= scalar;
this.y *= scalar;
}
multScalarNew(scalar: number): Vec2 {
return new Vec2(this.x * scalar, this.y * scalar);
}
div(other: Vec2) {
if ( other.x == 0 ||
other.y == 0)
{
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
}
divNew(other: Vec2): Vec2 {
if ( other.x == 0 ||
other.y == 0)
{
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
return vec;
}
reduce(): null {
throw new Error("Can't reduce Vec2!");
}
extend(value: number): Vec3 {
return new Vec3(this.x, this.y, value);
}
splatToArray(): Array<number> {
return [this.x, this.y];
}
static angle(angle: number): Vec2 {
const eps = 1e-6;
let x = Math.cos(angle);
let y = Math.sin(angle);
if ((x > 0 && x < eps)
|| (x < 0 && x > -eps))
x = 0;
if ((y > 0 && y < eps)
|| (y < 0 && y > -eps))
y = 0;
return new Vec2(x, y);
}
}
class Vec3 implements Vector<Vec2, Vec3, Vec4> {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
copy(): Vec3 {
return new Vec3(this.x, this.y, this.z);
}
add(other: Vec3) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
}
addNew(other: Vec3): Vec3 {
let vec = this.copy();
vec.x += other.x;
vec.y += other.y;
vec.z += other.z;
return vec;
}
sub(other: Vec3) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
}
subNew(other: Vec3): Vec3 {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
vec.z -= other.z;
return vec;
}
multScalar(scalar: number) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
}
multScalarNew(scalar: number): Vec3 {
let vec = this.copy();
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
return vec;
}
div(other: Vec3) {
if ( other.x == 0 ||
other.y == 0 ||
other.z == 0)
{
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
this.z /= other.z;
}
divNew(other: Vec3): Vec3 {
if ( other.x == 0 ||
other.y == 0 ||
other.z == 0)
{
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
vec.z /= other.z;
return vec;
}
reduce(): Vec2 {
return new Vec2(this.x, this.y);
}
extend(value: number): Vec4 {
return new Vec4(this.x, this.y, this.z, value);
}
splatToArray(): Array<number> {
return [this.x, this.y, this.z];
}
}
class Vec4 implements Vector<Vec3, Vec4, void> {
x: number;
y: number;
z: number;
w: number;
constructor(x: number, y: number, z: number, w: number) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
copy(): Vec4 {
return new Vec4(this.x, this.y, this.z, this.w);
}
add(other: Vec4) {
this.x += other.x;
this.y += other.y;
this.z += other.z;
this.w += other.w;
}
addNew(other: Vec4): Vec4 {
let vec = this.copy();
vec.x += other.x;
vec.y += other.y;
vec.z += other.z;
vec.w += other.w;
return vec;
}
sub(other: Vec4) {
this.x -= other.x;
this.y -= other.y;
this.z -= other.z;
this.w -= other.w;
}
subNew(other: Vec4): Vec4 {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
vec.z -= other.z;
vec.w -= other.w;
return vec;
}
multScalar(scalar: number) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
this.w *= scalar;
}
multScalarNew(scalar: number): Vec4 {
let vec = this.copy();
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
vec.w *= scalar;
return vec;
}
div(other: Vec4) {
if ( other.x == 0 ||
other.y == 0 ||
other.z == 0 ||
other.w == 0)
{
throw new Error("Division by zero in Vec4");
}
this.x /= other.x;
this.y /= other.y;
this.z /= other.z;
this.w /= other.w;
}
divNew(other: Vec4): Vec4 {
if ( other.x == 0 ||
other.y == 0 ||
other.z == 0 ||
other.w == 0)
{
throw new Error("Division by zero in Vec4");
}
let vec = this.copy();
vec.x /= other.x;
vec.y /= other.y;
vec.z /= other.z;
vec.w /= other.w;
return vec;
}
reduce(): Vec3 {
return new Vec3(this.x, this.y, this.z);
}
extend(_value: number): void {
throw new Error("Can't extend Vec4");
}
}
type Mat4Init = number | Float32Array;
type Mat4Row = 0 | 1 | 2 | 3
type Mat4Col = Mat4Row
type Mat4X = Mat4Row
type Mat4Y = Mat4Row
type Mat4Z = Mat4Row
type Mat4W = Mat4Row
class Mat4 {
data: Float32Array;
constructor(i: Mat4Init) {
if (i instanceof Float32Array) {
console.assert(i.length == 16, "Mat4 has to have 16 elements");
this.data = i;
return;
} else if (typeof(i) === 'number') {
this.data = new Float32Array(16).fill(i);
return;
}
this.data = new Float32Array(16);
}
static IDENTITY(): Mat4 {
return new Mat4(new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4 {
let data = new Float32Array([
2 / (right - left), 0, 0, 0,
0, 2 / (top - bottom), 0, 0,
0, 0, -2 / (far - near), 0,
-(right + left)/(right - left),
-(top + bottom)/(top - bottom),
-(far + near)/(far - near),
1,
]);
return new Mat4(data);
}
static isometric(): Mat4 {
return new Mat4(new Float32Array([
1, -1, 0, 0,
1, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static isometric_inverse(): Mat4 {
return new Mat4(new Float32Array([
0.5, 0.5, 0, 0,
-0.5, 0.5, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]));
}
static rotation_x(angle: number): Mat4 {
let data = new Float32Array([
1, 0, 0, 0,
0, Math.cos(angle), -Math.sin(angle), 0,
0, Math.sin(angle), Math.cos(angle), 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static rotation_y(angle: number): Mat4 {
let data = new Float32Array([
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static rotation_z(angle: number): Mat4 {
let data = new Float32Array([
Math.cos(angle), -Math.sin(angle), 0, 0,
Math.sin(angle), Math.cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);
return new Mat4(data);
}
static translate(t: Vec3): Mat4 {
return new Mat4(new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
t.x, t.y, t.z, 1,
]));
}
static scale(scales: Vec3) {
return new Mat4(new Float32Array([
scales.x, 0, 0, 0,
0, scales.y, 0, 0,
0, 0, scales.z, 0,
0, 0, 0, 1,
]));
}
x(n: Mat4X) {
return this.data[n];
}
y(n: Mat4Y) {
return this.data[n + 4];
}
z(n: Mat4Z) {
return this.data[n + 8];
}
w(n: Mat4W) {
return this.data[n + 12];
}
splat() {
return Array.from(this.data);
}
row(row: Mat4Row, data: [number, number, number, number]) {
for (let i = 0; i < data.length; ++i)
this.data[i + 4 * row] = data[i];
}
col(col: Mat4Col, data: [number, number, number, number]) {
for (let i = 0; i < data.length; ++i)
this.data[i*4 + col] = data[i];
}
transform(v: Vec4) {
let x = v.x * this.x(0) + v.y * this.x(1) + v.z * this.x(2) + v.w * this.x(3);
let y = v.x * this.y(0) + v.y * this.y(1) + v.z * this.y(2) + v.w * this.y(3);
let z = v.x * this.z(0) + v.y * this.z(1) + v.z * this.z(2) + v.w * this.z(3);
let w = v.x * this.w(0) + v.y * this.w(1) + v.z * this.w(2) + v.w * this.w(3);
v.x = x;
v.y = y;
v.z = z;
v.w = w;
}
transformNew(v: Vec4): Vec4 {
let vec = v.copy();
let x = v.x * this.x(0) + v.y * this.x(1) + v.z * this.x(2) + v.w * this.x(3);
let y = v.x * this.y(0) + v.y * this.y(1) + v.z * this.y(2) + v.w * this.y(3);
let z = v.x * this.z(0) + v.y * this.z(1) + v.z * this.z(2) + v.w * this.z(3);
let w = v.x * this.w(0) + v.y * this.w(1) + v.z * this.w(2) + v.w * this.w(3);
vec.x = x;
vec.y = y;
vec.z = z;
vec.w = w;
return vec;
}
multNew(other: Mat4): Mat4 {
let m = new Mat4(0);
for (let i = 0; i < 4; ++i) {
for (let j = 0; j < 4; ++j) {
m.data[(i * 4) + j] += this.data[(i * 4) + 0] * other.data[j + 0]
m.data[(i * 4) + j] += this.data[(i * 4) + 1] * other.data[j + 4]
m.data[(i * 4) + j] += this.data[(i * 4) + 2] * other.data[j + 8]
m.data[(i * 4) + j] += this.data[(i * 4) + 3] * other.data[j + 12]
}
}
return m;
}
multScalar(scalar: number) {
for (let i = 0; i < this.data.length; ++i) {
this.data[i] *= scalar;
}
}
}
export { initializeContext, Mat4, Vec2, Vec3, Vec4 };

159
src/js/draw.js Normal file
View File

@ -0,0 +1,159 @@
import { Vec3 } from "./common.js";
import { Texture, DrawTag } 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;
}
// Attrib format
// position color uv
// (3) (4) (2) => 3 + 4 + 2 = 9 <=> data.len % 9 == 0
export class Rectangle {
fill;
attribs = ["a_position", "a_color", "a_tex_position"];
tags = [];
data = [];
stride = 0;
vertexStride = 0;
constructor(fill, tags, attribs) {
this.fill = fill;
if (attribs !== undefined) {
this.attribs = attribs;
}
if (tags !== undefined) {
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;
gfx.toRender.push(this);
}
}
export function drawIsometricCube(gfx, position, exts, color, edge) {
let points = [
// Left Top
position,
//Top
new Vec3(position.x, position.y + exts.y, position.z),
//Mid
new Vec3(position.x + exts.x, position.y, position.z),
//Right Top
new Vec3(position.x + exts.x, position.y + exts.y, position.z),
//Bottom
new Vec3(position.x + 1.5 * exts.x, position.y - 0.5 * exts.y, position.z + 0.25 * exts.z),
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, [
points[0],
points[1],
points[3],
points[2],
]);
// Right Edge
if (edge == TileEdge.Right || edge == TileEdge.Both) {
r.fill = color.right;
r.draw(gfx, [
points[3],
points[2],
points[4],
points[5],
]);
}
// Left Edge
if (edge == TileEdge.Left || edge == TileEdge.Both) {
r.fill = color.left;
r.draw(gfx, [
points[0],
points[2],
points[4],
points[6],
]);
}
}
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);
// TODO: Optimize this
for (let k = 0; k < grid.height; ++k) {
for (let j = 0; j < grid.length; ++j) {
for (let i = 0; i < grid.width; ++i) {
tileCoord.x = i;
tileCoord.y = j;
tileCoord.z = k;
let tile = grid.getTile(tileCoord);
if (tile === null) {
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);
position.x += grid.tileSize;
}
position.y -= grid.tileSize;
position.x = grid.position.x;
}
position.y = grid.position.y;
}
}

201
src/js/draw.ts Normal file
View File

@ -0,0 +1,201 @@
import { Vec3, Vec2, Vec4 } from "./common.js"
import { Graphics, Texture, Drawable, DrawTag } from "./graphics.js";
import { TileEdge, TileFill, 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
export class Rectangle implements Drawable {
fill: Texture | Assets.Color;
attribs: string[] = ["a_position", "a_color", "a_tex_position"];
tags: Array<DrawTag> = [];
data: number[] = [];
stride: number = 0;
vertexStride: number = 0;
constructor(fill: Texture | Assets.Color, tags?: Array<DrawTag>, attribs?: string[]) {
this.fill = fill;
if (attribs !== undefined) {
this.attribs = attribs;
}
if (tags !== undefined) {
this.tags = tags;
}
}
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
gfx.toRender.push(this);
}
}
export function drawIsometricCube(gfx: Graphics, position: Vec3, exts: Vec3, color: TileFill, edge: TileEdge) {
let points = [
// Left Top
position,
//Top
new Vec3(position.x, position.y + exts.y, position.z),
//Mid
new Vec3(position.x + exts.x, position.y, position.z),
//Right Top
new Vec3(position.x + exts.x, position.y + exts.y, position.z),
//Bottom
new Vec3(position.x + 1.5 * exts.x, position.y - 0.5 * exts.y, position.z + 0.25 * exts.z),
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,
[
points[0],
points[1],
points[3],
points[2],
],
);
// Right Edge
if (edge == TileEdge.Right || edge == TileEdge.Both) {
r.fill = color.right;
r.draw(
gfx,
[
points[3],
points[2],
points[4],
points[5],
]
);
}
// Left Edge
if (edge == TileEdge.Left || edge == TileEdge.Both) {
r.fill = color.left;
r.draw(
gfx,
[
points[0],
points[2],
points[4],
points[6],
]
);
}
}
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);
// TODO: Optimize this
for (let k = 0; k < grid.height; ++k) {
for (let j = 0; j < grid.length; ++j) {
for (let i = 0; i < grid.width; ++i) {
tileCoord.x = i;
tileCoord.y = j;
tileCoord.z = k;
let tile = grid.getTile(tileCoord);
if (tile === null) {
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);
position.x += grid.tileSize;
}
position.y -= grid.tileSize;
position.x = grid.position.x;
}
position.y = grid.position.y;
}
}

220
src/js/graphics.js Normal file
View File

@ -0,0 +1,220 @@
import { Vec4 } from "./common.js";
export function fullscreenCanvas(gfx, id) {
const canvas = document.getElementById(id);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gfx.ctx.viewport(0, 0, canvas.width, canvas.height);
}
function createShader(ctx, type, source) {
var shader = ctx.createShader(type);
if (!shader) {
throw new Error("Couldn't create shader: " + type);
}
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
var success = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
if (!success) {
console.log(ctx.getShaderInfoLog(shader));
ctx.deleteShader(shader);
throw new Error("Couldn't compile shader: " + type);
}
return shader;
}
function createProgram(ctx, vertexShaderSource, fragmentShaderSource) {
var program = ctx.createProgram();
if (vertexShaderSource !== null) {
const vs = createShader(ctx, ctx.VERTEX_SHADER, vertexShaderSource);
ctx.attachShader(program, vs);
}
if (fragmentShaderSource !== null) {
const fs = createShader(ctx, ctx.FRAGMENT_SHADER, fragmentShaderSource);
ctx.attachShader(program, fs);
}
ctx.linkProgram(program);
var success = ctx.getProgramParameter(program, ctx.LINK_STATUS);
if (!success) {
console.log(ctx.getProgramInfoLog(program));
ctx.deleteProgram(program);
throw new Error("Failed to create program!");
}
return program;
}
export var DrawTag;
(function (DrawTag) {
DrawTag[DrawTag["ISO"] = 0] = "ISO";
})(DrawTag || (DrawTag = {}));
export class Graphics {
ctx;
program;
attribs = new Map();
uniforms = new Map();
vao;
vbo;
toRender = [];
texCount = 0;
constructor(ctx, vs, fs) {
this.ctx = ctx;
this.program = createProgram(ctx, vs, fs);
this.vao = ctx.createVertexArray();
this.vbo = ctx.createBuffer();
ctx.bindVertexArray(this.vao);
ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.useProgram(this.program);
ctx.blendFunc(ctx.SRC_ALPHA, ctx.ONE_MINUS_SRC_ALPHA);
ctx.enable(ctx.BLEND);
}
clear(r, g, b, a) {
this.ctx.clearColor(r, g, b, a);
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);
}
createAttribute(name) {
const attrib = new Attribute(this.ctx, this.program, name);
this.attribs.set(name, attrib);
return attrib;
}
getAttribute(name) {
const attrib = this.attribs.get(name);
if (attrib === undefined)
throw new Error("Tried to get uninitialized attribute: " + name);
return attrib;
}
createUniform(name) {
const loc = this.ctx.getUniformLocation(this.program, name);
if (loc === null)
throw new Error("Couldn't get location for uniform: " + name);
this.uniforms.set(name, loc);
}
getUniform(name) {
const loc = this.uniforms.get(name);
if (loc === undefined)
throw new Error("Tried to get uninitialized uniform: " + name);
return loc;
}
draw() {
for (let o of this.toRender) {
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vbo);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(o.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);
aid += attr.size;
}
// Generalize the tag uniforms aka. don't hard code them
for (let t of o.tags) {
switch (t) {
case DrawTag.ISO: {
this.ctx.uniform1ui(this.getUniform("u_isIso"), 1);
break;
}
}
}
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);
}
for (let t of o.tags) {
switch (t) {
case DrawTag.ISO: {
this.ctx.uniform1ui(this.getUniform("u_isIso"), 0);
break;
}
}
}
}
// TODO: Maybe add persistent rendering?
this.toRender = [];
}
}
export class Attribute {
loc;
formatted = false;
size = 0;
type = 0;
offset = 0;
normalized = false;
constructor(ctx, program, name) {
this.loc = ctx.getAttribLocation(program, name);
}
format(size, type, normalized, offset) {
this.size = size;
this.type = type;
this.normalized = normalized;
this.offset = offset;
this.formatted = true;
}
}
export class Texture {
tex;
texId;
width = 0;
height = 0;
constructor(texId, tex, width, height) {
this.height = height;
this.width = width;
this.tex = tex;
this.texId = texId;
}
// TODO: Load sprite sheet only once
// TODO: Allow changing sprite size
static async load(gfx, path) {
let image = await loadTexture(path);
let tex = gfx.ctx.createTexture();
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, tex);
gfx.ctx.texImage2D(gfx.ctx.TEXTURE_2D, 0, gfx.ctx.RGBA, gfx.ctx.RGBA, gfx.ctx.UNSIGNED_BYTE, image);
gfx.ctx.texParameteri(gfx.ctx.TEXTURE_2D, gfx.ctx.TEXTURE_MAG_FILTER, gfx.ctx.NEAREST);
gfx.ctx.texParameteri(gfx.ctx.TEXTURE_2D, gfx.ctx.TEXTURE_MIN_FILTER, gfx.ctx.NEAREST);
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, null);
gfx.texCount += 1;
return new Texture(gfx.texCount, tex, image.width, image.height);
}
bind(gfx) {
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, this.tex);
}
unbind(gfx) {
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, null);
}
}
async function loadTexture(path) {
return new Promise(resolve => {
const img = new Image();
img.addEventListener("load", () => {
resolve(img);
});
img.src = path;
});
}
export class Camera {
dt = 0;
position;
movement;
speed = 600;
scale = 1.0;
scaling = 0.0;
scaleSpeed = 1.5;
constructor(position) {
this.position = position;
this.movement = new Vec4(0, 0, 0, 0);
}
update(dt) {
this.dt = dt;
let newPosition = this.movement.multScalarNew(this.dt);
this.position.x += (newPosition.x + newPosition.y) * this.speed;
this.position.y += (newPosition.z + newPosition.w) * this.speed;
this.scale += this.scaling * this.dt * this.scaleSpeed;
if (this.scale < 0.5) {
this.scale = 0.5;
}
if (this.scale > 1.5) {
this.scale = 1.5;
}
}
}

298
src/js/graphics.ts Normal file
View File

@ -0,0 +1,298 @@
import * as Assets from "./assets.js";
import {Vec2, Vec3, Vec4} from "./common.js";
export function fullscreenCanvas(gfx: Graphics, id: string) {
const canvas = document.getElementById(id) as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gfx.ctx.viewport(0, 0, canvas.width, canvas.height);
}
function createShader(ctx: WebGL2RenderingContext, type: GLenum, source: string): WebGLShader {
var shader = ctx.createShader(type);
if (!shader) {
throw new Error("Couldn't create shader: " + type);
}
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
var success = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
if (!success) {
console.log(ctx.getShaderInfoLog(shader));
ctx.deleteShader(shader);
throw new Error("Couldn't compile shader: " + type);
}
return shader;
}
function createProgram(ctx: WebGL2RenderingContext, vertexShaderSource: string | null, fragmentShaderSource: string | null): WebGLProgram {
var program = ctx.createProgram();
if (vertexShaderSource !== null) {
const vs = createShader(ctx, ctx.VERTEX_SHADER, vertexShaderSource);
ctx.attachShader(program, vs);
}
if (fragmentShaderSource !== null) {
const fs = createShader(ctx, ctx.FRAGMENT_SHADER, fragmentShaderSource);
ctx.attachShader(program, fs);
}
ctx.linkProgram(program);
var success = ctx.getProgramParameter(program, ctx.LINK_STATUS);
if (!success) {
console.log(ctx.getProgramInfoLog(program));
ctx.deleteProgram(program);
throw new Error("Failed to create program!");
}
return program;
}
export enum DrawTag {
ISO,
}
export interface Drawable {
fill: Texture | Assets.Color;
attribs: string[];
tags: Array<DrawTag>;
data: Array<number>;
vertexStride: number;
stride: number;
}
export class Graphics {
ctx: WebGL2RenderingContext;
program: WebGLProgram;
attribs: Map<string, Attribute> = new Map();
uniforms: Map<string, WebGLUniformLocation> = new Map();
vao: WebGLVertexArrayObject;
vbo: WebGLBuffer;
toRender: Array<Drawable> = [];
texCount: number = 0;
constructor(ctx: WebGL2RenderingContext, vs: string, fs: string) {
this.ctx = ctx;
this.program = createProgram(ctx, vs, fs);
this.vao = ctx.createVertexArray();
this.vbo = ctx.createBuffer();
ctx.bindVertexArray(this.vao);
ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.useProgram(this.program);
ctx.blendFunc(ctx.SRC_ALPHA, ctx.ONE_MINUS_SRC_ALPHA);
ctx.enable(ctx.BLEND);
}
clear(r: number, g: number, b: number, a: number) {
this.ctx.clearColor(r, g, b, a);
this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);
}
createAttribute(name: string): Attribute {
const attrib = new Attribute(this.ctx, this.program, name);
this.attribs.set(name, attrib);
return attrib;
}
getAttribute(name: string): Attribute {
const attrib = this.attribs.get(name);
if (attrib === undefined)
throw new Error("Tried to get uninitialized attribute: " + name);
return attrib;
}
createUniform(name: string) {
const loc = this.ctx.getUniformLocation(this.program, name);
if (loc === null)
throw new Error("Couldn't get location for uniform: " + name);
this.uniforms.set(name, loc);
}
getUniform(name: string): WebGLUniformLocation {
const loc = this.uniforms.get(name);
if (loc === undefined)
throw new Error("Tried to get uninitialized uniform: " + name);
return loc;
}
draw() {
for (let o of this.toRender) {
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vbo);
this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(o.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);
aid += attr.size;
}
// Generalize the tag uniforms aka. don't hard code them
for (let t of o.tags) {
switch (t) {
case DrawTag.ISO: {
this.ctx.uniform1ui(this.getUniform("u_isIso"), 1);
break;
}
}
}
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);
}
for (let t of o.tags) {
switch (t) {
case DrawTag.ISO: {
this.ctx.uniform1ui(this.getUniform("u_isIso"), 0);
break;
}
}
}
}
// TODO: Maybe add persistent rendering?
this.toRender = [];
}
}
export class Attribute {
loc: GLint;
formatted: boolean = false;
size: GLint = 0;
type: GLenum = 0;
offset: GLintptr = 0;
normalized: GLboolean = false;
constructor(ctx: WebGL2RenderingContext, program: WebGLProgram, name: string) {
this.loc = ctx.getAttribLocation(program, name);
}
format(
size: GLint,
type: GLenum,
normalized: GLboolean,
offset: GLintptr)
{
this.size = size;
this.type = type;
this.normalized = normalized;
this.offset = offset;
this.formatted = true;
}
}
export class Texture {
tex: WebGLTexture | null;
texId: number;
width: number = 0;
height: number = 0;
constructor(texId: number, tex: WebGLTexture, width: number, height: number) {
this.height = height;
this.width = width;
this.tex = tex;
this.texId = texId;
}
// TODO: Load sprite sheet only once
// TODO: Allow changing sprite size
static async load(gfx: Graphics, path: string): Promise<Texture> {
let image = await loadTexture(path);
let tex = gfx.ctx.createTexture();
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, tex);
gfx.ctx.texImage2D(gfx.ctx.TEXTURE_2D, 0, gfx.ctx.RGBA, gfx.ctx.RGBA, gfx.ctx.UNSIGNED_BYTE, image);
gfx.ctx.texParameteri(gfx.ctx.TEXTURE_2D, gfx.ctx.TEXTURE_MAG_FILTER, gfx.ctx.NEAREST);
gfx.ctx.texParameteri(gfx.ctx.TEXTURE_2D, gfx.ctx.TEXTURE_MIN_FILTER, gfx.ctx.NEAREST);
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, null);
gfx.texCount += 1;
return new Texture(gfx.texCount, tex, image.width, image.height);
}
bind(gfx: Graphics) {
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, this.tex);
}
unbind(gfx: Graphics) {
gfx.ctx.bindTexture(gfx.ctx.TEXTURE_2D, null);
}
}
async function loadTexture(path: string): Promise<HTMLImageElement> {
return new Promise(resolve => {
const img = new Image();
img.addEventListener("load", () => {
resolve(img);
})
img.src = path;
});
}
export class Camera {
dt: number = 0;
position: Vec3;
movement: Vec4;
speed: number = 600;
scale: number = 1.0;
scaling: number = 0.0;
scaleSpeed: number = 1.5;
constructor(position: Vec3) {
this.position = position;
this.movement = new Vec4(0, 0, 0, 0);
}
update(dt: number) {
this.dt = dt;
let newPosition = this.movement.multScalarNew(this.dt);
this.position.x += (newPosition.x + newPosition.y) * this.speed;
this.position.y += (newPosition.z + newPosition.w) * this.speed;
this.scale += this.scaling * this.dt * this.scaleSpeed;
if (this.scale < 0.5) {
this.scale = 0.5;
}
if (this.scale > 1.5) {
this.scale = 1.5;
}
}
}

35
src/js/input.js Normal file
View File

@ -0,0 +1,35 @@
class KeyAction {
key;
keyup;
keydown;
data;
constructor(key, ku, kd, data) {
this.key = key;
this.keyup = ku;
this.keydown = kd;
this.data = data;
}
}
export class Input {
handlers = new Map();
constructor() {
window.addEventListener("keyup", e => {
this.handlers.forEach(ka => {
if (ka.keyup !== null && ka.key == e.code) {
ka.keyup(ka.data);
}
});
});
window.addEventListener("keydown", e => {
this.handlers.forEach(ka => {
if (ka.keydown !== null && ka.key == e.code) {
ka.keydown(ka.data);
}
});
});
}
//TODO: add modifier key support
addKeyAction(key, modifiers, data, keydown, keyup) {
this.handlers.set(key, new KeyAction(key, keydown, keyup, data));
}
}

39
src/js/input.ts Normal file
View File

@ -0,0 +1,39 @@
type Action = ((data: any) => void);
class KeyAction {
key: string;
keyup: Action | null;
keydown: Action | null;
data: any;
constructor(key: string, ku: Action | null, kd: Action | null, data: any) {
this.key = key;
this.keyup = ku;
this.keydown = kd;
this.data = data;
}
}
export class Input {
handlers: Map<string, KeyAction> = new Map();
constructor() {
window.addEventListener("keyup", e => {
this.handlers.forEach(ka => {
if (ka.keyup !== null && ka.key == e.code) {
ka.keyup(ka.data);
}
});
});
window.addEventListener("keydown", e => {
this.handlers.forEach(ka => {
if (ka.keydown !== null && ka.key == e.code) {
ka.keydown(ka.data);
}
});
});
}
//TODO: add modifier key support
addKeyAction(key: string, modifiers: [string] | [], data: any, keydown: Action | null, keyup: Action | null) {
this.handlers.set(key, new KeyAction(key, keydown, keyup, data));
}
}

170
src/js/script.js Normal file
View File

@ -0,0 +1,170 @@
import { initializeContext, Vec3, Mat4, Vec4, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas, Camera } 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 * 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 = 2) in vec4 a_color;
out vec4 v_color;
out vec2 v_tex_position;
uniform mat4 u_matrix;
uniform bool u_isIso;
uniform bool u_isTex;
mat4 Iso = mat4(
1, -1, 0, 0,
1, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
void main() {
vec4 orthographic;
if (u_isIso) {
vec4 isometric = Iso * vec4(a_position.xyz, 1.0);
orthographic = u_matrix * isometric;
//orthographic = u_matrix * vec4(a_position.xyz, 1.0);
} else {
orthographic = u_matrix * vec4(a_position.xyz, 1.0);
}
gl_Position = orthographic;
if (u_isTex) {
v_tex_position = a_tex_position;
} else {
v_color = a_color;
}
}
`;
const fragmentShader = `#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_tex_position;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
} else {
outColor = v_color;
}
}
`;
function draw(gfx, camera, dt, grid) {
gfx.clear(0, 0, 0, 0);
camera.update(dt);
let right = gfx.ctx.canvas.width;
let left = 0;
let top = gfx.ctx.canvas.height;
let bottom = 0;
let near = -100;
let far = 100;
let m = Mat4.IDENTITY();
let mo = Mat4.orthographic(left, right, bottom, top, near, far);
let mc = Mat4.translate(new Vec3((gfx.ctx.canvas.width / 2 - camera.position.x), (gfx.ctx.canvas.height / 2 - camera.position.y), 1.0));
let mr = Mat4.translate(new Vec3(-(gfx.ctx.canvas.width / 2 - camera.position.x), -(gfx.ctx.canvas.height / 2 - camera.position.y), 1.0));
let mt = Mat4.translate(camera.position);
let ms = Mat4.scale(new Vec3(camera.scale, camera.scale, 1));
m = m.multNew(mr);
m = m.multNew(ms);
m = m.multNew(mc);
m = m.multNew(mt);
m = m.multNew(mo);
gfx.ctx.uniformMatrix4fv(gfx.getUniform("u_matrix"), false, m.splat());
drawing.drawIsometricGrid(gfx, grid);
gfx.draw();
}
function addDefaultKeybinds(input, camera) {
input.addKeyAction("KeyA", [], camera, (c) => {
c.movement.x = 0;
}, (c) => {
c.movement.x = 1;
});
input.addKeyAction("KeyD", [], camera, (c) => {
c.movement.y = 0;
}, (c) => {
c.movement.y = -1;
});
input.addKeyAction("KeyW", [], camera, (c) => {
c.movement.z = 0;
}, (c) => {
c.movement.z = -1;
});
input.addKeyAction("KeyS", [], camera, (c) => {
c.movement.w = 0;
}, (c) => {
c.movement.w = 1;
});
input.addKeyAction("KeyQ", [], camera, (c) => {
c.scaling = 0.0;
}, (c) => {
c.scaling = 1.0;
});
input.addKeyAction("KeyE", [], camera, (c) => {
c.scaling = 0.0;
}, (c) => {
c.scaling = -1.0;
});
}
(async () => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
if (ctx === null)
return;
const gfx = new Graphics(ctx, vertexShader, fragmentShader);
fullscreenCanvas(gfx, canvasId);
let a = gfx.createAttribute("a_position");
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);
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"),
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));
//for (let i = 0; i < 10; i++) {
// tree(grid, new Vec2(Math.floor(Math.random() * size - 1), Math.floor(Math.random() * size - 1)));
//}
//grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(0, 29, 1));
let prevTimestamp = 0;
const frame = (timestamp) => {
const deltaTime = (timestamp - prevTimestamp) / 1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, camera, deltaTime, grid);
window.requestAnimationFrame(frame);
};
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
const input = new Input();
addDefaultKeybinds(input, camera);
const wasmgl = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();

230
src/js/script.ts Normal file
View File

@ -0,0 +1,230 @@
import { initializeContext, Vec3, Mat4, Vec4, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas, Camera, DrawTag } 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 * 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 = 2) in vec4 a_color;
out vec4 v_color;
out vec2 v_tex_position;
uniform mat4 u_matrix;
uniform bool u_isIso;
uniform bool u_isTex;
mat4 Iso = mat4(
1, -1, 0, 0,
1, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
void main() {
vec4 orthographic;
if (u_isIso) {
vec4 isometric = Iso * vec4(a_position.xyz, 1.0);
orthographic = u_matrix * isometric;
//orthographic = u_matrix * vec4(a_position.xyz, 1.0);
} else {
orthographic = u_matrix * vec4(a_position.xyz, 1.0);
}
gl_Position = orthographic;
if (u_isTex) {
v_tex_position = a_tex_position;
} else {
v_color = a_color;
}
}
`;
const fragmentShader =
`#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_tex_position;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
} else {
outColor = v_color;
}
}
`;
function draw(gfx: Graphics, camera: Camera, dt: number, grid: Grid) {
gfx.clear(0, 0, 0, 0);
camera.update(dt);
let right = gfx.ctx.canvas.width;
let left = 0;
let top = gfx.ctx.canvas.height;
let bottom = 0;
let near = -100;
let far = 100;
let m = Mat4.IDENTITY();
let mo = Mat4.orthographic(left, right, bottom, top, near, far);
let mc = Mat4.translate(
new Vec3(
(gfx.ctx.canvas.width / 2 - camera.position.x),
(gfx.ctx.canvas.height / 2 - camera.position.y),
1.0));
let mr = Mat4.translate(
new Vec3(
-(gfx.ctx.canvas.width / 2 - camera.position.x),
-(gfx.ctx.canvas.height / 2 - camera.position.y),
1.0));
let mt = Mat4.translate(camera.position);
let ms = Mat4.scale(new Vec3(camera.scale, camera.scale, 1));
m = m.multNew(mr);
m = m.multNew(ms);
m = m.multNew(mc);
m = m.multNew(mt);
m = m.multNew(mo);
gfx.ctx.uniformMatrix4fv(
gfx.getUniform("u_matrix"),
false,
m.splat()
);
drawing.drawIsometricGrid(gfx, grid);
gfx.draw();
}
function addDefaultKeybinds(input: Input, camera: Camera) {
input.addKeyAction("KeyA", [], camera,
(c) => {
c.movement.x = 0;
},
(c) => {
c.movement.x = 1;
});
input.addKeyAction("KeyD", [], camera,
(c) => {
c.movement.y = 0;
},
(c) => {
c.movement.y = -1;
});
input.addKeyAction("KeyW", [], camera,
(c) => {
c.movement.z = 0;
},
(c) => {
c.movement.z = -1;
});
input.addKeyAction("KeyS", [], camera,
(c) => {
c.movement.w = 0;
},
(c) => {
c.movement.w = 1;
});
input.addKeyAction("KeyQ", [], camera,
(c) => {
c.scaling = 0.0;
},
(c) => {
c.scaling = 1.0;
});
input.addKeyAction("KeyE", [], camera,
(c) => {
c.scaling = 0.0;
},
(c) => {
c.scaling = -1.0;
});
}
(async () => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
if (ctx === null) return;
const gfx = new Graphics(ctx, vertexShader, fragmentShader);
fullscreenCanvas(gfx, canvasId);
let a = gfx.createAttribute("a_position");
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)
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"),
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));
//for (let i = 0; i < 10; i++) {
// tree(grid, new Vec2(Math.floor(Math.random() * size - 1), Math.floor(Math.random() * size - 1)));
//}
//grid.setTile(new Tile(Assets.AssetToTileFill("log"), TileEdge.Both), new Vec3(0, 29, 1));
let prevTimestamp = 0;
const frame = (timestamp: number) => {
const deltaTime = (timestamp - prevTimestamp)/1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, camera, deltaTime, grid);
window.requestAnimationFrame(frame);
}
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
const input = new Input();
addDefaultKeybinds(input, camera);
const wasmgl: wasm.WASMGL = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();

37
src/js/wasm.js Normal file
View File

@ -0,0 +1,37 @@
export class WASMGLvalue {
ptr;
size;
constructor(ptr, size) {
this.ptr = ptr;
this.size = size;
}
}
export async function loadWasmModule(path) {
return WebAssembly.instantiateStreaming(fetch(path));
}
export class WASMGL {
exports;
mem;
constructor(wasm) {
this.exports = wasm.instance.exports;
const memSize = new Float32Array(this.exports.memory.buffer, this.exports.WASMGLmemory.value + 4, 1).at(0);
this.mem = new Float32Array(this.exports.memory.buffer, this.exports.WASMGLmemory.value, memSize);
}
alloc(size) {
const head = this.mem[2];
this.mem[2] += size;
return head;
}
setstr(ptr, str) {
for (let i = 0; i < str.length; i++) {
this.mem[ptr] = str.charCodeAt(i);
++ptr;
}
}
set(ptr, data) {
data.forEach((v, i) => {
this.mem[ptr + i] = v;
});
return ptr;
}
}

61
src/js/wasm.ts Normal file
View File

@ -0,0 +1,61 @@
export class WASMGLvalue {
ptr: number;
size: number;
constructor(ptr: number, size: number) {
this.ptr = ptr;
this.size = size;
}
}
export interface WASMGLEnvironment {
exports: any,
mem: Float32Array,
}
export async function loadWasmModule(path: string): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
return WebAssembly.instantiateStreaming(fetch(path));
}
export class WASMGL {
exports: any;
mem: Float32Array;
constructor(wasm: WebAssembly.WebAssemblyInstantiatedSource) {
this.exports = wasm.instance.exports;
const memSize = new Float32Array(
this.exports.memory.buffer,
this.exports.WASMGLmemory.value + 4,
1
).at(0);
this.mem = new Float32Array(
this.exports.memory.buffer,
this.exports.WASMGLmemory.value,
memSize
);
}
alloc(size: number): number {
const head = this.mem[2];
this.mem[2] += size;
return head;
}
setstr(ptr: number, str: string) {
for (let i = 0; i < str.length; i++) {
this.mem[ptr] = str.charCodeAt(i);
++ptr;
}
}
set(ptr: number, data: number[]): number {
data.forEach((v, i) => {
this.mem[ptr + i] = v;
});
return ptr;
}
}

97
src/js/world.js Normal file
View File

@ -0,0 +1,97 @@
import * as Assets from "./assets.js";
import { Vec3 } from "./common.js";
export var TileEdge;
(function (TileEdge) {
TileEdge[TileEdge["None"] = 0] = "None";
TileEdge[TileEdge["Left"] = 1] = "Left";
TileEdge[TileEdge["Right"] = 2] = "Right";
TileEdge[TileEdge["Both"] = 3] = "Both";
})(TileEdge || (TileEdge = {}));
export class Tile {
fill = {
top: [1, 0, 1, 1],
left: [0, 0, 0, 1],
right: [0, 0, 0, 1],
};
edge = TileEdge.None;
constructor(fill, edge) {
if (fill !== undefined)
this.fill = fill;
if (edge !== undefined)
this.edge = edge;
}
}
export function ColorToTile(c) {
return {
left: c,
top: c,
right: c,
};
}
export class Grid {
position;
tiles3d;
tileSize;
width;
length;
height;
constructor(position, tileSize, width, length, height) {
this.tiles3d = new Array(height);
this.position = position;
this.tileSize = tileSize;
this.width = width;
this.length = length;
this.height = height;
let layer = new Array(width * length);
for (let i = 0; i < this.height; ++i)
this.tiles3d[i] = { ...layer };
}
fillLayer(tile, z) {
for (let i = 0; i < this.width; ++i) {
for (let j = 0; j < this.length; ++j) {
this.tiles3d[z][i + j * this.width] = { ...tile };
}
}
for (let i = 0; i < this.width; ++i) {
this.tiles3d[z][this.length * i - 1] = { ...tile, edge: TileEdge.Right };
}
for (let i = 0; i < this.length - 1; ++i) {
this.tiles3d[z][this.width * (this.length - 1) + i] = { ...tile, edge: TileEdge.Left };
}
this.tiles3d[z][this.width * this.length - 1] = { ...tile, edge: TileEdge.Both };
}
setTile(tile, coord) {
let index = coord.x + coord.y * this.width;
this.tiles3d[coord.z][index] = { ...tile };
}
getTile(coord) {
let index = coord.x + coord.y * this.width;
let tile = this.tiles3d[coord.z][index];
if (tile === undefined)
return null;
return tile;
}
}
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));
}
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));
}

122
src/js/world.ts Normal file
View File

@ -0,0 +1,122 @@
import * as Assets from "./assets.js";
import {Vec2, Vec3} from "./common.js";
import {Texture} from "./graphics.js";
export enum TileEdge {
None,
Left,
Right,
Both,
}
export type TileFillament = Texture | Assets.Color;
export type TileFill = { left: TileFillament, top: TileFillament, right: TileFillament };
export class Tile {
fill: TileFill = {
top : [1, 0, 1, 1],
left : [0, 0, 0, 1],
right : [0, 0, 0, 1],
};
edge: TileEdge = TileEdge.None;
constructor(fill?: TileFill, edge?: TileEdge) {
if (fill !== undefined)
this.fill = fill;
if (edge !== undefined)
this.edge = edge;
}
}
export function ColorToTile(c: Assets.Color) {
return {
left: c,
top: c,
right: c,
};
}
export class Grid {
position: Vec3;
tiles3d: Tile[][];
tileSize: number;
width: number;
length: number;
height: number;
constructor(position: Vec3, tileSize: number, width: number, length: number, height: number) {
this.tiles3d = new Array<Tile[]>(height);
this.position = position;
this.tileSize = tileSize;
this.width = width;
this.length = length;
this.height = height;
let layer = new Array(width * length);
for (let i = 0; i < this.height; ++i)
this.tiles3d[i] = {...layer};
}
fillLayer(tile: Tile, z: number) {
for (let i = 0; i < this.width; ++i) {
for (let j = 0; j < this.length; ++j) {
this.tiles3d[z][i + j * this.width] = {...tile};
}
}
for (let i = 0; i < this.width; ++i) {
this.tiles3d[z][this.length * i - 1] = {...tile, edge: TileEdge.Right};
}
for (let i = 0; i < this.length - 1; ++i) {
this.tiles3d[z][this.width * (this.length - 1) + i] = {...tile, edge: TileEdge.Left};
}
this.tiles3d[z][this.width * this.length - 1] = {...tile, edge: TileEdge.Both};
}
setTile(tile: Tile, coord: Vec3) {
let index = coord.x + coord.y * this.width;
this.tiles3d[coord.z][index] = {...tile};
}
getTile(coord: Vec3): Tile | null {
let index = coord.x + coord.y * this.width;
let tile = this.tiles3d[coord.z][index];
if (tile === undefined) return null;
return tile;
}
}
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));
}
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));
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));
}

10
src/wasm/build.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
set -xe
CC="clang"
CLIBS=""
CFLAGS="--target=wasm32 -flto -Wl,--lto-O3 -nostdlib -Wl,--no-entry -Wl,--export-all"
$CC $CFLAGS -o module.wasm *.c $CLIBS

12
src/wasm/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>aaa</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="" />
</head>
<body>
<script src="test.js"></script>
</body>
</html>

20
src/wasm/main.c Normal file
View File

@ -0,0 +1,20 @@
#include "wasmgl.h"
void doubel(WASMGLvalue(0))
{
for (int i = 0; i < size_0; ++i)
WASMGLset(ptr_0 + i, WASMGLmemory[ptr_0 + i] * 2.0);
}
void uppercase(WASMGLvalue(0)) {
int val = 0;
for (int i = 0; i < size_0; ++i)
{
val = WASMGLmemory[ptr_0 + i] - 32;
if (val < 65 || val > 122) val = WASMGLmemory[ptr_0 + i];
WASMGLset(ptr_0 + i, val);
}
}

BIN
src/wasm/module.wasm Executable file

Binary file not shown.

34
src/wasm/test.js Normal file
View File

@ -0,0 +1,34 @@
(async () => {
let instance = await WebAssembly.instantiateStreaming(fetch("module.wasm"));
const memSize = new Int32Array(
instance.instance.exports.memory.buffer,
instance.instance.exports.WASMGLmemory.value + 4,
1
)[0]
let mem = new Int32Array(
instance.instance.exports.memory.buffer,
instance.instance.exports.WASMGLmemory.value,
memSize
);
let ptr = alloc(mem, 4);
setstr(mem, ptr, 4, "game");
console.log(String.fromCharCode(instance.instance.exports.initialize(ptr, 4)));
})();
function alloc(mem, size) {
const head = mem[2];
console.log(head);
mem[2] += size;
return head;
}
function setstr(mem, ptr, size, str) {
for (let i = 0; i < size; i++) {
mem[ptr] = str.charCodeAt(i);
++ptr;
}
}

26
src/wasm/wasmgl.c Normal file
View File

@ -0,0 +1,26 @@
#include "wasmgl.h"
float WASMGLmemory[WASMGLmemory_size] = {
[MEM_SIZE] = WASMGLmemory_size,
[MEM_HEAD] = MEM_ELEM_COUNT,
0,
};
WASMGLptr WASMGLmalloc(WASMGLsize size)
{
WASMGLptr ptr = WASMGLmemory[MEM_HEAD];
WASMGLmemory[MEM_HEAD] += size;
return ptr;
}
void WASMGLset(WASMGLptr ptr, int value)
{
WASMGLmemory[ptr] = value;
}
void WASMGLsetstr(WASMGLptr ptr, const char * cstr, WASMGLsize size)
{
for (int i = 0; i < size; ++i)
WASMGLset(ptr + i, cstr[i]);
}

25
src/wasm/wasmgl.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef WASMGL_H_
#define WASMGL_H_
#define main WASMGLmain
#define WASMGLmemory_size (1024 * 1024)
typedef enum {
MEM_NULL = 0,
MEM_SIZE = 1,
MEM_HEAD,
MEM_ELEM_COUNT,
} WASMGLmemory_layout;
extern float WASMGLmemory[WASMGLmemory_size];
typedef unsigned int WASMGLptr;
typedef unsigned int WASMGLsize;
#define WASMGLvalue(n) WASMGLptr ptr_##n, WASMGLsize size_##n
WASMGLptr WASMGLmalloc(WASMGLsize size);
void WASMGLset(WASMGLptr ptr, int value);
void WASMGLsetstr(WASMGLptr ptr, const char * cstr, WASMGLsize size);
#endif // WASMGL_H_