"use strict";
let fgImg, bgImg;
let starPalette;
function setup() {
pixelDensity(1);
createCanvas(windowWidth, windowHeight);
starPalette = ['#DC3535', '#FFE15D', '#82CD47', '#fff'];
fgImg = createGraphics(width, height);
bgImg = createGraphics(width, height);
}
function draw() {
background(0);
noLoop();
drawBg(100, 1, 15);
image(bgImg, 0, 0);
filter(BLUR, 1);
drawFg();
image(fgImg, 0, 0);
bgImg.clear();
drawBg(1000, 1, 5);
image(bgImg, 0, 0);
drawGrain();
}
const drawFg = () => {
// Cone Top y_{i}: y_{i} = y_{i-1} + b r0
// Cone Rad r_{i}: r_{i} = a r_{i-1}
// Cone Bottom h_{i}: h_{i} = y_{i} + r_{i}
const a = 3/2;
const b = 1/3;
const N = 5;
let y = 0;
let r = calcR0(a, b, 0, N);
const r0 = r;
const ys = [y];
const rs = [r];
for (let i = 0; i <= N+1; i++) {
y = y + r0/3;
r *= a;
ys.push(y);
rs.push(r);
}
for (let i = N+1; i >= 0; i--) {
const x = width/2;
const y = ys[i];
const r = rs[i];
const rotMax = floor(PI*r)/15;
drawCone(x, y, r/2, r, rotMax);
}
}
const calcR0 = (a, b, h0, N) => {
// binary search
let lb = 0;
let ub = height;
while (abs(lb - ub) >= 1e-9) {
const mid = (lb + ub) / 2;
// check
// i = 0
const r0 = mid;
let y = 0;
let r = r0;
let h = h0 + r;
// i = 1, 2, ... , N
for (let i = 1; i < N; i++) {
y = y + b*r0;
r = a*r;
h = y + r;
}
if (h <= height) lb = mid;
else ub = mid;
}
return lb;
}
const drawCone = (x, y, rMin, rMax, rotMax) => {
const points = [];
let off = 0;
for (let j = 0; j <= rotMax; j++) {
const angle = j / rotMax * PI/3 + PI/3;
const r = map(noise(y, off), 0, 1, rMin, rMax);
off += 0.05;
points.push([r, angle]);
}
fgImg.push();
{
fgImg.translate(x, y);
fgImg.noFill();
fgImg.drawingContext.shadowBlur = 0;
fgImg.drawingContext.shadowColor = 'black';
fgImg.drawingContext.shadowOffsetX = 0;
fgImg.drawingContext.shadowOffsetY = 2;
points.forEach((point) => {
fgImg.noFill();
fgImg.stroke('#4E6C50');
fgImg.strokeWeight(1);
fgImg.line(0, 0, ...polarToCart(point));
});
drawLight(...points[0],
...points[points.length - 1]);
}
fgImg.pop();
}
const drawLight = (r1, ang1, r2, ang2) => {
const lightNum = floor(random(1, 4));
for (let i = 0; i < lightNum; i++) {
const r3 = random(0.25, 1) * r1;
const r4 = random(0.25, 1) * r2;
const [x1, y1] = polarToCart([r3, ang1]);
const [x2, y2] = polarToCart([r4, ang2]);
const [xm, ym] = [(x1 + x2)/2, (y1 + y2)/2 + (r1 + r2)/2*random(0, 0.5)];
fgImg.drawingContext.shadowColor = 'white';
fgImg.noFill();
const col = color(random(starPalette));
col.setAlpha(128);
fgImg.stroke(col);
fgImg.strokeWeight(random(1, 2));
fgImg.drawingContext.shadowBlur = 10;
drawBezier2d(fgImg, x1, y1, xm, ym, x2, y2);
fgImg.drawingContext.shadowBlur = 5;
drawBezier2d(fgImg, x1, y1, xm, ym, x2, y2);
}
}
const drawBezier2d = (img, xs, ys, cx, cy, xe, ye) => {
img.beginShape();
img.vertex(xs, ys);
img.quadraticVertex(cx, cy, xe, ye);
img.endShape();
}
const polarToCart = ([r, ang]) => [r*cos(ang), r*sin(ang)];
const drawBg = (N, rMin, rMax) => {
bgImg.drawingContext.shadowBlur = 10;
bgImg.drawingContext.shadowColor = 'white';
for (let i = 0; i < N; i++) {
const x = random(0, width);
const y = random(0, height);
bgImg.noStroke();
bgImg.fill(random(starPalette));
bgImg.circle(x, y, random(rMin,rMax));
}
};
const drawGrain = () => {
const amount = 30;
loadPixels();
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (x + y*width)*4;
const val1 = random(-amount, amount);
const val2 = random(-amount, amount);
const val3 = random(-amount, amount);
pixels[i] += val1;
pixels[i + 1] += val2;
pixels[i + 2] += val3;
}
}
updatePixels();
}