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();
}
}
}
← 返回索引