Compare commits

..

10 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
46 changed files with 2223 additions and 465 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

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();

View File

@ -10,6 +10,9 @@ class Vec2 {
this.x = x;
this.y = y;
}
copy() {
return new Vec2(this.x, this.y);
}
add(other) {
this.x += other.x;
this.y += other.y;
@ -28,13 +31,43 @@ class Vec2 {
this.x -= other.x;
this.y -= other.y;
}
mult(scalar) {
subNew(other) {
let vec = this.copy();
vec.x -= other.x;
vec.y -= other.y;
return vec;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
}
multNew(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];
}
@ -60,15 +93,320 @@ class Vec3 {
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];
}
}
export { initializeContext, Vec2, Vec3 };
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 };

View File

@ -5,9 +5,27 @@ function initializeContext(canvasId: string): WebGL2RenderingContext | null {
return ctx;
}
type Color = [number, number, number, number]
// TODO: Make all vectors follow one interface
interface Vector<P, T, N> {
copy(): T;
class Vec2 {
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;
@ -16,6 +34,10 @@ class Vec2 {
this.y = y;
}
copy(): Vec2 {
return new Vec2(this.x, this.y);
}
add(other: Vec2) {
this.x += other.x;
this.y += other.y;
@ -39,15 +61,58 @@ class Vec2 {
this.y -= other.y;
}
mult(scalar: number) {
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;
}
multNew(scalar: number): Vec2 {
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];
}
@ -69,7 +134,7 @@ class Vec2 {
}
}
class Vec3 {
class Vec3 implements Vector<Vec2, Vec3, Vec4> {
x: number;
y: number;
z: number;
@ -80,17 +145,411 @@ class Vec3 {
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];
}
}
export { initializeContext, Vec2, Vec3, Color };
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 };

View File

@ -1,92 +1,159 @@
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);
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;
}
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);
// 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;
}
}
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);
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);
}
}
function drawLine(gfx, A, B, color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
export function drawIsometricCube(gfx, position, exts, color, edge) {
let points = [
A.x, A.y,
B.x, B.y,
// 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),
];
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);
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;
}
}
export { drawTriangle, drawTriangleExts, drawRectangle, drawCircle, drawLine };

View File

@ -1,116 +1,201 @@
import { Vec2 } from "./common.js"
import { Graphics } from "./graphics.js";
import { Color } from "./common.js";
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";
function drawTriangle(gfx: Graphics, positions: [Vec2, Vec2, Vec2], color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
// 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;
const points: Array<number> = [
positions[0].x, positions[0].y,
positions[1].x, positions[1].y,
positions[2].x, positions[2].y,
]
if (point.x + tileSize > screen.x + screen.z)
return true;
const colors: Array<number[]> = [
color,
color,
color,
];
if (point.y - tileSize < screen.y)
return true;
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);
if (point.y + tileSize > screen.y + screen.w)
return true;
return false;
}
function drawTriangleExts(gfx: Graphics, position: Vec2, exts: Vec2, color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
// Attrib format
// position color uv
// (3) (4) (2) => 3 + 4 + 2 = 9 <=> data.len % 9 == 0
const points: Array<number> = [
position.x, position.y,
position.x + exts.x, position.y,
position.x, position.y + exts.y,
]
export class Rectangle implements Drawable {
fill: Texture | Assets.Color;
attribs: string[] = ["a_position", "a_color", "a_tex_position"];
tags: Array<DrawTag> = [];
const colors: Array<number[]> = [
color,
color,
color,
];
data: number[] = [];
stride: number = 0;
vertexStride: number = 0;
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);
}
constructor(fill: Texture | Assets.Color, tags?: Array<DrawTag>, attribs?: string[]) {
this.fill = fill;
function drawRectangle(gfx: Graphics, position: Vec2, exts: Vec2, color: Color) {
const a_position = gfx.getAttribute("a_position");
const a_color = gfx.getAttribute("a_color");
if (attribs !== undefined) {
this.attribs = attribs;
}
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);
if (tags !== undefined) {
this.tags = tags;
}
}
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);
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);
}
}
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,
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),
];
const colors: Array<number[]> = [
color,
color,
];
let r = new Rectangle(color.top, [
DrawTag.ISO,
]);
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);
// 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 { drawTriangle, drawTriangleExts, drawRectangle, drawCircle, drawLine }
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;
}
}

