๐Ÿ”ฎ :: Sun Flares

BamgasiJMยท2025๋…„ 10์›” 5์ผ

Nannou <Generative Art>

๋ชฉ๋ก ๋ณด๊ธฐ
39/55
post-thumbnail

๐Ÿ“ Rust Code

use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin};
use rand::Rng;

// === ์„ค์ • ์ƒ์ˆ˜ ===
const WIN_W: u32 = 800;
const WIN_H: u32 = 800;
const RADIUS: f32 = 200.0;

const NUM_FLARE: usize = 5000;
const CLUSTERS: usize = 12;
const TAU: f32 = std::f32::consts::TAU;
const CLUSTER_SPREAD: f32 = 0.08 * TAU;

const MIN_LEN: f32 = 30.0;
const MAX_LEN: f32 = 170.0;

const START_THICK: f32 = 0.8;
const END_THICK: f32 = 0.1;

const START_ALPHA: f32 = 0.8;
const END_ALPHA: f32 = 0.1;

const SEGMENTS: usize = 20;

// ๋…ธ์ด์ฆˆ ๊ด€๋ จ
const NOISE_SCALE: f64 = 2.0;
const NOISE_AMPLITUDE: f32 = 10.0;
const NOISE_SPEED: f64 = 0.2;

fn main() {
    nannou::app(model)
        .update(update)
        .view(view)
        .size(WIN_W, WIN_H)
        .run();
}

struct Model {
    flares: Vec<Flare>,
    perlin: Perlin,
    time: f64,
}

struct Flare {
    angle: f32,
    length: f32,
    seed: f64,
    cluster_id: usize,
}

fn model(app: &App) -> Model {
    let _window = app
        .new_window()
        .title("Clustered Curved Solar Flares (Animated)")
        .build()
        .unwrap();

    let mut rng = rand::thread_rng();
    let perlin = Perlin::new();

    let flares = generate_flares(&mut rng);

    Model {
        flares,
        perlin,
        time: 0.0,
    }
}

fn generate_flares<R: Rng>(rng: &mut R) -> Vec<Flare> {
    let mut flares = Vec::with_capacity(NUM_FLARE);
    let flares_per_cluster = NUM_FLARE / CLUSTERS;

    for c in 0..CLUSTERS {
        let cluster_angle = (c as f32) / (CLUSTERS as f32) * TAU;
        let cluster_seed = rng.gen_range(0.0..1000.0);
        
        for _ in 0..flares_per_cluster {
            let angle = cluster_angle + rng.gen_range(-CLUSTER_SPREAD..CLUSTER_SPREAD);
            let length = rng.gen_range(MIN_LEN..=MAX_LEN);
            let seed = cluster_seed + rng.gen_range(0.0..100.0);
            
            flares.push(Flare {
                angle,
                length,
                seed,
                cluster_id: c,
            });
        }
    }

    flares
}

fn update(_app: &App, model: &mut Model, update: Update) {
    model.time += update.since_last.as_secs_f64() * NOISE_SPEED;
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(hsla(0.0, 0.0, 0.02, 1.0));

    let center = pt2(0.0, 0.0);
    let white = hsla(0.0, 0.0, 0.9, 1.0);

    // ์ค‘์‹ฌ ์› (ํ•ญ์„ฑ)
    draw.ellipse()
        .no_fill()
        .stroke(white)
        .stroke_weight(1.0)
        .xy(center)
        .w_h(RADIUS * 2.0, RADIUS * 2.0);

    // ํ”Œ๋ ˆ์–ด๋“ค
    for flare in &model.flares {
        draw_flare(&draw, flare, center, &model.perlin, model.time);
    }

    draw.to_frame(app, &frame).unwrap();
}

