๐ŸŽ“ 16์ฃผ์ฐจ : ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ธฐ๋ฐ˜ ์•„ํŠธ (Artworks based on Physical Simulation)

BamgasiJMยท2025๋…„ 11์›” 6์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
27/67
post-thumbnail

โœ… ์˜ˆ์ œ 1 : Springโ€“Mass โ€œJelly Blobโ€

(ํ›„ํฌ๋ฒ•์น™ + ๊ฐ์‡ )

  • ์งˆ์  i์˜ ํž˜:
    F=ฮฃk(โˆฃฮ”xโˆฃโˆ’L)nโˆ’cv(+์ค‘๋ ฅ/์™ธ๋ ฅ)F = ฮฃ k (|ฮ”x|โˆ’L) n โˆ’ c v (+ ์ค‘๋ ฅ/์™ธ๋ ฅ)
  • ์„ธ๋ฏธ-implicit Euler:
    v+=(F/m)โ‹…dtv += (F/m)ยทdt
    x+=vโ‹…dtx += vยทdt
  • ์›ํ˜•(Closed loop)์œผ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด ์ ค๋ฆฌ์ฒ˜๋Ÿผ ์ถœ๋ 
  • rest๋ฅผ ๊ท ๋“ฑ ๊ฐ„๊ฒฉ์œผ๋กœ ๋‘๊ณ  k, c๋ฅผ ํŠœ๋‹ โ†’ ๋ฐœ์‚ฐ ์—†์ด ์ถœ๋ 
  • ๋งˆ์šฐ์Šค ๋“œ๋ž˜๊ทธ๋Š” โ€œ๊ฐ€์ƒ ์Šคํ”„๋งโ€์œผ๋กœ ์—ฐ๊ฒฐ โ†’ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋Œ๋ฆผ
// === 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);
  }
}

โœ… ์˜ˆ์ œ 2 : Collision Particles

(PBD ๋А๋‚Œ์˜ ์œ„์น˜๊ธฐ๋ฐ˜ ์ถฉ๋Œ ํ•ด์†Œ)

  • ์˜ˆ์ธก ์œ„์น˜๋กœ ์ ๋ถ„ โ†’ ์œ„์น˜ ์ œ์•ฝ ํ•ด๊ฒฐ(์ถฉ๋Œ, ๊ฒฝ๊ณ„) โ†’ ์†๋„ ๊ฐฑ์‹ 
  • ์› ์ถฉ๋Œ: pen = rA + rB โˆ’ |ฮ”x|, ๋ณด์ • ๋ฒกํ„ฐ๋ฅผ ์–‘์ชฝ์— ๋ฐ˜์”ฉ ์ ์šฉ
  • ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์ถฉ๋Œ ๋ฐ˜๋ณต(Iterations)
// === 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);
  }
}

โœ… ์˜ˆ์ œ 3 : 2D Stable Fluids

(์†๋„์žฅ + ์—ผ๋ฃŒ์žฅ, ๋ฐ˜-์‹ค์‹œ๊ฐ„)

  • Advection(์ด๋ฅ˜): ๋ฐ˜-๋ผ๊ทธ๋ž‘์ฃผ(๋’ค๋กœ ์ถ”์ ) ์ƒ˜ํ”Œ
  • Diffusion(ํ™•์‚ฐ): Jacobi ๋ฐ˜๋ณต์œผ๋กœ ์„ ํ˜•๊ณ„ ๊ทผ์‚ฌ
  • Projection: ๋ฐœ์‚ฐ=0 ๋˜๋„๋ก ์••๋ ฅ ํ’€๊ณ  v โ† v โˆ’ โˆ‡p
  • ๋งˆ์šฐ์Šค๋กœ ํž˜ ์ถ”๊ฐ€(velocity) + ์—ผ๋ฃŒ ์ฃผ์ž…(dye)
  • ํ•ด์ƒ๋„๋ฅผ ๋‚ฎ๊ฒŒ(N=64) ๋‘๊ณ  ๋ฐ˜๋ณต ํšŸ์ˆ˜(JACOBI=15)๋กœ ํƒ€ํ˜‘. p5์˜ pixels[]๋กœ ๋ Œ๋”.
  • ๋งˆ์šฐ์Šค ๋“œ๋ž˜๊ทธ๊ฐ€ ์†๋„์žฅ์„ ์ง์ ‘ ํ”๋“ฆ(+ ์—ผ๋ฃŒ ์ฃผ์ž…) โ†’ ์™€๋ฅ˜๊ฐ€ ์ƒ๊น€
  • Projection ์œผ๋กœ ๋ฐœ์‚ฐ์„ ์ œ๊ฑฐ โ†’ โ€œ๋ฌผ ๊ฐ™์€โ€ ์งˆ๊ฐ ์œ ์ง€
  • ํ•ด์ƒ๋„๋ฅผ ๋‚ฎ์ถฐ CPU์—์„œ๋„ ํ”„๋ ˆ์ž„ ์œ ์ง€
