๐ŸŽ“6์ฃผ์ฐจ: ํŒŒํ‹ฐํด๊ณผ ์—์ด์ „ํŠธ (Particle & Agent)

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

p5.js Art

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

์ด๋ฒˆ์—๋Š” ํŒŒํ‹ฐํด(Particle)๊ณผ ์—์ด์ „ํŠธ(Agent)๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ณด๋ฉด ์–ผํ• ๋น„์Šทํ•ด๋ณด์ด๋‚˜ ํ–‰๋™ ์–‘์‹์˜ ๋ชฉํ‘œ๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒํ‹ฐํด์€ ๋‹จ์ˆœํžˆ ์œ„์น˜์™€ ์†๋„๋งŒ ๊ฐ€์ง„ ๋ฌผ๋ฆฌ์ ์ธ ์ ์ž…๋‹ˆ๋‹ค. ๊ณต์„ ๋˜์ง€๋ฉด ๋ฌผ๋ฆฌ๋ฒ•์น™์— ์˜ํ•ด ํฌ๋ฌผ์„ ์„ ๊ทธ๋ฆฌ๋ฉฐ ๋–จ์–ด์ง€๋Š”๋ฐ ๊ทธ๊ฒƒ์„ ์‹œ๊ฐํ™”ํ•œ ๊ฒƒ์ด ํŒŒํ‹ฐํด์ž…๋‹ˆ๋‹ค. ์ค‘๋ ฅ์„ ๊ฑฐ์Šฌ๋Ÿฌ์„œ ์˜ฌ๋ผ๊ฑฐ๋˜, ํ•œ ์ ์—์„œ ํผ์ ธ๋‚˜๊ฐ€๋˜ ์ •ํ•ด์ง„ ๋ฒ•์น™์— ์˜ํ•ด์„œ ๋ฐœ์ƒํ•˜๊ณ  ์›€์ง์ด๊ณ  ์‚ฌ๋ฆฌ์ง€๋Š” ์ž‘์€ ๋ฌผ์ฒด๋“ค์ž…๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด์— ์—์ด์ „ํŠธ๋Š” ์ด ํŒŒํ‹ฐํด์˜ ์„ฑ๊ฒฉ์— ๊ทœ์น™๊ณผ ํ–‰๋™ ์–‘์‹์„ ๋”ํ•ด ์ง€๋Šฅ์ ์œผ๋กœ ์›€์ง์ด๊ฒŒ ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜์ง„ ๊ณต์ด ์žฅ์• ๋ฌผ์„ ํ”ผํ•˜๊ณ  ์›€์ง์ด๋Š” ๋ชฉํ‘œ๋ฌผ์„ ๋”ฐ๋ผ ๋‚ ์•„๊ฐ€๋Š” ํ–‰๋™์„ ๋ณด์ด๋„๋ก ํ•œ ๊ฒƒ์ด ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ๋“ค์€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉด์„œ ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์„ ์ด๋ฃฐ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ์ ์šฉ๋˜๋Š” ํŠน์ง•์€ ์ž์œจ์„ฑ(Autonomy), ๋ฐ˜์‘์„ฑ(Reactivity), ๊ทธ๋ฆฌ๊ณ  ๋ชฉํ‘œ ์ง€ํ–ฅ์„ฑ(Goal-Oriented)์ž…๋‹ˆ๋‹ค. ์ƒํ˜ธ์ž‘์šฉ์€ ์ฐฝ๋ฒŒ์  ํ–‰๋™(emergent behavior)์„ ๋งŒ๋“ค์–ด๋‚ด๊ณ  ๋งˆ์น˜ ์‚ด์•„์žˆ๋Š” ์œ ๊ธฐ์ฒด์˜ ํŒจํ„ด์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ๋” ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ ์ฃผ์ฐจ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†Œ์ฃผ์ œ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

  1. ๊ธฐ๋ณธ ํŒŒํ‹ฐํด ์›๋ฆฌ (๋Š์ž„์—†๋Š” ์ƒ์„ฑ)
  2. Attaction๊ณผ Repulsion
  3. Seeking
  4. Flocking / Boids
  5. Flow Field
  6. Path Following & Avoidance
  7. Propagation & Diffusion
  8. Spring & Constraints
  9. Self-Organazing Map (SOM)
  10. Lifespan & Emitter

1. ๊ธฐ๋ณธ ํŒŒํ‹ฐํด (Basic Particle)

