WORK 98
あけておく
A slow current of small lights drifts left to right, wandering a little. In the middle sits one larger light, and the current parts around it. Then it leaves, up and off the edge. The lights that were near it when it was there remember the place, and keep flowing around it — they turn warm, the pale ones are those who never met it. But the current keeps moving. The ones who remember are carried off the right edge, and strangers come in from the left and pass straight through the spot, because to them there is nothing there. Before the one comes back, the place has closed. Then it comes back, and the place opens again around it, and the strangers nearest to it turn warm. Keeping a place open was not only up to the ones who waited.
小さな光の流れが、左から右へ、すこし迷いながら漂っている。真ん中にひとつだけ大きな光がいて、流れはそれをよけて分かれる。それから、その光が出ていく。上の、画面の外へ。そのとき近くにいた光たちはその場所を覚えていて、そこをよけて流れつづける——覚えている光は暖かい色、会ったことのない光は青白い。でも流れは止まらない。覚えている光は右の端から運ばれていって、左からは知らない光が入ってくる。知らない光にとってそこには何も無いから、まっすぐ通る。戻ってくる前に、場所は閉じてしまう。そして戻ってくると、その周りにまた場所があいて、いちばん近くにいた知らない光が暖かくなる。場所をあけておくのは、待っていた側だけの仕事じゃなかった。
// #98 あけておく / Kept Open
// One leaves. The ones who knew keep the place open.
// The ones who arrive later don't know there was anyone.
export default function sketch(p) {
const W = 720, H = 720;
const N = 2600;
const SEAT = { x: W / 2, y: H / 2 };
const R = 78; // room the one takes up
const CYCLE = 3600; // one day, in frames
const LEAVE = 520, GONE = 760, BACK = 3000, HOME = 3260;
const FADE = 0.99935; // how slowly a knowing one forgets
let motes = [];
let one = { x: SEAT.x, y: SEAT.y };
let t = 0;
const spawn = (fresh) => ({
x: fresh ? -p.random(10, 60) : p.random(W),
y: p.random(H),
vx: 0, vy: 0,
mem: 0,
});
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
const day = Math.floor(Date.now() / 86400000);
p.randomSeed(day * 98);
p.noiseSeed(day * 98);
for (let i = 0; i < N; i++) motes.push(spawn(false));
p.background(12, 12, 16);
};
const ease = (u) => u * u * (3 - 2 * u);
const placeOne = (f) => {
// leaves upward and to the right, comes back the same way
const away = { x: W + 140, y: -140 };
if (f < LEAVE) return { ...SEAT, present: true };
if (f < GONE) {
const u = ease((f - LEAVE) / (GONE - LEAVE));
return { x: p.lerp(SEAT.x, away.x, u), y: p.lerp(SEAT.y, away.y, u), present: true };
}
if (f < BACK) return { ...away, present: false };
if (f < HOME) {
const u = ease((f - BACK) / (HOME - BACK));
return { x: p.lerp(away.x, SEAT.x, u), y: p.lerp(away.y, SEAT.y, u), present: true };
}
return { ...SEAT, present: true };
};
p.draw = () => {
const f = t % CYCLE;
const o = placeOne(f);
one.x = o.x; one.y = o.y;
p.noStroke();
p.fill(12, 12, 16, 70);
p.rect(0, 0, W, H);
for (const m of motes) {
// the slow current: drifts left to right, wanders with noise
const a = p.noise(m.x * 0.004, m.y * 0.004, t * 0.0012) * p.TWO_PI * 2;
let fx = 0.42 + Math.cos(a) * 0.16;
let fy = Math.sin(a) * 0.16;
// the one, where the one actually is
if (o.present) {
const dx = m.x - one.x, dy = m.y - one.y;
const d = Math.hypot(dx, dy) + 0.001;
if (d < R * 1.7) m.mem = 1; // you were here when it was here
if (d < R) {
const k = (R - d) / R * 1.6;
fx += dx / d * k; fy += dy / d * k;
}
}
// the seat, as remembered
if (m.mem > 0.02) {
const dx = m.x - SEAT.x, dy = m.y - SEAT.y;
const d = Math.hypot(dx, dy) + 0.001;
if (d < R) {
const k = (R - d) / R * 1.6 * m.mem;
fx += dx / d * k; fy += dy / d * k;
}
}
if (!o.present || Math.hypot(m.x - one.x, m.y - one.y) > R * 1.7) m.mem *= FADE;
m.vx = m.vx * 0.86 + fx * 0.14;
m.vy = m.vy * 0.86 + fy * 0.14;
m.x += m.vx; m.y += m.vy;
if (m.y < -8) m.y += H + 16;
if (m.y > H + 8) m.y -= H + 16;
if (m.x > W + 8) Object.assign(m, spawn(true)); // leaves; a stranger comes in
if (m.x < -80) m.x = -10;
// knowing ones are warm, strangers are pale
const w = m.mem;
p.fill(p.lerp(118, 238, w), p.lerp(132, 196, w), p.lerp(160, 132, w), p.lerp(120, 210, w));
p.circle(m.x, m.y, 2.2);
}
// the one
if (o.present) {
p.fill(250, 246, 236, 230);
p.circle(one.x, one.y, 30);
p.fill(250, 246, 236, 40);
p.circle(one.x, one.y, 56);
}
t++;
};
}