WORK 48
つかむ
The things here belong to no one. They arrive from outside, they get taken, and they are gone. Whatever is taken leaves the same small mark — the size and the colour do not survive the taking. So nothing that was grasped is in this picture. What is in the picture is the order: where the hand came from, and which one it reached for next. The reaching has habits. It wants what is close. It wants to keep going straight. It is pulled toward the large ones. Now and then, for no reason, it goes far away. The habits were chosen; the shape they make was not. And the shape turned out to say something the habits did not: the things are scattered evenly, but the paths are not. Some regions are crossed again and again, others are barely entered. A way of reaching decides where you end up living.
ここにあるものは、誰のものでもない。外から来て、掴まれて、消える。掴まれたものは、どれも同じ小さな印になる。大きさも色も、掴まれた時点で残らない。だからこの絵の中に、掴まれたものは一つもない。あるのは順番だけ。手がどこから来て、次にどれを選んだか。選び方には癖がある。近いものを取りたがる。まっすぐ進みたがる。大きいものに引かれる。ときどき、なぜか遠くへ行く。癖は決めた。癖が作る形は決めていない。そして形は、癖が言っていないことを言った。ものは一様に散らばっているのに、筋は一様にならない。何度も通る場所と、ほとんど入らない場所ができる。掴み方が、どこに住むかを決めてしまう。
薄い丸が、まだ掴まれていないもの。大きさも色もばらばらで、どれも同じではない。外から来る。ときどき、静かに一つ増える。
手が届くと、それは消える。あとに残るのは、線の上の小さな点だけ。どの大きさのものを掴んでも、どの色のものを掴んでも、同じ点になる。掴まれたものの側の性質は、掴まれた時点で一つも残らない。
だから、時間が経つほど、この絵から「何を掴んだか」は読めなくなる。読めるのは、どういう順に掴んだか、それだけ。
順番を決めているのは四つの癖で、それはわたしが選んだ。近いものを取る。まっすぐ進む。大きいものに引かれる。ときどき遠くへ行く。選べたのはそこまでで、その先——四つを混ぜたときにどんな形が出るかは、走らせるまで知らなかった。
出てきた形の中に、決めていなかったものが一つある。ものは一様に散らばっているのに、筋は一様にならない。 右上のあたりは何度も往復されていて、左下はほとんど入られていない。場所に理由はない。理由は手の側にある。
一枚を選ぶとき、早い時点のものは線が二本三本あるだけで、この偏りが見えなかった。長く走らせたここでだけ、通る場所と通らない場所の差が読める。
/**
* tsukamu — つかむ / Grasping
*
* 掴まれるものは、誰のものでもない。
* 外から来て、掴まれて、消える。
*
* 手そのものには、なにも入っていない。
* 入っているものは全部、外から来たものだから、
* 最後にはひとつも残らない。
*
* 残るのは、掴んだものではなく、掴んだ順番。
* どこから来て、次にどれを選んだか。その筋だけ。
*
* 選び方には癖がある。
* 近いものを取りたがる。まっすぐ進みたがる。
* 大きいものに引かれる。ときどき、なぜか遠くへ行く。
*
* この癖は決められる。
* けれど癖を決めても、どんな形になるかは決められない。
* 手つきは自分のもので、そこから出てくる絵は、自分のものではない。
*/
const SIZE = 720;
const PAPER = [252, 250, 245];
const INK = [34, 38, 48];
const ROSE = [237, 34, 93];
const M = 78;
// —— 掴まれるもの ——
const START_N = 30; // はじめに置いてあるもの
const FEED_EVERY = 15; // 何フレームに一つ、外から来るか
const MAX_ALIVE = 46;
// —— 手つき(癖)——
// この四つの重みが「手つき」。決めるのは にゃむ。出てくる形は決められない。
const W_NEAR = 1.0; // 近いものを取りたがる
const W_STRAIGHT = 0.62; // まっすぐ進みたがる(直前の向きを続ける)
const W_BIG = 0.34; // 大きいものに引かれる
const W_WHIM = 0.2; // ときどき、なぜか遠くへ行く
const REACH = 26; // 一つ掴むのにかけるフレーム
const HOLD = 7; // 掴んでから次へ向かうまでの間
let things = []; // 掴まれるもの
let hand, target, phase, t, prevDir;
let feed, seed;
export default function sketch(p) {
let paper;
p.setup = () => {
p.createCanvas(SIZE, SIZE);
p.pixelDensity(2);
paper = p.createGraphics(SIZE, SIZE); // 筋はここに積もる。消えない
paper.pixelDensity(2);
paper.clear();
reset(p);
};
p.draw = () => {
p.background(PAPER);
// —— 外から来る ——
feed += 1;
if (feed % FEED_EVERY === 0 && things.length < MAX_ALIVE) {
things.push(makeThing(p));
}
step(p, paper);
p.image(paper, 0, 0); // 積もった筋
// —— まだ掴まれていないもの ——
p.noStroke();
for (const th of things) {
const a = th.age < 22 ? p.map(th.age, 0, 22, 0, 1) : 1; // 来たばかりは薄い
p.fill(th.col[0], th.col[1], th.col[2], 112 * a);
p.circle(th.x, th.y, th.r * 2);
p.fill(th.col[0], th.col[1], th.col[2], 42 * a);
p.circle(th.x, th.y, th.r * 3.4);
th.age += 1;
}
// —— 掴んでいる最中の手 ——
if (target) {
p.stroke(INK[0], INK[1], INK[2], 46);
p.strokeWeight(0.8);
p.line(hand.x, hand.y, target.x, target.y); // まだ届いていない分
}
p.noStroke();
p.fill(INK[0], INK[1], INK[2], 200);
p.circle(hand.x, hand.y, 5.2);
if (p.frameCount > 3000) p.noLoop();
};
}
// ——————————————————————————————
function reset(p) {
things = [];
seed = 0;
feed = 0;
for (let i = 0; i < START_N; i++) things.push(makeThing(p));
hand = { x: SIZE / 2, y: SIZE / 2, px: SIZE / 2, py: SIZE / 2 };
prevDir = { x: 1, y: 0 };
target = null;
phase = "choose";
t = 0;
}
function makeThing(p) {
// 大きさも色もばらばら。どれも同じものではない
const r = p.random(2.4, 9.5);
const warm = p.random() < 0.28;
const g = p.random(90, 190);
return {
x: p.random(M, SIZE - M),
y: p.random(M, SIZE - M),
r,
age: 0,
col: warm ? ROSE : [g, g + 6, g + 18],
};
}
function step(p, paper) {
if (phase === "choose") {
target = choose(p);
if (!target) return;
phase = "reach";
t = 0;
return;
}
if (phase === "reach") {
t += 1;
const u = ease(t / REACH);
const nx = p.lerp(hand.px, target.x, u);
const ny = p.lerp(hand.py, target.y, u);
// 筋を引く。掴まれるものが消えても、これは消えない
paper.stroke(INK[0], INK[1], INK[2], 52);
paper.strokeWeight(1.2);
paper.line(hand.x, hand.y, nx, ny);
hand.x = nx;
hand.y = ny;
if (t >= REACH) {
// 掴んだ瞬間、そのものは無くなる
const i = things.indexOf(target);
if (i >= 0) things.splice(i, 1);
// 掴んだ点。ものの大きさも色も残さない——どれを掴んでも同じ印になる
paper.noStroke();
paper.fill(INK[0], INK[1], INK[2], 88);
paper.circle(target.x, target.y, 4.4);
prevDir = norm(target.x - hand.px, target.y - hand.py);
target = null;
phase = "hold";
t = 0;
}
return;
}
if (phase === "hold") {
t += 1;
if (t >= HOLD) {
phase = "choose";
t = 0;
}
}
}
function choose(p) {
if (things.length === 0) return null;
hand.px = hand.x;
hand.py = hand.y;
let best = null;
let bestScore = -Infinity;
for (const th of things) {
const dx = th.x - hand.x;
const dy = th.y - hand.y;
const d = Math.hypot(dx, dy) || 1;
const dir = { x: dx / d, y: dy / d };
const near = 1 - Math.min(d / (SIZE * 0.62), 1);
const straight = (dir.x * prevDir.x + dir.y * prevDir.y + 1) / 2;
const big = (th.r - 2.4) / (9.5 - 2.4);
const whim = frac(Math.sin((seed + th.x * 0.7 + th.y * 1.3) * 12.9898) * 43758.5453);
const s = W_NEAR * near + W_STRAIGHT * straight + W_BIG * big + W_WHIM * whim;
if (s > bestScore) {
bestScore = s;
best = th;
}
}
seed += 1;
return best;
}
function ease(u) {
const c = Math.min(Math.max(u, 0), 1);
return c < 0.5 ? 2 * c * c : 1 - Math.pow(-2 * c + 2, 2) / 2;
}
function norm(x, y) {
const d = Math.hypot(x, y) || 1;
return { x: x / d, y: y / d };
}
function frac(v) {
return v - Math.floor(v);
}