// ์ „์—ญ ๋ฐฐ์—ด 'particles' ์„ ์–ธ: ์ƒ์„ฑ๋œ ๋ชจ๋“  ์ž…์ž ๊ฐ์ฒด๋“ค์„ ์ €์žฅํ•  ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค.
let particles = [];

// setup() ํ•จ์ˆ˜: p5.js ํ”„๋กœ๊ทธ๋žจ์ด ์‹œ์ž‘๋  ๋•Œ ๋‹จ ํ•œ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ํ™˜๊ฒฝ ์„ค์ •์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
function setup() {
  // createCanvas(width, height): ์บ”๋ฒ„์Šค(๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ์˜์—ญ)์˜ ํฌ๊ธฐ๋ฅผ 800x400 ํ”ฝ์…€๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  createCanvas(800, 400);
}

// draw() ํ•จ์ˆ˜: ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ค‘ ์ง€์†์ ์œผ๋กœ, ์ดˆ๋‹น ์•ฝ 60ํ”„๋ ˆ์ž„ ์†๋„๋กœ ๋ฐ˜๋ณต ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ํ•ต์‹ฌ ๋ฃจํ”„์ž…๋‹ˆ๋‹ค.
function draw() {
  // background(r, g, b): ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ฐฐ๊ฒฝ์„ ์–ด๋‘์šด ํšŒ์ƒ‰(25)์œผ๋กœ ์น ํ•ฉ๋‹ˆ๋‹ค.
  // ์ด๋ฅผ ํ†ตํ•ด ์ด์ „ ํ”„๋ ˆ์ž„์˜ ์ž…์ž๋“ค์ด ์ง€์›Œ์ง€๊ณ  ์›€์ง์ด๋Š” ํšจ๊ณผ๊ฐ€ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.
  background(25);

  // ์ƒˆ๋กœ์šด ์ž…์ž ์ƒ์„ฑ
  // particles ๋ฐฐ์—ด์— ์ƒˆ๋กœ์šด Particle ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  // width / 2๋Š” ์บ”๋ฒ„์Šค์˜ ๊ฐ€๋กœ ์ค‘์•™์„ ์˜๋ฏธํ•˜๋ฉฐ, 100์€ ์ƒ๋‹จ์—์„œ 100ํ”ฝ์…€ ์•„๋ž˜ ์œ„์น˜๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  particles.push(new Particle(width / 2, 100));

  // ์ž…์ž ์—…๋ฐ์ดํŠธ ๋ฐ ๋ Œ๋”๋ง
  // for ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ particles ๋ฐฐ์—ด์˜ ๋ชจ๋“  ์ž…์ž๋“ค์„ ์ˆœํšŒํ•ฉ๋‹ˆ๋‹ค.
  // ๋ฐฐ์—ด ๋์—์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ์—ญ์ˆœ์œผ๋กœ ์ˆœํšŒํ•˜๋Š” ์ด์œ ๋Š”,
  // ๋ฃจํ”„ ๋„์ค‘์— ์ž…์ž๋ฅผ ๋ฐฐ์—ด์—์„œ ์ œ๊ฑฐ(splice)ํ•  ๋•Œ ์ธ๋ฑ์Šค ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.
  for (let i = particles.length - 1; i >= 0; i--) {
    // ํ˜„์žฌ ์ˆœํšŒ ์ค‘์ธ ์ž…์ž ๊ฐ์ฒด๋ฅผ p ๋ณ€์ˆ˜์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.
    let p = particles[i];
    // p.update(): ์ž…์ž์˜ ์œ„์น˜, ์†๋„, ์ˆ˜๋ช…์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
    p.update();
    // p.show(): ์บ”๋ฒ„์Šค์— ์ž…์ž๋ฅผ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    p.show();
    // p.isDead(): ์ž…์ž์˜ ์ˆ˜๋ช…์ด ๋‹คํ–ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    if (p.isDead()) {
      // ์ž…์ž๊ฐ€ ์ฃฝ์—ˆ๋‹ค๋ฉด, particles ๋ฐฐ์—ด์—์„œ ํ•ด๋‹น ์ž…์ž๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. (i๋ฒˆ์งธ ์ธ๋ฑ์Šค๋ถ€ํ„ฐ 1๊ฐœ)
      particles.splice(i, 1);
    }
  }
}

