-
Notifications
You must be signed in to change notification settings - Fork 0
/
geometry.ts
91 lines (76 loc) · 2.72 KB
/
geometry.ts
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
export type Vector = {
x: number;
y: number;
};
export type BoundingBox = {topL: Vector; topR: Vector; bottomL: Vector; bottomR: Vector};
export type Rectangle = {
x: number;
y: number;
width: number;
height: number;
angle: number;
boundingBox: BoundingBox;
};
export type Circle = {
x: number;
y: number;
radius: number;
};
export type AxisOverlap = {overlap: number; axis: Vector};
export function rotatePoint(x: number, y: number, originX: number, originY: number, angle: number): Vector {
const cosTheta = Math.cos(angle);
const sinTheta = Math.sin(angle);
const newX = cosTheta * (x - originX) - sinTheta * (y - originY) + originX;
const newY = sinTheta * (x - originX) + cosTheta * (y - originY) + originY;
return {x: newX, y: newY};
}
function projectRectangleOntoAxis(rectangleCorners: BoundingBox, axis: Vector): {min: number; max: number} {
const dotProduct1 = rectangleCorners.topL.x * axis.x + rectangleCorners.topL.y * axis.y;
const dotProduct2 = rectangleCorners.topR.x * axis.x + rectangleCorners.topR.y * axis.y;
const dotProduct3 = rectangleCorners.bottomL.x * axis.x + rectangleCorners.bottomL.y * axis.y;
const dotProduct4 = rectangleCorners.bottomR.x * axis.x + rectangleCorners.bottomR.y * axis.y;
const min = Math.min(dotProduct1, dotProduct2, dotProduct3, dotProduct4);
const max = Math.max(dotProduct1, dotProduct2, dotProduct3, dotProduct4);
return {min, max};
}
export function overlapOnAxis(circle: Circle, axis: Vector, rectangleCorners: BoundingBox): number {
const circleProjection = circle.x * axis.x + circle.y * axis.y;
const rectProjection = projectRectangleOntoAxis(rectangleCorners, axis);
// Calculate the overlap value
const overlap = Math.max(
0,
Math.min(rectProjection.max, circleProjection + circle.radius) -
Math.max(rectProjection.min, circleProjection - circle.radius),
);
return overlap;
}
/**
* Calculates the overlap between a circle and a rectangle on each axis, accounting for rotation
* @param rect the possibly rotated rectangle
* @param circle the moving ball
* @returns the overlap on each axis in ascending order
*/
export function getOverlapsAndAxes(rect: Rectangle, circle: Circle): Array<AxisOverlap> {
return [
{x: 1, y: 0}, // X-axis
{x: 0, y: -1}, // Y-axis
]
.map(axis => {
const rotatedAxis = rotatePoint(axis.x, axis.y, 0, 0, rect.angle);
return {
overlap: overlapOnAxis(circle, rotatedAxis, rect.boundingBox),
axis: rotatedAxis,
};
})
.sort((a, b) => a.overlap - b.overlap);
}
export function normalizeAngle(angle: number) {
let res = angle;
while (res > Math.PI) {
res -= 2 * Math.PI;
}
while (res <= -Math.PI) {
res += 2 * Math.PI;
}
return res;
}