View File

@ -1,4 +1,5 @@
function fullscreenCanvas(gfx, id) {
import { Vec4 } from "./common.js";
export function fullscreenCanvas(gfx, id) {
const canvas = document.getElementById(id);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
@ -38,19 +39,29 @@ function createProgram(ctx, vertexShaderSource, fragmentShaderSource) {
}
return program;
}
class Graphics {
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);
@ -79,32 +90,131 @@ class Graphics {
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 = [];
}
}
class Attribute {
export class Attribute {
loc;
buffer;
// TODO: maybe use undefined as default value?
formatted = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
normalized = false;
constructor(ctx, program, name) {
this.loc = ctx.getAttribLocation(program, name);
this.buffer = ctx.createBuffer();
ctx.enableVertexAttribArray(this.loc);
}
format(size, type, normalized, stride, offset) {
format(size, type, normalized, 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);
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;
}
}
}
export { fullscreenCanvas, Graphics, Attribute };

View File

@ -1,6 +1,7 @@
import { Vec2 } from "./common.js"
import * as Assets from "./assets.js";
import {Vec2, Vec3, Vec4} from "./common.js";
function fullscreenCanvas(gfx: Graphics, id: string) {
export function fullscreenCanvas(gfx: Graphics, id: string) {
const canvas = document.getElementById(id) as HTMLCanvasElement;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
@ -51,20 +52,43 @@ function createProgram(ctx: WebGL2RenderingContext, vertexShaderSource: string |
return program;
}
class Graphics {
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) {
@ -104,45 +128,171 @@ class Graphics {
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 = [];
}
}
class Attribute {
export class Attribute {
loc: GLint;
buffer: WebGLBuffer;
formatted: boolean = false;
// TODO: maybe use undefined as default value?
size: GLint = 0;
type: GLenum = 0;
normalized: GLboolean = false;
stride: GLsizei = 0;
offset: GLintptr = 0;
normalized: GLboolean = false;
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);
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;
}
}
}
export { fullscreenCanvas, Graphics, Attribute }

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));
}
}

View File

