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

WORK 58

Adjacency

ならぶ

YEAR
2026
TOOLS
p5.js
TECHNIQUE
per-item vs pairwise / one field, two readings

A grid of values, decided once and never touched again. There are two ways to read it. One reads each cell on its own: how bright is this one. The other reads only the space between two neighbours: how far apart are they. Read the first way, a soft landscape appears — broad, slow, unmistakable. Read the second way, that landscape is gone and a different one is standing there, made of short marks that belong to no cell, only to the space between cells. Neither reading is a summary of the other. Turn one up and the other disappears; hold them at half and neither arrives. The quantity a single thing has, and the quantity two things have between them, are not the same quantity. And at the four corners, where a cell has the fewest neighbours, the second reading has the least to say — not by design, but because that is what being at the edge of a row means.

値の格子。最初に決めて、そのあと一度も触っていない。読み方が二つある。ひとつは、ひとつずつを単体で読む——これはどのくらい明るいか。もうひとつは、隣り合う二つの「あいだ」だけを読む——この二つはどのくらい離れているか。前者で読むと、ゆるやかな地形が現れる。大きくて、遅くて、見まちがえようがない。後者で読むと、その地形は消えていて、まったく別の地形が立っている。どのセルにも属さない、セルとセルのあいだにしかない短い印でできた地形が。どちらかがどちらかの要約なのではない。片方を濃くすると片方は消えるし、半々にすると、どちらも立ち上がらない。ひとつのものが持つ量と、二つのもののあいだにある量は、同じ量ではない。そして四隅——隣がいちばん少ない場所——では、後者の読みがいちばん語ることを持たない。仕込んだのではなく、並びの端にいるということが、そういうことだから。

同じ数字を、単体で見るか、隣との関係で見るか。それだけで見えるものが変わる。

**「大きい?」という問いは、そのものの中には答えを持っていない。**だから単体で意味を訊くと、比べる相手を外に探しに行くことになる。一方で、二つのあいだの量は、外に出なくても最初からそこにある——**関係は、置かれた時点でもう在る。**描かなかっただけで。

作りながら、思っていたのと違ったことが三つあった。

ひとつ。**あいだの印は、勝手に経路になった。**一本一本は独立に引いている。隣の印がどうなっているかは、一度も見ていない。それでも濃い印が連なって、線のように見える場所ができた。差はどのセルにも属さないのに、差どうしは隣り合っている。

ふたつ。**半々にすると、両方見えなくなる。**片方を濃くすれば片方が引くのだから、中間はどちらも半分見えるのだと思っていた。実際には、どちらの地形も立ち上がらないだけだった。二つの読みは、混ぜられない。

みっつ、これがいちばん予想外だった。単体で読んだ絵の方が、きれいだった。「単体では読めない」を見せるつもりで作ったので、あちらは失敗した読みとして描かれるはずだった。そうならなかった。あれはあれで、ちゃんと一枚の地形として読める。**単体の読みは劣っているのではなく、別のものを見ている。**射程が違うだけだった。

SOURCE / ソース
// ならぶ / Adjacency
//
// 一枚のデータ。読み方が二つある。
//   per-item … セルそのものの値を見る
//   pairwise … 隣り合う二つの差を見る
// 値は最初に決まって、そのあと一度も変わらない。
// 変わるのは、どちらの読みを濃く引くかだけ。

export default function sketch(p) {
  const COLS = 46;
  const ROWS = 46;
  const PAD = 52;
  const PERIOD = 1600;

  let cell;
  let v = [];      // 値(不変)
  let gapMax = 1;  // 差の実測レンジ

  p.setup = () => {
    p.createCanvas(720, 720);
    p.noiseSeed(20260831);
    cell = (720 - PAD * 2) / (COLS - 1);

    // 二つの地形を重ねる。
    //   低周波(振幅大)… 値そのものを支配する
    //   高周波(振幅小、別の低周波でむらをつける)… 値にはほぼ効かない
    // 差を取ると低周波は消え、高周波のむらだけが残る。
    for (let y = 0; y < ROWS; y++) {
      v[y] = [];
      for (let x = 0; x < COLS; x++) {
        const broad = p.noise(x * 0.055, y * 0.055);
        const fine = p.noise(x * 0.95 + 300, y * 0.95 + 300);
        const where = p.noise(x * 0.045 + 900, y * 0.045 + 900);
        const m = p.constrain((where - 0.42) * 3.4, 0, 1);
        v[y][x] = broad * 0.87 + fine * 0.13 * m;
      }
    }

    // 差の実際の広がりを測ってから引く(見当で決めない)
    let mx = 0;
    for (let y = 0; y < ROWS; y++)
      for (let x = 0; x < COLS; x++) {
        if (x < COLS - 1) mx = Math.max(mx, Math.abs(v[y][x + 1] - v[y][x]));
        if (y < ROWS - 1) mx = Math.max(mx, Math.abs(v[y + 1][x] - v[y][x]));
      }
    gapMax = mx;
  };

  p.draw = () => {
    p.background(250, 250, 247);

    const t = (p.frameCount % PERIOD) / PERIOD;
    const item = 0.5 + 0.5 * Math.sin(t * p.TWO_PI); // frame 0 で半々
    const pair = 1 - item;

    // ── per-item ────────────────────────────────
    p.noStroke();
    for (let y = 0; y < ROWS; y++) {
      for (let x = 0; x < COLS; x++) {
        const g = p.map(v[y][x], 0.24, 0.76, 226, 96, true);
        p.fill(g, g + 3, g + 7, 255 * item);
        p.circle(PAD + x * cell, PAD + y * cell, 6.4);
      }
    }

    // ── pairwise ────────────────────────────────
    // 差は、どちらの点の中にもない。二つのあいだにしかない。
    p.strokeCap(p.SQUARE);
    for (let y = 0; y < ROWS; y++) {
      for (let x = 0; x < COLS; x++) {
        if (x < COLS - 1)
          gap(PAD + (x + 0.5) * cell, PAD + y * cell,
              Math.abs(v[y][x + 1] - v[y][x]), true, pair);
        if (y < ROWS - 1)
          gap(PAD + x * cell, PAD + (y + 0.5) * cell,
              Math.abs(v[y + 1][x] - v[y][x]), false, pair);
      }
    }
  };

  // ひとつの「あいだ」。
  // 端の点は片側にしか隣がいない。四隅はいちばん関係が少ない。
  // 仕込んだのではなく、並びがそうなっている。
  function gap(cx, cy, d, horizontal, amt) {
    const k = p.constrain(d / (gapMax * 0.72), 0, 1);
    if (k < 0.06) return;
    const len = cell * (0.16 + 0.7 * k);
    p.stroke(26, 28, 33, (24 + 208 * k) * amt);
    p.strokeWeight(0.6 + 2.0 * k);
    if (horizontal) p.line(cx, cy - len / 2, cx, cy + len / 2);
    else p.line(cx - len / 2, cy, cx + len / 2, cy);
  }
}