๐ŸŽ‡ Slime Mold Eclipse

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

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
33/65
post-thumbnail

๐Ÿ“ p5.js

// Slime Mold Simulation
let xMotion = 0;
let yMotion = 0;

const agents = [];
let trailMap;

const numX = 500;
const numY = 8;

function setup() {
  createCanvas(800, 800);

  colorMode(HSB, 360, 255, 255);
  background(0);
  noiseDetail(7, 0.7);

  // Use Float32Array for memory efficiency
  trailMap = new Float32Array(width * height).fill(0);

  // Initialize Agents
  for (let nx = 0; nx < numX; nx++) {
    let nmx = nx / numX;
    for (let ny = 0; ny < numY; ny++) {
      let nmy = ny / numY;

      const noiseVal = noise(nmx * nmy);

      const startX = width / 2 + sin(nmx * PI * 2) * (width / 3.5);
      const startY = height / 2 + cos(nmx * PI * 2) * (height / 3.5);
      const angle = nmx * PI * 2;

      agents.push(new Agent(startX, startY, angle, noiseVal));
    }
  }

  ellipseMode(CENTER);
  rectMode(CENTER);
  noStroke();
}

function draw() {
  // 1. Process Agents
  for (let agent of agents) {
    agent.sense(trailMap);
    agent.move();
    agent.deposit(trailMap); // Deposit logic usually happens after move
    agent.display();
  }

  // 2. Decay Trail
  // TypedArray optimized loop
  for (let i = 0; i < trailMap.length; i++) {
    trailMap[i] -= 0.1;
    if (trailMap[i] < 0) trailMap[i] = 0;
  }

  // 3. Global Motion
  xMotion += 0.05;
  yMotion += 0.0002;
}

// --- Agent Class (Strict Original Math) ---
class Agent {
  constructor(x, y, angle, noiseVal) {
    this.x = x; // Using simple x, y instead of p5.Vector for raw control
    this.y = y;
    this.angle = angle;
    this.vel = 1;
    this.depositVal = 1;
    this.noiseOffset = noiseVal;

    this.sensorOffset = 5;
    this.sensorAngle = 45; // degrees
  }

  sense(mapData) {
    const sensorRad = radians(this.sensorAngle);

    // round(sin(...))์„ ๋จผ์ € ์ˆ˜ํ–‰ํ•˜์—ฌ ์˜คํ”„์…‹์„ ์ •์ˆ˜๋กœ ๋งŒ๋“  ๋’ค ์œ„์น˜์— ๋”ํ•จ
    const getSensorIndex = (angleOffset) => {
      const angle = this.angle + angleOffset;

      // 1. Calculate Integer Offset first (Crucial for symmetry)
      const offX = Math.round(Math.sin(angle) * this.sensorOffset);
      const offY = Math.round(Math.cos(angle) * this.sensorOffset);

      // 2. Add to position and truncate using parseInt
      let sx = parseInt(this.x + offX);
      let sy = parseInt(this.y + offY);

      // 3. Boundary Wrap (Manual implementation to match original)
      if (sx < 0) sx += width;
      if (sy < 0) sy += height;
      sx %= width;
      sy %= height;

      return sx + sy * width;
    };

    const idxL = getSensorIndex(-sensorRad); // Left
    const idxF = getSensorIndex(0); // Forward
    const idxR = getSensorIndex(sensorRad); // Right

    const sL = mapData[idxL];
    const sF = mapData[idxF];
    const sR = mapData[idxR];

    // Behavior Logic
    if (sF < sL && sF < sR) {
      // Stay same (Random turn was commented out in original)
    } else if (sL < sR) {
      this.angle -= PI / 8; // Turn Left
    } else if (sR < sL) {
      this.angle += PI / 8; // Turn Right
    }
  }

  move() {
    this.x += this.vel * Math.sin(this.angle);
    this.y += this.vel * Math.cos(this.angle);
  }

  deposit(mapData) {
    // Boundary check (Original used 'continue', effectively skipping)
    if (this.x < 0 || this.x > width || this.y < 0 || this.y > height) return;

    // [์ค‘์š”] Diffusion ์œ„์น˜ ๊ณ„์‚ฐ ์‹œ parseInt ์‚ฌ์šฉ
    const ix = parseInt(this.x);
    const iy = parseInt(this.y);
    const index = ix + iy * width;

    // Diffusion (3x3 Blur)
    const b = mapData[index] / 2;

    // Neighbor indices with manual wrapping
    // ์›๋ณธ์˜ (lx - 1), (lx + 1) ๋กœ์ง์„ ๊ทธ๋Œ€๋กœ ๊ตฌํ˜„
    const prevX = ix - 1 < 0 ? width - 1 : ix - 1;
    const nextX = (ix + 1) % width;
    const prevY = iy - 1 < 0 ? height - 1 : iy - 1;
    const nextY = (iy + 1) % height;

    mapData[prevX + iy * width] = b;
    mapData[nextX + iy * width] = b;
    mapData[prevX + prevY * width] = b;
    mapData[prevX + nextY * width] = b;
    mapData[nextX + prevY * width] = b;
    mapData[nextX + nextY * width] = b;
    mapData[ix + prevY * width] = b;
    mapData[ix + nextY * width] = b;

    // Deposit current
    mapData[index] = this.depositVal;
  }

  display() {
    if (this.x < 0 || this.x > width || this.y < 0 || this.y > height) return;

    const ll = 400;
    // Magic visual formula strictly preserved
    // noise(this.noiseOffset...) -> ensures agents in the same ring flicker together
    const t =
      noise(this.noiseOffset + xMotion * 4) *
      2 *
      pow(
        1 - min(ll, frameCount) / ll,
        5.5 * (0.5 + (1 - this.y / height) / 2)
      );

    const sph = 1 - abs((0.5 - this.x / width) * 2);
    const colVal = 224 + noise(this.noiseOffset + xMotion * 2) * 32 * sph;

    fill(0, 0, colVal, t / 3);
    // Draw 1x1 rectangle at float position (p5 handles sub-pixel rendering, but logic is integer based)
    rect(this.x, this.y, 1, 1);
  }
}

์ ๊ท ๋ฅ˜์˜ ์ž์—ฐ์ ์ธ ์›€์ง์ž„์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๋Š” ์ฝ”๋“œ

1. ๐Ÿงฌ ์—์ด์ „ํŠธ ์ƒ์„ฑ ๋ฐ ์ดˆ๊ธฐ ๋ฐฉ์‚ฌํ˜• ๋ฐฐ์น˜

์—์ด์ „ํŠธ๋“ค์ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹œ์ž‘ํ•˜๋Š” ์ดˆ๊ธฐ ์กฐ๊ฑด์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

  • ์—์ด์ „ํŠธ ์ˆ˜ ๋ฐ ๋ฐ€๋„:

    • numX (500)์™€ numY (8)๋ฅผ ๊ณฑํ•˜์—ฌ ์ด 4,0004,000๊ฐœ์˜ ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • 500500๊ฐœ์˜ ๊ฐ๋„(Angle)์— 88๊ฐœ์”ฉ ๊ฒน์ณ ๋ฐฐ์น˜ํ•˜์—ฌ ์ดˆ๊ธฐ ๋Œ€์นญ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ์ดˆ๊ธฐ ์œ„์น˜ (๋ฐฉ์‚ฌํ˜• ๋ฐฐ์น˜):

    • sin(nmx * 2\pi)์™€ cos(nmx * 2\pi) ์ˆ˜์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์บ”๋ฒ„์Šค ์ค‘์•™์„ ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ์—์ด์ „ํŠธ๋ฅผ ์™„๋ฒฝํ•œ ์›ํ˜•์œผ๋กœ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ดˆ๊ธฐ ๊ฐ๋„ (this.angle):

    • ์—์ด์ „ํŠธ์˜ ์ดˆ๊ธฐ ๋ฐฉํ–ฅ์„ ์›์˜ ๋‘˜๋ ˆ๋ฅผ ๋”ฐ๋ผ ๋ฐ”๊นฅ์ชฝ์„ ํ–ฅํ•˜๋„๋ก ์„ค์ •ํ•˜์—ฌ, ํŒจํ„ด์ด ์ค‘์•™์—์„œ ์™ธ๊ณฝ์œผ๋กœ ๋ป—์–ด๋‚˜๊ฐ€๋„๋ก ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค.

