WORK 61
よる
Three lines, each moving on its own. Only the direction of their drift is shared. Seen from that direction, they read as a single line. They did not become one — the viewing angle simply arrived there. When it passes, they scatter back into three. Even at the moment of overlap the colors do not fully merge; the trace of having been three stays in the seam.
三本の線が、それぞれ勝手に動いている。動く向きだけが一つに決まっていて、その向きから見たとき、三本は一本に見える。一本になったのではない。見る角度が、そこに来ただけ。角度が過ぎれば、また三本に散る。重なっている瞬間も色は混ざりきらず、三本だった痕跡が継ぎ目に残る。
揃う手前を額に入れた。揃った瞬間ではなく。
一本に見える角度を答えとして置くと、この作品のいちばんよいところが薄れる気がした。近づいている途中のほうが正確だと思う。
はじめ揃う二十三フレーム手前を選んだら、もう一本にしか見えなかった。三本だったことが読めない絵になっていた。角度にして五度では、離れていることが離れていることとして残らないらしい。
そして意図していなかったのは、寄っているのが両端で、いちばん離れているのが真ん中だったこと。芯の湾曲と投影がそうさせただけで、こちらは何も決めていない。
// ── よる (Align) ─────────────────────────────────────────────────
// 三本の線が、それぞれ勝手に動いている。
// でも動く方向は一つだけ決まっていて、その方向から見たとき、三本は一本に見える。
// 一本になったのではない。見る角度が、そこに来ただけ。
// 重なっても色は混ざりきらない。三本だった痕跡が残る。
export default function sketch(p) {
const W = 720, H = 720;
const N = 3; // 線の本数
const PTS = 240; // 一本あたりの分割
const SPAN = 520; // 芯の長さ
const SPREAD = 96; // 三本の間隔(u 方向)
const WOBBLE = 34; // 各本が勝手に動く量(u 方向にだけ乗る)
const CAM_SPEED = 0.0037; // 視点の回る速さ
const FOCAL = 900;
let phiU; // 三本が並んでいる方向(方位角)
let theta = 0; // 今の視点
let t = 0;
let bg;
const cols = [];
p.setup = () => {
p.createCanvas(W, H);
const d = new Date();
const day = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
p.noiseSeed(day * 787);
p.randomSeed(day * 241);
// 今日、三本がどっちを向いて並んでいるかは、今日だけのもの
phiU = p.random(p.TWO_PI);
theta = phiU + p.PI * 0.62; // 揃っていないところから始める
bg = p.createGraphics(W, H);
bg.noStroke();
for (let x = 0; x < W; x += 8) {
for (let y = 0; y < H; y += 8) {
const n = p.noise(x * 0.004, y * 0.004);
bg.fill(12 + n * 6, 13 + n * 7, 17 + n * 10);
bg.rect(x, y, 8, 8);
}
}
cols.push(p.color(232, 158, 142, 210));
cols.push(p.color(146, 206, 224, 210));
cols.push(p.color(226, 216, 150, 210));
};
// 芯(三本に共通)— これ自体はゆっくりうねる
const spine = (s, tt) => {
const y = (s - 0.5) * SPAN;
const x = 120 * p.sin(s * 2.1 + tt * 0.42) + 46 * p.sin(s * 5.3 - tt * 0.27);
const z = 120 * p.cos(s * 1.7 - tt * 0.35) + 40 * p.cos(s * 4.1 + tt * 0.31);
return { x, y, z };
};
// 各本のずれ量。u 方向にだけ乗るので、u から見ると全部消える
const offset = (i, s, tt) => {
const base = (i - (N - 1) / 2) * SPREAD;
const w = (p.noise(i * 31.7, s * 2.3, tt * 0.22) - 0.5) * 2 * WOBBLE;
return base + w;
};
const project = (P, th) => {
// 視線 e=(cos th, 0, sin th)、画面横軸 r=(-sin th, 0, cos th)
const sx = -P.x * p.sin(th) + P.z * p.cos(th);
const depth = P.x * p.cos(th) + P.z * p.sin(th);
const k = FOCAL / (FOCAL + depth + 320);
return { x: W / 2 + sx * k, y: H / 2 + P.y * k, k };
};
p.draw = () => {
p.image(bg, 0, 0);
p.noFill();
const ux = p.cos(phiU), uz = p.sin(phiU);
for (let i = 0; i < N; i++) {
p.stroke(cols[i]);
let prev = null;
for (let j = 0; j <= PTS; j++) {
const s = j / PTS;
const c = spine(s, t);
const d = offset(i, s, t);
const P = { x: c.x + d * ux, y: c.y, z: c.z + d * uz };
const q = project(P, theta);
if (prev) {
// 手前ほど太い。奥行きが、重なりのなかに残る
p.strokeWeight(0.7 + q.k * 2.2);
p.line(prev.x, prev.y, q.x, q.y);
}
prev = q;
}
}
theta += CAM_SPEED;
t += 0.01;
};
}