fn draw_flare(draw: &Draw, flare: &Flare, center: Point2, perlin: &Perlin, time: f64) {
    let dir = vec2(flare.angle.cos(), flare.angle.sin());
    let perp = vec2(-dir.y, dir.x);
    let base_center = center + dir * RADIUS;

    for s in 0..SEGMENTS {
        let t0 = s as f32 / SEGMENTS as f32;
        let t1 = (s + 1) as f32 / SEGMENTS as f32;

        // Perlin ๊ธฐ๋ฐ˜ ํœ˜์–ด์ง + ์‹œ๊ฐ„ ์• ๋‹ˆ๋ฉ”์ด์…˜
        let n0 = perlin.get([t0 as f64 * NOISE_SCALE, flare.seed, time]) as f32;
        let n1 = perlin.get([t1 as f64 * NOISE_SCALE, flare.seed, time]) as f32;

        let offset0 = perp * (n0 * NOISE_AMPLITUDE);
        let offset1 = perp * (n1 * NOISE_AMPLITUDE);

        let p0 = base_center + dir * (flare.length * t0) + offset0;
        let p1 = base_center + dir * (flare.length * t1) + offset1;

        let w0 = lerp(START_THICK, END_THICK, t0);
        let w1 = lerp(START_THICK, END_THICK, t1);
        let a0 = lerp(START_ALPHA, END_ALPHA, t0);
        let a1 = lerp(START_ALPHA, END_ALPHA, t1);

        let alpha = (a0 + a1) * 0.5;
        let color = hsla(0.0, 0.0, 1.0, alpha);

        let left0 = p0 + perp * (w0 * 0.5);
        let right0 = p0 - perp * (w0 * 0.5);
        let left1 = p1 + perp * (w1 * 0.5);
        let right1 = p1 - perp * (w1 * 0.5);

        draw.tri()
            .points(left0, right0, left1)
            .color(color);
        draw.tri()
            .points(right0, right1, left1)
            .color(color);
    }
}

#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

๐Ÿ“ Rust Code + Comment

// Nannou์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๊ณผ ํผ๋ฆฐ ๋…ธ์ด์ฆˆ, ๋žœ๋ค ์ƒ์„ฑ๊ธฐ ์‚ฌ์šฉ์„ ์œ„ํ•œ ๋ชจ๋“ˆ ์ž„ํฌํŠธ
use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin}; // ํผ๋ฆฐ ๋…ธ์ด์ฆˆ ํ•จ์ˆ˜
use rand::Rng; // ๋žœ๋ค ์ˆซ์ž ์ƒ์„ฑ๊ธฐ ํŠธ๋ ˆ์ž‡

// === ์„ค์ • ์ƒ์ˆ˜: ์ฝ”๋“œ ์ „์ฒด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ณ ์ •๊ฐ’ ์ •์˜ ===

// ์ฐฝ ํฌ๊ธฐ (ํ”ฝ์…€)
const WIN_W: u32 = 800;
const WIN_H: u32 = 800;

// ์ค‘์‹ฌ ์›(ํ•ญ์„ฑ)์˜ ๋ฐ˜์ง€๋ฆ„
const RADIUS: f32 = 200.0;

// ์ƒ์„ฑํ•  ํ”Œ๋ ˆ์–ด(ํƒœ์–‘ ํ”Œ๋ ˆ์–ด) ๊ฐœ์ˆ˜
const NUM_FLARE: usize = 5000;

// ํ”Œ๋ ˆ์–ด๋ฅผ ๋ช‡ ๊ฐœ์˜ ํด๋Ÿฌ์Šคํ„ฐ(๋ฌด๋ฆฌ)๋กœ ๋‚˜๋ˆŒ์ง€
const CLUSTERS: usize = 12;

// ์›์ฃผ์œจ ร— 2 (360๋„) โ€” Nannou๋Š” TAU๋ฅผ ๊ธฐ๋ณธ ์ƒ์ˆ˜๋กœ ์ œ๊ณตํ•˜์ง€๋งŒ ๋ช…์‹œ์  ์ •์˜
const TAU: f32 = std::f32::consts::TAU;

// ๊ฐ ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ ํ”Œ๋ ˆ์–ด๊ฐ€ ํผ์ง€๋Š” ๊ฐ๋„ ๋ฒ”์œ„ (์ „์ฒด ์›์ฃผ์˜ 8%)
const CLUSTER_SPREAD: f32 = 0.08 * TAU;

// ํ”Œ๋ ˆ์–ด ๊ธธ์ด ๋ฒ”์œ„ (ํ”ฝ์…€)
const MIN_LEN: f32 = 30.0;
const MAX_LEN: f32 = 170.0;

