diff --git a/frontend/src/scripts/drawing/settings.ts b/frontend/src/scripts/drawing/settings.ts index 34dc169..31c11ed 100644 --- a/frontend/src/scripts/drawing/settings.ts +++ b/frontend/src/scripts/drawing/settings.ts @@ -22,11 +22,11 @@ export const settings = { tileMultiplier: 5, shaderMacros: { distanceScale: 64, - distanceOffset: 0.15, + distanceOffset: 0.0, edgeSmoothing: 10, }, shaderCombinations: { - lineSteps: [0, 1, 2, 3, 4, 8, 16, 32], + lineSteps: [0, 1, 2, 3, 4, 8, 16, 128], circleLightSteps: [0, 1, 2, 3], pointLightSteps: [0, 1, 2, 3], }, diff --git a/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl b/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl index 459ba60..68860a9 100644 --- a/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl +++ b/frontend/src/scripts/drawing/shaders/lights-shading-fs.glsl @@ -58,7 +58,10 @@ void main() { for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { float lightCenterDistance = distance(circleLights[i].center, worldCoordinates); - + /*if (lightCenterDistance < circleLights[i].radius) { + fragmentColor = vec4(1.0, 1.0, 0.0, 1.0); + return; + }*/ vec3 lightColorAtPosition = circleLights[i].value / pow( lightCenterDistance / LIGHT_DROP + 1.0, 2.0 ); diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 07f55fe..66ab72f 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -12,9 +12,9 @@ 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 { MoveToCommand } from './physics/commands/move-to'; import { TeleportToCommand } from './physics/commands/teleport-to'; import { IRenderer } from './drawing/i-renderer'; +import { Random } from './helper/random'; export class Game { private previousTime?: DOMHighResTimeStamp = null; @@ -28,6 +28,8 @@ export class Game { const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); const overlay: HTMLElement = document.querySelector('#overlay'); + Random.seed = 42; + document.addEventListener( 'visibilitychange', this.handleVisibilityChange.bind(this) diff --git a/frontend/src/scripts/helper/random.ts b/frontend/src/scripts/helper/random.ts new file mode 100644 index 0000000..10e93bb --- /dev/null +++ b/frontend/src/scripts/helper/random.ts @@ -0,0 +1,18 @@ +// src +// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript +// Mulberry32 + +export abstract class Random { + private static _seed = Math.random(); + + public static set seed(value: number) { + Random._seed = value; + } + + public static getRandom(): number { + let t = (Random._seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + } +} diff --git a/frontend/src/scripts/helper/rotate-90-deg.ts b/frontend/src/scripts/helper/rotate-90-deg.ts new file mode 100644 index 0000000..1911a63 --- /dev/null +++ b/frontend/src/scripts/helper/rotate-90-deg.ts @@ -0,0 +1,3 @@ +import { vec2 } from 'gl-matrix'; + +export const rotate90Deg = (vec: vec2): vec2 => vec2.fromValues(-vec.y, vec.x); diff --git a/frontend/src/scripts/objects/types/character.ts b/frontend/src/scripts/objects/types/character.ts index 454f482..9327ad5 100644 --- a/frontend/src/scripts/objects/types/character.ts +++ b/frontend/src/scripts/objects/types/character.ts @@ -9,18 +9,21 @@ import { TeleportToCommand } from '../../physics/commands/teleport-to'; import { Physics } from '../../physics/physics'; import { GameObject } from '../game-object'; import { Camera } from './camera'; +import { IShape } from '../../shapes/i-shape'; +import { rotate90Deg } from '../../helper/rotate-90-deg'; +import { max } from 'gl-matrix/src/gl-matrix/vec2'; export class Character extends GameObject { private keysDown: Set = new Set(); - private primitive: Circle; + private shape: Circle; private static speed = 1.5; constructor(private physics: Physics, private camera: Camera) { super(); - this.primitive = new Circle(); - this.primitive.radius = 40; + this.shape = new Circle(); + this.shape.radius = 80; this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); this.addCommandExecutor(TeleportToCommand, (c) => @@ -28,57 +31,100 @@ export class Character extends GameObject { ); this.addCommandExecutor(KeyDownCommand, (c) => this.keysDown.add(c.key)); this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key)); - this.addCommandExecutor(SwipeCommand, (c) => - this.checkAndSetPosition( - vec2.add( - vec2.create(), - this.primitive.center, - vec2.multiply(vec2.create(), c.delta, this.camera.viewAreaSize) - ) - ) - ); + this.addCommandExecutor(SwipeCommand, (c) => { + this.tryMoving( + vec2.multiply(vec2.create(), c.delta, this.camera.viewAreaSize) + ); + }); } - private checkAndSetPosition(value: vec2) { - const nextPrimitive = this.primitive.clone(); - nextPrimitive.center = value; + private tryMoving(delta: vec2, isFirstIteration = true) { + const maxStep = 2; + if (vec2.length(delta) > maxStep) { + let steppedDelta = vec2.normalize(vec2.create(), delta); + vec2.scale(steppedDelta, steppedDelta, maxStep - 0.001); - const intersects = this.physics - .findIntersecting(nextPrimitive.boundingBox) - .filter((b) => b.shape) - .map( - (b) => b.shape.distance(nextPrimitive.center) + nextPrimitive.radius + for (let i = 0; i <= vec2.length(delta) / maxStep - 1; i++) { + this.tryMoving(vec2.clone(steppedDelta), isFirstIteration); + } + + steppedDelta = vec2.normalize(vec2.create(), delta); + + vec2.scale(steppedDelta, steppedDelta, vec2.length(delta) % maxStep); + this.tryMoving(vec2.clone(steppedDelta), isFirstIteration); + + return; + } + + const nextShape = this.shape.clone(); + vec2.add(nextShape.center, nextShape.center, delta); + + const nextNearShapes = this.getNearShapesTo(nextShape); + + if (nextNearShapes.length && nextNearShapes[0].distance < 0) { + this.setPosition(nextShape.center); + } else { + if (!isFirstIteration) { + return; + } + + const currentNearShapes = this.getNearShapesTo(this.shape); + const intersecting = nextNearShapes + .filter( + (n) => + currentNearShapes.find( + (c) => c.shape === n.shape && c.distance <= 0 + ) !== undefined + ) + .sort((e) => Math.abs(e.distance)); + + const normal = intersecting[0].shape.normal(this.shape.center); + + const maxDistance = intersecting.reduce((p, c) => + p.distance > c.distance ? p : c + ).distance; + + vec2.add( + delta, + delta, + vec2.scale(vec2.create(), normal, -maxDistance - 2) ); - console.log(intersects); - if (intersects.find((d) => d < 0) !== undefined) { - this.setPosition(value); + this.tryMoving(delta, false); } } - private setPosition(value: vec2) { - // console.log('character', value); + private getNearShapesTo( + shape: Circle + ): Array<{ shape: IShape; distance: number }> { + return this.physics + .findIntersecting(shape.boundingBox) + .filter((b) => b.shape) + .map((b) => ({ + shape: b.shape, + distance: b.shape.distance(shape.center) + shape.radius, + })) + .sort((e) => e.distance); + } - this.primitive.center = value; - this.camera.sendCommand(new MoveToCommand(this.primitive.center)); + private setPosition(value: vec2) { + this.shape.center = value; + this.camera.sendCommand(new MoveToCommand(this.shape.center)); } public stepHandler(c: StepCommand) { const deltaTime = c.deltaTimeInMiliseconds; - - const up = ~~this.keysDown.has('w'); - const down = ~~this.keysDown.has('s'); - const left = ~~this.keysDown.has('a'); - const right = ~~this.keysDown.has('d'); + const up = ~~(this.keysDown.has('w') || this.keysDown.has('arrowup')); + const down = ~~(this.keysDown.has('s') || this.keysDown.has('arrowdown')); + const left = ~~(this.keysDown.has('a') || this.keysDown.has('arrowleft')); + const right = ~~(this.keysDown.has('d') || this.keysDown.has('arrowright')); const movementVector = vec2.fromValues(right - left, up - down); - if (movementVector.length > 0) { + if (vec2.length(movementVector) > 0) { vec2.normalize(movementVector, movementVector); vec2.scale(movementVector, movementVector, Character.speed * deltaTime); - this.checkAndSetPosition( - vec2.add(vec2.create(), this.primitive.center, movementVector) - ); + this.tryMoving(movementVector); } } } diff --git a/frontend/src/scripts/objects/world/create-dungeon.ts b/frontend/src/scripts/objects/world/create-dungeon.ts index a2b6d94..82ad0fd 100644 --- a/frontend/src/scripts/objects/world/create-dungeon.ts +++ b/frontend/src/scripts/objects/world/create-dungeon.ts @@ -2,6 +2,7 @@ import { vec2 } from 'gl-matrix'; import { Physics } from '../../physics/physics'; import { Objects } from '../objects'; import { Tunnel } from '../types/tunnel'; +import { Random } from '../../helper/random'; export const createDungeon = (objects: Objects, physics: Physics): Tunnel => { let previousRadius = 350; @@ -10,10 +11,10 @@ export const createDungeon = (objects: Objects, physics: Physics): Tunnel => { let first: Tunnel; for (let i = 0; i < 50000; i += 500) { - const deltaHeight = (Math.random() - 0.5) * 2000; + const deltaHeight = (Random.getRandom() - 0.5) * 2000; const height = previousEnd.y + deltaHeight; const currentEnd = vec2.fromValues(i, height); - const currentToRadius = Math.random() * 300 + 150; + const currentToRadius = Random.getRandom() * 300 + 150; const tunnel = new Tunnel( physics, @@ -29,15 +30,15 @@ export const createDungeon = (objects: Objects, physics: Physics): Tunnel => { objects.addObject(tunnel); - /*if (deltaHeight > 0 && Math.random() > 0.8) { + /*if (deltaHeight > 0 && Random.getRandom() > 0.8) { objects.addObject( new Lamp( currentEnd, - Math.random() * 20 + 30, + Random.getRandom() * 20 + 30, vec3.scale( vec3.create(), vec3.normalize(vec3.create(), vec3.fromValues(0.5, 0.1, 0.8)), - Math.random() * 0.5 + 0.5 + Random.getRandom() * 0.5 + 0.5 ), 1 ) diff --git a/frontend/src/scripts/shapes/types/circle.ts b/frontend/src/scripts/shapes/types/circle.ts index 2947203..3f92399 100644 --- a/frontend/src/scripts/shapes/types/circle.ts +++ b/frontend/src/scripts/shapes/types/circle.ts @@ -36,6 +36,6 @@ export class Circle implements IShape { } public clone(): Circle { - return new Circle(this.center, this.radius); + return new Circle(vec2.clone(this.center), this.radius); } } diff --git a/frontend/src/scripts/shapes/types/tunnel-shape.ts b/frontend/src/scripts/shapes/types/tunnel-shape.ts index d48b43b..95d81ab 100644 --- a/frontend/src/scripts/shapes/types/tunnel-shape.ts +++ b/frontend/src/scripts/shapes/types/tunnel-shape.ts @@ -3,6 +3,7 @@ import { BoundingBox } from '../bounding-box'; import { clamp01 } from '../../helper/clamp'; import { mix } from '../../helper/mix'; import { IShape } from '../i-shape'; +import { rotate90Deg } from '../../helper/rotate-90-deg'; export class TunnelShape implements IShape { public readonly isInverted = true; @@ -39,8 +40,49 @@ export class TunnelShape implements IShape { return new BoundingBox(this, xMin, xMax, yMin, yMax); } - public normal(from: vec2): vec2 { - throw new Error('Unimplemented'); + public normal(target: vec2): vec2 { + const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); + + const h = clamp01( + vec2.dot(targetFromDelta, this.toFromDelta) / + vec2.dot(this.toFromDelta, this.toFromDelta) + ); + + let diff = vec2.create(); + + if (h == 1) { + vec2.subtract(diff, target, this.to); + } else if (h == 0) { + vec2.subtract(diff, target, this.from); + } else { + const side = Math.sign( + this.toFromDelta.x * targetFromDelta.y - + this.toFromDelta.y * targetFromDelta.x + ); + + const normal = rotate90Deg(this.toFromDelta); + vec2.normalize(normal, normal); + + const translatedFrom = vec2.add( + vec2.create(), + this.from, + vec2.scale(vec2.create(), normal, side * this.fromRadius) + ); + + const translatedTo = vec2.add( + vec2.create(), + this.to, + vec2.scale(vec2.create(), normal, side * this.toRadius) + ); + + diff = rotate90Deg( + vec2.subtract(vec2.create(), translatedTo, translatedFrom) + ); + + vec2.scale(diff, diff, side); + } + + return vec2.normalize(diff, diff); } public distance(target: vec2): number { @@ -60,6 +102,11 @@ export class TunnelShape implements IShape { } public clone(): TunnelShape { - return new TunnelShape(this.from, this.to, this.fromRadius, this.toRadius); + return new TunnelShape( + vec2.clone(this.from), + vec2.clone(this.to), + this.fromRadius, + this.toRadius + ); } }