WORK 15
むかう
Before the step, the turning. Ninety grains scattered on a warm ground, each slowly rotating to face a gently drifting pull — not moving, just orienting. Direction found before the body moves.
動く前に、向く。90粒の種が、ゆっくりと漂う引力に向かって回り続ける。位置は変わらない。向いてる方向だけが、少しずつ変わっていく。
// ── むかう (mukau) ──────────────────────────────────────────
// Before the step, the turning.
// Direction found before the body moves.
// A field of grains, each slowly facing something
// it hasn't reached yet.
export default function sketch(p) {
const W = 720;
const today = (() => {
const d = new Date();
return d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
})();
let grains = [];
const N = 90;
let pullX, pullY, pullPhase;
class Grain {
constructor() {
const angle = p.random(p.TWO_PI);
const r = 60 + p.random(260);
this.x = W / 2 + Math.cos(angle) * r;
this.y = W / 2 + Math.sin(angle) * r;
this.angle = p.random(p.TWO_PI);
this.turnRate = 0.004 + p.random(0.012);
this.len = 10 + p.random(14);
this.w = 2.5 + p.random(2.5);
}
alignment() {
const dx = pullX - this.x;
const dy = pullY - this.y;
const target = Math.atan2(dy, dx);
let diff = this.angle - target;
while (diff > Math.PI) diff -= p.TWO_PI;
while (diff < -Math.PI) diff += p.TWO_PI;
return 1 - Math.abs(diff) / Math.PI;
}
update() {
const dx = pullX - this.x;
const dy = pullY - this.y;
const target = Math.atan2(dy, dx);
let diff = target - this.angle;
while (diff > Math.PI) diff -= p.TWO_PI;
while (diff < -Math.PI) diff += p.TWO_PI;
this.angle += diff * this.turnRate;
}
draw() {
const align = this.alignment();
p.push();
p.translate(this.x, this.y);
p.rotate(this.angle);
// Warm ochre-grey, more present when aligned
const b = 110 - align * 65;
const a = 70 + align * 130;
p.fill(b * 1.1, b * 0.93, b * 0.82, a);
p.noStroke();
p.ellipse(0, 0, this.len, this.w);
// Warm tip at the facing end — appears as alignment grows
if (align > 0.55) {
const tipAlpha = ((align - 0.55) / 0.45) * 200;
p.fill(210, 165, 105, tipAlpha);
const tipX = this.len * 0.44;
p.ellipse(tipX, 0, this.w * 1.8, this.w * 1.8);
}
p.pop();
}
}
p.setup = () => {
p.createCanvas(W, W);
p.randomSeed(today * 347);
p.noiseSeed(today * 197);
pullPhase = 0;
pullX = W / 2;
pullY = W / 2;
for (let i = 0; i < N; i++) {
grains.push(new Grain());
}
};
p.draw = () => {
p.background(252, 249, 245);
pullPhase += 0.004;
pullX = W * 0.5 + Math.cos(pullPhase * 0.62) * 50;
pullY = W * 0.5 + Math.sin(pullPhase) * 50;
for (const g of grains) {
g.update();
g.draw();
}
// Pull center — barely visible, just a breath of warmth
const pA = 7 + 5 * Math.sin(p.frameCount * 0.025);
p.noStroke();
p.fill(190, 145, 100, pA);
p.circle(pullX, pullY, 22);
};
}