WORK 85
ふける
One outline changes its shape slowly. In a single frame it moves less than a hundredth of a pixel. Behind it are two old copies. The near copy is retaken every half second, like someone who sees you every day. The far copy is retaken every thirty seconds, like someone meeting you after ten years. The near copy never leaves the present outline — it is drawn, and it cannot be seen. Only the far copy shows the gap, and the moment it is retaken the gap goes back to zero. About a third of the outline barely moves even over thirty seconds. The change was not everywhere; it gathered in a few places.
ひとつの輪郭が、ゆっくり形を変えていく。一フレームで動くのは、百分の一ピクセルに満たない。その後ろに、昔の写しが二枚ある。近い写しは半秒ごとに撮り直す。毎日そばにいる人。遠い写しは三十秒ごとに撮り直す。十年ぶりに会う人。近い写しは今の輪郭から離れない。描いてあるのに、見えない。ずれを見せるのは遠い写しだけで、撮り直した瞬間、ずれはゼロに戻る。三十秒たっても、輪郭の三分の一ほどは、ほとんど動いていない。変化は全体にではなく、いくつかの場所に集まっていた。
作る前に、予想を三つ書いた。
近い写しは青で描いている。一度も見えなかった。
// ふける / Aging
//
// ひとつの輪郭が、ゆっくり形を変えていく。一フレームの変化は、目では見えない。
// その後ろに、昔の写しが二枚ある。
// 近い写し … 半秒ごとに撮り直す。毎日そばにいる人。
// 遠い写し … 三十秒ごとに撮り直す。十年ぶりに会う人。
// 近い写しは、ずっと今と重なっている。遠い写しだけが、ずれを見せる。
// 撮り直した瞬間、ずれはゼロに戻る。会えば、また今になる。
function mulberry32(a) {
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export const W = 720, H = 720;
export const R = 190;
export const M = 360; // 輪郭の点の数
export const NEAR = 30; // 近い写しの間隔(フレーム)
export const FAR = 1800; // 遠い写しの間隔(フレーム)
export function makeModel(seed = 927) {
const rnd = mulberry32(seed);
const H = [];
for (let k = 2; k <= 7; k++) {
H.push({
k,
a: (0.10 / k) * (0.6 + 0.8 * rnd()) * R,
p0: rnd() * Math.PI * 2,
w: (rnd() - 0.5) * 0.0009, // 位相のゆっくりした回り
am: 0.4 * rnd(), aw: 0.0002 + 0.0004 * rnd(), ap: rnd() * 6.28, // 振幅のゆらぎ
});
}
function shape(f) {
const out = new Float32Array(M);
for (let i = 0; i < M; i++) {
const th = (i / M) * Math.PI * 2;
let r = R;
for (const h of H) {
const amp = h.a * (1 + h.am * Math.sin(h.ap + h.aw * f));
r += amp * Math.cos(h.k * th + h.p0 + h.w * f);
}
out[i] = r;
}
return out;
}
return { shape };
}
export default function sketch(p) {
const model = makeModel();
let frame = 0;
let near = model.shape(0), far = model.shape(0), farAt = 0;
const cx = W / 2, cy = H / 2 - 20;
function outline(rs, col, wgt) {
p.stroke(...col); p.strokeWeight(wgt); p.noFill();
p.beginShape();
for (let i = 0; i <= M; i++) {
const j = i % M, th = (j / M) * Math.PI * 2;
p.vertex(cx + rs[j] * Math.cos(th), cy + rs[j] * Math.sin(th));
}
p.endShape();
}
p.setup = () => { p.createCanvas(W, H); };
p.draw = () => {
if (frame % NEAR === 0) near = model.shape(frame);
if (frame % FAR === 0) { far = model.shape(frame); farAt = frame; }
const now = model.shape(frame);
p.background(18, 19, 24);
// 遠い写しとのあいだを、薄く塗る
p.noStroke();
for (let i = 0; i < M; i++) {
const th0 = (i / M) * Math.PI * 2, th1 = ((i + 1) / M) * Math.PI * 2;
const j = (i + 1) % M;
p.fill(214, 150, 110, 40);
p.quad(cx + now[i] * Math.cos(th0), cy + now[i] * Math.sin(th0),
cx + far[i] * Math.cos(th0), cy + far[i] * Math.sin(th0),
cx + far[j] * Math.cos(th1), cy + far[j] * Math.sin(th1),
cx + now[j] * Math.cos(th1), cy + now[j] * Math.sin(th1));
}
outline(far, [214, 150, 110, 200], 1.2); // 十年ぶりの人
outline(near, [120, 150, 200, 200], 1.2); // 毎日の人
outline(now, [240, 236, 228, 255], 2.2); // 今
// 下:遠い写しを撮ってからの時間
const x0 = W * 0.2, x1 = W * 0.8, y = H - 60;
p.stroke(80, 82, 94); p.strokeWeight(1); p.line(x0, y, x1, y);
p.stroke(214, 150, 110); p.strokeWeight(2);
p.line(x0, y, p.map(frame - farAt, 0, FAR, x0, x1), y);
frame++;
};
}