147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
// Lava Lamp preset for LEDLab
|
|
|
|
const BasePreset = require('./base-preset');
|
|
const { createFrame, frameToPayload, hexToRgb, samplePalette, toIndex, clamp } = require('./frame-utils');
|
|
|
|
class LavaLampPreset extends BasePreset {
|
|
constructor(width = 16, height = 16) {
|
|
super(width, height);
|
|
this.blobs = [];
|
|
this.paletteStops = [
|
|
{ stop: 0.0, color: hexToRgb('050319') },
|
|
{ stop: 0.28, color: hexToRgb('2a0c4f') },
|
|
{ stop: 0.55, color: hexToRgb('8f1f73') },
|
|
{ stop: 0.75, color: hexToRgb('ff4a22') },
|
|
{ stop: 0.9, color: hexToRgb('ff9333') },
|
|
{ stop: 1.0, color: hexToRgb('fff7b0') },
|
|
];
|
|
this.defaultParameters = {
|
|
blobCount: 6,
|
|
blobSpeed: 0.18,
|
|
minBlobRadius: 0.18,
|
|
maxBlobRadius: 0.38,
|
|
intensity: 1.0,
|
|
};
|
|
}
|
|
|
|
init() {
|
|
super.init();
|
|
this.blobs = this.createBlobs(this.getParameter('blobCount') || 6);
|
|
}
|
|
|
|
createBlobs(count) {
|
|
const blobList = [];
|
|
const maxAxis = Math.max(this.width, this.height);
|
|
const minBlobRadius = Math.max(3, maxAxis * (this.getParameter('minBlobRadius') || 0.18));
|
|
const maxBlobRadius = Math.max(minBlobRadius + 1, maxAxis * (this.getParameter('maxBlobRadius') || 0.38));
|
|
|
|
for (let index = 0; index < count; ++index) {
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const speed = (this.getParameter('blobSpeed') || 0.18) * (0.6 + Math.random() * 0.8);
|
|
blobList.push({
|
|
x: Math.random() * Math.max(1, this.width - 1),
|
|
y: Math.random() * Math.max(1, this.height - 1),
|
|
vx: Math.cos(angle) * speed,
|
|
vy: Math.sin(angle) * speed,
|
|
minRadius: minBlobRadius * (0.6 + Math.random() * 0.3),
|
|
maxRadius: maxBlobRadius * (0.8 + Math.random() * 0.4),
|
|
intensity: (this.getParameter('intensity') || 1.0) * (0.8 + Math.random() * 0.7),
|
|
phase: Math.random() * Math.PI * 2,
|
|
phaseVelocity: 0.6 + Math.random() * 0.6,
|
|
});
|
|
}
|
|
return blobList;
|
|
}
|
|
|
|
updateBlobs(deltaSeconds) {
|
|
const maxX = Math.max(0, this.width - 1);
|
|
const maxY = Math.max(0, this.height - 1);
|
|
|
|
this.blobs.forEach((blob) => {
|
|
blob.x += blob.vx * deltaSeconds;
|
|
blob.y += blob.vy * deltaSeconds;
|
|
|
|
if (blob.x < 0) {
|
|
blob.x = -blob.x;
|
|
blob.vx = Math.abs(blob.vx);
|
|
} else if (blob.x > maxX) {
|
|
blob.x = 2 * maxX - blob.x;
|
|
blob.vx = -Math.abs(blob.vx);
|
|
}
|
|
|
|
if (blob.y < 0) {
|
|
blob.y = -blob.y;
|
|
blob.vy = Math.abs(blob.vy);
|
|
} else if (blob.y > maxY) {
|
|
blob.y = 2 * maxY - blob.y;
|
|
blob.vy = -Math.abs(blob.vy);
|
|
}
|
|
|
|
blob.phase += blob.phaseVelocity * deltaSeconds;
|
|
});
|
|
}
|
|
|
|
renderFrame() {
|
|
this.updateBlobs(0.016); // Assume 60 FPS
|
|
|
|
const frame = createFrame(this.width, this.height);
|
|
|
|
for (let row = 0; row < this.height; ++row) {
|
|
for (let col = 0; col < this.width; ++col) {
|
|
const energy = this.calculateEnergyAt(col, row);
|
|
const color = samplePalette(this.paletteStops, energy);
|
|
frame[toIndex(col, row, this.width)] = color;
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
calculateEnergyAt(col, row) {
|
|
let energy = 0;
|
|
|
|
this.blobs.forEach((blob) => {
|
|
const radius = this.getBlobRadius(blob);
|
|
const dx = col - blob.x;
|
|
const dy = row - blob.y;
|
|
const distance = Math.hypot(dx, dy);
|
|
const falloff = Math.max(0, 1 - distance / radius);
|
|
energy += blob.intensity * falloff * falloff;
|
|
});
|
|
|
|
return clamp(energy / this.blobs.length, 0, 1);
|
|
}
|
|
|
|
getBlobRadius(blob) {
|
|
const oscillation = (Math.sin(blob.phase) + 1) * 0.5;
|
|
return blob.minRadius + (blob.maxRadius - blob.minRadius) * oscillation;
|
|
}
|
|
|
|
setParameter(name, value) {
|
|
super.setParameter(name, value);
|
|
|
|
// Recreate blobs if blob-related parameters change
|
|
if (name === 'blobCount' || name === 'blobSpeed' || name === 'minBlobRadius' || name === 'maxBlobRadius' || name === 'intensity') {
|
|
this.blobs = this.createBlobs(this.getParameter('blobCount') || 6);
|
|
}
|
|
}
|
|
|
|
getMetadata() {
|
|
return {
|
|
name: 'Lava Lamp',
|
|
description: 'Floating blobs creating organic color gradients',
|
|
parameters: {
|
|
blobCount: { type: 'range', min: 3, max: 12, step: 1, default: 6 },
|
|
blobSpeed: { type: 'range', min: 0.1, max: 0.5, step: 0.05, default: 0.18 },
|
|
minBlobRadius: { type: 'range', min: 0.1, max: 0.3, step: 0.02, default: 0.18 },
|
|
maxBlobRadius: { type: 'range', min: 0.2, max: 0.5, step: 0.02, default: 0.38 },
|
|
intensity: { type: 'range', min: 0.5, max: 2.0, step: 0.1, default: 1.0 },
|
|
},
|
|
width: this.width,
|
|
height: this.height,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = LavaLampPreset;
|