WORK 77
なぞる
A quantity moves. It is never drawn. Two readers follow it. One was calibrated at the start and reports absolute position, so it leaves a continuous line. The other reads only the difference between consecutive frames, and refuses any difference wider than what it has lately been seeing; it can only speak one change at a time, so it leaves dots. Twice, there is a step. Once the instrument's zero moves; once the quantity itself moves. In the reading, the two steps are the same shape. The line follows both. The dots refuse both. Each is therefore right once and wrong once — and neither says so. Both continue at the same smoothness, the same spacing.
動いている量がある。それは一度も描かれない。読み手が二人いて、それぞれ自分の読みだけを残す。ひとりは最初に較正してあって、絶対の位置を言えるので、続いた線になる。もうひとりは隣り合うフレームの差しか見ず、しかも直近に見てきた幅より大きな差は受け取らない。言えるのが一回ぶんの変化だけなので、点で残る。途中で段が二回ある。一度は計器のゼロ点が動き、一度は量そのものが動く。読まれた値の上では、二つはまったく同じ形をしている。線は両方に従い、点は両方を撥ねる。だからどちらも、一度は正しく、一度は外す。そして外したことを、どちらも言わない。同じなめらかさで、同じ間隔で、続ける。
Aの線とBの点は、はじめ完全に重なっている。較正が生きているあいだ、二人は同じことを言う。
離れたあとも、二人とも黙らない。
// なぞる / Tracing
//
// 動いている量がある。それは描かない。
// 読み手が二人いて、それぞれ自分の読みだけを残す。
//
// A は frame 0 で合わせた変換で「位置」を言う。言えるから、続いた線になる。
// B は隣り合うフレームの「差」しか見ない。そして、直近に見てきた幅より大きな差は
// 受け取らない。言えるのが一回ぶんの変化だけなので、点で残る。
//
// 途中で、段が二回ある。
// 一度は計器のゼロ点が動く。一度は、量そのものが動く。
// 計器が読む値の上では、二つはまったく同じ形をしている。
//
// A は両方に従う。B は両方を撥ねる。
// だからどちらも、一度は正しく、一度は外す。
// そして外したことを、どちらも言わない。同じなめらかさで、同じ間隔で、続ける。
//
// 本当の値は一度も描かない。
export default function sketch(p) {
const W = 720;
const R = W * 0.29;
const GI = 380; // 計器のゼロ点が動くフレーム
const GW = 700; // 量そのものが動くフレーム
const OFF = { x: 0.105, y: -0.072 }; // 計器側の段
const WOFF = { x: -0.088, y: -0.092 }; // 世界側の段
const SIG = 0.0016; // 計器のノイズ
const WIN = 24; // B が見てきた幅の窓
const CLIP = 3.0; // B が受け取る上限(窓の rms の何倍まで)
const LAST = 1100;
let cx, cy, t = 0;
let prevS = null, bx = null, by = null, aPrev = null;
let rec = [];
let sd;
const seed = () => {
const d = new Date();
return d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
};
const rnd = () => { sd = (sd * 1103515245 + 12345) & 0x7fffffff; return sd / 0x7fffffff; };
const gauss = () => {
let u = 0, v = 0;
while (u === 0) u = rnd();
while (v === 0) v = rnd();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
};
// 隠れている量。描かない。
const world = (f) => {
const u = f * 0.0135;
const q = f < GW ? { x: 0, y: 0 } : WOFF;
return {
x: Math.sin(u) * 0.62 + Math.sin(u * 2.31 + 1.1) * 0.26 + Math.sin(u * 0.53 + 2.7) * 0.34 + q.x,
y: Math.cos(u * 0.87) * 0.58 + Math.cos(u * 1.97 + 0.4) * 0.22 + Math.sin(u * 0.41 + 1.9) * 0.30 + q.y,
};
};
p.setup = () => {
p.createCanvas(W, W);
sd = seed();
cx = W / 2;
cy = W / 2;
p.background(250, 249, 245);
p.strokeJoin(p.ROUND);
p.strokeCap(p.ROUND);
};
p.draw = () => {
if (t > LAST) { p.noLoop(); return; }
const w = world(t);
const o = t < GI ? { x: 0, y: 0 } : OFF;
const s = { x: w.x + o.x + gauss() * SIG, y: w.y + o.y + gauss() * SIG }; // 計器が読む値
// A ── 較正あり。frame 0 の変換をそのまま使い続ける。
const a = { x: cx + s.x * R, y: cy + s.y * R };
// B ── 較正なし。差だけ。見てきた幅より大きいものは受け取らない。
if (bx === null) { bx = a.x; by = a.y; }
else {
let dx = (s.x - prevS.x) * R;
let dy = (s.y - prevS.y) * R;
const m = Math.hypot(dx, dy);
const rms = rec.length ? Math.sqrt(rec.reduce((q, v) => q + v * v, 0) / rec.length) : m;
if (rec.length >= WIN && m > CLIP * rms) {
const f = (CLIP * rms) / m; // 撥ねる。全部は捨てない。
dx *= f; dy *= f;
} else {
rec.push(m);
if (rec.length > WIN) rec.shift();
}
bx += dx; by += dy;
}
prevS = s;
// ── 描く ─────────────────────────────────────────
p.noFill();
p.stroke(30, 32, 36, 138);
p.strokeWeight(1.1);
if (aPrev) p.line(aPrev.x, aPrev.y, a.x, a.y);
aPrev = a;
p.noStroke();
p.fill(30, 32, 36, 120);
p.circle(bx, by, 1.8);
t++;
};
}