(() => { const { Bodies, Composites, Body } = Matter; const create = ({ config, getCurrentScene }) => { const applyBallPlugin = (body, { color, shape, blobId } = {}) => { body.plugin = { color, hasEntered: false, entryCheckId: null, }; if (shape) body.plugin.shape = shape; if (blobId) body.plugin.blobId = blobId; }; const getCommonBallOpts = (scene, color) => { const ballPhysics = scene?.config?.ballPhysics || {}; return { restitution: ballPhysics.restitution ?? 0.72, friction: ballPhysics.friction ?? 0.01, frictionAir: ballPhysics.frictionAir ?? 0.012, frictionStatic: ballPhysics.frictionStatic ?? 0, density: ballPhysics.density ?? 0.001, render: { fillStyle: color, strokeStyle: "#0b1222", lineWidth: 2, }, }; }; const createSoftBlob = (x, y, color, commonOpts) => { const cols = 3; const rows = 2; const radius = Math.max(10, config.ballRadius * 0.55); const soft = Composites.softBody( x - cols * radius * 1.2, y - rows * radius * 1.2, cols, rows, 0, 0, true, radius, commonOpts, ); const blobId = `blob-${Date.now()}-${Math.random() .toString(16) .slice(2)}`; soft.bodies.forEach((b) => applyBallPlugin(b, { color, blobId })); soft.constraints.forEach((c) => { c.plugin = { blobId, blobConstraint: true }; c.render = c.render || {}; c.render.type = "line"; }); return { bodies: soft.bodies, constraints: soft.constraints, blobId }; }; const createJaggedBall = (x, y, color, commonOpts) => { const points = []; const segments = 6; for (let i = 0; i < segments; i += 1) { const angle = Math.min((i / segments) * Math.PI * 2, Math.PI * 2); const variance = 0.6 + Math.random() * 0.5; const r = config.ballRadius * variance; points.push({ x: x + Math.cos(angle) * r, y: y + Math.sin(angle) * r, }); } const body = Bodies.fromVertices(x, y, [points], commonOpts, true); applyBallPlugin(body, { color, shape: "jagged" }); return { bodies: [body], constraints: [], blobId: null }; }; const createGiftBall = (x, y, color, commonOpts, scene, debugSpawn) => { const size = config.ballRadius * 2; const ribbonSize = Math.max(4, config.ballRadius * 0.35); const ribbonColor = scene?.config?.giftRibbonColor || "#f8fafc"; if (debugSpawn) { console.log("Spawn gift", { sceneId: scene?.id, x, y, color, size, }); } const base = Bodies.rectangle(x, y, size, size, { ...commonOpts, chamfer: { radius: Math.max(2, config.ballRadius * 0.18) }, }); const ribbonRender = { fillStyle: ribbonColor, strokeStyle: "#0b1222", lineWidth: 2, }; const verticalRibbon = Bodies.rectangle(x, y, ribbonSize, size * 1.02, { render: ribbonRender, }); const horizontalRibbon = Bodies.rectangle( x, y, size * 1.02, ribbonSize, { render: ribbonRender, }, ); const bow = Bodies.rectangle( x, y - size * 0.34, ribbonSize * 1.4, ribbonSize * 0.8, { render: ribbonRender, }, ); const body = Body.create({ parts: [base, verticalRibbon, horizontalRibbon, bow], }); Body.setPosition(body, { x, y }); body.restitution = commonOpts.restitution; body.friction = commonOpts.friction; body.frictionAir = commonOpts.frictionAir; body.frictionStatic = commonOpts.frictionStatic; body.density = commonOpts.density; body.render = { ...body.render, ...commonOpts.render, visible: true, }; applyBallPlugin(body, { color, shape: "gift" }); return { bodies: [body], constraints: [], blobId: null }; }; const createRectBall = (x, y, color, commonOpts) => { const side = config.ballRadius * 2; const body = Bodies.rectangle(x, y, side, side, { ...commonOpts, chamfer: 0, }); applyBallPlugin(body, { color, shape: "rect" }); return { bodies: [body], constraints: [], blobId: null }; }; const createCircleBall = (x, y, color, commonOpts) => { const body = Bodies.circle(x, y, config.ballRadius, commonOpts); applyBallPlugin(body, { color, shape: "circle" }); return { bodies: [body], constraints: [], blobId: null }; }; const ballShapeFactories = { gift: createGiftBall, rect: createRectBall, circle: createCircleBall, }; const createBallBodies = (x, y, color) => { const scene = getCurrentScene(); const debugSpawn = !!scene?.config?.debugSpawn; const commonOpts = getCommonBallOpts(scene, color); if (scene?.config?.blobBalls === "soft") { return createSoftBlob(x, y, color, commonOpts); } if (scene?.config?.blobBalls === "jagged") { return createJaggedBall(x, y, color, commonOpts); } const shape = scene?.config?.ballShape || "circle"; const factory = ballShapeFactories[shape] || createCircleBall; return factory(x, y, color, commonOpts, scene, debugSpawn); }; return { createBallBodies, applyBallPlugin, getCommonBallOpts, }; }; window.PhysilinksSpawnBalls = { create }; })();