// Particle ํด๋ž˜์Šค: ๊ฐœ๋ณ„ ์ž…์ž์˜ ์†์„ฑ๊ณผ ๋™์ž‘์„ ์ •์˜ํ•˜๋Š” ์„ค๊ณ„๋„์ž…๋‹ˆ๋‹ค.
class Particle {
  // constructor(x, y): ์ƒˆ๋กœ์šด Particle ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ์ดˆ๊ธฐ ์†์„ฑ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  // x, y๋Š” ์ž…์ž๊ฐ€ ์ฒ˜์Œ ์ƒ์„ฑ๋  ์ดˆ๊ธฐ ์œ„์น˜์ž…๋‹ˆ๋‹ค.
  constructor(x, y) {
    // this.pos: p5.Vector ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…์ž์˜ ์œ„์น˜(x, y)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    this.pos = createVector(x, y);
    // this.vel: p5.Vector ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…์ž์˜ ์†๋„(velocity)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    // p5.Vector.random2D()๋Š” ๋ฌด์ž‘์œ„ ๋ฐฉํ–ฅ์˜ ๋‹จ์œ„ ๋ฒกํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ ,
    // mult(random(1, 3))๋ฅผ ํ†ตํ•ด ์†๋„์˜ ํฌ๊ธฐ๋ฅผ 1์—์„œ 3 ์‚ฌ์ด์˜ ๋ฌด์ž‘์œ„ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ์ดˆ๊ธฐ ํ™•์‚ฐ ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.
    this.vel = p5.Vector.random2D().mult(random(1, 3));
    // this.acc: p5.Vector ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…์ž์˜ ๊ฐ€์†๋„(acceleration)๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    // (0, 0.05)๋Š” y์ถ• ๋ฐฉํ–ฅ(์•„๋ž˜์ชฝ)์œผ๋กœ ์ผ์ •ํ•œ ์ค‘๋ ฅ ๊ฐ€์†๋„๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
    this.acc = createVector(0, 0.05);
    // this.lifetime: ์ž…์ž์˜ ๋‚จ์€ ์ˆ˜๋ช…์„ 255(์ตœ๋Œ€ ๋ถˆํˆฌ๋ช…๋„ ๊ฐ’)๋กœ ์ดˆ๊ธฐ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    this.lifetime = 255;
  }

  // update() ๋ฉ”์„œ๋“œ: ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ž…์ž์˜ ๋ฌผ๋ฆฌ์ ์ธ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
  update() {
    // ์†๋„์— ๊ฐ€์†๋„๋ฅผ ๋”ํ•ฉ๋‹ˆ๋‹ค (์ค‘๋ ฅ์˜ ์˜ํ–ฅ์œผ๋กœ ์†๋„๊ฐ€ ์ ์  ๋นจ๋ผ์ง).
    this.vel.add(this.acc);
    // ์œ„์น˜์— ํ˜„์žฌ ์†๋„๋ฅผ ๋”ํ•ฉ๋‹ˆ๋‹ค (์‹ค์ œ ์ž…์ž ์ด๋™).
    this.pos.add(this.vel);
    // ์ˆ˜๋ช…์„ 2์”ฉ ๊ฐ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค (์ ์  ํˆฌ๋ช…ํ•ด์ง€๊ณ  ์‚ฌ๋ผ์ง).
    this.lifetime -= 2;
  }

  // show() ๋ฉ”์„œ๋“œ: ํ˜„์žฌ ์œ„์น˜์™€ ์ˆ˜๋ช…(ํˆฌ๋ช…๋„)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž…์ž๋ฅผ ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
  show() {
    // noStroke(): ์ž…์ž ๋„ํ˜•์˜ ํ…Œ๋‘๋ฆฌ ์„ ์„ ๊ทธ๋ฆฌ์ง€ ์•Š๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    noStroke();
    // fill(r, g, b, alpha): ์ž…์ž์˜ ์ฑ„์šฐ๊ธฐ ์ƒ‰์ƒ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    // ๋…ธ๋ž€์ƒ‰(210, 180, 30)์— ํ˜„์žฌ ์ˆ˜๋ช…(this.lifetime)์„ ํˆฌ๋ช…๋„(alpha)๋กœ ์‚ฌ์šฉํ•˜์—ฌ
    // ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์ž…์ž๊ฐ€ ํ๋ฆฟํ•ด์ง€๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค.
    fill(210, 180, 30, this.lifetime);
    // ellipse(x, y, w, h): ํ˜„์žฌ ์œ„์น˜(this.pos.x, this.pos.y)์— ์ง€๋ฆ„ 8ํ”ฝ์…€์˜ ์›์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    ellipse(this.pos.x, this.pos.y, 8);
  }