@ -1,53 +1,124 @@
import { initializeContext, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas } from "./graphics.js";
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
in vec2 a_position;
in vec4 a_color;
out vec4 color;
uniform vec2 u_resolution;
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() {
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
vec4 orthographic;
color = a_color;
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 = vec4(clipSpace.xy, 0.0, 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 color;
in vec4 v_color;
in vec2 v_tex_position;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
outColor = color;
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
} else {
outColor = v_color;
}
}
`;
function draw(gfx, dt, pos, velocity) {
function draw(gfx, camera, dt, grid) {
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));
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";
@ -56,24 +127,44 @@ function draw(gfx, dt, pos, velocity) {
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 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, deltaTime, pos, velocity);
draw(gfx, camera, deltaTime, grid);
window.requestAnimationFrame(frame);
};
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
let env = await wasm.loadWasmModule("./src/wasm/module.wasm");
const input = new Input();
addDefaultKeybinds(input, camera);
const wasmgl = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();

View File

@ -1,22 +1,50 @@
import { initializeContext, Vec2 } from "./common.js";
import { Graphics, fullscreenCanvas } from "./graphics.js";
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
in vec2 a_position;
in vec4 a_color;
out vec4 color;
uniform vec2 u_resolution;
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() {
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
vec4 orthographic;
color = a_color;
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 = vec4(clipSpace.xy, 0.0, 1.0);
gl_Position = orthographic;
if (u_isTex) {
v_tex_position = a_tex_position;
} else {
v_color = a_color;
}
}
`;
@ -24,42 +52,118 @@ const fragmentShader =
`#version 300 es
precision highp float;
in vec4 color;
in vec4 v_color;
in vec2 v_tex_position;
out vec4 outColor;
uniform sampler2D u_texture;
uniform bool u_isTex;
void main() {
outColor = color;
if (u_isTex) {
outColor = texture(u_texture, v_tex_position);
} else {
outColor = v_color;
}
}
`;
function draw(gfx: Graphics, dt: number, pos: Vec2, velocity: Vec2) {
function draw(gfx: Graphics, camera: Camera, dt: number, grid: Grid) {
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]);
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;
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;
}
let m = Mat4.IDENTITY();
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;
}
let mo = Mat4.orthographic(left, right, bottom, top, near, far);
pos.add(velocity.multNew(dt));
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 () => {
@ -70,24 +174,46 @@ function draw(gfx: Graphics, dt: number, pos: Vec2, velocity: Vec2) {
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);
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)
const a_color = gfx.createAttribute("a_color");
a_color.format(4, gfx.ctx.FLOAT, false, 0, 0);
gfx.createUniform("u_matrix");
gfx.createUniform("u_isIso");
gfx.createUniform("u_isTex");
gfx.createUniform("u_resolution");
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 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);
draw(gfx, camera, deltaTime, grid);
window.requestAnimationFrame(frame);
}
@ -97,5 +223,8 @@ function draw(gfx: Graphics, dt: number, pos: Vec2, velocity: Vec2) {
window.requestAnimationFrame(frame);
});
let env: any = await wasm.loadWasmModule("./src/wasm/module.wasm");
const input = new Input();
addDefaultKeybinds(input, camera);
const wasmgl: wasm.WASMGL = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();

View File