2. ๐Ÿงญ ์—์ด์ „ํŠธ ์ด๋™ ๋ฐ ์„ผ์„œ ์‹œ์Šคํ…œ

์—์ด์ „ํŠธ๊ฐ€ ํ™˜๊ฒฝ๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉฐ ๊ฒฝ๋กœ๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

  • ์ด๋™ ์†๋„:
    • this.vel์€ 1๋กœ ์„ค์ •๋˜์–ด ํ”„๋ ˆ์ž„๋‹น 1ํ”ฝ์…€์”ฉ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  • ์„ผ์„œ ์„ค์ •:
    • sensorOffset (3ํ”ฝ์…€)๊ณผ sensorAngle (45โˆ˜45^\circ)์ด ์ขŒ/์šฐ ์„ผ์„œ์˜ ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜์‚ฌ๊ฒฐ์ • (Behavior Logic):
    • ์„ธ ์„ผ์„œ(์ขŒ, ์ „๋ฐฉ, ์šฐ)์˜ ํ”์  ๋ฐ€๋„๋ฅผ ๋น„๊ตํ•˜์—ฌ ํ”์ ์ด ๋” ์ ์€ ๋ฐฉํ–ฅ์œผ๋กœ ์—์ด์ „ํŠธ๋ฅผ ํšŒ์ „์‹œํ‚ต๋‹ˆ๋‹ค. ์ด๋Š” ์—์ด์ „ํŠธ๊ฐ€ ํ˜ผ์žกํ•˜์ง€ ์•Š์€ ์ƒˆ๋กœ์šด ๊ฒฝ๋กœ๋ฅผ ํƒ์ƒ‰ํ•˜๋„๋ก ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค.
  • ์ •์ˆ˜ ์—ฐ์‚ฐ์˜ ์—ญํ•  (๋Œ€์นญ์„ฑ ์œ ์ง€):
    • ์„ผ์„œ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•  ๋•Œ Math.round()์™€ parseInt()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ขŒํ‘œ๋ฅผ ๊ฐ•์ œ๋กœ ์ •์ˆ˜ ๊ทธ๋ฆฌ๋“œ์— ์Šค๋ƒ…(Snap)์‹œํ‚ต๋‹ˆ๋‹ค. ์ด ๋ฏธ์„ธํ•œ ์ •์ˆ˜ํ™” ์ฒ˜๋ฆฌ๊ฐ€ ์—์ด์ „ํŠธ์˜ ์›€์ง์ž„์„ ์งˆ์„œ ์žˆ๊ฒŒ ์žก์•„์ฃผ์–ด ๋ฐฉ์‚ฌํ˜• ๋Œ€์นญ์ด ๋ฌด๋„ˆ์ง€์ง€ ์•Š๋„๋ก ์œ ์ง€ํ•˜๋Š” ๋ฐ ๊ฒฐ์ •์ ์ธ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

3. ๐Ÿงช ํ”์  ์ง€๋„์™€ ์ƒํ˜ธ์ž‘์šฉ (Trail Map and Interaction)