// ํ”Œ๋ ˆ์–ด ๋‘๊ป˜: ์‹œ์ž‘(๋ฟŒ๋ฆฌ)๊ณผ ๋(๋๋ถ€๋ถ„)์˜ ์„  ๋‘๊ป˜
const START_THICK: f32 = 0.8; // ๋ฟŒ๋ฆฌ ๋ถ€๋ถ„์ด ๋‘๊บผ์›€
const END_THICK: f32 = 0.1;   // ๋ ๋ถ€๋ถ„์ด ์–‡์Œ

// ํ”Œ๋ ˆ์–ด ํˆฌ๋ช…๋„: ์‹œ์ž‘๊ณผ ๋์˜ ์•ŒํŒŒ๊ฐ’
const START_ALPHA: f32 = 0.8; // ๋ฟŒ๋ฆฌ ๋ถ€๋ถ„์ด ์„ ๋ช…
const END_ALPHA: f32 = 0.1;   // ๋ ๋ถ€๋ถ„์ด ํ๋ฆฟ

// ๊ฐ ํ”Œ๋ ˆ์–ด๋ฅผ ๋ช‡ ๊ฐœ์˜ ์„ธ๊ทธ๋จผํŠธ(๊ตฌ๊ฐ„)๋กœ ๋‚˜๋ˆŒ์ง€ โ†’ ๊ณก์„  ํ‘œํ˜„ ์ •๋ฐ€๋„
const SEGMENTS: usize = 20;

// ๋…ธ์ด์ฆˆ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ด€๋ จ ์ƒ์ˆ˜
const NOISE_SCALE: f64 = 2.0;       // ๋…ธ์ด์ฆˆ ์ž…๋ ฅ ์ขŒํ‘œ ์Šค์ผ€์ผ โ†’ ๊ฐ’์ด ํด์ˆ˜๋ก ๋” ์„ธ๋ฐ€ํ•œ ํŒจํ„ด
const NOISE_AMPLITUDE: f32 = 10.0;  // ๋…ธ์ด์ฆˆ ์ถœ๋ ฅ ์ง„ํญ โ†’ ํ”Œ๋ ˆ์–ด ํœ˜์–ด์ง ์ •๋„
const NOISE_SPEED: f64 = 0.2;       // ์‹œ๊ฐ„ ํ๋ฆ„ ์†๋„ โ†’ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„ ์กฐ์ ˆ

// ํ”„๋กœ๊ทธ๋žจ ์ง„์ž…์ 
fn main() {
    nannou::app(model)        // ์ดˆ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ ํ•จ์ˆ˜
        .update(update)       // ๋งค ํ”„๋ ˆ์ž„ ์ƒํƒœ ๊ฐฑ์‹ 
        .view(view)           // ๋ Œ๋”๋ง ํ•จ์ˆ˜
        .size(WIN_W, WIN_H)   // ์ฐฝ ํฌ๊ธฐ ์„ค์ • (์•ฑ ๋ ˆ๋ฒจ์—์„œ ์ง€์ •)
        .run();               // ์‹คํ–‰
}

// ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด ์ƒํƒœ ๊ตฌ์กฐ์ฒด
struct Model {
    flares: Vec<Flare>,       // ๋ชจ๋“  ํ”Œ๋ ˆ์–ด ๋ฐ์ดํ„ฐ
    perlin: Perlin,           // ํผ๋ฆฐ ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ ์ธ์Šคํ„ด์Šค (์žฌ์‚ฌ์šฉ)
    time: f64,                // ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„ (์ดˆ ๋‹จ์œ„)
}

// ๊ฐœ๋ณ„ ํ”Œ๋ ˆ์–ด์˜ ์ •์  ์†์„ฑ์„ ์ €์žฅํ•˜๋Š” ๊ตฌ์กฐ์ฒด
// โ†’ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ view์—์„œ ์‹ค์‹œ๊ฐ„ ๊ณ„์‚ฐ๋˜๋ฏ€๋กœ, ์—ฌ๊ธฐ์„  ์ดˆ๊ธฐ ์ƒํƒœ๋งŒ ์ €์žฅ
struct Flare {
    angle: f32,               // ์ค‘์‹ฌ์—์„œ ๋ป—์–ด๋‚˜๊ฐ€๋Š” ๊ฐ๋„ (๋ผ๋””์•ˆ)
    length: f32,              // ํ”Œ๋ ˆ์–ด ์ด ๊ธธ์ด
    seed: f64,                // ๋…ธ์ด์ฆˆ ํŒจํ„ด ๊ณ ์œ  ์‹๋ณ„์ž (๊ฐ™์€ ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ๋„ ๋‹ค์–‘์„ฑ ํ™•๋ณด)
    cluster_id: usize,        // ์†Œ์† ํด๋Ÿฌ์Šคํ„ฐ ID (๋””๋ฒ„๊น…/ํ™•์žฅ์šฉ)
}

// ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜: ์ฐฝ ์ƒ์„ฑ ๋ฐ ํ”Œ๋ ˆ์–ด ๋ฐ์ดํ„ฐ ์ค€๋น„
fn model(app: &App) -> Model {
    // ์ฐฝ ์ƒ์„ฑ ๋ฐ ์ œ๋ชฉ ์„ค์ •
    let _window = app
        .new_window()
        .title("Clustered Curved Solar Flares (Animated)")
        .build()
        .unwrap();

    // ๋žœ๋ค ์ƒ์„ฑ๊ธฐ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (์Šค๋ ˆ๋“œ ์•ˆ์ „)
    let mut rng = rand::thread_rng();
    // ํผ๋ฆฐ ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ ์ƒ์„ฑ
    let perlin = Perlin::new();

    // ํ”Œ๋ ˆ์–ด ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
    let flares = generate_flares(&mut rng);

    // ์ดˆ๊ธฐ Model ๋ฐ˜ํ™˜
    Model {
        flares,
        perlin,
        time: 0.0, // ์‹œ๊ฐ„ 0๋ถ€ํ„ฐ ์‹œ์ž‘
    }
}

// ํ”Œ๋ ˆ์–ด ๋ฐ์ดํ„ฐ๋ฅผ ํด๋Ÿฌ์Šคํ„ฐ ๋‹จ์œ„๋กœ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
// R: Rng โ€” ์ œ๋„ค๋ฆญ์œผ๋กœ ๋žœ๋ค ์ƒ์„ฑ๊ธฐ ํƒ€์ž…์„ ์ถ”์ƒํ™” (์œ ์—ฐ์„ฑ ํ–ฅ์ƒ)
fn generate_flares<R: Rng>(rng: &mut R) -> Vec<Flare> {
    // ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์ „ ํ• ๋‹น (์„ฑ๋Šฅ ์ตœ์ ํ™”)
    let mut flares = Vec::with_capacity(NUM_FLARE);
    // ํด๋Ÿฌ์Šคํ„ฐ๋‹น ๋ช‡ ๊ฐœ์˜ ํ”Œ๋ ˆ์–ด๋ฅผ ๋ฐฐ์ •ํ• ์ง€ ๊ณ„์‚ฐ
    let flares_per_cluster = NUM_FLARE / CLUSTERS;

    // ๊ฐ ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ
    for c in 0..CLUSTERS {
        // ํด๋Ÿฌ์Šคํ„ฐ ์ค‘์‹ฌ ๊ฐ๋„: ์›์„ CLUSTERS ๋“ฑ๋ถ„
        let cluster_angle = (c as f32) / (CLUSTERS as f32) * TAU;
        // ํด๋Ÿฌ์Šคํ„ฐ ์ „์ฒด์— ๊ณต์œ ๋˜๋Š” ๋žœ๋ค ์‹œ๋“œ (๋‚ด๋ถ€ ๋‹ค์–‘์„ฑ ํ™•๋ณด์šฉ)
        let cluster_seed = rng.gen_range(0.0..1000.0);
        
        // ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด ํ”Œ๋ ˆ์–ด ์ƒ์„ฑ
        for _ in 0..flares_per_cluster {
            // ์ค‘์‹ฌ ๊ฐ๋„ ์ฃผ๋ณ€์— ๋žœ๋ค ํผ์ง
            let angle = cluster_angle + rng.gen_range(-CLUSTER_SPREAD..CLUSTER_SPREAD);
            // ๊ธธ์ด ๋žœ๋ค ์„ค์ •
            let length = rng.gen_range(MIN_LEN..=MAX_LEN);
            // ๊ณ ์œ  ์‹œ๋“œ: ํด๋Ÿฌ์Šคํ„ฐ ์‹œ๋“œ + ์ถ”๊ฐ€ ๋žœ๋ค โ†’ ๊ฐ™์€ ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์—์„œ๋„ ๋‹ค๋ฅธ ๋…ธ์ด์ฆˆ ํŒจํ„ด
            let seed = cluster_seed + rng.gen_range(0.0..100.0);
            
            // Flare ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๋ฐ ์ถ”๊ฐ€
            flares.push(Flare {
                angle,
                length,
                seed,
                cluster_id: c,
            });
        }
    }

    flares
}

