Add dynamic SAT collision, oval/circle shapes, rotation support, and score display
This commit is contained in:
+39
-28
@@ -1,4 +1,5 @@
|
||||
import type { Point, Rect, ShapeData } from '../types';
|
||||
import { colliderCorners, containsWorldPoint } from './collision';
|
||||
|
||||
export class Shape {
|
||||
type: ShapeData['type'];
|
||||
@@ -10,7 +11,7 @@ export class Shape {
|
||||
points: Point[];
|
||||
|
||||
constructor(data: ShapeData) {
|
||||
this.type = data.type;
|
||||
this.type = data.type === 'circle' ? 'oval' : data.type;
|
||||
this.x = data.x;
|
||||
this.y = data.y;
|
||||
this.width = Math.max(1, data.width);
|
||||
@@ -31,15 +32,12 @@ export class Shape {
|
||||
});
|
||||
}
|
||||
|
||||
getCenter(): Point {
|
||||
return { x: this.x + this.width / 2, y: this.y + this.height / 2 };
|
||||
}
|
||||
|
||||
getBounds(): Rect {
|
||||
if (this.type === 'rectangle') {
|
||||
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||
}
|
||||
if (this.type === 'circle') {
|
||||
const r = Math.max(this.width, this.height) / 2;
|
||||
return { x: this.x - r, y: this.y - r, width: r * 2, height: r * 2 };
|
||||
}
|
||||
if (this.points.length > 0) {
|
||||
if (this.type === 'polygon' && this.points.length > 0) {
|
||||
const xs = this.points.map((p) => p.x);
|
||||
const ys = this.points.map((p) => p.y);
|
||||
const minX = Math.min(...xs);
|
||||
@@ -48,22 +46,26 @@ export class Shape {
|
||||
const maxY = Math.max(...ys);
|
||||
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
||||
}
|
||||
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||
if (this.rotation % 360 === 0) {
|
||||
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||
}
|
||||
const corners = colliderCorners({
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
rotation: this.rotation,
|
||||
});
|
||||
const xs = corners.map((p) => p.x);
|
||||
const ys = corners.map((p) => p.y);
|
||||
const minX = Math.min(...xs);
|
||||
const maxX = Math.max(...xs);
|
||||
const minY = Math.min(...ys);
|
||||
const maxY = Math.max(...ys);
|
||||
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
||||
}
|
||||
|
||||
containsPoint(p: Point): boolean {
|
||||
const b = this.getBounds();
|
||||
if (p.x < b.x || p.x > b.x + b.width || p.y < b.y || p.y > b.y + b.height) {
|
||||
if (this.type !== 'polygon') return false;
|
||||
}
|
||||
if (this.type === 'circle') {
|
||||
const cx = b.x + b.width / 2;
|
||||
const cy = b.y + b.height / 2;
|
||||
const r = b.width / 2;
|
||||
const dx = p.x - cx;
|
||||
const dy = p.y - cy;
|
||||
return dx * dx + dy * dy <= r * r + 1e-8;
|
||||
}
|
||||
if (this.type === 'polygon' && this.points.length > 0) {
|
||||
let inside = false;
|
||||
for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
|
||||
@@ -76,11 +78,20 @@ export class Shape {
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
return p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
|
||||
}
|
||||
|
||||
intersectsRect(r: Rect): boolean {
|
||||
const b = this.getBounds();
|
||||
return !(r.x + r.width < b.x || r.x > b.x + b.width || r.y + r.height < b.y || r.y > b.y + b.height);
|
||||
const c = { x: this.x, y: this.y, width: this.width, height: this.height, rotation: this.rotation };
|
||||
if (this.type === 'oval') {
|
||||
const center = this.getCenter();
|
||||
const rad = (-this.rotation * Math.PI) / 180;
|
||||
const cos = Math.cos(rad);
|
||||
const sin = Math.sin(rad);
|
||||
const dx = p.x - center.x;
|
||||
const dy = p.y - center.y;
|
||||
const lx = dx * cos - dy * sin;
|
||||
const ly = dx * sin + dy * cos;
|
||||
const rx = this.width / 2;
|
||||
const ry = this.height / 2;
|
||||
return (lx * lx) / (rx * rx) + (ly * ly) / (ry * ry) <= 1 + 1e-8;
|
||||
}
|
||||
return containsWorldPoint(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user