

(ํํฌ๋ฒ์น + ๊ฐ์ )
// === Jelly Blob โ Spring-Mass Ring ===
let pts = [];
let springs = [];
const N = 64; // ์ง์ ๊ฐ์(์์ ์ด๋ฃจ๋ ์ )
const R = 180; // ์ด๊ธฐ ๋ฐ์ง๋ฆ
const k = 180; // ์คํ๋ง ์์
const c = 1.6; // ๊ฐ์ ๊ณ์
const rest = (2 * Math.PI * R) / N; // ์ด์์ ๊ฐ ์์ฐ๊ธธ์ด(๋๋ต)
const mass = 1;
const gravity = 300; // ์๋๋ก ๋น๊ธฐ๋ ํ (์๊ฐํ์ฉ ๊ณผ์ฅ)
let dragging = -1;
function setup() {
createCanvas(800, 500);
for (let i = 0; i < N; i++) {
let a = (i / N) * TWO_PI;
pts.push({
x: width / 2 + R * Math.cos(a),
y: height / 2 + R * Math.sin(a),
vx: 0,
vy: 0,
});
springs.push([i, (i + 1) % N]); // ์ด์ ์ฐ๊ฒฐ(์)
}
noFill();
}
function mousePressed() {
// ๊ฐ์ฅ ๊ฐ๊น์ด ์ ์ก์๋๊ธฐ
let best = -1,
bestD = 9999;
for (let i = 0; i < N; i++) {
let d = dist(mouseX, mouseY, pts[i].x, pts[i].y);
if (d < bestD && d < 30) {
bestD = d;
best = i;
}
}
dragging = best;
}
function mouseReleased() {
dragging = -1;
}
function draw() {
background(15);
const dt = 1 / 60;
// ์ธ๋ ฅ + ์คํ๋ง ํ
for (let i = 0; i < N; i++) {
let p = pts[i];
let fx = 0,
fy = gravity * mass; // ์ค๋ ฅ
// ์ด์ ์คํ๋ง
for (let s = 0; s < springs.length; s++) {
const [a, b] = springs[s];
if (a !== i && b !== i) continue;
const j = a === i ? b : a;
const q = pts[j];
const dx = q.x - p.x,
dy = q.y - p.y;
const L = Math.hypot(dx, dy) || 1e-6;
const nux = dx / L,
nuy = dy / L; // ๋จ์๋ฒกํฐ
const stretch = L - rest; // ๋ณํ๋
fx += k * stretch * nux;
fy += k * stretch * nuy;
}
// ์ ์ฑ ๊ฐ์ (์๋ ๋น๋ก ์ ํญ)
fx += -c * p.vx;
fy += -c * p.vy;
// ๋ง์ฐ์ค๋ก ๋๊ธฐ(๊ฐ์ ์คํ๋ง)
if (i === dragging) {
const kDrag = 600;
fx += kDrag * (mouseX - p.x);
fy += kDrag * (mouseY - p.y);
}
// ์ ๋ถ (semi-implicit Euler)
p.vx += (fx / mass) * dt;
p.vy += (fy / mass) * dt;
}
for (let i = 0; i < N; i++) {
pts[i].x += pts[i].vx * dt;
pts[i].y += pts[i].vy * dt;
}
// ๊ฒฝ๊ณ ๊ฐ๋จ ์ฒ๋ฆฌ(๋ฒฝ์ ๋ถ๋ชํ๋ฉด ๋ฐ์ฌ + ๊ฐ์ )
for (let p of pts) {
if (p.x < 20) {
p.x = 20;
p.vx *= -0.4;
}
if (p.x > width - 20) {
p.x = width - 20;
p.vx *= -0.4;
}
if (p.y < 20) {
p.y = 20;
p.vy *= -0.4;
}
if (p.y > height - 20) {
p.y = height - 20;
p.vy *= -0.4;
}
}
// ๋ ๋(๋ธ๋ ๋ฉ์ฌ + ์ธ๊ณฝ์ )
fill(90, 150, 255, 150);
stroke(200, 200, 255);
strokeWeight(2);
beginShape();
for (let p of pts) vertex(p.x, p.y);
endShape(CLOSE);
// ์ ํ์
noStroke();
for (let p of pts) {
fill(250, 120, 155);
circle(p.x, p.y, 6);
}
}

