Add basic multiplayer
This commit is contained in:
parent
0f0a1eaf67
commit
46a48e7c15
113 changed files with 1362 additions and 754 deletions
62
backend/src/physics/bounds/bounding-box-base.ts
Normal file
62
backend/src/physics/bounds/bounding-box-base.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
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
|
||||
) {}
|
||||
|
||||
public get 0(): number {
|
||||
return this._xMin;
|
||||
}
|
||||
|
||||
public get 1(): number {
|
||||
return this._xMax;
|
||||
}
|
||||
|
||||
public get 2(): number {
|
||||
return this._yMin;
|
||||
}
|
||||
|
||||
public get 3(): number {
|
||||
return this._yMax;
|
||||
}
|
||||
|
||||
public get xMin(): number {
|
||||
return this._xMin;
|
||||
}
|
||||
|
||||
public get xMax(): number {
|
||||
return this._xMax;
|
||||
}
|
||||
|
||||
public get yMin(): number {
|
||||
return this._yMin;
|
||||
}
|
||||
|
||||
public get yMax(): number {
|
||||
return this._yMax;
|
||||
}
|
||||
|
||||
public get topLeft(): vec2 {
|
||||
return vec2.fromValues(this._xMin, this._yMax);
|
||||
}
|
||||
|
||||
public get size(): vec2 {
|
||||
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
|
||||
}
|
||||
|
||||
public intersects(other: BoundingBoxBase): boolean {
|
||||
return (
|
||||
this._xMin < other._xMax &&
|
||||
this._xMax > other._xMin &&
|
||||
this._yMin < other._yMax &&
|
||||
this._yMax > other._yMin
|
||||
);
|
||||
}
|
||||
}
|
||||
65
backend/src/physics/bounds/bounding-box.ts
Normal file
65
backend/src/physics/bounds/bounding-box.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { BoundingBoxBase } from './bounding-box-base';
|
||||
import { ImmutableBoundingBox } from './immutable-bounding-box';
|
||||
|
||||
export class BoundingBox extends BoundingBoxBase {
|
||||
public get xMin(): number {
|
||||
return this._xMin;
|
||||
}
|
||||
|
||||
public set xMin(value: number) {
|
||||
this._xMin = value;
|
||||
}
|
||||
|
||||
public set xMax(value: number) {
|
||||
this._xMax = value;
|
||||
}
|
||||
|
||||
public get xMax(): number {
|
||||
return this._xMax;
|
||||
}
|
||||
|
||||
public set yMin(value: number) {
|
||||
this._yMin = value;
|
||||
}
|
||||
|
||||
public get yMin(): number {
|
||||
return this._yMin;
|
||||
}
|
||||
|
||||
public set yMax(value: number) {
|
||||
this._yMax = value;
|
||||
}
|
||||
|
||||
public get yMax(): number {
|
||||
return this._yMax;
|
||||
}
|
||||
|
||||
public get topLeft(): vec2 {
|
||||
return vec2.fromValues(this._xMin, this._yMax);
|
||||
}
|
||||
|
||||
public set topLeft(value: vec2) {
|
||||
this._xMin = value.x;
|
||||
this._yMax = value.y;
|
||||
}
|
||||
|
||||
public set size(value: vec2) {
|
||||
this._xMax = this.xMin + value.x;
|
||||
this._yMin = this.yMax - value.y;
|
||||
}
|
||||
|
||||
public get size(): vec2 {
|
||||
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
|
||||
}
|
||||
|
||||
public cloneAsImmutable(): ImmutableBoundingBox {
|
||||
return new ImmutableBoundingBox(
|
||||
this.owner,
|
||||
this.xMin,
|
||||
this.xMax,
|
||||
this.yMin,
|
||||
this.yMax
|
||||
);
|
||||
}
|
||||
}
|
||||
3
backend/src/physics/bounds/immutable-bounding-box.ts
Normal file
3
backend/src/physics/bounds/immutable-bounding-box.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { BoundingBoxBase } from './bounding-box-base';
|
||||
|
||||
export class ImmutableBoundingBox extends BoundingBoxBase {}
|
||||
20
backend/src/physics/containers/bounding-box-list.ts
Normal file
20
backend/src/physics/containers/bounding-box-list.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { BoundingBoxBase } from '../bounds/bounding-box-base';
|
||||
|
||||
export class BoundingBoxList {
|
||||
constructor(private boundingBoxes: Array<BoundingBoxBase> = []) {}
|
||||
|
||||
public insert(box: BoundingBoxBase) {
|
||||
this.boundingBoxes.push(box);
|
||||
}
|
||||
|
||||
public remove(box: BoundingBoxBase) {
|
||||
this.boundingBoxes.splice(
|
||||
this.boundingBoxes.findIndex((i) => i === box),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
|
||||
return this.boundingBoxes.filter((b) => b.intersects(box));
|
||||
}
|
||||
}
|
||||
121
backend/src/physics/containers/bounding-box-tree.ts
Normal file
121
backend/src/physics/containers/bounding-box-tree.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// 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) {}
|
||||
}
|
||||
|
||||
export class BoundingBoxTree {
|
||||
root?: Node;
|
||||
|
||||
constructor(boxes: Array<ImmutableBoundingBox> = []) {
|
||||
this.build(boxes);
|
||||
}
|
||||
|
||||
public build(boxes: Array<ImmutableBoundingBox>) {
|
||||
this.root = this.buildRecursive(boxes, 0, null);
|
||||
}
|
||||
|
||||
private buildRecursive(
|
||||
boxes: Array<ImmutableBoundingBox>,
|
||||
depth: number,
|
||||
parent: Node
|
||||
): Node {
|
||||
if (boxes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (boxes.length === 1) {
|
||||
return new Node(boxes[0], parent);
|
||||
}
|
||||
|
||||
const dimension = depth % 4;
|
||||
|
||||
boxes.sort((a, b) => a[dimension] - b[dimension]);
|
||||
|
||||
const median = Math.floor(boxes.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);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public insert(box: ImmutableBoundingBox) {
|
||||
const [insertPosition, depth] = this.findParent(box, this.root, 0, null);
|
||||
|
||||
if (insertPosition === null) {
|
||||
this.root = new Node(box, null);
|
||||
} else {
|
||||
const node = new Node(box, insertPosition);
|
||||
const dimension = depth % 4;
|
||||
|
||||
if (box[dimension] < insertPosition.rectangle[dimension]) {
|
||||
insertPosition.left = node;
|
||||
} else {
|
||||
insertPosition.right = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public findIntersecting(box: ImmutableBoundingBox): Array<ImmutableBoundingBox> {
|
||||
const maybeResults = this.findMaybeIntersecting(box, this.root, 0);
|
||||
const results = maybeResults.filter((b) => b.intersects(box));
|
||||
return results;
|
||||
}
|
||||
|
||||
private findMaybeIntersecting(
|
||||
box: ImmutableBoundingBox,
|
||||
node: Node,
|
||||
depth: number
|
||||
): Array<ImmutableBoundingBox> {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (depth % 4 == 0 && box.xMax < node.rectangle.xMin) {
|
||||
return this.findMaybeIntersecting(box, node.left, depth + 1);
|
||||
}
|
||||
|
||||
if (depth % 4 == 1 && box.xMin > node.rectangle.xMax) {
|
||||
return this.findMaybeIntersecting(box, node.right, depth + 1);
|
||||
}
|
||||
|
||||
if (depth % 4 == 2 && box.yMax < node.rectangle.yMin) {
|
||||
return this.findMaybeIntersecting(box, node.left, depth + 1);
|
||||
}
|
||||
|
||||
if (depth % 4 == 3 && box.yMin > node.rectangle.yMax) {
|
||||
return this.findMaybeIntersecting(box, node.right, depth + 1);
|
||||
}
|
||||
|
||||
return [
|
||||
node.rectangle,
|
||||
...this.findMaybeIntersecting(box, node.left, depth + 1),
|
||||
...this.findMaybeIntersecting(box, node.right, depth + 1),
|
||||
];
|
||||
}
|
||||
|
||||
private findParent(
|
||||
box: ImmutableBoundingBox,
|
||||
node: Node,
|
||||
depth: number,
|
||||
parent: Node
|
||||
): [Node, number] {
|
||||
if (node === null) {
|
||||
return [parent, depth - 1];
|
||||
}
|
||||
|
||||
const dimension = depth % 4;
|
||||
|
||||
if (box[dimension] < node.rectangle[dimension]) {
|
||||
return this.findParent(box, node.left, depth + 1, node);
|
||||
}
|
||||
|
||||
return this.findParent(box, node.right, depth + 1, node);
|
||||
}
|
||||
}
|
||||
86
backend/src/physics/physical-game-object-container.ts
Normal file
86
backend/src/physics/physical-game-object-container.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { GameObject, Id } from 'shared';
|
||||
import { BoundingBoxBase } from './bounds/bounding-box-base';
|
||||
|
||||
import { BoundingBoxList } from './containers/bounding-box-list';
|
||||
import { BoundingBoxTree } from './containers/bounding-box-tree';
|
||||
import { Command } from 'shared';
|
||||
import { PhysicalGameObject } from './physical-game-object';
|
||||
import { ImmutableBoundingBox } from './bounds/immutable-bounding-box';
|
||||
|
||||
export class PhysicalGameObjectContainer {
|
||||
private isTreeInitialized = false;
|
||||
private staticBoundingBoxesWaitList: Array<ImmutableBoundingBox> = [];
|
||||
private staticBoundingBoxes = new BoundingBoxTree();
|
||||
private dynamicBoundingBoxes = new BoundingBoxList();
|
||||
|
||||
protected objects: Map<Id, GameObject> = new Map();
|
||||
private objectsGroupedByAbilities: Map<string, Array<GameObject>> = new Map();
|
||||
|
||||
public initialize() {
|
||||
this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList);
|
||||
this.isTreeInitialized = true;
|
||||
}
|
||||
|
||||
public addObject(object: PhysicalGameObject, isDynamic) {
|
||||
this.objects.set(object.id, object);
|
||||
|
||||
for (const command of this.objectsGroupedByAbilities.keys()) {
|
||||
if (object.reactsToCommand(command)) {
|
||||
this.objectsGroupedByAbilities.get(command).push(object);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDynamic) {
|
||||
this.dynamicBoundingBoxes.insert(object.getBoundingBox());
|
||||
} else {
|
||||
if (!this.isTreeInitialized) {
|
||||
this.staticBoundingBoxesWaitList.push(object.getBoundingBox());
|
||||
} else {
|
||||
this.staticBoundingBoxes.insert(object.getBoundingBox());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeObject(object: PhysicalGameObject) {
|
||||
this.objects.delete(object.id);
|
||||
|
||||
for (const command of this.objectsGroupedByAbilities.keys()) {
|
||||
if (object.reactsToCommand(command)) {
|
||||
const array = this.objectsGroupedByAbilities.get(command);
|
||||
array.splice(
|
||||
array.findIndex((i) => i.id == object.id),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.dynamicBoundingBoxes.remove(object.getBoundingBox());
|
||||
}
|
||||
|
||||
public sendCommand(e: Command) {
|
||||
if (!this.objectsGroupedByAbilities.has(e.type)) {
|
||||
this.createGroupForCommand(e.type);
|
||||
}
|
||||
|
||||
this.objectsGroupedByAbilities.get(e.type).forEach((o, _) => o.sendCommand(e));
|
||||
}
|
||||
|
||||
private createGroupForCommand(commandType: string) {
|
||||
const objectsReactingToCommand = [];
|
||||
|
||||
this.objects.forEach((o, _) => {
|
||||
if (o.reactsToCommand(commandType)) {
|
||||
objectsReactingToCommand.push(o);
|
||||
}
|
||||
});
|
||||
|
||||
this.objectsGroupedByAbilities.set(commandType, objectsReactingToCommand);
|
||||
}
|
||||
|
||||
public findIntersecting(box: BoundingBoxBase): Array<PhysicalGameObject> {
|
||||
return [
|
||||
...this.staticBoundingBoxes.findIntersecting(box),
|
||||
...this.dynamicBoundingBoxes.findIntersecting(box),
|
||||
].map((b) => b.owner);
|
||||
}
|
||||
}
|
||||
10
backend/src/physics/physical-game-object.ts
Normal file
10
backend/src/physics/physical-game-object.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue