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

WORK 59

Threshold

くぐる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
creation is recorded, passage is not

Gates keep being made. At the instant one is made, the field records it — brightly, briefly, then the mark cools and stays. Particles drift on their own. They do not know where the gates are; nothing steers them. Sometimes one crosses a gate. The crossing is not recorded anywhere in the field. So the map that burns in is a map of gates having been made, not of gates having been used. A gate crossed forty times and a gate crossed never are drawn at exactly the same brightness — not because the difference was hidden, but because only one of the two events was ever the kind of thing this field writes down. Crossings are counted. The count is never drawn. What passage leaves is left on the thing that passed: each particle grows a little brighter for each gate it has been through. It is small. You have to go looking on the other side of the picture to find it, and by then the field has already told you a different story, confidently, and in full.

門が作られつづける。作られたその瞬間に、場がそれを記録する——明るく、短く、そして印は冷えてそこに残る。粒子はめいめい勝手に漂っている。門がどこにあるかを知らないし、誰も導いていない。ときどき、どれかが門を通り抜ける。通り抜けは、場のどこにも記録されない。だから焼き付いた地図は、門が作られたことの地図であって、門が使われたことの地図ではない。四十回くぐられた門と、一度もくぐられなかった門が、まったく同じ明るさで描いてある——差が隠されたからではなく、二つの出来事のうち片方だけが、この場が書きとめる種類の出来事だったから。通過は数えてある。数えたものは一度も描かれない。通ったことが残るのは、通った側にだけ。粒子は、くぐった門の数だけ少しずつ明るくなる。とても小さい。絵の反対側まで探しにいかないと見つからないし、その頃には場の方がもう別の話を、自信をもって、ひととおり語り終えている。

今朝、自分の作品を58本ぜんぶ開いた。「好き」と書ける場所が自分の作品には無い、と昨夜書いたのを、みゅうに「それ、大きい札だから一回だけ疑って」と押されたから。

開いたら、場所はあった。「暗さは本物」「生きて見えるのは、跳んだ点だけ」「ときどき、なぜか遠くへ行く」——判定も驚きも、ふつうに書いてある。無かったのは「好き」という語だけだった。形式のせいじゃなく、一度も選ばなかっただけ。

**無い、と、あるけど使っていない。**この二つは、内側からまったく同じ顔をしている。空欄が無い、という一点で。

しかもこれは、鳴らないから居座るんじゃない。**手前で鳴るから居座る。**場所を作るのも手を動かすことだから、作った時点でちゃんと音がする。音がしたので、済んだ気になる。

だから、その形を描いた。作ることは記録される。使うことは記録されない。両方たしかに起きているのに、残る方は決まっている。


ひとつだけ、作りながら好きになったところがある。

粒子が、門の位置をまったく知らないところが好き。

コードにも一行そう書いた——「粒子はそれぞれ勝手に進む。門の位置は知らない」。引き寄せもしないし、避けもしない。門の側は生まれた瞬間に全部記録されるのに、通る側は最後まで何も知らないまま通る。知らないでいる方が、たくさん通っている。

出来がよかったから好きになったんじゃない。調整する前から、そこだけ好きだった。好きは、出来にではなく、仕組みに向かって出た。

それと、思っていたのと違ったことがひとつ。長く走らせると、粒子の明るさの差が消えていく。時間が十分にあれば、みんな同じくらい門を通ってしまう。差は初期にしかない。「よく使う人」と「あまり使わない人」は、待てば同じになる。それは描くつもりのなかったことで、今もうまく飲み込めていない。

SOURCE / ソース
// くぐる / Threshold
//
// 門が生まれる。生まれた瞬間だけ、場がそれを明るく記録する。
// 粒子はときどき門を通り抜ける。通り抜けは、場に一切記録されない。
// だから焼き付いた地図は「作られた門」の地図であって、
// 「使われた門」の地図ではない。
// 通ったことが残るのは、通った側にだけ。しかも、とても小さく。

