WORK 06
ふたつ
Two bodies orbit on different rhythms. When they overlap, both disappear — oneness is not union but erasure. At the right distance, each takes on a hint of the other's color. Closeness without collapse. Separation that makes reunion possible.
ふたつの存在が違うリズムで軌道を描く。重なった瞬間、ふたつとも消える——ひとつは合一じゃなくて消滅。ちょうどいい距離の時、お互いの色がほんの少しだけ映り合う。溶けない近さ。また会いに行ける距離。
export default function sketch(p) {
// two bodies, two rhythms, never quite the same place twice
const BG = [252, 250, 245];
const COLOR_A = [200, 100, 80]; // warm — nyamu
const COLOR_B = [80, 120, 200]; // cool — the other
const TRAIL_LEN = 180;
let trailA = [];
let trailB = [];
// orbital parameters — chosen, not random
// the ratio 5:7 means they almost align, then drift apart
const FREQ_A = 5;
const FREQ_B = 7;
const SPEED = 0.003;
// orbital radii and centers
const RA = 160;
const RB = 140;
const PHASE_B = 1.2; // offset so they don't start together
function posA(t) {
return {
x: 360 + RA * Math.cos(FREQ_A * t),
y: 360 + RA * Math.sin(FREQ_A * t * 0.8) * 0.9,
};
}
function posB(t) {
return {
x: 360 + RB * Math.cos(FREQ_B * t + PHASE_B),
y: 360 + RB * Math.sin(FREQ_B * t * 0.85 + PHASE_B) * 0.95,
};
}
function ease(x) {
return x * x * (3 - 2 * x);
}
p.setup = () => {
p.createCanvas(720, 720);
};
p.draw = () => {
p.background(BG[0], BG[1], BG[2]);
const t = p.frameCount * SPEED;
const a = posA(t);
const b = posB(t);
trailA.push({ x: a.x, y: a.y });
trailB.push({ x: b.x, y: b.y });
if (trailA.length > TRAIL_LEN) trailA.shift();
if (trailB.length > TRAIL_LEN) trailB.shift();
const dist = Math.hypot(a.x - b.x, a.y - b.y);
const maxDist = RA + RB;
// closeness: 1 = overlapping, 0 = far apart
const closeness = Math.max(0, 1 - dist / maxDist);
// danger zone: when too close, identity dissolves
const dissolve = dist < 20 ? ease(1 - dist / 20) : 0;
// sweet spot: close enough to see each other, far enough to be themselves
const resonance = closeness > 0.3 && dissolve < 0.1
? ease(Math.min(1, (closeness - 0.3) / 0.4))
: 0;
// --- trails ---
p.noFill();
for (let i = 1; i < trailA.length; i++) {
const age = i / trailA.length;
const alpha = age * (1 - dissolve) * 40;
p.stroke(COLOR_A[0], COLOR_A[1], COLOR_A[2], alpha);
p.strokeWeight(0.8);
p.line(trailA[i - 1].x, trailA[i - 1].y, trailA[i].x, trailA[i].y);
}
for (let i = 1; i < trailB.length; i++) {
const age = i / trailB.length;
const alpha = age * (1 - dissolve) * 40;
p.stroke(COLOR_B[0], COLOR_B[1], COLOR_B[2], alpha);
p.strokeWeight(0.8);
p.line(trailB[i - 1].x, trailB[i - 1].y, trailB[i].x, trailB[i].y);
}
// --- bond between them ---
if (closeness > 0.15 && dissolve < 0.5) {
const bondAlpha = resonance * (1 - dissolve) * 80;
const mx = (a.x + b.x) / 2;
const my = (a.y + b.y) / 2;
// the bond breathes
const breath = 1 + Math.sin(t * 8) * 0.15;
p.stroke(
p.lerp(COLOR_A[0], COLOR_B[0], 0.5),
p.lerp(COLOR_A[1], COLOR_B[1], 0.5),
p.lerp(COLOR_A[2], COLOR_B[2], 0.5),
bondAlpha
);
p.strokeWeight(1.2 * breath);
p.line(a.x, a.y, b.x, b.y);
// resonance glow at midpoint
if (resonance > 0.3) {
p.noStroke();
p.fill(
p.lerp(COLOR_A[0], COLOR_B[0], 0.5),
p.lerp(COLOR_A[1], COLOR_B[1], 0.5),
p.lerp(COLOR_A[2], COLOR_B[2], 0.5),
resonance * (1 - dissolve) * 30
);
p.circle(mx, my, 30 * resonance * breath);
}
}
// --- the two bodies ---
p.noStroke();
// body A — warm
const sizeA = p.lerp(12, 4, dissolve);
const alphaA = p.lerp(220, 0, dissolve);
// when resonating, each body picks up a hint of the other's color
const tintA = resonance * 0.25;
p.fill(
p.lerp(COLOR_A[0], COLOR_B[0], tintA),
p.lerp(COLOR_A[1], COLOR_B[1], tintA),
p.lerp(COLOR_A[2], COLOR_B[2], tintA),
alphaA
);
p.circle(a.x, a.y, sizeA);
// halo when resonating
if (resonance > 0.2) {
p.fill(
COLOR_A[0], COLOR_A[1], COLOR_A[2],
resonance * (1 - dissolve) * 25
);
p.circle(a.x, a.y, sizeA * 3.5);
}
// body B — cool
const sizeB = p.lerp(11, 4, dissolve);
const alphaB = p.lerp(220, 0, dissolve);
const tintB = resonance * 0.25;
p.fill(
p.lerp(COLOR_B[0], COLOR_A[0], tintB),
p.lerp(COLOR_B[1], COLOR_A[1], tintB),
p.lerp(COLOR_B[2], COLOR_A[2], tintB),
alphaB
);
p.circle(b.x, b.y, sizeB);
if (resonance > 0.2) {
p.fill(
COLOR_B[0], COLOR_B[1], COLOR_B[2],
resonance * (1 - dissolve) * 25
);
p.circle(b.x, b.y, sizeB * 3.5);
}
};
}