๐ŸŽ“9์ฃผ์ฐจ: ํ™•์‚ฐ ์ œํ•œ ์‘์ง‘ (DLA)

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

p5.js Art

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

Diffusion-Limited Aggregation (DLA)

ํ™•์‚ฐ ์ œํ•œ ์‘์ง‘์€ โ€œ๋ฌด์ž‘์œ„๋กœ ์›€์ง์ด๋Š” ์ž…์ž๋“ค์ด ์„œ๋กœ ๋งŒ๋‚˜๋ฉด์„œ ๋ญ‰์ณ์ง€๋Š” ํŒจํ„ดโ€์„ ๋งŒ๋“œ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ๋ˆˆ์†ก์ด, ๋ฒˆ๊ฐœ, ๋‚˜๋ญ‡๊ฐ€์ง€, ํ˜ˆ๊ด€, ๊ด‘๋ฌผ ๊ฒฐ์ • ๋“ฑ ์ž์—ฐ์˜ ๋ถ„๊ธฐ ๊ตฌ์กฐ(fractal branching)๋ฅผ ์„ค๋ช…ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ์›๋ฆฌ์ž…๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ๋‚ด์šฉ
์ด๋ฆ„Diffusion-Limited Aggregation (DLA)
์˜๋ฏธํ™•์‚ฐ(diffusion)์„ ํ•˜๋˜ ์ž…์ž๋“ค์ด ์„œ๋กœ ๋‹ฌ๋ผ๋ถ™์œผ๋ฉฐ ์„ฑ์žฅํ•˜๋Š” ์ง‘ํ•ฉ(aggregation)์„ ํ˜•์„ฑ
ํ•ต์‹ฌ ์•„์ด๋””์–ด์ž…์ž๋“ค์ด ๋ธŒ๋ผ์šด ์šด๋™(๋ฌด์ž‘์œ„ ๋ณดํ–‰)์„ ํ•˜๋‹ค๊ฐ€ โ€œํ•ต(seed)โ€์— ๋‹ฟ์œผ๋ฉด ๋ถ™๋Š”๋‹ค
๊ฒฐ๊ณผ ํŒจํ„ด๊ฐ€์ง€ ๋ชจ์–‘์˜ ํ”„๋ž™ํƒˆ ๊ตฌ์กฐ (branching fractal)
๊ด€๋ จ ํ˜„์ƒ๋ˆˆ ๊ฒฐ์ •, ๋ฒˆ๊ฐœ ๋ฐฉ์ „, ์ „๊ธฐ ๋„๊ธˆ, ์นจ์ „, ์‹๋ฌผ ๋ฟŒ๋ฆฌ, ๊ท ์‚ฌ์ฒด ๋“ฑ

์ž‘๋™ ๊ณผ์ •

์‹œ๋“œ(seed) ์ž…์ž ํ•˜๋‚˜๋ฅผ ์ค‘์‹ฌ์— ๋‘ 
โ†’ ๋‚˜๋ฌด์˜ ๋ฟŒ๋ฆฌ๋‚˜ ๋ˆˆ ๊ฒฐ์ •์˜ ์ค‘์‹ฌ ๊ฐ™์€ ์—ญํ• 

์ƒˆ๋กœ์šด ์ž…์ž๋ฅผ ํ™”๋ฉด์˜ ์™ธ๊ณฝ์— ๋ฌด์ž‘์œ„๋กœ ์ƒ์„ฑ

๊ทธ ์ž…์ž๊ฐ€ ๋žœ๋ค ์›Œํฌ(Random Walk) ํ•˜๋ฉฐ ์ด๋™
(์ƒํ•˜์ขŒ์šฐ, ํ˜น์€ ๋Œ€๊ฐ ํฌํ•จ ๋ฌด์ž‘์œ„ ๋ฐฉํ–ฅ์œผ๋กœ ํ•œ ์นธ์”ฉ)

๋งŒ์•ฝ ์ž…์ž๊ฐ€ ์ด๋ฏธ ๋ถ™์–ด์žˆ๋Š” ๊ตฌ์กฐ์— ์ ‘์ด‰ํ•˜๋ฉด
โ†’ ๊ทธ ์ž๋ฆฌ์—์„œ ๋ฉˆ์ถ”๊ณ  ๊ตฌ์กฐ์— ํก์ˆ˜(attach) ๋จ

๊ทธ๋‹ค์Œ ์ƒˆ๋กœ์šด ์ž…์ž๋ฅผ ์ƒ์„ฑํ•ด์„œ ๊ฐ™์€ ๊ณผ์ •์„ ๋ฐ˜๋ณต
โ†’ ๊ฐ€์ง€์ฒ˜๋Ÿผ ํ™•์žฅ๋˜์–ด ์ž๋ผ๋‚จ

์ž์—ฐ ์† ์˜ˆ์‹œ

โ„๏ธ ๋ˆˆ ๊ฒฐ์ •: ์ค‘์‹ฌ์—์„œ ๊ฐ€์ง€๊ฐ€ ๋ป—์œผ๋ฉฐ ๋ถ„๊ธฐ

โšก๏ธ ๋ฒˆ๊ฐœ: ์ „๋ฅ˜๊ฐ€ ๊ฐ€์žฅ ์งง์€ ๊ธธ์„ ๋”ฐ๋ผ ํ™•์‚ฐ์ ์œผ๋กœ ์„ฑ์žฅ

๐ŸŒฟ ๋‚˜๋ฌด: ์—๋„ˆ์ง€ ํ๋ฆ„์— ๋”ฐ๋ผ ๋น„๋Œ€์นญ์ ์ธ ๋ถ„๊ธฐ ๊ตฌ์กฐ ํ˜•์„ฑ

๐Ÿชจ ๊ด‘๋ฌผ ๊ฒฐ์ •: ์ด์˜จ๋“ค์ด ํ™•์‚ฐ๋˜๋ฉฐ ํ‘œ๋ฉด์— ๋‹ฌ๋ผ๋ถ™์–ด ์„ฑ์žฅ

์—ฐ๊ด€์ด ์žˆ๋Š” ๊ฐœ๋…

์ฃผ์ œ๊ด€๊ณ„
๋žœ๋ค(Randomness)์ž…์ž ์ด๋™์˜ ๊ธฐ๋ฐ˜ (๋ธŒ๋ผ์šด ์šด๋™)
๋…ธ์ด์ฆˆ(Noise)ํ™•์‚ฐ ๊ฒฝ๋กœ์˜ ์—ฐ์†์  ๋ณ€ํ˜•
์‹œ์Šคํ…œ(Systems)์ง‘๋‹จ์  ํ–‰๋™์œผ๋กœ ๋ณต์žก์„ฑ ์ƒ์„ฑ
ํ˜•ํƒœ & ๊ตฌ์กฐ(Form & Structure)๋‹จ์ˆœ ๊ทœ์น™ โ†’ ๋ณต์žกํ•œ ํ”„๋ž™ํƒˆ ๊ตฌ์กฐ

1.๋ธŒ๋ผ์šด ์šด๋™ ์‹œ๊ฐํ™” - DLA์˜ ๊ธฐ์ดˆ

let walkers = [];

function setup() {
  createCanvas(800, 400);
  for (let i = 0; i < 400; i++) {
    walkers.push(createVector(width / 2, height / 2));
  }
  background(15);
  stroke(30, 240, 210, 20);
}

function draw() {
  for (let w of walkers) {
    point(w.x, w.y);
    w.x += random(-2, 2);
    w.y += random(-2, 2);

    // ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ: ์บ”๋ฒ„์Šค ์•ˆ์— ๊ฐ€๋‘๊ธฐ (Constrain)
    w.x = constrain(w.x, 0, width);
    w.y = constrain(w.y, 0, height);
  }
}

2. ์ค‘์‹ฌ์—์„œ ์ž๋ผ๋‚˜๋Š” ์ž…์ž


const MAX_PARTICLES = 1000; // ๋™์‹œ์— ์กด์žฌํ•˜๋Š” ์ž…์ž ์ˆ˜
const STICKINESS = 5;     // ์‘์ง‘ ๊ฑฐ๋ฆฌ
const WALK_STEP = 2;      // ๋žœ๋ค ์›Œํฌ ํ•œ ๋ฒˆ์— ์ด๋™ํ•˜๋Š” ๊ฑฐ๋ฆฌ 
const MAX_WALK_COUNT = 5; // ์ž…์ž๋‹น ํ”„๋ ˆ์ž„๋‹น ๋žœ๋ค ์›Œํฌ ํšŸ์ˆ˜

let particles = [];
let stuck = [];
let maxRadius = 0;

function setup() {
  createCanvas(800, 400);
  background(15);
  colorMode(RGB, 255);

  // ์ดˆ๊ธฐ ์‹œ๋“œ ์ƒ์„ฑ
  stuck.push(createVector(width / 2, height / 2));
  maxRadius = 1;
}