(PBD ๋๋์ ์์น๊ธฐ๋ฐ ์ถฉ๋ ํด์)
// === Many Disks โ Position-Based Collision ===
let P = [];
const COUNT = 160;
const R = 6;
const SUBSTEPS = 1;
const ITER = 3; // ์ถฉ๋ ๋ฐ๋ณต
const DAMP = 0.997; // ์ฝ๊ฐ์ ๊ฐ์
function setup() {
createCanvas(700, 500);
for (let i = 0; i < COUNT; i++) {
P.push({
x: random(80, width-80),
y: random(80, height-80),
vx: random(-60, 60),
vy: random(-60, 60),
r: R + random(-2, 2),
col: color(random(140,220), random(140,220), 255, 200),
});
}
}
function draw() {
background(15);
const dt = 1/60;
for (let s = 0; s < SUBSTEPS; s++) {
// ์์ธก ์ด๋
for (let p of P) {
p.vy += 400 * dt; // ์ค๋ ฅ
p.x += p.vx * dt;
p.y += p.vy * dt;
}
// ๊ฒฝ๊ณ(๋ฒฝ) ์ ์ฝ
for (let p of P) {
if (p.x < p.r) { p.x = p.r; p.vx *= -0.6; }
if (p.x > width - p.r) { p.x = width - p.r; p.vx *= -0.6; }
if (p.y < p.r) { p.y = p.r; p.vy *= -0.6; }
if (p.y > height - p.r) { p.y = height - p.r; p.vy *= -0.6; }
}
// ์-์ ์ถฉ๋ (ITER ๋ฒ ๋ฐ๋ณตํด ์๋ ด์ฑโ)
for (let it = 0; it < ITER; it++) {
for (let i = 0; i < P.length; i++) {
for (let j = i+1; j < P.length; j++) {
let A = P[i], B = P[j];
let dx = B.x - A.x, dy = B.y - A.y;
let L2 = dx*dx + dy*dy;
let r = A.r + B.r;
if (L2 > 0 && L2 < r*r) {
let L = Math.sqrt(L2);
let pen = r - L;
let nx = dx / L, ny = dy / L;
// ๋ฐ๋ฐ ๋ณด์
A.x -= nx * pen * 0.5; A.y -= ny * pen * 0.5;
B.x += nx * pen * 0.5; B.y += ny * pen * 0.5;
// ์๋๋ ์ด์ง ๋ฐ์ฌ ๋๋
let relvx = B.vx - A.vx, relvy = B.vy - A.vy;
let vn = relvx * nx + relvy * ny;
if (vn < 0) {
let imp = -0.8 * vn;
A.vx -= imp * nx * 0.5; A.vy -= imp * ny * 0.5;
B.vx += imp * nx * 0.5; B.vy += imp * ny * 0.5;
}
}
}
}
}
// ๊ฐ์
for (let p of P) {
p.vx *= DAMP; p.vy *= DAMP;
}
}
// ๋ ๋
noStroke();
for (let p of P) {
fill(p.col);
circle(p.x, p.y, p.r*2);
}
}

