๐Ÿ”ฎ :: Perlin Noise Flow 3

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

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

fn main() {
    nannou::app(model).update(update).run();
}

struct Particle {
    pos: Vec2, 
    prev_pos: Vec2,
    vel: Vec2,
    acc: Vec2,
}

impl Particle {
    fn new(x: f32, y: f32) -> Self {
        let pos = vec2(x, y);
        Particle {
            pos,
            prev_pos: pos,
            vel: Vec2::ZERO,
            acc: Vec2::ZERO,
        }
    }

    fn update(&mut self) {
        self.vel += self.acc;
        self.vel = self.vel.clamp_length_max(4.0);
        self.prev_pos = self.pos;
        self.pos += self.vel;
        self.acc *= 0.0; 
    }

    fn apply_force(&mut self, force: Vec2) {
        self.acc += force;
    }

    fn edges(&mut self, rect: &Rect) {
        if self.pos.x > rect.right() {
            self.pos.x = rect.left();
            self.prev_pos.x = self.pos.x;
        }
        if self.pos.x < rect.left() {
            self.pos.x = rect.right();
            self.prev_pos.x = self.pos.x;
        }
        if self.pos.y > rect.top() {
            self.pos.y = rect.bottom();
            self.prev_pos.y = self.pos.y;
        }
        if self.pos.y < rect.bottom() {
            self.pos.y = rect.top();
            self.prev_pos.y = self.pos.y;
        }
    }
}

struct Model {
    perlin: Perlin,
    particles: Vec<Particle>,
    time: f64, 
    noise_scale: f64,
}

