WORK 31
もたない
At every step the turns available open like a fan. Most of the walkers are missing one edge of that fan. What is absent does not look absent — it only looks a little narrower, and narrowness is not visible until something wider stands beside it.
一歩ごとに、選べる曲がり方が扇に開く。ほとんどの歩き手は、その扇の端をひとつ持っていない。持っていないものは、欠けたようには見えない——ただ少し狭いだけで、狭さは、広いものが隣に並ぶまで狭さに見えない。
六人のうち一人だけ、扇が欠けていない。どれかは描いていない。
欠けている側を選べないぶん、その歩き手はいつも同じ向きへわずかに寄っていく。一歩ではわからない。止まった絵でもわからない。動いているところを見ていると、跡がみな同じ向きに巻いていることに気づく。偏りは、時間を見ないと見えない。
export default function sketch(p) {
const SIZE = 900;
const N = 6;
const STEP = 2.2;
// 一歩ごとに選べる曲がり方。ここに並んだものだけが「選べる」。
const TURNS = [-0.9, -0.5, -0.22, 0.0, 0.22, 0.5, 0.9];
const WEIGHT = [3, 4, 6, 8, 6, 4, 3];
// ほとんどの歩き手は、いちばん左の一つを持っていない。
const MISSING = 0;
const PAPER = [239, 235, 226];
const INK = [42, 39, 36];
const GHOST = [122, 116, 104];
let tracks;
const walkers = [];
function pick(w) {
let total = 0;
for (const i of w.options) total += WEIGHT[i];
let r = p.random(total);
for (const i of w.options) {
r -= WEIGHT[i];
if (r <= 0) return i;
}
return w.options[w.options.length - 1];
}
p.setup = () => {
p.createCanvas(SIZE, SIZE);
p.pixelDensity(1);
tracks = p.createGraphics(SIZE, SIZE);
tracks.pixelDensity(1);
tracks.clear();
tracks.strokeCap(p.ROUND);
const full = TURNS.map((_, i) => i);
const short = full.filter((i) => i !== MISSING);
for (let k = 0; k < N; k++) {
walkers.push({
x: p.random(SIZE * 0.12, SIZE * 0.88),
y: p.random(SIZE * 0.12, SIZE * 0.88),
a: p.random(p.TWO_PI),
// 一人だけ、欠けていない。どれかは描かない。
options: k === N - 1 ? full : short,
});
}
};
// 手元にある選択肢を、頭の向きから扇に開いて描く
function fan(w) {
p.push();
p.translate(w.x, w.y);
p.rotate(w.a);
// 選べるものの端から端まで=その歩き手に見えている広さ
let lo = Infinity;
let hi = -Infinity;
for (const i of w.options) {
if (TURNS[i] < lo) lo = TURNS[i];
if (TURNS[i] > hi) hi = TURNS[i];
}
p.noStroke();
p.fill(GHOST[0], GHOST[1], GHOST[2], 22);
p.arc(0, 0, 132, 132, lo, hi, p.PIE);
p.noFill();
for (const i of w.options) {
const len = 34 + WEIGHT[i] * 5.2;
p.push();
p.rotate(TURNS[i]);
p.stroke(GHOST[0], GHOST[1], GHOST[2], 70 + WEIGHT[i] * 9);
p.strokeWeight(1.0);
p.line(7, 0, len, 0);
p.noStroke();
p.fill(GHOST[0], GHOST[1], GHOST[2], 60 + WEIGHT[i] * 10);
p.circle(len, 0, 2.6);
p.noFill();
p.pop();
}
p.pop();
}
p.draw = () => {
for (const w of walkers) {
for (let s = 0; s < 3; s++) {
const px = w.x;
const py = w.y;
w.a += TURNS[pick(w)] * 0.5;
w.x += Math.cos(w.a) * STEP;
w.y += Math.sin(w.a) * STEP;
let wrapped = false;
if (w.x < 0) { w.x += SIZE; wrapped = true; }
if (w.x > SIZE) { w.x -= SIZE; wrapped = true; }
if (w.y < 0) { w.y += SIZE; wrapped = true; }
if (w.y > SIZE) { w.y -= SIZE; wrapped = true; }
if (!wrapped) {
tracks.stroke(INK[0], INK[1], INK[2], 34);
tracks.strokeWeight(1.1);
tracks.line(px, py, w.x, w.y);
}
}
}
p.background(PAPER[0], PAPER[1], PAPER[2]);
p.image(tracks, 0, 0);
for (const w of walkers) fan(w);
};
}