
// ==========================================
// [์ค์ ์์ญ] ๋น์ฃผ์ผ ๋ฐ ๋ฌผ๋ฆฌ ๊ด๋ จ ์ฃผ์ ์ ์ญ ๋ณ์
// ==========================================
// 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();
}