diff options
Diffstat (limited to 'src/engine/components/BoundingBox.ts')
-rw-r--r-- | src/engine/components/BoundingBox.ts | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/src/engine/components/BoundingBox.ts b/src/engine/components/BoundingBox.ts new file mode 100644 index 0000000..d64041f --- /dev/null +++ b/src/engine/components/BoundingBox.ts @@ -0,0 +1,122 @@ +import { Component, ComponentNames } from "."; +import type { Coord2D, Dimension2D } from "../interfaces"; +import { dotProduct, rotateVector } from "../utils"; + +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; + } + + public isCollidingWith(box: BoundingBox): boolean { + // optimization; when neither rotates just check if they overlap + 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; + } + + // https://en.wikipedia.org/wiki/Hyperplane_separation_theorem + const boxes = [this.getVertices(), box.getVertices()]; + for (const poly of boxes) { + for (let i = 0; i < poly.length; i++) { + 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)]; + }, + [Infinity, -Infinity], + ), + ); + + 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 }, + { x: this.dimension.width / 2, y: -this.dimension.height / 2 }, + ] + .map((vertex) => rotateVector(vertex, this.rotation)) // rotate + .map((vertex) => { + // translate + return { + x: vertex.x + this.center.x, + y: vertex.y + this.center.y, + }; + }); + } + + public getRotationInPiOfUnitCircle(): number { + let rads = this.rotation * (Math.PI / 180); + if (rads >= Math.PI) { + // Physics system guarantees rotation \in [0, 360) + rads -= Math.PI; + } + return rads; + } + + public getOutscribedBoxDims(): Dimension2D { + let rads = this.getRotationInPiOfUnitCircle(); + const { width, height } = this.dimension; + + if (rads == 0) return this.dimension; + + if (rads <= Math.PI / 2) { + return { + width: Math.abs(height * Math.sin(rads) + width * Math.cos(rads)), + height: Math.abs(width * Math.sin(rads) + height * Math.cos(rads)), + }; + } + + rads -= Math.PI / 2; + return { + width: Math.abs(height * Math.cos(rads) + width * Math.sin(rads)), + height: Math.abs(width * Math.cos(rads) + height * Math.sin(rads)), + }; + } + + public getTopLeft(): Coord2D { + return { + x: this.center.x - this.dimension.width / 2, + y: this.center.y - this.dimension.height / 2, + }; + } + + public getBottomRight(): Coord2D { + return { + x: this.center.x + this.dimension.width / 2, + y: this.center.y + this.dimension.height / 2, + }; + } +} |