fn model(app: &App) -> Model {
    let window_id = app.new_window()
        .size(1024, 1024)
        .title("Perlin Noise Flow Field")
        .view(view)
        .build()
        .unwrap();

    let win = app.window(window_id).unwrap().rect();
    let perlin = Perlin::new();
    let time = 0.0;
    let noise_scale = 0.005; 

    let particles = (0..500)
        .map(|_| {
            let x = random_range(win.left(), win.right());
            let y = random_range(win.bottom(), win.top());
            Particle::new(x, y)
        })
        .collect();

    Model {
        perlin,
        particles,
        time,
        noise_scale,
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let win = app.window_rect();

    for p in &mut model.particles {
        let noise_val = model.perlin.get([
            p.pos.x as f64 * model.noise_scale,
            p.pos.y as f64 * model.noise_scale,
            model.time,
        ]);

        let angle = map_range(noise_val, -1.0, 1.0, 0.0, 2.0 * PI as f64) as f32;

        let force = vec2(angle.cos(), angle.sin()) * 0.1;

        p.apply_force(force);
        p.update();
        p.edges(&win);
    }

    model.time += 0.005;
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();

    if app.elapsed_frames() == 1 {
        draw.background().color(BLACK);
    }
    
    draw.rect()
        .wh(app.window_rect().wh())
        .color(srgba(0.0, 0.0, 0.0, 0.05));

    for p in &model.particles {
        let noise_val_color = model.perlin.get([
            p.pos.x as f64 * model.noise_scale,
            p.pos.y as f64 * model.noise_scale,
            model.time + 100.0, // ์ƒ‰์ƒ ๋ณ€ํ™”์— ๋‹ค๋ฅธ ๋…ธ์ด์ฆˆ ์‹œ๋“œ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด ์ž„์˜์˜ ๊ฐ’์„ ๋”ํ•จ
        ]);

        let hue = map_range(noise_val_color, -1.0, 1.0, 0.5, 1.0);
        let color = hsla(hue as f32, 0.7, 0.6, 0.7);

        draw.line()
            .start(p.prev_pos)
            .end(p.pos)
            .weight(1.5)
            .color(color);
    }

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

๐Ÿ“ Rust Code + Comment

// nannou์˜ prelude(๊ฐ€์žฅ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ๋“ค์˜ ๋ชจ์Œ)๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// App, Frame, vec2, aabb, rect, PI, TAU, HSL, ๋“ฑ ํ•ต์‹ฌ ํƒ€์ž…๊ณผ ํ•จ์ˆ˜๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
use nannou::prelude::*;
// nannou์— ๋‚ด์žฅ๋œ noise ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ Perlin ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ์™€,
// ๋…ธ์ด์ฆˆ ๊ฐ’ ์ƒ์„ฑ์„ ์œ„ํ•œ `get` ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋Š” NoiseFn ํŠธ๋ ˆ์ž‡(trait)์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
use nannou::noise::{NoiseFn, Perlin};


// ======================================================================
// ==== 1. ์ „์—ญ ์ƒ์ˆ˜ ์„ค์ • (Global Constants)
// ======================================================================
// ์•„ํŠธ์›Œํฌ์˜ ํŠน์„ฑ์„ ๊ฒฐ์ •ํ•˜๋Š” ์ฃผ์š” ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์„ ์ƒ์ˆ˜๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
// ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฝ”๋“œ ์ƒ๋‹จ์—์„œ ์‰ฝ๊ฒŒ ๊ฐ’์„ ์กฐ์ •ํ•˜๊ณ  ์‹คํ—˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, '๋งˆ๋ฒ•์˜ ์ˆซ์ž'๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// `const`๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์— ๊ฐ’์ด ๊ฒฐ์ •๋˜๋Š” ๋ถˆ๋ณ€ ์ƒ์ˆ˜์ž…๋‹ˆ๋‹ค.

const NUM_PARTICLES: usize = 500;   // ํ™”๋ฉด์— ์ƒ์„ฑํ•  ํŒŒํ‹ฐํด์˜ ์ด ๊ฐœ์ˆ˜. `usize`๋Š” ์ปฌ๋ ‰์…˜์˜ ์ธ๋ฑ์‹ฑ์ด๋‚˜ ํฌ๊ธฐ๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๋ถ€ํ˜ธ ์—†๋Š” ์ •์ˆ˜ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
const NOISE_SCALE: f64 = 0.005;   // ๋…ธ์ด์ฆˆ ํ•„๋“œ์˜ ์Šค์ผ€์ผ. ๊ฐ’์ด ์ž‘์„์ˆ˜๋ก ๋…ธ์ด์ฆˆ ํŒจํ„ด์ด ํ™•๋Œ€๋˜์–ด(zoom-in) ๋ถ€๋“œ๋Ÿฌ์šด ํ๋ฆ„์„, ํด์ˆ˜๋ก ์ž๊ธ€์ž๊ธ€ํ•œ ํ๋ฆ„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
const MAX_SPEED: f32 = 4.0;       // ๊ฐ ํŒŒํ‹ฐํด์ด ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์†๋ ฅ. ์ด ๊ฐ’์„ ํ†ตํ•ด ํŒŒํ‹ฐํด์ด ๋„ˆ๋ฌด ๋น ๋ฅด๊ฒŒ ์›€์ง์ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
const FORCE_STRENGTH: f32 = 0.1;  // ๋…ธ์ด์ฆˆ ํ•„๋“œ๊ฐ€ ํŒŒํ‹ฐํด์— ๊ฐ€ํ•˜๋Š” ํž˜์˜ ์„ธ๊ธฐ. ํด์ˆ˜๋ก ๋…ธ์ด์ฆˆ ํ•„๋“œ์˜ ํ๋ฆ„์„ ๋” ๊ฐ•ํ•˜๊ฒŒ ๋”ฐ๋ผ๊ฐ‘๋‹ˆ๋‹ค.
const TIME_INCREMENT: f64 = 0.005;  // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์‹œ๊ฐ„์„ ์–ผ๋งˆ๋‚˜ ์ฆ๊ฐ€์‹œํ‚ฌ์ง€ ๊ฒฐ์ •. ๋…ธ์ด์ฆˆ ํ•„๋“œ๊ฐ€ ๋ณ€ํ™”ํ•˜๋Š” ์†๋„๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
const TRAIL_ALPHA: f32 = 0.05;    // ์ž”์ƒ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ฐฐ๊ฒฝ์˜ ํˆฌ๋ช…๋„. 0.0์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์ž”์ƒ์ด ๊ธธ๊ฒŒ ๋‚จ๊ณ , 1.0์— ๊ฐ€๊นŒ์šฐ๋ฉด ๊ฑฐ์˜ ๋‚จ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


// ======================================================================
// ==== 2. ๋ฉ”์ธ ํ•จ์ˆ˜ (Main Function)
// ======================================================================
// Rust ํ”„๋กœ๊ทธ๋žจ์˜ ์ง„์ž…์ (entry point)์ž…๋‹ˆ๋‹ค.
fn main() {
    // nannou ์•ฑ์„ ์„ค์ •ํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋”(builder) ํŒจํ„ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    // app(model): `model` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์•ฑ์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    // .update(update): ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  `update` ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
    // .run(): ๋ชจ๋“  ์„ค์ •์„ ๋งˆ์น˜๊ณ  ์•ฑ์˜ ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    nannou::app(model).update(update).run();
}


// ======================================================================
// ==== 3. ํŒŒํ‹ฐํด ๊ตฌ์กฐ์ฒด ์ •์˜ (Particle Struct)
// ======================================================================
// ๊ฐœ๋ณ„ ํŒŒํ‹ฐํด์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ ํƒ€์ž…(๊ตฌ์กฐ์ฒด)์ž…๋‹ˆ๋‹ค.
struct Particle {
    pos: Vec2,      // ํŒŒํ‹ฐํด์˜ ํ˜„์žฌ ์œ„์น˜ (x, y ์ขŒํ‘œ). `Vec2`๋Š” nannou์˜ 2์ฐจ์› ๋ฒกํ„ฐ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
    prev_pos: Vec2, // ํŒŒํ‹ฐํด์˜ ์ด์ „ ํ”„๋ ˆ์ž„ ์œ„์น˜. ํ˜„์žฌ ์œ„์น˜์™€ ์—ฐ๊ฒฐํ•˜์—ฌ ๊ถค์ (์„ )์„ ๊ทธ๋ฆฌ๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
    vel: Vec2,      // ํŒŒํ‹ฐํด์˜ ์†๋„ (x, y ๋ฐฉํ–ฅ์˜ ์†๋„). ๋งค ํ”„๋ ˆ์ž„ ์œ„์น˜์— ๋”ํ•ด์ ธ ์›€์ง์ž„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
}


// ======================================================================
// ==== 4. ํŒŒํ‹ฐํด ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ (Particle Implementation)
// ======================================================================
// `impl` ํ‚ค์›Œ๋“œ๋Š” ํŠน์ • ๊ตฌ์กฐ์ฒด(์—ฌ๊ธฐ์„œ๋Š” Particle)์— ๋Œ€ํ•œ ๋ฉ”์„œ๋“œ(ํ•จ์ˆ˜)๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ธ”๋ก์ž…๋‹ˆ๋‹ค.
impl Particle {
    // `Particle`์˜ ์ƒ์„ฑ์ž(constructor) ์—ญํ• ์„ ํ•˜๋Š” ์—ฐ๊ด€ ํ•จ์ˆ˜(associated function)์ž…๋‹ˆ๋‹ค.
    // `&self`๋ฅผ ์ธ์ž๋กœ ๋ฐ›์ง€ ์•Š์œผ๋ฏ€๋กœ `Particle::new(...)` ํ˜•ํƒœ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
    fn new(x: f32, y: f32) -> Self {
        let pos = vec2(x, y); // `vec2` ํ•จ์ˆ˜๋กœ `Vec2` ํƒ€์ž…์˜ ์œ„์น˜ ๋ฒกํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
        
        // ์ƒˆ๋กœ์šด Particle ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        Particle {
            pos,                  // ํ˜„์žฌ ์œ„์น˜๋ฅผ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
            prev_pos: pos,        // ์ฒ˜์Œ์—๋Š” ์ด์ „ ์œ„์น˜์™€ ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
            vel: Vec2::ZERO,      // ์ฒ˜์Œ ์†๋„๋Š” 0์ž…๋‹ˆ๋‹ค. `Vec2::ZERO`๋Š” `vec2(0.0, 0.0)`์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
        }
    }

    // ์™ธ๋ถ€์—์„œ ์ฃผ์–ด์ง„ ํž˜(force)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํŒŒํ‹ฐํด์˜ ์†๋„์™€ ์œ„์น˜๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
    // `&mut self`๋Š” ์ด ๋ฉ”์„œ๋“œ๊ฐ€ Particle ์ธ์Šคํ„ด์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ '์ˆ˜์ •(mutate)'ํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
    fn update_with_force(&mut self, force: Vec2) {
        // ํž˜์„ ๊ฐ€์†๋„๋กœ ๊ฐ„์ฃผํ•˜์—ฌ ์†๋„์— ๋”ํ•ฉ๋‹ˆ๋‹ค. (์งˆ๋Ÿ‰=1๋กœ ๊ฐ€์ •)
        let acc = force; 
        
        // ์†๋„ ์—…๋ฐ์ดํŠธ ๋ฐ ์ตœ๋Œ€ ์†๋ ฅ ์ œํ•œ
        self.vel += acc;
        // `clamp_length_max`๋Š” ๋ฒกํ„ฐ์˜ ๋ฐฉํ–ฅ์€ ์œ ์ง€ํ•œ ์ฑ„, ํฌ๊ธฐ(๊ธธ์ด)๊ฐ€ ์ตœ๋Œ“๊ฐ’์„ ๋„˜์ง€ ์•Š๋„๋ก ์ œํ•œํ•˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
        self.vel = self.vel.clamp_length_max(MAX_SPEED);
        
        // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
        self.prev_pos = self.pos;  // ํ˜„์žฌ ์œ„์น˜๋ฅผ ์ด์ „ ์œ„์น˜๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
        self.pos += self.vel;      // ์†๋„๋ฅผ ํ˜„์žฌ ์œ„์น˜์— ๋”ํ•˜์—ฌ ๋‹ค์Œ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    }

    // ํŒŒํ‹ฐํด์ด ํ™”๋ฉด ๊ฒฝ๊ณ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ, ๋ฐ˜๋Œ€ํŽธ์—์„œ ๋‹ค์‹œ ๋‚˜ํƒ€๋‚˜๋„๋ก ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค (ํ™”๋ฉด ๊ฐ์‹ธ๊ธฐ).
    fn edges(&mut self, rect: &Rect) {
        // `rect`๋Š” ํ™”๋ฉด์˜ ์‚ฌ๊ฐํ˜• ์˜์—ญ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
        if self.pos.x > rect.right()  { self.pos.x = rect.left();  self.prev_pos.x = self.pos.x; }
        if self.pos.x < rect.left()   { self.pos.x = rect.right(); self.prev_pos.x = self.pos.x; }
        if self.pos.y > rect.top()    { self.pos.y = rect.bottom();self.prev_pos.y = self.pos.y; }
        if self.pos.y < rect.bottom() { self.pos.y = rect.top();   self.prev_pos.y = self.pos.y; }
        // ๊ฒฝ๊ณ„๋ฅผ ๋„˜์„ ๋•Œ `prev_pos`๋„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
        // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ™”๋ฉด์„ ๊ฐ€๋กœ์ง€๋ฅด๋Š” ๊ธด ์„ ์ด ๊ทธ๋ ค์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    }
}


// ======================================================================
// ==== 5. ๋ชจ๋ธ(์ƒํƒœ) ๊ตฌ์กฐ์ฒด ์ •์˜ (Model Struct)
// ======================================================================
// nannou ์•ฑ์˜ ์ „์ฒด ์ƒํƒœ(state)๋ฅผ ๋‹ด๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
// ์ด `Model`์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ `update`์™€ `view` ํ•จ์ˆ˜ ์‚ฌ์ด์—์„œ ๊ณ„์† ์ „๋‹ฌ๋˜๋ฉฐ ์•ฑ์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
struct Model {
    perlin: Perlin,                 // Perlin ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ ์ธ์Šคํ„ด์Šค.
    particles: Vec<Particle>,       // ๋ชจ๋“  `Particle`๋“ค์„ ๋‹ด๊ณ  ์žˆ๋Š” ๋™์  ๋ฐฐ์—ด(Vector).
    time: f64,                      // ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•œ ์‹œ๊ฐ„ ๊ฐ’. 3D ๋…ธ์ด์ฆˆ์˜ z์ถ•์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
}


// ======================================================================
// ==== 6. ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ (Model Initialization)
// ======================================================================
// ์•ฑ์ด ์‹œ์ž‘๋  ๋•Œ ๋‹จ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜์–ด `Model`์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
fn model(app: &App) -> Model {
    // ์ฐฝ(window)์„ ์ƒ์„ฑํ•˜๊ณ , ํฌ๊ธฐ/์ œ๋ชฉ ๋“ฑ์„ ์„ค์ •ํ•œ ๋’ค, `view` ํ•จ์ˆ˜๋ฅผ ์ด ์ฐฝ์˜ ๋ Œ๋”๋ง ๋ฃจํ”„๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
    app.new_window()
        .size(1024, 1024)
        .title("Perlin Noise Flow Field (Refactored & Commented)")
        .view(view) // ์ด ์ฐฝ์„ ๊ทธ๋ฆด ๋•Œ `view` ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
        .build()
        .unwrap(); // `build()`๋Š” `Result`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, `unwrap()`์œผ๋กœ ์„ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

    let win = app.window_rect(); // ํ˜„์žฌ ์ฐฝ์˜ ์‚ฌ๊ฐํ˜• ์˜์—ญ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    let perlin = Perlin::new();  // ์ƒˆ๋กœ์šด Perlin ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    // Rust์˜ ์ดํ„ฐ๋ ˆ์ดํ„ฐ(iterator)์™€ `map`, `collect`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒํ‹ฐํด์„ ํšจ์œจ์ ์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    let particles = (0..NUM_PARTICLES) // 0๋ถ€ํ„ฐ `NUM_PARTICLES - 1`๊นŒ์ง€์˜ ์ˆซ์ž ๋ฒ”์œ„๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        .map(|_| { // ๊ฐ ์ˆซ์ž์— ๋Œ€ํ•ด ๋‹ค์Œ ํด๋กœ์ €(closure, ์ต๋ช… ํ•จ์ˆ˜)๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. `_`๋Š” ์ˆซ์ž๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
            // ํ™”๋ฉด ๋‚ด ๋ฌด์ž‘์œ„ ์œ„์น˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
            let x = random_range(win.left(), win.right());
            let y = random_range(win.bottom(), win.top());
            Particle::new(x, y) // ํ•ด๋‹น ์œ„์น˜์— ์ƒˆ๋กœ์šด ํŒŒํ‹ฐํด์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
        })
        .collect(); // `map`์„ ํ†ตํ•ด ์ƒ์„ฑ๋œ ๋ชจ๋“  `Particle`๋“ค์„ ๋ชจ์•„ `Vec<Particle>` ์ปฌ๋ ‰์…˜์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    // ์™„์„ฑ๋œ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋‹ด์€ `Model` ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    Model {
        perlin,
        particles,
        time: 0.0, // ์‹œ๊ฐ„์€ 0์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    }
}


// ======================================================================
// ==== 7. ์ƒํƒœ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ (State Update Function)
// ======================================================================
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค `view` ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ง์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
// ์•ฑ์˜ ๋…ผ๋ฆฌ์ ์ธ ์ƒํƒœ(์œ„์น˜, ์†๋„ ๋“ฑ)๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn update(app: &App, model: &mut Model, _update: Update) {
    let win = app.window_rect();

    // `&mut model.particles`๋ฅผ ํ†ตํ•ด ๊ฐ ํŒŒํ‹ฐํด์„ ๊ฐ€๋ณ€์ ์œผ๋กœ ๋นŒ๋ ค์™€ ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    for p in &mut model.particles {
        // ํŒŒํ‹ฐํด์˜ ํ˜„์žฌ ์œ„์น˜(x, y)์™€ ํ˜„์žฌ ์‹œ๊ฐ„(time)์„ ์ด์šฉํ•ด 3D Perlin ๋…ธ์ด์ฆˆ ๊ฐ’์„ ์–ป์Šต๋‹ˆ๋‹ค.
        let noise_val = model.perlin.get([
            p.pos.x as f64 * NOISE_SCALE, // x, y ์ขŒํ‘œ์— ์Šค์ผ€์ผ์„ ๊ณฑํ•ด ๋…ธ์ด์ฆˆ ์ƒ์„ธ๋„๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
            p.pos.y as f64 * NOISE_SCALE, // `get`์€ f64 ๋ฐฐ์—ด์„ ๋ฐ›์œผ๋ฏ€๋กœ ํƒ€์ž… ์บ์ŠคํŒ…(as f64)์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
            model.time,                   // z์ถ• ๊ฐ’์œผ๋กœ ์‹œ๊ฐ„์„ ๋„ฃ์–ด ๋…ธ์ด์ฆˆ ํ•„๋“œ๊ฐ€ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
        ]);

        // ๋…ธ์ด์ฆˆ ๊ฐ’(-1.0 ~ 1.0)์„ ๊ฐ๋„(0 ~ 2ฯ€) ๋ฒ”์œ„๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        // `TAU`๋Š” nannou prelude์— ์ •์˜๋œ ์ƒ์ˆ˜์ด๋ฉฐ `2.0 * PI`์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์› ์ „์ฒด๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ๊ฐ€๋…์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
        let angle = map_range(noise_val, -1.0, 1.0, 0.0, TAU as f64) as f32;
        
        // ๊ณ„์‚ฐ๋œ ๊ฐ๋„๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํž˜(force) ๋ฒกํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
        // `(angle.cos(), angle.sin())`์€ ํ•ด๋‹น ๊ฐ๋„์˜ ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹จ์œ„ ๋ฒกํ„ฐ(๊ธธ์ด๊ฐ€ 1์ธ ๋ฒกํ„ฐ)๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        let force = vec2(angle.cos(), angle.sin()) * FORCE_STRENGTH;

        // ๊ณ„์‚ฐ๋œ ํž˜์œผ๋กœ ํŒŒํ‹ฐํด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ , ํ™”๋ฉด ๊ฒฝ๊ณ„๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
        p.update_with_force(force);
        p.edges(&win);
    }

    // ๋‹ค์Œ ํ”„๋ ˆ์ž„์„ ์œ„ํ•ด ์‹œ๊ฐ„์„ ์•ฝ๊ฐ„ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
    model.time += TIME_INCREMENT;
}