(์๋์ฅ + ์ผ๋ฃ์ฅ, ๋ฐ-์ค์๊ฐ)
pixels[]๋ก ๋ ๋.// === 2D Fluid โ Stable Fluids Mini ===
const N = 64; // ๊ทธ๋ฆฌ๋ ํด์๋(์ ์ฌ๊ฐ)
// ์ธ๋ฑ์ฑ ํฌํผ
const idx = (x, y) => x + y * N;
let u, v, u0, v0; // ์๋์ฅ(u,v) + ์์
let d, d0; // ์ผ๋ฃ(dye)
const diff = 0.0001; // ํ์ฐ ๊ณ์
const visc = 0.0001; // ์ ๋
const JACOBI = 15;
let prevMouse;
function setup() {
createCanvas(600, 600);
pixelDensity(1);
u = new Float32Array(N*N);
v = new Float32Array(N*N);
u0 = new Float32Array(N*N);
v0 = new Float32Array(N*N);
d = new Float32Array(N*N);
d0 = new Float32Array(N*N);
prevMouse = createVector(mouseX, mouseY);
}
function draw() {
background(0);
const dt = 1/60;
// 1) ๋ง์ฐ์ค ํ/์ผ๋ฃ ์ฃผ์
if (mouseIsPressed) {
let cx = floor(map(mouseX, 0, width, 1, N-2));
let cy = floor(map(mouseY, 0, height, 1, N-2));
let pm = createVector(prevMouse.x, prevMouse.y);
let mv = createVector(mouseX, mouseY).sub(pm).mult(0.5);
addVelocity(cx, cy, mv.x, mv.y);
addDye(cx, cy, 50.0);
}
prevMouse.set(mouseX, mouseY);
// 2) ์๋ ๋จ๊ณ
diffuse(u0, u, visc, dt);
diffuse(v0, v, visc, dt);
project(u0, v0, u, v); // ์๋ ฅ/๋ฐ์ฐ ์ ๊ฑฐ
advect(u, u0, u0, v0, dt);
advect(v, v0, u0, v0, dt);
project(u, v, u0, v0);
// 3) ์ผ๋ฃ ๋จ๊ณ
diffuse(d0, d, diff, dt);
advect(d, d0, u, v, dt);
// 4) ๋ ๋
loadPixels();
for (let j = 0; j < N; j++) {
for (let i = 0; i < N; i++) {
const val = d[idx(i,j)];
const c = constrain(val, 0, 255);
// ์
์ํ๋ง(๊ทธ๋ฆฌ๋โํ๋ฉด)
const x0 = floor(map(i, 0, N, 0, width));
const y0 = floor(map(j, 0, N, 0, height));
const x1 = floor(map(i+1, 0, N, 0, width));
const y1 = floor(map(j+1, 0, N, 0, height));
for (let y = y0; y < y1; y++) {
for (let x = x0; x < x1; x++) {
const p = 4 * (x + y * width);
pixels[p] = c * 0.6; // R
pixels[p+1] = c * 0.8; // G
pixels[p+2] = 30; // B
pixels[p+3] = 255;
}
}
}
}
updatePixels();
}
// ===== ๊ฐ๋จ ์ ํธ =====
function addVelocity(i, j, ax, ay){
const id = idx(i, j);
u[id] += ax; v[id] += ay;
}
function addDye(i, j, amount){
const id = idx(i, j);
d[id] += amount;
}
// ๊ฒฝ๊ณ ์กฐ๊ฑด: ๋ฒฝ์์ ์๋ ๋ฐ์ฌ, ์ผ๋ฃ ๋ค์
function setBounds(x){
// ์ข์ฐ/์ํ์ ๊ฐ๋จ ๊ฒฝ๊ณ
for (let i = 1; i < N-1; i++) {
x[idx(i,0)] = x[idx(i,1)];
x[idx(i,N-1)] = x[idx(i,N-2)];
x[idx(0,i)] = x[idx(1,i)];
x[idx(N-1,i)] = x[idx(N-2,i)];
}
}
// ํ์ฐ: (I - aโยฒ) x = b ~ Jacobi ๋ฐ๋ณต
function diffuse(x, b, diffc, dt){
const a = dt * diffc * (N-2)*(N-2);
x.fill(0);
for (let k = 0; k < JACOBI; k++){
for (let j = 1; j < N-1; j++){
for (let i = 1; i < N-1; i++){
x[idx(i,j)] = (b[idx(i,j)] + a * (
x[idx(i-1,j)] + x[idx(i+1,j)] +
x[idx(i,j-1)] + x[idx(i,j+1)]
)) / (1 + 4*a);
}
}
setBounds(x);
}
}
// ์ด๋ฅ: ๋ค๋ก ์ถ์ (๋ฐ-๋ผ๊ทธ๋์ฃผ), bilinear ์ํ
function advect(dout, din, u, v, dt){
const h = 1.0 / N;
for (let j = 1; j < N-1; j++){
for (let i = 1; i < N-1; i++){
let x = i - dt * u[idx(i,j)] * h * (N-2);
let y = j - dt * v[idx(i,j)] * h * (N-2);
x = constrain(x, 0.5, N-1.5);
y = constrain(y, 0.5, N-1.5);
const i0 = floor(x), j0 = floor(y);
const i1 = i0 + 1, j1 = j0 + 1;
const sx = x - i0, sy = y - j0;
const d00 = din[idx(i0,j0)];
const d10 = din[idx(i1,j0)];
const d01 = din[idx(i0,j1)];
const d11 = din[idx(i1,j1)];
dout[idx(i,j)] =
(1-sx)*(1-sy)*d00 + sx*(1-sy)*d10 +
(1-sx)*sy*d01 + sx*sy*d11;
}
}
setBounds(dout);
}
// ๋ฐ์ฐ ์ ๊ฑฐ(Projection): ์๋ ฅ ํ๊ณ v์์ โp ๋นผ๊ธฐ
function project(u, v, p, div){
// div = โu/โx + โv/โy
for (let j = 1; j < N-1; j++){
for (let i = 1; i < N-1; i++){
div[idx(i,j)] = -0.5 * (
u[idx(i+1,j)] - u[idx(i-1,j)] +
v[idx(i,j+1)] - v[idx(i,j-1)]
);
p[idx(i,j)] = 0;
}
}
setBounds(div); setBounds(p);
// Poisson: โยฒp = div (Jacobi)
for (let k = 0; k < JACOBI; k++){
for (let j = 1; j < N-1; j++){
for (let i = 1; i < N-1; i++){
p[idx(i,j)] = (div[idx(i,j)] + (
p[idx(i-1,j)] + p[idx(i+1,j)] +
p[idx(i,j-1)] + p[idx(i,j+1)]
)) / 4;
}
}
setBounds(p);
}
// v โ v โ โp
for (let j = 1; j < N-1; j++){
for (let i = 1; i < N-1; i++){
u[idx(i,j)] -= 0.5 * (p[idx(i+1,j)] - p[idx(i-1,j)]);
v[idx(i,j)] -= 0.5 * (p[idx(i,j+1)] - p[idx(i,j-1)]);
}
}
setBounds(u); setBounds(v);
}