// ๋งค ํ”„๋ ˆ์ž„ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜
fn update(_app: &App, model: &mut Model, update: Update) {
    // ๊ฒฝ๊ณผ ์‹œ๊ฐ„(์ดˆ) ร— ์†๋„ ๊ณ„์ˆ˜ โ†’ ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„ ํ๋ฆ„
    model.time += update.since_last.as_secs_f64() * NOISE_SPEED;
}

// ๋งค ํ”„๋ ˆ์ž„ ๋ Œ๋”๋ง ํ•จ์ˆ˜
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();

    // ๋งค์šฐ ์–ด๋‘์šด ๋ฐฐ๊ฒฝ (์€์€ํ•œ ๊ฒ€์ •)
    draw.background().color(hsla(0.0, 0.0, 0.02, 1.0));

    // ํ™”๋ฉด ์ค‘์•™ ์ขŒํ‘œ
    let center = pt2(0.0, 0.0);
    // ํฐ์ƒ‰ (ํ•ญ์„ฑ๊ณผ ํ”Œ๋ ˆ์–ด ์ƒ‰์ƒ ๊ธฐ์ค€)
    let white = hsla(0.0, 0.0, 0.9, 1.0);

    // ์ค‘์‹ฌ ์› (ํ•ญ์„ฑ): ํ…Œ๋‘๋ฆฌ๋งŒ ์žˆ๊ณ  ์ฑ„์šฐ๊ธฐ ์—†์Œ
    draw.ellipse()
        .no_fill()                // ๋‚ด๋ถ€ ์ฑ„์šฐ๊ธฐ ์—†์Œ
        .stroke(white)            // ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ
        .stroke_weight(1.0)       // ํ…Œ๋‘๋ฆฌ ๋‘๊ป˜
        .xy(center)               // ์ค‘์‹ฌ ์œ„์น˜
        .w_h(RADIUS * 2.0, RADIUS * 2.0); // ์ง€๋ฆ„ = ๋ฐ˜์ง€๋ฆ„ ร— 2

    // ๋ชจ๋“  ํ”Œ๋ ˆ์–ด ๊ทธ๋ฆฌ๊ธฐ
    for flare in &model.flares {
        draw_flare(&draw, flare, center, &model.perlin, model.time);
    }

    // ํ”„๋ ˆ์ž„์— ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น ์ ์šฉ
    draw.to_frame(app, &frame).unwrap();
}