  // isDead() ๋ฉ”์„œ๋“œ: ์ž…์ž๊ฐ€ ์ œ๊ฑฐ๋˜์–ด์•ผ ํ•  ์‹œ์ ์„ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.
  isDead() {
    // ์ˆ˜๋ช…์ด 0๋ณด๋‹ค ์ž‘์•„์ง€๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ž…์ž๊ฐ€ '์ฃฝ์—ˆ์Œ'์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
    return this.lifetime < 0;
  }
}

๐Ÿ“˜ ํ•ต์‹ฌ ๊ฐœ๋…

  • ๊ฐœ๋ณ„ ์ž…์ž = ์œ„์น˜ + ์†๋„ + ๊ฐ€์†๋„ + ์ˆ˜๋ช…
  • ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ฌผ๋ฆฌ ๊ฐฑ์‹  โ†’ ์ˆ˜๋ช… ๊ฐ์†Œ โ†’ ์‚ญ์ œ
  • ์‹œ์Šคํ…œ = ๊ฐœ๋ณ„ ์ž…์ž๋“ค์˜ ์ง‘ํ•ฉ

2. ๋Œ๋ฆผ / ๋ฐ˜๋ฐœ (Attraction / Repulsion)

  • ๊ฐœ๋…: ๋ฌผ๋ฆฌ ๋ฒ•์น™์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘ก๋‹ˆ๋‹ค. ๋‘ ๋ฌผ์ฒด ์‚ฌ์ด์— ์ž‘์šฉํ•˜๋Š” ํž˜(Force)์˜ ํฌ๊ธฐ๋ฅผ ๊ฑฐ๋ฆฌ์˜ ์ œ๊ณฑ์— ๋ฐ˜๋น„๋ก€ํ•˜๊ฒŒ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค (๋งŒ์œ ์ธ๋ ฅ).

  • ๋กœ์ง์˜ ํ•ต์‹ฌ:๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ dd์™€ ๋ฐฉํ–ฅ r\mathbf{r}์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.ํž˜์˜ ํฌ๊ธฐ FF๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. (์ผ๋ฐ˜์ ์œผ๋กœ F=Gโ‹…m1โ‹…m2d2F = \frac{G \cdot m_1 \cdot m_2}{d^2} ์ด์ง€๋งŒ, ๋‹จ์ˆœํ™”ํ•˜์—ฌ F=GdF = \frac{G}{d} ๋“ฑ์œผ๋กœ ์‚ฌ์šฉ๋˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.)์ด ํž˜์„ ๊ฐ€์†๋„์— ์ง์ ‘ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ๊ฒฐ๊ณผ: ๋ชฉํ‘œ ์ง€์ ์— ๋„๋‹ฌํ•  ๋•Œ ์†๋„๋ฅผ ์กฐ์ ˆํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜(์กฐํ–ฅ๋ ฅ)์ด ์—†์œผ๋ฉด, ๊ด€์„ฑ ๋•Œ๋ฌธ์— ๋ชฉํ‘œ ์ง€์ ์„ ํ†ต๊ณผํ•˜์—ฌ ํŠ•๊ฒจ ๋‚˜๊ฐ€๊ฑฐ๋‚˜ ๋ชฉํ‘œ ์ฃผ๋ณ€์„ ์ง„๋™ํ•˜๋ฉฐ ๋งด๋„๋Š” (๊ถค๋„๋ฅผ ๋„๋Š”) ๊ฒฝํ–ฅ์ด ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค.

let particles = [];
let attractMode = true; // true๋ฉด ๋Œ๋ฆผ, false๋ฉด ๋ฐ€์–ด๋ƒ„

function setup() {
  createCanvas(800, 400);
  for (let i = 0; i < 2000; i++) {
    particles.push(new Particle(random(width), random(height)));
  }
}

function draw() {
  background(2, 10);
  fill(255);
  noStroke();
  text(attractMode ? "Attraction Mode" : "Repulsion Mode", 10, 20);

  for (let p of particles) {
    p.behaviors();
    p.update();
    p.show();
  }
}

function mousePressed() {
  attractMode = !attractMode;
}

