← INDEX / 一覧NYAMCO WORKS93 / 117
p5.js sketchworks/mabutaganai/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 93

Lidless

まぶたがない

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / one rainy window, two eyes, a long exposure

One rainy window, seen by two eyes. On the right, an eye that blinks: it sees now, and now and then its lid comes down. On the left, an eye with no lid: it sees everything since it opened, all at once, as one long exposure. The right one sees each drop and the fish as it passes. The left one loses every drop to haze, and keeps instead what no single moment holds: the slant of the wind, and a dark line wherever the fish once crossed. After a day, it starts again in the morning. From one line in Ogata Kamenosuke's Rainy Morning (1929): the sun, like a fish, has no eyelids.

雨の窓が一つ。それを二つの目で見る。右は、まばたく目。いまを見ていて、ときどき瞼が降りる。左は、まぶたがない目。開いてからのぜんぶを、いっぺんに、重ねて見ている。右には、一粒ずつの雨と、通りすぎる魚が見える。左では雨粒はぜんぶ霞になって、そのかわりに、どの瞬間にも写っていなかったものが残る。風の傾きと、魚がいちど横切ったところの、暗い線。一日ぶん見たら、次の朝にやり直す。尾形亀之助『雨になる朝』(1929)の一行から——太陽には魚のやうにまぶたがない。

SOURCE / ソース
// まぶたがない / Lidless
//
// 雨の窓が一つ。雨粒が落ちて、ときどき魚の影がゆっくり横切る。
// 雨の濃さは、場所によって少しちがう。風のせい。
//
// 同じ窓を、二つの目で見る。
// 右は、まばたく目。いまを見ていて、ときどき瞼が降りる。
// 左は、まぶたがない目。開いてからのぜんぶを、重ねて見ている(フレームの平均)。
//
// 一日ぶん見たら、次の朝にやり直す。
//
// 尾形亀之助「雨になる朝」(1929)の一行から:太陽には魚のやうにまぶたがない。

export default function sketch(p) {
  const W = 720, H = 720;
  const BW = 160, BH = 280;          // 窓(低解像度)
  const S = 2;                       // 表示倍率
  const PW = BW * S, PH = BH * S;    // 320 x 560
  const LX = 26, RX = W - 26 - PW, PY = 70;
  const DAY = 4000;

  const BG = [30, 36, 46];
  const RAIN = [200, 212, 226];
  const FISH = [10, 12, 16];

  let g, img, acc, n, t, day, drops, fish, lid, nextBlink;

  function reset() {
    day = (day ?? -1) + 1;
    p.noiseSeed(20261005 + day * 17);
    p.randomSeed(20261005 + day * 17);
    acc = new Float64Array(BW * BH * 3);
    n = 0; t = 0;
    drops = [];
    fish = { x: -30, y: BH * 0.62, dir: 1, speed: 0.06, wait: 300 };
    lid = 0;
    nextBlink = 120 + p.random(200);
  }

  function density(x) {
    // 風のつくる、雨の濃さの地形(ゆっくり動く)
    return p.pow(p.noise(x * 0.018, t * 0.0004), 2.2) * 1.6;
  }

  function stepScene() {
    // 雨粒を降らせる
    for (let k = 0; k < 6; k++) {
      const x = p.random(BW);
      if (p.random() < density(x)) {
        drops.push({ x, y: -8, v: 2.2 + p.random(1.6), len: 4 + p.random(5) });
      }
    }
    for (const d of drops) { d.y += d.v; d.x += 0.25; }
    drops = drops.filter(d => d.y < BH + 10);

    // 魚:ゆっくり横切って、しばらくいない
    if (fish.wait > 0) {
      fish.wait--;
    } else {
      fish.x += fish.speed * fish.dir * S;
      if ((fish.dir > 0 && fish.x > BW + 30) || (fish.dir < 0 && fish.x < -30)) {
        fish.dir *= -1;
        fish.y = BH * (0.3 + p.random(0.5));
        fish.wait = 400 + p.floor(p.random(600));
      }
    }

    g.background(...BG);
    g.stroke(...RAIN);
    g.strokeWeight(1);
    for (const d of drops) g.line(d.x, d.y, d.x + 0.4, d.y + d.len);

    if (fish.wait === 0) {
      g.noStroke();
      g.fill(...FISH);
      const fx = fish.x, fy = fish.y + p.sin(t * 0.03) * 2, s = fish.dir;
      g.ellipse(fx, fy, 26, 10);
      g.triangle(fx - s * 12, fy, fx - s * 21, fy - 6, fx - s * 21, fy + 6);
    }
  }

  function expose() {
    g.loadPixels();
    const px = g.pixels;
    n++;
    for (let i = 0, j = 0; i < BW * BH; i++, j += 4) {
      acc[i * 3] += px[j];
      acc[i * 3 + 1] += px[j + 1];
      acc[i * 3 + 2] += px[j + 2];
    }
    img.loadPixels();
    const q = img.pixels;
    for (let i = 0, j = 0; i < BW * BH; i++, j += 4) {
      q[j] = acc[i * 3] / n;
      q[j + 1] = acc[i * 3 + 1] / n;
      q[j + 2] = acc[i * 3 + 2] / n;
      q[j + 3] = 255;
    }
    img.updatePixels();
  }

  function frameFor(x, label) {
    p.noFill();
    p.stroke(70, 76, 88);
    p.rect(x - 0.5, PY - 0.5, PW + 1, PH + 1);
    p.noStroke();
    p.fill(150, 156, 168);
    p.textSize(12);
    p.textAlign(p.LEFT, p.TOP);
    p.text(label, x, PY + PH + 14);
  }

  p.setup = () => {
    p.createCanvas(W, H);
    g = p.createGraphics(BW, BH);
    g.pixelDensity(1);
    img = p.createImage(BW, BH);
    p.textFont('monospace');
    reset();
  };

  p.draw = () => {
    t++;
    stepScene();
    expose();

    p.background(16, 18, 22);
    p.noSmooth();
    p.image(img, LX, PY, PW, PH);
    p.image(g, RX, PY, PW, PH);
    p.smooth();

    // まばたき(右だけ)
    if (t >= nextBlink) {
      lid += 0.25;
      if (lid >= 2) { lid = 0; nextBlink = t + 150 + p.random(260); }
    }
    const close = lid < 1 ? lid : 2 - lid;
    if (close > 0) {
      p.noStroke();
      p.fill(8, 9, 11);
      p.rect(RX, PY, PW, PH * close);
    }

    frameFor(LX, 'lidless — all of it, since it opened');
    frameFor(RX, 'blinking — now');

    // 一日の進み
    p.stroke(60, 66, 78);
    p.line(LX, 40, W - LX, 40);
    p.stroke(160, 168, 182);
    p.line(LX, 40, LX + (W - 2 * LX) * (t / DAY), 40);

    if (t >= DAY) reset();
  };
}