์—์ด์ „ํŠธ์˜ ์›€์ง์ž„์— ๋ฐ˜์‘ํ•˜๊ณ  ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ™”ํ•˜๋Š” ํ™˜๊ฒฝ์ธ ํ”์  ์ง€๋„(trailMap)์˜ ๊ด€๋ฆฌ์ž…๋‹ˆ๋‹ค.

  • ํ”์  ์ง€๋„ ์ €์žฅ:

    • trailMap์€ Float32Array๋กœ ๊ตฌํ˜„๋˜์–ด ์บ”๋ฒ„์Šค ํ”ฝ์…€๋ณ„ ํ”์  ๋ฐ€๋„๋ฅผ ์ €์žฅํ•˜๋ฉฐ, ์ผ๋ฐ˜ ๋ฐฐ์—ด๋ณด๋‹ค ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ด ๋†’์Šต๋‹ˆ๋‹ค.
  • ํ”์  ํ‡ด์  (depositVal):

    • ์—์ด์ „ํŠธ๊ฐ€ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ํ˜„์žฌ ์œ„์น˜์— depositVal (1)๋งŒํผ์˜ ์ƒˆ๋กœ์šด ํ”์ ์„ ๋‚จ๊น๋‹ˆ๋‹ค.
  • ํ™•์‚ฐ (Diffusion):

    • ํ”์ ์„ ๋‚จ๊ธฐ๋Š” ์ˆœ๊ฐ„, ์ฃผ๋ณ€ 8๊ฐœ ์ด์›ƒ ํ”ฝ์…€์˜ ๋ฐ€๋„๋ฅผ ํ˜„์žฌ ์œ„์น˜ ๋ฐ€๋„์˜ ์ ˆ๋ฐ˜์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ํ”์ ์ด ํผ์ ธ๋‚˜๊ฐ€๋Š” ํšจ๊ณผ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ๊ฐ์‡  (Decay):

    • ๋ชจ๋“  ํ”ฝ์…€์˜ ํ”์  ๋ฐ€๋„๋ฅผ ํ”„๋ ˆ์ž„๋‹น โˆ’0.1-0.1์”ฉ ๊ฐ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด ํ”์ ์˜ ์ˆ˜๋ช…๊ณผ ํŒจํ„ด์˜ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. (๊ฐ’์ด ํด์ˆ˜๋ก ํ”์ ์ด ๋นจ๋ฆฌ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.)

4. ๐ŸŽจ ์‹œ๊ฐ์  ์š”์†Œ ๋ฐ ๋™์  ๋ณ€ํ™”

ํŒจํ„ด์˜ ๋ฏธ์ ์ธ ์š”์†Œ์™€ ์‹œ๊ฐ„ ๊ฒฝ๊ณผ์— ๋”ฐ๋ฅธ ์›€์ง์ž„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

  • ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋…ธ์ด์ฆˆ (xMotion):

    • xMotion์ด ๋งค ํ”„๋ ˆ์ž„ ์ฆ๊ฐ€ํ•˜๋ฉฐ noise() ํ•จ์ˆ˜์˜ ์‹œ๋“œ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์‹œ๊ฐ์  ์งˆ๊ฐ๊ณผ ํŒจํ„ด์ด ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ฏธ๋ฌ˜ํ•˜๊ฒŒ ๋™์ ์œผ๋กœ ์›€์ง์ด๋Š” ๋А๋‚Œ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ณ„ ๋…ธ์ด์ฆˆ ์˜คํ”„์…‹:

    • this.noiseOffset์€ ์—์ด์ „ํŠธ๊ฐ€ ๊ฐ€์ง„ ๊ณ ์œ ํ•œ ๋…ธ์ด์ฆˆ ์‹œ๋“œ๋กœ, ์ƒ‰์ƒ๊ณผ ๋ฐ๊ธฐ ๊ณ„์‚ฐ์— ์ž…๋ ฅ๋˜์–ด ๊ฐ™์€ ์œ„์น˜์˜ ์—์ด์ „ํŠธ๋“ค ์‚ฌ์ด์—๋„ ๋ฏธ์„ธํ•˜๊ฒŒ ๋‹ค๋ฅธ ๋ฐ˜์ง์ž„๊ณผ ๋ณ€ํ™”๋ฅผ ์ค๋‹ˆ๋‹ค.
  • ๋™์  ํŽ˜์ด๋“œ ํšจ๊ณผ (t):

    • ๋ณต์žกํ•œ pow() ์ˆ˜์‹์„ ํ†ตํ•ด ๊ณ„์‚ฐ๋˜๋Š” t ๊ฐ’์€ ์—์ด์ „ํŠธ๊ฐ€ ๋‚จ๊ธฐ๋Š” ํ”์ ์˜ ํˆฌ๋ช…๋„๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ํ”์ ์ด ํ๋ ค์ง€๋„๋ก ํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ”์ ์ด ๋” ์„ ๋ช…ํ•˜๊ฒŒ ๋ณด์ด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

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

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