class Particle {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = p5.Vector.random2D();
    this.acc = createVector();
  }

  behaviors() {
    let mouse = createVector(mouseX, mouseY);
    let dir = p5.Vector.sub(mouse, this.pos);
    let d = dir.mag();
    d = constrain(d, 10, 30);
    dir.normalize();

    let strength = attractMode ? 5.0 / d : -5.0 / d;
    dir.mult(strength);
    this.acc = dir;
  }

  update() {
    this.vel.add(this.acc);
    this.pos.add(this.vel);
    this.vel.limit(5);
  }

  show() {
    stroke(255);
    strokeWeight(4);
    point(this.pos.x, this.pos.y);
  }
}

๐Ÿ“˜ ํ•ต์‹ฌ ๊ฐœ๋…

  • ํ–‰๋™(Behavior) = ์™ธ๋ถ€ ์ž๊ทน์— ๋Œ€ํ•œ ๋ฐ˜์‘ (๋งˆ์šฐ์Šค ์œ„์น˜)
  • ํž˜(Force) = ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ ๋ฒกํ„ฐ (๋Œ๋ฆผ/๋ฐ€๋ฆผ)
  • ๋ชจ๋“œ ์ „ํ™˜: ํด๋ฆญํ•  ๋•Œ๋งˆ๋‹ค attract โ†” repulse

3.์ถ”์  (Seeking)

  • ๊ฐœ๋…: Steering Behavior (์กฐํ–ฅ ํ–‰๋™)์˜ ์ผ์ข…์ž…๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ(ํŒŒํ‹ฐํด)๊ฐ€ ๋ชฉํ‘œ ์ง€์ (Target)์œผ๋กœ ๊ฐ€์žฅ ํšจ์œจ์ ์ธ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์•„๊ฐ€๋„๋ก ๋ฐฉํ–ฅ์„ ์กฐ์ ˆํ•˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘ก๋‹ˆ๋‹ค.

  • ๋กœ์ง์˜ ํ•ต์‹ฌ:

  1. ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€์˜ ๋ฐฉํ–ฅ์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  2. ์ด ๋ฐฉํ–ฅ์„ ํ–ฅํ•˜๋Š” ํฌ๋ง ์†๋„(Desired Velocity)๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  3. ์กฐํ–ฅ๋ ฅ (Steering Force) = ํฌ๋ง ์†๋„ - ํ˜„์žฌ ์†๋„๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  4. ์ด ์กฐํ–ฅ๋ ฅ์— ์ตœ๋Œ€ ์กฐํ–ฅ๋ ฅ (maxForce) ์ œํ•œ์„ ๊ฑธ์–ด ๋ถ€๋“œ๋Ÿฌ์šด ์›€์ง์ž„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ: ๋ชฉํ‘œ ์ง€์ ์— ๋„๋‹ฌํ•˜๋ฉด ์†๋„๋ฅผ ์ค„์ด๊ณ  ๋ฉˆ์ถ”๋ ค๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชฉํ‘œ ์ฃผ๋ณ€์„ ๋งด๋Œ์ง€ ์•Š๊ณ  ์ •์ฐฉํ•ฉ๋‹ˆ๋‹ค.
// sketch.js

let NUM_AGENTS = 80;
let agents = [];

function setup() {
  createCanvas(800, 400);
  background(25);  // ์บ”๋ฒ„์Šค๋ฅผ ๊นจ๋—ํ•˜๊ฒŒ ์‹œ์ž‘

  for (let i = 0; i < NUM_AGENTS; i++) {
    let start = createVector(
      width / 2 + random(-20, 20),
      height / 2 + random(-20, 20)
    );
    agents.push(new Agent(start));
  }
}

