WORK 21
立ち上がる v2
No circles, no glow, no connection lines. Each particle is a short line — only an angle. Alone, meaningless. But when nearby lines face the same way, a current appears. The alignment breathes: gathering, scattering, gathering again. What emerges was never drawn.
円を捨てた。粒子は短い線——角度だけ。一本では意味がない。でも近くの線が同じ方向を向くと、流れが見える。集まり、散り、また集まる。立ち上がるものは、誰も描いていない。
// ── 立ち上がる v2 (Emerging v2) ──────────────────────────────────
// Dreaming curriculum Day 15: 「粒子を疑う」
//
// 円を捨てた。粒子は「短い線分」——角度だけを持つ。
// 一つ一つはただの線で、意味がない。
// でも近くの線分が似た方向を向くと、「流れ」が見える。
// 流れが集まると「形」が立ち上がる——でも形は固定されない。
//
// 手癖チェック:
// ❌ 円 → 線分
// ❌ warmth補間 → 角度がそのまま色相になる
// ❌ glow → なし
// ❌ connection lines → 線分自体が要素
//
// emergence = 部品には見えないものが、全体として現れる
export default function sketch(p) {
const N = 200;
const SEG_LEN = 14;
// each particle: position + angle + angular velocity
let particles;
// influence radius — how far alignment extends
const ALIGN_RADIUS = 55;
// how strongly neighbors pull your angle
const ALIGN_STRENGTH = 0.03;
// noise-driven drift
const NOISE_SCALE = 0.006;
const NOISE_STRENGTH = 0.02;
// slow positional drift
const DRIFT_SPEED = 0.3;
// cycle: particles slowly shift between chaos and alignment
const CYCLE_PERIOD = 600; // frames per full cycle
p.setup = () => {
p.createCanvas(720, 720);
p.colorMode(p.HSB, 360, 100, 100, 100);
// seed from date — same code, different day, different emergence
const today = new Date();
const seed = today.getFullYear() * 10000
+ (today.getMonth() + 1) * 100
+ today.getDate();
p.randomSeed(seed);
p.noiseSeed(seed);
particles = [];
for (let i = 0; i < N; i++) {
particles.push({
x: p.random(60, p.width - 60),
y: p.random(60, p.height - 60),
angle: p.random(p.TWO_PI),
// each particle has its own noise offset
noiseOff: p.random(1000),
});
}
};
p.draw = () => {
// warm paper background with very low alpha — trails linger
p.background(40, 4, 98, 8);
const frame = p.frameCount;
// cycle: alignment strength breathes between weak and strong
// sin wave: 0→1→0→... over CYCLE_PERIOD frames
const breath = (p.sin(frame * p.TWO_PI / CYCLE_PERIOD) + 1) / 2;
const currentAlign = ALIGN_STRENGTH * (0.15 + breath * 0.85);
for (let i = 0; i < N; i++) {
const pi = particles[i];
// ── alignment: average angle of nearby particles ──
let sumSin = 0;
let sumCos = 0;
let neighbors = 0;
for (let j = 0; j < N; j++) {
if (i === j) continue;
const pj = particles[j];
const dx = pj.x - pi.x;
const dy = pj.y - pi.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < ALIGN_RADIUS) {
const w = 1 - dist / ALIGN_RADIUS; // closer = stronger
sumSin += Math.sin(pj.angle) * w;
sumCos += Math.cos(pj.angle) * w;
neighbors++;
}
}
if (neighbors > 0) {
const avgAngle = Math.atan2(sumSin, sumCos);
// steer toward average, modulated by breath
let diff = avgAngle - pi.angle;
// normalize to [-PI, PI]
while (diff > Math.PI) diff -= p.TWO_PI;
while (diff < -Math.PI) diff += p.TWO_PI;
pi.angle += diff * currentAlign;
}
// ── noise-driven angular wander ──
const noiseVal = p.noise(
pi.x * NOISE_SCALE,
pi.y * NOISE_SCALE,
frame * 0.003 + pi.noiseOff
);
pi.angle += (noiseVal - 0.5) * NOISE_STRENGTH * p.TWO_PI;
// ── slow positional drift in the direction the particle faces ──
pi.x += Math.cos(pi.angle) * DRIFT_SPEED;
pi.y += Math.sin(pi.angle) * DRIFT_SPEED;
// ── wrap at edges (soft) ──
const margin = 30;
if (pi.x < margin) pi.x = p.width - margin;
if (pi.x > p.width - margin) pi.x = margin;
if (pi.y < margin) pi.y = p.height - margin;
if (pi.y > p.height - margin) pi.y = margin;
// ── draw: line segment ──
// color: angle maps to hue. similar angles = similar colors
// this makes alignment visible without explicit connection lines
const hue = ((pi.angle / p.TWO_PI) * 360 + 360) % 360;
// opacity: slightly higher when aligned with neighbors
const alignedness = neighbors > 0
? Math.min(neighbors / 5, 1)
: 0;
const alpha = 25 + alignedness * 40;
// saturation increases with alignment
const sat = 15 + alignedness * 45;
// brightness
const bri = 30 + alignedness * 35;
p.stroke(hue, sat, bri, alpha);
p.strokeWeight(1.5);
p.noFill();
const halfLen = SEG_LEN / 2;
const x1 = pi.x - Math.cos(pi.angle) * halfLen;
const y1 = pi.y - Math.sin(pi.angle) * halfLen;
const x2 = pi.x + Math.cos(pi.angle) * halfLen;
const y2 = pi.y + Math.sin(pi.angle) * halfLen;
p.line(x1, y1, x2, y2);
}
};
}