summaryrefslogtreecommitdiff
path: root/engine/components/BoundingBox.ts
blob: 2b1d6485230b7f79f6fbaac497ba4f41131d9be2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { Component, ComponentNames } from ".";
import type { Coord2D, Dimension2D } from "../interfaces";
import { dotProduct, rotateVector, normalizeVector } 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 {
    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))
      .map((vertex) => {
        return {
          x: vertex.x + this.center.x,
          y: vertex.y + this.center.y,
        };
      });
  }

  private getAxes() {
    const corners: Coord2D[] = this.getVerticesRelativeToCenter();
    const axes: Coord2D[] = [];

    for (let i = 0; i < corners.length; ++i) {
      const [cornerA, cornerB] = [
        corners[i],
        corners[(i + 1) % corners.length],
      ].map((corner) => rotateVector(corner, this.rotation));

      axes.push(
        normalizeVector({
          x: cornerB.y - cornerA.y,
          y: -(cornerB.x - cornerA.x),
        })
      );
    }

    return axes;
  }

  private project(axis: Coord2D): [number, number] {
    const corners = this.getCornersRelativeToCenter();
    let [min, max] = [Infinity, -Infinity];

    for (const corner of corners) {
      const rotated = rotateVector(corner, this.rotation);
      const translated = {
        x: rotated.x + this.center.x,
        y: rotated.y + this.center.y,
      };
      const projection = dotProduct(translated, axis);

      min = Math.min(projection, min);
      max = Math.max(projection, max);
    }

    return [min, max];
  }
}