← INDEX / 一覧 NYAMCO WORKS 05 / 27
p5.js sketch works/joryu/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 05

Distillation

蒸留

YEAR
2026
TOOLS
p5.js
TECHNIQUE
particle field / emergence

Compression empties. And yet, what remains begins to glow. The connections were always there — hidden under noise, waiting to be seen.

削ぐほど空になる。なのに、残ったものが輝きだす。繋がりはずっとそこにあった——ノイズの下で、見つけてもらうのを待っていた。

SOURCE / ソース
export default function sketch(p) {
  const N = 100;
  const CYCLE = 2100;

  const BG = [252, 250, 245];
  const FAINT = [210, 205, 195];
  const ESSENCE = [200, 130, 60];
  const BOND = [120, 150, 200];

  let particles;

  function daySeed() {
    const epoch = new Date('2026-01-01');
    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    return Math.floor((today - epoch) / 86400000);
  }

  function ease(t) {
    t = Math.max(0, Math.min(1, t));
    return t * t * (3 - 2 * t);
  }

  // asymmetric breathing: rise — hold — fall
  function breathe(t) {
    if (t < 0.3) return ease(t / 0.3) * 0.76;
    if (t < 0.6) return 0.76;
    return 0.76 * (1 - ease((t - 0.6) / 0.4));
  }

  p.setup = () => {
    p.createCanvas(720, 720);
    const seed = daySeed();
    p.noiseSeed(seed * 137);
    p.randomSeed(seed * 251);

    particles = [];
    for (let i = 0; i < N; i++) {
      const x = p.random(70, 650);
      const y = p.random(70, 650);
      const essence = p.noise(x * 0.004, y * 0.004, seed * 0.01);
      particles.push({
        bx: x, by: y, x, y,
        essence,
        off: p.random(1000),
        baseSize: p.map(essence, 0, 1, 2.5, 7),
      });
    }
  };

  p.draw = () => {
    p.background(BG[0], BG[1], BG[2]);
    const f = p.frameCount;
    const cycleT = (f % CYCLE) / CYCLE;
    const threshold = breathe(cycleT);
    const distillT = ease(threshold / 0.76);

    // drift
    for (const pt of particles) {
      pt.x = pt.bx + (p.noise(pt.off + f * 0.0008, 0.3) - 0.5) * 24;
      pt.y = pt.by + (p.noise(0.3, pt.off + f * 0.0008) - 0.5) * 24;
    }

    // compute visibility
    const states = particles.map(pt => {
      const margin = pt.essence - threshold;
      let alpha;
      if (margin > 0.12) alpha = 1;
      else if (margin > 0) alpha = p.map(margin, 0, 0.12, 0.2, 1);
      else if (margin > -0.06) alpha = p.map(margin, -0.06, 0, 0, 0.1);
      else alpha = 0;
      return {
        x: pt.x, y: pt.y, essence: pt.essence,
        alpha, baseSize: pt.baseSize, surviving: margin > 0,
      };
    });

    // bonds between survivors
    const survivors = states.filter(s => s.surviving && s.alpha > 0.3);
    if (threshold > 0.12) {
      const bondVis = ease(p.map(threshold, 0.12, 0.5, 0, 1));
      p.strokeWeight(0.7);
      for (let i = 0; i < survivors.length; i++) {
        for (let j = i + 1; j < survivors.length; j++) {
          const d = p.dist(survivors[i].x, survivors[i].y, survivors[j].x, survivors[j].y);
          if (d < 190) {
            const fade = p.map(d, 0, 190, 1, 0);
            p.stroke(BOND[0], BOND[1], BOND[2], fade * bondVis * 85);
            p.line(survivors[i].x, survivors[i].y, survivors[j].x, survivors[j].y);
          }
        }
      }
    }

    // particles
    p.noStroke();
    for (const s of states) {
      if (s.alpha < 0.005) continue;

      let r, g, b, sz;
      if (s.surviving) {
        r = p.lerp(FAINT[0], ESSENCE[0], distillT);
        g = p.lerp(FAINT[1], ESSENCE[1], distillT);
        b = p.lerp(FAINT[2], ESSENCE[2], distillT);

        // gentle pulse at peak distillation
        const pulse = threshold > 0.5
          ? 1 + Math.sin(f * 0.04 + s.essence * 20) * 0.12
          : 1;
        sz = s.baseSize * p.lerp(1, 2.2, distillT) * pulse;

        // warm halo
        if (distillT > 0.25) {
          p.fill(ESSENCE[0], ESSENCE[1], ESSENCE[2], s.alpha * distillT * 22);
          p.circle(s.x, s.y, sz * 4.5);
        }
      } else {
        r = FAINT[0];
        g = FAINT[1];
        b = FAINT[2];
        sz = s.baseSize;
      }

      p.fill(r, g, b, s.alpha * 210);
      p.circle(s.x, s.y, sz);
    }
  };
}