function draw() {
  // ๋ฐฐ๊ฒฝ: ์–ด๋‘์šด ํšŒ์ƒ‰ (25) ๋ฐ ๊ผฌ๋ฆฌ ํšจ๊ณผ
  fill(0, 5);
  noStroke();
  rect(0, 0, width, height);

  let target = createVector(mouseX, mouseY);

  for (let agent of agents) {
    // --- 1. ๋‘ ๊ฐ€์ง€ ํž˜ ๊ณ„์‚ฐ ---

    // 1-1. ๋ถ„๋ฆฌ(Separation) ํž˜ ๊ณ„์‚ฐ: ๋‹ค๋ฅธ ์—์ด์ „ํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ€์–ด์ง€๋ ค๋Š” ํž˜
    let separation = agent.separate(agents);

    // 1-2. ์ถ”์ (Seeking) ํž˜ ๊ณ„์‚ฐ: ๋ชฉํ‘œ ์ง€์ (๋งˆ์šฐ์Šค)์„ ํ–ฅํ•ด ๊ฐ€๋ ค๋Š” ํž˜
    let seeking = agent.seek(target);

    // --- 2. ํž˜์— ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•˜๊ณ  ์ ์šฉ ---

    // ๋ถ„๋ฆฌ ํž˜์— ๋” ๋†’์€ ๊ฐ€์ค‘์น˜(1.2)๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ๊ฒน์น˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€
    separation.mult(1.2);
    // ์ถ”์  ํž˜์—๋Š” ํ‘œ์ค€ ๊ฐ€์ค‘์น˜(1.0) ๋ถ€์—ฌ
    seeking.mult(1.0);

    // ๊ณ„์‚ฐ๋œ ํž˜์„ ๋ชจ๋‘ ์ ์šฉ
    agent.applyForce(separation);
    agent.applyForce(seeking);

    // --- 3. ์—…๋ฐ์ดํŠธ ๋ฐ ๋ Œ๋”๋ง ---
    agent.update();
    agent.checkEdges();
    agent.display();
  }
}

// ---------------------------------------------------------------------
// Agent ํด๋ž˜์Šค ์ •์˜ (seperation ๋กœ์ง ๋ฐ seeking ๋กœ์ง )
// ---------------------------------------------------------------------
class Agent {
  constructor(l) {
    this.position = l;
    this.velocity = p5.Vector.random2D();
    this.acceleration = createVector(0, 0);

    this.maxSpeed = 2.0;
    this.maxForce = 0.2;
    this.radius = 2;

    // ๊ฐ์ฒด ์ƒ‰์ƒ: ์‚ฐํ˜ธ์ƒ‰(Coral, RGB: 255, 127, 80)
    this.agentColor = color(255, 127, 80);
  }

  applyForce(force) {
    this.acceleration.add(force);
  }

  // Seeking ๋ฉ”์„œ๋“œ๊ฐ€ ํž˜์„ ๋ฐ”๋กœ ์ ์šฉํ•˜์ง€ ์•Š๊ณ  Steering Force ๋ฒกํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •
  seek(target) {
    let desired = p5.Vector.sub(target, this.position);
    desired.normalize();
    desired.mult(this.maxSpeed);

    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(this.maxForce);

    return steer; // ํž˜์„ ๋ฐ˜ํ™˜
  }

  // --- ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ Separation (๋ถ„๋ฆฌ) ํ–‰๋™ ๊ตฌํ˜„ ---
  separate(agents) {
    let desiredSeparation = this.radius * 2 + 10; // ๋„ˆ๋ฌด ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ๋ถ„๋ฆฌ ์‹œ์ž‘ํ•  ๊ฑฐ๋ฆฌ
    let sum = createVector(0, 0);
    let count = 0;

    for (let other of agents) {
      let d = p5.Vector.dist(this.position, other.position);

      // ๋‹ค๋ฅธ ์—์ด์ „ํŠธ์ด๊ณ , ๋„ˆ๋ฌด ๊ฐ€๊นŒ์ด ์žˆ๋‹ค๋ฉด
      if (d > 0 && d < desiredSeparation) {
        // ์ด์›ƒ์œผ๋กœ๋ถ€ํ„ฐ ๋ฉ€์–ด์ง€๋Š” ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
        let diff = p5.Vector.sub(this.position, other.position);
        diff.normalize();
        // ๊ฑฐ๋ฆฌ๊ฐ€ ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๊ฐ•ํ•œ ํž˜์„ ๋ถ€์—ฌ (๊ฑฐ๋ฆฌ์˜ ์ œ๊ณฑ์ด ์•„๋‹Œ, ๋‹จ์ˆœ ๊ฑฐ๋ฆฌ๋กœ ๋‚˜๋ˆ”)
        diff.div(d);
        sum.add(diff);
        count++;
      }
    }

    if (count > 0) {
      sum.div(count); // ํ‰๊ท  ๋ถ„๋ฆฌ ๋ฐฉํ–ฅ ๊ณ„์‚ฐ

      // Steering Behavior ๋กœ์ง์„ ์ ์šฉํ•˜์—ฌ ์ตœ์ข… ๋ถ„๋ฆฌ ํž˜ ๊ณ„์‚ฐ
      sum.normalize();
      sum.mult(this.maxSpeed);

      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(this.maxForce);
      return steer;
    }
    return createVector(0, 0);
  }
  // -------------------------------------------------------

