Chanomic Sketch

Composition 2025 (1)

let t = 0;
let pg, pg_back;
let buildings = [];
const palette = [
  '#5A9CB5',
  '#FACE68',
  '#FAAC68',
  '#FA6868',
];

const walkers = [];
const num = 1200;
const life_max = 1200;
const life_min = 300;

function setup() {
  createCanvas(windowWidth, windowHeight);

  pg_back = createGraphics(windowWidth, windowHeight);
  pg_back.colorMode(HSB, 360, 100, 100, 100);
  pg_back.background(0, 0, 0)
  for (let i = 0; i < num; i++) {
    walkers.push(new Walker());
  }


  pg = createGraphics(windowWidth, windowHeight);
  let x = 0;
  while (x < width) {
    buildings.push(new Building(x));
    x += random(20, 40);
  }
  for (let b of buildings) {
    b.draw(pg);
  }
  const snow = new Snow();
  snow.draw(pg);
}

function draw() {
  pg_back.noStroke();
  pg_back.background(0, 0, 0, 1)
  pg_back.blendMode(ADD);
  for (const w of walkers) {
    const n = noise(w.x * 0.002, w.y * 0.002);
    const angle = n * TAU * 2;
    const weight = map(w.life, 0, life_max, 0.2, 3.5);
    const bright = map(w.life, 0, life_max, 40, 100);

    pg_back.stroke(w.hue, 80, bright, 50);
    pg_back.strokeWeight(weight);
    pg_back.point(w.x, w.y);

    w.update(angle)
  }

  pg_back.blendMode(BLEND);

  background(0);
  image(pg_back, 0, 0);
  image(pg, 0, 0);
}

class Snow {
  constructor() {
    this.x = random(0, width);
    this.y = random(0, height / 3);
    this.size = 50;
    this.maxDepth = 3;
  }

  draw(g) {
    g.push();
    g.translate(this.x, this.y);
    g.stroke(255);
    g.noFill();

    // 6方向に回転して同じ枝を描く
    for (let i = 0; i < 6; i++) {
      g.push();
      g.rotate((TWO_PI / 6) * i);
      this.drawFlake(g, this.size, 4, 0);
      g.pop();
    }

    g.pop();
  }

  drawFlake(g, len, w, depth) {
    if (depth > this.maxDepth) return;

    g.strokeWeight(w);

    // 中心から伸びる主線
    g.line(0, 0, 0, -len);

    // 枝の先へ移動
    g.translate(0, -len);

    // 左右に枝分かれ
    g.push();
    g.rotate(PI / 6);
    g.scale(0.7);
    this.drawFlake(g, len / 1.1, w / 1.5, depth + 1);
    g.pop();

    g.push();
    g.rotate(-PI / 6);
    g.scale(0.7);
    this.drawFlake(g, len / 1.1, w / 1.5, depth + 1);
    g.pop();
  }
}



class Building {
  constructor(x) {
    this.x = x;
    this.w = random(40, 120);
    this.h = random(height * 0.2, height / 3 * 2);
    this.col = color(random(palette));
    this.col.setAlpha(200);
  }

  draw(g) {
    g.push();
    g.noStroke();
    g.fill(this.col);

    // 画面下から生える
    g.rect(
      this.x,
      height - this.h,
      this.w,
      this.h
    );

    g.pop();
  }
}

class Walker {
  constructor() {
    this.init()
  }

  init() {
    this.vx = random(-0.2, 0.2);
    this.vy = random(-0.2, 0.2);
    this.x = random(width);
    this.y = random(height);
    this.life = random(life_min, life_max);
    this.hue = random(0, 60);
  }

  update(angle) {
    this.vx += 0.001 * cos(angle);
    this.vy += 0.001 * sin(angle);
    this.x += this.vx;
    this.y += this.vy;
    this.life--;

    if (this.x < 0) this.x = width;
    if (this.x > width) this.x = 0;
    if (this.y < 0) this.y = height;
    if (this.y > height) this.y = 0;

    if (this.life <= 0) {
      this.init()
    }
  }
}