// === 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);
}

โœ… ์˜ˆ์ œ 4 : Cloth Wave Sculpture

(๋งˆ์šฐ์Šค๋กœ ์ฒœ ํœ˜๋‚ ๋ฆฌ๊ธฐ)

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;
  }
}

โœ… ์˜ˆ์ œ 5 : Conservation of Angular Momentum

์ผ์ • ๋ฐ˜์ง€๋ฆ„ ๋ฐ–์œผ๋กœ ๋Œ๊ณ  ์žˆ๋Š” ๋ฌผ์ฒด๊ฐ€ ์ค‘์‹ฌ์œผ๋กœ ๊ฐ€๊นŒ์›Œ์ง€๋ฉด์„œ ํšŒ์ „ ์†๋„๊ฐ€ ๋นจ๋ผ์ง€๋Š” ๊ฐ์šด๋™๋Ÿ‰ ๋ณด์กด ๋ฒ•์น™์„ ํ‘œํ˜„

๊ฐ์šด๋™๋Ÿ‰ ๋ณด์กด ๋ฒ•์น™

์™ธ๋ถ€ ํ† ํฌ๊ฐ€ ์ž‘์šฉํ•˜์ง€ ์•Š๋Š” ๊ณ„์—์„œ ๊ฐ์šด๋™๋Ÿ‰ L ์€ ๋ณด์กด๋œ๋‹ค.

L=mr2ฯ‰L = m r^2 ฯ‰

  • m : ์งˆ๋Ÿ‰
  • r : ํšŒ์ „ ๋ฐ˜์ง€๋ฆ„
  • ฯ‰ : ๊ฐ์†๋„

๋ณธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ๋Š” ์งˆ๋Ÿ‰ m ์„ ์ƒ์ˆ˜๋กœ ๋‘๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹จ์ˆœํ™”ํ•œ๋‹ค.

r2ฯ‰=constantr^2 ฯ‰ = constant

์ฆ‰, ๋ฐ˜์ง€๋ฆ„์ด ๊ฐ์†Œํ•˜๋ฉด ๊ฐ์†๋„๋Š” ์ฆ๊ฐ€ํ•˜๊ณ  ๋ฐ˜์ง€๋ฆ„์ด ์ฆ๊ฐ€ํ•˜๋ฉด ๊ฐ์†๋„๋Š” ๊ฐ์†Œํ•œ๋‹ค.

์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋™์ž‘ ๊ทœ์น™

  • ์ดˆ๊ธฐ ๋ฐ˜์ง€๋ฆ„์€ 300, ๋ณ„์€ ์ค‘์‹ฌ์„ ๊ธฐ์ค€์œผ๋กœ ์ผ์ • ๊ฐ์†๋„๋กœ ํšŒ์ „ ์ค‘.
  • ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ
    • ๋ฐ˜์ง€๋ฆ„์ด ์ ์  ๊ฐ์†Œ + ๊ฐ์šด๋™๋Ÿ‰ ๋ณด์กด์— ์˜ํ•ด ํšŒ์ „ ์†๋„๋Š” ์ฆ๊ฐ€.
  • ๋งˆ์šฐ์Šค๋ฅผ ๋–ผ๋ฉด
    • ๋ฐ˜์ง€๋ฆ„์ด ๋‹ค์‹œ ์›๋ž˜ ๊ถค๋„๋กœ ๋ณต๊ท€ + ํšŒ์ „ ์†๋„๋Š” ๋‹ค์‹œ ๊ฐ์†Œ.

์ขŒํ‘œ ๊ณ„์‚ฐ ๋ฐฉ์‹

ํšŒ์ „์€ 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);
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€