๐ŸŽ‡ Collapsing Blackhole

BamgasiJMยท2025๋…„ 12์›” 14์ผ

p5.js Art

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

๐Ÿ“ p5.js

// ==========================================
// [์„ค์ • ์˜์—ญ] ๋น„์ฃผ์–ผ ๋ฐ ๋ฌผ๋ฆฌ ๊ด€๋ จ ์ฃผ์š” ์ „์—ญ ๋ณ€์ˆ˜
// ==========================================

// 1. ๊ธฐ๋ณธ ์„ค์ •
const POINT_COUNT = 6000;       // ํŒŒํ‹ฐํด ๊ฐœ์ˆ˜ (์„ฑ๋Šฅ์— ๋”ฐ๋ผ ์กฐ์ ˆ: 4000 ~ 10000)
const BG_ALPHA = 30;            // ๋ฐฐ๊ฒฝ์˜ ์ž”์ƒ ๋†๋„ (๋‚ฎ์„์ˆ˜๋ก ๊ธด ์ž”์ƒ, 0~255)

// 2. ํฌ๊ธฐ ๋ฐ ๋…ธ์ด์ฆˆ ์„ค์ •
let BASE_RADIUS_RATIO = 0.4;    // ํ™”๋ฉด ๋Œ€๋น„ ํŒŒํ‹ฐํด ๋ถ„ํฌ ๋ฐ˜์ง€๋ฆ„ ๋น„์œจ (0.1 ~ 0.5)
let NOISE_SCALE = 0.01;         // ๋…ธ์ด์ฆˆ์˜ ํ…์Šค์ฒ˜ ํฌ๊ธฐ (์ž‘์„์ˆ˜๋ก ๋ถ€๋“œ๋Ÿฌ์›€)
let NOISE_STRENGTH = 200;       // ๋…ธ์ด์ฆˆ๊ฐ€ ์œ„์น˜์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ๋ ฅ (ํ”ฝ์…€ ๋‹จ์œ„)
let TIME_SPEED = 0.003;         // ๋…ธ์ด์ฆˆ ๋ณ€ํ™” ์†๋„

// 3. ์›€์ง์ž„ ์„ค์ •
let ROTATION_SPEED_MIN = 0.006; // ์ตœ์†Œ ํšŒ์ „ ์†๋„
let ROTATION_SPEED_MAX = 0.012; // ์ตœ๋Œ€ ํšŒ์ „ ์†๋„
let EXPLOSION_FORCE_MIN = 20;   // ํญ๋ฐœ ์‹œ ์ตœ์†Œ ํž˜
let EXPLOSION_FORCE_MAX = 35;   // ํญ๋ฐœ ์‹œ ์ตœ๋Œ€ ํž˜
let DRAG = 0.91;                // ํญ๋ฐœ ํ›„ ๊ฐ์† ๋น„์œจ (1์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๋ฏธ๋„๋Ÿฌ์ง)

// 4. ์ƒ‰์ƒ ์„ค์ • (RGB)
// ์ค‘์‹ฌ๋ถ€/์ €์†์ผ ๋•Œ ์ƒ‰์ƒ (Deep Blue-Purple)
const COLOR_CORE = [60, 20, 220];
// ์™ธ๊ณฝ/๊ณ ์†์ผ ๋•Œ ์ƒ‰์ƒ (Bright Cyan-White)
const COLOR_OUTER = [100, 255, 200];

// ==========================================

let points = [];
let state = "normal"; // 'normal' | 'explode'
let resetFrame = 0;
let cCore, cOuter;

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(RGB, 255);
  strokeWeight(2);

  // ์ƒ‰์ƒ ๊ฐ์ฒด ์ƒ์„ฑ
  cCore = color(...COLOR_CORE);
  cOuter = color(...COLOR_OUTER);

  initParticles();
}

function draw() {
  // ๋ฐฐ๊ฒฝ ์ž”์ƒ ํšจ๊ณผ
  background(10, 10, 15, BG_ALPHA);

  translate(width / 2, height / 2);

  // ํญ๋ฐœ ์ƒํƒœ๊ฐ€ ์˜ค๋ž˜ ์ง€์†๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ณต๊ท€
  if (state === "explode" && frameCount - resetFrame > 40) {
    state = "normal";
  }

  // ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ ๋ฐ ๊ทธ๋ฆฌ๊ธฐ
  for (let i = 0; i < points.length; i++) {
    points[i].update();
    points[i].display();
  }
}

// ํŒŒํ‹ฐํด ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜
function initParticles() {
  points = [];
  let maxR = min(width, height) * BASE_RADIUS_RATIO;

  for (let i = 0; i < POINT_COUNT; i++) {
    // 1. ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ • (๋„๋„› ๋ชจ์–‘ ๋ถ„ํฌ๋ฅผ ์œ„ํ•ด ๋žœ๋ค ์กฐ์ •)
    let r = random(maxR * 0.3, maxR);
    let angle = random(TWO_PI);

    // 2. ํŒŒํ‹ฐํด ๊ฐ์ฒด ์ƒ์„ฑ
    points.push(new Particle(r, angle));
  }
}