// ๊ฐœ๋ณ„ ํ”Œ๋ ˆ์–ด๋ฅผ ๊ณก์„  ํ˜•ํƒœ๋กœ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜
// - draw: ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น ๊ฐ์ฒด (๋ถˆ๋ณ€ ์ฐธ์กฐ)
// - flare: ๊ทธ๋ฆด ํ”Œ๋ ˆ์–ด ๋ฐ์ดํ„ฐ
// - center: ํ•ญ์„ฑ ์ค‘์‹ฌ ์ขŒํ‘œ
// - perlin: ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ (๋ถˆ๋ณ€ ์ฐธ์กฐ)
// - time: ํ˜„์žฌ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ๊ฐ„
fn draw_flare(draw: &Draw, flare: &Flare, center: Point2, perlin: &Perlin, time: f64) {
    // ํ”Œ๋ ˆ์–ด ๋ฐฉํ–ฅ ๋‹จ์œ„ ๋ฒกํ„ฐ (๊ฐ๋„ โ†’ ๋ฒกํ„ฐ ๋ณ€ํ™˜)
    let dir = vec2(flare.angle.cos(), flare.angle.sin());
    // ๋ฐฉํ–ฅ์— ์ˆ˜์ง์ธ ๋ฒกํ„ฐ (๋‘๊ป˜ ๊ณ„์‚ฐ์šฉ)
    let perp = vec2(-dir.y, dir.x);
    // ํ”Œ๋ ˆ์–ด ์‹œ์ž‘์ : ํ•ญ์„ฑ ํ‘œ๋ฉด (๋ฐ˜์ง€๋ฆ„๋งŒํผ ๋–จ์–ด์ง„ ์ง€์ )
    let base_center = center + dir * RADIUS;

    // ํ”Œ๋ ˆ์–ด๋ฅผ SEGMENTS ๊ฐœ์˜ ๊ตฌ๊ฐ„์œผ๋กœ ๋‚˜๋ˆ„์–ด ๊ณก์„  ํ‘œํ˜„
    for s in 0..SEGMENTS {
        // ํ˜„์žฌ ๊ตฌ๊ฐ„์˜ ์‹œ์ž‘(t0)๊ณผ ๋(t1) ๋น„์œจ (0.0 ~ 1.0)
        let t0 = s as f32 / SEGMENTS as f32;
        let t1 = (s + 1) as f32 / SEGMENTS as f32;

        // ํผ๋ฆฐ ๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜ ํœ˜์–ด์ง ๊ณ„์‚ฐ:
        // - x: ๊ตฌ๊ฐ„ ์œ„์น˜ (t0, t1) ร— ์Šค์ผ€์ผ
        // - y: ํ”Œ๋ ˆ์–ด ๊ณ ์œ  ์‹œ๋“œ โ†’ ๊ณ ์œ  ํŒจํ„ด ๋ณด์žฅ
        // - z: ์‹œ๊ฐ„ โ†’ ์• ๋‹ˆ๋ฉ”์ด์…˜
        let n0 = perlin.get([t0 as f64 * NOISE_SCALE, flare.seed, time]) as f32;
        let n1 = perlin.get([t1 as f64 * NOISE_SCALE, flare.seed, time]) as f32;

        // ์ˆ˜์ง ๋ฐฉํ–ฅ์œผ๋กœ ํœ˜์–ด์ง ์˜คํ”„์…‹ ๊ณ„์‚ฐ
        let offset0 = perp * (n0 * NOISE_AMPLITUDE);
        let offset1 = perp * (n1 * NOISE_AMPLITUDE);

        // ์‹ค์ œ 3D ๊ณก์„  ์ƒ์˜ ์  ๊ณ„์‚ฐ:
        // - ๊ธฐ๋ณธ ์ง์„  ์œ„์น˜ + ๋…ธ์ด์ฆˆ ์˜คํ”„์…‹
        let p0 = base_center + dir * (flare.length * t0) + offset0;
        let p1 = base_center + dir * (flare.length * t1) + offset1;

        // ๋‘๊ป˜์™€ ํˆฌ๋ช…๋„๋ฅผ ์„ ํ˜• ๋ณด๊ฐ„(lerp)์œผ๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ™”
        let w0 = lerp(START_THICK, END_THICK, t0);
        let w1 = lerp(START_THICK, END_THICK, t1);
        let a0 = lerp(START_ALPHA, END_ALPHA, t0);
        let a1 = lerp(START_ALPHA, END_ALPHA, t1);

        // ํ‰๊ท  ์•ŒํŒŒ๊ฐ’์œผ๋กœ ์ƒ‰์ƒ ๊ฒฐ์ • (๊ฐ„๋‹จํ•œ ๊ทผ์‚ฌ)
        let alpha = (a0 + a1) * 0.5;
        let color = hsla(0.0, 0.0, 1.0, alpha); // ํฐ์ƒ‰, ์•ŒํŒŒ๋งŒ ๋ณ€ํ™”

        // ์‚ฌ๋‹ค๋ฆฌ๊ผด์„ ๋‘ ๊ฐœ์˜ ์‚ผ๊ฐํ˜•์œผ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๊ทธ๋ฆฌ๊ธฐ:
        // - ์™ผ์ชฝ/์˜ค๋ฅธ์ชฝ ๊ฒฝ๊ณ„์  ๊ณ„์‚ฐ (๋‘๊ป˜ ๊ธฐ๋ฐ˜)
        let left0 = p0 + perp * (w0 * 0.5);
        let right0 = p0 - perp * (w0 * 0.5);
        let left1 = p1 + perp * (w1 * 0.5);
        let right1 = p1 - perp * (w1 * 0.5);

        // ์ฒซ ๋ฒˆ์งธ ์‚ผ๊ฐํ˜• (์™ผ์ชฝ ๋ฐ˜)
        draw.tri()
            .points(left0, right0, left1)
            .color(color);
        // ๋‘ ๋ฒˆ์งธ ์‚ผ๊ฐํ˜• (์˜ค๋ฅธ์ชฝ ๋ฐ˜)
        draw.tri()
            .points(right0, right1, left1)
            .color(color);
    }
}