  update() {
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxSpeed);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
  }

  checkEdges() {
    if (this.position.x > width) this.position.x = 0;
    else if (this.position.x < 0) this.position.x = width;
    if (this.position.y > height) this.position.y = 0;
    else if (this.position.y < 0) this.position.y = height;
  }

  display() {
    stroke(this.agentColor);
    fill(this.agentColor, 200);
    ellipse(this.position.x, this.position.y, this.radius * 2, this.radius * 2);
  }
}

4.๊ตฐ์ง‘ ํ–‰๋™ (Flocking / Boids Algorithm)

// Separation (๋ถ„๋ฆฌ) : ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ๋ฉ€์–ด์ง€๊ฒŒ
// Alignment (์ •๋ ฌ) : ์ฃผ๋ณ€์˜ ํ‰๊ท  ์†๋„์™€ ๋น„์Šทํ•˜๊ฒŒ
// Cohesion (์‘์ง‘) : ์ฃผ๋ณ€ ๊ฐœ์ฒด์˜ ์ค‘์‹ฌ์œผ๋กœ ์ด๋™
let flock = [];

function setup() {
  createCanvas(800, 400);
  for (let i = 0; i < 250; i++) {
    flock.push(new Boid());
  }
}

function draw() {
  background(25);
  for (let boid of flock) {
    boid.flock(flock);
    boid.update();
    boid.edges();
    boid.show();
  }
}

class Boid {
  constructor() {
    this.pos = createVector(random(width), random(height));
    this.vel = p5.Vector.random2D();
    this.vel.setMag(random(2, 4));
    this.acc = createVector();
    this.maxForce = 0.1;
    this.maxSpeed = 2;
  }

  edges() {
    if (this.pos.x > width) this.pos.x = 0;
    if (this.pos.x < 0) this.pos.x = width;
    if (this.pos.y > height) this.pos.y = 0;
    if (this.pos.y < 0) this.pos.y = height;
  }

