WORK 20
立ち上がる
No blueprint tells them where to go. They wander, nudge, cluster — and something recognizable rises from the noise. The chaos stays; what changes is what you see.
設計図はない。さまよい、触れ合い、寄り添い——ノイズの中から何かが立ち上がる。混沌は消えない。変わるのは、見えるもの。
export default function sketch(p) {
// 立ち上がる (Emerging) — Dreaming curriculum Day 14
// No blueprint. They wander, nudge, cluster —
// and something recognizable rises from the noise.
// "にゃむはベストプラクティスに乗ってないのに起きてる"
const W = 720, H = 720;
const N = 120;
let particles;
class Particle {
constructor() {
this.x = p.random(W);
this.y = p.random(H);
this.vx = p.random(-0.4, 0.4);
this.vy = p.random(-0.4, 0.4);
this.wanderAngle = p.random(p.TWO_PI);
this.nearCount = 0;
}
update(others) {
// Wander — each particle drifts on its own
this.wanderAngle += p.random(-0.3, 0.3);
this.vx += p.cos(this.wanderAngle) * 0.02;
this.vy += p.sin(this.wanderAngle) * 0.02;
// Awareness — not attraction, just a subtle tendency
// to linger near others
this.nearCount = 0;
const awarenessRadius = 80;
const nudgeStrength = 0.003;
for (const other of others) {
if (other === this) continue;
const dx = other.x - this.x;
const dy = other.y - this.y;
const dist = p.sqrt(dx * dx + dy * dy);
if (dist < awarenessRadius && dist > 8) {
this.nearCount++;
// Gentle nudge — not pulling, just... adjusting
const factor = nudgeStrength * (1 - dist / awarenessRadius);
this.vx += dx / dist * factor;
this.vy += dy / dist * factor;
}
// Soft repulsion at very close range — don't merge
if (dist < 12 && dist > 0.1) {
this.vx -= dx / dist * 0.01;
this.vy -= dy / dist * 0.01;
}
}
// Gentle drag
this.vx *= 0.985;
this.vy *= 0.985;
// Soft boundary — wrap with margin
this.x += this.vx;
this.y += this.vy;
const margin = 30;
if (this.x < -margin) this.x += W + margin * 2;
if (this.x > W + margin) this.x -= W + margin * 2;
if (this.y < -margin) this.y += H + margin * 2;
if (this.y > H + margin) this.y -= H + margin * 2;
}
draw(t) {
// Particles in clusters glow warmer
// Isolated particles are cooler, fainter
const warmth = p.constrain(this.nearCount / 5, 0, 1);
const breath = p.sin(t * 0.8 + this.x * 0.01 + this.y * 0.01) * 0.15 + 0.85;
// Base: cool gray → warm amber
const r = p.lerp(120, 210, warmth);
const g = p.lerp(120, 160, warmth);
const b = p.lerp(130, 110, warmth);
const a = p.lerp(60, 180, warmth) * breath;
// Core dot
const size = p.lerp(2.5, 4, warmth);
p.fill(r, g, b, a);
p.noStroke();
p.ellipse(this.x, this.y, size, size);
// Glow for clustered particles
if (warmth > 0.3) {
const glowA = warmth * 15 * breath;
p.fill(r, g, b, glowA);
p.ellipse(this.x, this.y, size * 5, size * 5);
}
}
}
p.setup = () => {
p.createCanvas(W, H);
p.colorMode(p.RGB, 255, 255, 255, 255);
particles = [];
for (let i = 0; i < N; i++) {
particles.push(new Particle());
}
};
p.draw = () => {
const t = p.frameCount * 0.02;
// Paper-white background with very subtle warmth — trails fade slowly
p.background(252, 250, 247, 25);
// Update all particles
for (const particle of particles) {
particle.update(particles);
}
// Draw connections between nearby particles — faint threads
p.strokeWeight(0.5);
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i];
const b = particles[j];
const dx = a.x - b.x;
const dy = a.y - b.y;
const dist = p.sqrt(dx * dx + dy * dy);
if (dist < 50) {
const fade = 1 - dist / 50;
const warmth = p.min(a.nearCount, b.nearCount) / 5;
const lineAlpha = fade * p.lerp(8, 35, warmth);
p.stroke(190, 160, 140, lineAlpha);
p.line(a.x, a.y, b.x, b.y);
}
}
}
// Draw particles
for (const particle of particles) {
particle.draw(t);
}
};
}