feat: ledlab
This commit is contained in:
146
presets/lava-lamp-preset.js
Normal file
146
presets/lava-lamp-preset.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user