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

WORK 60

Sway

ふる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
ノイズによる多関節の揺れ/非対称な追従(止まるのは速く、ほどけるのは遅い)

Small things sway across the field. An unseen point drifts through, and wherever it passes, the swaying stops. Stopping is quick; loosening is slow. What has stopped is not dead — only looked at.

画面じゅうで小さなものが揺れている。見えない点がゆっくり巡回していて、通りかかったところは揺れが止まる。止まるのは速く、ほどけるのは遅い。止まっているところは、死んでいるわけではない。見られているだけ。

観測点は描かれない。だから画面に見えるのは、静かになった場所のほうだけ。

描いてから気づいたこと。止まったものの方が、きれいに見える。揺れをなくせば向きが揃うのだから当たり前なのだけれど、揃うと整って見える、というところまでは考えていなかった。だから画面でいちばん目を引くのは、その静かなところになる。動いているものは散らかっていて、目に留まらない。

SOURCE / ソース
// ── ふる (Furu) ──────────────────────────────────────────────────
// 画面じゅうで小さなものが揺れている。
// 見えない観測点がゆっくり巡回していて、通りかかったところは揺れが止まる。
// 止まるのは速く、戻るのは遅い。観測点そのものは描かれない。
export default function sketch(p) {
  const W = 720, H = 720;

  const COLS = 26, ROWS = 26;      // 毛の格子
  const JOINTS = 4;                // 一本あたりの節(根元→先)
  const SEG_LEN = 7.0;             // 節の長さ
  const SWAY = 0.72;               // 揺れの最大角(rad)
  const GAZE_R = 132;              // 観測点の効く半径
  const CATCH = 0.34;              // 止まるときの追従(速い)
  const RELEASE = 0.022;           // ほどけるときの追従(遅い)

  let hairs;
  let t = 0;
  let bg;

  p.setup = () => {
    p.createCanvas(W, H);

    // 今日の「ふる」は今日だけのもの
    const d = new Date();
    const day = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
    p.noiseSeed(day * 613);
    p.randomSeed(day * 379);

    // ---- 背景(わずかにむらのある暗い面) ----
    bg = p.createGraphics(W, H);
    bg.noStroke();
    const step = 8;
    for (let x = 0; x < W; x += step) {
      for (let y = 0; y < H; y += step) {
        const n = p.noise(x * 0.005, y * 0.005);
        bg.fill(13 + n * 7, 13 + n * 6, 16 + n * 9);
        bg.rect(x, y, step, step);
      }
    }

    // ---- 毛を格子に置く(少しずらす) ----
    hairs = [];
    const mx = W / (COLS + 1), my = H / (ROWS + 1);
    for (let i = 0; i < COLS; i++) {
      for (let j = 0; j < ROWS; j++) {
        hairs.push({
          x: mx * (i + 1) + p.random(-mx * 0.28, mx * 0.28),
          y: my * (j + 1) + p.random(-my * 0.28, my * 0.28),
          base: p.random(p.TWO_PI),          // 生えている向き
          phase: p.random(1000),             // 揺れの位相(個体ごと)
          rate: p.random(0.010, 0.021),      // 揺れの速さ
          still: 0,                          // 0=揺れてる 1=止まってる
        });
      }
    }
  };

  // 観測点。リサージュでゆっくり巡回する。描かない。
  const gazeAt = (tt) => ({
    x: W / 2 + Math.sin(tt * 0.00381) * (W * 0.36) + Math.sin(tt * 0.00117) * 52,
    y: H / 2 + Math.cos(tt * 0.00265) * (H * 0.36) + Math.cos(tt * 0.00153) * 44,
  });

  p.draw = () => {
    p.image(bg, 0, 0);
    const g = gazeAt(t);

    p.noFill();
    for (const h of hairs) {
      // ---- 観測されているか ----
      const d = p.dist(h.x, h.y, g.x, g.y);
      const target = d < GAZE_R ? 1 - (d / GAZE_R) * (d / GAZE_R) : 0;
      // 止まるのは速く、ほどけるのは遅い
      const k = target > h.still ? CATCH : RELEASE;
      h.still += (target - h.still) * k;

      const amp = SWAY * (1 - h.still);

      // ---- 節をつないで描く ----
      let x = h.x, y = h.y;
      let ang = h.base - amp * 0.5;
      // 止まっているものほど色が抜けて、冷たくなる
      const warm = 1 - h.still;
      const r = 96 + warm * 122;
      const gg = 104 + warm * 74;
      const b = 128 + warm * 18;

      p.stroke(r, gg, b, 150 + warm * 85);
      p.strokeWeight(1.25 - h.still * 0.45);
      p.beginShape();
      p.vertex(x, y);
      for (let s = 0; s < JOINTS; s++) {
        const n = p.noise(h.phase + s * 0.55, t * h.rate);
        ang += (n - 0.5) * 2 * amp;
        x += Math.cos(ang) * SEG_LEN;
        y += Math.sin(ang) * SEG_LEN;
        p.vertex(x, y);
      }
      p.endShape();

      // 先端。止まっているものだけ、かすかに点が残る
      if (h.still > 0.12) {
        p.noStroke();
        p.fill(150, 158, 176, h.still * 105);
        p.circle(x, y, 1.7 + h.still * 1.5);
        p.noFill();
      }
    }

    t += 1;
  };
}