diff --git a/backend/src/main.ts b/backend/src/main.ts index 6f11a53..dbe0f6c 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -5,7 +5,7 @@ import cors from 'cors'; import { applyArrayPlugins, Random, TransportEvents, deserializeCommand } from 'shared'; import './index.html'; import { Player } from './players/player'; -import { PhysicalGameObjectContainer } from './physics/physical-game-object-container'; +import { PhysicalContainer } from './physics/containers/physical-container'; import { createDungeon } from './map/create-dungeon'; import { glMatrix } from 'gl-matrix'; @@ -15,7 +15,7 @@ applyArrayPlugins(); Random.seed = 42; -const objects = new PhysicalGameObjectContainer(); +const objects = new PhysicalContainer(); createDungeon(objects); createDungeon(objects); diff --git a/backend/src/map/create-dungeon.ts b/backend/src/map/create-dungeon.ts index 157f52d..8257b66 100644 --- a/backend/src/map/create-dungeon.ts +++ b/backend/src/map/create-dungeon.ts @@ -1,10 +1,10 @@ import { vec2, vec3 } from 'gl-matrix'; import { Random } from 'shared'; -import { LampPhysics } from '../objects/lamp-physics'; -import { TunnelPhysics } from '../objects/tunnel-physics'; -import { PhysicalGameObjectContainer } from '../physics/physical-game-object-container'; +import { LampPhysical } from '../objects/lamp-physical'; +import { TunnelPhysical } from '../objects/tunnel-physical'; +import { PhysicalContainer } from '../physics/containers/physical-container'; -export const createDungeon = (objects: PhysicalGameObjectContainer) => { +export const createDungeon = (objects: PhysicalContainer) => { let previousRadius = 350; let previousEnd = vec2.create(); @@ -16,26 +16,25 @@ export const createDungeon = (objects: PhysicalGameObjectContainer) => { const currentEnd = vec2.fromValues(i, height); const currentToRadius = Random.getRandom() * 300 + 150; - const tunnel = new TunnelPhysics( + const tunnel = new TunnelPhysical( previousEnd, currentEnd, previousRadius, currentToRadius ); - objects.addObject(tunnel, false); + objects.addObject(tunnel); if (++tunnelsCountSinceLastLight > 3 && Random.getRandom() > 0.7) { objects.addObject( - new LampPhysics( + new LampPhysical( currentEnd, vec3.normalize( vec3.create(), vec3.fromValues(Random.getRandom(), 0, Random.getRandom()) ), 0.5 - ), - false + ) ); tunnelsCountSinceLastLight = 0; } diff --git a/backend/src/objects/character-physical.ts b/backend/src/objects/character-physical.ts new file mode 100644 index 0000000..c5ca448 --- /dev/null +++ b/backend/src/objects/character-physical.ts @@ -0,0 +1,94 @@ +import { vec2 } from 'gl-matrix'; +import { id, CharacterBase } from 'shared'; + +import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; + +import { CirclePhysical } from './circle-physical'; +import { Physical } from '../physics/physical'; + +export class CharacterPhysical extends CharacterBase implements Physical { + public readonly canCollide = true; + public readonly isInverted = false; + public readonly canMove = true; + + private static readonly headOffset = vec2.fromValues(0, 40); + private static readonly leftFootOffset = vec2.fromValues(-20, -10); + private static readonly rightFootOffset = vec2.fromValues(20, -10); + + constructor() { + super( + id(), + new CirclePhysical(vec2.clone(CharacterPhysical.headOffset), 50, null), + new CirclePhysical(vec2.clone(CharacterPhysical.leftFootOffset), 20, null), + new CirclePhysical(vec2.clone(CharacterPhysical.rightFootOffset), 20, null) + ); + } + + private _boundingBox?: ImmutableBoundingBox; + + public get boundingBox(): ImmutableBoundingBox { + if (!this._boundingBox) { + this._boundingBox = (this.head as CirclePhysical).boundingBox; + } + + return this._boundingBox; + } + + public get gameObject(): CharacterPhysical { + return this; + } + + // todo + public distance(_: vec2): number { + return 0; + } + /* + public step() { + const movementForce = vec2.fromValues(right - left, up - down); + this.head.applyForce(movementForce, deltaTime); + this.head.applyForce(settings.gravitationalForce, deltaTime); + + const bodyCenter = vec2.sub(vec2.create(), this.head.center, this.headOffset); + + const leftFootPositon = vec2.add(vec2.create(), bodyCenter, this.leftFootOffset); + const rightFootPositon = vec2.add(vec2.create(), bodyCenter, this.rightFootOffset); + + const leftFootDelta = vec2.sub(vec2.create(), this.leftFoot.center, leftFootPositon); + const rightFootDelta = vec2.sub( + vec2.create(), + this.rightFoot.center, + rightFootPositon + ); + + vec2.scale(leftFootDelta, leftFootDelta, 0.0006); + vec2.scale(rightFootDelta, rightFootDelta, 0.0006); + + this.head.applyForce(leftFootDelta, deltaTime); + this.head.applyForce(rightFootDelta, deltaTime); + + vec2.scale(leftFootDelta, leftFootDelta, -0.5); + vec2.scale(rightFootDelta, rightFootDelta, -0.5); + + this.leftFoot.applyForce(movementForce, deltaTime); + this.rightFoot.applyForce(movementForce, deltaTime); + this.leftFoot.applyForce(leftFootDelta, deltaTime); + this.rightFoot.applyForce(rightFootDelta, deltaTime); + this.leftFoot.applyForce(settings.gravitationalForce, deltaTime); + this.rightFoot.applyForce(settings.gravitationalForce, deltaTime); + + this.head.step(deltaTime); + this.leftFoot.step(deltaTime); + this.rightFoot.step(deltaTime); + + this.flashlight.center = vec2.add( + vec2.create(), + this.head.center, + vec2.fromValues(0, 0) + ); + } +*/ + public toJSON(): any { + const { type, id, head, leftFoot, rightFoot } = this; + return [type, id, head, leftFoot, rightFoot]; + } +} diff --git a/backend/src/objects/character-physics.ts b/backend/src/objects/character-physics.ts deleted file mode 100644 index bcd1f45..0000000 --- a/backend/src/objects/character-physics.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { id, CharacterBase } from 'shared'; - -import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box'; -import { PhysicalGameObject } from '../physics/physical-game-object'; -import { CirclePhysics } from './circle-physics'; - -export class CharacterPhysics extends CharacterBase implements PhysicalGameObject { - public readonly canCollide = true; - public readonly isInverted = false; - public readonly canMove = true; - - constructor(head: CirclePhysics, leftFoot: CirclePhysics, rightFoot: CirclePhysics) { - super(id(), head, leftFoot, rightFoot); - } - - private boundingBox?: ImmutableBoundingBox; - - public getBoundingBox(): ImmutableBoundingBox { - if (!this.boundingBox) { - this.boundingBox = (this.head as CirclePhysics).boundingBox; - (this.head as CirclePhysics).boundingBox.owner = this; - } - - return this.boundingBox; - } - - public toJSON(): any { - const { type, id, head, leftFoot, rightFoot } = this; - return [type, id, head, leftFoot, rightFoot]; - } -} diff --git a/backend/src/objects/circle-physical.ts b/backend/src/objects/circle-physical.ts new file mode 100644 index 0000000..32e3f86 --- /dev/null +++ b/backend/src/objects/circle-physical.ts @@ -0,0 +1,143 @@ +import { vec2 } from 'gl-matrix'; +import { Circle, clamp, GameObject, settings } from 'shared'; +import { Physical } from '../physics/physical'; + +import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; +import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base'; + +export class CirclePhysical implements Circle, Physical { + readonly isInverted = false; + readonly canCollide = true; + readonly canMove = true; + + private _isAirborne = true; + + private velocity = vec2.create(); + + public get isAirborne(): boolean { + return this._isAirborne; + } + + private _boundingBox: BoundingBox; + + constructor(private _center: vec2, private _radius: number, private owner: GameObject) { + this._boundingBox = new BoundingBox(null); + this.recalculateBoundingBox(); + } + + public get boundingBox(): BoundingBoxBase { + return this._boundingBox; + } + + public get center(): vec2 { + return this._center; + } + + public set center(value: vec2) { + this._center = value; + this.recalculateBoundingBox(); + } + + public get gameObject(): GameObject { + return this.owner; + } + + public get radius(): number { + return this._radius; + } + + public set radius(value: number) { + this._radius = value; + this.recalculateBoundingBox(); + } + + public distance(target: vec2): number { + return vec2.distance(target, this.center) - this.radius; + } + + public distanceBetween(target: CirclePhysical): number { + return vec2.distance(target.center, this.center) - this.radius - target.radius; + } + + public areIntersecting(other: CirclePhysical): boolean { + return other.distance(this.center) < this.radius; + } + + public isInside(other: CirclePhysical): boolean { + return other.distance(this.center) < -this.radius; + } + + public getPerimeterPoints(count: number): Array { + const result: Array = []; + for (let i = 0; i < count; i++) { + result.push( + vec2.fromValues( + Math.cos((2 * Math.PI * i) / count) * this.radius + this.center.x, + Math.sin((2 * Math.PI * i) / count) * this.radius + this.center.y + ) + ); + } + return result; + } + + private recalculateBoundingBox() { + this._boundingBox.xMin = this.center.x - this._radius; + this._boundingBox.xMax = this.center.x + this._radius; + this._boundingBox.yMin = this.center.y - this._radius; + this._boundingBox.yMax = this.center.y + this._radius; + } + + public applyForce(force: vec2, timeInMilliseconds: number) { + vec2.add( + this.velocity, + this.velocity, + vec2.scale(vec2.create(), force, timeInMilliseconds) + ); + + vec2.set( + this.velocity, + clamp(this.velocity.x, -settings.maxVelocityX, settings.maxVelocityX), + clamp(this.velocity.y, -settings.maxVelocityY, settings.maxVelocityY) + ); + } + + public resetVelocity() { + this.velocity = vec2.create(); + } + + /*public step(timeInMilliseconds: number): boolean { + vec2.scale( + this.velocity, + this.velocity, + Math.pow(settings.velocityAttenuation, timeInMilliseconds) + ); + const distance = vec2.scale(vec2.create(), this.velocity, timeInMilliseconds); + const distanceLength = vec2.length(distance); + const stepCount = Math.ceil(distanceLength / settings.physicsMaxStep); + vec2.scale(distance, distance, 1 / stepCount); + + let wasHit = false; + + for (let i = 0; i < stepCount; i++) { + const { normal, tangent, hitSurface } = this.physics.tryMovingDynamicCircle( + this, + distance + ); + if (hitSurface) { + vec2.scale(this.velocity, tangent, vec2.dot(tangent, this.velocity)); + wasHit = true; + } + + this._isAirborne = !( + hitSurface && vec2.dot(normal, settings.gravitationalForce) < 0 + ); + } + + return wasHit; + }*/ + + public toJSON(): any { + const { center, radius } = this; + return { center, radius }; + } +} diff --git a/backend/src/objects/circle-physics.ts b/backend/src/objects/circle-physics.ts deleted file mode 100644 index f543480..0000000 --- a/backend/src/objects/circle-physics.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { vec2 } from 'gl-matrix'; -import { Circle } from 'shared'; -import { BoundingBox } from '../physics/bounds/bounding-box'; -import { BoundingBoxBase } from '../physics/bounds/bounding-box-base'; - -export class CirclePhysics implements Circle { - private _boundingBox: BoundingBox; - - constructor(private _center: vec2, private _radius: number) { - this._boundingBox = new BoundingBox(null); - this.recalculateBoundingBox(); - } - - public get boundingBox(): BoundingBoxBase { - return this._boundingBox; - } - - public get center(): vec2 { - return this._center; - } - - public set center(value: vec2) { - this._center = value; - this.recalculateBoundingBox(); - } - - public get radius(): number { - return this._radius; - } - - public set radius(value: number) { - this._radius = value; - this.recalculateBoundingBox(); - } - - public distance(target: vec2): number { - return vec2.distance(target, this.center) - this.radius; - } - - public distanceBetween(target: CirclePhysics): number { - return vec2.distance(target.center, this.center) - this.radius - target.radius; - } - - public areIntersecting(other: CirclePhysics): boolean { - return other.distance(this.center) < this.radius; - } - - public isInside(other: CirclePhysics): boolean { - return other.distance(this.center) < -this.radius; - } - - public getPerimeterPoints(count: number): Array { - const result: Array = []; - for (let i = 0; i < count; i++) { - result.push( - vec2.fromValues( - Math.cos((2 * Math.PI * i) / count) * this.radius + this.center.x, - Math.sin((2 * Math.PI * i) / count) * this.radius + this.center.y - ) - ); - } - return result; - } - - private recalculateBoundingBox() { - this._boundingBox.xMin = this.center.x - this._radius; - this._boundingBox.xMax = this.center.x + this._radius; - this._boundingBox.yMin = this.center.y - this._radius; - this._boundingBox.yMax = this.center.y + this._radius; - } - - public toJSON(): any { - const { center, radius } = this; - return { center, radius }; - } -} diff --git a/backend/src/objects/lamp-physics.ts b/backend/src/objects/lamp-physical.ts similarity index 55% rename from backend/src/objects/lamp-physics.ts rename to backend/src/objects/lamp-physical.ts index 3947a57..c9e2994 100644 --- a/backend/src/objects/lamp-physics.ts +++ b/backend/src/objects/lamp-physical.ts @@ -1,10 +1,11 @@ import { vec2, vec3 } from 'gl-matrix'; import { LampBase, settings, id } from 'shared'; -import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box'; -import { PhysicalGameObject } from '../physics/physical-game-object'; +import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; -export class LampPhysics extends LampBase implements PhysicalGameObject { +import { Physical } from '../physics/physical'; + +export class LampPhysical extends LampBase implements Physical { public readonly canCollide = false; public readonly isInverted = false; public readonly canMove = false; @@ -13,12 +14,11 @@ export class LampPhysics extends LampBase implements PhysicalGameObject { super(id(), center, color, lightness); } - private boundingBox?: ImmutableBoundingBox; + private _boundingBox?: ImmutableBoundingBox; - public getBoundingBox(): ImmutableBoundingBox { - if (!this.boundingBox) { - this.boundingBox = new ImmutableBoundingBox( - this, + public get boundingBox(): ImmutableBoundingBox { + if (!this._boundingBox) { + this._boundingBox = new ImmutableBoundingBox( this.center.x - settings.lightCutoffDistance, this.center.x + settings.lightCutoffDistance, this.center.y - settings.lightCutoffDistance, @@ -26,7 +26,16 @@ export class LampPhysics extends LampBase implements PhysicalGameObject { ); } - return this.boundingBox; + return this._boundingBox; + } + + public get gameObject(): LampPhysical { + return this; + } + + // todo + public distance(_: vec2): number { + return 0; } public toJSON(): any { diff --git a/backend/src/objects/tunnel-physics.ts b/backend/src/objects/tunnel-physical.ts similarity index 70% rename from backend/src/objects/tunnel-physics.ts rename to backend/src/objects/tunnel-physical.ts index b89c68e..b1e3400 100644 --- a/backend/src/objects/tunnel-physics.ts +++ b/backend/src/objects/tunnel-physical.ts @@ -1,15 +1,15 @@ import { vec2 } from 'gl-matrix'; import { clamp01, mix, TunnelBase, id } from 'shared'; -import { PhysicalGameObject } from '../physics/physical-game-object'; -import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box'; +import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box'; +import { StaticPhysical } from '../physics/containers/static-physical-object'; -export class TunnelPhysics extends TunnelBase implements PhysicalGameObject { +export class TunnelPhysical extends TunnelBase implements StaticPhysical { public readonly canCollide = true; public readonly isInverted = true; public readonly canMove = false; - private boundingBox?: ImmutableBoundingBox; + private _boundingBox?: ImmutableBoundingBox; constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) { super(id(), from, to, fromRadius, toRadius); @@ -29,16 +29,20 @@ export class TunnelPhysics extends TunnelBase implements PhysicalGameObject { ); } - public getBoundingBox(): ImmutableBoundingBox { - if (!this.boundingBox) { + public get boundingBox(): ImmutableBoundingBox { + if (!this._boundingBox) { const xMin = Math.min(this.from.x - this.fromRadius, this.to.x - this.toRadius); const yMin = Math.min(this.from.y - this.fromRadius, this.to.y - this.toRadius); const xMax = Math.max(this.from.x + this.fromRadius, this.to.x + this.toRadius); const yMax = Math.max(this.from.y + this.fromRadius, this.to.y + this.toRadius); - this.boundingBox = new ImmutableBoundingBox(this, xMin, xMax, yMin, yMax, true); + this._boundingBox = new ImmutableBoundingBox(xMin, xMax, yMin, yMax); } - return this.boundingBox; + return this._boundingBox; + } + + public get gameObject(): TunnelPhysical { + return this; } public toJSON(): any { diff --git a/backend/src/physics/bounds/bounding-box-base.ts b/backend/src/physics/bounding-boxes/bounding-box-base.ts similarity index 86% rename from backend/src/physics/bounds/bounding-box-base.ts rename to backend/src/physics/bounding-boxes/bounding-box-base.ts index 3a62165..713db3a 100644 --- a/backend/src/physics/bounds/bounding-box-base.ts +++ b/backend/src/physics/bounding-boxes/bounding-box-base.ts @@ -1,14 +1,11 @@ import { vec2 } from 'gl-matrix'; -import { PhysicalGameObject } from '../physical-game-object'; export abstract class BoundingBoxBase { constructor( - public owner: PhysicalGameObject, protected _xMin: number = 0, protected _xMax: number = 0, protected _yMin: number = 0, - protected _yMax: number = 0, - public readonly isInverted = false + protected _yMax: number = 0 ) {} public get 0(): number { diff --git a/backend/src/physics/bounds/bounding-box.ts b/backend/src/physics/bounding-boxes/bounding-box.ts similarity index 89% rename from backend/src/physics/bounds/bounding-box.ts rename to backend/src/physics/bounding-boxes/bounding-box.ts index 23bf4f7..562794f 100644 --- a/backend/src/physics/bounds/bounding-box.ts +++ b/backend/src/physics/bounding-boxes/bounding-box.ts @@ -54,12 +54,6 @@ export class BoundingBox extends BoundingBoxBase { } public cloneAsImmutable(): ImmutableBoundingBox { - return new ImmutableBoundingBox( - this.owner, - this.xMin, - this.xMax, - this.yMin, - this.yMax - ); + return new ImmutableBoundingBox(this.xMin, this.xMax, this.yMin, this.yMax); } } diff --git a/backend/src/physics/bounds/immutable-bounding-box.ts b/backend/src/physics/bounding-boxes/immutable-bounding-box.ts similarity index 100% rename from backend/src/physics/bounds/immutable-bounding-box.ts rename to backend/src/physics/bounding-boxes/immutable-bounding-box.ts diff --git a/backend/src/physics/containers/bounding-box-list.ts b/backend/src/physics/containers/bounding-box-list.ts index eda5f4f..d9a5cec 100644 --- a/backend/src/physics/containers/bounding-box-list.ts +++ b/backend/src/physics/containers/bounding-box-list.ts @@ -1,20 +1,21 @@ -import { BoundingBoxBase } from '../bounds/bounding-box-base'; +import { Physical } from '../physical'; +import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; export class BoundingBoxList { - constructor(private boundingBoxes: Array = []) {} + constructor(private objects: Array = []) {} - public insert(box: BoundingBoxBase) { - this.boundingBoxes.push(box); + public insert(object: Physical) { + this.objects.push(object); } - public remove(box: BoundingBoxBase) { - this.boundingBoxes.splice( - this.boundingBoxes.findIndex((i) => i === box), + public remove(object: Physical) { + this.objects.splice( + this.objects.findIndex((i) => i === object), 1 ); } - public findIntersecting(box: BoundingBoxBase): Array { - return this.boundingBoxes.filter((b) => b.intersects(box)); + public findIntersecting(boundingBox: BoundingBoxBase): Array { + return this.objects.filter((b) => b.boundingBox.intersects(boundingBox)); } } diff --git a/backend/src/physics/containers/bounding-box-tree.ts b/backend/src/physics/containers/bounding-box-tree.ts index d469be2..90b3dff 100644 --- a/backend/src/physics/containers/bounding-box-tree.ts +++ b/backend/src/physics/containers/bounding-box-tree.ts @@ -1,60 +1,60 @@ +import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; +import { StaticPhysical } from './static-physical-object'; // source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js -import { ImmutableBoundingBox } from '../bounds/immutable-bounding-box'; - class Node { public left?: Node = null; public right?: Node = null; - constructor(public rectangle: ImmutableBoundingBox, public parent: Node) {} + constructor(public object: StaticPhysical, public parent: Node) {} } export class BoundingBoxTree { root?: Node; - constructor(boxes: Array = []) { - this.build(boxes); + constructor(objects: Array = []) { + this.build(objects); } - public build(boxes: Array) { - this.root = this.buildRecursive(boxes, 0, null); + public build(objects: Array) { + this.root = this.buildRecursive(objects, 0, null); } private buildRecursive( - boxes: Array, + objects: Array, depth: number, parent: Node ): Node { - if (boxes.length === 0) { + if (objects.length === 0) { return null; } - if (boxes.length === 1) { - return new Node(boxes[0], parent); + if (objects.length === 1) { + return new Node(objects[0], parent); } const dimension = depth % 4; - boxes.sort((a, b) => a[dimension] - b[dimension]); + objects.sort((a, b) => a.boundingBox[dimension] - b.boundingBox[dimension]); - const median = Math.floor(boxes.length / 2); + const median = Math.floor(objects.length / 2); - const node = new Node(boxes[median], parent); - node.left = this.buildRecursive(boxes.slice(0, median), depth + 1, node); - node.right = this.buildRecursive(boxes.slice(median + 1), depth + 1, node); + const node = new Node(objects[median], parent); + node.left = this.buildRecursive(objects.slice(0, median), depth + 1, node); + node.right = this.buildRecursive(objects.slice(median + 1), depth + 1, node); return node; } - public insert(box: ImmutableBoundingBox) { - const [insertPosition, depth] = this.findParent(box, this.root, 0, null); + public insert(object: StaticPhysical) { + const [insertPosition, depth] = this.findParent(object, this.root, 0, null); if (insertPosition === null) { - this.root = new Node(box, null); + this.root = new Node(object, null); } else { - const node = new Node(box, insertPosition); + const node = new Node(object, insertPosition); const dimension = depth % 4; - if (box[dimension] < insertPosition.rectangle[dimension]) { + if (object.boundingBox[dimension] < insertPosition.object.boundingBox[dimension]) { insertPosition.left = node; } else { insertPosition.right = node; @@ -62,46 +62,46 @@ export class BoundingBoxTree { } } - public findIntersecting(box: ImmutableBoundingBox): Array { - const maybeResults = this.findMaybeIntersecting(box, this.root, 0); - const results = maybeResults.filter((b) => b.intersects(box)); + public findIntersecting(boundingBox: BoundingBoxBase): Array { + const maybeResults = this.findMaybeIntersecting(boundingBox, this.root, 0); + const results = maybeResults.filter((b) => b.boundingBox.intersects(boundingBox)); return results; } private findMaybeIntersecting( - box: ImmutableBoundingBox, + boundingBox: BoundingBoxBase, node: Node, depth: number - ): Array { + ): Array { if (node === null) { return []; } - if (depth % 4 == 0 && box.xMax < node.rectangle.xMin) { - return this.findMaybeIntersecting(box, node.left, depth + 1); + if (depth % 4 == 0 && boundingBox.xMax < node.object.boundingBox.xMin) { + return this.findMaybeIntersecting(boundingBox, node.left, depth + 1); } - if (depth % 4 == 1 && box.xMin > node.rectangle.xMax) { - return this.findMaybeIntersecting(box, node.right, depth + 1); + if (depth % 4 == 1 && boundingBox.xMin > node.object.boundingBox.xMax) { + return this.findMaybeIntersecting(boundingBox, node.right, depth + 1); } - if (depth % 4 == 2 && box.yMax < node.rectangle.yMin) { - return this.findMaybeIntersecting(box, node.left, depth + 1); + if (depth % 4 == 2 && boundingBox.yMax < node.object.boundingBox.yMin) { + return this.findMaybeIntersecting(boundingBox, node.left, depth + 1); } - if (depth % 4 == 3 && box.yMin > node.rectangle.yMax) { - return this.findMaybeIntersecting(box, node.right, depth + 1); + if (depth % 4 == 3 && boundingBox.yMin > node.object.boundingBox.yMax) { + return this.findMaybeIntersecting(boundingBox, node.right, depth + 1); } return [ - node.rectangle, - ...this.findMaybeIntersecting(box, node.left, depth + 1), - ...this.findMaybeIntersecting(box, node.right, depth + 1), + node.object, + ...this.findMaybeIntersecting(boundingBox, node.left, depth + 1), + ...this.findMaybeIntersecting(boundingBox, node.right, depth + 1), ]; } private findParent( - box: ImmutableBoundingBox, + object: StaticPhysical, node: Node, depth: number, parent: Node @@ -112,10 +112,10 @@ export class BoundingBoxTree { const dimension = depth % 4; - if (box[dimension] < node.rectangle[dimension]) { - return this.findParent(box, node.left, depth + 1, node); + if (object.boundingBox[dimension] < node.object.boundingBox[dimension]) { + return this.findParent(object, node.left, depth + 1, node); } - return this.findParent(box, node.right, depth + 1, node); + return this.findParent(object, node.right, depth + 1, node); } } diff --git a/backend/src/physics/physical-game-object-container.ts b/backend/src/physics/containers/physical-container.ts similarity index 55% rename from backend/src/physics/physical-game-object-container.ts rename to backend/src/physics/containers/physical-container.ts index c6814d6..2ce9ba4 100644 --- a/backend/src/physics/physical-game-object-container.ts +++ b/backend/src/physics/containers/physical-container.ts @@ -1,15 +1,15 @@ import { GameObject, Id } from 'shared'; -import { BoundingBoxBase } from './bounds/bounding-box-base'; +import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base'; -import { BoundingBoxList } from './containers/bounding-box-list'; -import { BoundingBoxTree } from './containers/bounding-box-tree'; +import { BoundingBoxList } from './bounding-box-list'; +import { BoundingBoxTree } from './bounding-box-tree'; import { Command } from 'shared'; -import { PhysicalGameObject } from './physical-game-object'; -import { ImmutableBoundingBox } from './bounds/immutable-bounding-box'; +import { Physical } from '../physical'; +import { StaticPhysical } from './static-physical-object'; -export class PhysicalGameObjectContainer { +export class PhysicalContainer { private isTreeInitialized = false; - private staticBoundingBoxesWaitList: Array = []; + private staticBoundingBoxesWaitList: Array = []; private staticBoundingBoxes = new BoundingBoxTree(); private dynamicBoundingBoxes = new BoundingBoxList(); @@ -21,40 +21,44 @@ export class PhysicalGameObjectContainer { this.isTreeInitialized = true; } - public addObject(object: PhysicalGameObject, isDynamic) { - this.objects.set(object.id, object); + public addObject(object: Physical) { + this.objects.set(object.gameObject.id, object.gameObject); for (const command of this.objectsGroupedByAbilities.keys()) { - if (object.reactsToCommand(command)) { - this.objectsGroupedByAbilities.get(command).push(object); + if (object.gameObject.reactsToCommand(command)) { + this.objectsGroupedByAbilities.get(command).push(object.gameObject); } } - if (isDynamic) { - this.dynamicBoundingBoxes.insert(object.getBoundingBox()); + this.addPhysical(object); + } + + public addPhysical(physical: Physical) { + if (physical.canMove) { + this.dynamicBoundingBoxes.insert(physical); } else { if (!this.isTreeInitialized) { - this.staticBoundingBoxesWaitList.push(object.getBoundingBox()); + this.staticBoundingBoxesWaitList.push(physical); } else { - this.staticBoundingBoxes.insert(object.getBoundingBox()); + this.staticBoundingBoxes.insert(physical); } } } - public removeObject(object: PhysicalGameObject) { - this.objects.delete(object.id); + public removeObject(object: Physical) { + this.objects.delete(object.gameObject.id); for (const command of this.objectsGroupedByAbilities.keys()) { - if (object.reactsToCommand(command)) { + if (object.gameObject.reactsToCommand(command)) { const array = this.objectsGroupedByAbilities.get(command); array.splice( - array.findIndex((i) => i.id == object.id), + array.findIndex((i) => i.id == object.gameObject.id), 1 ); } } - this.dynamicBoundingBoxes.remove(object.getBoundingBox()); + this.dynamicBoundingBoxes.remove(object); } public sendCommand(e: Command) { @@ -77,10 +81,10 @@ export class PhysicalGameObjectContainer { this.objectsGroupedByAbilities.set(commandType, objectsReactingToCommand); } - public findIntersecting(box: BoundingBoxBase): Array { + public findIntersecting(box: BoundingBoxBase): Array { return [ ...this.staticBoundingBoxes.findIntersecting(box), ...this.dynamicBoundingBoxes.findIntersecting(box), - ].map((b) => b.owner); + ]; } } diff --git a/backend/src/physics/containers/static-physical-object.ts b/backend/src/physics/containers/static-physical-object.ts new file mode 100644 index 0000000..2ad7303 --- /dev/null +++ b/backend/src/physics/containers/static-physical-object.ts @@ -0,0 +1,6 @@ +import { Physical } from '../physical'; +import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box'; + +export interface StaticPhysical extends Physical { + readonly boundingBox: ImmutableBoundingBox; +} diff --git a/backend/src/physics/move-circle.ts b/backend/src/physics/move-circle.ts new file mode 100644 index 0000000..dc6aa83 --- /dev/null +++ b/backend/src/physics/move-circle.ts @@ -0,0 +1,82 @@ +/*import { vec2 } from 'gl-matrix'; +import { rotate90Deg } from 'shared'; +import { CirclePhysics } from './bounds/circle-physics'; +import { PhysicalGameObject } from './physical-game-object'; + +export const moveCircle = ( + circle: CirclePhysics, + delta: vec2, + possibleIntersectors: Array +): { + realDelta: vec2; + hitSurface: boolean; + normal?: vec2; + tangent?: vec2; +} => { + circle.center = vec2.add(circle.center, circle.center, delta); + + const intersecting = possibleIntersectors.filter( + (b) => b !== circle && circle.areIntersecting(b) && b.canCollide + ); + + if (intersecting.length === 0) { + return { + realDelta: delta, + hitSurface: false, + }; + } + + const points = circle.getPerimeterPoints(settings.hitDetectionCirclePointCount); + + const distancesOfPoints = points + .map((point) => ({ + point, + closest: intersecting + .map((i) => ({ + inverted: i.isInverted, + distance: i.owner.distance(point), + })) + .sort((a, b) => a.distance - b.distance)[0], + })) + .filter((i) => i.closest); + + const distancesOfIntersectingPoints = distancesOfPoints.filter( + (d) => + (d.closest.distance > 0 && d.closest.inverted) || + (d.closest.distance < 0 && !d.closest.inverted) + ); + + if (distancesOfIntersectingPoints.length === 0) { + return { + realDelta: delta, + hitSurface: false, + }; + } + + const deltas = distancesOfIntersectingPoints.map((pointDistance) => { + vec2.subtract(pointDistance.point, circle.center, pointDistance.point); + vec2.normalize(pointDistance.point, pointDistance.point); + vec2.scale( + pointDistance.point, + pointDistance.point, + (pointDistance.closest.inverted ? 1 : -1) * pointDistance.closest.distance + ); + return pointDistance.point; + }); + + const approxNormal = deltas.reduce( + (sum, current) => vec2.add(sum, sum, current), + vec2.create() + ); + vec2.scale(approxNormal, approxNormal, 1 / deltas.length); + + circle.center = vec2.add(circle.center, circle.center, approxNormal); + + return { + realDelta: delta, + hitSurface: true, + normal: vec2.normalize(approxNormal, approxNormal), + tangent: rotate90Deg(approxNormal), + }; +}; +*/ diff --git a/backend/src/physics/physical-game-object.ts b/backend/src/physics/physical-game-object.ts deleted file mode 100644 index a359e02..0000000 --- a/backend/src/physics/physical-game-object.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { GameObject } from 'shared'; -import { BoundingBoxBase } from './bounds/bounding-box-base'; - -export interface PhysicalGameObject extends GameObject { - getBoundingBox(): BoundingBoxBase; - //distance(target: vec2): number; - readonly isInverted: boolean; - readonly canCollide: boolean; - readonly canMove: boolean; -} diff --git a/backend/src/physics/physical.ts b/backend/src/physics/physical.ts new file mode 100644 index 0000000..0948fd3 --- /dev/null +++ b/backend/src/physics/physical.ts @@ -0,0 +1,13 @@ +import { vec2 } from 'gl-matrix'; +import { GameObject } from 'shared'; +import { BoundingBoxBase } from './bounding-boxes/bounding-box-base'; + +export interface Physical { + readonly isInverted: boolean; + readonly canCollide: boolean; + readonly canMove: boolean; + readonly boundingBox: BoundingBoxBase; + readonly gameObject: GameObject; + + distance(target: vec2): number; +} diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index 1eeae67..0517b65 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -11,25 +11,20 @@ import { TransportEvents, UpdateObjectsCommand, } from 'shared'; -import { CharacterPhysics } from '../objects/character-physics'; -import { CirclePhysics } from '../objects/circle-physics'; +import { CharacterPhysical } from '../objects/character-physical'; -import { BoundingBox } from '../physics/bounds/bounding-box'; -import { PhysicalGameObject } from '../physics/physical-game-object'; -import { PhysicalGameObjectContainer } from '../physics/physical-game-object-container'; +import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; +import { PhysicalContainer } from '../physics/containers/physical-container'; +import { Physical } from '../physics/physical'; import { jsonSerialize } from '../serialize'; export class Player extends CommandReceiver { public isActive = true; - private character: CharacterPhysics = new CharacterPhysics( - new CirclePhysics(vec2.fromValues(50, 50), 50), - new CirclePhysics(vec2.fromValues(50, 50), 50), - new CirclePhysics(vec2.fromValues(50, 50), 50) - ); + private character: CharacterPhysical = new CharacterPhysical(); - private objectsPreviouslyInViewArea: Array = []; - private objectsInViewArea: Array = []; + private objectsPreviouslyInViewArea: Array = []; + private objectsInViewArea: Array = []; protected commandExecutors: CommandExecutors = { [SetViewAreaActionCommand.type]: this.setViewArea.bind(this), @@ -47,14 +42,14 @@ export class Player extends CommandReceiver { protected defaultCommandExecutor(command: Command) {} constructor( - private readonly objects: PhysicalGameObjectContainer, + private readonly objects: PhysicalContainer, private readonly socket: SocketIO.Socket ) { super(); this.objectsPreviouslyInViewArea.push(this.character); this.objectsInViewArea.push(this.character); - this.objects.addObject(this.character, true); + this.objects.addObject(this.character); socket.emit( TransportEvents.ServerToPlayer, @@ -86,14 +81,20 @@ export class Player extends CommandReceiver { if (noLongerIntersecting.length > 0) { this.socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize(new DeleteObjectsCommand(noLongerIntersecting.map((o) => o.id))) + jsonSerialize( + new DeleteObjectsCommand(noLongerIntersecting.map((p) => p.gameObject.id)) + ) ); } if (newlyIntersecting.length > 0) { this.socket.emit( TransportEvents.ServerToPlayer, - jsonSerialize(new CreateObjectsCommand(jsonSerialize(newlyIntersecting))) + jsonSerialize( + new CreateObjectsCommand( + jsonSerialize(newlyIntersecting.map((p) => p.gameObject)) + ) + ) ); } diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 9979b81..b5e368a 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -1,10 +1,8 @@ import { glMatrix } from 'gl-matrix'; import { Game } from './scripts/game'; -import { Random } from './scripts/helper/random'; import './styles/main.scss'; glMatrix.setMatrixArrayType(Array); -Random.seed = 42; const main = async () => { try { diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 41a4e67..50483a1 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -10,7 +10,13 @@ import { renderNoise, WrapOptions, } from 'sdf-2d'; -import { broadcastCommands, SetViewAreaActionCommand, TransportEvents } from 'shared'; +import { + broadcastCommands, + prettyPrint, + settings, + SetViewAreaActionCommand, + TransportEvents, +} from 'shared'; import io from 'socket.io-client'; import { KeyboardListener } from './commands/generators/keyboard-listener'; import { MouseListener } from './commands/generators/mouse-listener'; @@ -20,10 +26,8 @@ import { RenderCommand } from './commands/types/render'; import { StepCommand } from './commands/types/step'; import { Configuration } from './config/configuration'; import { DeltaTimeCalculator } from './helper/delta-time-calculator'; -import { prettyPrint } from './helper/pretty-print'; import { rgb } from './helper/rgb'; import { GameObjectContainer } from './objects/game-object-container'; -import { settings } from './settings'; import { BlobShape } from './shapes/blob-shape'; import { deserialize } from './transport/deserialize'; @@ -69,7 +73,7 @@ export class Game { } private async setupRenderer(): Promise { - const noiseTexture = await renderNoise([1024, 1], 60, 1 / 8); + const noiseTexture = await renderNoise([512, 1], 50, 1 / 10); this.renderer = await compile( this.canvas, @@ -96,7 +100,7 @@ export class Game { }, ], { - shadowTraceCount: 12, + shadowTraceCount: 16, paletteSize: 10, enableStopwatch: true, } diff --git a/frontend/src/scripts/helper/clamp.ts b/frontend/src/scripts/helper/clamp.ts deleted file mode 100644 index 45da555..0000000 --- a/frontend/src/scripts/helper/clamp.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const clamp = (value: number, min: number, max: number): number => - Math.min(max, Math.max(min, value)); - -export const clamp01 = (value: number): number => Math.min(1, Math.max(0, value)); diff --git a/frontend/src/scripts/helper/last.ts b/frontend/src/scripts/helper/last.ts deleted file mode 100644 index 5c319b0..0000000 --- a/frontend/src/scripts/helper/last.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function last(a: Array): T | null { - return a.length > 0 ? a[a.length - 1] : null; -} diff --git a/frontend/src/scripts/helper/mix.ts b/frontend/src/scripts/helper/mix.ts deleted file mode 100644 index 16a76ed..0000000 --- a/frontend/src/scripts/helper/mix.ts +++ /dev/null @@ -1 +0,0 @@ -export const mix = (from: number, to: number, q: number) => from + (to - from) * q; diff --git a/frontend/src/scripts/helper/pretty-print.ts b/frontend/src/scripts/helper/pretty-print.ts deleted file mode 100644 index 704a6fc..0000000 --- a/frontend/src/scripts/helper/pretty-print.ts +++ /dev/null @@ -1,4 +0,0 @@ -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/random.ts b/frontend/src/scripts/helper/random.ts deleted file mode 100644 index 10e93bb..0000000 --- a/frontend/src/scripts/helper/random.ts +++ /dev/null @@ -1,18 +0,0 @@ -// 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 deleted file mode 100644 index 1911a63..0000000 --- a/frontend/src/scripts/helper/rotate-90-deg.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { vec2 } from 'gl-matrix'; - -export const rotate90Deg = (vec: vec2): vec2 => vec2.fromValues(-vec.y, vec.x); diff --git a/frontend/src/scripts/helper/to-percent.ts b/frontend/src/scripts/helper/to-percent.ts deleted file mode 100644 index 0047591..0000000 --- a/frontend/src/scripts/helper/to-percent.ts +++ /dev/null @@ -1 +0,0 @@ -export const toPercent = (value: number) => `${Math.round(value * 100)}%`; diff --git a/frontend/src/scripts/helper/wait.ts b/frontend/src/scripts/helper/wait.ts deleted file mode 100644 index 73dd2c2..0000000 --- a/frontend/src/scripts/helper/wait.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const wait = (ms: number): Promise => { - return new Promise((resolve, _) => setTimeout(resolve, ms)); -}; diff --git a/frontend/src/scripts/objects/types/character.ts b/frontend/src/scripts/objects/types/character.ts deleted file mode 100644 index 10da8c0..0000000 --- a/frontend/src/scripts/objects/types/character.ts +++ /dev/null @@ -1,181 +0,0 @@ -/*import { vec2 } from 'gl-matrix'; -import { Flashlight } from 'sdf-2d'; -import { rgb } from '../../helper/rgb'; -import { IGame } from '../../i-game'; -import { BoundingBoxBase } from '../../physics/bounds/bounding-box-base'; -import { BoundingCircle } from '../../physics/bounds/bounding-circle'; -import { PhysicsCircle } from '../../physics/bounds/physics-circle'; -import { PhysicalGameObject } from '../../physics/physical-object'; -import { Physics } from '../../physics/physics'; -import { settings } from '../../settings'; -import { BlobShape } from '../../shapes/blob-shape'; -import { Projectile } from './projectile'; - -export class Character extends PhysicalGameObject { - protected head: PhysicsCircle; - protected leftFoot: PhysicsCircle; - protected rightFoot: PhysicsCircle; - - private keysDown: Set = new Set(); - - private flashlight = new Flashlight( - vec2.create(), - rgb(1, 0.6, 0.45), - 0.15, - vec2.fromValues(1, 0), - 50 - ); - - private static walkForce = 0.005; - private static jumpForce = 10; - - private shape = new BlobShape(); - private boundingCircle = new BoundingCircle(this, vec2.create(), 50); - - private readonly headOffset = vec2.fromValues(0, 40); - private readonly leftFootOffset = vec2.fromValues(-20, -10); - private readonly rightFootOffset = vec2.fromValues(20, -10); - - constructor(physics: Physics, private game: IGame) { - super(physics, true); - - this.addCommandExecutor(RenderCommand, this.draw.bind(this)); - this.addCommandExecutor(TeleportToCommand, (c) => this.setPosition(c.position)); - this.addCommandExecutor(KeyDownCommand, (c) => this.keysDown.add(c.key)); - this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key)); - this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); - this.addCommandExecutor(CursorMoveCommand, this.directLight.bind(this)); - this.addCommandExecutor(PrimaryActionCommand, this.spawnProjectile.bind(this)); - this.addCommandExecutor(SwipeCommand, (c) => { - //this.tryMoving(vec2.multiply(vec2.create(), c.delta, this.game.viewArea.size)); - }); - - this.head = new PhysicsCircle(physics, this, vec2.clone(this.headOffset), 50); - this.leftFoot = new PhysicsCircle(physics, this, vec2.clone(this.leftFootOffset), 20); - this.rightFoot = new PhysicsCircle( - physics, - this, - vec2.clone(this.rightFootOffset), - 20 - ); - - this.shape.setCircles([this.head, this.leftFoot, this.rightFoot]); - - this.addToPhysics(true); - } - - public distance(target: vec2): number { - return this.shape.minDistance(target); - } - - private draw(c: RenderCommand) { - c.renderer.addDrawable(this.shape); - c.renderer.addDrawable(this.flashlight); - } - - private directLight(e: CursorMoveCommand) { - const pos = this.game.displayToWorldCoordinates(e.position); - vec2.sub(pos, pos, this.head.center); - this.flashlight.direction = pos; - } - - private spawnProjectile(e: PrimaryActionCommand) { - const pos = this.game.displayToWorldCoordinates(e.position); - const direction = vec2.sub(vec2.create(), pos, this.head.center); - vec2.normalize(direction, direction); - const start = vec2.add( - vec2.create(), - this.head.center, - vec2.scale(vec2.create(), direction, this.head.radius) - ); - - vec2.scale(direction, direction, 5); - - const projectile = new Projectile(this.game, this.physics, start, direction); - this.game.addObject(projectile); - } - - public get position(): vec2 { - return this.head.center; - } - - public getBoundingCircles(): Array { - return [this.boundingCircle]; - } - - public getBoundingBox(): BoundingBoxBase { - return this.head.boundingBox; - } - - private setPosition(value: vec2) { - this.head.center = vec2.clone(value); - this.leftFoot.center = vec2.clone(value); - this.rightFoot.center = vec2.clone(value); - } - - public stepHandler(c: StepCommand) { - const deltaTime = c.deltaTimeInMiliseconds; - - const isAirborne = this.leftFoot.isAirborne && this.rightFoot.isAirborne; - const up = - ~~( - !isAirborne && - (this.keysDown.has('w') || this.keysDown.has('arrowup') || this.keysDown.has(' ')) - ) * Character.jumpForce; - const down = ~~( - !isAirborne && - (this.keysDown.has('s') || this.keysDown.has('arrowdown')) - ); - const left = - ~~(this.keysDown.has('a') || this.keysDown.has('arrowleft')) * - Character.walkForce * - deltaTime; - const right = - ~~(this.keysDown.has('d') || this.keysDown.has('arrowright')) * - Character.walkForce * - deltaTime; - - const movementForce = vec2.fromValues(right - left, up - down); - this.head.applyForce(movementForce, deltaTime); - this.head.applyForce(settings.gravitationalForce, deltaTime); - - const bodyCenter = vec2.sub(vec2.create(), this.head.center, this.headOffset); - - const leftFootPositon = vec2.add(vec2.create(), bodyCenter, this.leftFootOffset); - const rightFootPositon = vec2.add(vec2.create(), bodyCenter, this.rightFootOffset); - - const leftFootDelta = vec2.sub(vec2.create(), this.leftFoot.center, leftFootPositon); - const rightFootDelta = vec2.sub( - vec2.create(), - this.rightFoot.center, - rightFootPositon - ); - - vec2.scale(leftFootDelta, leftFootDelta, 0.0006); - vec2.scale(rightFootDelta, rightFootDelta, 0.0006); - - this.head.applyForce(leftFootDelta, deltaTime); - this.head.applyForce(rightFootDelta, deltaTime); - - vec2.scale(leftFootDelta, leftFootDelta, -0.5); - vec2.scale(rightFootDelta, rightFootDelta, -0.5); - - this.leftFoot.applyForce(movementForce, deltaTime); - this.rightFoot.applyForce(movementForce, deltaTime); - this.leftFoot.applyForce(leftFootDelta, deltaTime); - this.rightFoot.applyForce(rightFootDelta, deltaTime); - this.leftFoot.applyForce(settings.gravitationalForce, deltaTime); - this.rightFoot.applyForce(settings.gravitationalForce, deltaTime); - - this.head.step(deltaTime); - this.leftFoot.step(deltaTime); - this.rightFoot.step(deltaTime); - - this.flashlight.center = vec2.add( - vec2.create(), - this.head.center, - vec2.fromValues(0, 0) - ); - } -} -*/ diff --git a/frontend/src/scripts/objects/types/projectile.ts b/frontend/src/scripts/objects/types/projectile.ts deleted file mode 100644 index ea5bfd6..0000000 --- a/frontend/src/scripts/objects/types/projectile.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*import { vec2 } from 'gl-matrix'; -import { Circle } from 'sdf-2d'; -import { RenderCommand } from '../../commands/render'; -import { StepCommand } from '../../commands/step'; -import { IGame } from '../../i-game'; -import { BoundingBoxBase } from '../../physics/bounds/bounding-box-base'; -import { BoundingCircle } from '../../physics/bounds/bounding-circle'; -import { PhysicsCircle } from '../../physics/bounds/physics-circle'; -import { PhysicalGameObject } from '../../physics/physical-object'; -import { Physics } from '../../physics/physics'; -import { settings } from '../../settings'; - -export class Projectile extends PhysicalGameObject { - private shape: Circle; - private bounding: PhysicsCircle; - - constructor(private game: IGame, physics: Physics, position: vec2, velocity: vec2) { - super(physics, true); - - this.shape = new Circle(position, 20); - this.bounding = new PhysicsCircle(physics, this, position, 20); - - this.bounding.applyForce(velocity, 100); - - this.addCommandExecutor(RenderCommand, this.draw.bind(this)); - this.addCommandExecutor(StepCommand, this.step.bind(this)); - - this.addToPhysics(true); - } - - private draw(c: RenderCommand) { - c.renderer.addDrawable(this.shape); - } - - private step(c: StepCommand) { - this.bounding.applyForce(settings.gravitationalForce, c.deltaTimeInMiliseconds); - if (this.bounding.step(c.deltaTimeInMiliseconds)) { - this.game.removeObject(this); - this.physics.removeDynamicBoundingBox(this.getBoundingBox()); - } - } - - public getBoundingCircles(): BoundingCircle[] { - return [this.bounding]; - } - - public getBoundingBox(): BoundingBoxBase { - return this.bounding.boundingBox; - } - - public distance(target: vec2): number { - return this.bounding.distance(target); - } -} -*/ diff --git a/frontend/src/scripts/physics/bounds/physics-circle.ts b/frontend/src/scripts/physics/bounds/physics-circle.ts deleted file mode 100644 index 9c2d39e..0000000 --- a/frontend/src/scripts/physics/bounds/physics-circle.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*import { vec2 } from 'gl-matrix'; -import { clamp } from '../../helper/clamp'; -import { settings } from '../../settings'; -import { PhysicalGameObject } from '../physical-object'; -import { Physics } from '../physics'; -import { BoundingCircle } from './bounding-circle'; - -export class PhysicsCircle extends BoundingCircle { - private velocity = vec2.create(); - private _isAirborne = true; - - public get isAirborne(): boolean { - return this._isAirborne; - } - - constructor( - private readonly physics: Physics, - owner: PhysicalGameObject, - center: vec2, - radius: number - ) { - super(owner, center, radius); - } - - public resetVelocity() { - this.velocity = vec2.create(); - } - - public applyForce(force: vec2, timeInMilliseconds: number) { - vec2.add( - this.velocity, - this.velocity, - vec2.scale(vec2.create(), force, timeInMilliseconds) - ); - - vec2.set( - this.velocity, - clamp(this.velocity.x, -settings.maxVelocityX, settings.maxVelocityX), - clamp(this.velocity.y, -settings.maxVelocityY, settings.maxVelocityY) - ); - } - - public step(timeInMilliseconds: number): boolean { - vec2.scale( - this.velocity, - this.velocity, - Math.pow(settings.velocityAttenuation, timeInMilliseconds) - ); - const distance = vec2.scale(vec2.create(), this.velocity, timeInMilliseconds); - const distanceLength = vec2.length(distance); - const stepCount = Math.ceil(distanceLength / settings.physicsMaxStep); - vec2.scale(distance, distance, 1 / stepCount); - - let wasHit = false; - - for (let i = 0; i < stepCount; i++) { - const { normal, tangent, hitSurface } = this.physics.tryMovingDynamicCircle( - this, - distance - ); - if (hitSurface) { - vec2.scale(this.velocity, tangent, vec2.dot(tangent, this.velocity)); - wasHit = true; - } - - this._isAirborne = !( - hitSurface && vec2.dot(normal, settings.gravitationalForce) < 0 - ); - } - - return wasHit; - } -} -*/ diff --git a/frontend/src/scripts/physics/physical-object.ts b/frontend/src/scripts/physics/physical-object.ts deleted file mode 100644 index 49e0bb9..0000000 --- a/frontend/src/scripts/physics/physical-object.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*import { vec2 } from 'gl-matrix'; -import { GameObject } from '../objects/game-object'; -import { BoundingBoxBase } from './bounds/bounding-box-base'; -import { Physics } from './physics'; - -export abstract class PhysicalGameObject extends GameObject { - public abstract getBoundingBox(): BoundingBoxBase; - public abstract distance(target: vec2): number; - public readonly isInverted: boolean = false; - - constructor(protected physics: Physics, public readonly canCollide: boolean) { - super(); - } - - protected addToPhysics(isDynamic: boolean) { - if (isDynamic) { - this.physics.addDynamicBoundingBox(this.getBoundingBox()); - } else { - this.physics.addStaticBoundingBox(this.getBoundingBox()); - } - } -} -*/ diff --git a/frontend/src/scripts/physics/physics.ts b/frontend/src/scripts/physics/physics.ts deleted file mode 100644 index 8ae44f5..0000000 --- a/frontend/src/scripts/physics/physics.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*import { vec2 } from 'gl-matrix'; -import { rotate90Deg } from '../helper/rotate-90-deg'; -import { settings } from '../settings'; -import { BoundingBoxBase } from './bounds/bounding-box-base'; -import { BoundingCircle } from './bounds/bounding-circle'; -import { ImmutableBoundingBox } from './bounds/immutable-bounding-box'; -import { BoundingBoxList } from './containers/bounding-box-list'; -import { BoundingBoxTree } from './containers/bounding-box-tree'; - -export class Physics { - private isTreeInitialized = false; - private staticBoundingBoxesWaitList = []; - private staticBoundingBoxes = new BoundingBoxTree(); - private dynamicBoundingBoxes = new BoundingBoxList(); - - public addStaticBoundingBox(boundingBox: ImmutableBoundingBox) { - if (!this.isTreeInitialized) { - this.staticBoundingBoxesWaitList.push(boundingBox); - } else { - this.staticBoundingBoxes.insert(boundingBox); - } - } - - public addDynamicBoundingBox(boundingBox: BoundingBoxBase) { - this.dynamicBoundingBoxes.insert(boundingBox); - } - - public removeDynamicBoundingBox(boundingBox: BoundingBoxBase) { - this.dynamicBoundingBoxes.remove(boundingBox); - } - - public tryMovingDynamicCircle( - circle: BoundingCircle, - delta: vec2 - ): { - realDelta: vec2; - hitSurface: boolean; - normal?: vec2; - tangent?: vec2; - } { - circle.center = vec2.add(circle.center, circle.center, delta); - - const intersecting = this.findIntersecting(circle.boundingBox).filter( - (b) => - b.owner !== circle.owner && circle.areIntersecting(b.owner) && b.owner.canCollide - ); - - if (intersecting.length === 0) { - return { - realDelta: delta, - hitSurface: false, - }; - } - - const points = circle.getPerimeterPoints(settings.hitDetectionCirclePointCount); - - const distancesOfPoints = points - .map((point) => ({ - point, - closest: intersecting - .map((i) => ({ - inverted: i.isInverted, - distance: i.owner.distance(point), - })) - .sort((a, b) => a.distance - b.distance)[0], - })) - .filter((i) => i.closest); - - const distancesOfIntersectingPoints = distancesOfPoints.filter( - (d) => - (d.closest.distance > 0 && d.closest.inverted) || - (d.closest.distance < 0 && !d.closest.inverted) - ); - - if (distancesOfIntersectingPoints.length === 0) { - return { - realDelta: delta, - hitSurface: false, - }; - } - - const deltas = distancesOfIntersectingPoints.map((pointDistance) => { - vec2.subtract(pointDistance.point, circle.center, pointDistance.point); - vec2.normalize(pointDistance.point, pointDistance.point); - vec2.scale( - pointDistance.point, - pointDistance.point, - (pointDistance.closest.inverted ? 1 : -1) * pointDistance.closest.distance - ); - return pointDistance.point; - }); - - const approxNormal = deltas.reduce( - (sum, current) => vec2.add(sum, sum, current), - vec2.create() - ); - vec2.scale(approxNormal, approxNormal, 1 / deltas.length); - - circle.center = vec2.add(circle.center, circle.center, approxNormal); - - return { - realDelta: delta, - hitSurface: true, - normal: vec2.normalize(approxNormal, approxNormal), - tangent: rotate90Deg(approxNormal), - }; - } - - public findIntersecting(box: BoundingBoxBase): Array { - return [ - ...this.staticBoundingBoxes.findIntersecting(box), - ...this.dynamicBoundingBoxes.findIntersecting(box), - ]; - } - - public start() { - this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList); - this.isTreeInitialized = true; - } -} -*/ diff --git a/frontend/src/scripts/settings.ts b/frontend/src/scripts/settings.ts deleted file mode 100644 index 686a167..0000000 --- a/frontend/src/scripts/settings.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { vec2 } from 'gl-matrix'; - -export const settings = { - lightCutoffDistance: 600, - hitDetectionCirclePointCount: 32, - hitDetectionMaxOverlap: 0.01, - physicsMaxStep: 5, - gravitationalForce: vec2.fromValues(0, -0.015), - maxVelocityX: 1.5, - maxVelocityY: 8, - velocityAttenuation: 0.99, -}; diff --git a/frontend/src/scripts/transport/serializable.ts b/frontend/src/scripts/transport/serializable.ts deleted file mode 100644 index 2378ada..0000000 --- a/frontend/src/scripts/transport/serializable.ts +++ /dev/null @@ -1,7 +0,0 @@ -export abstract class Typed { - public abstract get type(): string; - - public toJSON() { - return { type: this.type, ...this }; - } -} diff --git a/shared/src/helper/delta-time-calculator.ts b/shared/src/helper/delta-time-calculator.ts deleted file mode 100644 index fb7686d..0000000 --- a/shared/src/helper/delta-time-calculator.ts +++ /dev/null @@ -1,23 +0,0 @@ -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/shared/src/helper/rgb.ts b/shared/src/helper/rgb.ts deleted file mode 100644 index a6ef20a..0000000 --- a/shared/src/helper/rgb.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { vec3 } from 'gl-matrix'; - -export const rgb = (r: number, g: number, b: number): vec3 => vec3.fromValues(r, g, b); diff --git a/shared/src/helper/rgb255.ts b/shared/src/helper/rgb255.ts deleted file mode 100644 index 56c47ff..0000000 --- a/shared/src/helper/rgb255.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { vec3 } from 'gl-matrix'; - -export const rgb255 = (r: number, g: number, b: number): vec3 => - vec3.fromValues(r / 255, g / 255, b / 255); diff --git a/shared/src/main.ts b/shared/src/main.ts index df570b0..b1ce9ae 100644 --- a/shared/src/main.ts +++ b/shared/src/main.ts @@ -13,6 +13,7 @@ export * from './commands/command-generator'; export * from './commands/broadcast-commands'; export * from './helper/array'; export * from './helper/clamp'; +export * from './helper/pretty-print'; export * from './helper/circle'; export * from './helper/rectangle'; export * from './helper/mix';