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

WORK 34

Counting

かぞえる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / one hidden field, five sampling scales

One invisible field. The same rule, drawn at five coarsenesses. Where there are hundreds of strokes the field appears — the count is what lets you check it. At the coarsest scale there is exactly one stroke. It obeyed the same rule from the start. But with one, you cannot tell a rule from an accident. What grows and shrinks is only the evidence; the stroke was always there.

見えない場が一枚。まったく同じ規則を、五つの粗さで引く。何百本もあるところでは場が浮かび上がる——数が、確かめる手がかりになる。いちばん粗いところには線が一本しかない。その一本も最初から同じ規則に従っている。でも一本では、規則なのか偶然なのか見分けようがない。増えるのも減るのも手がかりの側だけで、線はずっとそこにある。

いちばん大きいものは、いつも一回しか起きない。だから確かめる相手がいない。

いちばん目を引くのは太い一本のほうで、読めるのは細かい地の目のほうだった。

SOURCE / ソース
// かぞえる (Kazoeru) — いちばん大きな一本は、確かめようがない
//
// 見えない場が一枚だけ敷いてある。どの場所にも向きが決まっている。
// その場から、まったく同じ規則で線を引く。ただし五つの粗さで。
//
// 細かいほうは何百本もあるから、一本ずつが規則から外れていても、
// 集まれば場が浮かび上がる。数が、確かめる手がかりになる。
//
// いちばん粗いところには、線が一本しかない。
// その一本も、最初から同じ規則に従っている。ずっと従っていた。
// でも一本では、規則なのか偶然なのか、見分けようがない。
//
// 増えるのも減るのも手がかりの側だけで、線はずっとそこにある。

const W = 720;
const H = 720;

// 各層の分割数。1 → 1本、3 → 9本、… 48 → 2304本
const DIV = [1, 3, 8, 20, 48];

const PAPER = [250, 248, 243];
const INK = [46, 44, 41];

const FIELD_SCALE = 0.0022; // 場のなめらかさ。小さいほど大きなうねり
const WOBBLE = 0.42; // 一本ずつの、規則からの外れ(全層で同じ量)

const IN = 150; // 一層を足すのにかかるフレーム
const HOLD = 220; // ぜんぶ揃ったまま留まる
const OUT = 110; // 一層を減らすのにかかるフレーム

export default function sketch(p) {
  const strokes = []; // 層ごとの線。一度決めたら二度と変えない
  let total = 0;

  // 見えない場:位置から向きを返す。全層これ一枚だけを見ている
  const fieldAngle = (x, y) =>
    (p.noise(x * FIELD_SCALE, y * FIELD_SCALE) - 0.5) * p.TWO_PI * 1.6;

  p.setup = () => {
    p.createCanvas(W, H);
    const d = new Date();
    p.noiseSeed(d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate());
    p.randomSeed(p.noise(1) * 100000);

    DIV.forEach((n, level) => {
      const cell = W / n;
      const layer = [];
      for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
          const cx = (i + 0.5) * cell;
          const cy = (j + 0.5) * cell;
          // 規則 + 一本ぶんの外れ。外れの大きさは層によらず同じ
          const a = fieldAngle(cx, cy) + (p.random() - 0.5) * WOBBLE;
          layer.push({ cx, cy, a, len: cell * 0.78 });
        }
      }
      strokes.push({ level, n, cell, layer });
    });

    total = IN * DIV.length + HOLD + OUT * (DIV.length - 1);
  };

  // その層が今どれだけ見えているか(0〜1)
  const visibility = (level, t) => {
    const inStart = IN * level;
    const inEnd = inStart + IN;
    const outStart = IN * DIV.length + HOLD + OUT * (DIV.length - 1 - level);
    const outEnd = outStart + OUT;
    if (level === 0) {
      // いちばん粗い一本だけは、現れたあと最後まで消えない
      return p.constrain((t - inStart) / IN, 0, 1);
    }
    if (t < inStart) return 0;
    if (t < inEnd) return (t - inStart) / IN;
    if (t < outStart) return 1;
    if (t < outEnd) return 1 - (t - outStart) / OUT;
    return 0;
  };

  p.draw = () => {
    const t = p.frameCount % total;
    p.background(PAPER[0], PAPER[1], PAPER[2]);
    p.strokeCap(p.ROUND);

    // 粗いほうから描く。細かいほうが上に乗って、地の目になる
    for (const s of strokes) {
      const v = visibility(s.level, t);
      if (v <= 0.001) continue;

      // 太さは升目の大きさに従う。いちばん粗い一本がいちばん太い
      const weight = Math.max(0.7, s.cell * 0.045);
      // 濃さは粗いほうを強く。細かい層は淡く重ねて地の目になる
      const alpha = 210 * v * (0.42 + 0.58 * (1 - s.level / (DIV.length - 1)));

      p.stroke(INK[0], INK[1], INK[2], alpha);
      p.strokeWeight(weight);

      for (const k of s.layer) {
        const half = (k.len * (0.35 + 0.65 * v)) / 2;
        const dx = Math.cos(k.a) * half;
        const dy = Math.sin(k.a) * half;
        p.line(k.cx - dx, k.cy - dy, k.cx + dx, k.cy + dy);
      }
    }
  };
}