// ======================================================================
// ==== 8. ๋“œ๋กœ์ž‰ ํ•จ์ˆ˜ (View / Drawing Function)
// ======================================================================
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค `update` ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ ํ›„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
// ํ˜„์žฌ `Model`์˜ ์ƒํƒœ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ™”๋ฉด์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw(); // `Draw` ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น์€ ์ด `draw` ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

    // ์ฒซ ํ”„๋ ˆ์ž„์—๋งŒ ๋ฐฐ๊ฒฝ์„ ์™„์ „ํžˆ ๊ฒ€์€์ƒ‰์œผ๋กœ ์น ํ•ฉ๋‹ˆ๋‹ค.
    if app.elapsed_frames() == 1 {
        draw.background().color(BLACK);
    }
    
    // ๋งค ํ”„๋ ˆ์ž„, ์ด์ „์— ๊ทธ๋ ค์ง„ ๊ทธ๋ฆผ ์œ„์— ๋ฐ˜ํˆฌ๋ช…ํ•œ ๊ฒ€์€ ์‚ฌ๊ฐํ˜•์„ ๋ฎ์–ด ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    // ์ด ๊ณผ์ •์ด ๋ฐ˜๋ณต๋˜๋ฉด์„œ ํŒŒํ‹ฐํด์˜ ์›€์ง์ž„์— ์ž์—ฐ์Šค๋Ÿฌ์šด ์ž”์ƒ(trail) ํšจ๊ณผ๊ฐ€ ์ƒ๊น๋‹ˆ๋‹ค.
    draw.rect()
        .wh(app.window_rect().wh()) // ์ฐฝ ์ „์ฒด ํฌ๊ธฐ์˜ ์‚ฌ๊ฐํ˜•
        .color(srgba(0.0, 0.0, 0.0, TRAIL_ALPHA)); // `srgba`๋Š” ํˆฌ๋ช…๋„๋ฅผ ํฌํ•จํ•œ ์ƒ‰์ƒ์ž…๋‹ˆ๋‹ค.

    // ๋ชจ๋“  ํŒŒํ‹ฐํด์„ ์ˆœํšŒํ•˜๋ฉฐ ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ์ƒํƒœ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ `&model.particles`๋กœ ๋นŒ๋ ค์˜ต๋‹ˆ๋‹ค.
    for p in &model.particles {
        // ์ƒ‰์ƒ์„ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค๋ฅธ ๋…ธ์ด์ฆˆ ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        // `time + 100.0` ์ฒ˜๋Ÿผ ์‹œ๊ฐ„์— ํฐ ๊ฐ’์„ ๋”ํ•ด์ฃผ๋ฉด, ์›€์ง์ž„์˜ ๋ฐฉํ–ฅ๊ณผ ์ƒ‰์ƒ ๋ณ€ํ™”๊ฐ€
        // ์„œ๋กœ ๋‹ค๋ฅธ ํŒจํ„ด์„ ๊ฐ€์ง€๊ฒŒ ๋˜์–ด ๋” ํฅ๋ฏธ๋กœ์šด ์‹œ๊ฐ์  ํšจ๊ณผ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        let noise_val_color = model.perlin.get([
            p.pos.x as f64 * NOISE_SCALE,
            p.pos.y as f64 * NOISE_SCALE,
            model.time + 100.0,
        ]);

        // ๋…ธ์ด์ฆˆ ๊ฐ’์„ HSL ์ƒ‰์ƒ ๋ชจ๋ธ์˜ ์ƒ‰์ƒ(Hue) ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        // HSL ๋ชจ๋ธ์—์„œ Hue ๊ฐ’์„ ์—ฐ์†์ ์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๋ฌด์ง€๊ฐœ์ฒ˜๋Ÿผ ๋ถ€๋“œ๋Ÿฌ์šด ์ƒ‰์ƒ ์ „ํ™˜์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        let hue = map_range(noise_val_color, -1.0, 1.0, 0.5, 1.0); // ํŒŒ๋ž€์ƒ‰~๋ณด๋ผ์ƒ‰~๋นจ๊ฐ„์ƒ‰ ๊ณ„์—ด
        let color = hsla(hue as f32, 0.7, 0.6, 0.7); // Hue, Saturation, Lightness, Alpha

        // ํŒŒํ‹ฐํด์˜ ์ด์ „ ์œ„์น˜์—์„œ ํ˜„์žฌ ์œ„์น˜๊นŒ์ง€ ์„ ์„ ๊ทธ์–ด ๊ถค์ ์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.
        draw.line()
            .start(p.prev_pos)
            .end(p.pos)
            .weight(1.5) // ์„ ์˜ ๋‘๊ป˜
            .color(color);
    }

    // ์ง€๊ธˆ๊นŒ์ง€ ๋ฒ„ํผ์— ์Œ“์ธ ๋ชจ๋“  ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น๋“ค์„ ์‹ค์ œ ํ”„๋ ˆ์ž„์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
    draw.to_frame(app, &frame).unwrap();
}

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

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