Files
Physilinks/scenes/scene-relax.js
2025-12-13 20:46:50 +01:00

135 lines
3.8 KiB
JavaScript

(() => {
const { Bodies, Composites } = Matter;
const scenes = (window.PhysilinksSceneDefs =
window.PhysilinksSceneDefs || []);
scenes.push({
id: "relax",
name: "Relax drift",
config: {
gravity: 0.08,
spawnIntervalMs: 850,
spawnBatchMin: 3,
spawnBatchMax: 5,
spawnFrom: "bottom",
autoSpawn: true,
minChain: 2,
palette: ["#38bdf8", "#f472b6", "#fbbf24", "#22c55e", "#a855f7"],
ballRadius: 20,
blobBalls: false,
noGameOver: true,
winCondition: {
type: "timer",
durationSec: 120,
onWin: { setGravity: -0.4, swirlBalls: true },
},
link: {
stiffness: 0.6,
lengthScale: 1.1,
damping: 0.1,
lineWidth: 3,
rope: true,
renderType: "line",
maxLengthMultiplier: 3.8,
},
},
createBodies: (w, h) => {
const wallThickness = Math.max(30, w * 0.04);
const wallHeight = h + wallThickness * 2;
const floorHeight = Math.max(40, h * 0.08);
const bumperRadius = Math.max(30, Math.min(w, h) * 0.04);
const makeSoft = (cx, cy, cols, rows, radius, color) => {
const particleOpts = {
friction: 0.02,
frictionStatic: 0.04,
restitution: 0.02,
render: { fillStyle: color, strokeStyle: color },
plugin: { draggable: true, nonLinkable: true },
};
const constraintOpts = {
stiffness: 0.08,
damping: 0.35,
render: { visible: false, type: "line", anchors: false },
};
const comp = Composites.softBody(
cx - cols * radius * 1.1,
cy - rows * radius * 1.1,
cols,
rows,
0,
0,
true,
radius,
particleOpts,
constraintOpts,
);
comp.bodies.forEach((b) => {
b.plugin = b.plugin || {};
b.plugin.draggable = true;
b.plugin.nonLinkable = true;
});
comp.constraints.forEach((c) => {
c.plugin = { soft: true };
});
return comp;
};
const softRadius = Math.max(18, w * 0.025);
const softA = makeSoft(w * 0.35, h * 0.4, 3, 3, softRadius, "#38bdf8");
const softB = makeSoft(w * 0.65, h * 0.55, 3, 3, softRadius, "#f472b6");
return [
Bodies.rectangle(
w / 2,
h + floorHeight / 2,
w + wallThickness * 2,
floorHeight,
{
isStatic: true,
restitution: 0.8,
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
},
),
Bodies.rectangle(
w / 2,
-wallThickness / 2,
w + wallThickness * 2,
wallThickness,
{
isStatic: true,
render: { fillStyle: "#0ea5e9", strokeStyle: "#0ea5e9" },
},
),
Bodies.rectangle(-wallThickness / 2, h / 2, wallThickness, wallHeight, {
isStatic: true,
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
}),
Bodies.rectangle(
w + wallThickness / 2,
h / 2,
wallThickness,
wallHeight,
{
isStatic: true,
render: { fillStyle: "#7c3aed", strokeStyle: "#7c3aed" },
},
),
Bodies.circle(w * 0.35, h * 0.35, bumperRadius, {
isStatic: true,
restitution: 1.05,
render: { fillStyle: "#fbbf24", strokeStyle: "#fbbf24" },
}),
Bodies.circle(w * 0.65, h * 0.55, bumperRadius * 0.9, {
isStatic: true,
restitution: 1.05,
render: { fillStyle: "#22c55e", strokeStyle: "#22c55e" },
}),
...softA.bodies,
...softA.constraints,
...softB.bodies,
...softB.constraints,
];
},
});
})();