import type { Vec2 } from '../geometry/Vec2'; import { polygonArea } from '../geometry/Polygon'; /** 으깨기 모드의 조정값. 간격은 벨트가 실제 진행한 시간이다. */ export const CRUSH = { halfWidth: 0.90, halfDepth: 0.70, beltHalfWidth: 40, beltHalfDepth: 1.15, beltY: 0.16, hover: 1.45, flat: 0.15, spread: 0.25, edge: 0.07, } as const; export interface CrushContact { minX: number; maxX: number; minZ: number; maxZ: number } /** 볼록 압착판에 대한 클리핑. 원본 폴리곤을 변경하지 않는다. */ export function crushScore(outline: readonly Vec2[], x: number): { score: number; contact: CrushContact } { const contact = { minX: -CRUSH.halfWidth-x, maxX: CRUSH.halfWidth-x, minZ: -CRUSH.halfDepth, maxZ: CRUSH.halfDepth }; let points = [...outline]; for (const [axis, bound, sign] of [['x',contact.minX,1],['x',contact.maxX,-1],['y',contact.minZ,1],['y',contact.maxZ,-1]] as const) { const next: Vec2[]=[]; for (let i=0;i=0) next.push(a); if ((da>=0)!==(db>=0)) { const t=da/(da-db); next.push({x:a.x+(b.x-a.x)*t,y:a.y+(b.y-a.y)*t}); } } points=next; } const area=Math.abs(polygonArea(outline)); const ratio=area>1e-10 ? Math.abs(polygonArea(points))/area : 0; return {score:Math.min(100, Math.max(0,ratio*100)),contact}; }