๐Ÿ”ฎ :: Turbulence in Circle

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

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

struct Model {
    particles: Vec<Particle>,
    noise: Perlin,
}

struct Particle {
    pos: Vec2,
    vel: Vec2,
    col: Hsla,
}

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

fn model(app: &App) -> Model {
    app.new_window().size(800, 800).view(view).build().unwrap();

    // ์ดˆ๊ธฐ ์ž…์ž ์ƒ์„ฑ
    let particles = (0..2000)
        .map(|_| {
            let x = random_range(-400.0, 400.0);
            let y = random_range(-400.0, 400.0);
            Particle {
                pos: vec2(x, y),
                vel: Vec2::ZERO,
                col: hsla(0.0, 0.0, 1.0, 0.15), // ํ™”์ดํŠธ, ๋ฐ˜ํˆฌ๋ช…
            }
        })
        .collect();

    Model {
        particles,
        noise: Perlin::new(),
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let time = (app.elapsed_frames() as f64) * 0.005;

    for p in model.particles.iter_mut() {
        // ๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜ ํ๋ฆ„์žฅ
        let angle =
            (
                model.noise.get([(p.pos.x as f64) * 0.004, (p.pos.y as f64) * 0.004, time]) as f32 //์„ธ๋ฐ€ํ•จ
            ) * TAU;

        let dir = vec2(angle.cos(), angle.sin()) * 1.0; //๋†’์œผ๋ฉด ์„ฑ๊ฒจ์ง
        p.vel = dir;
        p.pos += p.vel;

        // ํ™”๋ฉด ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€๋ฉด ๋ฆฌ์…‹
        if p.pos.length() > 300.0 {
            p.pos = vec2(random_range(-100.0, 100.0), random_range(-100.0, 100.0));
        }

        // ์ž…์ž ์ƒ‰์ƒ์€ ํ™”์ดํŠธ ์œ ์ง€ (์ถ”ํ›„ ์†๋„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ)
        p.col = hsla(0.0, 0.0, 1.0, 0.05);
    }
}

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

    // ์ฒซ ํ”„๋ ˆ์ž„์—์„œ๋งŒ ๋ฐฐ๊ฒฝ ์ดˆ๊ธฐํ™” (์•„์ฃผ ์–ด๋‘์šด ๋ฐฐ๊ฒฝ)
    if app.elapsed_frames() == 1 {
        draw.background().color(hsla(0.0, 0.0, 0.02, 1.0));
    }

    for p in &model.particles {
        draw.ellipse().x_y(p.pos.x, p.pos.y).w_h(1.0, 1.0).color(p.col);
    }

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

๐Ÿ“ Rust Code + Comment

// nannou ํ”„๋ ˆ์ž„์›Œํฌ์˜ prelude ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// prelude๋Š” Nannou๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ํƒ€์ž…, ํŠธ๋ ˆ์ž‡, ํ•จ์ˆ˜๋“ค์„ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด
// ๋งค๋ฒˆ ๊ฐœ๋ณ„์ ์œผ๋กœ use ์„ ์–ธ์„ ํ•˜์ง€ ์•Š์•„๋„ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
use nannou::prelude::*;

// nannou์—์„œ ์ œ๊ณตํ•˜๋Š” ๋…ธ์ด์ฆˆ ํ•จ์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// NoiseFn ํŠธ๋ ˆ์ž‡์€ ๋…ธ์ด์ฆˆ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜(get)๋ฅผ ์ •์˜ํ•˜๊ณ ,
// Perlin์€ Perlin ๋…ธ์ด์ฆˆ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
use nannou::noise::{ NoiseFn, Perlin };

// ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์ƒํƒœ(State)๋ฅผ ์ €์žฅํ•  ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
// Model์€ Nannou ์•ฑ์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์—ญํ• ์„ ํ•˜๋ฉฐ, ์•ฑ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ ๊ณ„์† ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.
struct Model {
    // ํ™”๋ฉด์— ๊ทธ๋ ค์งˆ ๋ชจ๋“  ํŒŒํ‹ฐํด(์ž…์ž)๋“ค์„ ๋‹ด์•„๋‘˜ ๋ฒกํ„ฐ(๋™์  ๋ฐฐ์—ด)์ž…๋‹ˆ๋‹ค.
    particles: Vec<Particle>,
    // Perlin ๋…ธ์ด์ฆˆ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค. ์ด ๋…ธ์ด์ฆˆ๋ฅผ ์ด์šฉํ•ด ํŒŒํ‹ฐํด์˜ ์›€์ง์ž„์„ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
    noise: Perlin,
}

// ๊ฐœ๋ณ„ ํŒŒํ‹ฐํด(์ž…์ž)์˜ ์†์„ฑ์„ ์ •์˜ํ•˜๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
struct Particle {
    // ํŒŒํ‹ฐํด์˜ ํ˜„์žฌ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” 2์ฐจ์› ๋ฒกํ„ฐ(x, y)์ž…๋‹ˆ๋‹ค.
    pos: Vec2,
    // ํŒŒํ‹ฐํด์˜ ์†๋„์™€ ๋ฐฉํ–ฅ์„ ๋‚˜ํƒ€๋‚ด๋Š” 2์ฐจ์› ๋ฒกํ„ฐ์ž…๋‹ˆ๋‹ค.
    vel: Vec2,
    // ํŒŒํ‹ฐํด์˜ ์ƒ‰์ƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. HSLA(์ƒ‰์ƒ, ์ฑ„๋„, ๋ช…๋„, ํˆฌ๋ช…๋„) ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    col: Hsla,
}

// ํ”„๋กœ๊ทธ๋žจ์˜ ์ง„์ž…์ (Entry Point)์ž…๋‹ˆ๋‹ค.
fn main() {
    // nannou ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์ •ํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    // model ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ดˆ๊ธฐ ๋ชจ๋ธ์„ ์„ค์ •ํ•˜๊ณ ,
    // update ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๋งค ํ”„๋ ˆ์ž„ ๋ชจ๋ธ์˜ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฉฐ,
    // run()์„ ํ˜ธ์ถœํ•˜์—ฌ ์•ฑ์˜ ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    nannou::app(model).update(update).run();
}

// ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ฒ˜์Œ ์‹œ์ž‘๋  ๋•Œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋˜๋Š” ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
// ์•ฑ์˜ ์ดˆ๊ธฐ ์ƒํƒœ(Model)๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
fn model(app: &App) -> Model {
    // ์ƒˆ ์ฐฝ์„ ์ƒ์„ฑํ•˜๊ณ , ๊ฐ€๋กœ 800, ์„ธ๋กœ 800 ํ”ฝ์…€ ํฌ๊ธฐ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    // view ํ•จ์ˆ˜๋ฅผ ์ด ์ฐฝ์˜ ๊ทธ๋ฆฌ๊ธฐ ์ฝœ๋ฐฑ์œผ๋กœ ์ง€์ •ํ•˜๊ณ , ์ตœ์ข…์ ์œผ๋กœ ์ฐฝ์„ ๋นŒ๋“œํ•ฉ๋‹ˆ๋‹ค.
    app.new_window().size(800, 800).view(view).build().unwrap();

    // 2000๊ฐœ์˜ ํŒŒํ‹ฐํด์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฒกํ„ฐ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    let particles = (0..2000)
        .map(|_| { // _ ๋Š” ๋ฐ˜๋ณต ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
            // ์ฐฝ ํฌ๊ธฐ(-400 ~ 400) ๋‚ด์—์„œ ๋ฌด์ž‘์œ„ x, y ์ขŒํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
            let x = random_range(-400.0, 400.0);
            let y = random_range(-400.0, 400.0);
            
            // Particle ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
            Particle {
                // ์œ„์—์„œ ์ƒ์„ฑํ•œ ๋ฌด์ž‘์œ„ ์ขŒํ‘œ๋ฅผ ์ดˆ๊ธฐ ์œ„์น˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
                pos: vec2(x, y),
                // ์ดˆ๊ธฐ ์†๋„๋Š” 0์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
                vel: Vec2::ZERO,
                // ์ดˆ๊ธฐ ์ƒ‰์ƒ์€ ํฐ์ƒ‰(๋ช…๋„ 1.0)์— ์•ฝ๊ฐ„์˜ ํˆฌ๋ช…๋„(0.15)๋ฅผ ์ค๋‹ˆ๋‹ค.
                col: hsla(0.0, 0.0, 1.0, 0.15),
            }
        })
        .collect(); // map์„ ํ†ตํ•ด ์ƒ์„ฑ๋œ ๋ชจ๋“  Particle๋“ค์„ Vec<Particle> ์ปฌ๋ ‰์…˜์œผ๋กœ ๋ชจ์๋‹ˆ๋‹ค.

    // ์ตœ์ข…์ ์œผ๋กœ ์™„์„ฑ๋œ Model ๊ตฌ์กฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    Model {
        // ์œ„์—์„œ ์ƒ์„ฑํ•œ ํŒŒํ‹ฐํด ๋ฒกํ„ฐ๋ฅผ ๋ชจ๋ธ์˜ ์ƒํƒœ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
        particles,
        // ์ƒˆ๋กœ์šด Perlin ๋…ธ์ด์ฆˆ ์ƒ์„ฑ๊ธฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ๋ชจ๋ธ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
        noise: Perlin::new(),
    }
}

// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋˜์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ(Model)๋ฅผ ๊ฐฑ์‹ ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
// ํŒŒํ‹ฐํด์˜ ์œ„์น˜, ์†๋„, ์ƒ‰์ƒ ๋“ฑ์„ ๊ณ„์‚ฐํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์ด ๋‹ด๊น๋‹ˆ๋‹ค.
fn update(app: &App, model: &mut Model, _update: Update) {
    // ์•ฑ์ด ์‹œ์ž‘๋œ ํ›„ ๊ฒฝ๊ณผํ•œ ํ”„๋ ˆ์ž„ ์ˆ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹œ๊ฐ„ ๊ฐ’์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    // ์ด ์‹œ๊ฐ„ ๊ฐ’์€ Perlin ๋…ธ์ด์ฆˆ์˜ z์ถ• ์ž…๋ ฅ์œผ๋กœ ์‚ฌ์šฉ๋˜์–ด, ์‹œ๊ฐ„์— ๋”ฐ๋ผ ํ๋ฆ„์žฅ์ด ๊ณ„์† ๋ณ€ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
    // 0.005๋ฅผ ๊ณฑํ•ด ์‹œ๊ฐ„์˜ ํ๋ฆ„ ์†๋„๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
    let time = (app.elapsed_frames() as f64) * 0.005;

    // ๋ชจ๋ธ์— ์ €์žฅ๋œ ๋ชจ๋“  ํŒŒํ‹ฐํด์„ ์ˆœํšŒํ•˜๋ฉฐ ๊ฐ ํŒŒํ‹ฐํด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
    // iter_mut()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ํŒŒํ‹ฐํด์˜ ๊ฐ’์„ ์ง์ ‘ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    for p in model.particles.iter_mut() {
        // Perlin ๋…ธ์ด์ฆˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ํŒŒํ‹ฐํด ์œ„์น˜์™€ ์‹œ๊ฐ„(x, y, z)์— ํ•ด๋‹นํ•˜๋Š” ๋…ธ์ด์ฆˆ ๊ฐ’์„ ์–ป์Šต๋‹ˆ๋‹ค.
        // ์ด ๋…ธ์ด์ฆˆ ๊ฐ’์€ -1.0์—์„œ 1.0 ์‚ฌ์ด์˜ ๊ฐ’์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
        // ์œ„์น˜ ๊ฐ’์— 0.004๋ฅผ ๊ณฑํ•˜์—ฌ ๋…ธ์ด์ฆˆ ๊ณต๊ฐ„์˜ ์Šค์ผ€์ผ์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด ์ž‘์„์ˆ˜๋ก ๋ถ€๋“œ๋Ÿฝ๊ณ  ๊ฑฐ๋Œ€ํ•œ ํ๋ฆ„์ด, ํด์ˆ˜๋ก ์ž๊ธ€์ž๊ธ€ํ•œ ํ๋ฆ„์ด ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค.
        let noise_val = model.noise.get([(p.pos.x as f64) * 0.004, (p.pos.y as f64) * 0.004, time]) as f32;
        
        // ๋…ธ์ด์ฆˆ ๊ฐ’(-1.0 ~ 1.0)์„ ๊ฐ๋„(0 ~ 2ฯ€)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        // TAU๋Š” 2 * PI ์™€ ๊ฐ™์€ ์ƒ์ˆ˜์ž…๋‹ˆ๋‹ค.
        let angle = noise_val * TAU;

        // ๊ณ„์‚ฐ๋œ ๊ฐ๋„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        // ์ด ๋ฒกํ„ฐ๋Š” ํŒŒํ‹ฐํด์ด ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ๋‚˜์•„๊ฐˆ ๋ฐฉํ–ฅ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
        // ๋’ค์— ๊ณฑํ•ด์ง€๋Š” ๊ฐ’(1.0)์€ ํŒŒํ‹ฐํด์˜ ์†๋ ฅ์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ’์„ ํ‚ค์šฐ๋ฉด ํŒŒํ‹ฐํด์ด ๋” ๋นจ๋ฆฌ ์›€์ง์ž…๋‹ˆ๋‹ค.
        let dir = vec2(angle.cos(), angle.sin()) * 1.0; 
        
        // ํŒŒํ‹ฐํด์˜ ์†๋„๋ฅผ ์œ„์—์„œ ๊ณ„์‚ฐํ•œ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
        p.vel = dir;
        // ํŒŒํ‹ฐํด์˜ ํ˜„์žฌ ์œ„์น˜์— ์†๋„๋ฅผ ๋”ํ•˜์—ฌ ๋‹ค์Œ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        p.pos += p.vel;

        // ํŒŒํ‹ฐํด์ด ํŠน์ • ๊ฒฝ๊ณ„(์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ 300.0)๋ฅผ ๋ฒ—์–ด๋‚ฌ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        // length()๋Š” ์›์ (0,0)์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        if p.pos.length() > 300.0 {
            // ๊ฒฝ๊ณ„๋ฅผ ๋ฒ—์–ด๋‚ฌ๋‹ค๋ฉด, ํŒŒํ‹ฐํด์„ ํ™”๋ฉด ์ค‘์•™ ๊ทผ์ฒ˜์˜ ์ƒˆ๋กœ์šด ๋ฌด์ž‘์œ„ ์œ„์น˜๋กœ ๋ฆฌ์…‹ํ•ฉ๋‹ˆ๋‹ค.
            // ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ํŒŒํ‹ฐํด๋“ค์ด ๊ณ„์†ํ•ด์„œ ํ™”๋ฉด ์ค‘์•™ ์˜์—ญ์—์„œ ํ๋ฆ„์„ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.
            p.pos = vec2(random_range(-100.0, 100.0), random_range(-100.0, 100.0));
        }

        // ํŒŒํ‹ฐํด์˜ ์ƒ‰์ƒ์„ ํฐ์ƒ‰(๋ช…๋„ 1.0)์œผ๋กœ ์„ค์ •ํ•˜๊ณ , ๋งค์šฐ ๋‚ฎ์€ ํˆฌ๋ช…๋„(0.05)๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
        // ๋ฐฐ๊ฒฝ์„ ์ง€์šฐ์ง€ ์•Š๊ณ  ๊ณ„์† ๋ง๊ทธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—, ๋‚ฎ์€ ํˆฌ๋ช…๋„๋Š” ํŒŒํ‹ฐํด์˜ ์ด๋™ ๊ถค์ ์„ ๋ถ€๋“œ๋Ÿฌ์šด ์„ ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๋Š” ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.
        p.col = hsla(0.0, 0.0, 1.0, 0.05);
    }
}