// ==========================================
// [ํด๋ž˜์Šค] Particle
// ==========================================
class Particle {
  constructor(r, angle) {
    this.baseR = r;     // ์›๋ž˜ ๊ถค๋„ ๋ฐ˜์ง€๋ฆ„
    this.r = r;         // ํ˜„์žฌ ๋ฐ˜์ง€๋ฆ„
    this.angle = angle; // ํ˜„์žฌ ๊ฐ๋„

    // ๊ฐœ๋ณ„ ์†์„ฑ ๋žœ๋คํ™”
    this.angSpeed = random(ROTATION_SPEED_MIN, ROTATION_SPEED_MAX);
    this.noiseOffset = random(1000);

    // ๋ฌผ๋ฆฌ ๋ณ€์ˆ˜
    this.burstSpeed = 0; // ํญ๋ฐœ ์†๋„
    this.drift = 0;      // ๋…ธ์ด์ฆˆ์— ์˜ํ•œ ํ”๋“ค๋ฆผ ๊ฐ’
  }

  update() {
    // 1. ์ƒํƒœ๋ณ„ ๋ฌผ๋ฆฌ ์—ฐ์‚ฐ
    if (state === "explode") {
      // ํญ๋ฐœ: ๋ฐ–์œผ๋กœ ํŠ•๊ฒจ๋‚˜๊ฐ + ๋งˆ์ฐฐ๋ ฅ(๊ฐ์†)
      this.burstSpeed *= DRAG;
      this.r += this.burstSpeed;
    } else {
      // ์ •์ƒ: ์ฒœ์ฒœํžˆ ์›๋ž˜ ๊ถค๋„๋กœ ๋ณต๊ท€ (ํƒ„์„ฑ)
      this.r = lerp(this.r, this.baseR, 0.009);

      // ์ง€์†์ ์ธ ํšŒ์ „
      this.angle += this.angSpeed;
    }

    // 2. ๋…ธ์ด์ฆˆ ๊ณ„์‚ฐ (์œ ๊ธฐ์ ์ธ ์›€์ง์ž„)
    // ์‹œ๊ฐ„๊ณผ ๊ณ ์œ  ์˜คํ”„์…‹์„ ์ด์šฉํ•ด ๋ถ€๋“œ๋Ÿฌ์šด ๋‚œ์ˆ˜ ์ƒ์„ฑ
    let n = noise(this.noiseOffset + frameCount * TIME_SPEED);

    // ๋…ธ์ด์ฆˆ ๊ฐ’์„ -1 ~ 1 ์‚ฌ์ด๋กœ ๋งคํ•‘ํ•˜์—ฌ ํ”๋“ค๋ฆผ(drift) ์ƒ์„ฑ
    this.drift = map(n, 0, 1, -1, 1) * NOISE_STRENGTH;
  }

  display() {
    // 3. ์ตœ์ข… ์œ„์น˜ ๊ณ„์‚ฐ (๊ทน์ขŒํ‘œ๊ณ„ -> ์ง๊ต์ขŒํ‘œ๊ณ„)
    // ๋ฐ˜์ง€๋ฆ„ = ํ˜„์žฌ ๋ฐ˜์ง€๋ฆ„ + ๋…ธ์ด์ฆˆ ํ”๋“ค๋ฆผ
    let finalR = this.r + this.drift;

    let x = cos(this.angle) * finalR;
    let y = sin(this.angle) * finalR;

    // 4. ์ƒ‰์ƒ ๊ณ„์‚ฐ
    // ํญ๋ฐœ ์†๋„๋‚˜ ๋…ธ์ด์ฆˆ ๊ฐ•๋„์— ๋”ฐ๋ผ ์ƒ‰์ƒ์„ ์„ž์Œ
    let energy = constrain(
      abs(this.burstSpeed) * 0.1 +
        map(abs(this.drift), 0, NOISE_STRENGTH, 0, 1),
      0,
      1
    );
    let c = lerpColor(cCore, cOuter, energy);

    stroke(c);
    point(x, y);
  }

  // ํญ๋ฐœ ํž˜ ์ ์šฉ
  explode() {
    this.burstSpeed += random(EXPLOSION_FORCE_MIN, EXPLOSION_FORCE_MAX);
  }
}

// ==========================================
// [์ด๋ฒคํŠธ] ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ฒ˜๋ฆฌ
// ==========================================

function keyPressed() {
  if (key === " ") {
    state = "explode";
    resetFrame = frameCount;

    // ๋ชจ๋“  ํŒŒํ‹ฐํด์— ํญ๋ฐœ ํž˜ ์ „๋‹ฌ
    for (let p of points) {
      p.explode();
    }
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // ํ™”๋ฉด ํฌ๊ธฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํŒŒํ‹ฐํด ์œ„์น˜ ์žฌ๊ณ„์‚ฐ
  initParticles();
}

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

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