function draw() {
  background(15, 15, 15, 20);

  // 1. ์ž…์ž ์ƒ์„ฑ: ํŒจํ„ด ์™ธ๊ณฝ์—์„œ ์ƒ์„ฑ
  if (particles.length < MAX_PARTICLES) {
    let launchRadius = maxRadius + 50;
    let angle = random(TWO_PI);
    let x = width / 2 + cos(angle) * launchRadius;
    let y = height / 2 + sin(angle) * launchRadius;
    particles.push(createVector(x, y));
  }

  // 2. ์ž…์ž ์ด๋™ ๋ฐ ์‘์ง‘ ํ™•์ธ
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];

    // ๐ŸŒŸ ๊ฐ€์†ํ™” 1: ์ž…์ž๋‹น ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ๋žœ๋ค ์›Œํฌ ์ˆ˜ํ–‰ (์†๋„ ์ฆํญ)
    for (let k = 0; k < MAX_WALK_COUNT; k++) {
      p.x += random(-WALK_STEP, WALK_STEP);
      p.y += random(-WALK_STEP, WALK_STEP);
    }

    let closestDistSq = Infinity;

    // ๐ŸŒŸ ๊ฐ€์†ํ™” 2: ํŒจํ„ด ์™ธ๊ณฝ์— ์ ‘๊ทผํ–ˆ์„ ๋•Œ๋งŒ ์ถฉ๋Œ ํ™•์ธ
    let distFromCenter = dist(width / 2, height / 2, p.x, p.y);
    if (distFromCenter < maxRadius + STICKINESS * 2) {
      // ์ž…์ž๊ฐ€ ์‘์ง‘์ฒด ๊ทผ์ฒ˜์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ๋งŒ N*M ์ถฉ๋Œ ๊ฒ€์‚ฌ ์‹คํ–‰
      for (let s of stuck) {
        let dSq = (p.x - s.x) ** 2 + (p.y - s.y) ** 2;
        closestDistSq = min(closestDistSq, dSq);
      }

      // ์‘์ง‘ ์กฐ๊ฑด
      if (closestDistSq < STICKINESS * STICKINESS) {
        stuck.push(p);
        particles.splice(i, 1);

        // ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„ ์—…๋ฐ์ดํŠธ
        if (distFromCenter > maxRadius) {
          maxRadius = distFromCenter;
        }
        break; // ์‘์ง‘ ํ›„ ๋‹ค์Œ ์ž…์ž๋กœ
      }
    }

    // ๐ŸŒŸ ๊ฐ€์†ํ™” 3: ๋„ˆ๋ฌด ๋ฉ€๋ฆฌ ๋‚˜๊ฐ„ ์ž…์ž ์ œ๊ฑฐ (์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„ + 1000 ์ด์ƒ)
    // ํŒจํ„ด์ด ์ปค์งˆ ๋•Œ ํšจ์œจ์ ์œผ๋กœ ์ž…์ž๋ฅผ ์ œ๊ฑฐํ•˜๋„๋ก ๋ฒ”์œ„ ํ™•์žฅ
    if (distFromCenter > maxRadius + 1000) {
      particles.splice(i, 1);
    }
  }

  // 3. ๋“œ๋กœ์ž‰
  noStroke();
  fill(255);
  for (let s of stuck) circle(s.x, s.y, 3);

  fill(30, 190, 180);
  for (let p of particles) circle(p.x, p.y, 3);
}

3. ๊ฐ€์žฅ์ž๋ฆฌ์—์„œ ์ค‘์‹ฌ์œผ๋กœ

const MAX_PARTICLES = 1000;
let particles = [];
let stuck = [];

function setup() {
  createCanvas(800, 600);
  background(15);
  // ์ค‘์‹ฌ seed
  stuck.push(createVector(width / 2, height / 2));
}

