Added Input support

This commit is contained in:
Maciej Samborski 2024-12-27 14:05:20 +01:00
parent bdde494b5b
commit 2a1fe1d0a9
11 changed files with 307 additions and 46 deletions

BIN
assets/character.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -70,6 +70,18 @@ class Vec3 {
this.y -= other.y;
this.z -= other.z;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
}
multScalarNew(scalar) {
let vec = new Vec3(this.x, this.y, this.z);
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
return vec;
}
splatToArray() {
return [this.x, this.y, this.z];
}
@ -97,6 +109,20 @@ class Vec4 {
this.z -= other.z;
this.w -= other.w;
}
multScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
this.w *= scalar;
}
multScalarNew(scalar) {
let vec = new Vec4(this.x, this.y, this.z, this.w);
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
vec.w *= scalar;
return vec;
}
div(other) {
if (other.x == 0 ||
other.y == 0 ||
@ -123,6 +149,9 @@ class Vec4 {
vec.w /= other.w;
return vec;
}
reduce() {
return new Vec3(this.x, this.y, this.z);
}
}
class Mat4 {
data;
@ -150,13 +179,6 @@ class Mat4 {
return new Mat4(data);
}
static isometric() {
//let m = new Mat4(new Float32Array([
// Math.sqrt(3), 0, -Math.sqrt(3), 0,
// 1, 2, 1, 0,
// Math.sqrt(2), -Math.sqrt(2), Math.sqrt(2), 0,
// 0, 0, 0, 1,
//]));
//m.multScalar(1 / Math.sqrt(6));
let m = new Mat4(new Float32Array([
1, -1, 0, 0,
1, 1, 0, 0,
@ -192,6 +214,14 @@ class Mat4 {
]);
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,
]));
}
x(n) {
return this.data[n];
}

View File

@ -7,6 +7,11 @@ function initializeContext(canvasId: string): WebGL2RenderingContext | null {
type Color = [number, number, number, number]
// TODO: Make all vectors follow one interface
interface Vector {
}
class Vec2 {
x: number;
y: number;
@ -92,6 +97,22 @@ class Vec3 {
this.z -= other.z;
}
multScalar(scalar: number) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
}
multScalarNew(scalar: number): Vec3 {
let vec = new Vec3(this.x, this.y, this.z);
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
return vec;
}
splatToArray(): Array<number> {
return [this.x, this.y, this.z];
}
@ -124,6 +145,24 @@ class Vec4 {
this.w -= other.w;
}
multScalar(scalar: number) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
this.w *= scalar;
}
multScalarNew(scalar: number): Vec4 {
let vec = new Vec4(this.x, this.y, this.z, this.w);
vec.x *= scalar;
vec.y *= scalar;
vec.z *= scalar;
vec.w *= scalar;
return vec;
}
div(other: Vec4) {
if ( other.x == 0 ||
other.y == 0 ||
@ -157,6 +196,10 @@ class Vec4 {
return vec;
}
reduce(): Vec3 {
return new Vec3(this.x, this.y, this.z);
}
}
type Mat4Init = number | Float32Array;
@ -199,15 +242,6 @@ class Mat4 {
}
static isometric(): Mat4 {
//let m = new Mat4(new Float32Array([
// Math.sqrt(3), 0, -Math.sqrt(3), 0,
// 1, 2, 1, 0,
// Math.sqrt(2), -Math.sqrt(2), Math.sqrt(2), 0,
// 0, 0, 0, 1,
//]));
//m.multScalar(1 / Math.sqrt(6));
let m = new Mat4(new Float32Array([
1, -1, 0, 0,
1, 1, 0, 0,
@ -251,6 +285,15 @@ class Mat4 {
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,
]));
}
x(n: Mat4X) {
return this.data[n];
}

View File

@ -87,7 +87,7 @@ export function drawRectangle(gfx: Graphics, corners: [Vec3, Vec3, Vec3, Vec3],
}
export function drawIsometricCube(gfx: Graphics, position: Vec3, exts: Vec3, color: Color) {
export function drawIsometricCube(gfx: Graphics, position: Vec3, exts: Vec3, color: Color | Texture) {
let points = [
position,
new Vec3(position.x, position.y + exts.y, position.z),

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,7 +39,7 @@ function createProgram(ctx, vertexShaderSource, fragmentShaderSource) {
}
return program;
}
class Graphics {
export class Graphics {
ctx;
program;
attribs = new Map();
@ -80,7 +81,7 @@ class Graphics {
return loc;
}
}
class Attribute {
export class Attribute {
loc;
buffer;
// TODO: maybe use undefined as default value?
@ -107,7 +108,7 @@ class Attribute {
ctx.vertexAttribPointer(this.loc, this.size, this.type, this.normalized, this.stride, this.offset);
}
}
class Texture {
export class Texture {
tex;
constructor(tex) {
this.tex = tex;
@ -141,4 +142,18 @@ async function loadTexture(path) {
img.src = path;
});
}
export { Texture, fullscreenCanvas, Graphics, Attribute };
export class Camera {
position;
movement;
dt = 0;
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) / 1.5;
this.position.y += newPosition.z + newPosition.w;
}
}

View File

@ -1,4 +1,6 @@
function fullscreenCanvas(gfx: Graphics, id: string) {
import {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;
@ -49,7 +51,7 @@ function createProgram(ctx: WebGL2RenderingContext, vertexShaderSource: string |
return program;
}
class Graphics {
export class Graphics {
ctx: WebGL2RenderingContext;
program: WebGLProgram;
attribs: Map<string, Attribute> = new Map();
@ -104,7 +106,7 @@ class Graphics {
}
}
class Attribute {
export class Attribute {
loc: GLint;
buffer: WebGLBuffer;
@ -142,7 +144,7 @@ class Attribute {
}
}
class Texture {
export class Texture {
tex: WebGLTexture | null;
constructor(tex: WebGLTexture) {
@ -184,4 +186,22 @@ async function loadTexture(path: string): Promise<HTMLImageElement> {
});
}
export { Texture, fullscreenCanvas, Graphics, Attribute }
export class Camera {
position: Vec3;
movement: Vec4;
dt: number = 0;
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) / 1.5;
this.position.y += newPosition.z + newPosition.w;
}
}

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,7 +1,8 @@
import { initializeContext, Vec3, Mat4 } from "./common.js";
import { Graphics, fullscreenCanvas, Texture } from "./graphics.js";
import { Graphics, fullscreenCanvas, Texture, Camera } from "./graphics.js";
import * as drawing from "./draw.js";
import * as wasm from "./wasm.js";
import { Input } from "./input.js";
const vertexShader = `#version 300 es
in vec3 a_position;
@ -11,9 +12,16 @@ const vertexShader = `#version 300 es
out vec4 v_color;
uniform mat4 u_matrix;
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 transformed = u_matrix * vec4(a_position.xyz, 1.0);
vec4 transformed = u_matrix * Iso * vec4(a_position.xyz, 1.0);
gl_Position = transformed;
@ -43,8 +51,9 @@ const fragmentShader = `#version 300 es
}
}
`;
function draw(gfx, angle, tex) {
function draw(gfx, camera, dt, tex) {
gfx.clear(0, 0, 0, 0);
camera.update(dt);
let right = gfx.ctx.canvas.width;
let left = 0;
let top = gfx.ctx.canvas.height;
@ -52,8 +61,8 @@ function draw(gfx, angle, tex) {
let near = -100;
let far = 100;
let mo = Mat4.orthographic(left, right, bottom, top, near, far);
let mi = Mat4.isometric();
let m = mi.multNew(mo);
let mt = Mat4.translate(camera.position);
let m = mo.multNew(mt);
//m = m.multNew(Mat4.rotation_x(angle));
//m = m.multNew(Mat4.rotation_y(angle));
//m = m.multNew(Mat4.rotation_z(angle));
@ -65,10 +74,32 @@ function draw(gfx, angle, tex) {
if ((i + j) % 2)
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, [1, 1, 1, 1]);
else
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, [0, 0, 0, 1]);
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, tex);
}
}
}
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;
});
}
(async () => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
@ -85,19 +116,21 @@ function draw(gfx, angle, tex) {
gfx.createUniform("u_matrix");
gfx.createUniform("u_isTex");
let city = await Texture.load(ctx, "../../assets/genetica/rt/City Night.jpg");
let angle = 0;
let wall = await Texture.load(ctx, "../../assets/wall.png");
let camera = new Camera(new Vec3(0, 0, 0));
let prevTimestamp = 0;
const frame = (timestamp) => {
const deltaTime = (timestamp - prevTimestamp) / 1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, angle, city);
angle += Math.PI * deltaTime * 0.5;
draw(gfx, camera, deltaTime, wall);
window.requestAnimationFrame(frame);
};
window.requestAnimationFrame((timestamp) => {
prevTimestamp = timestamp;
window.requestAnimationFrame(frame);
});
let input = new Input();
addDefaultKeybinds(input, camera);
let wasmgl = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();

View File

@ -1,7 +1,8 @@
import { initializeContext, Vec2, Vec3, Mat4 } from "./common.js";
import { Graphics, fullscreenCanvas, Texture } from "./graphics.js";
import { initializeContext, Vec3, Mat4 } from "./common.js";
import { Graphics, fullscreenCanvas, Texture, Camera } from "./graphics.js";
import * as drawing from "./draw.js";
import * as wasm from "./wasm.js";
import { Input } from "./input.js";
const vertexShader =
`#version 300 es
@ -13,9 +14,16 @@ const vertexShader =
out vec4 v_color;
uniform mat4 u_matrix;
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 transformed = u_matrix * vec4(a_position.xyz, 1.0);
vec4 transformed = u_matrix * Iso * vec4(a_position.xyz, 1.0);
gl_Position = transformed;
@ -50,8 +58,10 @@ const fragmentShader =
function draw(gfx: Graphics, angle: number, tex: Texture) {
function draw(gfx: Graphics, camera: Camera, dt: number, tex: Texture) {
gfx.clear(0, 0, 0, 0);
camera.update(dt);
let right = gfx.ctx.canvas.width;
let left = 0;
@ -61,8 +71,9 @@ function draw(gfx: Graphics, angle: number, tex: Texture) {
let far = 100;
let mo = Mat4.orthographic(left, right, bottom, top, near, far);
let mi = Mat4.isometric();
let m = mi.multNew(mo);
let mt = Mat4.translate(camera.position);
let m = mo.multNew(mt);
//m = m.multNew(Mat4.rotation_x(angle));
//m = m.multNew(Mat4.rotation_y(angle));
//m = m.multNew(Mat4.rotation_z(angle));
@ -81,11 +92,42 @@ function draw(gfx: Graphics, angle: number, tex: Texture) {
if ((i + j) % 2)
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, [1, 1, 1, 1]);
else
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, [0, 0, 0, 1]);
drawing.drawIsometricCube(gfx, new Vec3(exts.x * i, 1000 - exts.y * j, 0), exts, tex);
}
}
}
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;
});
}
(async () => {
const canvasId = "game";
const ctx = initializeContext(canvasId);
@ -107,16 +149,17 @@ function draw(gfx: Graphics, angle: number, tex: Texture) {
gfx.createUniform("u_isTex");
let city = await Texture.load(ctx, "../../assets/genetica/rt/City Night.jpg");
let wall = await Texture.load(ctx, "../../assets/wall.png");
let camera = new Camera(new Vec3(0, 0, 0));
let angle = 0;
let prevTimestamp = 0;
const frame = (timestamp: number) => {
const deltaTime = (timestamp - prevTimestamp)/1000;
prevTimestamp = timestamp;
fullscreenCanvas(gfx, "game");
draw(gfx, angle, city);
angle += Math.PI * deltaTime * 0.5;
draw(gfx, camera, deltaTime, wall);
window.requestAnimationFrame(frame);
}
@ -126,5 +169,8 @@ function draw(gfx: Graphics, angle: number, tex: Texture) {
window.requestAnimationFrame(frame);
});
let input = new Input();
addDefaultKeybinds(input, camera);
let wasmgl: wasm.WASMGL = new wasm.WASMGL(await wasm.loadWasmModule("./src/wasm/module.wasm"));
})();