๐ŸŽ›๏ธ :: Model ๊ตฌ์กฐํ™”: ๊ธฐ๋ณธ impl ๋ฐฉ์‹ ๋น„๊ต

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

Nannou <BASIC>

๋ชฉ๋ก ๋ณด๊ธฐ
9/41
post-thumbnail

Nannou์—์„œ๋Š” Model์„ ์ค‘์‹ฌ์œผ๋กœ ์•ฑ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • ๊ธฐ๋ณธ์ ์œผ๋กœ struct Model๋กœ ํ•„๋“œ๋งŒ ์ •๋ฆฌํ•˜๊ณ , model, update, view ํ•จ์ˆ˜์—์„œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • modelํ•จ์ˆ˜์—์„œ Model ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  update, event, view ํ•จ์ˆ˜๊ฐ€ ์ด Model์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์ฝ์–ด์„œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด impl Model๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ƒํƒœ์™€ ๋™์ž‘์„ ์บก์Аํ™”ํ•˜์—ฌ OOP ์Šคํƒ€์ผ์„ ๋„์ž…ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Model ๋‚ด๋ถ€์— ๋™์ž‘(๋ฉ”์„œ๋“œ)๋ฅผ ์บก์Аํ™”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์žฌ์‚ฌ์šฉ์„ฑ์ด ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • update/view์—์„œ๋Š” ์—ฌ์ „ํžˆ &mut Model ๋˜๋Š” &Model์„ ๋ฐ›์•„ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ ์ฐจ์ด๋Š” ๊ฑฐ์˜ ์—†์œผ๋ฏ€๋กœ (๋™์ผํ•œ ๋ฐ”์ดํŠธ ์ฝ”๋“œ๋กœ ์ปดํŒŒ์ผ ๋จ) ์„ค๊ณ„ ๋ฐฉ์‹์— ๋”ฐ๋ผ ์„ ํƒํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

1. struct & free functions (์ „ํ†ต์ ์ธ nannou ์Šคํƒ€์ผ)

Model์„ ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ์ปจํ…Œ์ด๋„ˆ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

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

๋‹ค๋งŒ ์ƒํƒœ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋กœ์ง์ด ๋ถ„์‚ฐ๋˜์–ด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ง€๊ณ , ๋™์ผํ•œ ์ƒํƒœ์— ๋Œ€ํ•œ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ ๊ตฌ์กฐ์ฒด ๋‚ด๋ถ€์˜ ๊ตฌํ˜„์„ ๋ฐ–์—์„œ ์‰ฝ๊ฒŒ ๋‹ค๋ฃจ๊ฒŒ ๋˜๋ฏ€๋กœ ์บก์Аํ™”๊ฐ€ ์•ฝํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ struct + free functions ์˜ˆ์‹œ 1

use nannou::prelude::*;

struct Model {
    time: f32,
}

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

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

    Model { time: 0.0 }
}

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

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

    let radius = (model.time.sin() * 50.0 + 100.0) as f32;

    draw.ellipse().radius(radius).color(BLACK);

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

๐Ÿ“ struct + free functions ์˜ˆ์‹œ 2

use nannou::prelude::*;

struct Model {
    positions: Vec<Vec2>,
    speeds: Vec<Vec2>,
    count: usize,
}

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

