
// ๋ณ ๋ฐ์ดํฐ (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๋ ์ ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ง๋๋ค. ๋ฐ์ดํฐ๊ฐ 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์ด๋ ๋ฃจํ ๋ฐ์์ ๊ณตํต ์์ฑ์ ์ ์ํ์ต๋๋ค.