// update ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ ํ›„, ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ™”๋ฉด์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
fn view(app: &App, model: &Model, frame: Frame) {
    // ๊ทธ๋ฆฌ๊ธฐ๋ฅผ ์œ„ํ•œ Draw ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    let draw = app.draw();

    // ์•ฑ์˜ ์ฒซ ๋ฒˆ์งธ ํ”„๋ ˆ์ž„์—์„œ๋งŒ ์‹คํ–‰๋˜๋Š” ๋ธ”๋ก์ž…๋‹ˆ๋‹ค.
    if app.elapsed_frames() == 1 {
        // ๋ฐฐ๊ฒฝ์„ ์•„์ฃผ ์–ด๋‘์šด ์ƒ‰์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์น ํ•ฉ๋‹ˆ๋‹ค.
        // ์ดํ›„ ํ”„๋ ˆ์ž„์—์„œ๋Š” ๋ฐฐ๊ฒฝ์„ ๋‹ค์‹œ ์น ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํŒŒํ‹ฐํด์ด ์›€์ง์ด๋Š” ๊ถค์ ์ด ๊ทธ๋Œ€๋กœ ํ™”๋ฉด์— ๋ˆ„์ ๋ฉ๋‹ˆ๋‹ค.
        // ์ด๊ฒƒ์ด ์ด ์•„ํŠธ์›Œํฌ์˜ ํ•ต์‹ฌ ์‹œ๊ฐ ํšจ๊ณผ์ž…๋‹ˆ๋‹ค.
        draw.background().color(hsla(0.0, 0.0, 0.02, 1.0));
    }

    // ๋ชจ๋ธ์˜ ๋ชจ๋“  ํŒŒํ‹ฐํด๋“ค์„ ์ˆœํšŒํ•ฉ๋‹ˆ๋‹ค.
    for p in &model.particles {
        // ๊ฐ ํŒŒํ‹ฐํด์„ ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
        draw.ellipse() // ํƒ€์›(์›)์„ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜
            .x_y(p.pos.x, p.pos.y) // ํŒŒํ‹ฐํด์˜ ์œ„์น˜์—
            .w_h(1.0, 1.0) // ๊ฐ€๋กœ, ์„ธ๋กœ 1.0 ํ”ฝ์…€ ํฌ๊ธฐ๋กœ
            .color(p.col); // ํŒŒํ‹ฐํด์˜ ์ƒ‰์ƒ(ํˆฌ๋ช…๋„๊ฐ€ ํฌํ•จ๋œ ํฐ์ƒ‰)์œผ๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    }

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

๐Ÿ“ Processing

// Nannou ์ฝ”๋“œ๋ฅผ Processing์œผ๋กœ ํฌํŒ…ํ•œ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.
// 2000๊ฐœ์˜ ํŒŒํ‹ฐํด์ด 3D ๋…ธ์ด์ฆˆ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์„ฑ๋œ ํ๋ฆ„์žฅ(flow field)์„ ๋”ฐ๋ผ ์›€์ง์ž…๋‹ˆ๋‹ค.

// ํŒŒํ‹ฐํด๋“ค์„ ๋‹ด์„ ArrayList
ArrayList<Particle> particles;

// ๋…ธ์ด์ฆˆ์˜ 3๋ฒˆ์งธ ์ฐจ์›์œผ๋กœ ์‚ฌ์šฉ๋  ์‹œ๊ฐ„ ๋ณ€์ˆ˜
float time = 0;

// ํŒŒํ‹ฐํด ํด๋ž˜์Šค ์ •์˜
// Nannou์˜ 'struct Particle'์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
class Particle {
  PVector pos; // ์œ„์น˜ (position)
  PVector vel; // ์†๋„ (velocity)

  // ์ƒ์„ฑ์ž (Constructor)
  Particle(float x, float y) {
    pos = new PVector(x, y);
    vel = new PVector(0, 0); // ์ดˆ๊ธฐ ์†๋„๋Š” 0
  }

  // ํŒŒํ‹ฐํด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฉ”์†Œ๋“œ
  void update() {
    // 3D Perlin ๋…ธ์ด์ฆˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ๋„(angle)๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    // Nannou ์ฝ”๋“œ์˜ model.noise.get(...) * TAU ๋ถ€๋ถ„์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
    // pos.x * 0.004, pos.y * 0.004๋Š” ๋…ธ์ด์ฆˆ ์Šค์ผ€์ผ(๋””ํ…Œ์ผ)์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
    float angle = noise(pos.x * 0.004f, pos.y * 0.004f, time) * TWO_PI;

    // ๊ณ„์‚ฐ๋œ ๊ฐ๋„๋กœ๋ถ€ํ„ฐ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ(velocity)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    vel = PVector.fromAngle(angle);

    // ์œ„์น˜์— ์†๋„๋ฅผ ๋”ํ•ด ํŒŒํ‹ฐํด์„ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.
    pos.add(vel);

    // ํŒŒํ‹ฐํด์ด ํŠน์ • ๋ฐ˜๊ฒฝ(300)์„ ๋ฒ—์–ด๋‚˜๋ฉด ์ค‘์•™ ๊ทผ์ฒ˜์˜ ์ƒˆ๋กœ์šด ๋ฌด์ž‘์œ„ ์œ„์น˜๋กœ ๋ฆฌ์…‹ํ•ฉ๋‹ˆ๋‹ค.
    // Nannou ์ฝ”๋“œ์˜ p.pos.length() > 300.0 ๋ถ€๋ถ„์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
    if (pos.mag() > 300) {
      pos.set(random(-100, 100), random(-100, 100));
    }
  }

  // ํŒŒํ‹ฐํด์„ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๋Š” ๋ฉ”์†Œ๋“œ
  void display() {
    // 1x1 ํฌ๊ธฐ์˜ ์ ์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    // Nannou ์ฝ”๋“œ์˜ draw.ellipse().w_h(1.0, 1.0)์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
    point(pos.x, pos.y);
  }
}

// ์ดˆ๊ธฐ ์„ค์ •์„ ์œ„ํ•œ setup() ํ•จ์ˆ˜
// Nannou์˜ model() ํ•จ์ˆ˜์™€ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
void setup() {
  size(800, 800);

  // ํŒŒํ‹ฐํด ArrayList ์ดˆ๊ธฐํ™”
  particles = new ArrayList<Particle>();

  // 2000๊ฐœ์˜ ํŒŒํ‹ฐํด ์ƒ์„ฑ
  for (int i = 0; i < 2000; i++) {
    float x = random(-400, 400);
    float y = random(-400, 400);
    particles.add(new Particle(x, y));
  }

  // Nannou ์ฝ”๋“œ์—์„œ ์ฒซ ํ”„๋ ˆ์ž„์—๋งŒ ๋ฐฐ๊ฒฝ์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.
  // ์–ด๋‘์šด ํšŒ์ƒ‰(R:5, G:5, B:5)์œผ๋กœ ๋ฐฐ๊ฒฝ์„ ํ•œ ๋ฒˆ๋งŒ ์น ํ•ด, ํŒŒํ‹ฐํด์˜ ๊ถค์ ์ด ๊ณ„์† ๋‚จ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  // hsla(0.0, 0.0, 0.02, 1.0)๋Š” ์•ฝ RGB(5, 5, 5)์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  background(5);
}

// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” draw() ํ•จ์ˆ˜
// Nannou์˜ update()์™€ view() ํ•จ์ˆ˜์˜ ์—ญํ• ์„ ๋ชจ๋‘ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
void draw() {
  // Nannou์™€ ๊ฐ™์ด (0,0)์„ ํ™”๋ฉด ์ค‘์•™์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ขŒํ‘œ๊ณ„๋ฅผ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.
  translate(width / 2, height / 2);

  // ์‹œ๊ฐ„ ๋ณ€์ˆ˜ ์—…๋ฐ์ดํŠธ
  // Nannou์˜ (app.elapsed_frames() as f64) * 0.005 ๋ถ€๋ถ„์— ํ•ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
  time += 0.005;

  // ํŒŒํ‹ฐํด์˜ ์ƒ‰์ƒ๊ณผ ๋‘๊ป˜ ์„ค์ •
  // hsla(0.0, 0.0, 1.0, 0.05)๋Š” ํฐ์ƒ‰์— ์•ŒํŒŒ๊ฐ’ 0.05๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  // Processing์˜ ์•ŒํŒŒ๊ฐ’์€ 0-255 ๋ฒ”์œ„์ด๋ฏ€๋กœ 0.05 * 255 โ‰ˆ 13์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  stroke(255, 13);
  strokeWeight(1);

  // ๋ชจ๋“  ํŒŒํ‹ฐํด์— ๋Œ€ํ•ด update์™€ display๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  for (Particle p : particles) {
    p.update();
    p.display();
  }
}

Nannou(Rust)์™€ Processing(Java)์€ ๊ตฌ์กฐ์™€ ๋ฌธ๋ฒ•์ด ๋‹ค๋ฅด๋ฏ€๋กœ, ๋ช‡ ๊ฐ€์ง€ ํ•ต์‹ฌ์ ์ธ ๋ณ€ํ™˜ ํฌ์ธํŠธ๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

1. ๊ตฌ์กฐ : model, update, view โ†’ setup, draw

  • Nannou๋Š” model (์ดˆ๊ธฐํ™”), update (์ƒํƒœ ๋ณ€๊ฒฝ), view (๊ทธ๋ฆฌ๊ธฐ) ํ•จ์ˆ˜๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ์—ญํ• ์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Processing์€ setup() (์ดˆ๊ธฐํ™”)๊ณผ draw() (๋งค ํ”„๋ ˆ์ž„ ์ƒํƒœ ๋ณ€๊ฒฝ ๋ฐ ๊ทธ๋ฆฌ๊ธฐ) ํ•จ์ˆ˜๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Nannou์˜ update์™€ view ๋กœ์ง์ด ๋ชจ๋‘ Processing์˜ draw() ํ•จ์ˆ˜ ์•ˆ์— ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

2. ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ : struct์™€ Vec โ†’ class์™€ ArrayList

  • Rust์˜ ๋ฐ์ดํ„ฐ ๋ฌถ์Œ์ธ struct Particle์€ Java์˜ ๊ฐ์ฒด์ง€ํ–ฅ class Particle๋กœ ๋ณ€ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • ํŒŒํ‹ฐํด ๋ชฉ๋ก์„ ์ €์žฅํ•˜๋˜ Rust์˜ ๋™์  ๋ฐฐ์—ด Vec<Particle>์€ Java์˜ ArrayList<Particle>๋กœ ๋Œ€์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ์ ์œผ๋กœ ๊ฑฐ์˜ ๋™์ผํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

3. ์ขŒํ‘œ๊ณ„ (Coordinate System)

  • Nannou๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์œˆ๋„์šฐ์˜ ์ค‘์•™์ด (0, 0) ์ž…๋‹ˆ๋‹ค.

  • Processing์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์œˆ๋„์šฐ์˜ ์ขŒ์ธก ์ƒ๋‹จ์ด (0, 0) ์ž…๋‹ˆ๋‹ค.

  • ์›๋ณธ๊ณผ ๋™์ผํ•œ ์‹œ๊ฐ์  ๊ฒฐ๊ณผ๋ฌผ์„ ์–ป๊ธฐ ์œ„ํ•ด Processing์˜ draw() ํ•จ์ˆ˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— translate(width / 2, height / 2);๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ขŒํ‘œ๊ณ„์˜ ์›์ ์„ ํ™”๋ฉด ์ค‘์•™์œผ๋กœ ์ด๋™์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

4. ๋ฒกํ„ฐ: Vec2 โ†’ PVector

  • Nannou์˜ 2D ๋ฒกํ„ฐ ํƒ€์ž…์ธ Vec2๋Š” Processing์˜ ๋‚ด์žฅ ๋ฒกํ„ฐ ํด๋ž˜์Šค์ธ PVector๋กœ ๋Œ€์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. PVector๋Š” ์œ„์น˜, ์†๋„ ๋“ฑ์„ ๋‹ค๋ฃจ๋Š” ๋ฐ ํ•„์š”ํ•œ ๋‹ค์–‘ํ•œ ๋ฉ”์†Œ๋“œ(add, mag, fromAngle ๋“ฑ)๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋งค์šฐ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

5. ๋…ธ์ด์ฆˆ (Noise)

  • Nannou์—์„œ๋Š” Perlin ๋…ธ์ด์ฆˆ ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, Processing์€ noise() ํ•จ์ˆ˜๋ฅผ ๋‚ด์žฅํ•˜๊ณ  ์žˆ์–ด ๋ณ„๋„์˜ ๊ฐ์ฒด ์ƒ์„ฑ ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์›๋ณธ ์ฝ”๋“œ์˜ 3์ฐจ์› ๋…ธ์ด์ฆˆ noise.get([x, y, time])๋Š” Processing์˜ noise(x, y, time)์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

6. ๋ Œ๋”๋ง๊ณผ ์ƒ‰์ƒ (Rendering & Color)

  • ๊ถค์  ํšจ๊ณผ: Nannou ์ฝ”๋“œ์—์„œ๋Š” ์ฒซ ํ”„๋ ˆ์ž„์—๋งŒ draw.background()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํŒŒํ‹ฐํด์˜ ๊ถค์ ์ด ๊ณ„์† ๋‚จ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. Processing์—์„œ๋„ background()๋ฅผ draw()๊ฐ€ ์•„๋‹Œ setup()์— ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜์—ฌ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ƒ‰์ƒ ํ‘œํ˜„: Nannou์˜ hsla(h, s, l, a) ์ƒ‰์ƒ๊ฐ’์€ 0.0์—์„œ 1.0 ์‚ฌ์ด์˜ ์‹ค์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์›๋ณธ์˜ hsla(0.0, 0.0, 1.0, 0.05)๋Š” '์ฑ„๋„ 0, ๋ฐ๊ธฐ 100%'์ด๋ฏ€๋กœ ํฐ์ƒ‰์ด๋ฉฐ, ์•ŒํŒŒ(ํˆฌ๋ช…๋„)๋Š” 5%์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ Processing์˜ stroke(gray, alpha)๋กœ ๋ณ€ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒ‰์ƒ์€ 255 (ํฐ์ƒ‰), ์•ŒํŒŒ๊ฐ’์€ 0.05 * 255, ์ฆ‰ ์•ฝ 13์ด ๋ฉ๋‹ˆ๋‹ค.


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

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