From 76282a4cf7b9eaf0002222b2f4555293d22e79df Mon Sep 17 00:00:00 2001 From: schmelczerandras Date: Tue, 18 Aug 2020 15:49:15 +0200 Subject: [PATCH] Refactor rendering --- frontend/.prettierrc | 1 + .../drawing/drawables/drawable-blob.ts | 31 +++++++-- .../drawing/drawables/drawable-tunnel.ts | 19 ++++-- .../scripts/drawing/drawables/i-drawable.ts | 4 +- .../drawing/drawables/lights/circle-light.ts | 13 ++-- .../drawing/drawables/lights/point-light.ts | 13 ++-- frontend/src/scripts/drawing/i-renderer.ts | 5 +- .../drawing/rendering/rendering-pass.ts | 28 ++++---- .../drawing/rendering/webgl2-renderer.ts | 35 ++++------ frontend/src/scripts/drawing/settings.ts | 8 +-- .../drawing/shaders/cave-distance-fs.glsl | 2 - .../drawing/shaders/lights-shading-fs.glsl | 34 +++++----- .../drawing/shaders/rainbow-shading-fs.glsl | 49 -------------- frontend/src/scripts/game.ts | 64 +++++++++++++++---- frontend/src/scripts/helper/timing.ts | 14 ++-- frontend/src/scripts/i-game.ts | 9 +++ frontend/src/scripts/objects/types/camera.ts | 32 ++++++---- .../src/scripts/objects/types/character.ts | 33 ++++++---- frontend/src/scripts/objects/types/lamp.ts | 2 +- frontend/src/scripts/objects/types/tunnel.ts | 2 +- .../scripts/objects/world/create-character.ts | 27 -------- frontend/src/scripts/shapes/i-shape.ts | 3 + frontend/src/scripts/shapes/types/blob.ts | 8 ++- frontend/src/scripts/shapes/types/circle.ts | 9 ++- .../src/scripts/shapes/types/tunnel-shape.ts | 7 +- 25 files changed, 239 insertions(+), 213 deletions(-) delete mode 100644 frontend/src/scripts/drawing/shaders/rainbow-shading-fs.glsl create mode 100644 frontend/src/scripts/i-game.ts delete mode 100644 frontend/src/scripts/objects/world/create-character.ts diff --git a/frontend/.prettierrc b/frontend/.prettierrc index ea2afbe..53d01de 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,5 +1,6 @@ { "trailingComma": "es5", + "printWidth": 120, "tabWidth": 2, "singleQuote": true, "endOfLine": "lf" diff --git a/frontend/src/scripts/drawing/drawables/drawable-blob.ts b/frontend/src/scripts/drawing/drawables/drawable-blob.ts index f27ea0b..cf9da86 100644 --- a/frontend/src/scripts/drawing/drawables/drawable-blob.ts +++ b/frontend/src/scripts/drawing/drawables/drawable-blob.ts @@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable'; import { IDrawableDescriptor } from './i-drawable-descriptor'; import { settings } from '../settings'; import { Blob } from '../../shapes/types/blob'; +import { vec2, mat2d } from 'gl-matrix'; export class DrawableBlob extends Blob implements IDrawable { public static descriptor: IDrawableDescriptor = { @@ -10,17 +11,37 @@ export class DrawableBlob extends Blob implements IDrawable { shaderCombinationSteps: settings.shaderCombinations.blobSteps, }; - public serializeToUniforms(uniforms: any): void { + public serializeToUniforms( + uniforms: any, + scale: number, + transform: mat2d + ): void { const uniformName = DrawableBlob.descriptor.uniformName; if (!uniforms.hasOwnProperty(uniformName)) { uniforms[uniformName] = []; } uniforms[uniformName].push({ - headCenter: this.head.center, - torsoCenter: this.torso.center, - leftFootCenter: this.leftFoot.center, - rightFootCenter: this.rightFoot.center, + headCenter: vec2.transformMat2d( + vec2.create(), + this.head.center, + transform + ), + torsoCenter: vec2.transformMat2d( + vec2.create(), + this.torso.center, + transform + ), + leftFootCenter: vec2.transformMat2d( + vec2.create(), + this.leftFoot.center, + transform + ), + rightFootCenter: vec2.transformMat2d( + vec2.create(), + this.rightFoot.center, + transform + ), }); } } diff --git a/frontend/src/scripts/drawing/drawables/drawable-tunnel.ts b/frontend/src/scripts/drawing/drawables/drawable-tunnel.ts index 7c656d8..40b4072 100644 --- a/frontend/src/scripts/drawing/drawables/drawable-tunnel.ts +++ b/frontend/src/scripts/drawing/drawables/drawable-tunnel.ts @@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable'; import { TunnelShape } from '../../shapes/types/tunnel-shape'; import { IDrawableDescriptor } from './i-drawable-descriptor'; import { settings } from '../settings'; +import { mat2d, vec2 } from 'gl-matrix'; export class DrawableTunnel extends TunnelShape implements IDrawable { public static descriptor: IDrawableDescriptor = { @@ -10,17 +11,25 @@ export class DrawableTunnel extends TunnelShape implements IDrawable { shaderCombinationSteps: settings.shaderCombinations.lineSteps, }; - public serializeToUniforms(uniforms: any): void { + public serializeToUniforms( + uniforms: any, + scale: number, + transform: mat2d + ): void { const uniformName = DrawableTunnel.descriptor.uniformName; if (!uniforms.hasOwnProperty(uniformName)) { uniforms[uniformName] = []; } uniforms[uniformName].push({ - from: this.from, - toFromDelta: this.toFromDelta, - fromRadius: this.fromRadius, - toRadius: this.toRadius, + from: vec2.transformMat2d(vec2.create(), this.from, transform), + toFromDelta: vec2.transformMat2d( + vec2.create(), + this.toFromDelta, + transform + ), + fromRadius: this.fromRadius * scale, + toRadius: this.toRadius * scale, }); } } diff --git a/frontend/src/scripts/drawing/drawables/i-drawable.ts b/frontend/src/scripts/drawing/drawables/i-drawable.ts index beb84a8..cca827b 100644 --- a/frontend/src/scripts/drawing/drawables/i-drawable.ts +++ b/frontend/src/scripts/drawing/drawables/i-drawable.ts @@ -1,6 +1,6 @@ -import { vec2 } from 'gl-matrix'; +import { vec2, mat2d } from 'gl-matrix'; export interface IDrawable { distance(target: vec2): number; - serializeToUniforms(uniforms: any): void; + serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void; } diff --git a/frontend/src/scripts/drawing/drawables/lights/circle-light.ts b/frontend/src/scripts/drawing/drawables/lights/circle-light.ts index 7e1c678..4122e1c 100644 --- a/frontend/src/scripts/drawing/drawables/lights/circle-light.ts +++ b/frontend/src/scripts/drawing/drawables/lights/circle-light.ts @@ -1,4 +1,4 @@ -import { vec2, vec3 } from 'gl-matrix'; +import { vec2, vec3, mat2d } from 'gl-matrix'; import { GameObject } from '../../../objects/game-object'; import { settings } from '../../settings'; import { IDrawableDescriptor } from '../i-drawable-descriptor'; @@ -12,7 +12,6 @@ export class CircleLight implements ILight { }; constructor( - public readonly owner: GameObject, public center: vec2, public radius: number, public color: vec3, @@ -23,7 +22,11 @@ export class CircleLight implements ILight { return 0; } - public serializeToUniforms(uniforms: any): void { + public serializeToUniforms( + uniforms: any, + scale: number, + transform: mat2d + ): void { const uniformName = CircleLight.descriptor.uniformName; if (!uniforms.hasOwnProperty(uniformName)) { @@ -31,8 +34,8 @@ export class CircleLight implements ILight { } uniforms[uniformName].push({ - center: this.center, - radius: this.radius, + center: vec2.transformMat2d(vec2.create(), this.center, transform), + radius: this.radius * scale, value: this.value, }); } diff --git a/frontend/src/scripts/drawing/drawables/lights/point-light.ts b/frontend/src/scripts/drawing/drawables/lights/point-light.ts index c4ea132..0417e78 100644 --- a/frontend/src/scripts/drawing/drawables/lights/point-light.ts +++ b/frontend/src/scripts/drawing/drawables/lights/point-light.ts @@ -1,5 +1,5 @@ import { ILight } from './i-light'; -import { vec2, vec3 } from 'gl-matrix'; +import { vec2, vec3, mat2d } from 'gl-matrix'; import { IDrawableDescriptor } from '../i-drawable-descriptor'; import { settings } from '../../settings'; import { GameObject } from '../../../objects/game-object'; @@ -12,7 +12,6 @@ export class PointLight implements ILight { }; public constructor( - public readonly owner: GameObject, public center: vec2, public radius: number, public color: vec3, @@ -23,7 +22,11 @@ export class PointLight implements ILight { return vec2.distance(this.center, target) - this.radius; } - public serializeToUniforms(uniforms: any): void { + public serializeToUniforms( + uniforms: any, + scale: number, + transform: mat2d + ): void { const listName = PointLight.descriptor.uniformName; if (!uniforms.hasOwnProperty(listName)) { @@ -31,8 +34,8 @@ export class PointLight implements ILight { } uniforms[listName].push({ - center: this.center, - radius: this.radius, + center: vec2.transformMat2d(vec2.create(), this.center, transform), + radius: this.radius * scale, value: this.value, }); } diff --git a/frontend/src/scripts/drawing/i-renderer.ts b/frontend/src/scripts/drawing/i-renderer.ts index ac073c6..dd19304 100644 --- a/frontend/src/scripts/drawing/i-renderer.ts +++ b/frontend/src/scripts/drawing/i-renderer.ts @@ -1,6 +1,7 @@ import { vec2 } from 'gl-matrix'; import { ILight } from './drawables/lights/i-light'; import { IDrawable } from './drawables/i-drawable'; +import { BoundingBoxBase } from '../shapes/bounding-box-base'; export interface IRenderer { startFrame(deltaTime: DOMHighResTimeStamp): void; @@ -10,7 +11,7 @@ export interface IRenderer { drawLight(light: ILight): void; drawInfoText(text: string): void; - setCameraPosition(position: vec2): void; + readonly canvasSize: vec2; + setViewArea(viewArea: BoundingBoxBase): void; setCursorPosition(position: vec2): void; - setInViewArea(size: number): vec2; } diff --git a/frontend/src/scripts/drawing/rendering/rendering-pass.ts b/frontend/src/scripts/drawing/rendering/rendering-pass.ts index 4201ff2..22d9908 100644 --- a/frontend/src/scripts/drawing/rendering/rendering-pass.ts +++ b/frontend/src/scripts/drawing/rendering/rendering-pass.ts @@ -1,4 +1,4 @@ -import { vec2 } from 'gl-matrix'; +import { vec2, mat2d } from 'gl-matrix'; import { InfoText } from '../../objects/types/info-text'; import { IDrawable } from '../drawables/i-drawable'; import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor'; @@ -27,20 +27,11 @@ export class RenderingPass { this.drawables.push(drawable); } - public render( - commonUniforms: any, - viewBoxCenter: vec2, - viewBoxRadius: number, - inputTexture?: WebGLTexture - ) { + public render(commonUniforms: any, inputTexture?: WebGLTexture) { this.frame.bindAndClear(inputTexture); const q = 1 / settings.tileMultiplier; const tileUvSize = vec2.fromValues(q, q); - const possiblyOnScreenDrawables = this.drawables.filter( - (p) => p.distance(viewBoxCenter) < viewBoxRadius - ); - const origin = vec2.transformMat2d( vec2.create(), vec2.fromValues(0, 0), @@ -59,6 +50,9 @@ export class RenderingPass { let sumLineCount = 0; + const scale = 1; + const transform = mat2d.create(); + for (let x = 0; x < 1; x += q) { for (let y = 0; y < 1; y += q) { const uniforms = { ...commonUniforms }; @@ -73,24 +67,24 @@ export class RenderingPass { uniforms.uvToWorld ); - const primitivesNearTile = possiblyOnScreenDrawables.filter( + const primitivesNearTile = this.drawables.filter( (p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR ); sumLineCount += primitivesNearTile.length; - primitivesNearTile.forEach((p) => p.serializeToUniforms(uniforms)); + primitivesNearTile.forEach((p) => + p.serializeToUniforms(uniforms, scale, transform) + ); this.program.bindAndSetUniforms(uniforms); this.program.draw(); } } - this.drawables = []; - InfoText.modifyRecord( 'nearby ' + this.drawableDescriptors[0].countMacroName, - possiblyOnScreenDrawables.length.toFixed(2) + this.drawables.length.toFixed(2) ); InfoText.modifyRecord( @@ -101,5 +95,7 @@ export class RenderingPass { settings.tileMultiplier ).toFixed(2) ); + + this.drawables = []; } } diff --git a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts index 269727c..d036f51 100644 --- a/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts +++ b/frontend/src/scripts/drawing/rendering/webgl2-renderer.ts @@ -18,13 +18,13 @@ import { IDrawable } from '../drawables/i-drawable'; import { DrawableTunnel } from '../drawables/drawable-tunnel'; import { enableExtension } from '../graphics-library/helper/enable-extension'; import { DrawableBlob } from '../drawables/drawable-blob'; +import { BoundingBoxBase } from '../../shapes/bounding-box-base'; export class WebGl2Renderer implements IRenderer { private gl: WebGL2RenderingContext; private stopwatch?: WebGlStopwatch; private viewBoxBottomLeft = vec2.create(); - private cameraPosition = vec2.create(); private viewBoxSize = vec2.create(); private cursorPosition = vec2.create(); @@ -92,16 +92,10 @@ export class WebGl2Renderer implements IRenderer { public finishFrame() { this.calculateMatrices(); - const viewBoxRadius = vec2.length( - vec2.scale(vec2.create(), this.viewBoxSize, 0.5) - ); - - this.distancePass.render(this.uniforms, this.cameraPosition, viewBoxRadius); + this.distancePass.render(this.uniforms); this.lightingPass.render( this.uniforms, - this.cameraPosition, - viewBoxRadius, this.distanceFieldFrameBuffer.colorTexture ); @@ -164,11 +158,16 @@ export class WebGl2Renderer implements IRenderer { ); } - public setCameraPosition(position: vec2) { - this.cameraPosition = position; - this.viewBoxBottomLeft = vec2.fromValues( - this.cameraPosition.x - this.viewBoxSize.x / 2, - this.cameraPosition.y - this.viewBoxSize.y / 2 + public get canvasSize(): vec2 { + return vec2.fromValues(this.canvas.clientWidth, this.canvas.clientHeight); + } + + public setViewArea(viewArea: BoundingBoxBase) { + this.viewBoxSize = viewArea.size; + this.viewBoxBottomLeft = vec2.add( + vec2.create(), + viewArea.topLeft, + vec2.fromValues(0, -viewArea.size.y) ); } @@ -176,16 +175,6 @@ export class WebGl2Renderer implements IRenderer { this.cursorPosition = position; } - public setInViewArea(size: number): vec2 { - const canvasAspectRatio = - this.canvas.clientWidth / this.canvas.clientHeight; - - return (this.viewBoxSize = vec2.fromValues( - Math.sqrt(size * canvasAspectRatio), - Math.sqrt(size / canvasAspectRatio) - )); - } - public drawInfoText(text: string) { if (this.overlay.innerText != text) { this.overlay.innerText = text; diff --git a/frontend/src/scripts/drawing/settings.ts b/frontend/src/scripts/drawing/settings.ts index c6d89a2..0c1a66e 100644 --- a/frontend/src/scripts/drawing/settings.ts +++ b/frontend/src/scripts/drawing/settings.ts @@ -7,7 +7,7 @@ export const settings = { scaleTargets: [ [0.2, 0.1], [0.6, 0.1], - [1, 0.3], + [1, 1], /*[1.25, 0.75], [1.5, 1], [1.75, 1.25], @@ -20,12 +20,12 @@ export const settings = { multiplicativeDecrease: 1.15, }, }, - tileMultiplier: 5, + tileMultiplier: 8, shaderMacros: { edgeSmoothing: 10, headRadius: 5, - torsoRadius: 10, - footRadius: 4, + torsoRadius: 8, + footRadius: 2, }, shaderCombinations: { lineSteps: [0, 1, 2, 4, 8, 16, 128], diff --git a/frontend/src/scripts/drawing/shaders/cave-distance-fs.glsl b/frontend/src/scripts/drawing/shaders/cave-distance-fs.glsl index 24fabe0..076d5cd 100644 --- a/frontend/src/scripts/drawing/shaders/cave-distance-fs.glsl +++ b/frontend/src/scripts/drawing/shaders/cave-distance-fs.glsl @@ -69,8 +69,6 @@ uniform float maxMinDistance; } #endif - - in vec2 worldCoordinates; out vec2 fragmentColor; diff --git a/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl b/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl index c4d8cfa..be40f31 100644 --- a/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl +++ b/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl @@ -61,40 +61,41 @@ void main() { #if CIRCLE_LIGHT_COUNT > 0 for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { - float lightCenterDistance = distance(circleLights[i].center, worldCoordinates); + float lightCenterDistance = distance( + circleLights[i].center, + worldCoordinates + ); + float lightDistance = lightCenterDistance - circleLights[i].radius; + + /*if (lightDistance < 0.0) { + lighting = vec3(1.0, 1.0, 0.0); + }*/ vec3 lightColorAtPosition = circleLights[i].value / pow( - lightCenterDistance / LIGHT_DROP + 1.0, 2.0 + lightDistance / LIGHT_DROP + 1.0, 2.0 ); float q = INFINITY; float rayLength = startingDistance; - float exponentialDecayDistance = rayLength; vec2 direction = normalize(circleLightDirections[i]) / viewBoxSize; for (int j = 0; j < 48; j++) { - if (rayLength > lightCenterDistance - circleLights[i].radius) { - rayLength = lightCenterDistance - circleLights[i].radius; - float minDistance = getDistance(uvCoordinates + direction * rayLength); - exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0; - q = min(q, minDistance / rayLength); - + if (rayLength >= lightDistance) { lighting += lightColorAtPosition * clamp( - q / circleLights[i].radius * (lightCenterDistance + 1.0), 0.0, 1.0 + q / circleLights[i].radius * lightCenterDistance, 0.0, 1.0 ); break; } float minDistance = getDistance(uvCoordinates + direction * rayLength); - exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0; - q = min(q, exponentialDecayDistance / rayLength); + q = min(q, minDistance / rayLength); rayLength += minDistance; } } #endif #if POINT_LIGHT_COUNT > 0 - for (int i = 0; i < POINT_LIGHT_COUNT; i++) { + /*for (int i = 0; i < POINT_LIGHT_COUNT; i++) { float lightDistance = distance(pointLights[i].center, worldCoordinates); vec3 lightColorAtPosition = mix( @@ -120,8 +121,11 @@ void main() { q = min(q, exponentialDecayDistance); rayLength += minDistance; } - } + }*/ #endif - fragmentColor = vec4(colorAtPosition * lighting * step(0.0, startingDistance), 1.0); + fragmentColor = vec4( + colorAtPosition * lighting * step(0.0, startingDistance), + 1.0 + ); } diff --git a/frontend/src/scripts/drawing/shaders/rainbow-shading-fs.glsl b/frontend/src/scripts/drawing/shaders/rainbow-shading-fs.glsl deleted file mode 100644 index 8ef59b2..0000000 --- a/frontend/src/scripts/drawing/shaders/rainbow-shading-fs.glsl +++ /dev/null @@ -1,49 +0,0 @@ -#version 300 es - -precision mediump float; - -vec3 rainbow(float level) { - float r = float(level <= 2.0) + float(level > 4.0) * 0.5; - float g = max(1.0 - abs(level - 2.0) * 0.5, 0.0); - float b = (1.0 - (level - 4.0) * 0.5) * float(level >= 4.0); - return vec3(r, g, b); -} - -vec4 smoothRainbow(float x) { - float level1 = floor(x*6.0); - float level2 = min(6.0, floor(x*6.0) + 1.0); - - vec3 a = rainbow(level1); - vec3 b = rainbow(level2); - - return vec4(mix(a, b, fract(x*6.0)), 1.0); -} - - -uniform sampler2D distanceTexture; -uniform mat3 worldToDistanceUV; -uniform mat3 lightingScreenToWorld; -out vec4 fragmentColor; -uniform vec2 cursorPosition; - - -float getDistance(in vec2 targetUV, out vec3 color) { - vec4 values = texture(distanceTexture, targetUV); - color = values.rgb; - return values.a; -} - -in vec2 worldCoordinates; - -void main() { - vec2 targetUV = (vec3(worldCoordinates.xy, 1.0) * worldToDistanceUV).xy; - - vec4 previous = texture(distanceTexture, targetUV); - - //fragmentColor = smoothRainbow(previous.a); - fragmentColor = previous.a > 0.5 ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0); - - if (distance(worldCoordinates, cursorPosition) < 10.0) { - fragmentColor = vec4(1.0, 1.0, 0.0, 1.0); - } -} diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 66ab72f..65f1150 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -8,22 +8,33 @@ import { MouseListener } from './input/mouse-listener'; import { TouchListener } from './input/touch-listener'; import { Objects } from './objects/objects'; import { InfoText } from './objects/types/info-text'; -import { createCharacter } from './objects/world/create-character'; import { createDungeon } from './objects/world/create-dungeon'; import { RenderCommand } from './drawing/commands/render'; import { Physics } from './physics/physics'; import { TeleportToCommand } from './physics/commands/teleport-to'; import { IRenderer } from './drawing/i-renderer'; import { Random } from './helper/random'; +import { Camera } from './objects/types/camera'; +import { Character } from './objects/types/character'; +import { IGame } from './i-game'; +import { GameObject } from './objects/game-object'; +import { vec2 } from 'gl-matrix'; +import { BoundingBoxBase } from './shapes/bounding-box-base'; +import { MoveToCommand } from './physics/commands/move-to'; +import { BoundingBox } from './shapes/bounding-box'; + +export class Game implements IGame { + public readonly objects = new Objects(); + public readonly physics = new Physics(); + public readonly camera = new Camera(); -export class Game { private previousTime?: DOMHighResTimeStamp = null; - private objects = new Objects(); - private physics = new Physics(); - - private renderer: IRenderer; private previousFpsValues: Array = []; + private infoText = new InfoText(); + private character: Character; + private renderer: IRenderer; + constructor() { const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); const overlay: HTMLElement = document.querySelector('#overlay'); @@ -52,13 +63,29 @@ export class Game { requestAnimationFrame(this.gameLoop.bind(this)); } + public addObject(o: GameObject) { + this.objects.addObject(o); + } + + public get viewArea(): BoundingBoxBase { + return this.camera.viewArea; + } + + public findIntersecting(box: BoundingBoxBase): Array { + return this.physics.findIntersecting(box); + } + private initializeScene() { - this.objects.addObject(new InfoText()); + this.objects.addObject(this.infoText); + const start = createDungeon(this.objects, this.physics); createDungeon(this.objects, this.physics); - const character = createCharacter(this.objects, this.physics); - console.log('start', start.from); - character.sendCommand(new TeleportToCommand(start.from)); + + this.character = new Character(this); + //this.physics.addDynamicBoundingBox(this.character.boundingBox); + this.addObject(this.character); + this.addObject(this.camera); + this.character.sendCommand(new TeleportToCommand(start.from)); } private handleVisibilityChange() { @@ -78,10 +105,23 @@ export class Game { this.calculateFps(deltaTime); this.objects.sendCommand(new StepCommand(deltaTime)); + this.camera.sendCommand(new MoveToCommand(this.character.position)); this.renderer.startFrame(deltaTime); - this.objects.sendCommand(new BeforeRenderCommand(this.renderer)); - this.objects.sendCommand(new RenderCommand(this.renderer)); + + this.camera.sendCommand(new BeforeRenderCommand(this.renderer)); + + const shouldBeDrawn = this.physics + .findIntersecting(this.camera.viewArea) + .map((b) => b.shape?.gameObject); + + for (let 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(); window.requestAnimationFrame(this.gameLoop.bind(this)); diff --git a/frontend/src/scripts/helper/timing.ts b/frontend/src/scripts/helper/timing.ts index 6da804a..1e3be3a 100644 --- a/frontend/src/scripts/helper/timing.ts +++ b/frontend/src/scripts/helper/timing.ts @@ -1,11 +1,8 @@ import { InfoText } from '../objects/types/info-text'; +import { last } from './last'; export function timeIt(interval = 60) { - return function ( - target: any, - propertyKey: string, - descriptor: PropertyDescriptor - ) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { let i = 0; let previousTimes: Array = []; @@ -19,10 +16,9 @@ export function timeIt(interval = 60) { previousTimes.push(end - start); if (i++ % interval == 0) { - previousTimes.sort(); - const text = `Max: ${previousTimes[previousTimes.length - 1].toFixed( - 2 - )} ms\n\tMedian: ${previousTimes[ + previousTimes = previousTimes.sort(); + + const text = `Max: ${last(previousTimes).toFixed(2)} ms\n\tMedian: ${previousTimes[ Math.floor(previousTimes.length / 2) ].toFixed(2)} ms`; diff --git a/frontend/src/scripts/i-game.ts b/frontend/src/scripts/i-game.ts new file mode 100644 index 0000000..1a32748 --- /dev/null +++ b/frontend/src/scripts/i-game.ts @@ -0,0 +1,9 @@ +import { GameObject } from './objects/game-object'; +import { vec2 } from 'gl-matrix'; +import { BoundingBoxBase } from './shapes/bounding-box-base'; + +export interface IGame { + addObject(o: GameObject); + readonly viewArea: BoundingBoxBase; + findIntersecting(box: BoundingBoxBase): Array; +} diff --git a/frontend/src/scripts/objects/types/camera.ts b/frontend/src/scripts/objects/types/camera.ts index 802d62b..442d827 100644 --- a/frontend/src/scripts/objects/types/camera.ts +++ b/frontend/src/scripts/objects/types/camera.ts @@ -9,40 +9,46 @@ import { GameObject } from '../game-object'; import { Lamp } from './lamp'; export class Camera extends GameObject { - private inViewArea = 1920 * 1080 * 5; + private inViewAreaSize = 1920 * 1080 * 5; private cursorPosition = vec2.create(); - private boundingBox: BoundingBox; + private _viewArea: BoundingBox; constructor() { super(); - this.boundingBox = new BoundingBox(null); + this._viewArea = new BoundingBox(null); this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); - this.addCommandExecutor( - CursorMoveCommand, - this.setCursorPosition.bind(this) - ); + this.addCommandExecutor(CursorMoveCommand, this.setCursorPosition.bind(this)); this.addCommandExecutor(ZoomCommand, this.zoom.bind(this)); } - public get viewAreaSize(): vec2 { - return this.boundingBox.size; + public get viewArea(): BoundingBox { + return this._viewArea; } private draw(c: BeforeRenderCommand) { - c.renderer.setCameraPosition(this.boundingBox.topLeft); + const canvasAspectRatio = c.renderer.canvasSize.x / c.renderer.canvasSize.y; + + this.viewArea.size = vec2.fromValues( + Math.sqrt(this.inViewAreaSize * canvasAspectRatio), + Math.sqrt(this.inViewAreaSize / canvasAspectRatio) + ); + + c.renderer.setViewArea(this._viewArea); c.renderer.setCursorPosition(this.cursorPosition); - this.boundingBox.size = c.renderer.setInViewArea(this.inViewArea); } private moveTo(c: MoveToCommand) { - this.boundingBox.topLeft = c.position; + this._viewArea.topLeft = vec2.fromValues( + c.position.x - this._viewArea.size.x / 2, + c.position.y + this._viewArea.size.y / 2 + ); } private zoom(c: ZoomCommand) { - this.inViewArea *= c.factor; + this.inViewAreaSize *= c.factor; } private setCursorPosition(c: CursorMoveCommand) { diff --git a/frontend/src/scripts/objects/types/character.ts b/frontend/src/scripts/objects/types/character.ts index 5a81fb0..4c8d228 100644 --- a/frontend/src/scripts/objects/types/character.ts +++ b/frontend/src/scripts/objects/types/character.ts @@ -1,4 +1,4 @@ -import { vec2 } from 'gl-matrix'; +import { vec2, vec3 } from 'gl-matrix'; import { KeyDownCommand } from '../../input/commands/key-down'; import { KeyUpCommand } from '../../input/commands/key-up'; import { SwipeCommand } from '../../input/commands/swipe'; @@ -13,21 +13,25 @@ import { Blob } from '../../shapes/types/blob'; import { RenderCommand } from '../../drawing/commands/render'; import { DrawableBlob } from '../../drawing/drawables/drawable-blob'; import { Lamp } from './lamp'; +import { Game } from '../../game'; +import { IGame } from '../../i-game'; export class Character extends GameObject { private keysDown: Set = new Set(); + private light = new Lamp( + vec2.create(), + 40, + vec3.fromValues(0.67, 0.0, 0.33), + 2 + ); - private shape: DrawableBlob; + private shape = new DrawableBlob(vec2.create()); private static speed = 1.5; - constructor( - private physics: Physics, - private camera: Camera, - private light: Lamp - ) { + constructor(private game: IGame) { super(); - this.shape = new DrawableBlob(vec2.create()); + game.addObject(this.light); this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); @@ -38,13 +42,18 @@ export class Character extends GameObject { this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key)); this.addCommandExecutor(SwipeCommand, (c) => { this.tryMoving( - vec2.multiply(vec2.create(), c.delta, this.camera.viewAreaSize) + vec2.multiply(vec2.create(), c.delta, this.game.viewArea.size) ); }); } + public get position(): vec2 { + return this.shape.center; + } + private draw(c: RenderCommand) { c.renderer.drawShape(this.shape); + this.light.sendCommand(c); } private tryMoving(delta: vec2, isFirstIteration = true) { @@ -87,6 +96,9 @@ export class Character extends GameObject { ) .sort((e) => Math.abs(e.distance)); + if (intersecting.length < 1) { + return; + } const normal = intersecting[0].shape.normal(this.shape.center); const maxDistance = intersecting.reduce((p, c) => @@ -106,7 +118,7 @@ export class Character extends GameObject { private getNearShapesTo( shape: Blob ): Array<{ shape: IShape; distance: number }> { - return this.physics + return this.game .findIntersecting(shape.boundingBox) .filter((b) => b.shape) .map((b) => ({ @@ -118,7 +130,6 @@ export class Character extends GameObject { private setPosition(value: vec2) { this.shape.position = value; - this.camera.sendCommand(new MoveToCommand(value)); vec2.add(value, value, vec2.fromValues(80, 0)); this.light.sendCommand(new MoveToCommand(value)); } diff --git a/frontend/src/scripts/objects/types/lamp.ts b/frontend/src/scripts/objects/types/lamp.ts index fbb19af..1e31d7d 100644 --- a/frontend/src/scripts/objects/types/lamp.ts +++ b/frontend/src/scripts/objects/types/lamp.ts @@ -10,7 +10,7 @@ export class Lamp extends GameObject { constructor(center: vec2, radius: number, color: vec3, lightness: number) { super(); - this.light = new CircleLight(this, center, radius, color, lightness); + this.light = new CircleLight(center, radius, color, lightness); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); diff --git a/frontend/src/scripts/objects/types/tunnel.ts b/frontend/src/scripts/objects/types/tunnel.ts index 850aca7..19927d8 100644 --- a/frontend/src/scripts/objects/types/tunnel.ts +++ b/frontend/src/scripts/objects/types/tunnel.ts @@ -17,7 +17,7 @@ export class Tunnel extends GameObject { ) { super(); - this.shape = new DrawableTunnel(from, to, fromRadius, toRadius); + this.shape = new DrawableTunnel(from, to, fromRadius, toRadius, this); physics.addStaticBoundingBox(this.shape.boundingBox); this.addCommandExecutor(RenderCommand, this.draw.bind(this)); } diff --git a/frontend/src/scripts/objects/world/create-character.ts b/frontend/src/scripts/objects/world/create-character.ts deleted file mode 100644 index 56b32b1..0000000 --- a/frontend/src/scripts/objects/world/create-character.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { vec2, vec3 } from 'gl-matrix'; -import { Objects } from '../objects'; -import { Camera } from '../types/camera'; -import { Character } from '../types/character'; -import { Lamp } from '../types/lamp'; -import { Physics } from '../../physics/physics'; -import { GameObject } from '../game-object'; - -export const createCharacter = ( - objects: Objects, - physics: Physics -): GameObject => { - const light = new Lamp( - vec2.create(), - 40, - vec3.fromValues(0.67, 0.0, 0.33), - 2 - ); - - const camera = new Camera(); - const character = new Character(physics, camera, light); - objects.addObject(light); - objects.addObject(camera); - objects.addObject(character); - - return character; -}; diff --git a/frontend/src/scripts/shapes/i-shape.ts b/frontend/src/scripts/shapes/i-shape.ts index 449413b..cbd1d6c 100644 --- a/frontend/src/scripts/shapes/i-shape.ts +++ b/frontend/src/scripts/shapes/i-shape.ts @@ -1,10 +1,13 @@ import { BoundingBox } from './bounding-box'; import { vec2 } from 'gl-matrix'; +import { GameObject } from '../objects/game-object'; export interface IShape { readonly isInverted: boolean; readonly boundingBox: BoundingBox; + readonly gameObject?: GameObject; + distance(target: vec2): number; normal(from: vec2): vec2; diff --git a/frontend/src/scripts/shapes/types/blob.ts b/frontend/src/scripts/shapes/types/blob.ts index 55c7969..9ef4731 100644 --- a/frontend/src/scripts/shapes/types/blob.ts +++ b/frontend/src/scripts/shapes/types/blob.ts @@ -3,6 +3,7 @@ import { IShape } from '../i-shape'; import { BoundingBox } from '../bounding-box'; import { Circle } from './circle'; import { settings } from '../../drawing/settings'; +import { GameObject } from '../../objects/game-object'; export class Blob implements IShape { private static readonly boundingCircleRadius = 19; @@ -31,7 +32,10 @@ export class Blob implements IShape { settings.shaderMacros.footRadius ); - public constructor(center: vec2) { + public constructor( + center: vec2, + public readonly gameObject: GameObject = null + ) { this.position = center; } @@ -68,6 +72,6 @@ export class Blob implements IShape { } public clone(): Blob { - return new Blob(this.boundingCircle.center); + 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.ts index 3f92399..18db443 100644 --- a/frontend/src/scripts/shapes/types/circle.ts +++ b/frontend/src/scripts/shapes/types/circle.ts @@ -1,11 +1,16 @@ import { vec2 } from 'gl-matrix'; import { IShape } from '../i-shape'; import { BoundingBox } from '../bounding-box'; +import { GameObject } from '../../objects/game-object'; export class Circle implements IShape { public readonly isInverted = false; - public constructor(public center = vec2.create(), public radius = 0) {} + public constructor( + public center = vec2.create(), + public radius = 0, + public readonly gameObject: GameObject = null + ) {} public distance(target: vec2): number { return vec2.distance(this.center, target) - this.radius; @@ -36,6 +41,6 @@ export class Circle implements IShape { } public clone(): Circle { - return new Circle(vec2.clone(this.center), this.radius); + return new Circle(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 95d81ab..0fa83e2 100644 --- a/frontend/src/scripts/shapes/types/tunnel-shape.ts +++ b/frontend/src/scripts/shapes/types/tunnel-shape.ts @@ -4,6 +4,7 @@ import { clamp01 } from '../../helper/clamp'; import { mix } from '../../helper/mix'; import { IShape } from '../i-shape'; import { rotate90Deg } from '../../helper/rotate-90-deg'; +import { GameObject } from '../../objects/game-object'; export class TunnelShape implements IShape { public readonly isInverted = true; @@ -14,7 +15,8 @@ export class TunnelShape implements IShape { public readonly from: vec2, public readonly to: vec2, public readonly fromRadius: number, - public readonly toRadius: number + public readonly toRadius: number, + public readonly gameObject: GameObject = null ) { this.toFromDelta = vec2.subtract(vec2.create(), to, from); } @@ -106,7 +108,8 @@ export class TunnelShape implements IShape { vec2.clone(this.from), vec2.clone(this.to), this.fromRadius, - this.toRadius + this.toRadius, + this.gameObject ); } }