Fix physics
This commit is contained in:
parent
37954e2ef1
commit
f9f6825776
51 changed files with 832 additions and 541 deletions
|
|
@ -8,6 +8,8 @@ export class BoundingBoxBase {
|
|||
protected _yMax: number = 0,
|
||||
) {}
|
||||
|
||||
[key: number]: number | undefined;
|
||||
|
||||
public get 0(): number {
|
||||
return this._xMin;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
import { Physical } from '../physical';
|
||||
import { DynamicPhysical } from '../dynamic-physical';
|
||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||
|
||||
export class BoundingBoxList {
|
||||
constructor(private objects: Array<Physical> = []) {}
|
||||
constructor(private objects: Array<DynamicPhysical> = []) {}
|
||||
|
||||
public insert(object: Physical) {
|
||||
public insert(object: DynamicPhysical) {
|
||||
this.objects.push(object);
|
||||
}
|
||||
|
||||
public remove(object: Physical) {
|
||||
public remove(object: DynamicPhysical) {
|
||||
this.objects.splice(
|
||||
this.objects.findIndex((i) => i === object),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
public findIntersecting(boundingBox: BoundingBoxBase): Array<Physical> {
|
||||
public forEach(func: (object: DynamicPhysical) => unknown) {
|
||||
this.objects.forEach(func);
|
||||
}
|
||||
|
||||
public findIntersecting(boundingBox: BoundingBoxBase): Array<DynamicPhysical> {
|
||||
return this.objects.filter((b) => b.boundingBox.intersects(boundingBox));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||
import { StaticPhysical } from './static-physical-object';
|
||||
import { StaticPhysical } from './static-physical';
|
||||
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
|
||||
|
||||
class Node {
|
||||
public left?: Node = null;
|
||||
public right?: Node = null;
|
||||
constructor(public object: StaticPhysical, public parent: Node) {}
|
||||
public left: Node | null = null;
|
||||
public right: Node | null = null;
|
||||
constructor(public object: StaticPhysical, public parent: Node | null) {}
|
||||
}
|
||||
|
||||
export class BoundingBoxTree {
|
||||
root?: Node;
|
||||
root?: Node | null;
|
||||
|
||||
constructor(objects: Array<StaticPhysical> = []) {
|
||||
this.build(objects);
|
||||
|
|
@ -22,8 +22,8 @@ export class BoundingBoxTree {
|
|||
private buildRecursive(
|
||||
objects: Array<StaticPhysical>,
|
||||
depth: number,
|
||||
parent: Node,
|
||||
): Node {
|
||||
parent: Node | null,
|
||||
): Node | null {
|
||||
if (objects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ export class BoundingBoxTree {
|
|||
|
||||
const dimension = depth % 4;
|
||||
|
||||
objects.sort((a, b) => a.boundingBox[dimension] - b.boundingBox[dimension]);
|
||||
objects.sort((a, b) => a.boundingBox[dimension]! - b.boundingBox[dimension]!);
|
||||
|
||||
const median = Math.floor(objects.length / 2);
|
||||
|
||||
|
|
@ -54,7 +54,9 @@ export class BoundingBoxTree {
|
|||
const node = new Node(object, insertPosition);
|
||||
const dimension = depth % 4;
|
||||
|
||||
if (object.boundingBox[dimension] < insertPosition.object.boundingBox[dimension]) {
|
||||
if (
|
||||
object.boundingBox[dimension]! < insertPosition.object.boundingBox[dimension]!
|
||||
) {
|
||||
insertPosition.left = node;
|
||||
} else {
|
||||
insertPosition.right = node;
|
||||
|
|
@ -63,14 +65,14 @@ export class BoundingBoxTree {
|
|||
}
|
||||
|
||||
public findIntersecting(boundingBox: BoundingBoxBase): Array<StaticPhysical> {
|
||||
const maybeResults = this.findMaybeIntersecting(boundingBox, this.root, 0);
|
||||
const maybeResults = this.findMaybeIntersecting(boundingBox, this.root!, 0);
|
||||
const results = maybeResults.filter((b) => b.boundingBox.intersects(boundingBox));
|
||||
return results;
|
||||
}
|
||||
|
||||
private findMaybeIntersecting(
|
||||
boundingBox: BoundingBoxBase,
|
||||
node: Node,
|
||||
node: Node | null,
|
||||
depth: number,
|
||||
): Array<StaticPhysical> {
|
||||
if (node === null) {
|
||||
|
|
@ -102,17 +104,17 @@ export class BoundingBoxTree {
|
|||
|
||||
private findParent(
|
||||
object: StaticPhysical,
|
||||
node: Node,
|
||||
node: Node | null | undefined,
|
||||
depth: number,
|
||||
parent: Node,
|
||||
): [Node, number] {
|
||||
if (node === null) {
|
||||
parent: Node | null,
|
||||
): [Node | null, number] {
|
||||
if (!node) {
|
||||
return [parent, depth - 1];
|
||||
}
|
||||
|
||||
const dimension = depth % 4;
|
||||
|
||||
if (object.boundingBox[dimension] < node.object.boundingBox[dimension]) {
|
||||
if (object.boundingBox[dimension]! < node.object.boundingBox[dimension]!) {
|
||||
return this.findParent(object, node.left, depth + 1, node);
|
||||
}
|
||||
|
||||
|
|
|
|||
5
backend/src/physics/containers/dynamic-physical.ts
Normal file
5
backend/src/physics/containers/dynamic-physical.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { Physical } from '../physical';
|
||||
|
||||
export interface DynamicPhysical extends Physical {
|
||||
step(deltaTimeInMilliseconds: number): void;
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { GameObject, Id } from 'shared';
|
||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||
|
||||
import { BoundingBoxList } from './bounding-box-list';
|
||||
import { BoundingBoxTree } from './bounding-box-tree';
|
||||
import { Command } from 'shared';
|
||||
|
||||
import { Physical } from '../physical';
|
||||
import { StaticPhysical } from './static-physical-object';
|
||||
import { StaticPhysical } from './static-physical';
|
||||
import { DynamicPhysical } from './dynamic-physical';
|
||||
|
||||
export class PhysicalContainer {
|
||||
private isTreeInitialized = false;
|
||||
|
|
@ -13,27 +13,12 @@ export class PhysicalContainer {
|
|||
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: Physical) {
|
||||
this.objects.set(object.gameObject.id, object.gameObject);
|
||||
|
||||
for (const command of this.objectsGroupedByAbilities.keys()) {
|
||||
if (object.gameObject.reactsToCommand(command)) {
|
||||
this.objectsGroupedByAbilities.get(command).push(object.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
this.addPhysical(object);
|
||||
}
|
||||
|
||||
public addPhysical(physical: Physical) {
|
||||
public addObject(physical: Physical) {
|
||||
if (physical.canMove) {
|
||||
this.dynamicBoundingBoxes.insert(physical);
|
||||
} else {
|
||||
|
|
@ -45,39 +30,12 @@ export class PhysicalContainer {
|
|||
}
|
||||
}
|
||||
|
||||
public removeObject(object: Physical) {
|
||||
this.objects.delete(object.gameObject.id);
|
||||
|
||||
for (const command of this.objectsGroupedByAbilities.keys()) {
|
||||
if (object.gameObject.reactsToCommand(command)) {
|
||||
const array = this.objectsGroupedByAbilities.get(command);
|
||||
array.splice(
|
||||
array.findIndex((i) => i.id == object.gameObject.id),
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public removeObject(object: DynamicPhysical) {
|
||||
this.dynamicBoundingBoxes.remove(object);
|
||||
}
|
||||
|
||||
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 stepObjects(deltaTimeInMilliseconds: number) {
|
||||
this.dynamicBoundingBoxes.forEach((o) => o.step(deltaTimeInMilliseconds));
|
||||
}
|
||||
|
||||
public findIntersecting(box: BoundingBoxBase): Array<Physical> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { rotate90Deg, settings } from 'shared';
|
||||
import { Circle, rotate90Deg } from 'shared';
|
||||
import { CirclePhysical } from '../objects/circle-physical';
|
||||
import { Physical } from './physical';
|
||||
|
||||
|
|
@ -13,71 +13,48 @@ export const moveCircle = (
|
|||
normal?: vec2;
|
||||
tangent?: vec2;
|
||||
} => {
|
||||
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||
const nextCircle = new Circle(vec2.clone(circle.center), circle.radius);
|
||||
vec2.add(nextCircle.center, nextCircle.center, delta);
|
||||
|
||||
const intersecting = possibleIntersectors.filter(
|
||||
(b) =>
|
||||
b.gameObject !== circle.gameObject && circle.areIntersecting(b) && b.canCollide,
|
||||
possibleIntersectors = possibleIntersectors.filter(
|
||||
(b) => b.gameObject !== circle.gameObject && b.canCollide,
|
||||
);
|
||||
|
||||
if (intersecting.length === 0) {
|
||||
const getSdfAtPoint = (point: vec2): number => {
|
||||
return possibleIntersectors
|
||||
.filter((i) => i.canCollide)
|
||||
.reduce((min, i) => (min = Math.min(min, i.distance(point))), 1000);
|
||||
};
|
||||
|
||||
const sdfAtCenter = getSdfAtPoint(nextCircle.center);
|
||||
|
||||
if (sdfAtCenter > nextCircle.radius) {
|
||||
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||
|
||||
return {
|
||||
realDelta: delta,
|
||||
hitSurface: false,
|
||||
};
|
||||
}
|
||||
|
||||
const points = circle.getPerimeterPoints(settings.hitDetectionCirclePointCount);
|
||||
const dx =
|
||||
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) -
|
||||
sdfAtCenter;
|
||||
const dy =
|
||||
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) -
|
||||
sdfAtCenter;
|
||||
const normal = vec2.fromValues(dx, dy);
|
||||
|
||||
const distancesOfPoints = points
|
||||
.map((point) => ({
|
||||
point,
|
||||
closest: intersecting
|
||||
.map((i) => ({
|
||||
inverted: i.isInverted,
|
||||
distance: i.distance(point),
|
||||
}))
|
||||
.sort((a, b) => a.distance - b.distance)[0],
|
||||
}))
|
||||
.filter((i) => i.closest);
|
||||
vec2.normalize(normal, normal);
|
||||
|
||||
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);
|
||||
|
||||
vec2.normalize(approxNormal, approxNormal);
|
||||
const rotatedNormal = rotate90Deg(normal);
|
||||
return {
|
||||
realDelta: delta,
|
||||
hitSurface: true,
|
||||
normal: approxNormal,
|
||||
tangent: rotate90Deg(approxNormal),
|
||||
normal,
|
||||
tangent:
|
||||
vec2.dot(rotatedNormal, delta) < 0
|
||||
? vec2.scale(rotatedNormal, rotatedNormal, -1)
|
||||
: rotatedNormal,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ 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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue