๐ŸŽ‡ Star Size Comparison

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

p5.js Art

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

๐Ÿ“ p5.js

// ๋ณ„ ๋ฐ์ดํ„ฐ (1/10์œผ๋กœ ์กฐ์ •๋œ ๋ฐ˜์ง€๋ฆ„ ๋ฐ ์˜จ๋„ ๊ธฐ๋ฐ˜ ์ƒ‰์ƒ)
let stars = [
  { name: "Sun (ํƒœ์–‘)", radiusSun: 1, temp: 5778, color: "#FFFF00" },
  { name: "Sirius A", radiusSun: 1.7, temp: 9940, color: "#B0E0E6" },
  { name: "Vega", radiusSun: 2.3, temp: 9600, color: "#B0E0E6" },
  { name: "Pollux", radiusSun: 8.8, temp: 4868, color: "#FFDEAD" },
  { name: "Arcturus", radiusSun: 25.7, temp: 4286, color: "#FFA07A" },
  { name: "Aldebaran", radiusSun: 44, temp: 3900, color: "#FF8C00" },
  { name: "Rigel A", radiusSun: 78, temp: 12100, color: "#4169E1" },
  { name: "Deneb", radiusSun: 100, temp: 8525, color: "#ADD8E6" },
  { name: "VV Cephei A", radiusSun: 120, temp: 3600, color: "#B22222" },
  { name: "VY Canis Majoris", radiusSun: 130, temp: 3490, color: "#A52A2A" },
  { name: "Betelgeuse", radiusSun: 140, temp: 3500, color: "#D2691E" },
  { name: "KY Cygni", radiusSun: 150, temp: 3500, color: "#8B4513" },
  { name: "AH Scorpii", radiusSun: 155, temp: 3600, color: "#800000" },
  { name: "V354 Cephei", radiusSun: 160, temp: 3500, color: "#A52A2A" },
  { name: "UY Scuti", radiusSun: 170, temp: 3365, color: "#FFA07A" },
  { name: "LGGS J0045+4147", radiusSun: 190, temp: 3500, color: "#FF4500" },
  { name: "RSGC1-F01", radiusSun: 195, temp: 3400, color: "#E9967A" },
  { name: "VX Sagittarii", radiusSun: 205, temp: 3300, color: "#DAA520" },
  { name: "Stephenson 2-18", radiusSun: 215, temp: 3200, color: "#FF4500" },
];

let CANVAS_WIDTH;
let CANVAS_HEIGHT;

// ์คŒ ๊ด€๋ จ ์ƒ์ˆ˜ ๋ฐ ๋ณ€์ˆ˜
const MIN_SCALE = 0.1;
const MAX_SCALE = 50.0;
let scaleFactor = MIN_SCALE;

// ์บ์‹ฑ์„ ์œ„ํ•œ ๋ณ€์ˆ˜ (๋ฉ”๋ชจ๋ฆฌ ์žฌํ• ๋‹น ๋ฐฉ์ง€)
let maxDim;

function setup() {
  CANVAS_WIDTH = windowWidth;
  CANVAS_HEIGHT = windowHeight;
  createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);

  // [์ตœ์ ํ™” 1] ์ •๋ ฌ์„ setup์œผ๋กœ ์ด๋™
  // ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ•œ ๋ฒˆ๋งŒ ์ •๋ ฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (ํฐ ๋ณ„ -> ์ž‘์€ ๋ณ„ ์ˆœ์„œ)
  stars.sort((a, b) => b.radiusSun - a.radiusSun);

  // ํ…์ŠคํŠธ ์ •๋ ฌ ์„ค์ • (๋ฃจํ”„ ๋ฐ–์—์„œ ํ•œ ๋ฒˆ๋งŒ ์„ค์ •ํ•ด๋„ ๋˜๋Š” ๊ฒฝ์šฐ)
  textAlign(CENTER, BOTTOM);
  noStroke();
}

function draw() {
  background(0);

  // [์ตœ์ ํ™” 2] ์ขŒํ‘œ๊ณ„ ์ด๋™
  // ๋งค๋ฒˆ x + centerX๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋Œ€์‹  ์บ”๋ฒ„์Šค ์›์ ์„ ์ค‘์•™์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  translate(width / 2, height / 2);

  // ์„ฑ๋Šฅ์„ ์œ„ํ•ด ๋ฃจํ”„ ๋ฐ–์—์„œ max ๊ณ„์‚ฐ
  maxDim = (width > height ? width : height) * 4;

  // ์—ญ๋ฐฉํ–ฅ for๋ฌธ ์‚ฌ์šฉ (์„ ํƒ ์‚ฌํ•ญ์ด๋‚˜, JS ์—”์ง„์— ๋”ฐ๋ผ ๋ฏธ์„ธํ•˜๊ฒŒ ๋น ๋ฅผ ์ˆ˜ ์žˆ์Œ)
  // ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด ์ผ๋ฐ˜ for๋ฌธ์„ ์‚ฌ์šฉํ•˜๋˜, stars.length๋ฅผ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  // ํ•˜์ง€๋งŒ stars๋Š” const์— ๊ฐ€๊นŒ์šฐ๋ฏ€๋กœ ์ผ๋ฐ˜ ๋ฃจํ”„๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  for (let i = 0; i < stars.length; i++) {
    // ์ง์ ‘ ์ ‘๊ทผ์ด ํ•จ์ˆ˜ ํ˜ธ์ถœ๋ณด๋‹ค ๋น ๋ฆ…๋‹ˆ๋‹ค.
    const star = stars[i];
    const diameter = star.radiusSun * scaleFactor * 2;

    // [์ตœ์ ํ™” 3] ๊ทธ๋ฆฌ๊ธฐ ์ œํ•œ (Culling)
    // 1. ํ™”๋ฉด ์ „์ฒด๋ฅผ ๋ฎ์„ ์ •๋„๋กœ ๋„ˆ๋ฌด ์ปค์„œ ๋ฐฐ๊ฒฝ์ด ๋œ ๊ฒฝ์šฐ (๊ณผ๋„ํ•œ Fill Rate ๋ฐฉ์ง€)
    //    ๋‹จ, ๊ฐ€์žฅ ํฐ ๋ณ„์ด ๋ฐฐ๊ฒฝ์ƒ‰ ์—ญํ• ์„ ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ด ์กฐ๊ฑด์€ ์‹ ์ค‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    //    Jai๋‹˜์˜ ์›๋ž˜ ์˜๋„๋Œ€๋กœ 4๋ฐฐ ์ด์ƒ ํฌ๋ฉด ๊ทธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    if (diameter > maxDim) continue;

    // 2. ๋„ˆ๋ฌด ์ž‘์•„์„œ 1ํ”ฝ์…€๋„ ์•ˆ ๋˜๋Š” ๊ฒฝ์šฐ ๊ทธ๋ฆฌ์ง€ ์•Š์Œ (GPU ํ˜ธ์ถœ ์ ˆ์•ฝ)
    if (diameter < 0.5) continue;

    fill(star.color);
    circle(0, 0, diameter); // translate๋ฅผ ํ–ˆ์œผ๋ฏ€๋กœ (0,0)์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

    // ํ…์ŠคํŠธ ๋ Œ๋”๋ง (๋น„์šฉ์ด ๋น„์‹ผ ์ž‘์—…์ด๋ฏ€๋กœ ์กฐ๊ฑด๋ถ€ ์‹คํ–‰)
    if (diameter > 10) {
      fill(255);
      // ํ…์ŠคํŠธ ํฌ๊ธฐ ์„ค์ • ๋“ฑ์€ ์ƒํƒœ ๋ณ€๊ฒฝ ๋น„์šฉ์ด ๋“ค์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ํ•„์š”ํ•˜๋ฏ€๋กœ ์œ ์ง€
      // textSize(14)๋ฅผ ๋ฃจํ”„ ๋ฐ–์œผ๋กœ ๋บ„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ข‹๊ฒ ์ง€๋งŒ, ์•„๋ž˜ ๋ถ„๊ธฐ(Sun) ๋•Œ๋ฌธ์— ์œ ์ง€
      textSize(14);
      // ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ์‚ฌ์šฉ
      text(`${star.name} (${star.radiusSun} Rโ˜‰)`, 0, -diameter / 2 - 5);
    } else if (star.name === "Sun (ํƒœ์–‘)" && diameter > 2) {
      // Sun์€ ์ž‘์•„๋„ ์กฐ๊ธˆ ๋” ์˜ค๋ž˜ ํ‘œ์‹œ (1px -> 2px๋กœ ๊ฐ€์‹œ์„ฑ ์กฐ์ •)
      fill(255);
      textSize(12);
      // textAlign์„ setup์—์„œ BOTTOM์œผ๋กœ ํ–ˆ์œผ๋ฏ€๋กœ CENTER๋กœ ์ผ์‹œ ๋ณ€๊ฒฝ ํ•„์š”
      textAlign(CENTER, CENTER);
      text("Sun", 0, 0);
      textAlign(CENTER, BOTTOM); // ๋‹ค์‹œ ์›๋ณต
    }
  }
}

function mouseWheel(event) {
  // [์ตœ์ ํ™” 4] ์คŒ ๋กœ์ง ๊ฐœ์„  (Speed & UX)
  // ๊ธฐ์กด์˜ ๋ง์…ˆ ๋ฐฉ์‹ ๋Œ€์‹  ๊ณฑ์…ˆ ๋ฐฉ์‹(Exponential scaling)์„ ์‚ฌ์šฉํ•˜๋ฉด
  // ์Šค์ผ€์ผ์ด ์ž‘์„ ๋• ์ •๋ฐ€ํ•˜๊ฒŒ, ํด ๋• ๋น ๋ฅด๊ฒŒ ์คŒ์ธ/์•„์›ƒ์ด ๋˜์–ด ์—ฐ์‚ฐ๊ณผ ๋ฐ˜์‘์„ฑ์ด ์ข‹์•„์ง‘๋‹ˆ๋‹ค.

  let zoomSensitivity = 0.05; // ๋ฏผ๊ฐ๋„ ์กฐ์ ˆ

  if (event.deltaY > 0) {
    scaleFactor *= 1 - zoomSensitivity; // ์คŒ ์•„์›ƒ
  } else {
    scaleFactor *= 1 + zoomSensitivity; // ์คŒ ์ธ
  }

  scaleFactor = constrain(scaleFactor, MIN_SCALE, MAX_SCALE);

  // HTML ์—…๋ฐ์ดํŠธ (DOM ์กฐ์ž‘์€ p5 ๋ฃจํ”„์™€ ๋…๋ฆฝ์ ์ด๋ฏ€๋กœ ์œ ์ง€)
  const scaleDisplay = document.getElementById("scale-display");
  if (scaleDisplay) {
    // toFixed ์—ฐ์‚ฐ์€ ๊ฐ€๋ณ์ง€๋งŒ ํ•„์š”ํ•  ๋•Œ๋งŒ ํ˜ธ์ถœ
    scaleDisplay.textContent = scaleFactor.toFixed(6);
  }

  // ๊ธฐ๋ณธ ์Šคํฌ๋กค ๋™์ž‘ ๋ฐฉ์ง€ (์„ ํƒ ์‚ฌํ•ญ)
  return false;
}

function windowResized() {
  CANVAS_WIDTH = windowWidth;
  CANVAS_HEIGHT = windowHeight;
  resizeCanvas(windowWidth, windowHeight);
}

์ƒ์„ธ ์ˆ˜์ • ๋‚ด์—ญ ๋ฐ ๊ธฐ์ˆ ์  ์„ค๋ช…

1. ๋ฐฐ์—ด ์ •๋ ฌ (stars.sort) ์œ„์น˜ ๋ณ€๊ฒฝ:

JavaScript์˜ sort๋Š” O(NlogโกN)O(N \log N)์˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ 20๊ฐœ ์ •๋„๋กœ ์ ๋”๋ผ๋„, ์ด๋ฅผ 60FPS๋งˆ๋‹ค ๋งค๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์€ "๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น(Allocation)"๊ณผ "๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜(GC)"์„ ์œ ๋ฐœํ•˜์—ฌ ๋ฏธ์„ธํ•œ ๋Š๊น€์˜ ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค. setup()์œผ๋กœ ์˜ฎ๊ฒจ์„œ ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ์‹œ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

2. translate(width/2, height/2) ์‚ฌ์šฉ:

๊ธฐ์กด์—๋Š” centerX, centerY ๋ณ€์ˆ˜๋ฅผ ๋งค ํ”„๋ ˆ์ž„ ํ• ๋‹นํ•˜๊ณ , ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น๋งˆ๋‹ค ๋”ํ•˜๊ธฐ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

p5.js(WebGL/Canvas)์˜ ํ–‰๋ ฌ ๋ณ€ํ™˜ ๊ธฐ๋Šฅ์ธ translate๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์ดํ›„ ๋ชจ๋“  ์ขŒํ‘œ๋ฅผ (0, 0) ๊ธฐ์ค€์œผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์–ด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•ด์ง€๊ณ  ๋‚ด๋ถ€์ ์œผ๋กœ GPU์— ์ตœ์ ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

3. mouseWheel ๋กœ์ง ๋ณ€๊ฒฝ (์„ ํ˜• -> ์ง€์ˆ˜ํ˜•):

๊ธฐ์กด ์ฝ”๋“œ: scaleFactor -= event.deltaY * 0.0001

๋ฌธ์ œ์ : ์Šค์ผ€์ผ์ด 0.1์ผ ๋•Œ 0.0001์„ ๋นผ๋Š” ๊ฒƒ๊ณผ, ์Šค์ผ€์ผ์ด 100์ผ ๋•Œ 0.0001์„ ๋นผ๋Š” ๊ฒƒ์€ ์ฒด๊ฐ ์†๋„๊ฐ€ ์™„์ „ํžˆ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. (ํฐ ๋ณ„์„ ๋ณผ ๋•Œ ์คŒ์ด ๋ฉˆ์ถ˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์Œ)

ํ•ด๊ฒฐ: ํ˜„์žฌ ์Šค์ผ€์ผ์— ๋น„๋ก€ํ•˜์—ฌ ๊ณฑํ•˜๋Š” ๋ฐฉ์‹(*= 1.05)์„ ์‚ฌ์šฉํ•˜์—ฌ, ์–ด๋А ๊ตฌ๊ฐ„์—์„œ๋“  ๋ถ€๋“œ๋Ÿฌ์šด ์คŒ ์†๋„๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” UX๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์€ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์œผ๋ฏ€๋กœ ์„ฑ๋Šฅ์—๋„ ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

4. ํ…์ŠคํŠธ ๋ Œ๋”๋ง ์ƒํƒœ ๊ด€๋ฆฌ:

textAlign์ด๋‚˜ noStroke ๊ฐ™์€ ์ƒํƒœ ๋ณ€๊ฒฝ ํ•จ์ˆ˜๋Š” ์บ”๋ฒ„์Šค ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฏ€๋กœ ๋น„์šฉ์ด ๋“ญ๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋ฌธ ๋‚ด์—์„œ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์ตœ์†Œํ™”ํ•˜๊ณ , ๊ฐ€๋Šฅํ•œ setup์ด๋‚˜ ๋ฃจํ”„ ๋ฐ–์—์„œ ๊ณตํ†ต ์†์„ฑ์„ ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

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

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