Chanomic Sketch

Composition 2025 (3)

const flowerPalette = [
  "#3B0270",
  "#6F00FF",
  "#E9B3FB",
  "#FFF1F1",
  "#000000",
  "#CF0F47",
  "#FF0B55",
  "#FFDEDE"
];
const palette = [
  '#5A9CB5',
  '#FACE68',
  '#FAAC68',
  '#FA6868',
];
const bgColor = "#333333";
const flowersBack = [];
const flowersMid = [];
const flowersFront = [];
let buildings = [];
let bubbles = [];
const MAX_BUBBLES = 120;
const num = 500;
let pg, pg_building, pg_back;

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

  draw(g) {
    g.push();
    g.translate(this.x, height - this.h);
    g.rect(0, 0, this.w, this.h);
    g.pop();
  }
}

class Flower {
  constructor(layer) {
    this.layer = layer
    this.reset(this.layer);
  }

  reset(layer) {
    this.x = random(width, width * 1.3);
    this.y = random(height);

    this.type = random(["round", "star", "tulip"]);

    this.size = random(6, 20);
    this.petals = int(random(5, 10));

    this.col1 = color(random(flowerPalette));
    this.col2 = color(random(flowerPalette));

    if (layer === "back") {
      this.depth = random(2.0, 3.0);
    } else if (layer === "mid") {
      this.depth = random(0.8, 2.0);
    } else {
      this.depth = random(0.4, 0.8);
    }

    this.angle = random(TAU);

    this.speedX = map(this.depth, 0.4, 3.0, 0.8, 3.2);
    this.speedY = random(-0.3, 0.3); 
    this.windSeed = random(1000);
    this.angle = random(TAU);
    this.spin = random(-0.01, 0.01);
  }

  update() {
    // 左へ流れる
    this.x -= this.speedX;

    // 風の揺れ(なめらか)
    const sway = noise(frameCount * 0.0025, this.windSeed) - 0.5;
    this.y += sway * map(this.depth, 0.3, 1.6, 0.5, 1.6);

    // わずかな上下移動
    this.y += this.speedY;

    // 回転
    this.angle += this.spin;

    // 画面外でリセット
    if (this.x < -100 || this.y < -100 || this.y > height + 100) {
      this.reset(this.layer);
    }
  }

  draw(g) {
    g.push();
    g.translate(this.x, this.y);
    g.rotate(this.angle);

    const s = this.size * this.depth;

    g.noStroke();
    g.fill(this.col1);

    if (this.type === "round") {
      for (let i = 0; i < this.petals; i++) {
        const a = (TAU / this.petals) * i;
        g.push();
        g.rotate(a);
        g.translate(s, 0);
        g.ellipse(0, 0, s * 0.9, s * 0.4);
        g.pop();
      }
    } else if (this.type === "star") {
      for (let i = 0; i < this.petals; i++) {
        const a = (TAU / this.petals) * i;
        g.push();
        g.rotate(a);
        g.triangle(0, 0, s, -s * 0.3, s, s * 0.3);
        g.pop();
      }
    } else if (this.type === "tulip") {
      g.beginShape();
      g.vertex(-s * 0.5, 0);
      g.vertex(0, -s);
      g.vertex(s * 0.5, 0);
      g.vertex(0, s * 0.2);
      g.endShape(CLOSE);
    }

    g.fill(this.col2);
    g.circle(0, 0, s * 0.6);
    g.pop();
  }
}


function drawBuildings(g) {
  let x = 0;
  while (x < width) {
    buildings.push(new Building(x));
    x += random(20, 40);
  }
  for (let b of buildings) {
    pg_building.fill(0);
    b.draw(pg_building);
  }
}

class Bubble {
  constructor() {
    this.pos = createVector(
      random(width),
      height + random(50, 150)
    );

    this.vel = createVector(
      random(-0.15, 0.15),
      random(-0.8, -1.4)
    );

    this.acc = createVector(0, 0);

    this.r = random(14, 42);
    this.buoyancy = random(0.016, 0.024);

    this.depth = random(0.3, 1.0);
    this.r *= this.depth;
    this.buoyancy *= this.depth;
    this.drag = lerp(0.995, 0.985, this.depth);

    this.noiseSeed = random(1000);

    // ストライプ用
    this.stripeAngle = random(TAU);
    this.stripeGap = random(5, 9);
    this.stripeCount = int(random(4, 8));
    this.stripePalette = shuffle([...palette]).slice(0, int(random(2, 4)));
    this.stripeColors = [];
    for (let i = 0; i < this.stripeCount; i++) {
      this.stripeColors.push(
        color(this.stripePalette[i % this.stripePalette.length])
      );
    }
  }