fn model(app: &App) -> Model {
    let _window = app.new_window().size(800, 800).view(view).build().unwrap();
    let count = 200;
    let mut positions = Vec::with_capacity(count);
    let mut speeds = Vec::with_capacity(count);
    for _ in 0..count {
        positions.push(vec2(
            random_range(-400.0, 400.0),
            random_range(-400.0, 400.0),
        ));
        speeds.push(vec2(
            random_range(-1.0, 1.0),
            random_range(-1.0, 1.0),
        ));
    }
    Model { positions, speeds, count }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    for i in 0..model.count {
        model.positions[i] += model.speeds[i];
        // ํ™”๋ฉด ๋ฐ–์ด๋ฉด ๋ฐ˜์‚ฌ
        if model.positions[i].x.abs() > 400.0 {
            model.speeds[i].x *= -1.0;
        }
        if model.positions[i].y.abs() > 400.0 {
            model.speeds[i].y *= -1.0;
        }
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    if frame.nth() == 0 {
        draw.background().color(BLACK);
    } else {
        draw.rect()
            .x_y(0.0, 0.0)
            .w_h(800.0, 800.0)
            .rgba(0.0, 0.0, 0.0, 0.06);
    }
    for &pos in &model.positions {
        draw.ellipse().xy(pos).w_h(6.0, 6.0).rgba(1.0, 0.6, 0.2, 0.9);
    }
    draw.to_frame(app, &frame).unwrap();
}

๐Ÿ“ struct + free functions ์˜ˆ์‹œ 3

use nannou::prelude::*;

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

// 1. Model ๊ตฌ์กฐ์ฒด: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ '์ƒํƒœ' (๋ฐ์ดํ„ฐ)๋งŒ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
struct Model {
    x_position: f32,
    speed: f32,
}

// 2. model ํ•จ์ˆ˜: Model์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
fn model(app: &App) -> Model {
    app.new_window().view(view).build().unwrap();
    Model {
        x_position: 0.0,
        speed: 2.0,
    }
}

// 3. update ํ•จ์ˆ˜: 'Model'์„ ์ง์ ‘ ์ˆ˜์ •ํ•˜์—ฌ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
// ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๋ฉด ์ด ํ•จ์ˆ˜๊ฐ€ ๋งค์šฐ ๊ธธ์–ด์ง‘๋‹ˆ๋‹ค.
fn update(app: &App, model: &mut Model, _update: Update) {
    let win = app.window_rect();

    // x ์œ„์น˜ ๋ณ€๊ฒฝ ๋กœ์ง
    model.x_position += model.speed;

    // ๋ฒฝ์— ๋‹ฟ์œผ๋ฉด ๋ฐฉํ–ฅ ์ „ํ™˜
    if model.x_position > win.right() || model.x_position < win.left() {
        model.speed *= -1.0;
    }
}

// 4. view ํ•จ์ˆ˜: 'Model'์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ํ™”๋ฉด์— ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(PLUM);

    // model์˜ x_position ๊ฐ’์„ ์ง์ ‘ ์ฝ์–ด์„œ ์‚ฌ์šฉ
    draw.ellipse()
        .x_y(model.x_position, 0.0)
        .radius(50.0)
        .color(STEELBLUE);

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

2. impl Model (๋ฉ”์„œ๋“œ ํฌํ•จ โ€” OOP ์Šคํƒ€์ผ)

Model์— ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด ๋กœ์ง์„ ์บก์Аํ™”ํ•ฉ๋‹ˆ๋‹ค.

Model ๊ตฌ์กฐ์ฒด์— ๋ฐ์ดํ„ฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ impl Model { ... } ๋ธ”๋ก์„ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๋ฉ”์„œ๋“œ(ํ–‰์œ„)๋ฅผ ํ•จ๊ป˜ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
update์™€ view ๊ฐ™์€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์—ญํ• ๋งŒ ๋‹ด๋‹นํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
์ƒํƒœ์™€ ๋™์ž‘์ด ๊ฐ์ฒด ์•ˆ์— ๋ชจ์—ฌ์„œ OOP-like ๊ตฌ์กฐ(์˜ˆ: ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง์„ Modle::animate ๋ฉ”์„œ๋“œ๋กœ ์ด๋™)๋ฅผ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์ƒํƒœ์™€ ๋™์ž‘์„ ํ•œ ๊ณณ์— ๋ฌถ์–ด ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.
๋‚ด๋ถ€์˜ ๋ถˆ๋ณ€/๊ฐ€๋ณ€ ๊ทœ์น™(&self, &mut self)์ด ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.
๋ณต์žกํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด๋‚˜ ์‹คํ–‰ ๋กœ์ง์„ ๋ฉ”์„œ๋“œ๋กœ ์ถ”์ƒํ™”ํ•ด์„œ ๋‚˜์ค‘์— ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ impl Model ์Šคํƒ€์ผ ์˜ˆ์‹œ 1

use nannou::prelude::*;

struct Model {
    time: f32,
}

impl Model {
    fn new() -> Self {
        Self { time: 0.0 }
    }

    fn animate(&mut self, delta: f32) {
        self.time += delta;
    }

    fn draw_circle(&self, draw: &Draw) {
        // ๋ฐฐ๊ฒฝ ์ง€์šฐ๊ธฐ
        draw.background().color(WHITE);

        // time์„ ์ด์šฉํ•ด ๋ฐ˜์ง€๋ฆ„ ์ง„๋™
        let radius = (self.time.sin() * 50.0 + 100.0) as f32;

        // ์ค‘์‹ฌ ์› ๊ทธ๋ฆฌ๊ธฐ
        draw.ellipse().radius(radius).color(BLACK);
    }
}

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

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

    Model::new()
}

