Fix physics

This commit is contained in:
schmelczerandras 2020-10-12 20:49:17 +02:00
parent 37954e2ef1
commit f9f6825776
51 changed files with 832 additions and 541 deletions

View file

@ -8,6 +8,8 @@ export class BoundingBoxBase {
protected _yMax: number = 0,
) {}
[key: number]: number | undefined;
public get 0(): number {
return this._xMin;
}

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -0,0 +1,5 @@
import { Physical } from '../physical';
export interface DynamicPhysical extends Physical {
step(deltaTimeInMilliseconds: number): void;
}

View file

@ -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> {

View file

@ -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,
};
};

View file

@ -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;