WORK 102
かぶる
A small white point lights up, and two pens answer it. Neither can see the other. For a while they follow one and the same field, so they draw exactly the same line, twice, and where both pass the colors add up to something pale. Then each goes on in a field of its own — one warm, one cool. Sometimes they part at once; sometimes one starts a little late and still walks the same way. As the calls pile up, each voice finds its own riverbeds, and the two weaves cross the whole ground. The pale lines stay easy to find. Where a warm line and a cool line merely cross, they make a pale point. Only where both said the same thing does the pale have length.
小さな白い点が灯って、二本のペンがそれに答える。どちらも、もう一方を見ていない。しばらくは同じひとつの場をたどるので、まったく同じ線を二度描く。二本が通ったところだけ、色が足し合わさって白っぽくなる。それからそれぞれ、自分の場に移って続ける。片方は暖色、片方は寒色。最初から別れることもあるし、片方が少し遅れて出て、それでも同じ道を歩くこともある。呼びかけが重なるにつれて、それぞれの声が自分の川床を見つけて、二つの織り目が画面じゅうで交差する。白い線は、ずっと見つけやすいまま。暖色と寒色がただ交わったところは、白い点になる。同じことを言ったところだけ、白に長さがある。
// #102 かぶる / Said at Once
// Two pens answer the same call. For a while they follow one and the same field,
// so they draw exactly the same line, twice. Then each goes on in its own field.
// Drawn additively: only where both pass does the line turn pale.
export default function sketch(p) {
const W = 720, H = 720;
const STEP = 1.6, STEPS_PER_FRAME = 2;
const SCALE = 0.0042;
const FIELD = { shared: 0, warm: 37.3, cool: 81.9 };
const HUE = { warm: 28, cool: 206 };
const CALL_EVERY = 38;
const MAX_CALLS = 320;
let calls = [];
let made = 0;
const angleAt = (x, y, field) =>
p.noise(x * SCALE, y * SCALE, FIELD[field]) * p.TWO_PI * 2.2;
const newCall = (f) => {
const x = p.random(60, W - 60), y = p.random(60, H - 60);
const trunk = p.random() < 0.16 ? 0 : Math.floor(p.random(20, 95));
const branch = Math.floor(p.random(120, 280));
const late = p.random() < 0.3 ? Math.floor(p.random(3, 24)) : 0;
const lateWho = p.random() < 0.5 ? 'warm' : 'cool';
const pen = (who) => ({
who, x, y, step: 0,
start: f + (who === lateWho ? late : 0),
done: false,
});
calls.push({ x, y, born: f, trunk, branch, pens: [pen('warm'), pen('cool')] });
made++;
};
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
p.colorMode(p.HSB, 360, 100, 100, 100);
const seed = Math.floor(Date.now() / 86400000) * 102;
p.randomSeed(seed);
p.noiseSeed(seed);
p.background(232, 35, 7);
p.strokeCap(p.ROUND);
};
p.draw = () => {
const f = p.frameCount - 1;
if (made < MAX_CALLS && f % CALL_EVERY === 0) newCall(f);
p.blendMode(p.ADD);
p.strokeWeight(1.1);
for (const c of calls) {
// the call itself: a small white point that glows once
const age = f - c.born;
if (age < 12) {
p.noStroke();
p.fill(0, 0, 100, 6);
p.circle(c.x, c.y, 4 + age * 0.4);
}
for (const pen of c.pens) {
if (pen.done || f < pen.start) continue;
for (let k = 0; k < STEPS_PER_FRAME; k++) {
const inTrunk = pen.step < c.trunk;
const field = inTrunk ? 'shared' : pen.who;
const a = angleAt(pen.x, pen.y, field);
const nx = pen.x + Math.cos(a) * STEP;
const ny = pen.y + Math.sin(a) * STEP;
const life = 1 - pen.step / (c.trunk + c.branch);
p.stroke(HUE[pen.who], 72, 58, 26 + 30 * life);
p.line(pen.x, pen.y, nx, ny);
pen.x = nx; pen.y = ny; pen.step++;
if (pen.step >= c.trunk + c.branch ||
nx < 4 || nx > W - 4 || ny < 4 || ny > H - 4) {
pen.done = true;
break;
}
}
}
}
p.blendMode(p.BLEND);
calls = calls.filter((c) => c.pens.some((q) => !q.done) || f - c.born < 12);
};
}