  applyForce(f) {
    this.acc.add(f);
  }

  update() {
    // 浮力
    this.applyForce(createVector(0, -this.buoyancy));

    // 水流ノイズ
    const n =
      noise(this.noiseSeed, frameCount * 0.01) - 0.5;
    this.applyForce(createVector(n * 0.025, 0));

    this.vel.add(this.acc);
    this.vel.mult(this.drag);
    this.pos.add(this.vel);

    this.acc.mult(0);
  }

  draw(g) {
    g.push();
    g.translate(this.pos.x, this.pos.y);
    g.rotate(this.stripeAngle);

    const path = this.getWobblyPath();

    // === ① ゆらいだ泡でクリップ ===
    g.drawingContext.save();
    g.drawingContext.beginPath();
    g.drawingContext.moveTo(path[0].x, path[0].y);
    for (let p of path) {
      g.drawingContext.lineTo(p.x, p.y);
    }
    g.drawingContext.closePath();
    g.drawingContext.clip();

    // === ② ストライプで中身を塗る ===
    g.noStroke();

    const stripeH = (this.r * 2) / this.stripeCount;

    for (let i = 0; i < this.stripeCount; i++) {
      const col = this.stripeColors[i % this.stripeColors.length];
      col.setAlpha(lerp(200, 255, this.depth));
      g.fill(col);

      g.rect(
        -this.r,
        -this.r + i * stripeH,
        this.r * 2,
        stripeH + 1
      );
    }

    g.drawingContext.restore();

    // === ③ ゆらいだ輪郭を描く ===
    g.noFill();
    g.stroke(0, 50);
    g.strokeWeight(lerp(0.6, 1.2, this.depth));

    g.beginShape();
    for (let p of path) {
      g.vertex(p.x, p.y);
    }
    g.endShape(CLOSE);

    g.pop();
  }

  getWobblyPath() {
    const pts = [];
    const steps = 60;

    for (let i = 0; i <= steps; i++) {
      const a = (i / steps) * TAU;

      const wobble =
        noise(
          cos(a) + 1 + this.noiseSeed,
          sin(a) + 1,
          frameCount * 0.02
        ) - 0.5;

      const rr = this.r + wobble * 10 * this.depth;

      pts.push({
        x: cos(a) * rr,
        y: sin(a) * rr
      });
    }

    return pts;
  }

  isDead() {
    return this.pos.y < -this.r;
  }
}

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

  const backCount = num * 0.10;
  const midCount  = num * 0.20;
  const frontCount = num * 0.70;

  for (let i = 0; i < backCount; i++) flowersBack.push(new Flower("back"));
  for (let i = 0; i < midCount; i++) flowersMid.push(new Flower("mid"));
  for (let i = 0; i < frontCount; i++) flowersFront.push(new Flower("front"));

  pg_back = createGraphics(windowWidth, windowHeight);
  pg_building = createGraphics(windowWidth, windowHeight);
  pg = createGraphics(windowWidth, windowHeight);

  pixelDensity(1);
  pg_back.pixelDensity(1);
  pg_building.pixelDensity(1);
  pg.pixelDensity(1);

  drawBuildings(pg_building);
}

function draw() {

  pg.background(0);
  if (bubbles.length < MAX_BUBBLES && random() < 0.4) {
    bubbles.push(new Bubble());
  }
  for (let i = bubbles.length - 1; i >= 0; i--) {
    bubbles[i].update();
    bubbles[i].draw(pg);

    if (bubbles[i].isDead()) {
      bubbles.splice(i, 1);
    }
  }
  for (let b of buildings) {
    pg.stroke(0);
    pg.strokeWeight(2);
    pg.noFill()
    b.draw(pg);
  }

  pg_back.background(color(bgColor));
  for (const e of flowersBack) {
    e.update();
    e.draw(pg_back);
  }
  pg_back.filter(BLUR, 4);
  for (const e of flowersMid) {
    e.update();
    e.draw(pg_back);
  }
  pg_back.filter(BLUR, 1);
  for (const e of flowersFront) {
    e.update();
    e.draw(pg_back);
  }

  const img_building = pg_back.get();
  img_building.mask(pg_building);
  image(pg, 0, 0);
  image(img_building, 0, 0);
}