Add lava lamp scene with soft-body blobs

This commit is contained in:
Daddy32
2025-12-13 19:54:20 +01:00
parent 357922510a
commit f57e993964
6 changed files with 337 additions and 53 deletions

122
main.js
View File

@@ -78,6 +78,7 @@
// Static boundaries and scene-specific obstacles.
let boundaries = [];
let rotators = [];
let oscillators = [];
let currentScene =
scenes.find((s) => s.id === defaultSceneId) || scenes[0] || null;
@@ -90,6 +91,7 @@
boundaries.forEach((b) => World.remove(world, b));
boundaries = currentScene.createBodies(width, height);
rotators = boundaries.filter((b) => b.plugin && b.plugin.rotSpeed);
oscillators = boundaries.filter((b) => b.plugin && b.plugin.oscillate);
World.add(world, boundaries);
};
@@ -176,18 +178,18 @@
config.ballRadius + 10,
Math.min(width - config.ballRadius - 10, Math.random() * width),
);
const y = -config.ballRadius * 2;
const ball = Bodies.circle(x, y, config.ballRadius, {
restitution: 0.72,
friction: 0.01,
frictionAir: 0.015,
render: {
fillStyle: color,
strokeStyle: "#0b1222",
lineWidth: 2,
},
});
ball.plugin = { color, hasEntered: false, entryCheckId: null };
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
const y = spawnFromBottom
? height + config.ballRadius * 2
: -config.ballRadius * 2;
const ball = createBallBody(x, y, color);
ball.plugin = {
color,
hasEntered: false,
entryCheckId: null,
squishX: 1,
squishY: 1,
};
balls.push(ball);
World.add(world, ball);
ball.plugin.entryCheckId = setTimeout(() => {
@@ -352,6 +354,28 @@
if (typeof winCond.onWin.setGravity === "number") {
engine.gravity.y = winCond.onWin.setGravity;
}
if (winCond.onWin.shoveBalls) {
balls.forEach((ball) => {
const angle = Math.random() * Math.PI * 2;
const magnitude = 12 + Math.random() * 10;
const force = {
x: Math.cos(angle) * magnitude,
y: Math.sin(angle) * magnitude,
};
Body.applyForce(ball, ball.position, force);
});
}
if (winCond.onWin.removeCurves) {
const remaining = [];
boundaries.forEach((b) => {
if (b.plugin && b.plugin.curve) {
World.remove(world, b);
} else {
remaining.push(b);
}
});
boundaries = remaining;
}
};
const checkWinCondition = () => {
@@ -578,6 +602,34 @@
config.ballRadius = nextRadius;
};
const createBallBody = (x, y, color) => {
const commonOpts = {
restitution: 0.72,
friction: 0.01,
frictionAir: 0.015,
render: {
fillStyle: color,
strokeStyle: "#0b1222",
lineWidth: 2,
},
};
if (currentScene?.config?.blobBalls) {
const points = [];
const segments = 12;
for (let i = 0; i < segments; i += 1) {
const angle = (i / segments) * Math.PI * 2;
const variance = 0.75 + Math.random() * 0.35;
const r = config.ballRadius * variance;
points.push({
x: x + Math.cos(angle) * r,
y: y + Math.sin(angle) * r,
});
}
return Bodies.fromVertices(x, y, [points], commonOpts);
}
return Bodies.circle(x, y, config.ballRadius, commonOpts);
};
const getGoalState = () => {
const winCond = currentScene?.config?.winCondition;
if (!winCond) return null;
@@ -669,11 +721,17 @@
if (
ball.position.x < -100 ||
ball.position.x > width + 100 ||
ball.position.y > height + 500
(currentScene?.config?.spawnFrom === "bottom"
? ball.position.y < -500
: ball.position.y > height + 500)
) {
cleanupBall(ball);
ball.plugin.hasEntered = true;
Matter.Body.setPosition(ball, { x: Math.random() * width, y: -40 });
const spawnFromBottom = currentScene?.config?.spawnFrom === "bottom";
Matter.Body.setPosition(ball, {
x: Math.random() * width,
y: spawnFromBottom ? height + 40 : -40,
});
Matter.Body.setVelocity(ball, { x: 0, y: 0 });
}
});
@@ -705,6 +763,42 @@
Body.rotate(b, speed * ((dt * timeScale) / 1000));
}
});
oscillators.forEach((b) => {
const osc = b.plugin.oscillate;
if (!osc) return;
if (!osc.base) {
osc.base = { x: b.position.x, y: b.position.y };
}
const now = (engine.timing.timestamp || 0) / 1000;
const amplitude = osc.amplitude ?? 0;
const speed = osc.speed ?? 1;
const phase = osc.phase ?? 0;
const offset = Math.sin(now * speed + phase) * amplitude;
const target =
osc.axis === "x"
? { x: osc.base.x + offset, y: osc.base.y }
: { x: osc.base.x, y: osc.base.y + offset };
Body.setPosition(b, target);
Body.setVelocity(b, { x: 0, y: 0 });
});
if (currentScene?.config?.blobBalls) {
balls.forEach((ball) => {
if (!ball.plugin) return;
const speed = Vector.magnitude(ball.velocity || { x: 0, y: 0 });
const squeeze = Math.min(0.22, speed / 20);
const targetX = 1 + squeeze;
const targetY = Math.max(0.7, 1 - squeeze);
const currX = ball.plugin.squishX || 1;
const currY = ball.plugin.squishY || 1;
const factorX = targetX / currX;
const factorY = targetY / currY;
if (Math.abs(factorX - 1) > 0.02 || Math.abs(factorY - 1) > 0.02) {
Body.scale(ball, factorX, factorY);
ball.plugin.squishX = targetX;
ball.plugin.squishY = targetY;
}
});
}
});
Events.on(render, "afterRender", () => {