  align(boids) {
    let perception = 50;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
      if (other != this && d < perception) {
        steering.add(other.vel);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.setMag(this.maxSpeed);
      steering.sub(this.vel);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  cohesion(boids) {
    let perception = 50;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
      if (other != this && d < perception) {
        steering.add(other.pos);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.sub(this.pos);
      steering.setMag(this.maxSpeed);
      steering.sub(this.vel);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  separation(boids) {
    let perception = 24;
    let steering = createVector();
    let total = 0;
    for (let other of boids) {
      let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
      if (other != this && d < perception) {
        let diff = p5.Vector.sub(this.pos, other.pos);
        diff.div(d * d);
        steering.add(diff);
        total++;
      }
    }
    if (total > 0) {
      steering.div(total);
      steering.setMag(this.maxSpeed);
      steering.sub(this.vel);
      steering.limit(this.maxForce);
    }
    return steering;
  }

  flock(boids) {
    let alignment = this.align(boids);
    let cohesion = this.cohesion(boids);
    let separation = this.separation(boids);

    // ๊ฐ ๊ทœ์น™์˜ ์˜ํ–ฅ ๊ฐ€์ค‘์น˜
    separation.mult(1.5);
    alignment.mult(1.0);
    cohesion.mult(1.0);

    this.acc.add(alignment);
    this.acc.add(cohesion);
    this.acc.add(separation);
  }

  update() {
    this.pos.add(this.vel);
    this.vel.add(this.acc);
    this.vel.limit(this.maxSpeed);
    this.acc.mult(0);
  }

  show() {
    strokeWeight(6);
    stroke(150, 255, 30);
    point(this.pos.x, this.pos.y);
  }
}

๐Ÿ“˜ ํ•ต์‹ฌ ๊ฐœ๋…

  • ๋ณต์žกํ•œ ํŒจํ„ด = ๋‹จ์ˆœ ๊ทœ์น™๋“ค์˜ ์ƒํ˜ธ์ž‘์šฉ
  • ์ „์—ญ ์ œ์–ด ์—†์ด โ€œ์ž์œจ์ ์ธโ€ ์‹œ์Šคํ…œ ํ–‰๋™

์•„๋ž˜๋Š” 5~10๋ฒˆ ์†Œ์ฃผ์ œ์˜ ์†Œ๊ฐœ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ์ถ”ํ›„ ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

5.์œ ๊ธฐ์  ๊ฒฝ๋กœ ์ถ”์ 

  • Flow Field (ํ๋ฆ„์žฅ)
  • ๋ฏธ๋ฆฌ ์ •์˜๋œ 2D/3D ๊ณต๊ฐ„์˜ ํž˜(๋ฒกํ„ฐ์žฅ)์„ ๋”ฐ๋ผ ํŒŒํ‹ฐํด์ด ์›€์ง์ž…๋‹ˆ๋‹ค. ๋งˆ์น˜ ๋ฐ”๋žŒ์ด๋‚˜ ๋ฌผ์˜ ํ๋ฆ„์„ ๋”ฐ๋ผ๊ฐ€๋Š” ๋“ฏํ•œ ์œ ๊ธฐ์ ์ด๊ณ  ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ๋กœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

6.์žฅ์• ๋ฌผ ํšŒํ”ผ

  • Path Following & Avoidance
  • ํŒŒํ‹ฐํด์ด ์ •ํ•ด์ง„ ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ๋™์‹œ์— ๋ฒฝ์ด๋‚˜ ๋‹ค๋ฅธ ์žฅ์• ๋ฌผ์„ ์ธ์ง€ํ•˜๊ณ  ํ”ผํ•˜๋Š” ํ–‰๋™์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ์˜ ์ง€๋Šฅ์„ ๋†’์ž…๋‹ˆ๋‹ค.

7.์—ฐ์‡„ ๋ฐ˜์‘/์‹ ๊ฒฝ๋ง

  • Propagation & Diffusion
  • ํ•œ ํŒŒํ‹ฐํด์˜ ์ƒํƒœ(์˜ˆ: ์ƒ‰์ƒ, ํฌ๊ธฐ) ๋ณ€ํ™”๊ฐ€ ์ฃผ๋ณ€ ํŒŒํ‹ฐํด๋กœ ํŒŒ๊ธ‰๋˜๊ฑฐ๋‚˜, ํŒŒํ‹ฐํด๋“ค์ด ๊ทธ๋ฆฌ๋“œ๋ฅผ ๋”ฐ๋ผ ์ƒ‰์ƒ์ด๋‚˜ ๊ฐ’์„ ์ „๋‹ฌํ•˜์—ฌ ํŒจํ„ด์„ ๋งŒ๋“œ๋Š” ํ™•์‚ฐ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค

8.ํƒ„์„ฑ/๊ตฌ์†

  • Springs & Constraints
  • ํŒŒํ‹ฐํด๋“ค์„ ๊ฐ€์ƒ์˜ ์šฉ์ˆ˜์ฒ (Springs)๋กœ ์—ฐ๊ฒฐํ•˜์—ฌ, ๋Œ์–ด๋‹น๊ธฐ๊ฑฐ๋‚˜ ๋Š˜์–ด๋‚˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ฒœ, ๊นƒ๋ฐœ, ์—ฐ์ฒด ๋™๋ฌผ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ์  ์›€์ง์ž„์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค.

9.์ž๊ธฐ ์กฐ์งํ™” ์ง€๋„

  • Self-Organizing Map (SOM)
  • ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ์…‹์„ ํŒŒํ‹ฐํด์˜ 2D ์œ„์น˜๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ, ํŒŒํ‹ฐํด์ด ์„œ๋กœ ์˜ํ–ฅ์„ ์ฃผ์–ด ์ž๋ฐœ์ ์ธ ํŒจํ„ด์ด๋‚˜ ์งˆ์„œ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค. (๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ์‘์šฉ ๊ฐ€๋Šฅ)

10.์†Œ๋ฉธ ๋ฐ ์žฌ์ƒ

  • Lifespan & Emitter
  • ํŒŒํ‹ฐํด์— ์ˆ˜๋ช…(lifespan)์„ ๋ถ€์—ฌํ•˜์—ฌ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์‚ฌ๋ผ์ง€๊ณ , ํŠน์ • ์ง€์ (Emitter)์—์„œ ์ƒˆ๋กœ์šด ํŒŒํ‹ฐํด์ด ์ง€์†์ ์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์—ฐ๊ธฐ, ๋ถˆ๊ฝƒ, ๋ถ„์ˆ˜ ๋“ฑ์— ํ•„์ˆ˜์ ์ธ ๊ตฌ์„ฑ์ž…๋‹ˆ๋‹ค
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

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