๐ŸŽ‡ Slime mode by Patt Vira

BamgasiJMยท2026๋…„ 1์›” 23์ผ

p5.js Art

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

๐Ÿ“ mold.js

class Mold {
  constructor() {
    // Mold ๊ฐ์ฒด์˜ ์ดˆ๊ธฐ ์œ„์น˜: ๋žœ๋คํ•œ ์บ”๋ฒ„์Šค ๋‚ด ์ขŒํ‘œ
    this.x = random(width);
    this.y = random(height);
    this.r = 0.5; // Mold์˜ ๋ฐ˜์ง€๋ฆ„

    // ์ด๋™ ๋ฐฉํ–ฅ: ๋žœ๋คํ•œ ๊ฐ๋„(0~359๋„)๋กœ ์ดˆ๊ธฐํ™”
    this.heading = random(360);
    this.vx = cos(this.heading); // x์ถ• ์ด๋™ ์†๋„ (cos: ๊ฐ๋„์˜ x ์„ฑ๋ถ„)
    this.vy = sin(this.heading); // y์ถ• ์ด๋™ ์†๋„ (sin: ๊ฐ๋„์˜ y ์„ฑ๋ถ„)
    this.rotAngle = 45;          // ํšŒ์ „ ๊ฐ๋„ (๋ฐฉํ–ฅ ์ „ํ™˜ ์‹œ ์‚ฌ์šฉ)
    this.stop = false;           // ์ด๋™ ์ •์ง€ ์—ฌ๋ถ€

    // ์„ผ์„œ ์œ„์น˜ ๊ณ„์‚ฐ์šฉ ๋ณ€์ˆ˜
    this.rSensorPos = createVector(0, 0); // ์˜ค๋ฅธ์ชฝ ์„ผ์„œ ์œ„์น˜
    this.lSensorPos = createVector(0, 0); // ์™ผ์ชฝ ์„ผ์„œ ์œ„์น˜
    this.fSensorPos = createVector(0, 0); // ์•ž์ชฝ ์„ผ์„œ ์œ„์น˜
    this.sensorAngle = 35;                // ์„ผ์„œ ๊ฐ๋„ (๊ธฐ์ค€ ๋ฐฉํ–ฅ์—์„œ ยฑ45๋„)
    this.sensorDist = 10;                 // ์„ผ์„œ ๊ฑฐ๋ฆฌ (Mold๋กœ๋ถ€ํ„ฐ 10ํ”ฝ์…€)
  }