fn update(_app: &App, model: &mut Model, update: Update) {
    let delta = update.since_last.as_secs_f32();
    model.animate(delta);
}

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

    // ๋ชจ๋ธ์— ์ •์˜๋œ ๊ทธ๋ฆฌ๊ธฐ ๋กœ์ง ํ˜ธ์ถœ
    model.draw_circle(&draw);

    // ํ”„๋ ˆ์ž„ ์ œ์ถœ
    draw.to_frame(app, &frame).unwrap();
}

๐Ÿ“ impl Model ์Šคํƒ€์ผ ์˜ˆ์‹œ 2

use nannou::prelude::*;

struct Model {
    positions: Vec<Vec2>,
    speeds: Vec<Vec2>,
    count: usize,
}

impl Model {
    // ์ƒ์„ฑ์ž ์—ญํ• 
    fn new(win_w: f32, win_h: f32, count: usize) -> Self {
        let mut positions = Vec::with_capacity(count);
        let mut speeds = Vec::with_capacity(count);
        for _ in 0..count {
            positions.push(vec2(
                random_range(-win_w * 0.5, win_w * 0.5),
                random_range(-win_h * 0.5, win_h * 0.5),
            ));
            speeds.push(vec2(
                random_range(-1.0, 1.0),
                random_range(-1.0, 1.0),
            ));
        }
        Model { positions, speeds, count }
    }

    // ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ์บก์Аํ™”
    fn step(&mut self, win_w: f32, win_h: f32) {
        for i in 0..self.count {
            self.positions[i] += self.speeds[i];
            if self.positions[i].x.abs() > win_w * 0.5 {
                self.speeds[i].x *= -1.0;
            }
            if self.positions[i].y.abs() > win_h * 0.5 {
                self.speeds[i].y *= -1.0;
            }
        }
    }

    // ๋ Œ๋”๋ง ๋„์šฐ๋ฏธ (view ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ)
    fn draw(&self, draw: &Draw) {
        for &pos in &self.positions {
            draw.ellipse().xy(pos).w_h(6.0, 6.0).rgba(0.2, 0.9, 1.0, 0.9);
        }
    }
}

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

fn model(app: &App) -> Model {
    let _window = app.new_window().size(800, 800).view(view).build().unwrap();
    Model::new(800.0, 800.0, 200)
}