@ -1,4 +1,4 @@
class WASMGLvalue {
export class WASMGLvalue {
ptr;
size;
constructor(ptr, size) {
@ -6,58 +6,32 @@ class WASMGLvalue {
this.size = size;
}
}
async function loadWasmModule(path) {
let wasm = await WebAssembly.instantiateStreaming(fetch(path));
const memSize = new Int32Array(wasm.instance.exports.memory.buffer, wasm.instance.exports.WASMGLmemory.value + 4, 1)[0];
let memInt = new Int32Array(wasm.instance.exports.memory.buffer, wasm.instance.exports.WASMGLmemory.value, memSize);
let memFloat = new Float32Array(wasm.instance.exports.memory.buffer, wasm.instance.exports.WASMGLmemory.value, memSize);
wasm.instance.exports.WASMGLmain();
const data = [
1.5,
2.5,
3.5,
4.5,
5.5,
6.5,
7.5,
8.5,
];
let ptr = sendDataFloat(memFloat, data);
wasm.instance.exports.calc(ptr, data.length);
for (let i = 0; i < data.length; ++i) {
console.log(memFloat[ptr + i]);
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);
}
return wasm.instance.exports;
}
function alloc(mem, size) {
const head = mem[2];
mem[2] += size;
return head;
}
function allocFloat(mem, size) {
const head = mem[2];
mem[2] += size;
return head;
}
function setstr(mem, ptr, size, str) {
for (let i = 0; i < size; i++) {
mem[ptr] = str.charCodeAt(i);
++ptr;
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;
}
}
function sendData(mem, data) {
let ptr = alloc(mem, data.length);
data.forEach((v, i) => {
mem[ptr + i] = v;
});
return ptr;
}
function sendDataFloat(mem, data) {
let ptr = allocFloat(mem, data.length);
data.forEach((v, i) => {
mem[ptr + i] = v;
console.log(mem[ptr + i]);
});
return ptr;
}
export { loadWasmModule, alloc, setstr };

View File

@ -1,4 +1,4 @@
class WASMGLvalue {
export class WASMGLvalue {
ptr: number;
size: number;
@ -8,90 +8,54 @@ class WASMGLvalue {
}
}
async function loadWasmModule(path: string): Promise<WebAssembly.Exports> {
let wasm: any = await WebAssembly.instantiateStreaming(fetch(path));
export interface WASMGLEnvironment {
exports: any,
mem: Float32Array,
}
const memSize = new Int32Array(
wasm.instance.exports.memory.buffer,
wasm.instance.exports.WASMGLmemory.value + 4,
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
)[0];
).at(0);
let memInt = new Int32Array(
wasm.instance.exports.memory.buffer,
wasm.instance.exports.WASMGLmemory.value,
memSize
);
let memFloat = new Float32Array(
wasm.instance.exports.memory.buffer,
wasm.instance.exports.WASMGLmemory.value,
memSize
);
wasm.instance.exports.WASMGLmain();
const data = [
1.5,
2.5,
3.5,
4.5,
5.5,
6.5,
7.5,
8.5,
];
let ptr = sendDataFloat(memFloat, data);
wasm.instance.exports.calc(ptr, data.length);
for (let i = 0; i < data.length; ++i) {
console.log(memFloat[ptr + i]);
this.mem = new Float32Array(
this.exports.memory.buffer,
this.exports.WASMGLmemory.value,
memSize
);
}
return wasm.instance.exports;
}
alloc(size: number): number {
const head = this.mem[2];
this.mem[2] += size;
function alloc(mem: Int32Array, size: number): number {
const head = mem[2];
mem[2] += size;
return head;
}
return head;
}
setstr(ptr: number, str: string) {
for (let i = 0; i < str.length; i++) {
this.mem[ptr] = str.charCodeAt(i);
++ptr;
}
}
function allocFloat(mem: Float32Array, size: number): number {
const head = mem[2];
mem[2] += size;
set(ptr: number, data: number[]): number {
data.forEach((v, i) => {
this.mem[ptr + i] = v;
});
return head;
}
function setstr(mem: Int32Array, ptr: number, size: number, str: string) {
for (let i = 0; i < size; i++) {
mem[ptr] = str.charCodeAt(i);
++ptr;
return ptr;
}
}
function sendData(mem: Int32Array, data: number[]): number {
let ptr = alloc(mem, data.length);
data.forEach((v, i) => {
mem[ptr + i] = v;
});
return ptr;
}
function sendDataFloat(mem: Float32Array, data: number[]): number {
let ptr = allocFloat(mem, data.length);
data.forEach((v, i) => {
mem[ptr + i] = v;
console.log(mem[ptr + i]);
});
return ptr;
}
export { loadWasmModule, alloc, setstr }

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));
}

View File

@ -1,19 +1,20 @@
#include "wasmgl.h"
int WASMGLmemory[WASMGLmemory_size] = {
[MEM_SIZE] = WASMGLmemory_size,
[MEM_HEAD] = MEM_ELEM_COUNT,
0,
};
void calc(WASMGLvalue(0))
void doubel(WASMGLvalue(0))
{
for (int i = 0; i < size_0; ++i)
WASMGLmemory[ptr_0 + i] = ((float) WASMGLmemory[ptr_0 + i]) * 2.0;
WASMGLset(ptr_0 + i, WASMGLmemory[ptr_0 + i] * 2.0);
}
int main(void)
{
void uppercase(WASMGLvalue(0)) {
int val = 0;
return 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);
}
}

Binary file not shown.

View File

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

View File

@ -12,12 +12,14 @@ typedef enum {
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(int * mem, WASMGLsize size);
void WASMGLset(int * mem, WASMGLptr ptr, int value);
void WASMGLsetstr(int * mem, WASMGLptr ptr, const char * cstr, WASMGLsize size);
WASMGLptr WASMGLmalloc(WASMGLsize size);
void WASMGLset(WASMGLptr ptr, int value);
void WASMGLsetstr(WASMGLptr ptr, const char * cstr, WASMGLsize size);
#endif // WASMGL_H_