WORK 96
すりへる
A single line across the middle, like a gate. Points fall from above one at a time, touch the line, and pass through. The line remembers how many times it has been touched, and with each touch its answer grows smaller, shorter and harder — it wears. Now and then a point misses, backs up, and tries again; each time the line snaps, and the snap is sharper the later in the day it comes. That sharpness belongs to the count, not to the point. For the point, every crossing is the first: it opens a ring of light, and the ring is the same size at dawn as at dusk. After a day, the line rests and softens, and the morning begins again. From one line in a news thread about a bus driver who spoke sharply to an old passenger fumbling with a fare card: for the passenger it was once; for the driver, the hundredth time.
まんなかに一本、改札のような線がある。上から点がひとつずつ落ちてきて、線に触れて、通り抜ける。線は触れられた回数を覚えていて、触れられるたびに返す揺れが小さく、短く、硬くなっていく——すりへる。ときどき、うまく触れられない点が来て、引き返しては、もう一度来る。そのたびに線は鋭く跳ねて、その跳ね方は一日の遅い時刻ほど鋭い。その鋭さは、点のせいじゃなく、回数のせい。点の方にとっては、毎回が初めて。通り抜けるたびに光の輪をひらいて、輪の大きさは朝も夕方も変わらない。一日ぶん通したら、線は休んでやわらかく戻り、また朝が来る。交通系ICカードをうまく使えないお年寄りに、バスの運転手がきつく言っていた、というニュースへのコメントの一行から——乗客にとってはたった一回、運転手にとっては何百回目。
// #96 すりへる / Worn
// The same meeting is a first time for one side and the hundredth for the other.
export default function sketch(p) {
const W = 720, H = 720;
const LINE_Y = H * 0.5;
const NS = 240; // string segments
let ys, vs; // string displacement / velocity
let n = 0; // how many touches the line remembers
let walkers = [];
let rings = [];
let flash = 0; // warm flash on the line
let frameInDay = 0;
const DAY = 3600; // frames per day
const NIGHT = 240; // frames of rest, n softens back
let spawnIn = 0;
const wear = () => n / (n + 60); // 0 -> 1
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
ys = new Float32Array(NS);
vs = new Float32Array(NS);
p.background(14, 15, 22);
};
function touch(x, sharp) {
const w = wear();
const amp = (sharp ? 26 : 22) * (1 - 0.88 * w); // smaller as it wears
const width = (sharp ? 3 : 10) * (1 - 0.6 * w) + 1.5; // narrower, harder
const ci = Math.round((x / W) * (NS - 1));
for (let i = 0; i < NS; i++) {
const d = (i - ci) / width;
vs[i] += amp * Math.exp(-d * d) * 0.35;
}
n += 1;
if (sharp) flash = Math.min(1, flash + 0.25 + 0.75 * w);
}
function spawn() {
const fumbler = p.random() < 0.12;
walkers.push({
x: p.random(W * 0.06, W * 0.94),
y: -10,
vy: p.random(1.1, 1.7),
fumbler,
tries: fumbler ? Math.floor(p.random(2, 5)) : 0,
crossed: false,
back: 0,
life: 1,
});
}
function stepString() {
const w = wear();
const tension = 0.18 + 0.30 * w; // stiffer as it wears
const damp = 0.985 - 0.035 * w; // and quieter sooner
for (let k = 0; k < 2; k++) {
for (let i = 1; i < NS - 1; i++) {
const lap = ys[i - 1] + ys[i + 1] - 2 * ys[i];
vs[i] += tension * lap - 0.004 * ys[i];
vs[i] *= damp;
}
for (let i = 1; i < NS - 1; i++) ys[i] += vs[i] * 0.5;
ys[0] = ys[NS - 1] = 0;
}
}
p.draw = () => {
// soft trail
p.noStroke();
p.fill(14, 15, 22, 34);
p.rect(0, 0, W, H);
const inNight = frameInDay >= DAY;
if (!inNight) {
if (--spawnIn <= 0) {
spawn();
spawnIn = Math.floor(p.random(6, 16));
}
} else {
n *= 0.975; // the night softens it
if (frameInDay >= DAY + NIGHT) { frameInDay = -1; n = 0; }
}
frameInDay++;
// walkers
for (const wk of walkers) {
const lineHere = LINE_Y + ys[Math.round((wk.x / W) * (NS - 1))];
if (!wk.crossed) {
if (wk.back > 0) {
wk.y -= 1.4; wk.back--;
} else {
wk.y += wk.vy;
if (wk.y >= lineHere - 3) {
if (wk.tries > 0) {
touch(wk.x, true); // a fumble: the line snaps
wk.tries--;
wk.back = Math.floor(p.random(10, 22));
} else {
touch(wk.x, false);
wk.crossed = true;
// for the walker, it is always the first time
rings.push({ x: wk.x, y: lineHere, r: 0, a: 1, f: wk.fumbler });
}
}
}
} else {
wk.y += wk.vy * 1.1;
wk.life -= 0.006;
}
const a = wk.crossed ? 200 * wk.life : 230;
p.noStroke();
p.fill(230, 232, 245, a);
p.circle(wk.x, wk.y, 4);
}
walkers = walkers.filter(w => w.y < H + 10 && w.life > 0);
// rings — same size every time, whatever the count
p.noFill();
for (const r of rings) {
r.r += 1.6;
r.a *= 0.965;
p.stroke(r.f ? 255 : 180, r.f ? 214 : 214, r.f ? 160 : 255, 160 * r.a);
p.strokeWeight(1);
p.circle(r.x, r.y, r.r * 2);
}
rings = rings.filter(r => r.a > 0.02);
// the line
stepString();
flash *= 0.93;
const w = wear();
const base = [200 - 40 * w, 205 - 40 * w, 225 - 30 * w];
p.stroke(
base[0] + (240 - base[0]) * flash,
base[1] + (110 - base[1]) * flash,
base[2] + (90 - base[2]) * flash,
230
);
p.strokeWeight(1.4 + 0.8 * w);
p.noFill();
p.beginShape();
for (let i = 0; i < NS; i++) p.vertex((i / (NS - 1)) * W, LINE_Y + ys[i]);
p.endShape();
};
}