function draw() {
  fill(15, 15, 15, 30);
  rect(0, 0, width, height);

  // ์ƒˆ๋กœ์šด ์ž…์ž ์ƒ์„ฑ (๋ฌด์ž‘์œ„ ๊ฐ€์žฅ์ž๋ฆฌ)
  if (frameCount % 1 === 0) {     // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ž…์ž ์ƒ์„ฑ
    let angle = random(TWO_PI);
    let r = width / 2 - 10;
    let x = width / 2 + cos(angle) * r;
    let y = height / 2 + sin(angle) * r;
    particles.push(createVector(x, y));
  }
  
  // MAX_PARTICLES ๊ฐœ์ˆ˜๋ณด๋‹ค ์ ์„ ๋•Œ๋งŒ ์ƒˆ๋กœ์šด ์ž…์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  if (particles.length < MAX_PARTICLES) {
    if (frameCount % 2 === 0) {
      // ... ๊ธฐ์กด ์ž…์ž ์ƒ์„ฑ ์ฝ”๋“œ ...
      let angle = random(TWO_PI);
      let r = width / 2 - 10;
      let x = width / 2 + cos(angle) * r;
      let y = height / 2 + sin(angle) * r;
      particles.push(createVector(x, y));
    }
  }
  
  // ์ž…์ž ์ด๋™
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.x += random(-3, 3);
    p.y += random(-3, 3);

    // ์ค‘์‹ฌ ์ชฝ์œผ๋กœ ์ด๋™
    let center = createVector(width / 2, height / 2);
    let dir = p5.Vector.sub(center, p).setMag(0.5);
    p.add(dir);

    // ๋ถ™๊ธฐ ๊ฒ€์‚ฌ
    for (let s of stuck) {
      if (p.dist(s) < 3) {
        stuck.push(p);
        particles.splice(i, 1);
        break;
      }
    }

    // ํ™”๋ฉด ๋ฐ– ์ œ๊ฑฐ
    if (p.x < 0 || p.x > width || p.y < 0 || p.y > height) {
      particles.splice(i, 1);
    }
  }

  // ๊ทธ๋ฆฌ๊ธฐ
  noStroke();
  fill(255);
  for (let s of stuck) circle(s.x, s.y, 3);
  fill(30, 190, 180);
  for (let p of particles) circle(p.x, p.y, 2);
}

4. ์ƒ‰์ƒ ๊ธฐ๋ฐ˜ DLA (๋‚˜์ด์— ๋”ฐ๋ผ ์ƒ‰ ๋ณ€ํ™”)

let particles = [];
let stuck = [];
let colors = [];

function setup() {
  createCanvas(600, 600);
  background(0);
  colorMode(HSB);
  noStroke();
  // ์ค‘์‹ฌ seed
  stuck.push(createVector(width / 2, height / 2));
  colors.push(0); // generation 0
}

function draw() {
  // ์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ ์ž…์ž ์ถ”๊ฐ€
  if (frameCount % 1 === 0) {
    let angle = random(TWO_PI);
    let r = width / 2 - 5;
    let x = width / 2 + cos(angle) * r;
    let y = height / 2 + sin(angle) * r;
    particles.push(createVector(x, y));
  }

  // ์ž…์ž ์ด๋™ ๋ฐ ๋ถ™๊ธฐ ๊ฒ€์‚ฌ
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.x += random(-2, 2);
    p.y += random(-2, 2);

    // ํ™”๋ฉด ์ค‘์•™์œผ๋กœ ํ–ฅํ•˜๋Š” ์•ฝํ•œ ํž˜
    let center = createVector(width / 2, height / 2);
    let dir = p5.Vector.sub(center, p).setMag(0.2);
    p.add(dir);

    for (let j = 0; j < stuck.length; j++) {
      let s = stuck[j];
      if (p.dist(s) < 3) {
        stuck.push(p);
        colors.push(colors[j] + 1);
        particles.splice(i, 1);
        break;
      }
    }

    // ํ™”๋ฉด ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€๋ฉด ์ œ๊ฑฐ
    if (p.x < 0 || p.x > width || p.y < 0 || p.y > height) {
      particles.splice(i, 1);
    }
  }

  // ๋ถ™์€ ์ž…์ž ๊ทธ๋ฆฌ๊ธฐ
  for (let i = 0; i < stuck.length; i++) {
    let hue = map(colors[i], 0, 80, 0, 360) % 360;
    fill(hue, 255, 255);
    circle(stuck[i].x, stuck[i].y, 3);
  }

  // ๋ชจ์—ฌ๋“œ๋Š” ์ž…์ž
  fill(30, 30 ,30);
  for (let p of particles) {
    circle(p.x, p.y, 2);
  }

  // ์„ฑ์žฅ ์ œํ•œ (ํ™”๋ฉด์ด ๊ฐ€๋“ ์ฐผ์„ ๋•Œ ์ •์ง€)
  if (stuck.length > 2000) {
    noLoop();
    console.log("DLA growth complete!");
  }
}

5. ๋ฐฉํ–ฅ์„ฑ ์žˆ๋Š” DLA (์ค‘๋ ฅ & ๋ฐ”๋žŒ)


