Chanomic Sketch

Baloons

"use strict";

let palette;

function setup() {
  pixelDensity(1);
  createCanvas(windowWidth, windowHeight);
  background(0);

  // red, purple, yellow, green, blue
  palette = ['#FD8A8A', '#BA94D1', '#FFF89A', '#B6E2A1', '#9ADCFF'];
}

function draw() {
  background(255);
  noLoop();

  const H = min(map(height, 0, 820, 0, 100), 100);

  push();
  translate(width/2, height/2);
  drawRectangle(width/2, H, 10);
  pop();

  overlayNoise();
}

const overlayNoise = () => {
  loadPixels();
  for (let x = 0; x < width; x++) {
    for (let y = 0; y < height; y++) {
      const i = (y * width + x) * 4;
      const dr = random(-20, 20);
      const dg = random(-20, 20);
      const db = random(-20, 20);
      if (pixels[i + 0] !== 255 ||
          pixels[i + 1] !== 255 ||
          pixels[i + 2] !== 255) {
        pixels[i + 0] += dr;
        pixels[i + 1] += dg;
        pixels[i + 2] += db;
      }
    }
  }
  updatePixels();
}

const drawRectangle = (W, H, N) => {
  const [positions, ws] = generateBars(W, 10);

  push();
  translate(-W/2, -H/2);
  
  for (let i = 0; i < ws.length; i++) {
    const x = positions[i];
    const w = ws[i];
    const col = random(palette);
    const colAlpha = color(col);
    colAlpha.setAlpha(196);
    const xMid = x + w/2;
    const baloon = new Baloon(10, 12, 5);

    push();
    stroke(196);
    line(xMid, H/2, xMid, -H);
    translate(xMid, -H);
    const sc = constrain(map(height, 0, 820, 0, 2), 1, 2);
    scale(sc);
    fill(colAlpha);
    baloon.draw();
    pop();

    noStroke();
    fill(col);
    rect(x, 0, w, H)
  }

  pop();
}

const generateBars = (W, N) => {
  const baseW = W / N;
  const tryMax = 100;

  for (let i = 0; i < tryMax; i++) {
    // Generate
    const positions = Array.from({ length: N + 1 })
                           .map((_, i) => {
                              if (i === 0) {
                                return 0;
                              } else if (i === N) {
                                return W;
                              } else {
                                return random(0, W);
                              }
                            })
                            .sort((a, b) => (a - b));
    const ws = pairDiff(positions);

    // Check
    if (ws.every((e) => e >= baseW/4)) {
      return [positions, ws];
    }
  }

  return [positions, ws];
}

class Baloon {
  constructor(radX, radY, triLen) {
    this.radX = radX;
    this.radY = radY;
    this.triLen = triLen;

    this.width = max(2*radX, triLen);
    this.height = 2*radY + triLen*sqrt(3)/2*0.5;
  }

  draw() {
    const radX = this.radX;
    const radY = this.radY;
    const triLen = this.triLen;
    const yMin = radY;
    const yMax = radY + triLen*sqrt(3)/2*0.5;
    const yMid = (yMin + yMax)/2;

    push();
    noStroke();
    translate(0, -yMid);
    ellipse(0, 0, 2*radX, 2*radY);
    translate(0, radY - triLen*sqrt(3)/2*0.5);
    triangle(0, 0, triLen/2, triLen*sqrt(3)/2, -triLen/2, triLen*sqrt(3)/2);
    pop();
  }
}

const pairDiff = (arr) => {
  const res = [];
  for (let i = 1; i < arr.length; i++) {
    res.push(arr[i] - arr[i-1]);
  }
  return res;
}