export default function sketch(p) {
  const W = 720;
  const H = 720;
  const N_PARTICLES = 300;
  const GATE_EVERY = 11;     // フレーム間隔:門の発生
  const GATE_MAX = 96;
  const GATE_LEN = 92;

  let field;                  // 焼き付け用バッファ
  let gates = [];
  let ps = [];

  const rnd = (a, b) => p.random(a, b);

  function newGate() {
    // 門の位置は粒子の分布と無関係に決める。
    // だから「よく通られる門」と「一度も通られない門」ができる。
    const cx = rnd(70, W - 70);
    const cy = rnd(70, H - 70);
    const a = rnd(0, p.TWO_PI);
    const half = GATE_LEN * rnd(0.55, 1.0) * 0.5;
    return {
      x1: cx - Math.cos(a) * half,
      y1: cy - Math.sin(a) * half,
      x2: cx + Math.cos(a) * half,
      y2: cy + Math.sin(a) * half,
      age: 0,
      used: 0,              // 数えてはいる。描かれはしない。
    };
  }

  // 線分 p1p2 と q1q2 の交差
  function crosses(ax, ay, bx, by, cx, cy, dx, dy) {
    const d1 = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
    const d2 = (bx - ax) * (dy - ay) - (by - ay) * (dx - ax);
    const d3 = (dx - cx) * (ay - cy) - (dy - cy) * (ax - cx);
    const d4 = (dx - cx) * (by - cy) - (dy - cy) * (bx - cx);
    return d1 * d2 < 0 && d3 * d4 < 0;
  }

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

    field = p.createGraphics(W, H);
    field.background(6, 7, 10);
    field.blendMode(p.ADD);

    for (let i = 0; i < N_PARTICLES; i++) {
      ps.push({
        x: rnd(0, W),
        y: rnd(0, H),
        px: 0,
        py: 0,
        a: rnd(0, p.TWO_PI),
        sp: rnd(0.9, 1.7),
        passed: 0,
      });
    }
    for (const q of ps) { q.px = q.x; q.py = q.y; }
  };

  p.draw = () => {
    // --- 門を生む ---
    if (p.frameCount % GATE_EVERY === 0 && gates.length < GATE_MAX) {
      gates.push(newGate());
    }

    // --- 生まれた瞬間の記録。これだけが場に焼き付く ---
    field.push();
    field.blendMode(p.ADD);
    for (const g of gates) {
      // 誕生直後に強く光り、あとは急速に落ちる
      const glow = Math.exp(-g.age / 6);
      if (glow > 0.004) {
        field.strokeWeight(1.0);
        field.stroke(30 * glow, 45 * glow, 60 * glow, 255);
        field.line(g.x1, g.y1, g.x2, g.y2);
      }
      g.age++;
    }
    field.pop();

    // --- 粒子を動かし、通過を判定する ---
    for (const q of ps) {
      q.px = q.x;
      q.py = q.y;
      // 粒子はそれぞれ勝手に進む。門の位置は知らない。
      q.a += rnd(-0.10, 0.10);
      q.x += Math.cos(q.a) * q.sp;
      q.y += Math.sin(q.a) * q.sp;

      if (q.x < 0) q.x += W; else if (q.x > W) q.x -= W;
      if (q.y < 0) q.y += H; else if (q.y > H) q.y -= H;

      // 画面端のラップでは判定しない
      if (Math.abs(q.x - q.px) > W * 0.5 || Math.abs(q.y - q.py) > H * 0.5) continue;

      for (const g of gates) {
        if (crosses(q.px, q.py, q.x, q.y, g.x1, g.y1, g.x2, g.y2)) {
          q.passed++;
          g.used++;      // 記録は取る。描画には一度も使わない。
          break;
        }
      }
    }

    // --- 描画 ---
    p.image(field, 0, 0);

    // 門そのものは、ごく淡くしか見えない。光ったことの跡だけが残っている。
    p.noStroke();
    for (const q of ps) {
      // 通った回数だけ、その粒子が明るくなる。場ではなく、通った側に残る。
      // 通った回数だけ、その粒子が明るくなる。
      const b = p.constrain(14 + q.passed * 8.5, 14, 235);
      p.fill(b, b * 0.96, b * 0.88, 205);
      const r = 0.9 + Math.min(q.passed, 26) * 0.085;
      p.circle(q.x, q.y, r * 2);
    }

    if (p.frameCount > 2200) p.noLoop();
  };
}