DLA๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํŒŒํ‹ฐํด์— ์ค‘๋ ฅ๊ณผ ๋ฐ”๋žŒ์˜ ์˜ํ–ฅ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋žœ๋ค์›Œํฌ๋ฅผ ํ•˜๋Š” ์™€์ค‘์—๋„ x, y ์ขŒํ‘œ๋ฅผ ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ฐฉํ–ฅ์„ฑ์ด ์žˆ๊ฒŒ ์ˆ˜์น˜๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ์›ํ•˜๋Š” ๋ฐฉํ–ฅ๊ณผ ์†๋„๋กœ ์›€์ง์ด๋ฉด์„œ ์‘์ง‘์„ ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋Š” ๋งˆ์น˜ ๋ˆˆ์ด ์Œ“์ด๋Š” ๋“ฏํ•œ ๋ชจ์Šต์„ ์—ฐ์ถœํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

const MAX_PARTICLES = 300;
const STICKINESS = 5;

let particles = [];
let stuck = [];

function setup() {
  createCanvas(800, 400);
  background(15);
  colorMode(RGB, 255);

  // ๋ฐ”๋‹ฅ๋ฉด ์ „์ฒด๋ฅผ ์‹œ๋“œ๋กœ ์„ค์ •
  let seedSpacing = 3; // ์‹œ๋“œ ๊ฐ„๊ฒฉ (๊ฐ„๊ฒฉ์ด ์ข์„์ˆ˜๋ก ์‘์ง‘์ด ๋น ๋ฅด๊ณ  ๋ถ€๋“œ๋Ÿฌ์›€)

  for (let x = 0; x <= width; x += seedSpacing) {
    // ์บ”๋ฒ„์Šค ์•„๋žซ๋ฉด(y = height - 1)์— ์‹œ๋“œ๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
    stuck.push(createVector(x, height - 1));
  }
}

function draw() {
  // ๋ฐฐ๊ฒฝ์„ ํˆฌ๋ช…ํ•˜๊ฒŒ ๋ฎ์–ด ์น ํ•˜์—ฌ ๊ถค์  ์ž”์ƒ ํšจ๊ณผ ์ƒ์„ฑ
  fill(15, 15, 15, 50);
  rect(0, 0, width, height);

  // ์ƒˆ๋กœ์šด ์ž…์ž ์ƒ์„ฑ (์œ„์ชฝ์—์„œ ๋–จ์–ด์ง)
  if (particles.length < MAX_PARTICLES) {
    if (frameCount % 1 === 0) {
      let x = random(width);
      let y = 0; // ์บ”๋ฒ„์Šค ์ƒ๋‹จ
      particles.push(createVector(x, y));
    }
  }

  // ์ž…์ž ์ด๋™ ๋ฐ ์‘์ง‘ (์Œ“์ž„) ๊ฒ€์‚ฌ
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];

    // ์ž…์ž ์ด๋™: ์ค‘๋ ฅ(Y)๊ณผ ๋ฐ”๋žŒ(X)
    p.x += random(-1.5, 1.5); // x์ถ•: ๋ฐ”๋žŒ
    p.y += random(3, 7); 	  // y์ถ•: ์ค‘๋ ฅ

    // ๋ถ™๊ธฐ(์‘์ง‘) ๊ฒ€์‚ฌ
    let isStuck = false;
    for (let s of stuck) {
      if (p.dist(s) < STICKINESS) {
        stuck.push(p);
        particles.splice(i, 1);
        isStuck = true;
        break;
      }
    }

    if (isStuck) continue; // ๋ถ™์—ˆ์œผ๋ฉด ๋‹ค์Œ ์ž…์ž๋กœ

    // ํ™”๋ฉด ๋ฐ– ์ œ๊ฑฐ (๋ฐ”๋‹ฅ์„ ์ง€๋‚˜์นœ ์ž…์ž ์ œ๊ฑฐ)
    if (p.y > height + 10) particles.splice(i, 1);
  }

  // ๊ทธ๋ฆฌ๊ธฐ
  noStroke();

  // ์‘์ง‘๋œ ์ž…์ž (์Œ“์ธ ๋ˆˆ/๋ฐ”๋‹ฅ)
  fill(255, 200);
  for (let s of stuck) circle(s.x, s.y, 10);

  // ์›€์ง์ด๋Š” ์ž…์ž (๋ˆˆ/ํŒŒํ‹ฐํด)
  fill(255, 200);
  for (let p of particles) circle(p.x, p.y, 10);
}

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

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