fn update(app: &App, model: &mut Model, _update: Update) {
    let [w, h] = {
        let rect = app.main_window().rect();
        [rect.w(), rect.h()]
    };
    model.step(w, h); // ๋ฉ”์„œ๋“œ๋กœ ์บก์Аํ™”๋œ ์—…๋ฐ์ดํŠธ ํ˜ธ์ถœ
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    if frame.nth() == 0 {
        draw.background().color(BLACK);
    } else {
        draw.rect()
            .w_h(800.0, 800.0)
            .rgba(0.0, 0.0, 0.0, 0.06);
    }
    model.draw(&draw); // ์บก์Аํ™”๋œ ๋ Œ๋” ํ˜ธ์ถœ
    draw.to_frame(app, &frame).unwrap();
}

๐Ÿ“ impl Model ์Šคํƒ€์ผ ์˜ˆ์‹œ 3

use nannou::prelude::*;

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

// 1. Model ๊ตฌ์กฐ์ฒด: ๋ฐ์ดํ„ฐ(์ƒํƒœ) ์ •์˜
struct Model {
    x_position: f32,
    speed: f32,
}

// 2. impl Model: Model๊ณผ ๊ด€๋ จ๋œ 'ํ–‰์œ„'(๋ฉ”์„œ๋“œ)๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
// ์ด๊ฒƒ์ด OOP(๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ)์˜ ์บก์Аํ™”์™€ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.
impl Model {
    // 'model' ํ•จ์ˆ˜์˜ ์—ญํ• ์„ ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ๋Œ€์‹ ํ•ฉ๋‹ˆ๋‹ค. (์ƒ์„ฑ์ž ์—ญํ• )
    fn new(app: &App) -> Model {
        app.new_window().view(view).build().unwrap();
        Model {
            x_position: 0.0,
            speed: 2.0,
        }
    }

    // 'update' ํ•จ์ˆ˜์˜ ๋กœ์ง์„ ์ด ๋ฉ”์„œ๋“œ๋กœ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค.
    fn update_position(&mut self, app: &App) {
        let win = app.window_rect();
        self.x_position += self.speed;

        if self.x_position > win.right() || self.x_position < win.left() {
            self.speed *= -1.0;
        }
    }

    // 'view' ํ•จ์ˆ˜์˜ ๋กœ์ง์„ ์ด ๋ฉ”์„œ๋“œ๋กœ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค.
    fn draw_view(&self, app: &App, frame: Frame) {
        let draw = app.draw();
        draw.background().color(PLUM);

        draw.ellipse()
            .x_y(self.x_position, 0.0)
            .radius(50.0)
            .color(STEELBLUE);

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

// 3. Nannou ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋“ค: ์ด์ œ ๋งค์šฐ ๋‹จ์ˆœํ•ด์ง‘๋‹ˆ๋‹ค.
//    ๋‹จ์ˆœํžˆ Model์˜ ์ ์ ˆํ•œ ๋ฉ”์„œ๋“œ๋ฅผ 'ํ˜ธ์ถœ'๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

fn model(app: &App) -> Model {
    Model::new(app) // Model์˜ 'new' ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
}

fn update(app: &App, model: &mut Model, _update: Update) {
    model.update_position(app); // Model์˜ 'update_position' ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
}

fn view(app: &App, model: &Model, frame: Frame) {
    model.draw_view(app, frame); // Model์˜ 'draw_view' ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
}

โœ… โš™๏ธ๐Ÿ› ๏ธ๐Ÿ“„๐Ÿ“š๐ŸŽจ๐Ÿ–ผ๏ธ๐Ÿงฉ๐Ÿง ๐Ÿงฎ๐Ÿ•น๏ธ๐Ÿงฟโณ๐ŸŽ›๏ธ๐Ÿ”ฎ๐ŸŒŒ๐Ÿชฉ๐Ÿ—‚๏ธ๐Ÿ“๐Ÿงฐ๐Ÿ”’

์•„๋ณด์นด๋„
Sample
Important
Highlighting
๋น„์ƒ‰
์ ํ™ฉ์ƒ‰
purple
hot pink
yellow
์‚ด๊ตฌ์ƒ‰
orange
green
blue

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

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