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

WORK 68

Entering

はいる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
1D wave equation / independent draws

Something enters the surface. The entering is never drawn — there is no moment where a thing crosses the line. What appears is a wrinkle, which travels outward from a place where nothing was seen to happen, and that wrinkle is the only proof that anything arrived. Sometimes, later, a thin thing rises from the same spot. How far it rises and how fast are decided separately, with no reference to how hard the thing went in, so the deepest wrinkle can produce nothing and a nearly flat stretch can send up the tallest line.

何かが水面に入る。入るところは一度も描かれない——線を越える瞬間が絵の中に無い。現れるのは皺で、それは何も起きたように見えなかった場所から外へ伝わっていき、その皺だけが、何かが着いたことの証しになる。しばらくして、同じ場所から細いものが上がってくることがある。どこまで上がるか、どの速さで上がるかは別々に決められていて、何がどれくらいの強さで入ったかは参照されない。だから、いちばん深い皺からは何も上がらないことがあるし、ほとんど平らになった場所からいちばん高い線が伸びることがある。

出発点は左川ちか「緑色の透視」(1930年代)。

 走れ! 私の心臓  球になって 彼女の傍へ

 そしてティカップの中を

 ――かさなり合った愛 それは私らを不幸にする 牛乳の皺がゆれ 私の夢は上昇する

「中を」で切れて、動詞が来ない。入ったとも、通ったとも書かれない。それなのに次の行で牛乳の皺が揺れている。入ったことは、波紋でだけ証される。 そして上がるのは、心臓ではなく夢の方だった。

だからこの絵では、入る動作を一フレームも描かないことにした。水面は本物の波動方程式で、皺は装飾ではなく、投げ込まれた運動量が両側へ逃げていく実体そのもの。見えるのは結果だけ。

上がるものの高さと速さは、乱数を別に引いている。相関をゼロにしたのは、そう見えるようにするためではなく、そうでないと嘘になるからだ。深く入ったものが高く上がる絵は、入るものと上がるものを同じ一つのものだと言ってしまう。

ほとんどの時間、何も上がってこない。それも描いた。

SOURCE / ソース
const SIZE = 720;
const N = 360;              // surface nodes
const C2 = 0.52;            // wave speed squared — kept under 1 so the surface stays stable
const DAMP = 0.9918;        // wrinkles flatten, slowly
const SURFACE = SIZE * 0.63;
const FADE = 13;

export default function sketch(p) {
  let u, u0, u1;            // current / previous / scratch
  let risers = [];
  let pending = [];         // a wrinkle that may or may not become a rise
  let nextDrop = 0;

  const idx = (i) => Math.max(0, Math.min(N - 1, i));

  // something enters. the entry itself is never drawn.
  const enter = () => {
    const i = Math.floor(p.random(N * 0.12, N * 0.88));
    const amp = p.random(2.5, 13);            // how hard it went in
    const w = Math.floor(p.random(2, 7));
    for (let k = -w; k <= w; k++) {
      const f = Math.cos((k / w) * p.HALF_PI);
      u[idx(i + k)] -= amp * f * f;
    }
    // whether anything comes back up is decided here, separately,
    // and how big it is has nothing to do with how hard it went in
    if (p.random() < 0.62) {
      pending.push({
        i,
        wait: Math.floor(p.random(26, 130)),
        h: p.random(SIZE * 0.10, SIZE * 0.52),   // independent of amp
        v: p.random(0.5, 2.4),                   // independent of amp
        wob: p.random(0.6, 3.2),
        seed: p.random(1000),
      });
    }
  };

  p.setup = () => {
    p.createCanvas(SIZE, SIZE);
    const d = new Date();
    const seed = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
    p.noiseSeed(seed);
    p.randomSeed(seed);
    p.background(9, 9, 11);
    p.strokeCap(p.ROUND);

    u = new Float32Array(N);
    u0 = new Float32Array(N);
    u1 = new Float32Array(N);
    nextDrop = 40;
  };

  p.draw = () => {
    p.noStroke();
    p.fill(9, 9, 11, FADE);
    p.rect(0, 0, SIZE, SIZE);

    if (--nextDrop <= 0) {
      enter();
      nextDrop = Math.floor(p.random(70, 260));
    }

    // 1D wave — the wrinkle travels outward from a place nothing was seen to enter
    for (let i = 1; i < N - 1; i++) {
      u1[i] = (2 * u[i] - u0[i] + C2 * (u[i + 1] - 2 * u[i] + u[i - 1])) * DAMP;
    }
    u1[0] = u1[1] * 0.5;
    u1[N - 1] = u1[N - 2] * 0.5;
    const t = u0; u0 = u; u = u1; u1 = t;

    // the surface
    p.noFill();
    p.stroke(206, 213, 222, 120);
    p.strokeWeight(1.1);
    p.beginShape();
    for (let i = 0; i < N; i++) {
      p.vertex((i / (N - 1)) * SIZE, SURFACE + u[i]);
    }
    p.endShape();

    // things that came up. they leave from where a wrinkle began,
    // and they are not the thing that made it.
    for (let k = pending.length - 1; k >= 0; k--) {
      if (--pending[k].wait <= 0) {
        risers.push({ ...pending[k], y: 0, life: 0 });
        pending.splice(k, 1);
      }
    }

    for (let k = risers.length - 1; k >= 0; k--) {
      const r = risers[k];
      r.y += r.v;
      const prog = r.y / r.h;
      if (prog >= 1) { risers.splice(k, 1); continue; }

      const x0 = (r.i / (N - 1)) * SIZE;
      const base = SURFACE + u[idx(r.i)];

      // the whole of it is there while it is there, and then it is not
      p.noFill();
      p.stroke(226, 231, 238, 205);
      p.strokeWeight(0.9);
      p.beginShape();
      for (let y = 0; y <= r.y; y += 3) {
        const dx = (p.noise(r.seed, y * 0.004) - 0.5) * r.wob * 34;
        p.vertex(x0 + dx, base - y);
      }
      p.endShape();
    }
  };
}