// ์„ ํ˜• ๋ณด๊ฐ„(Linear Interpolation) ํ•จ์ˆ˜
// - a: ์‹œ์ž‘๊ฐ’
// - b: ๋๊ฐ’
// - t: ๋ณด๊ฐ„ ๋น„์œจ (0.0=์‹œ์ž‘, 1.0=๋)
// ์˜ˆ: lerp(10.0, 20.0, 0.5) โ†’ 15.0
#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

๐Ÿ” #[inline] ์–ดํŠธ๋ฆฌ๋ทฐํŠธ

  • ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ "์ด ํ•จ์ˆ˜๋ฅผ ์ธ๋ผ์ธ ํ™•์žฅํ•˜๋ผ"๊ณ  ํžŒํŠธ๋ฅผ ์ฃผ๋Š” ์–ดํŠธ๋ฆฌ๋ทฐํŠธ(attribute).
  • ๋™์ž‘:
    • ์ผ๋ฐ˜ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์‹œ: ์Šคํƒ์— ์ธ์ž ์ €์žฅ โ†’ ํ•จ์ˆ˜๋กœ ์ ํ”„ โ†’ ๋ฐ˜ํ™˜ โ†’ ์Šคํƒ ๋ณต์› (์˜ค๋ฒ„ํ—ค๋“œ ์žˆ์Œ)
    • #[inline] ํ•จ์ˆ˜: ์ปดํŒŒ์ผ ์‹œ ํ•จ์ˆ˜ ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœ ์œ„์น˜์— ์ง์ ‘ ์‚ฝ์ž… โ†’ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์˜ค๋ฒ„ํ—ค๋“œ ์ œ๊ฑฐ
  • ์‚ฌ์šฉ ์ด์œ :
    • ๋งค์šฐ ์งง๊ณ  ์ž์ฃผ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜(์˜ˆ: lerp, min, max)์— ์ ํ•ฉ
    • ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ชฉ์  (ํŠนํžˆ ๋ฃจํ”„ ๋‚ด์—์„œ ์ˆ˜์ฒœ/์ˆ˜๋งŒ ๋ฒˆ ํ˜ธ์ถœ๋  ๊ฒฝ์šฐ)
  • ์ฃผ์˜:
    • ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์ด ํžŒํŠธ๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์Œ (ํ•จ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ํฌ๋ฉด ์ธ๋ผ์ธํ•˜์ง€ ์•Š์Œ)
    • #[inline(always)]์€ ๊ฐ•์ œ ์ธ๋ผ์ธ (์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Œ)
  • ์ด ์ฝ”๋“œ์—์„œ์˜ ์—ญํ• :
    • lerp๋Š” ํ”Œ๋ ˆ์–ด ํ•˜๋‚˜๋‹น 4๋ฒˆ, ์ „์ฒด 5000๊ฐœ ํ”Œ๋ ˆ์–ด ร— 20 ์„ธ๊ทธ๋จผํŠธ = 40๋งŒ ๋ฒˆ ์ด์ƒ ํ˜ธ์ถœ
    • ์ธ๋ผ์ธ์œผ๋กœ ์˜ค๋ฒ„ํ—ค๋“œ ์ œ๊ฑฐ โ†’ ๋ Œ๋”๋ง ์„ฑ๋Šฅ ํ–ฅ์ƒ

๐Ÿ’ก Rust ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋งŽ์€ ์ˆ˜ํ•™ ํ•จ์ˆ˜(f32::min, clamp ๋“ฑ)๋„ #[inline]์œผ๋กœ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

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