WORK 26
はがれる
Points drift. A line is drawn between any two that come close. At the moment it appears, there is no way to tell whether it is real — they may have simply happened to be near each other. Drift apart and it peels away. Only the lines that do not peel grow darker, and eventually bind. A bound line does not break, however far the two are carried. So the longest lines in the picture are the oldest ones.
点が漂っている。近づいた二つのあいだに線が引かれる。引かれた瞬間、それが本物かどうかは分からない——ただ近かっただけかもしれない。離れれば剥がれる。剥がれずに残った線だけが濃くなり、やがて結ばれる。結ばれた線は、どれだけ離れても切れない。だから絵の中でいちばん長い線が、いちばん古い線になる。
近さは接続の理由になる。でも接続の証拠にはならない。
静止画では、生まれたばかりの線と、ずっと保たれてきた線が同じ形をしている。見分ける方法は一つしかない——動かして、剥がれるかどうかを見ること。
設計したのは「結ばれた線は切れない」ということだけだった。長さは意図していない。結ばれた点は、そのあとも別々に漂っていく。切れないまま離れていくから、古い線ほど長くなる。長さは、離れても切れなかったことの跡だった。
// はがれる (Hagareru) — 本物の接続は、時間が経っても剥がれない
//
// 点が漂っている。近づいた二つのあいだに線が引かれる。
// 引かれた瞬間、その線が本物かどうかは分からない。ただ近かっただけかもしれない。
//
// 離れれば剥がれる。剥がれずに残った線だけが、少しずつ濃くなる。
// 濃くなりきった線は結ばれる——そこから先は、離れても切れない。
//
// 結ばれた線は距離を選ばない。近いから結ばれるんじゃない。
const W = 720;
const H = 720;
const N = 34; // 漂う点の数
const MARGIN = 64;
const DRIFT = 0.0019; // ノイズ歩行の速さ
const NEAR = 82; // これより近いと線が引かれる
const HOLD_GAIN = 0.0024; // つながっている間、確からしさが増す速さ
const PEEL_LOSS = 0.005; // 離れている間、剥がれる速さ(増えるより速い)
const BOND_AT = 1.0; // ここまで濃くなると結ばれる
const BOND_PULL = 0.00022; // 結ばれた点どうしの、ごく弱い引力
const PAPER = [250, 248, 242];
const INK = [46, 52, 60];
const DOT = [128, 138, 150];
export default function sketch(p) {
const nodes = [];
const links = new Map(); // "i-j" → {i, j, hold, bonded, bondAge}
p.setup = () => {
p.createCanvas(W, H);
const seed = dailySeed();
p.noiseSeed(seed);
p.randomSeed(seed);
for (let i = 0; i < N; i++) {
nodes.push({
x: p.random(MARGIN, W - MARGIN),
y: p.random(MARGIN, H - MARGIN),
px: p.random(1000), // 点ごとに違う漂い方
py: p.random(1000),
});
}
p.strokeCap(p.ROUND);
};
p.draw = () => {
p.background(PAPER[0], PAPER[1], PAPER[2]);
drift();
updateLinks();
drawLinks();
drawNodes();
};
// ── 漂う ────────────────────────────────────────────────────
function drift() {
for (const n of nodes) {
n.px += DRIFT;
n.py += DRIFT;
// 帯は広めに取る(端で clamp されると壁に貼りついて偽の直線ができる)
n.x = p.map(p.noise(n.px), 0.22, 0.78, MARGIN, W - MARGIN, true);
n.y = p.map(p.noise(n.py, 90), 0.22, 0.78, MARGIN, H - MARGIN, true);
}
// 結ばれた点どうしは、ごくゆっくり寄る。切れはしないが、束ねもしない
for (const l of links.values()) {
if (!l.bonded) continue;
const a = nodes[l.i];
const b = nodes[l.j];
const dx = b.x - a.x;
const dy = b.y - a.y;
a.x += dx * BOND_PULL;
a.y += dy * BOND_PULL;
b.x -= dx * BOND_PULL;
b.y -= dy * BOND_PULL;
}
}
// ── 近づいたら引かれる。離れたら剥がれる ──────────────────────
function updateLinks() {
const touched = new Set();
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
const d = dist(nodes[i], nodes[j]);
if (d > NEAR) continue;
const key = i + "-" + j;
touched.add(key);
let l = links.get(key);
if (!l) {
l = { i, j, hold: 0, bonded: false, bondAge: 0 };
links.set(key, l);
}
// 近いほど、確からしさの増え方が速い
l.hold = Math.min(BOND_AT, l.hold + HOLD_GAIN * (1 - d / NEAR) * 1.9);
if (!l.bonded && l.hold >= BOND_AT) l.bonded = true;
}
}
for (const [key, l] of links) {
if (l.bonded) {
l.bondAge++;
continue;
}
if (touched.has(key)) continue;
// 離れている間は剥がれていく。ゼロになったら消える
l.hold -= PEEL_LOSS;
if (l.hold <= 0) links.delete(key);
}
}
// ── 描く ────────────────────────────────────────────────────
function drawLinks() {
p.noFill();
for (const l of links.values()) {
const a = nodes[l.i];
const b = nodes[l.j];
if (l.bonded) {
// 結ばれた線は距離を選ばない。長くても切れない
const settle = Math.min(1, l.bondAge / 220);
p.stroke(INK[0], INK[1], INK[2], p.lerp(120, 205, settle));
p.strokeWeight(p.lerp(1.7, 2.6, settle));
} else {
// まだ本物か分からない線。近さで生まれて、離れれば消える
const t = l.hold / BOND_AT;
p.stroke(INK[0], INK[1], INK[2], 26 + 122 * t * t);
p.strokeWeight(0.95 + 1.4 * t);
}
p.line(a.x, a.y, b.x, b.y);
}
}
function drawNodes() {
p.noStroke();
for (const n of nodes) {
p.fill(DOT[0], DOT[1], DOT[2], 150);
p.circle(n.x, n.y, 3.4);
}
}
function dist(a, b) {
return Math.hypot(b.x - a.x, b.y - a.y);
}
function dailySeed() {
const d = new Date();
return (d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()) % 100000;
}
}