pxt-common-packages
Version:
Microsoft MakeCode (PXT) common packages
194 lines (161 loc) • 6.11 kB
text/typescript
namespace sprites {
let aabbPoints: number[];
export class RotatedBoundingBox {
protected _rotation: number;
protected _width: number;
protected _height: number;
protected points: number[];
protected cornerDistance: number;
protected cornerAngle: number;
public get x0(): number {
return this.points[0];
}
public get y0(): number {
return this.points[1];
}
public get x1(): number {
return this.points[2];
}
public get y1(): number {
return this.points[3];
}
public get x2(): number {
return this.points[4];
}
public get y2(): number {
return this.points[5];
}
public get x3(): number {
return this.points[6];
}
public get y3(): number {
return this.points[7];
}
public get rotation() {
return this._rotation;
}
public set rotation(value: number) {
this.setRotation(value);
}
public get width() {
return this._width;
}
public get height() {
return this._height;
}
constructor(
public anchor: Sprite,
width: number,
height: number
) {
this.points = [];
this._rotation = 0;
this.setDimensions(width, height);
}
setDimensions(width: number, height: number) {
width /= 2;
height /= 2;
this.cornerDistance = Math.sqrt(
width * width + height * height
);
this.cornerAngle = Math.atan2(height, width);
this.setRotation(this._rotation);
}
setRotation(angle: number) {
this._rotation = angle;
this.points[0] = Math.cos(this.cornerAngle + angle) * this.cornerDistance;
this.points[1] = Math.sin(this.cornerAngle + angle) * this.cornerDistance;
this.points[2] = Math.cos(Math.PI - this.cornerAngle + angle) * this.cornerDistance;
this.points[3] = Math.sin(Math.PI - this.cornerAngle + angle) * this.cornerDistance;
this.points[4] = Math.cos(Math.PI + this.cornerAngle + angle) * this.cornerDistance;
this.points[5] = Math.sin(Math.PI + this.cornerAngle + angle) * this.cornerDistance;
this.points[6] = Math.cos(angle - this.cornerAngle) * this.cornerDistance;
this.points[7] = Math.sin(angle - this.cornerAngle) * this.cornerDistance;
this.updateWidthHeight();
}
overlaps(other: RotatedBoundingBox): boolean {
return doRectanglesIntersect(
this.points,
this.anchor.x,
this.anchor.y,
other.points,
other.anchor.x,
other.anchor.y
);
}
overlapsAABB(left: number, top: number, right: number, bottom: number) {
if (!aabbPoints) {
aabbPoints = [];
}
aabbPoints[0] = left;
aabbPoints[1] = top;
aabbPoints[2] = right;
aabbPoints[3] = top;
aabbPoints[4] = right;
aabbPoints[5] = bottom;
aabbPoints[6] = left;
aabbPoints[7] = bottom;
return doRectanglesIntersect(
this.points,
this.anchor.x,
this.anchor.y,
aabbPoints,
0,
0
);
}
protected updateWidthHeight() {
let minX = this.points[0];
let maxX = minX;
let minY = this.points[1];
let maxY = minY;
for (let i = 2; i < 8; i += 2) {
minX = Math.min(minX, this.points[i]);
maxX = Math.max(maxX, this.points[i]);
minY = Math.min(minY, this.points[i + 1]);
maxY = Math.max(maxY, this.points[i + 1]);
}
this._width = (maxX - minX) | 0;
this._height = (maxY - minY) | 0;
}
}
// adapted from https://stackoverflow.com/questions/10962379/how-to-check-intersection-between-2-rotated-rectangles
// but optimized for rectangles
function doRectanglesIntersect(a: number[], ax: number, ay: number, b: number[], bx: number, by: number) {
return !(checkForNonIntersection(a, ax, ay, b, bx, by) || checkForNonIntersection(b, bx, by, a, ax, ay));
}
function checkForNonIntersection(a: number[], ax: number, ay: number, b: number[], bx: number, by: number) {
// we only need to check the first two sides because the
// normals are the same for the other two
for (let pointIndex = 0; pointIndex < 4; pointIndex += 2) {
const normalX = a[pointIndex + 3] - a[pointIndex + 1];
const normalY = a[pointIndex] - a[pointIndex + 2];
let minA: number = undefined;
let maxA: number = undefined;
let minB: number = undefined;
let maxB: number = undefined;
for (let i = 0; i < 8; i += 2) {
const projected = normalX * (a[i] + ax) + normalY * (a[i + 1] + ay);
if (minA === undefined || projected < minA) {
minA = projected;
}
if (maxA == undefined || projected > maxA) {
maxA = projected;
}
}
for (let i = 0; i < 8; i += 2) {
const projected = normalX * (b[i] + bx) + normalY * (b[i + 1] + by);
if (minB === undefined || projected < minB) {
minB = projected;
}
if (maxB == undefined || projected > maxB) {
maxB = projected;
}
}
if (maxA < minB || maxB < minA) {
return true;
}
}
return false;
}
}