diff --git a/assets/character.png b/assets/character.png new file mode 100644 index 0000000..4f0fe76 Binary files /dev/null and b/assets/character.png differ diff --git a/assets/wall.png b/assets/wall.png new file mode 100644 index 0000000..b80d69a Binary files /dev/null and b/assets/wall.png differ diff --git a/src/js/common.js b/src/js/common.js index 7276c5b..f0c813a 100644 --- a/src/js/common.js +++ b/src/js/common.js @@ -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]; } diff --git a/src/js/common.ts b/src/js/common.ts index 7359db9..ad60e56 100644 --- a/src/js/common.ts +++ b/src/js/common.ts @@ -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 { 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]; } diff --git a/src/js/draw.ts b/src/js/draw.ts index 8879fff..8863560 100644 --- a/src/js/draw.ts +++ b/src/js/draw.ts @@ -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), diff --git a/src/js/graphics.js b/src/js/graphics.js index 64d9a0b..d52313c 100644 --- a/src/js/graphics.js +++ b/src/js/graphics.js @@ -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; + } +} diff --git a/src/js/graphics.ts b/src/js/graphics.ts index 4214bdd..b638221 100644 --- a/src/js/graphics.ts +++ b/src/js/graphics.ts @@ -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 = 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 { }); } -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; + } +} + diff --git a/src/js/input.js b/src/js/input.js new file mode 100644 index 0000000..646dbbf --- /dev/null +++ b/src/js/input.js @@ -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)); + } +} diff --git a/src/js/input.ts b/src/js/input.ts new file mode 100644 index 0000000..ee34396 --- /dev/null +++ b/src/js/input.ts @@ -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 = 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)); + } +} diff --git a/src/js/script.js b/src/js/script.js index 8a154ec..f5c1c71 100644 --- a/src/js/script.js +++ b/src/js/script.js @@ -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")); })(); diff --git a/src/js/script.ts b/src/js/script.ts index ac2e9e4..b2a0c8e 100644 --- a/src/js/script.ts +++ b/src/js/script.ts @@ -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")); })();