0x57ca1267…d46csent to0xf28f163e…abec·#23,922,853·view on Etherscan
ipfs://bafybeic44kygkk5donu3b3kbo4srwi5hlcmgpzrwydr5kewb4ty4gwdqmelet canvasSize = 0;
let centerX = 0;
let centerY = 0;
let plumes = [];
let layerColors = [];
let animationProgress = 0;
let animationSpeed = 0.001;
let breathProgress = 0;
let lastRegenerationTime = 0;
let cycleDuration = 2000;
let expandDuration = 800;
let holdDuration = 200;
let contractDuration = 1000;
function getRandomColor() {
colorMode(HSB, 360, 100, 100);
let c = color(random(360), 100, 100);
colorMode(RGB, 255);
return c;
}
function generatePlumes() {
plumes = [];
layerColors = [];
let scaleFactor = canvasSize / 500;
let numLayers = floor(random(3, 6));
let innerRadius = canvasSize / 30;
let maxOuterRadius = canvasSize * 0.48;
for (let layer = 0; layer < numLayers; layer++) {
if (layer === 0) {
layerColors[layer] = getRandomColor();
} else {
layerColors[layer] = color(255, 255, 255);
}
let isOuterLayer = (layer === numLayers - 1);
let layerProgress = layer / max(1, numLayers - 1);
let layerInnerRadius = innerRadius + (maxOuterRadius - innerRadius) * layerProgress * random(0.7, 0.9);
let layerOuterRadius = layerInnerRadius + (maxOuterRadius - layerInnerRadius) * random(0.4, 0.9);
if (isOuterLayer) {
layerOuterRadius = maxOuterRadius;
}
let numPlumesInLayer = floor(random(12, 25));
let angleStep = TWO_PI / numPlumesInLayer;
for (let i = 0; i < numPlumesInLayer; i++) {
let angle = i * angleStep + random(-angleStep * 0.4, angleStep * 0.4);
let plumeInnerRadius = layerInnerRadius + random(-layerInnerRadius * 0.3, layerInnerRadius * 0.4);
plumeInnerRadius = max(0, plumeInnerRadius);
let plumeOuterRadius = layerOuterRadius + random(-layerOuterRadius * 0.2, layerOuterRadius * 0.15);
plumeOuterRadius = constrain(plumeOuterRadius, plumeInnerRadius + canvasSize * 0.02, maxOuterRadius);
let baseWidth = (canvasSize / 8) * (1 + layer * 0.5);
let width = baseWidth * random(0.5, 2.5);
let maxWidthRadius = random(0.2, 0.8);
plumes.push({
angle: angle,
innerRadius: plumeInnerRadius,
outerRadius: plumeOuterRadius,
width: width,
maxWidthRadius: maxWidthRadius,
layer: layer,
turbulence: random(0.5, 2.0),
density: random(0.6, 1.0)
});
}
}
}
function getPlumeWidthAtRadius(plume, radius) {
let normalizedRadius = (radius - plume.innerRadius) / (plume.outerRadius - plume.innerRadius);
if (normalizedRadius < 0 || normalizedRadius > 1) return 0;
let distanceFromMax = abs(normalizedRadius - plume.maxWidthRadius);
let widthFactor = 1 - pow(distanceFromMax, 0.8);
widthFactor = max(0, widthFactor);
let noiseFactor = 1 + noise(plume.angle * 10 + animationProgress * 2, normalizedRadius * 5) * 0.4 - 0.2;
let taperFactor = 1 - pow(normalizedRadius, 2.5) * 0.3;
return plume.width * widthFactor * taperFactor * noiseFactor;
}
function getBreathState() {
let elapsed = millis() - lastRegenerationTime;
if (elapsed < expandDuration) {
let progress = elapsed / expandDuration;
return {
phase: 'expand',
progress: progress,
size: progress,
opacity: 1
};
} else if (elapsed < expandDuration + holdDuration) {
return {
phase: 'hold',
progress: 1,
size: 1,
opacity: 1
};
} else if (elapsed < expandDuration + holdDuration + contractDuration) {
let contractElapsed = elapsed - expandDuration - holdDuration;
let progress = contractElapsed / contractDuration;
return {
phase: 'contract',
progress: progress,
size: 1 - progress,
opacity: 1 - progress
};
} else {
return {
phase: 'complete',
progress: 1,
size: 0,
opacity: 0
};
}
}
function drawGaseousPlume(plume, breathState) {
let col = layerColors[plume.layer];
let maxRadius = map(breathState.size, 0, 1, plume.innerRadius, plume.outerRadius);
maxRadius = constrain(maxRadius, plume.innerRadius, plume.outerRadius);
if (maxRadius <= plume.innerRadius) return;
let passes = 2;
for (let pass = 0; pass < passes; pass++) {
let passAlpha = map(pass, 0, passes - 1, 20, 8);
if (plume.layer !== 0) {
passAlpha *= breathState.opacity;
} else {
passAlpha = map(pass, 0, passes - 1, 50, 25);
}
let passRadiusStep = map(pass, 0, passes - 1, 8, 15);
for (let r = plume.innerRadius; r < maxRadius; r += passRadiusStep) {
let normalizedR = (r - plume.innerRadius) / (plume.outerRadius - plume.innerRadius);
let plumeWidth = getPlumeWidthAtRadius(plume, r);
if (plumeWidth <= 0) continue;
let densityFactor = 1 - abs(normalizedR - plume.maxWidthRadius) * 0.5;
densityFactor *= plume.density;
let distanceFromEdge = maxRadius - r;
let edgeFadeDist = (plume.outerRadius - plume.innerRadius) * 0.1;
let edgeFade = constrain(distanceFromEdge / edgeFadeDist, 0, 1);
densityFactor *= edgeFade;
let angleWidth = plumeWidth / r;
let numSegments = max(2, floor(angleWidth * r / 10));
for (let seg = 0; seg < numSegments; seg++) {
let segmentProgress = seg / numSegments;
let offsetAngle = map(segmentProgress, 0, 1, -angleWidth * 0.5, angleWidth * 0.5);
let currentAngle = plume.angle + offsetAngle;
let noiseOffset = noise(
currentAngle * 3 + animationProgress,
r * 0.01,
plume.layer * 0.5
) * plume.turbulence * 0.3;
currentAngle += noiseOffset;
let x = centerX + cos(currentAngle) * r;
let y = centerY + sin(currentAngle) * r;
let edgeFactor = 1 - abs((segmentProgress - 0.5) * 2);
edgeFactor = pow(edgeFactor, 0.7);
let alpha = passAlpha * densityFactor * edgeFactor;
let scaleFactor = canvasSize / 500;
let particleSize = (500 / 80) * (1 + pass * 0.8) * sqrt(scaleFactor);
fill(red(col), green(col), blue(col), alpha);
noStroke();
ellipse(x, y, particleSize, particleSize);
}
}
}
}
function setup() {
canvasSize = min(windowWidth, windowHeight);
createCanvas(canvasSize, canvasSize);
centerX = width / 2;
centerY = height / 2;
generatePlumes();
background(0);
lastRegenerationTime = millis();
}
function draw() {
let breathState = getBreathState();
if (breathState.phase === 'complete') {
generatePlumes();
lastRegenerationTime = millis();
background(0);
breathState = getBreathState();
}
background(0, 0, 0, 25);
animationProgress += animationSpeed;
for (let plume of plumes) {
drawGaseousPlume(plume, breathState);
}
}
function windowResized() {
canvasSize = min(windowWidth, windowHeight);
resizeCanvas(canvasSize, canvasSize);
centerX = width / 2;
centerY = height / 2;
generatePlumes();
lastRegenerationTime = millis();
background(0);
}
function mousePressed() {
generatePlumes();
lastRegenerationTime = millis();
background(0);
}