  update() {
    // stop์ด true์ด๋ฉด ์ด๋™ ์ •์ง€
    if (this.stop) {
      this.vx = 0;
      this.vy = 0;
    } else {
      this.vx = cos(this.heading); // ํ˜„์žฌ ๋ฐฉํ–ฅ์˜ x ์„ฑ๋ถ„
      this.vy = sin(this.heading); // ํ˜„์žฌ ๋ฐฉํ–ฅ์˜ y ์„ฑ๋ถ„
    }

    // ์บ”๋ฒ„์Šค ๊ฒฝ๊ณ„๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด ๋ฐ˜๋Œ€ํŽธ์—์„œ ๋“ฑ์žฅ (% ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ)
    this.x = (this.x + this.vx + width) % width;
    this.y = (this.y + this.vy + height) % height;

    // ์„ผ์„œ ์œ„์น˜ ๊ณ„์‚ฐ (ํ˜„์žฌ ๋ฐฉํ–ฅ์„ ๊ธฐ์ค€์œผ๋กœ ์™ผ์ชฝ, ์˜ค๋ฅธ์ชฝ, ์•ž์ชฝ ์„ผ์„œ ์œ„์น˜)
    this.getSensorPos(this.rSensorPos, this.heading + this.sensorAngle);
    this.getSensorPos(this.lSensorPos, this.heading - this.sensorAngle);
    this.getSensorPos(this.fSensorPos, this.heading);

    // ์„ผ์„œ ์œ„์น˜์˜ ํ”ฝ์…€ ์ƒ‰์ƒ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    let index, l, r, f;
    index =
      4 * (d * floor(this.rSensorPos.y)) * (d * width) +
      4 * (d * floor(this.rSensorPos.x));
    r = pixels[index]; // ์˜ค๋ฅธ์ชฝ ์„ผ์„œ ์œ„์น˜์˜ ํ”ฝ์…€ ์ƒ‰์ƒ ๊ฐ’ (๋นจ๊ฐ• ์„ฑ๋ถ„)

    index =
      4 * (d * floor(this.lSensorPos.y)) * (d * width) +
      4 * (d * floor(this.lSensorPos.x));
    l = pixels[index]; // ์™ผ์ชฝ ์„ผ์„œ ์œ„์น˜์˜ ํ”ฝ์…€ ์ƒ‰์ƒ ๊ฐ’ (๋นจ๊ฐ• ์„ฑ๋ถ„)

    index =
      4 * (d * floor(this.fSensorPos.y)) * (d * width) +
      4 * (d * floor(this.fSensorPos.x));
    f = pixels[index]; // ์•ž์ชฝ ์„ผ์„œ ์œ„์น˜์˜ ํ”ฝ์…€ ์ƒ‰์ƒ ๊ฐ’ (๋นจ๊ฐ• ์„ฑ๋ถ„)

    // ์„ผ์„œ ๊ฐ’์— ๋”ฐ๋ฅธ ๋ฐฉํ–ฅ ์ „ํ™˜ ๋กœ์ง
    if (f > l && f > r) {
      this.heading += 0; // ์•ž์ชฝ ์„ผ์„œ ๊ฐ’์ด ๊ฐ€์žฅ ํฌ๋ฉด ๋ฐฉํ–ฅ ์œ ์ง€
    } else if (f < l && f < r) {
      // ์•ž์ชฝ ์„ผ์„œ ๊ฐ’์ด ๊ฐ€์žฅ ์ž‘์œผ๋ฉด ๋žœ๋คํ•˜๊ฒŒ ์™ผ์ชฝ ๋˜๋Š” ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํšŒ์ „
      if (random(1) < 0.5) {
        this.heading += this.rotAngle;
      } else {
        this.heading -= this.rotAngle;
      }
    } else if (l > r) {
      this.heading -= this.rotAngle; // ์™ผ์ชฝ ์„ผ์„œ ๊ฐ’์ด ๋” ํฌ๋ฉด ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํšŒ์ „
    } else if (r > l) {
      this.heading += this.rotAngle; // ์˜ค๋ฅธ์ชฝ ์„ผ์„œ ๊ฐ’์ด ๋” ํฌ๋ฉด ์™ผ์ชฝ์œผ๋กœ ํšŒ์ „
    }
  }

  // Mold๋ฅผ ํ•˜์–€ ํ…Œ๋‘๋ฆฌ ์—†๋Š” ์›์œผ๋กœ ๊ทธ๋ฆฌ๊ธฐ
  display() {
    noStroke(); 
    fill(255);  
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
  }

  // ์„ผ์„œ ์œ„์น˜ ๊ณ„์‚ฐ ํ•จ์ˆ˜
  getSensorPos(sensor, angle) {
    sensor.x = (this.x + this.sensorDist * cos(angle) + width) % width;
    sensor.y = (this.y + this.sensorDist * sin(angle) + height) % height;
  }
}

๐Ÿ“ script.js

// Patt Vira's Slime Mold Simulation
// https://www.youtube.com/watch?v=VyXxSNcgDtg

let molds = [];             // Mold ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด
let num = 4000;             // ์ƒ์„ฑํ•  Mold ๊ฐ์ฒด์˜ ๊ฐœ์ˆ˜
let d;                      // pixelDensity() ๊ฐ’์„ ์ €์žฅํ•  ๋ณ€์ˆ˜

function setup() {
  createCanvas(400, 400);   // 400x400 ํ”ฝ์…€ ์บ”๋ฒ„์Šค ์ƒ์„ฑ
  angleMode(DEGREES);       // ๊ฐ๋„๋ฅผ ๋„(degree)๋กœ ์„ค์ •
  d = pixelDensity();       // ๋””์Šคํ”Œ๋ ˆ์ด์˜ ํ”ฝ์…€ ๋ฐ€๋„๋ฅผ ์ €์žฅ

  // num ๊ฐœ์ˆ˜๋งŒํผ Mold ๊ฐ์ฒด ์ƒ์„ฑ
  for (let i = 0; i < num; i++) {
    molds[i] = new Mold();
  }
}