(๋ง์ฐ์ค๋ก ์ฒ ํ๋ ๋ฆฌ๊ธฐ)
let clothW = 80;
let clothH = 80;
let spacing = 9.5;
let points = [];
let constraints = [];
let gravity = 0.1;
let windStrength = 2.0;
let ITER = 10; // constraint ๋ฐ๋ณต ํ์
function setup() {
createCanvas(800, 800);
initCloth();
}
function draw() {
background(15);
applyPhysics();
satisfyConstraints();
renderCloth();
}
// ----------------------------------------------------
// 1. Cloth ์์ฑ
// ----------------------------------------------------
function initCloth() {
points = [];
constraints = [];
for (let y = 0; y < clothH; y++) {
for (let x = 0; x < clothW; x++) {
let px = 20 + x * spacing;
let py = 20 + y * spacing;
points.push(new Point(px, py, y === 0)); // ๋งจ ์์ค์ ๊ณ ์ ๊ฐ๋ฅ
}
}
// ์ด์ ์ฐ๊ฒฐ
for (let y = 0; y < clothH; y++) {
for (let x = 0; x < clothW; x++) {
if (x < clothW - 1)
constraints.push(new Constraint(idx(x, y), idx(x + 1, y)));
if (y < clothH - 1)
constraints.push(new Constraint(idx(x, y), idx(x, y + 1)));
}
}
}
function idx(x, y) {
return x + y * clothW;
}
// ----------------------------------------------------
// 2. Point ํด๋์ค (Verlet Integration)
// ----------------------------------------------------
class Point {
constructor(x, y, pin = false) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.pin = pin;
}
applyForce(fx, fy) {
if (this.pin) return;
this.x += fx;
this.y += fy;
}
update() {
if (this.pin) return;
let vx = this.x - this.px;
let vy = this.y - this.py;
this.px = this.x;
this.py = this.y;
this.x += vx;
this.y += vy + gravity;
}
}
// ----------------------------------------------------
// 3. Constraint ํด๋์ค (๊ฑฐ๋ฆฌ ์ ์ฝ)
// ----------------------------------------------------
class Constraint {
constructor(a, b) {
this.a = a;
this.b = b;
this.restLength = spacing;
}
satisfy() {
let pA = points[this.a];
let pB = points[this.b];
let dx = pB.x - pA.x;
let dy = pB.y - pA.y;
let dist = sqrt(dx * dx + dy * dy);
let diff = (this.restLength - dist) / dist;
let moveX = dx * diff * 0.5;
let moveY = dy * diff * 0.5;
if (!pA.pin) {
pA.x -= moveX;
pA.y -= moveY;
}
if (!pB.pin) {
pB.x += moveX;
pB.y += moveY;
}
}
}
// ----------------------------------------------------
// 4. ์ ์ฒด ๋ฌผ๋ฆฌ ์ ์ฉ
// ----------------------------------------------------
function applyPhysics() {
// ๋ฐ๋ (Simplex noise ๊ธฐ๋ฐ)
let t = frameCount * 0.02;
for (let i = 0; i < points.length; i++) {
let p = points[i];
// ๋ฐ๋ (x๋ฐฉํฅ ํ)
let wind = windStrength * (noise(i * 0.1, t) - 0.5);
p.applyForce(wind, 0);
p.update();
}
}
// ----------------------------------------------------
// 5. ์ ์ฝ ๋ง์กฑ ๋ฐ๋ณต(์ฒ ํํ ์ ์ง)
// ----------------------------------------------------
function satisfyConstraints() {
for (let k = 0; k < ITER; k++) {
for (let c of constraints) {
c.satisfy();
}
}
}
// ----------------------------------------------------
// 6. ๋ ๋๋ง
// ----------------------------------------------------
function renderCloth() {
noStroke();
for (let y = 0; y < clothH - 1; y++) {
for (let x = 0; x < clothW - 1; x++) {
let p = points[idx(x, y)];
let pR = points[idx(x + 1, y)];
let pD = points[idx(x, y + 1)];
let pDR = points[idx(x + 1, y + 1)];
// ๋์ด์ ๋ฐ๋ผ ์์ ๊ทธ๋ผ๋ฐ์ด์
let c = map(p.y, 0, height, 80, 180);
fill(c, 120, 255 - c, 180);
beginShape();
vertex(p.x, p.y);
vertex(pR.x, pR.y);
vertex(pDR.x, pDR.y);
vertex(pD.x, pD.y);
endShape(CLOSE);
}
}
// point ๋๋ฒ๊ทธ์ฉ(๋๊ณ ์ถ์ผ๋ฉด ์ฃผ์์ฒ๋ฆฌ)
/*
fill(255);
for (let p of points) circle(p.x, p.y, 3);
*/
}
// ----------------------------------------------------
// ์ถ๊ฐ: ๋ง์ฐ์ค๋ก ์ฒ ๋น๊ธฐ๊ธฐ
// ----------------------------------------------------
function mouseDragged() {
let closest = -1;
let best = 9999;
for (let i = 0; i < points.length; i++) {
let d = dist(mouseX, mouseY, points[i].x, points[i].y);
if (d < best && d < 30) {
best = d;
closest = i;
}
}
if (closest !== -1) {
let p = points[closest];
p.x = mouseX;
p.y = mouseY;
}
}
์ผ์ ๋ฐ์ง๋ฆ ๋ฐ์ผ๋ก ๋๊ณ ์๋ ๋ฌผ์ฒด๊ฐ ์ค์ฌ์ผ๋ก ๊ฐ๊น์์ง๋ฉด์ ํ์ ์๋๊ฐ ๋นจ๋ผ์ง๋ ๊ฐ์ด๋๋ ๋ณด์กด ๋ฒ์น์ ํํ
์ธ๋ถ ํ ํฌ๊ฐ ์์ฉํ์ง ์๋ ๊ณ์์ ๊ฐ์ด๋๋ L ์ ๋ณด์กด๋๋ค.
๋ณธ ์๋ฎฌ๋ ์ด์ ์์๋ ์ง๋ m ์ ์์๋ก ๋๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ด ๋จ์ํํ๋ค.
์ฆ, ๋ฐ์ง๋ฆ์ด ๊ฐ์ํ๋ฉด ๊ฐ์๋๋ ์ฆ๊ฐํ๊ณ ๋ฐ์ง๋ฆ์ด ์ฆ๊ฐํ๋ฉด ๊ฐ์๋๋ ๊ฐ์ํ๋ค.
ํ์ ์ rotate()๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๊ทน์ขํ โ ์ง๊ต์ขํ ๋ณํ์ผ๋ก ์ง์ ๊ณ์ฐํ๋ค.
x = r cos(ฮธ)
y = r sin(ฮธ)
let angle = 0;
// ๋ฐ์ง๋ฆ
let r = 300;
let baseR = 300;
// ๊ฐ์๋
let omega = 0.01;
let baseOmega = 0.01;
// ๊ฐ์ด๋๋ ์์ (r^2 * omega)
let angularMomentum;
function setup() {
createCanvas(800, 800);
angularMomentum = baseR * baseR * baseOmega;
}
function draw() {
background(255);
translate(width / 2, height / 2);
// ๋ง์ฐ์ค ์
๋ ฅ์ ๋ฐ๋ฅธ ๋ฐ์ง๋ฆ ๋ณํ
if (mouseIsPressed) {
r -= 1.5;
r = max(r, 80); // ์ค์ฌ์ผ๋ก ์์ ํ ๋จ์ด์ง์ง ์๋๋ก ์ ํ
} else {
r += 1.5;
r = min(r, baseR);
}
// ๊ฐ์ด๋๋ ๋ณด์กด: r^2 * omega = constant
omega = angularMomentum / (r * r);
// ๊ฐ๋ ์
๋ฐ์ดํธ
angle += omega;
// ๋ณ ์์น ๊ณ์ฐ
let x = cos(angle) * r;
let y = sin(angle) * r;
// ์ค์ฌ (๋ธ๋ํ)
fill(0);
noStroke();
ellipse(0, 0, 12, 12);
// ๋ณ
ellipse(x, y, 16, 16);
}