import type { Stage } from '../scenes/stage'; import { centroid, type V } from './geom'; /** toastsim-style material params. resistance raises the cut threshold; moisture * drives the splat; wobble is jelly-class jiggle (soft, deferred to when a * jelly food actually ships). */ export interface Material { resistance: number; // 0..1 skin toughness moisture: number; // 0..1 wet food splats wobble: number; // 0..1 jiggle } export interface FoodDef { verts: V[]; // convex, centred on origin material: Material; color: number; face: string; // generated face sprite id (rendered later; gameplay is the silhouette) } const circle = (r: number, n: number, squashY = 1): V[] => Array.from({ length: n }, (_, i) => { const a = (i / n) * Math.PI * 2; return { x: Math.cos(a) * r, y: Math.sin(a) * r * squashY }; }); export const box = (w: number, h: number): V[] => { const x = w / 2, y = h / 2, c = Math.min(x, y) * 0.4; // chamfered corners → convex octagon return [ { x: -x + c, y: -y }, { x: x - c, y: -y }, { x, y: -y + c }, { x, y: y - c }, { x: x - c, y }, { x: -x + c, y }, { x: -x, y: y - c }, { x: -x, y: -y + c }, ]; }; export const FOODS: Record = { tomato: { verts: circle(34, 12), material: { resistance: 0.25, moisture: 0.85, wobble: 0.15 }, color: 0x151515, face: 'face_tomato' }, bread: { verts: box(78, 52), material: { resistance: 0.35, moisture: 0.05, wobble: 0.0 }, color: 0x1c150e, face: 'face_bread' }, cheese: { verts: box(60, 44), material: { resistance: 0.45, moisture: 0.15, wobble: 0.0 }, color: 0x191510, face: 'face_cheese' }, katsu: { verts: box(86, 46), material: { resistance: 0.55, moisture: 0.1, wobble: 0.0 }, color: 0x171008, face: 'face_katsu' }, egg: { verts: circle(28, 10, 1.15), material: { resistance: 0.3, moisture: 0.4, wobble: 0.1 }, color: 0x121212, face: 'face_egg' }, // ponytail: wobble is a material param only — real jelly-cluster jiggle waits // until a day needs it to be funny. Tofu cuts easy but splats if pressed. tofu: { verts: box(64, 46), material: { resistance: 0.12, moisture: 0.6, wobble: 0.4 }, color: 0x1a1a18, face: 'face_egg' }, }; let NEXT = 1; /** A food body + its material and origin colour. Pieces are Foods too. */ export class Food { readonly id: string; constructor( readonly body: MatterJS.BodyType, readonly material: Material, readonly color: number, readonly face: string, readonly kind: string, ) { this.id = `${kind}${NEXT++}`; } } /** Build a food body from centred local verts placed at world (x,y). Registers * the silhouette on the stage. Shared by spawn and by the cutter's pieces. */ export function makeFood(stage: Stage, local: V[], x: number, y: number, mat: Material, color: number, face: string, kind: string): Food { const body = stage.matter.add.fromVertices(x, y, local, { friction: 0.6, frictionAir: 0.01, density: 0.0009, restitution: 0.05, }) as MatterJS.BodyType; // fromVertices recentres COM; render verts relative to that centre. const c = centroid(local); const rel = local.map((v) => ({ x: v.x - c.x, y: v.y - c.y })); stage.addPoly(body, rel, color); // a comical face — whole foods only (pieces are pieces). `p`-suffixed kinds // are cut pieces; don't give a severed half a startled face. if (!kind.endsWith('p')) { const xs = local.map((v) => v.x), ys = local.map((v) => v.y); stage.addFace(body, Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys)); } return new Food(body, mat, color, face, kind); } /** Spawn a whole food of `kind` at x, resting just above the floor. */ export function spawnFood(stage: Stage, kind: string, x: number, y: number): Food { const def = FOODS[kind] ?? FOODS.tomato; return makeFood(stage, def.verts, x, y, def.material, def.color, def.face, kind); }