jumpstorm/engine/components/BoundingBox.ts

120 lines
3.4 KiB
TypeScript
Raw Normal View History

2023-08-25 18:48:17 -04:00
import { Component, ComponentNames } from '.';
import type { Coord2D, Dimension2D } from '../interfaces';
import { dotProduct, rotateVector } from '../utils';
2023-07-19 23:38:24 -04:00
export class BoundingBox extends Component {
public center: Coord2D;
public dimension: Dimension2D;
public rotation: number;
constructor(center: Coord2D, dimension: Dimension2D, rotation?: number) {
super(ComponentNames.BoundingBox);
this.center = center;
this.dimension = dimension;
this.rotation = rotation ?? 0;
}
2023-08-12 15:49:16 -04:00
// https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
2023-07-19 23:38:24 -04:00
public isCollidingWith(box: BoundingBox): boolean {
if (this.rotation == 0 && box.rotation == 0) {
const thisTopLeft = this.getTopLeft();
const thisBottomRight = this.getBottomRight();
const thatTopLeft = box.getTopLeft();
const thatBottomRight = box.getBottomRight();
if (
thisBottomRight.x <= thatTopLeft.x ||
thisTopLeft.x >= thatBottomRight.x ||
thisBottomRight.y <= thatTopLeft.y ||
thisTopLeft.y >= thatBottomRight.y
) {
return false;
}
return true;
}
2023-07-19 23:38:24 -04:00
const boxes = [this.getVertices(), box.getVertices()];
for (const poly of boxes) {
2023-08-12 15:49:16 -04:00
for (let i = 0; i < poly.length; i++) {
2023-07-19 23:38:24 -04:00
const [A, B] = [poly[i], poly[(i + 1) % poly.length]];
const normal: Coord2D = { x: B.y - A.y, y: A.x - B.x };
const [[minThis, maxThis], [minBox, maxBox]] = boxes.map((box) =>
box.reduce(
([min, max], vertex) => {
const projection = dotProduct(normal, vertex);
return [Math.min(min, projection), Math.max(max, projection)];
},
2023-08-25 18:48:17 -04:00
[Infinity, -Infinity]
)
2023-07-19 23:38:24 -04:00
);
if (maxThis < minBox || maxBox < minThis) return false;
}
}
return true;
}
public getVertices(): Coord2D[] {
return [
{ x: -this.dimension.width / 2, y: -this.dimension.height / 2 },
{ x: -this.dimension.width / 2, y: this.dimension.height / 2 },
{ x: this.dimension.width / 2, y: this.dimension.height / 2 },
2023-08-25 18:48:17 -04:00
{ x: this.dimension.width / 2, y: -this.dimension.height / 2 }
2023-07-19 23:38:24 -04:00
]
2023-08-13 18:47:58 -04:00
.map((vertex) => rotateVector(vertex, this.rotation)) // rotate
2023-07-19 23:38:24 -04:00
.map((vertex) => {
2023-08-13 18:47:58 -04:00
// translate
2023-07-19 23:38:24 -04:00
return {
x: vertex.x + this.center.x,
2023-08-25 18:48:17 -04:00
y: vertex.y + this.center.y
2023-07-19 23:38:24 -04:00
};
});
}
2023-08-13 18:47:58 -04:00
public getRotationInPiOfUnitCircle(): number {
2023-08-12 15:49:16 -04:00
let rads = this.rotation * (Math.PI / 180);
if (rads >= Math.PI) {
2023-08-13 18:47:58 -04:00
// Physics system guarantees rotation \in [0, 360)
2023-08-12 15:49:16 -04:00
rads -= Math.PI;
2023-07-19 23:38:24 -04:00
}
2023-08-12 15:49:16 -04:00
return rads;
2023-07-19 23:38:24 -04:00
}
2023-08-12 15:49:16 -04:00
public getOutscribedBoxDims(): Dimension2D {
let rads = this.getRotationInPiOfUnitCircle();
const { width, height } = this.dimension;
2023-07-19 23:38:24 -04:00
2023-08-12 15:49:16 -04:00
if (rads <= Math.PI / 2) {
return {
width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)),
2023-08-25 18:48:17 -04:00
height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads))
2023-07-19 23:38:24 -04:00
};
}
2023-08-12 15:49:16 -04:00
rads -= Math.PI / 2;
return {
width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)),
2023-08-25 18:48:17 -04:00
height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads))
2023-08-12 15:49:16 -04:00
};
2023-07-19 23:38:24 -04:00
}
public getTopLeft(): Coord2D {
return {
x: this.center.x - this.dimension.width / 2,
2023-08-25 18:48:17 -04:00
y: this.center.y - this.dimension.height / 2
};
}
public getBottomRight(): Coord2D {
return {
x: this.center.x + this.dimension.width / 2,
2023-08-25 18:48:17 -04:00
y: this.center.y + this.dimension.height / 2
};
}
2023-07-19 23:38:24 -04:00
}