From 1b7dee4be09207b2484b34ecf6fee402032bb734 Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Mon, 21 Sep 2020 09:42:00 +0200 Subject: [PATCH] Start using the sdf-2d library --- frontend/custom.d.ts | 24 --- frontend/package.json | 8 +- frontend/src/index.ts | 83 ++++----- frontend/src/scripts/game.ts | 71 ++++--- .../graphics/commands/before-render.ts | 12 -- .../src/scripts/graphics/commands/render.ts | 4 +- .../graphics/drawables/drawable-blob.ts | 32 ---- .../graphics/drawables/drawable-tunnel.ts | 28 --- .../drawables/i-drawable-descriptor.ts | 8 - .../scripts/graphics/drawables/i-drawable.ts | 6 - .../graphics/drawables/lights/circle-light.ts | 46 ----- .../graphics/drawables/lights/flashlight.ts | 50 ----- .../graphics/drawables/lights/i-light.ts | 3 - .../compiling/check-program.ts | 13 -- .../compiling/check-shader.ts | 7 - .../compiling/create-program.ts | 44 ----- .../compiling/create-shader.ts | 17 -- .../frame-buffer/default-frame-buffer.ts | 19 -- .../frame-buffer/frame-buffer.ts | 44 ----- .../frame-buffer/intermediate-frame-buffer.ts | 89 --------- .../helper/enable-extension.ts | 41 ---- .../helper/get-webgl2-context.ts | 9 - .../graphics-library/helper/load-uniform.ts | 82 -------- .../graphics-library/helper/stopwatch.ts | 50 ----- .../program/fragment-shader-only-program.ts | 48 ----- .../graphics-library/program/i-program.ts | 9 - .../graphics-library/program/program.ts | 101 ---------- .../uniform-array-autoscaling-program.ts | 90 --------- frontend/src/scripts/graphics/i-renderer.ts | 19 -- .../graphics/rendering/fps-autoscaler.ts | 45 ----- .../graphics/rendering/rendering-pass.ts | 86 --------- .../graphics/rendering/uniforms-provider.ts | 95 ---------- .../graphics/rendering/webgl2-renderer.ts | 129 ------------- frontend/src/scripts/graphics/settings.ts | 59 ------ .../scripts/graphics/shaders/distance-fs.glsl | 109 ----------- .../scripts/graphics/shaders/distance-vs.glsl | 15 -- .../scripts/graphics/shaders/shading-fs.glsl | 176 ------------------ .../scripts/graphics/shaders/shading-vs.glsl | 59 ------ frontend/src/scripts/helper/array.ts | 49 ----- frontend/src/scripts/helper/autoscaler.ts | 60 ------ .../scripts/helper/delta-time-calculator.ts | 23 +++ frontend/src/scripts/helper/pretty-print.ts | 4 + frontend/src/scripts/helper/timing.ts | 35 ---- frontend/src/scripts/objects/types/camera.ts | 10 +- .../src/scripts/objects/types/character.ts | 20 +- .../src/scripts/objects/types/info-text.ts | 36 ---- frontend/src/scripts/objects/types/lamp.ts | 8 +- frontend/src/scripts/objects/types/tunnel.ts | 8 +- frontend/src/scripts/shapes/i-shape.ts | 7 +- .../src/scripts/shapes/types/blob-shape.ts | 127 +++++++++++++ frontend/src/scripts/shapes/types/blob.ts | 60 ------ .../types/{circle.ts => circle-shape.ts} | 17 +- .../src/scripts/shapes/types/tunnel-shape.ts | 41 ++-- frontend/src/styles/main.scss | 41 ++-- frontend/tsconfig.json | 4 +- frontend/webpack.config.js | 53 ++---- 56 files changed, 314 insertions(+), 2119 deletions(-) delete mode 100644 frontend/custom.d.ts delete mode 100644 frontend/src/scripts/graphics/commands/before-render.ts delete mode 100644 frontend/src/scripts/graphics/drawables/drawable-blob.ts delete mode 100644 frontend/src/scripts/graphics/drawables/drawable-tunnel.ts delete mode 100644 frontend/src/scripts/graphics/drawables/i-drawable-descriptor.ts delete mode 100644 frontend/src/scripts/graphics/drawables/i-drawable.ts delete mode 100644 frontend/src/scripts/graphics/drawables/lights/circle-light.ts delete mode 100644 frontend/src/scripts/graphics/drawables/lights/flashlight.ts delete mode 100644 frontend/src/scripts/graphics/drawables/lights/i-light.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/compiling/check-program.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/compiling/check-shader.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/compiling/create-program.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/compiling/create-shader.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/frame-buffer/default-frame-buffer.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/frame-buffer/frame-buffer.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/helper/enable-extension.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/helper/get-webgl2-context.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/helper/load-uniform.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/helper/stopwatch.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/program/fragment-shader-only-program.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/program/i-program.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/program/program.ts delete mode 100644 frontend/src/scripts/graphics/graphics-library/program/uniform-array-autoscaling-program.ts delete mode 100644 frontend/src/scripts/graphics/i-renderer.ts delete mode 100644 frontend/src/scripts/graphics/rendering/fps-autoscaler.ts delete mode 100644 frontend/src/scripts/graphics/rendering/rendering-pass.ts delete mode 100644 frontend/src/scripts/graphics/rendering/uniforms-provider.ts delete mode 100644 frontend/src/scripts/graphics/rendering/webgl2-renderer.ts delete mode 100644 frontend/src/scripts/graphics/settings.ts delete mode 100644 frontend/src/scripts/graphics/shaders/distance-fs.glsl delete mode 100644 frontend/src/scripts/graphics/shaders/distance-vs.glsl delete mode 100644 frontend/src/scripts/graphics/shaders/shading-fs.glsl delete mode 100644 frontend/src/scripts/graphics/shaders/shading-vs.glsl delete mode 100644 frontend/src/scripts/helper/array.ts delete mode 100644 frontend/src/scripts/helper/autoscaler.ts create mode 100644 frontend/src/scripts/helper/delta-time-calculator.ts create mode 100644 frontend/src/scripts/helper/pretty-print.ts delete mode 100644 frontend/src/scripts/helper/timing.ts delete mode 100644 frontend/src/scripts/objects/types/info-text.ts create mode 100644 frontend/src/scripts/shapes/types/blob-shape.ts delete mode 100644 frontend/src/scripts/shapes/types/blob.ts rename frontend/src/scripts/shapes/types/{circle.ts => circle-shape.ts} (74%) diff --git a/frontend/custom.d.ts b/frontend/custom.d.ts deleted file mode 100644 index 7c29b64..0000000 --- a/frontend/custom.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module '*.glsl' { - const content: string; - export default content; -} - -/* -declare module '*.png' { - import { ResponsiveImage } from 'src/framework/model/misc'; - const content: ResponsiveImage; - export default content; -} - -declare module '*.jpg' { - import { ResponsiveImage } from 'src/framework/model/misc'; - const content: ResponsiveImage; - export default content; -} - -declare module '*.jpeg' { - import { ResponsiveImage } from 'src/framework/model/misc'; - const content: ResponsiveImage; - export default content; -} -*/ diff --git a/frontend/package.json b/frontend/package.json index 1836b01..2f2c3f1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,10 +7,10 @@ "scripts": { "start": "webpack-dev-server --mode development", "lint": "npx eslint --fix \"src/**/*.ts\" && npx prettier --write \"src/**/*.ts\"", - "build": "webpack && find dist -type f -not -name '*.html' | xargs rm" + "build": "webpack --mode production && find dist -type f -not -name '*.html' | xargs rm" }, "keywords": [], - "author": "András Schmelczer", + "author": "András Schmelczer (https://schmelczer.dev/)", "postcss": { "plugins": { "autoprefixer": {} @@ -22,9 +22,6 @@ "sideEffects": [ "*.scss" ], - "optimization": { - "usedExports": true - }, "devDependencies": { "@types/gl-matrix": "^2.4.5", "@types/uuid": "^8.0.0", @@ -35,6 +32,7 @@ "css-loader": "^3.5.2", "cssnano": "^4.1.10", "eslint": "^7.2.0", + "sdf-2d": "^0.1.0-alpha", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.21.2", "eslint-plugin-prettier": "^3.1.4", diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 048e364..2204ff0 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -1,13 +1,27 @@ -import { glMatrix, vec2 } from 'gl-matrix'; +import { glMatrix } from 'gl-matrix'; import { Game } from './scripts/game'; -import { applyArrayPlugins } from './scripts/helper/array'; import { Random } from './scripts/helper/random'; -import TunnelShape from './scripts/shapes/types/tunnel-shape'; import './styles/main.scss'; -glMatrix.setMatrixArrayType(Array); -applyArrayPlugins(); +glMatrix.setMatrixArrayType( + Array +); /* + } + } + zeroes.forEach((z) => { + const blueIndex = z.y * (width * 4) + z.x * 4 + 2; + imageData.data[blueIndex] = 255; + imageData.data[blueIndex - 1] = 255; + imageData.data[blueIndex - 2] = 255; + }); + + console.log(errors); + + ctx.putImageData(imageData, 0, 0); +};*/ //testSDF(); // extract as new image (data-uri) + +/* const testSDF = () => { Random.seed = 44; @@ -24,19 +38,32 @@ const testSDF = () => { res = -Math.log2(res) / k; return -res; - */ - /*let min = 1000; + */ /*let min = 1000; for (const t of objects) { min = Math.min(min, t.distance(position)); } if (min < 0) { // min = Math.min(min, vec2.distance(position, vec2.fromValues(40, 80 - 31.62)) - 31.62); } - return -min;*/ + return -min;*/ /* +const tree = new BoundingBoxTree([ + new BoundingBox(300, 550, 150, 550, 'A'), + new BoundingBox(400, 800, 50, 200, 'B'), + new BoundingBox(450, 500, 175, 185, 'C'), + new BoundingBox(100, 200, 100, 500, 'D'), + new BoundingBox(750, 950, 450, 600, 'E'), + new BoundingBox(940, 1000, -2, 180, 'F'), + new BoundingBox(960, 1050, 50, 190, 'G'), + new BoundingBox(150, 900, 0, 575, 'H'), + new BoundingBox(-10000, 10000, -10000, 10000, 'I'), +]); +tree.print(); +console.log(tree.findIntersecting(new BoundingBox(960, 1050, 50, 190, 'G'))); +*/ /* return -vec2.distance(position, vec2.fromValues(40, 40)) + 30; }; - +/* const width = 80; const height = 100; @@ -92,43 +119,7 @@ const testSDF = () => { /*if (zeroes.find((z) => vec2.distance(z, position) < dist) !== undefined) { const blueIndex = y * (width * 4) + x * 4 + 2; imageData.data[blueIndex] = 255; - }*/ - } - } - - zeroes.forEach((z) => { - const blueIndex = z.y * (width * 4) + z.x * 4 + 2; - imageData.data[blueIndex] = 255; - imageData.data[blueIndex - 1] = 255; - imageData.data[blueIndex - 2] = 255; - }); - - console.log(errors); - - ctx.putImageData(imageData, 0, 0); -}; - -//testSDF(); -// extract as new image (data-uri) - -/* -const tree = new BoundingBoxTree([ - new BoundingBox(300, 550, 150, 550, 'A'), - new BoundingBox(400, 800, 50, 200, 'B'), - new BoundingBox(450, 500, 175, 185, 'C'), - new BoundingBox(100, 200, 100, 500, 'D'), - new BoundingBox(750, 950, 450, 600, 'E'), - new BoundingBox(940, 1000, -2, 180, 'F'), - new BoundingBox(960, 1050, 50, 190, 'G'), - new BoundingBox(150, 900, 0, 575, 'H'), - new BoundingBox(-10000, 10000, -10000, 10000, 'I'), -]); - -tree.print(); -console.log(tree.findIntersecting(new BoundingBox(960, 1050, 50, 190, 'G'))); -*/ - -const main = async () => { + }*/ const main = async () => { try { Random.seed = 42; await new Game().start(); diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 9dd3844..7726d73 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -1,9 +1,8 @@ +import { vec3 } from 'gl-matrix'; +import { CircleLight, compile, Flashlight, Renderer } from 'sdf-2d'; import { CommandBroadcaster } from './commands/command-broadcaster'; -import { BeforeRenderCommand } from './graphics/commands/before-render'; import { RenderCommand } from './graphics/commands/render'; -import { IRenderer } from './graphics/i-renderer'; -import { WebGl2Renderer } from './graphics/rendering/webgl2-renderer'; -import { timeIt } from './helper/timing'; +import { prettyPrint } from './helper/pretty-print'; import { IGame } from './i-game'; import { KeyboardListener } from './input/keyboard-listener'; import { MouseListener } from './input/mouse-listener'; @@ -12,13 +11,14 @@ import { GameObject } from './objects/game-object'; import { Objects } from './objects/objects'; import { Camera } from './objects/types/camera'; import { Character } from './objects/types/character'; -import { InfoText } from './objects/types/info-text'; import { createDungeon } from './objects/world/create-dungeon'; import { MoveToCommand } from './physics/commands/move-to'; import { StepCommand } from './physics/commands/step'; import { TeleportToCommand } from './physics/commands/teleport-to'; import { Physics } from './physics/physics'; import { BoundingBoxBase } from './shapes/bounding-box-base'; +import { CircleShape } from './shapes/types/circle-shape'; +import { TunnelShape } from './shapes/types/tunnel-shape'; export class Game implements IGame { public readonly objects = new Objects(); @@ -26,14 +26,13 @@ export class Game implements IGame { public readonly camera = new Camera(); private previousTime?: DOMHighResTimeStamp = null; private previousFpsValues: Array = []; - private infoText = new InfoText(); private character: Character; - private renderer: IRenderer; - private initializeRendererPromise: Promise; + private renderer: Renderer; + private rendererPromise: Promise; + private overlay: HTMLElement = document.querySelector('#overlay'); constructor() { const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); - const overlay: HTMLElement = document.querySelector('#overlay'); document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); @@ -46,14 +45,31 @@ export class Game implements IGame { [this.objects] ); - this.renderer = new WebGl2Renderer(canvas, overlay); - this.initializeRendererPromise = this.renderer.initialize(); + this.rendererPromise = compile( + canvas, + [ + CircleShape.descriptor, + TunnelShape.descriptor, + Flashlight.descriptor, + CircleLight.descriptor, + ], + [ + vec3.fromValues(0, 0, 0), + vec3.fromValues(224 / 255, 96 / 255, 126 / 255), + vec3.fromValues(119 / 255, 173 / 255, 120 / 255), + ] + ); this.initializeScene(); this.physics.start(); } public async start(): Promise { - await this.initializeRendererPromise; + this.renderer = await this.rendererPromise; + this.renderer.setRuntimeSettings({ + isWorldInverted: true, + ambientLight: vec3.fromValues(0.35, 0.1, 0.45), + shadowLength: 300, + }); requestAnimationFrame(this.gameLoop.bind(this)); } @@ -70,8 +86,6 @@ export class Game implements IGame { } private initializeScene() { - this.objects.addObject(this.infoText); - const start = createDungeon(this.objects, this.physics); createDungeon(this.objects, this.physics); @@ -90,7 +104,6 @@ export class Game implements IGame { } } - @timeIt() private gameLoop(time: DOMHighResTimeStamp) { if (this.previousTime === null) { this.previousTime = time; @@ -98,44 +111,26 @@ export class Game implements IGame { const deltaTime = time - this.previousTime; this.previousTime = time; - this.calculateFps(deltaTime); this.objects.sendCommand(new StepCommand(deltaTime)); this.camera.sendCommand(new MoveToCommand(this.character.position)); - this.renderer.startFrame(deltaTime); - - this.camera.sendCommand(new BeforeRenderCommand(this.renderer)); + this.camera.sendCommand(new RenderCommand(this.renderer)); const shouldBeDrawn = this.physics .findIntersecting(this.camera.viewArea) .map((b) => b.shape?.gameObject); for (const object of shouldBeDrawn) { - object?.sendCommand(new BeforeRenderCommand(this.renderer)); object?.sendCommand(new RenderCommand(this.renderer)); } this.character.sendCommand(new RenderCommand(this.renderer)); - this.infoText.sendCommand(new RenderCommand(this.renderer)); - this.renderer.finishFrame(); + this.renderer.renderDrawables(); + + this.overlay.innerText = prettyPrint(this.renderer.insights); localStorage.setItem('character-position', JSON.stringify(this.character.position)); - window.requestAnimationFrame(this.gameLoop.bind(this)); - } - - private calculateFps(deltaTime: number) { - this.previousFpsValues.push(1000 / deltaTime); - if (this.previousFpsValues.length > 30) { - this.previousFpsValues.sort(); - const min = this.previousFpsValues[0]; - const median = this.previousFpsValues[ - Math.floor(this.previousFpsValues.length / 2) - ]; - - InfoText.modifyRecord('FPS', { min, median }); - - this.previousFpsValues = []; - } + requestAnimationFrame(this.gameLoop.bind(this)); } } diff --git a/frontend/src/scripts/graphics/commands/before-render.ts b/frontend/src/scripts/graphics/commands/before-render.ts deleted file mode 100644 index 982f6ad..0000000 --- a/frontend/src/scripts/graphics/commands/before-render.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Command } from '../../commands/command'; -import { IRenderer } from '../i-renderer'; - -export class BeforeRenderCommand extends Command { - public constructor(public readonly renderer?: IRenderer) { - super(); - } - - public get type(): string { - return 'BeforeRenderCommand'; - } -} diff --git a/frontend/src/scripts/graphics/commands/render.ts b/frontend/src/scripts/graphics/commands/render.ts index aceab30..5c0cc8d 100644 --- a/frontend/src/scripts/graphics/commands/render.ts +++ b/frontend/src/scripts/graphics/commands/render.ts @@ -1,8 +1,8 @@ +import { Renderer } from 'sdf-2d'; import { Command } from '../../commands/command'; -import { IRenderer } from '../../graphics/i-renderer'; export class RenderCommand extends Command { - public constructor(public readonly renderer?: IRenderer) { + public constructor(public readonly renderer?: Renderer) { super(); } diff --git a/frontend/src/scripts/graphics/drawables/drawable-blob.ts b/frontend/src/scripts/graphics/drawables/drawable-blob.ts deleted file mode 100644 index 9d89119..0000000 --- a/frontend/src/scripts/graphics/drawables/drawable-blob.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { mat2d, vec2 } from 'gl-matrix'; -import { Blob } from '../../shapes/types/blob'; -import { settings } from '../settings'; -import { IDrawable } from './i-drawable'; -import { IDrawableDescriptor } from './i-drawable-descriptor'; - -export class DrawableBlob extends Blob implements IDrawable { - public static descriptor: IDrawableDescriptor = { - uniformName: 'blobs', - countMacroName: 'blobCount', - shaderCombinationSteps: settings.shaderCombinations.blobSteps, - empty: new DrawableBlob(vec2.fromValues(0, 0)), - }; - - public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void { - const { uniformName } = DrawableBlob.descriptor; - if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) { - uniforms[uniformName] = []; - } - uniforms[uniformName].push({ - headCenter: vec2.transformMat2d(vec2.create(), this.head.center, transform), - leftFootCenter: vec2.transformMat2d(vec2.create(), this.leftFoot.center, transform), - rightFootCenter: vec2.transformMat2d( - vec2.create(), - this.rightFoot.center, - transform - ), - headRadius: this.headRadius * scale, - footRadius: this.footRadius * scale, - }); - } -} diff --git a/frontend/src/scripts/graphics/drawables/drawable-tunnel.ts b/frontend/src/scripts/graphics/drawables/drawable-tunnel.ts deleted file mode 100644 index 7076c32..0000000 --- a/frontend/src/scripts/graphics/drawables/drawable-tunnel.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { mat2d, vec2 } from 'gl-matrix'; -import TunnelShape from '../../shapes/types/tunnel-shape'; -import { settings } from '../settings'; -import { IDrawable } from './i-drawable'; -import { IDrawableDescriptor } from './i-drawable-descriptor'; - -export class DrawableTunnel extends TunnelShape implements IDrawable { - public static descriptor: IDrawableDescriptor = { - uniformName: 'lines', - countMacroName: 'lineCount', - shaderCombinationSteps: settings.shaderCombinations.lineSteps, - empty: new DrawableTunnel(vec2.fromValues(0, 0), vec2.fromValues(0, 0), 0, 0), - }; - - public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void { - const { uniformName } = DrawableTunnel.descriptor; - if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) { - uniforms[uniformName] = []; - } - - uniforms[uniformName].push({ - from: vec2.transformMat2d(vec2.create(), this.from, transform), - toFromDelta: vec2.scale(vec2.create(), this.toFromDelta, scale), - fromRadius: this.fromRadius * scale, - toRadius: this.toRadius * scale, - }); - } -} diff --git a/frontend/src/scripts/graphics/drawables/i-drawable-descriptor.ts b/frontend/src/scripts/graphics/drawables/i-drawable-descriptor.ts deleted file mode 100644 index 5e669bf..0000000 --- a/frontend/src/scripts/graphics/drawables/i-drawable-descriptor.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IDrawable } from './i-drawable'; - -export interface IDrawableDescriptor { - uniformName: string; - countMacroName: string; - shaderCombinationSteps: Array; - readonly empty: IDrawable; -} diff --git a/frontend/src/scripts/graphics/drawables/i-drawable.ts b/frontend/src/scripts/graphics/drawables/i-drawable.ts deleted file mode 100644 index cca827b..0000000 --- a/frontend/src/scripts/graphics/drawables/i-drawable.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { vec2, mat2d } from 'gl-matrix'; - -export interface IDrawable { - distance(target: vec2): number; - serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void; -} diff --git a/frontend/src/scripts/graphics/drawables/lights/circle-light.ts b/frontend/src/scripts/graphics/drawables/lights/circle-light.ts deleted file mode 100644 index 8bf7001..0000000 --- a/frontend/src/scripts/graphics/drawables/lights/circle-light.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { mat2d, vec2, vec3 } from 'gl-matrix'; -import { settings } from '../../settings'; -import { IDrawableDescriptor } from '../i-drawable-descriptor'; -import { ILight } from './i-light'; - -export class CircleLight implements ILight { - public static descriptor: IDrawableDescriptor = { - uniformName: 'circleLights', - countMacroName: 'circleLightCount', - shaderCombinationSteps: settings.shaderCombinations.circleLightSteps, - empty: new CircleLight(vec2.fromValues(0, 0), 0, vec3.fromValues(0, 0, 0), 0), - }; - - constructor( - public center: vec2, - public lightDrop: number, - public color: vec3, - public lightness: number - ) {} - - public distance(_: vec2): number { - return 0; - } - - public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void { - const { uniformName } = CircleLight.descriptor; - - if (!Object.prototype.hasOwnProperty.call(uniforms, uniformName)) { - uniforms[uniformName] = []; - } - - uniforms[uniformName].push({ - center: vec2.transformMat2d(vec2.create(), this.center, transform), - lightDrop: this.lightDrop, - value: this.value, - }); - } - - public get value(): vec3 { - return vec3.scale( - vec3.create(), - vec3.normalize(this.color, this.color), - this.lightness - ); - } -} diff --git a/frontend/src/scripts/graphics/drawables/lights/flashlight.ts b/frontend/src/scripts/graphics/drawables/lights/flashlight.ts deleted file mode 100644 index 180b464..0000000 --- a/frontend/src/scripts/graphics/drawables/lights/flashlight.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { mat2d, vec2, vec3 } from 'gl-matrix'; -import { settings } from '../../settings'; -import { IDrawableDescriptor } from '../i-drawable-descriptor'; -import { ILight } from './i-light'; - -export class Flashlight implements ILight { - public static descriptor: IDrawableDescriptor = { - uniformName: 'flashlights', - countMacroName: 'flashlightCount', - shaderCombinationSteps: settings.shaderCombinations.flashlightSteps, - empty: new Flashlight( - vec2.fromValues(0, 0), - vec2.fromValues(0, 0), - 0, - vec3.fromValues(0, 0, 0), - 0 - ), - }; - - public constructor( - public center: vec2, - public direction: vec2, - public lightDrop: number, - public color: vec3, - public lightness: number - ) {} - - public distance(_: vec2): number { - return 0; - } - - public serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void { - const listName = Flashlight.descriptor.uniformName; - - if (!Object.prototype.hasOwnProperty.call(uniforms, listName)) { - uniforms[listName] = []; - } - - uniforms[listName].push({ - center: vec2.transformMat2d(vec2.create(), this.center, transform), - direction: this.direction, - lightDrop: this.lightDrop, - value: this.value, - }); - } - - public get value(): vec3 { - return vec3.scale(vec3.create(), this.color, this.lightness); - } -} diff --git a/frontend/src/scripts/graphics/drawables/lights/i-light.ts b/frontend/src/scripts/graphics/drawables/lights/i-light.ts deleted file mode 100644 index 59e4431..0000000 --- a/frontend/src/scripts/graphics/drawables/lights/i-light.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IDrawable } from '../i-drawable'; - -export type ILight = IDrawable; diff --git a/frontend/src/scripts/graphics/graphics-library/compiling/check-program.ts b/frontend/src/scripts/graphics/graphics-library/compiling/check-program.ts deleted file mode 100644 index b750771..0000000 --- a/frontend/src/scripts/graphics/graphics-library/compiling/check-program.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { checkShader } from './check-shader'; - -export const checkProgram = (gl: WebGL2RenderingContext, program: WebGLProgram) => { - const success = gl.getProgramParameter(program, gl.LINK_STATUS); - - if (!success) { - gl.getAttachedShaders(program).forEach((s) => { - checkShader(gl, s); - }); - - throw new Error(gl.getProgramInfoLog(program)); - } -}; diff --git a/frontend/src/scripts/graphics/graphics-library/compiling/check-shader.ts b/frontend/src/scripts/graphics/graphics-library/compiling/check-shader.ts deleted file mode 100644 index 0608e06..0000000 --- a/frontend/src/scripts/graphics/graphics-library/compiling/check-shader.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const checkShader = (gl: WebGL2RenderingContext, shader: WebGLShader) => { - const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - - if (!success) { - throw new Error(gl.getShaderInfoLog(shader)); - } -}; diff --git a/frontend/src/scripts/graphics/graphics-library/compiling/create-program.ts b/frontend/src/scripts/graphics/graphics-library/compiling/create-program.ts deleted file mode 100644 index 174dbaa..0000000 --- a/frontend/src/scripts/graphics/graphics-library/compiling/create-program.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { waitWhileFalse } from '../../../helper/wait-while-false'; -import { tryEnableExtension } from '../helper/enable-extension'; -import { checkProgram } from './check-program'; -import { createShader } from './create-shader'; - -export const createProgram = async ( - gl: WebGL2RenderingContext, - vertexShaderSource: string, - fragmentShaderSource: string, - substitutions: { [name: string]: string } -): Promise => { - const program = gl.createProgram(); - - const extension = tryEnableExtension(gl, 'KHR_parallel_shader_compile'); - - const vertexShader = createShader( - gl, - gl.VERTEX_SHADER, - vertexShaderSource, - substitutions - ); - gl.attachShader(program, vertexShader); - - const fragmentShader = createShader( - gl, - gl.FRAGMENT_SHADER, - fragmentShaderSource, - substitutions - ); - gl.attachShader(program, fragmentShader); - - gl.linkProgram(program); - - if (extension) { - const checkCompileStatus = () => - gl.getProgramParameter(program, extension.COMPLETION_STATUS_KHR); - - await waitWhileFalse(checkCompileStatus); - } - - checkProgram(gl, program); - - return program; -}; diff --git a/frontend/src/scripts/graphics/graphics-library/compiling/create-shader.ts b/frontend/src/scripts/graphics/graphics-library/compiling/create-shader.ts deleted file mode 100644 index 5de03c2..0000000 --- a/frontend/src/scripts/graphics/graphics-library/compiling/create-shader.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const createShader = ( - gl: WebGL2RenderingContext, - type: GLenum, - source: string, - substitutions: { [name: string]: string } -): WebGLShader => { - source = source.replace(/{(.+)}/gm, (_, name: string): string => { - const value = substitutions[name]; - return Number.isInteger(value) ? `${value}.0` : value; - }); - - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - return shader; -}; diff --git a/frontend/src/scripts/graphics/graphics-library/frame-buffer/default-frame-buffer.ts b/frontend/src/scripts/graphics/graphics-library/frame-buffer/default-frame-buffer.ts deleted file mode 100644 index be495ad..0000000 --- a/frontend/src/scripts/graphics/graphics-library/frame-buffer/default-frame-buffer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { FrameBuffer } from './frame-buffer'; - -export class DefaultFrameBuffer extends FrameBuffer { - constructor(gl: WebGL2RenderingContext) { - super(gl); - this.frameBuffer = null; - this.setSize(); - } - - public setSize(): boolean { - const hasChanged = super.setSize(); - if (hasChanged) { - this.gl.canvas.width = this.size.x; - this.gl.canvas.height = this.size.y; - } - - return hasChanged; - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/frame-buffer/frame-buffer.ts b/frontend/src/scripts/graphics/graphics-library/frame-buffer/frame-buffer.ts deleted file mode 100644 index 14a2db8..0000000 --- a/frontend/src/scripts/graphics/graphics-library/frame-buffer/frame-buffer.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { settings } from '../../settings'; - -export abstract class FrameBuffer { - public renderScale = 1; - public enableHighDpiRendering = settings.enableHighDpiRendering; - - protected size = vec2.create(); - protected frameBuffer: WebGLFramebuffer; - - constructor(protected gl: WebGL2RenderingContext) {} - - public bindAndClear(colorInput?: WebGLTexture) { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); - - if (colorInput !== null) { - this.gl.bindTexture(this.gl.TEXTURE_2D, colorInput); - } - - this.gl.viewport(0, 0, this.size.x, this.size.y); - this.gl.clearColor(0, 0, 0, 0); - this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); - } - - public setSize(): boolean { - const realToCssPixels = - (this.enableHighDpiRendering ? window.devicePixelRatio : 1) * this.renderScale; - - const canvasWidth = (this.gl.canvas as HTMLCanvasElement).clientWidth; - const canvasHeight = (this.gl.canvas as HTMLCanvasElement).clientHeight; - - const displayWidth = Math.floor(canvasWidth * realToCssPixels); - const displayHeight = Math.floor(canvasHeight * realToCssPixels); - - const oldSize = vec2.clone(this.getSize()); - this.size = vec2.fromValues(displayWidth, displayHeight); - - return this.size.x != oldSize.x || this.size.y != oldSize.y; - } - - public getSize(): vec2 { - return this.size; - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts b/frontend/src/scripts/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts deleted file mode 100644 index f7fb178..0000000 --- a/frontend/src/scripts/graphics/graphics-library/frame-buffer/intermediate-frame-buffer.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { enableExtension } from '../helper/enable-extension'; -import { FrameBuffer } from './frame-buffer'; - -export class IntermediateFrameBuffer extends FrameBuffer { - private frameTexture: WebGLTexture; - private floatLinearEnabled = false; - - constructor(gl: WebGL2RenderingContext) { - super(gl); - - enableExtension(gl, 'EXT_color_buffer_float'); - - try { - enableExtension(gl, 'OES_texture_float_linear'); - this.floatLinearEnabled = true; - } catch { - // it's okay - } - - this.frameTexture = this.gl.createTexture(); - this.configureTexture(); - - this.frameBuffer = this.gl.createFramebuffer(); - this.configureFrameBuffer(); - - this.setSize(); - } - - public get colorTexture(): WebGLTexture { - return this.frameTexture; - } - - public setSize(): boolean { - const hasChanged = super.setSize(); - if (hasChanged) { - this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture); - - this.gl.texImage2D( - this.gl.TEXTURE_2D, - 0, - this.gl.RG16F, - this.size.x, - this.size.y, - 0, - this.gl.RG, - this.gl.FLOAT, - null - ); - } - - return hasChanged; - } - - private configureTexture() { - this.gl.bindTexture(this.gl.TEXTURE_2D, this.frameTexture); - this.gl.texParameteri( - this.gl.TEXTURE_2D, - this.gl.TEXTURE_MAG_FILTER, - this.floatLinearEnabled ? this.gl.LINEAR : this.gl.NEAREST - ); - this.gl.texParameteri( - this.gl.TEXTURE_2D, - this.gl.TEXTURE_MIN_FILTER, - this.floatLinearEnabled ? this.gl.LINEAR : this.gl.NEAREST - ); - this.gl.texParameteri( - this.gl.TEXTURE_2D, - this.gl.TEXTURE_WRAP_S, - this.gl.CLAMP_TO_EDGE - ); - this.gl.texParameteri( - this.gl.TEXTURE_2D, - this.gl.TEXTURE_WRAP_T, - this.gl.CLAMP_TO_EDGE - ); - } - - private configureFrameBuffer() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); - const attachmentPoint = this.gl.COLOR_ATTACHMENT0; - this.gl.framebufferTexture2D( - this.gl.FRAMEBUFFER, - attachmentPoint, - this.gl.TEXTURE_2D, - this.frameTexture, - 0 - ); - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/helper/enable-extension.ts b/frontend/src/scripts/graphics/graphics-library/helper/enable-extension.ts deleted file mode 100644 index 388a649..0000000 --- a/frontend/src/scripts/graphics/graphics-library/helper/enable-extension.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { InfoText } from '../../../objects/types/info-text'; - -const extensions: Map = new Map(); - -const printExtensions = () => { - const values = {}; - for (const [k, v] of extensions.entries()) { - values[k] = v !== null; - } - InfoText.modifyRecord('extensions', values); -}; - -export const tryEnableExtension = ( - gl: WebGL2RenderingContext, - name: string -): any | null => { - if (extensions.has(name)) { - return extensions.get(name); - } - - let extension = null; - if (gl.getSupportedExtensions().indexOf(name) != -1) { - extension = gl.getExtension(name); - } - - extensions.set(name, extension); - - printExtensions(); - - return extension; -}; - -export const enableExtension = (gl: WebGL2RenderingContext, name: string): any => { - const extension = tryEnableExtension(gl, name); - - if (extension === null) { - throw new Error(`Unsupported extension ${name}`); - } - - return extension; -}; diff --git a/frontend/src/scripts/graphics/graphics-library/helper/get-webgl2-context.ts b/frontend/src/scripts/graphics/graphics-library/helper/get-webgl2-context.ts deleted file mode 100644 index 30abe93..0000000 --- a/frontend/src/scripts/graphics/graphics-library/helper/get-webgl2-context.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const getWebGl2Context = (canvas: HTMLCanvasElement): WebGL2RenderingContext => { - const gl = canvas.getContext('webgl2'); - - if (!gl) { - throw new Error('WebGl2 is not supported'); - } - - return gl; -}; diff --git a/frontend/src/scripts/graphics/graphics-library/helper/load-uniform.ts b/frontend/src/scripts/graphics/graphics-library/helper/load-uniform.ts deleted file mode 100644 index 821816c..0000000 --- a/frontend/src/scripts/graphics/graphics-library/helper/load-uniform.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { mat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix'; - -const loaderMat3 = mat3.create(); - -export const loadUniform = ( - gl: WebGL2RenderingContext, - value: any, - type: GLenum, - location: WebGLUniformLocation -): any => { - const converters: Map< - GLenum, - (gl: WebGL2RenderingContext, value: any, location: WebGLUniformLocation) => void - > = new Map(); - { - converters.set(WebGL2RenderingContext.FLOAT, (gl, v, l) => { - if (v instanceof Array) { - if (v.length == 0) { - return; - } - - gl.uniform1fv(l, new Float32Array(v)); - } else { - gl.uniform1f(l, v); - } - }); - - converters.set(WebGL2RenderingContext.FLOAT_VEC2, (gl, v: vec2 | Array, l) => { - if (v.length == 0) { - return; - } - - if (v[0] instanceof Array) { - const result = new Float32Array(v.length * 2); - - for (let i = 0; i < v.length; i++) { - result[2 * i] = (v[i] as Array).x; - result[2 * i + 1] = (v[i] as Array).y; - } - - gl.uniform2fv(l, result); - } else { - gl.uniform2fv(l, v as vec2); - } - }); - - converters.set( - WebGL2RenderingContext.FLOAT_VEC3, - (gl, v: ReadonlyVec3 | Array, l) => { - if (v.length == 0) { - return; - } - - if (v[0] instanceof Array) { - const result = new Float32Array(v.length * 3); - - for (let i = 0; i < v.length; i++) { - result[3 * i] = (v[i] as Array)[0]; - result[3 * i + 1] = (v[i] as Array)[1]; - result[3 * i + 2] = (v[i] as Array)[2]; - } - - gl.uniform3fv(l, result); - } else { - gl.uniform3fv(l, v as vec3); - } - } - ); - - converters.set(WebGL2RenderingContext.FLOAT_MAT3, (gl, v, l) => { - gl.uniformMatrix3fv(l, true, mat3.fromMat2d(loaderMat3, v)); - }); - - converters.set(WebGL2RenderingContext.BOOL, (gl, v, l) => gl.uniform1i(l, v)); - - if (!converters.has(type)) { - throw new Error(`Unimplemented webgl type: ${type}`); - } - - converters.get(type)(gl, value, location); - } -}; diff --git a/frontend/src/scripts/graphics/graphics-library/helper/stopwatch.ts b/frontend/src/scripts/graphics/graphics-library/helper/stopwatch.ts deleted file mode 100644 index 595f0b9..0000000 --- a/frontend/src/scripts/graphics/graphics-library/helper/stopwatch.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { InfoText } from '../../../objects/types/info-text'; -import { enableExtension } from './enable-extension'; - -// https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query_webgl2/ - -export class WebGlStopwatch { - private timerExtension: any; - private timerQuery: WebGLQuery; - private isReady = true; - private resultsInNanoSeconds: number; - - constructor(private gl: WebGL2RenderingContext) { - this.timerExtension = enableExtension(gl, 'EXT_disjoint_timer_query_webgl2'); - } - - public start() { - if (this.isReady) { - this.timerQuery = this.gl.createQuery(); - this.gl.beginQuery(this.timerExtension.TIME_ELAPSED_EXT, this.timerQuery); - } - } - - public stop() { - if (this.isReady) { - this.gl.endQuery(this.timerExtension.TIME_ELAPSED_EXT); - this.isReady = false; - } - - const available = this.gl.getQueryParameter( - this.timerQuery, - this.gl.QUERY_RESULT_AVAILABLE - ); - const disjoint = this.gl.getParameter(this.timerExtension.GPU_DISJOINT_EXT); - - if (available && !disjoint) { - this.resultsInNanoSeconds = this.gl.getQueryParameter( - this.timerQuery, - this.gl.QUERY_RESULT - ); - - InfoText.modifyRecord('Draw time', `${this.resultsInMilliSeconds.toFixed(2)} ms`); - - this.isReady = true; - } - } - - public get resultsInMilliSeconds(): number { - return this.resultsInNanoSeconds / 1000 / 1000; - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/program/fragment-shader-only-program.ts b/frontend/src/scripts/graphics/graphics-library/program/fragment-shader-only-program.ts deleted file mode 100644 index 05701d4..0000000 --- a/frontend/src/scripts/graphics/graphics-library/program/fragment-shader-only-program.ts +++ /dev/null @@ -1,48 +0,0 @@ -import Program from './program'; - -export class FragmentShaderOnlyProgram extends Program { - private vao: WebGLVertexArrayObject; - - constructor( - gl: WebGL2RenderingContext, - sources: [string, string], - substitutions: { [name: string]: string } - ) { - super(gl, sources, substitutions); - } - - public async initialize(): Promise { - await super.initialize(); - this.prepareScreenQuad('vertexPosition'); - } - - public bind() { - super.bind(); - this.gl.bindVertexArray(this.vao); - } - - public draw() { - this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); - } - - private prepareScreenQuad(attributeName: string) { - const positionAttributeLocation = this.gl.getAttribLocation( - this.program, - attributeName - ); - - const positionBuffer = this.gl.createBuffer(); - - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); - this.gl.bufferData( - this.gl.ARRAY_BUFFER, - new Float32Array([-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]), - this.gl.STATIC_DRAW - ); - this.vao = this.gl.createVertexArray(); - - this.gl.bindVertexArray(this.vao); - this.gl.enableVertexAttribArray(positionAttributeLocation); - this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0); - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/program/i-program.ts b/frontend/src/scripts/graphics/graphics-library/program/i-program.ts deleted file mode 100644 index 901c327..0000000 --- a/frontend/src/scripts/graphics/graphics-library/program/i-program.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { vec2 } from 'gl-matrix'; - -export interface IProgram { - initialize(): Promise; - setDrawingRectangleUV(bottomLeft: vec2, size: vec2): void; - bindAndSetUniforms(values: { [name: string]: any }): void; - draw(): void; - delete(): void; -} diff --git a/frontend/src/scripts/graphics/graphics-library/program/program.ts b/frontend/src/scripts/graphics/graphics-library/program/program.ts deleted file mode 100644 index fef88b0..0000000 --- a/frontend/src/scripts/graphics/graphics-library/program/program.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { mat2d, vec2 } from 'gl-matrix'; -import { settings } from '../../settings'; -import { createProgram } from '../compiling/create-program'; -import { loadUniform } from '../helper/load-uniform'; -import { IProgram } from './i-program'; - -export default abstract class Program implements IProgram { - protected program: WebGLProgram; - private programPromise: Promise; - private modelTransform = mat2d.identity(mat2d.create()); - private readonly ndcToUv = mat2d.fromValues(0.5, 0, 0, 0.5, 0.5, 0.5); - private uniforms: Array<{ - name: Array; - location: WebGLUniformLocation; - type: GLenum; - }> = []; - - constructor( - protected gl: WebGL2RenderingContext, - [vertexShaderSource, fragmentShaderSource]: [string, string], - substitutions: { [name: string]: string } - ) { - substitutions = { ...settings.shaderMacros, ...substitutions }; - - this.programPromise = createProgram( - this.gl, - vertexShaderSource, - fragmentShaderSource, - substitutions - ); - } - - public async initialize(): Promise { - this.program = await this.programPromise; - this.queryUniforms(); - } - - public bindAndSetUniforms(values: { [name: string]: any }) { - this.bind(); - this.setUniforms({ modelTransform: this.modelTransform, ...values }); - } - - public setDrawingRectangleUV(bottomLeft: vec2, size: vec2) { - mat2d.invert(this.modelTransform, this.ndcToUv); - mat2d.translate(this.modelTransform, this.modelTransform, bottomLeft); - mat2d.scale(this.modelTransform, this.modelTransform, size); - mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv); - } - - public setUniforms(values: { [name: string]: any }) { - this.uniforms.forEach(({ name, location, type }) => { - const value = name.reduce( - (prev, prop) => (prev !== null && prop in prev ? prev[prop] : null), - values - ); - - if (value !== null) { - loadUniform(this.gl, value, type, location); - } - }); - } - - public delete() { - this.gl.getAttachedShaders(this.program).forEach((s) => this.gl.deleteShader(s)); - this.gl.deleteProgram(this.program); - } - - public abstract draw(): void; - - protected bind() { - this.gl.useProgram(this.program); - } - - private queryUniforms() { - const uniformCount = this.gl.getProgramParameter( - this.program, - WebGL2RenderingContext.ACTIVE_UNIFORMS - ); - - for (let i = 0; i < uniformCount; i++) { - const glUniform = this.gl.getActiveUniform(this.program, i); - - this.uniforms.push({ - name: glUniform.name.split(/\[|\]\.|\]|\./).filter((p) => p !== ''), - location: this.gl.getUniformLocation(this.program, glUniform.name), - type: glUniform.type, - }); - } - - this.uniforms.map((u1) => { - const isSingle = - this.uniforms.filter((u2) => u2.name.includes(u1.name[0])).length == 1; - - if (u1.name.includes('0') && isSingle) { - u1.name = u1.name.slice(0, -1); - } - - return u1; - }); - } -} diff --git a/frontend/src/scripts/graphics/graphics-library/program/uniform-array-autoscaling-program.ts b/frontend/src/scripts/graphics/graphics-library/program/uniform-array-autoscaling-program.ts deleted file mode 100644 index 8e1df24..0000000 --- a/frontend/src/scripts/graphics/graphics-library/program/uniform-array-autoscaling-program.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { mat2d, vec2 } from 'gl-matrix'; -import { getCombinations } from '../../../helper/get-combinations'; -import { last } from '../../../helper/last'; -import { IDrawableDescriptor } from '../../drawables/i-drawable-descriptor'; -import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; -import { IProgram } from './i-program'; - -export class UniformArrayAutoScalingProgram implements IProgram { - private programs: Array<{ - program: FragmentShaderOnlyProgram; - values: Array; - }> = []; - - private current: FragmentShaderOnlyProgram; - private drawingRectangleBottomLeft = vec2.fromValues(0, 0); - private drawingRectangleSize = vec2.fromValues(1, 1); - - constructor( - private gl: WebGL2RenderingContext, - shaderSources: [string, string], - private descriptors: Array - ) { - const names = descriptors.map((o) => o.countMacroName); - for (const combination of getCombinations( - descriptors.map((o) => o.shaderCombinationSteps) - )) { - this.createProgram(names, combination, shaderSources); - } - } - - public async initialize(): Promise { - await Promise.all(this.programs.map((p) => p.program.initialize())); - } - - public bindAndSetUniforms(uniforms: { [name: string]: any }): void { - const values = this.descriptors.map((d) => - uniforms[d.uniformName] ? uniforms[d.uniformName].length : 0 - ); - - const closest = this.programs.find((p) => p.values.every((v, i) => v >= values[i])); - - this.current = closest ? closest.program : last(this.programs).program; - - if (closest) { - this.descriptors.map((d, i) => { - const difference = closest.values[i] - values[i]; - for (let i = 0; i < difference; i++) { - d.empty.serializeToUniforms(uniforms, 0, mat2d.create()); - } - }); - } - - this.current.setDrawingRectangleUV( - this.drawingRectangleBottomLeft, - this.drawingRectangleSize - ); - this.current.bindAndSetUniforms(uniforms); - } - - public setDrawingRectangleUV(bottomLeft: vec2, size: vec2) { - this.drawingRectangleBottomLeft = bottomLeft; - this.drawingRectangleSize = size; - } - - public draw(): void { - this.current.draw(); - } - - public delete(): void { - this.programs.forEach((p) => p.program.delete()); - } - - private createProgram( - names: Array, - combination: Array, - shaderSources: [string, string] - ): FragmentShaderOnlyProgram { - const substitutions = {}; - names.forEach((v, i) => (substitutions[v] = combination[i].toString())); - - const program = new FragmentShaderOnlyProgram(this.gl, shaderSources, substitutions); - - this.programs.push({ - program, - values: combination, - }); - - return program; - } -} diff --git a/frontend/src/scripts/graphics/i-renderer.ts b/frontend/src/scripts/graphics/i-renderer.ts deleted file mode 100644 index fa6c0d8..0000000 --- a/frontend/src/scripts/graphics/i-renderer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { BoundingBoxBase } from '../shapes/bounding-box-base'; -import { IDrawable } from './drawables/i-drawable'; -import { ILight } from './drawables/lights/i-light'; - -export interface IRenderer { - initialize(): Promise; - - startFrame(deltaTime: DOMHighResTimeStamp): void; - finishFrame(): void; - - drawShape(drawable: IDrawable): void; - drawLight(light: ILight): void; - drawInfoText(text: string): void; - - readonly canvasSize: vec2; - setViewArea(viewArea: BoundingBoxBase): void; - setCursorPosition(position: vec2): void; -} diff --git a/frontend/src/scripts/graphics/rendering/fps-autoscaler.ts b/frontend/src/scripts/graphics/rendering/fps-autoscaler.ts deleted file mode 100644 index 528cadf..0000000 --- a/frontend/src/scripts/graphics/rendering/fps-autoscaler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Autoscaler as AutoScaler } from '../../helper/autoscaler'; -import { exponentialDecay } from '../../helper/exponential-decay'; -import { settings } from '../settings'; - -export class FpsAutoscaler extends AutoScaler { - private timeSinceLastAdjusment = 0; - private exponentialDecayedDeltaTime = 0.0; - - constructor(setters: { [key: string]: (value: number | boolean) => void }) { - super( - setters, - settings.qualityScaling.qualityTargets, - settings.qualityScaling.startingTargetIndex, - settings.qualityScaling.scalingOptions - ); - } - - public autoscale(lastDeltaTime: DOMHighResTimeStamp) { - this.timeSinceLastAdjusment += lastDeltaTime; - if ( - this.timeSinceLastAdjusment >= settings.qualityScaling.adjusmentRateInMilliseconds - ) { - this.timeSinceLastAdjusment = 0; - this.exponentialDecayedDeltaTime = exponentialDecay( - this.exponentialDecayedDeltaTime, - lastDeltaTime, - settings.qualityScaling.deltaTimeResponsiveness - ); - - if ( - this.exponentialDecayedDeltaTime <= - settings.qualityScaling.targetDeltaTimeInMilliseconds - - settings.qualityScaling.deltaTimeError - ) { - this.increase(); - } else if ( - this.exponentialDecayedDeltaTime > - settings.qualityScaling.targetDeltaTimeInMilliseconds + - settings.qualityScaling.deltaTimeError - ) { - this.decrease(); - } - } - } -} diff --git a/frontend/src/scripts/graphics/rendering/rendering-pass.ts b/frontend/src/scripts/graphics/rendering/rendering-pass.ts deleted file mode 100644 index 54c2a14..0000000 --- a/frontend/src/scripts/graphics/rendering/rendering-pass.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { IDrawable } from '../drawables/i-drawable'; -import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor'; -import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer'; -import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program'; -import { settings } from '../settings'; - -export class RenderingPass { - private drawables: Array = []; - private program: UniformArrayAutoScalingProgram; - - constructor( - gl: WebGL2RenderingContext, - shaderSources: [string, string], - drawableDescriptors: Array, - private frame: FrameBuffer - ) { - this.program = new UniformArrayAutoScalingProgram( - gl, - shaderSources, - drawableDescriptors - ); - } - - public async initialize(): Promise { - await this.program.initialize(); - } - - public addDrawable(drawable: IDrawable) { - this.drawables.push(drawable); - } - - public render(commonUniforms: any, inputTexture?: WebGLTexture) { - this.frame.bindAndClear(inputTexture); - - const stepsInUV = 1 / settings.tileMultiplier; - - const worldR = - 0.5 * - vec2.length(vec2.scale(vec2.create(), commonUniforms.worldAreaInView, stepsInUV)); - - const radiusInNDC = worldR * commonUniforms.scaleWorldLengthToNDC; - - const stepsInNDC = 2 * stepsInUV; - - for (let x = -1; x < 1; x += stepsInNDC) { - for (let y = -1; y < 1; y += stepsInNDC) { - const uniforms = { ...commonUniforms, maxMinDistance: 0.0 }; - - const ndcBottomLeft = vec2.fromValues(x, y); - - this.program.setDrawingRectangleUV( - [(ndcBottomLeft.x + 1) / 2, (ndcBottomLeft.y + 1) / 2], - vec2.fromValues(stepsInUV, stepsInUV) - ); - - const tileCenterWorldCoordinates = vec2.transformMat2d( - vec2.create(), - vec2.add( - vec2.create(), - [(ndcBottomLeft.x + 1) / 2, (ndcBottomLeft.y + 1) / 2], - vec2.fromValues(stepsInUV / 2, stepsInUV / 2) - ), - uniforms.uvToWorld - ); - - const primitivesNearTile = this.drawables.filter( - (d) => d.distance(tileCenterWorldCoordinates) < 2 * worldR - ); - - primitivesNearTile.forEach((p) => - p.serializeToUniforms( - uniforms, - uniforms.scaleWorldLengthToNDC, - uniforms.transformWorldToNDC - ) - ); - - this.program.bindAndSetUniforms(uniforms); - this.program.draw(); - } - } - - this.drawables = []; - } -} diff --git a/frontend/src/scripts/graphics/rendering/uniforms-provider.ts b/frontend/src/scripts/graphics/rendering/uniforms-provider.ts deleted file mode 100644 index e34b5e4..0000000 --- a/frontend/src/scripts/graphics/rendering/uniforms-provider.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { mat2d, vec2 } from 'gl-matrix'; -import { BoundingBoxBase } from '../../shapes/bounding-box-base'; - -export class UniformsProvider { - private scaleWorldLengthToNDC = 1; - private transformWorldToNDC = mat2d.create(); - - private viewAreaBottomLeft = vec2.create(); - private worldAreaInView = vec2.create(); - private squareToAspectRatio: vec2; - private uvToWorld: mat2d; - private cursorPosition = vec2.create(); - - public softShadowsEnabled: boolean; - - public constructor(private gl: WebGL2RenderingContext) {} - - public getUniforms(uniforms: any): any { - const cursorPosition = this.uvToWorldCoordinate(this.cursorPosition); - return { - ...uniforms, - cursorPosition, - uvToWorld: this.uvToWorld, - worldAreaInView: this.worldAreaInView, - squareToAspectRatio: this.squareToAspectRatio, - scaleWorldLengthToNDC: this.scaleWorldLengthToNDC, - transformWorldToNDC: this.transformWorldToNDC, - squareToAspectRatioTimes2: vec2.scale(vec2.create(), this.squareToAspectRatio, 2), - softShadowsEnabled: this.softShadowsEnabled, - }; - } - - private getScreenToWorldTransform(screenSize: vec2) { - const transform = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft); - mat2d.scale( - transform, - transform, - vec2.divide(vec2.create(), this.worldAreaInView, screenSize) - ); - mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5)); - - return transform; - } - - public uvToWorldCoordinate(screenUvPosition: vec2): vec2 { - const resolution = vec2.fromValues(this.gl.canvas.width, this.gl.canvas.height); - - return vec2.transformMat2d( - vec2.create(), - vec2.multiply(vec2.create(), screenUvPosition, resolution), - this.getScreenToWorldTransform(resolution) - ); - } - - public setViewArea(viewArea: BoundingBoxBase) { - this.worldAreaInView = viewArea.size; - - // world coordinates - this.viewAreaBottomLeft = vec2.add( - vec2.create(), - viewArea.topLeft, - vec2.fromValues(0, -viewArea.size.y) - ); - - const scaleLongerEdgeTo1 = - 1 / Math.max(this.worldAreaInView.x, this.worldAreaInView.y); - - this.squareToAspectRatio = vec2.fromValues( - this.worldAreaInView.x * scaleLongerEdgeTo1, - this.worldAreaInView.y * scaleLongerEdgeTo1 - ); - - this.scaleWorldLengthToNDC = scaleLongerEdgeTo1 * 2; - - mat2d.fromScaling( - this.transformWorldToNDC, - vec2.fromValues(this.scaleWorldLengthToNDC, this.scaleWorldLengthToNDC) - ); - mat2d.translate( - this.transformWorldToNDC, - this.transformWorldToNDC, - vec2.fromValues( - -this.viewAreaBottomLeft.x - 0.5 * this.worldAreaInView.x, - -this.viewAreaBottomLeft.y - 0.5 * this.worldAreaInView.y - ) - ); - - this.uvToWorld = mat2d.fromTranslation(mat2d.create(), this.viewAreaBottomLeft); - mat2d.scale(this.uvToWorld, this.uvToWorld, this.worldAreaInView); - } - - public setCursorPosition(position: vec2): void { - this.cursorPosition = position; - } -} diff --git a/frontend/src/scripts/graphics/rendering/webgl2-renderer.ts b/frontend/src/scripts/graphics/rendering/webgl2-renderer.ts deleted file mode 100644 index 822f22c..0000000 --- a/frontend/src/scripts/graphics/rendering/webgl2-renderer.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { BoundingBoxBase } from '../../shapes/bounding-box-base'; -import { DrawableBlob } from '../drawables/drawable-blob'; -import { DrawableTunnel } from '../drawables/drawable-tunnel'; -import { IDrawable } from '../drawables/i-drawable'; -import { CircleLight } from '../drawables/lights/circle-light'; -import { Flashlight } from '../drawables/lights/flashlight'; -import { ILight } from '../drawables/lights/i-light'; -import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer'; -import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer'; -import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context'; -import { WebGlStopwatch } from '../graphics-library/helper/stopwatch'; -import { IRenderer } from '../i-renderer'; -import distanceFragmentShader from '../shaders/distance-fs.glsl'; -import distanceVertexShader from '../shaders/distance-vs.glsl'; -import lightsFragmentShader from '../shaders/shading-fs.glsl'; -import lightsVertexShader from '../shaders/shading-vs.glsl'; -import { FpsAutoscaler } from './fps-autoscaler'; -import { RenderingPass } from './rendering-pass'; -import { UniformsProvider } from './uniforms-provider'; - -export class WebGl2Renderer implements IRenderer { - private gl: WebGL2RenderingContext; - private stopwatch?: WebGlStopwatch; - private uniformsProvider: UniformsProvider; - private distanceFieldFrameBuffer: IntermediateFrameBuffer; - private lightingFrameBuffer: DefaultFrameBuffer; - private distancePass: RenderingPass; - private lightingPass: RenderingPass; - private autoscaler: FpsAutoscaler; - - private initializePromise: Promise<[void, void]> = null; - - constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) { - this.gl = getWebGl2Context(canvas); - - this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl); - this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl); - - this.distancePass = new RenderingPass( - this.gl, - [distanceVertexShader, distanceFragmentShader], - [DrawableTunnel.descriptor, DrawableBlob.descriptor], - this.distanceFieldFrameBuffer - ); - - this.lightingPass = new RenderingPass( - this.gl, - [lightsVertexShader, lightsFragmentShader], - [CircleLight.descriptor, Flashlight.descriptor], - this.lightingFrameBuffer - ); - - this.initializePromise = Promise.all([ - this.distancePass.initialize(), - this.lightingPass.initialize(), - ]); - - this.uniformsProvider = new UniformsProvider(this.gl); - - this.autoscaler = new FpsAutoscaler({ - distanceRenderScale: (v) => - (this.distanceFieldFrameBuffer.renderScale = v as number), - finalRenderScale: (v) => (this.lightingFrameBuffer.renderScale = v as number), - softShadowsEnabled: (v) => - (this.uniformsProvider.softShadowsEnabled = v as boolean), - }); - - try { - this.stopwatch = new WebGlStopwatch(this.gl); - } catch { - // no problem - } - } - - public async initialize(): Promise { - await this.initializePromise; - } - - public drawShape(shape: IDrawable) { - this.distancePass.addDrawable(shape); - } - - public drawLight(light: ILight) { - this.lightingPass.addDrawable(light); - } - - public startFrame(deltaTime: DOMHighResTimeStamp) { - this.autoscaler.autoscale(deltaTime); - - this.stopwatch?.start(); - - this.distanceFieldFrameBuffer.setSize(); - this.lightingFrameBuffer.setSize(); - } - - public finishFrame() { - const common = { - distanceNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()), - shadingNdcPixelSize: 2 / Math.max(...this.distanceFieldFrameBuffer.getSize()), - }; - - this.distancePass.render(this.uniformsProvider.getUniforms(common)); - this.lightingPass.render( - this.uniformsProvider.getUniforms(common), - this.distanceFieldFrameBuffer.colorTexture - ); - - this.stopwatch?.stop(); - } - - public setViewArea(viewArea: BoundingBoxBase) { - this.uniformsProvider.setViewArea(viewArea); - } - - public setCursorPosition(position: vec2) { - this.uniformsProvider.setCursorPosition(position); - } - - public get canvasSize(): vec2 { - return vec2.fromValues(this.canvas.clientWidth, this.canvas.clientHeight); - } - - public drawInfoText(text: string) { - if (this.overlay.innerText != text) { - this.overlay.innerText = text; - } - } -} diff --git a/frontend/src/scripts/graphics/settings.ts b/frontend/src/scripts/graphics/settings.ts deleted file mode 100644 index 5d101c1..0000000 --- a/frontend/src/scripts/graphics/settings.ts +++ /dev/null @@ -1,59 +0,0 @@ -export const settings = { - enableHighDpiRendering: true, - qualityScaling: { - targetDeltaTimeInMilliseconds: 20, - deltaTimeError: 2, - deltaTimeResponsiveness: 1 / 16, - adjusmentRateInMilliseconds: 300, - qualityTargets: [ - { - distanceRenderScale: 0.1, - finalRenderScale: 0.2, - softShadowsEnabled: false, - }, - { - distanceRenderScale: 0.1, - finalRenderScale: 0.6, - softShadowsEnabled: false, - }, - { - distanceRenderScale: 0.3, - finalRenderScale: 1.0, - softShadowsEnabled: false, - }, - { - distanceRenderScale: 0.3, - finalRenderScale: 1.0, - softShadowsEnabled: true, - }, - /*{ - distanceRenderScale: 1.0, - finalRenderScale: 1.5, - softShadowsEnabled: true, - }, - { - distanceRenderScale: 1.25, - finalRenderScale: 1.75, - softShadowsEnabled: true, - }, - { - distanceRenderScale: 2, - finalRenderScale: 2, - softShadowsEnabled: true, - },*/ - ], - startingTargetIndex: 2, - scalingOptions: { - additiveIncrease: 0.2, - multiplicativeDecrease: 1.05, - }, - }, - tileMultiplier: 8, - shaderMacros: {}, - shaderCombinations: { - lineSteps: [0, 1, 2, 4, 8, 16, 128], - blobSteps: [0, 1, 2, 8], - circleLightSteps: [0, 1], - flashlightSteps: [0, 1], - }, -}; diff --git a/frontend/src/scripts/graphics/shaders/distance-fs.glsl b/frontend/src/scripts/graphics/shaders/distance-fs.glsl deleted file mode 100644 index 2568796..0000000 --- a/frontend/src/scripts/graphics/shaders/distance-fs.glsl +++ /dev/null @@ -1,109 +0,0 @@ -#version 300 es - -precision lowp float; - -#define LINE_COUNT {lineCount} -#define BLOB_COUNT {blobCount} - -#define SURFACE_OFFSET 0.001 - -uniform float maxMinDistance; -uniform float distanceNdcPixelSize; - -in vec2 position; - -float smoothMin(float a, float b) -{ - const float k = 2.0; - - a = pow(a, k); - b = pow(b, k); - return pow((a * b) / (a + b), 1.0 / k); -} - -#if LINE_COUNT > 0 - uniform struct { - vec2 from; - vec2 toFromDelta; - float fromRadius; - float toRadius; - }[LINE_COUNT] lines; - - void lineMinDistance(inout float minDistance, inout float color) { - float myMinDistance = maxMinDistance; - for (int i = 0; i < LINE_COUNT; i++) { - vec2 targetFromDelta = position - lines[i].from; - vec2 toFromDelta = lines[i].toFromDelta; - - float h = clamp( - dot(targetFromDelta, toFromDelta) / dot(toFromDelta, toFromDelta), - 0.0, 1.0 - ); - - float lineDistance = -mix( - lines[i].fromRadius, lines[i].toRadius, h - ) + distance( - targetFromDelta, toFromDelta * h - ); - - myMinDistance = min(myMinDistance, lineDistance); - } - - color = mix(0.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, -myMinDistance)); - minDistance = -myMinDistance; - } -#endif - -#if BLOB_COUNT > 0 - uniform struct { - vec2 headCenter; - vec2 leftFootCenter; - vec2 rightFootCenter; - float headRadius; - float footRadius; - float k; - }[BLOB_COUNT] blobs; - - float circleMinDistance(vec2 circleCenter, float radius) { - return distance(position, circleCenter) - radius; - } - - void blobMinDistance(inout float minDistance, inout float color) { - for (int i = 0; i < BLOB_COUNT; i++) { - float headDistance = circleMinDistance(blobs[i].headCenter, blobs[i].headRadius); - float leftFootDistance = circleMinDistance(blobs[i].leftFootCenter, blobs[i].footRadius); - float rightFootDistance = circleMinDistance(blobs[i].rightFootCenter, blobs[i].footRadius); - - float res = min( - smoothMin(headDistance, leftFootDistance), - smoothMin(headDistance, rightFootDistance) - ); - - res = min(100.0, headDistance); - res = min(res, leftFootDistance); - res = min(res, rightFootDistance); - //color = mix(2.0, color, step(distanceUvPixelSize + SURFACE_OFFSET, res)); - minDistance = min(minDistance, res); - color = mix(2.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, res)); - } - } -#endif - -out vec2 fragmentColor; - -void main() { - float minDistance = -maxMinDistance; - float color = 0.0; - - #if LINE_COUNT > 0 - lineMinDistance(minDistance, color); - #endif - - #if BLOB_COUNT > 0 - blobMinDistance(minDistance, color); - #endif - - - // minDistance / 2.0: NDC to UV scale - fragmentColor = vec2((minDistance - SURFACE_OFFSET) / 2.0, color); -} diff --git a/frontend/src/scripts/graphics/shaders/distance-vs.glsl b/frontend/src/scripts/graphics/shaders/distance-vs.glsl deleted file mode 100644 index 0e54025..0000000 --- a/frontend/src/scripts/graphics/shaders/distance-vs.glsl +++ /dev/null @@ -1,15 +0,0 @@ -#version 300 es - -precision lowp float; - -uniform mat3 modelTransform; -uniform vec2 squareToAspectRatio; - -in vec4 vertexPosition; -out vec2 position; - -void main() { - vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform; - gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0); - position = vertexPosition2D.xy * squareToAspectRatio; -} diff --git a/frontend/src/scripts/graphics/shaders/shading-fs.glsl b/frontend/src/scripts/graphics/shaders/shading-fs.glsl deleted file mode 100644 index 8a67946..0000000 --- a/frontend/src/scripts/graphics/shaders/shading-fs.glsl +++ /dev/null @@ -1,176 +0,0 @@ -#version 300 es - -precision lowp float; - -#define INFINITY 1000.0 -#define LIGHT_DROP_INSIDE_RATIO 0.3 -#define AMBIENT_LIGHT vec3(0.25, 0.15, 0.25) -#define SHADOW_HARDNESS 150.0 - -#define CIRCLE_LIGHT_COUNT {circleLightCount} -#define FLASHLIGHT_COUNT {flashlightCount} - -uniform bool softShadowsEnabled; -uniform vec2 squareToAspectRatioTimes2; -uniform float shadingNdcPixelSize; -uniform sampler2D distanceTexture; - -in vec2 position; -in vec2 uvCoordinates; - -vec3[3] colors = vec3[]( - vec3(0.4, 0.35, 0.6), // cave color - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0) -); - -float getDistance(in vec2 target, out vec3 color) { - vec4 values = texture(distanceTexture, target); - color = colors[int(values[1])]; - return values[0]; -} - -float getDistance(in vec2 target) { - return texture(distanceTexture, target)[0]; -} - -float softShadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { - float rayLength = startingDistance; - float q = 1.0 / SHADOW_HARDNESS; - - for (int j = 0; j < 128; j++) { - float minDistance = getDistance(uvCoordinates + direction * rayLength); - - q = min(q, minDistance / rayLength); - rayLength += minDistance / 2.5; - - if (rayLength >= lightCenterDistance) { - return q * SHADOW_HARDNESS; - } - } - - return 0.0; -} - -float hardShadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { - float rayLength = startingDistance; - - for (int j = 0; j < 32; j++) { - rayLength += getDistance(uvCoordinates + direction * rayLength); - } - - return step(lightCenterDistance, rayLength); -} - -float shadowTransparency(float startingDistance, float lightCenterDistance, vec2 direction) { - return softShadowsEnabled ? - softShadowTransparency(startingDistance, lightCenterDistance, direction) : - hardShadowTransparency(startingDistance, lightCenterDistance, direction); -} - - -#if CIRCLE_LIGHT_COUNT > 0 - uniform struct CircleLight { - vec2 center; - float lightDrop; - vec3 value; - }[CIRCLE_LIGHT_COUNT] circleLights; - - in vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; - - vec3 colorInPosition(CircleLight light, out float lightCenterDistance) { - lightCenterDistance = distance(light.center, position); - return light.value / pow( - lightCenterDistance / light.lightDrop + 1.0, 2.0 - ); - } - - vec3 colorInPositionInside(CircleLight light) { - float lightCenterDistance = distance(light.center, position); - return light.value / pow( - lightCenterDistance / (light.lightDrop * LIGHT_DROP_INSIDE_RATIO) + 1.0, 2.0 - ); - } -#endif - -#if FLASHLIGHT_COUNT > 0 - uniform struct Flashlight { - vec2 center; - vec2 direction; - float lightDrop; - vec3 value; - }[FLASHLIGHT_COUNT] flashlights; - - in vec2[FLASHLIGHT_COUNT] flashlightDirections; - - float intensityInDirection(vec2 lightDirection, vec2 targetDirection) { - return smoothstep(0.0, 1.0, 10.0 * max(0.0, dot(targetDirection, lightDirection) - 0.9)); - } - - vec3 colorInPosition(Flashlight light, vec2 positionDirection, out float lightCenterDistance) { - lightCenterDistance = distance(light.center, position); - return intensityInDirection(light.direction, positionDirection) * light.value / pow( - lightCenterDistance / light.lightDrop + 1.0, 2.0 - ); - } - - vec3 colorInPositionInside(Flashlight light, vec2 positionDirection) { - float lightCenterDistance = distance(light.center, position); - return intensityInDirection(light.direction, positionDirection) * light.value / pow( - lightCenterDistance / (light.lightDrop * LIGHT_DROP_INSIDE_RATIO) + 1.0, 2.0 - ); - } -#endif - -out vec4 fragmentColor; -void main() { - vec3 lighting = AMBIENT_LIGHT; - - vec3 colorAtPosition; - float startingDistance = getDistance(uvCoordinates, colorAtPosition); - - if (startingDistance < 0.0) { - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - lighting += colorInPositionInside(circleLights[i]); - } - #endif - - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - lighting += colorInPositionInside(flashlights[i], normalize(flashlightDirections[i])); - } - #endif - } else { - colorAtPosition = vec3(1.0); - - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - vec2 direction = normalize(circleLightDirections[i]) / squareToAspectRatioTimes2; - - float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(circleLights[i], lightCenterDistance); - - lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); - } - #endif - - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - vec2 originalDirection = normalize(flashlightDirections[i]); - vec2 direction = originalDirection / squareToAspectRatioTimes2; - - float lightCenterDistance; - vec3 lightColorAtPosition = colorInPosition(flashlights[i], originalDirection, lightCenterDistance); - - if (length(lightColorAtPosition) < 0.01) { - continue; - } - - lighting += lightColorAtPosition * shadowTransparency(startingDistance, lightCenterDistance, direction); - } - #endif - } - - fragmentColor = vec4(colorAtPosition * lighting, 1.0); -} diff --git a/frontend/src/scripts/graphics/shaders/shading-vs.glsl b/frontend/src/scripts/graphics/shaders/shading-vs.glsl deleted file mode 100644 index 5d13acf..0000000 --- a/frontend/src/scripts/graphics/shaders/shading-vs.glsl +++ /dev/null @@ -1,59 +0,0 @@ -#version 300 es - -precision lowp float; - -#define CIRCLE_LIGHT_COUNT {circleLightCount} -#define FLASHLIGHT_COUNT {flashlightCount} - -uniform mat3 modelTransform; -in vec4 vertexPosition; - -out vec2 position; -out vec2 uvCoordinates; - -uniform vec2 squareToAspectRatio; - -#if CIRCLE_LIGHT_COUNT > 0 - uniform struct CircleLight { - vec2 center; - float lightDrop; - vec3 value; - }[CIRCLE_LIGHT_COUNT] circleLights; - - out vec2[CIRCLE_LIGHT_COUNT] circleLightDirections; -#endif - -#if FLASHLIGHT_COUNT > 0 - uniform struct Flashlight { - vec2 center; - vec2 direction; - float lightDrop; - vec3 value; - }[FLASHLIGHT_COUNT] flashlights; - - out vec2[FLASHLIGHT_COUNT] flashlightDirections; -#endif - -void main() { - vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform; - gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0); - position = vertexPosition2D.xy * squareToAspectRatio; - - uvCoordinates = (vertexPosition2D * mat3( - 0.5, 0.0, 0.5, - 0.0, 0.5, 0.5, - 0.0, 0.0, 1.0 - )).xy; - - #if CIRCLE_LIGHT_COUNT > 0 - for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - circleLightDirections[i] = circleLights[i].center - position; - } - #endif - - #if FLASHLIGHT_COUNT > 0 - for (int i = 0; i < FLASHLIGHT_COUNT; i++) { - flashlightDirections[i] = flashlights[i].center - position; - } - #endif -} diff --git a/frontend/src/scripts/helper/array.ts b/frontend/src/scripts/helper/array.ts deleted file mode 100644 index bcde98b..0000000 --- a/frontend/src/scripts/helper/array.ts +++ /dev/null @@ -1,49 +0,0 @@ -declare global { - interface Array { - x: number; - y: number; - } - - interface Float32Array { - x: number; - y: number; - } -} - -export const applyArrayPlugins = () => { - Object.defineProperty(Array.prototype, 'x', { - get() { - return this[0]; - }, - set(value) { - this[0] = value; - }, - }); - - Object.defineProperty(Array.prototype, 'y', { - get() { - return this[1]; - }, - set(value) { - this[1] = value; - }, - }); - - Object.defineProperty(Float32Array.prototype, 'x', { - get() { - return this[0]; - }, - set(value) { - this[0] = value; - }, - }); - - Object.defineProperty(Float32Array.prototype, 'y', { - get() { - return this[1]; - }, - set(value) { - this[1] = value; - }, - }); -}; diff --git a/frontend/src/scripts/helper/autoscaler.ts b/frontend/src/scripts/helper/autoscaler.ts deleted file mode 100644 index b3e1ddb..0000000 --- a/frontend/src/scripts/helper/autoscaler.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { InfoText } from '../objects/types/info-text'; -import { clamp } from './clamp'; -import { mix } from './mix'; - -export class Autoscaler { - // can have fractions - private index: number; - - constructor( - private setters: { [key: string]: (value: number | boolean) => void }, - private targets: Array<{ [key: string]: number | boolean }>, - startingIndex: number, - private scalingOptions: { - additiveIncrease: number; - multiplicativeDecrease: number; - } - ) { - this.index = startingIndex; - this.applyScaling(); - } - - public increase() { - this.index += this.scalingOptions.additiveIncrease; - this.applyScaling(); - } - - public decrease() { - this.index /= this.scalingOptions.multiplicativeDecrease; - this.applyScaling(); - } - - private applyScaling() { - this.index = clamp(this.index, 0, this.targets.length - 1); - - const floor = Math.floor(this.index); - const fract = this.index - floor; - - const previousTarget = this.targets[floor]; - const nextTarget = - floor + 1 == this.targets.length ? previousTarget : this.targets[floor + 1]; - - const result = {}; - for (const key in this.setters) { - const previous = previousTarget[key]; - const next = nextTarget[key]; - let current: number | boolean; - if (typeof previous == 'number') { - current = mix(previous, next as number, fract); - } else { - current = next; - } - - result[key] = current; - - this.setters[key](current); - } - - InfoText.modifyRecord('quality', result); - } -} diff --git a/frontend/src/scripts/helper/delta-time-calculator.ts b/frontend/src/scripts/helper/delta-time-calculator.ts new file mode 100644 index 0000000..fb7686d --- /dev/null +++ b/frontend/src/scripts/helper/delta-time-calculator.ts @@ -0,0 +1,23 @@ +export class DeltaTimeCalculator { + private previousTime: DOMHighResTimeStamp | null = null; + + constructor() { + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); + } + + public getNextDeltaTime(currentTime: DOMHighResTimeStamp): DOMHighResTimeStamp { + if (this.previousTime === null) { + this.previousTime = currentTime; + } + + const delta = currentTime - this.previousTime; + this.previousTime = currentTime; + return delta; + } + + private handleVisibilityChange() { + if (!document.hidden) { + this.previousTime = null; + } + } +} diff --git a/frontend/src/scripts/helper/pretty-print.ts b/frontend/src/scripts/helper/pretty-print.ts new file mode 100644 index 0000000..704a6fc --- /dev/null +++ b/frontend/src/scripts/helper/pretty-print.ts @@ -0,0 +1,4 @@ +export const prettyPrint = (o: any): string => + JSON.stringify(o, (_, v) => (v.toFixed ? Number(v.toFixed(3)) : v), ' ') + .replace(/("|,|{|^\n)/g, '') + .replace(/(\W*}\n?)+/g, '\n\n'); diff --git a/frontend/src/scripts/helper/timing.ts b/frontend/src/scripts/helper/timing.ts deleted file mode 100644 index a8a3148..0000000 --- a/frontend/src/scripts/helper/timing.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { InfoText } from '../objects/types/info-text'; -import { last } from './last'; - -export function timeIt(interval = 60) { - return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { - let i = 0; - let previousTimes: Array = []; - - const targetFunction = descriptor.value; - - descriptor.value = function (...values) { - const start = performance.now(); - targetFunction.bind(this)(...values); - const end = performance.now(); - - previousTimes.push(end - start); - - if (i++ % interval == 0) { - previousTimes = previousTimes.sort(); - - const text = `Max: ${last(previousTimes).toFixed( - 2 - )} ms\n\tMedian: ${previousTimes[Math.floor(previousTimes.length / 2)].toFixed( - 2 - )} ms`; - - InfoText.modifyRecord(propertyKey, text); - - previousTimes = []; - } - }; - - return descriptor; - }; -} diff --git a/frontend/src/scripts/objects/types/camera.ts b/frontend/src/scripts/objects/types/camera.ts index 05847a9..d46d196 100644 --- a/frontend/src/scripts/objects/types/camera.ts +++ b/frontend/src/scripts/objects/types/camera.ts @@ -1,5 +1,5 @@ import { vec2 } from 'gl-matrix'; -import { BeforeRenderCommand } from '../../graphics/commands/before-render'; +import { RenderCommand } from '../../graphics/commands/render'; import { CursorMoveCommand } from '../../input/commands/cursor-move-command'; import { ZoomCommand } from '../../input/commands/zoom'; import { MoveToCommand } from '../../physics/commands/move-to'; @@ -17,7 +17,7 @@ export class Camera extends GameObject { this._viewArea = new BoundingBox(null); - this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this)); + this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); this.addCommandExecutor(CursorMoveCommand, this.setCursorPosition.bind(this)); this.addCommandExecutor(ZoomCommand, this.zoom.bind(this)); @@ -27,7 +27,7 @@ export class Camera extends GameObject { return this._viewArea; } - private draw(c: BeforeRenderCommand) { + private draw(c: RenderCommand) { const canvasAspectRatio = c.renderer.canvasSize.x / c.renderer.canvasSize.y; this.viewArea.size = vec2.fromValues( @@ -35,8 +35,8 @@ export class Camera extends GameObject { Math.sqrt(this.inViewAreaSize / canvasAspectRatio) ); - c.renderer.setViewArea(this._viewArea); - c.renderer.setCursorPosition(this.cursorPosition); + c.renderer.setViewArea(this._viewArea.topLeft, this.viewArea.size); + //c.renderer.setCursorPosition(this.cursorPosition); } private moveTo(c: MoveToCommand) { diff --git a/frontend/src/scripts/objects/types/character.ts b/frontend/src/scripts/objects/types/character.ts index c97a320..065dc1c 100644 --- a/frontend/src/scripts/objects/types/character.ts +++ b/frontend/src/scripts/objects/types/character.ts @@ -1,7 +1,6 @@ import { vec2, vec3 } from 'gl-matrix'; +import { Flashlight } from 'sdf-2d'; import { RenderCommand } from '../../graphics/commands/render'; -import { DrawableBlob } from '../../graphics/drawables/drawable-blob'; -import { Flashlight } from '../../graphics/drawables/lights/flashlight'; import { IGame } from '../../i-game'; import { KeyDownCommand } from '../../input/commands/key-down'; import { KeyUpCommand } from '../../input/commands/key-up'; @@ -9,19 +8,18 @@ import { SwipeCommand } from '../../input/commands/swipe'; import { StepCommand } from '../../physics/commands/step'; import { TeleportToCommand } from '../../physics/commands/teleport-to'; import { IShape } from '../../shapes/i-shape'; -import { Blob } from '../../shapes/types/blob'; +import { BlobShape } from '../../shapes/types/blob-shape'; import { GameObject } from '../game-object'; export class Character extends GameObject { private keysDown: Set = new Set(); private light = new Flashlight( vec2.create(), - vec2.fromValues(-1, 0), - 0.7, vec3.fromValues(1, 0.6, 0.45), - 1.5 + 1.5, + vec2.fromValues(-1, 0) ); - private shape = new DrawableBlob(vec2.create()); + private shape = new BlobShape(vec2.create()); private static speed = 1.5; constructor(private game: IGame) { @@ -41,8 +39,8 @@ export class Character extends GameObject { } private draw(c: RenderCommand) { - c.renderer.drawShape(this.shape); - c.renderer.drawLight(this.light); + c.renderer.addDrawable(this.shape); + c.renderer.addDrawable(this.light); } private tryMoving(delta: vec2, isFirstIteration = true) { @@ -98,14 +96,14 @@ export class Character extends GameObject { } } - private getNearShapesTo(shape: Blob): Array<{ shape: IShape; distance: number }> { + private getNearShapesTo(shape: BlobShape): Array<{ shape: IShape; distance: number }> { return this.game .findIntersecting(shape.boundingBox) .filter((b) => b.shape) .map((b) => ({ shape: b.shape, // TODO: fix this - distance: b.shape.distance(shape.center) + shape.radius - 20, + distance: b.shape.minDistance(shape.center) + shape.radius - 20, })) .sort((e) => e.distance); } diff --git a/frontend/src/scripts/objects/types/info-text.ts b/frontend/src/scripts/objects/types/info-text.ts deleted file mode 100644 index 66d8a72..0000000 --- a/frontend/src/scripts/objects/types/info-text.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { RenderCommand } from '../../graphics/commands/render'; -import { GameObject } from '../game-object'; - -export class InfoText extends GameObject { - private static MinRowLength = 60; - - constructor() { - super(); - - this.addCommandExecutor(RenderCommand, this.draw.bind(this)); - } - - private static records: Map = new Map(); - - public static modifyRecord(key: string, value: string | any) { - if (typeof value == 'string') { - value = ' ' + value; - } else { - value = JSON.stringify( - value, - (_, v) => (v.toFixed ? Number(v.toFixed(2)) : v), - ' ' - ); - } - - InfoText.records.set(key, value); - } - - private draw(e: RenderCommand) { - let text = ''; - InfoText.records.forEach( - (v, k) => (text += `${k}\n${v.padEnd(InfoText.MinRowLength)}\n`) - ); - e.renderer.drawInfoText(text); - } -} diff --git a/frontend/src/scripts/objects/types/lamp.ts b/frontend/src/scripts/objects/types/lamp.ts index 6786343..6be9793 100644 --- a/frontend/src/scripts/objects/types/lamp.ts +++ b/frontend/src/scripts/objects/types/lamp.ts @@ -1,23 +1,23 @@ import { vec2, vec3 } from 'gl-matrix'; +import { CircleLight } from 'sdf-2d'; import { RenderCommand } from '../../graphics/commands/render'; -import { CircleLight } from '../../graphics/drawables/lights/circle-light'; import { MoveToCommand } from '../../physics/commands/move-to'; import { GameObject } from '../game-object'; export class Lamp extends GameObject { private light: CircleLight; - constructor(center: vec2, radius: number, color: vec3, lightness: number) { + constructor(center: vec2, color: vec3, lightness: number) { super(); - this.light = new CircleLight(center, radius, color, lightness); + this.light = new CircleLight(center, color, lightness); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); } private draw(c: RenderCommand) { - c.renderer.drawLight(this.light); + c.renderer.addDrawable(this.light); } private moveTo(c: MoveToCommand) { diff --git a/frontend/src/scripts/objects/types/tunnel.ts b/frontend/src/scripts/objects/types/tunnel.ts index 93ee1fe..b5c417a 100644 --- a/frontend/src/scripts/objects/types/tunnel.ts +++ b/frontend/src/scripts/objects/types/tunnel.ts @@ -1,11 +1,11 @@ import { vec2 } from 'gl-matrix'; import { RenderCommand } from '../../graphics/commands/render'; -import { DrawableTunnel } from '../../graphics/drawables/drawable-tunnel'; import { Physics } from '../../physics/physics'; +import { TunnelShape } from '../../shapes/types/tunnel-shape'; import { GameObject } from '../game-object'; export class Tunnel extends GameObject { - private shape: DrawableTunnel; + private shape: TunnelShape; constructor( physics: Physics, @@ -16,12 +16,12 @@ export class Tunnel extends GameObject { ) { super(); - this.shape = new DrawableTunnel(from, to, fromRadius, toRadius, this); + this.shape = new TunnelShape(from, to, fromRadius, toRadius, this); physics.addStaticBoundingBox(this.shape.boundingBox); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); } private draw(c: RenderCommand) { - c.renderer.drawShape(this.shape); + c.renderer.addDrawable(this.shape); } } diff --git a/frontend/src/scripts/shapes/i-shape.ts b/frontend/src/scripts/shapes/i-shape.ts index ab7fdac..a2b48d1 100644 --- a/frontend/src/scripts/shapes/i-shape.ts +++ b/frontend/src/scripts/shapes/i-shape.ts @@ -1,15 +1,12 @@ import { vec2 } from 'gl-matrix'; -import { BoundingBox } from './bounding-box'; import { GameObject } from '../objects/game-object'; +import { BoundingBox } from './bounding-box'; export interface IShape { - readonly isInverted: boolean; readonly boundingBox: BoundingBox; - readonly gameObject?: GameObject; - distance(target: vec2): number; + minDistance(target: vec2): number; normal(from: vec2): vec2; - clone(): IShape; } diff --git a/frontend/src/scripts/shapes/types/blob-shape.ts b/frontend/src/scripts/shapes/types/blob-shape.ts new file mode 100644 index 0000000..d88a8df --- /dev/null +++ b/frontend/src/scripts/shapes/types/blob-shape.ts @@ -0,0 +1,127 @@ +import { mat2d, vec2 } from 'gl-matrix'; +import { Circle, Drawable, DrawableDescriptor } from 'sdf-2d'; +import { GameObject } from '../../objects/game-object'; +import { BoundingBox } from '../bounding-box'; +import { IShape } from '../i-shape'; +import { CircleShape } from './circle-shape'; + +export class BlobShape extends Drawable implements IShape { + public static descriptor: DrawableDescriptor = { + sdf: { + shader: ` + uniform struct { + vec2 headCenter; + vec2 leftFootCenter; + vec2 rightFootCenter; + float headRadius; + float footRadius; + float k; + }[BLOB_COUNT] blobs; + + float smoothMin(float a, float b) + { + const float k = 80.0; + float res = exp2( -k*a ) + exp2( -k*b ); + return -log2( res )/k; + } + + float circleDistance(vec2 circleCenter, float radius) { + return distance(position, circleCenter) - radius; + } + + void blobMinDistance(inout float minDistance, inout float color) { + for (int i = 0; i < BLOB_COUNT; i++) { + float headDistance = circleDistance(blobs[i].headCenter, blobs[i].headRadius); + float leftFootDistance = circleDistance(blobs[i].leftFootCenter, blobs[i].footRadius); + float rightFootDistance = circleDistance(blobs[i].rightFootCenter, blobs[i].footRadius); + + float res = min( + smoothMin(headDistance, leftFootDistance), + smoothMin(headDistance, rightFootDistance) + ); + + minDistance = min(minDistance, res); + color = mix(1.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, res)); + } + } + `, + distanceFunctionName: 'blobMinDistance', + }, + uniformName: 'blobs', + uniformCountMacroName: 'BLOB_COUNT', + shaderCombinationSteps: [1], + empty: new BlobShape(vec2.fromValues(0, 0)), + }; + + public readonly boundingCircleRadius = 100; + + protected readonly headRadius = 40; + protected readonly footRadius = 15; + + private readonly headOffset = vec2.fromValues(0, -15); + private readonly leftFootOffset = vec2.fromValues(-12, -60); + private readonly rightFootOffset = vec2.fromValues(12, -60); + + public readonly isInverted = false; + protected boundingCircle = new CircleShape(vec2.create(), this.boundingCircleRadius); + protected head = new Circle(vec2.create(), this.headRadius); + protected leftFoot = new Circle(vec2.create(), this.footRadius); + protected rightFoot = new Circle(vec2.create(), this.footRadius); + + public constructor(center: vec2, public readonly gameObject: GameObject = null) { + super(); + this.position = center; + } + + public set position(value: vec2) { + vec2.copy(this.boundingCircle.center, value); + vec2.add(this.head.center, value, this.headOffset); + vec2.add(this.leftFoot.center, value, this.leftFootOffset); + vec2.add(this.rightFoot.center, value, this.rightFootOffset); + } + + public get center(): vec2 { + return this.boundingCircle.center; + } + + public get radius(): number { + return this.boundingCircle.radius; + } + + public minDistance(target: vec2): number { + return this.boundingCircle.minDistance(target); + } + public normal(from: vec2): vec2 { + return this.boundingCircle.normal(from); + } + + public get boundingBox(): BoundingBox { + return this.boundingCircle.boundingBox; + } + + public isInside(target: vec2): boolean { + return this.minDistance(target) < 0; + } + + public clone(): BlobShape { + return new BlobShape(this.boundingCircle.center, this.gameObject); + } + + protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any { + return { + headCenter: vec2.transformMat2d(vec2.create(), this.head.center, transform2d), + leftFootCenter: vec2.transformMat2d( + vec2.create(), + this.leftFoot.center, + transform2d + ), + rightFootCenter: vec2.transformMat2d( + vec2.create(), + this.rightFoot.center, + transform2d + ), + headRadius: this.headRadius * transform1d, + footRadius: this.footRadius * transform1d, + }; + } +} diff --git a/frontend/src/scripts/shapes/types/blob.ts b/frontend/src/scripts/shapes/types/blob.ts deleted file mode 100644 index db5e970..0000000 --- a/frontend/src/scripts/shapes/types/blob.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { GameObject } from '../../objects/game-object'; -import { BoundingBox } from '../bounding-box'; -import { IShape } from '../i-shape'; -import { Circle } from './circle'; - -export class Blob implements IShape { - public readonly boundingCircleRadius = 100; - - protected readonly headRadius = 40; - protected readonly footRadius = 15; - - private readonly headOffset = vec2.fromValues(0, -15); - private readonly leftFootOffset = vec2.fromValues(-12, -60); - private readonly rightFootOffset = vec2.fromValues(12, -60); - - public readonly isInverted = false; - protected boundingCircle = new Circle(vec2.create(), this.boundingCircleRadius); - protected head = new Circle(vec2.create(), this.headRadius); - protected leftFoot = new Circle(vec2.create(), this.footRadius); - protected rightFoot = new Circle(vec2.create(), this.footRadius); - public constructor(center: vec2, public readonly gameObject: GameObject = null) { - this.position = center; - } - - public set position(value: vec2) { - vec2.copy(this.boundingCircle.center, value); - vec2.add(this.head.center, value, this.headOffset); - vec2.add(this.leftFoot.center, value, this.leftFootOffset); - vec2.add(this.rightFoot.center, value, this.rightFootOffset); - } - - public get center(): vec2 { - return this.boundingCircle.center; - } - - public get radius(): number { - return this.boundingCircle.radius; - } - - public distance(target: vec2): number { - return this.boundingCircle.distance(target); - } - - public normal(from: vec2): vec2 { - return this.boundingCircle.normal(from); - } - - public get boundingBox(): BoundingBox { - return this.boundingCircle.boundingBox; - } - - public isInside(target: vec2): boolean { - return this.distance(target) < 0; - } - - public clone(): Blob { - return new Blob(this.boundingCircle.center, this.gameObject); - } -} diff --git a/frontend/src/scripts/shapes/types/circle.ts b/frontend/src/scripts/shapes/types/circle-shape.ts similarity index 74% rename from frontend/src/scripts/shapes/types/circle.ts rename to frontend/src/scripts/shapes/types/circle-shape.ts index 116d40e..d9fbd34 100644 --- a/frontend/src/scripts/shapes/types/circle.ts +++ b/frontend/src/scripts/shapes/types/circle-shape.ts @@ -1,16 +1,19 @@ import { vec2 } from 'gl-matrix'; +import { Circle } from 'sdf-2d'; import { GameObject } from '../../objects/game-object'; import { BoundingBox } from '../bounding-box'; import { IShape } from '../i-shape'; -export class Circle implements IShape { +export class CircleShape extends Circle implements IShape { public readonly isInverted = false; public constructor( - public center = vec2.create(), - public radius = 0, + center = vec2.create(), + radius = 0, public readonly gameObject: GameObject = null - ) {} + ) { + super(center, radius); + } public distance(target: vec2): number { return vec2.distance(this.center, target) - this.radius; @@ -35,12 +38,12 @@ export class Circle implements IShape { return this.distance(target) < 0; } - public areIntersecting(other: Circle): boolean { + public areIntersecting(other: CircleShape): boolean { const distance = vec2.distance(this.center, other.center); return distance < this.radius + other.radius; } - public clone(): Circle { - return new Circle(vec2.clone(this.center), this.radius, this.gameObject); + public clone(): CircleShape { + return new CircleShape(vec2.clone(this.center), this.radius, this.gameObject); } } diff --git a/frontend/src/scripts/shapes/types/tunnel-shape.ts b/frontend/src/scripts/shapes/types/tunnel-shape.ts index 94a5bbd..df46f08 100644 --- a/frontend/src/scripts/shapes/types/tunnel-shape.ts +++ b/frontend/src/scripts/shapes/types/tunnel-shape.ts @@ -1,24 +1,20 @@ import { vec2 } from 'gl-matrix'; +import { InvertedTunnel } from 'sdf-2d'; import { clamp01 } from '../../helper/clamp'; -import { mix } from '../../helper/mix'; import { rotate90Deg } from '../../helper/rotate-90-deg'; import { GameObject } from '../../objects/game-object'; import { BoundingBox } from '../bounding-box'; import { IShape } from '../i-shape'; -export default class TunnelShape implements IShape { - public readonly isInverted = true; - - public readonly toFromDelta: vec2; - +export class TunnelShape extends InvertedTunnel implements IShape { constructor( - public readonly from: vec2, - public readonly to: vec2, - public readonly fromRadius: number, - public readonly toRadius: number, + readonly from: vec2, + readonly to: vec2, + readonly fromRadius: number, + readonly toRadius: number, public readonly gameObject: GameObject = null ) { - this.toFromDelta = vec2.subtract(vec2.create(), to, from); + super(from, to, fromRadius, toRadius); } public get boundingBox(): BoundingBox { @@ -33,9 +29,10 @@ export default class TunnelShape implements IShape { public normal(target: vec2): vec2 { const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); + const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from); + const h = clamp01( - vec2.dot(targetFromDelta, this.toFromDelta) / - vec2.dot(this.toFromDelta, this.toFromDelta) + vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta) ); let diff = vec2.create(); @@ -46,10 +43,10 @@ export default class TunnelShape implements IShape { vec2.subtract(diff, target, this.from); } else { const side = Math.sign( - this.toFromDelta.x * targetFromDelta.y - this.toFromDelta.y * targetFromDelta.x + toFromDelta.x * targetFromDelta.y - toFromDelta.y * targetFromDelta.x ); - const normal = rotate90Deg(this.toFromDelta); + const normal = rotate90Deg(toFromDelta); vec2.normalize(normal, normal); const translatedFrom = vec2.add( @@ -72,20 +69,6 @@ export default class TunnelShape implements IShape { return vec2.normalize(diff, diff); } - public distance(target: vec2): number { - const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); - - const h = clamp01( - vec2.dot(targetFromDelta, this.toFromDelta) / - vec2.dot(this.toFromDelta, this.toFromDelta) - ); - - return ( - vec2.distance(targetFromDelta, vec2.scale(vec2.create(), this.toFromDelta, h)) - - mix(this.fromRadius, this.toRadius, h) - ); - } - public clone(): TunnelShape { return new TunnelShape( vec2.clone(this.from), diff --git a/frontend/src/styles/main.scss b/frontend/src/styles/main.scss index 95d8266..710ef3a 100644 --- a/frontend/src/styles/main.scss +++ b/frontend/src/styles/main.scss @@ -1,3 +1,19 @@ +$breakpoint: 800px; + +* { + margin: 0; + box-sizing: border-box; + color: white; +} + +html { + background-color: linear-gradient(45deg, #103783, #9bafd9); + + @media (max-width: $breakpoint) { + font-size: 0.7rem; + } +} + html, body, canvas#main { @@ -6,31 +22,16 @@ canvas#main { } body { - position: relative; - margin: 0; - #overlay { - font-family: Helvetica, Arial, sans-serif; - font-size: 0.75em; - margin: 0.75em 1em; - user-select: none; - pointer-events: none; + margin: 0.5rem 1.25rem; position: absolute; right: 0; - $outline-width: 0.5px; - text-shadow: -$outline-width -$outline-width 0 #000, - $outline-width -$outline-width 0 #000, -$outline-width $outline-width 0 #000, - $outline-width $outline-width 0 #000; - color: white; + font-size: 0.75rem; white-space: pre; + font-family: 'Lucida Console', Monaco, monospace; - @media (max-width: 800px) { - font-size: 0.65em; + @media (max-width: $breakpoint) { + font-size: 0.6rem; } } - - canvas#main { - width: 100%; - background-color: hotpink; - } } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 13fb34c..468fc39 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -3,12 +3,12 @@ "outDir": "./dist/", "sourceMap": true, "noImplicitAny": false, - "target": "es5", + "target": "es6", "downlevelIteration": true, "allowJs": true, "experimentalDecorators": true, "moduleResolution": "Node", - "module": "commonjs", + "module": "es6", "lib": ["es2015", "dom"] } } diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 0185788..696d45b 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -7,23 +7,34 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); const Sass = require('sass'); -const isProduction = process.env.NODE_ENV == 'production'; -const isDevelopment = !isProduction; +const PATHS = { + entryPoint: path.resolve(__dirname, 'src/index.ts'), + bundles: path.resolve(__dirname, 'dist'), +}; module.exports = { + entry: { + index: PATHS.entryPoint, + }, + target: 'web', + output: { + filename: '[name].[contenthash].js', + path: PATHS.bundles, + }, + devtool: 'source-map', watchOptions: { ignored: /node_modules/, }, - devtool: 'inline-source-map', devServer: { host: '0.0.0.0', disableHostCheck: true, }, optimization: { minimize: true, + usedExports: true, minimizer: [ new TerserJSPlugin({ - sourceMap: isDevelopment, + sourceMap: true, cache: true, test: /\.ts$/i, terserOptions: { @@ -66,25 +77,8 @@ module.exports = { chunkFilename: '[id].[contenthash].css', }), ], - entry: { - index: './src/index.ts', - }, module: { rules: [ - { - test: /\.(glsl)$/, - use: { - loader: 'raw-loader', - }, - }, - { - test: /\.svg$/, - loader: 'svg-url-loader', - options: { - limit: 10 * 1024, - noquotes: true, - }, - }, { test: /\.ico$/i, use: { @@ -116,17 +110,6 @@ module.exports = { }, ], }, - { - test: /\.(woff2?|ttf|eot|svg)(?:[?#].+)?$/, - use: { - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'static/fonts/', - }, - }, - include: /fonts/, - }, { test: /\.ts$/, use: { @@ -137,10 +120,6 @@ module.exports = { ], }, resolve: { - extensions: ['.ts', '.js', '.glsl'], - }, - output: { - filename: '[name].[contenthash].js', - path: path.resolve(__dirname, 'dist'), + extensions: ['.ts', '.js'], }, };