Physics & Particle Systems

模拟真实世界的物质运动或大量对象的集群行为。从鸟群算法到布料物理、流体模拟与粒子瓦解,探索数字世界的物理法则。

🖱️ 鼠标移动产生排斥/吸引力 · 观察三规则 emergent behavior

🎯 分离 (Separation)

避免与邻近个体碰撞,保持最小距离。每个个体计算周围邻居的平均位置并远离。

📐 对齐 (Alignment)

朝向邻居的平均飞行方向转向。使群体运动趋于一致,形成整齐的队形。

🧲 内聚 (Cohesion)

向邻居的平均位置移动。防止群体分散,维持集群的整体性。

// Boids 三规则核心算法 (Craig Reynolds, 1986) class Boid { flock(boids) { let sep = this.separate(boids); // 分离 let ali = this.align(boids); // 对齐 let coh = this.cohesion(boids); // 内聚 // 加权叠加 sep.mult(1.5); ali.mult(1.0); coh.mult(1.0); this.applyForce(sep); this.applyForce(ali); this.applyForce(coh); } separate(boids) { let steer = createVector(0,0), count = 0; for (let other of boids) { let d = this.position.dist(other.position); if (d > 0 && d < desiredSeparation) { let diff = p5.Vector.sub(this.position, other.position); diff.normalize().div(d); // 距离越近力越大 steer.add(diff); count++; } } if (count > 0) { steer.div(count); steer.normalize().mult(maxSpeed); steer.sub(this.velocity).limit(maxForce); } return steer; } }
🖱️ 拖拽顶点 · 实时物理约束求解

🔗 约束求解

每个顶点通过弹簧约束与邻居连接,每帧迭代多次约束满足(Constraint Relaxation),保持布料结构稳定。

💨 风力模拟

基于 Perlin Noise 生成随时间变化的风场,对布料顶点施加周期性外力,模拟旗帜飘动。

📍 固定点

顶部一排顶点位置固定,形成悬挂约束。拖拽任意顶点可实时交互。

// Verlet 积分 + 约束求解 class Point { update() { let velX = (this.x - this.oldx) * friction; let velY = (this.y - this.oldy) * friction; this.oldx = this.x; this.oldy = this.y; this.x += velX; this.y += velY; this.y += gravity; // 重力 } } class Stick { update() { let dx = this.p1.x - this.p2.x, dy = this.p1.y - this.p2.y; let dist = Math.sqrt(dx*dx + dy*dy); let diff = this.length - dist; let percent = diff / dist / 2; let offsetX = dx * percent, offsetY = dy * percent; if (!this.p1.pinned) { this.p1.x += offsetX; this.p1.y += offsetY; } if (!this.p2.pinned) { this.p2.x -= offsetX; this.p2.y -= offsetY; } } }
🖱️ 鼠标移动产生风力场 · 逆运动学链式摆动

⛓️ 逆运动学链

每根毛发由多个线段节点组成,根部固定,末端自由。通过反向传播位置约束模拟自然的弯曲与摆动。

🌬️ 空气阻力

每个节点受虚拟风力场影响,同时受到与速度成正比的阻尼力,模拟空气对细长发丝的阻碍。

🌀 惯性延迟

节点运动具有惯性,位置变化不会瞬间传递,形成波浪状的延迟摆动效果。

// 毛发链式物理 class HairStrand { update(mouseX, mouseY) { // 头部跟随(逆运动学简化) this.segments[0].x = this.rootX; this.segments[0].y = this.rootY; for (let i = 1; i < this.segments.length; i++) { let seg = this.segments[i], prev = this.segments[i-1]; // 风力 + 重力 seg.vx += windX + (Math.random()-0.5)*0.5; seg.vy += gravity + (Math.random()-0.5)*0.3; // 鼠标排斥 let dx = seg.x - mouseX, dy = seg.y - mouseY; let dist = Math.sqrt(dx*dx + dy*dy); if (dist < 100) { seg.vx += dx/dist*2; seg.vy += dy/dist*2; } // 阻尼 seg.vx *= 0.9; seg.vy *= 0.9; seg.x += seg.vx; seg.y += seg.vy; // 长度约束 let dx2 = seg.x - prev.x, dy2 = seg.y - prev.y; let d = Math.sqrt(dx2*dx2 + dy2*dy2); seg.x = prev.x + dx2/d * segLen; seg.y = prev.y + dy2/d * segLen; } } }
🖱️ 拖拽注入速度场 · 观察 Navier-Stokes 涡旋形成

🌊 Navier-Stokes 简化

基于 Stam 1999 年的 Real-Time Fluid Dynamics 方法,在网格上求解速度场与密度场的扩散、平流与投影。

🌀 涡旋约束

通过涡度约束(Vorticity Confinement)恢复因数值耗散丢失的小尺度涡旋细节,使烟雾更加湍流与真实。

🎨 密度可视化

将速度场的大小映射为色彩与透明度,形成从发射源向外扩散的烟雾状有机形态。

// 2D 流体求解器 (Jos Stam 方法) function diffuse(b, x, x0, diff, dt) { let a = dt * diff * (N-2) * (N-2); lin_solve(b, x, x0, a, 1 + 6*a); } function advect(b, d, d0, u, v, dt) { // 回溯粒子轨迹进行密度平流 for (let i=1; i<=N; i++) { for (let j=1; j<=N; j++) { let x = i - dt*N*u[IX(i,j)]; let y = j - dt*N*v[IX(i,j)]; // 双线性插值采样 d[IX(i,j)] = interpolate(d0, x, y); } } } function project(u, v, p, div) { // Hodge 投影:速度场分解为无散度场 // 求解泊松方程使 div(u,v) = 0 }
🖱️ 鼠标悬停吹散粒子 · 点击瞬间瓦解 · 空格重置

🌪️ 力场驱动

每个像素粒子受虚拟风力场与鼠标 proximity 力影响,从附着表面脱离并沿力方向加速。

⚡ 状态机

粒子具有附着/脱离/消散三种状态。脱离后受物理力运动,速度逐渐衰减并降低不透明度直至消失。

🎭 图像采样

从 Canvas 像素数据采样颜色信息,将图像/文字转化为数千个有色粒子,瓦解后仍能辨识原轮廓。

// 像素粒子瓦解系统 class PixelParticle { update() { if (this.state === 'attached') { // 检测鼠标距离 let d = dist(this.x, this.y, mouseX, mouseY); if (d < blowRadius) { this.state = 'detached'; this.vx = (this.x - mouseX)/d * blowForce; this.vy = (this.y - mouseY)/d * blowForce; } } else { // 物理运动 this.x += this.vx; this.y += this.vy; this.vx *= 0.95; this.vy *= 0.95; // 阻尼 this.vy += 0.1; // 重力 this.life -= decay; this.alpha = this.life; if (this.life <= 0) this.reset(); } } }
← 返回索引