function draw() {
  background(0, 5);          // ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์€์ƒ‰ ๋ฐฐ๊ฒฝ (์•ŒํŒŒ๊ฐ’ 5)
  loadPixels();              // ํ”ฝ์…€ ๋ฐฐ์—ด์„ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ (pixel[] ๋ฐฐ์—ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

  // ๋ชจ๋“  Mold ๊ฐ์ฒด ์—…๋ฐ์ดํŠธ ๋ฐ ํ‘œ์‹œ
  for (let i = 0; i < num; i++) {
    if (key == "s") {
      molds[i].stop = true;  // "s" ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด Mold ๊ฐ์ฒด๊ฐ€ ๋ฉˆ์ถค
      updatePixels();        // ํ”ฝ์…€ ๋ฐฐ์—ด์„ ํ™”๋ฉด์— ์—…๋ฐ์ดํŠธ
      noLoop();              // draw() ๋ฃจํ”„ ์ค‘์ง€
    } else {
      molds[i].stop = false; // "s" ํ‚ค๋ฅผ ๋–ผ๋ฉด ๋‹ค์‹œ ์›€์ง์ž„
    }

    molds[i].update();       // Mold ๊ฐ์ฒด ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    molds[i].display();      // Mold ๊ฐ์ฒด ํ™”๋ฉด์— ํ‘œ์‹œ
  }
}

1. Mold ๊ฐ์ฒด

  • ๊ฐ Mold๋Š” ์บ”๋ฒ„์Šค ๋‚ด ๋žœ๋คํ•œ ์œ„์น˜์—์„œ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
  • heading์€ ์ด๋™ ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, vx์™€ vy๋Š” ํ•ด๋‹น ๋ฐฉํ–ฅ์˜ x, y ์„ฑ๋ถ„์ž…๋‹ˆ๋‹ค.
  • stop ๋ณ€์ˆ˜๋Š” "s" ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋ฉด true๊ฐ€ ๋˜์–ด ์ด๋™์„ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.

2. ์„ผ์„œ ์‹œ์Šคํ…œ

  • Mold๋Š” ์•ž์ชฝ, ์™ผ์ชฝ, ์˜ค๋ฅธ์ชฝ ์„ผ์„œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์„ผ์„œ๋Š” ํ˜„์žฌ ๋ฐฉํ–ฅ์„ ๊ธฐ์ค€์œผ๋กœ ยฑ45๋„ ์œ„์น˜์— ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„ผ์„œ๋Š” ์ฃผ๋ณ€ ํ”ฝ์…€์˜ ์ƒ‰์ƒ ๊ฐ’์„ ์ฝ์–ด, ์ฃผ๋ณ€ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์ด๋™ ๋ฐฉํ–ฅ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

3. ๋ฐฉํ–ฅ ์ „ํ™˜ ๋กœ์ง

  • ์•ž์ชฝ ์„ผ์„œ ๊ฐ’์ด ๊ฐ€์žฅ ํฌ๋ฉด ์ง์ง„ํ•ฉ๋‹ˆ๋‹ค.
  • ์•ž์ชฝ ์„ผ์„œ ๊ฐ’์ด ๊ฐ€์žฅ ์ž‘์œผ๋ฉด ๋žœ๋คํ•˜๊ฒŒ ์™ผ์ชฝ ๋˜๋Š” ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํšŒ์ „ํ•ฉ๋‹ˆ๋‹ค.
  • ์™ผ์ชฝ ์„ผ์„œ ๊ฐ’์ด ๋” ํฌ๋ฉด ์˜ค๋ฅธ์ชฝ์œผ๋กœ, ์˜ค๋ฅธ์ชฝ ์„ผ์„œ ๊ฐ’์ด ๋” ํฌ๋ฉด ์™ผ์ชฝ์œผ๋กœ ํšŒ์ „ํ•ฉ๋‹ˆ๋‹ค.

4. ์บ”๋ฒ„์Šค ๊ฒฝ๊ณ„ ์ฒ˜๋ฆฌ

  • Mold๊ฐ€ ์บ”๋ฒ„์Šค ๊ฒฝ๊ณ„๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด ๋ฐ˜๋Œ€ํŽธ์—์„œ ๋“ฑ์žฅํ•˜๋„๋ก % ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

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