import type { Point, Rect, ShapeData } from '../types'; import { colliderCorners, containsWorldPoint } from './collision'; export class Shape { type: ShapeData['type']; x: number; y: number; width: number; height: number; rotation: number; points: Point[]; constructor(data: ShapeData) { this.type = data.type === 'circle' ? 'oval' : data.type; this.x = data.x; this.y = data.y; this.width = Math.max(1, data.width); this.height = Math.max(1, data.height); this.rotation = data.rotation ?? 0; this.points = data.points ? data.points.map((p) => ({ x: p.x, y: p.y })) : []; } clone(): Shape { return new Shape({ type: this.type, x: this.x, y: this.y, width: this.width, height: this.height, rotation: this.rotation, points: this.points.map((p) => ({ x: p.x, y: p.y })), }); } getCenter(): Point { return { x: this.x + this.width / 2, y: this.y + this.height / 2 }; } getBounds(): Rect { 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); 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 }; } 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 { 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++) { const xi = this.points[i].x; const yi = this.points[i].y; const xj = this.points[j].x; const yj = this.points[j].y; const intersect = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / ((yj - yi) || 1e-9) + xi; if (intersect) inside = !inside; } return inside; } 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); } }