๐Ÿ”ฎ :: Constellation Bloom

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

Nannou <Generative Art>

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

SPACEBAR : ๊ฐœํ™”(Blooming) ์‹œ์ž‘
R : ๋ฆฌ์…‹ํ•ด์„œ ๋‹ค์‹œ ์‹œ์ž‘
S : ์Šคํฌ๋ฆฐ์ƒท ์ €์žฅ

๐Ÿ“ Rust Code

use nannou::prelude::*;

const WIDTH: f32 = 800.0;
const HEIGHT: f32 = 800.0;
const NUM_POINTS: usize = 20;

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

struct MovingPoint {
    pos: Vec2,
    vel: Vec2,
}

struct Model {
    points: Vec<MovingPoint>,
    width: f32,
    height: f32,
    running: bool,
    screenshot_count: u32, // ์Šคํฌ๋ฆฐ์ƒท ์นด์šดํ„ฐ
}

fn model(app: &App) -> Model {
    let _window_id = app
        .new_window()
        .size(WIDTH as u32, HEIGHT as u32)
        .title("Constellation Bloom")
        .view(view)
        .build()
        .unwrap();

    let points = (0..NUM_POINTS)
        .map(|_| MovingPoint {
            pos: vec2(0.0, 0.0),
            vel: vec2(0.0, 0.0),
        })
        .collect();

    Model {
        points,
        width: WIDTH,
        height: HEIGHT,
        running: false,
        screenshot_count: 0,
    }
}

fn update(_app: &App, model: &mut Model, update: Update) {
    if !model.running {
        return;
    }

    let dt = update.since_last.as_secs_f32();
    let half_w = model.width / 2.0;
    let half_h = model.height / 2.0;

    for mp in &mut model.points {
        mp.pos += mp.vel * dt;

        // X์ถ• ๋ฐ˜์‚ฌ
        if mp.pos.x > half_w {
            let overflow = mp.pos.x - half_w;
            mp.pos.x = half_w - overflow;
            mp.vel.x *= -1.0;
        } else if mp.pos.x < -half_w {
            let overflow = -half_w - mp.pos.x;
            mp.pos.x = -half_w + overflow;
            mp.vel.x *= -1.0;
        }

        // Y์ถ• ๋ฐ˜์‚ฌ
        if mp.pos.y > half_h {
            let overflow = mp.pos.y - half_h;
            mp.pos.y = half_h - overflow;
            mp.vel.y *= -1.0;
        } else if mp.pos.y < -half_h {
            let overflow = -half_h - mp.pos.y;
            mp.pos.y = -half_h + overflow;
            mp.vel.y *= -1.0;
        }
    }
}

fn event(app: &App, model: &mut Model, event: Event) {
    if let Event::WindowEvent { simple, .. } = event {
        if let Some(e) = simple {
            match e {
                KeyPressed(Key::Space) => {
                    for mp in &mut model.points {
                        mp.vel = vec2(
                            random_range(-150.0, 150.0),
                            random_range(-150.0, 150.0),
                        );
                    }
                    model.running = true;
                }
                KeyPressed(Key::R) => {
                    for mp in &mut model.points {
                        mp.pos = vec2(0.0, 0.0);
                        mp.vel = vec2(0.0, 0.0);
                    }
                    model.running = false;
                }
                KeyPressed(Key::S) => {
                    model.screenshot_count += 1;
                    let filename = format!("artwork_{:03}.png", model.screenshot_count);
                    app.main_window().capture_frame(filename);
                    println!("๐Ÿ“ธ Screenshot saved!");
                }
                _ => {}
            }
        }
    }
}

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

    let overlay = hsla(0.0, 0.0, 0.0, 0.04);
    draw.rect().w_h(model.width, model.height).color(overlay);

    let white = hsla(0.0, 0.0, 1.0, 1.0);

    for win in model.points.windows(2) {
        let p1 = win[0].pos;
        let p2 = win[1].pos;
        draw.line().start(p1).end(p2).stroke_weight(3.0).color(white);
    }

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


๐Ÿ“ Rust Code + Comment by Gemini

// nannou::prelude::* ๋Š” nannou ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•„์š”ํ•œ
// ๊ธฐ๋ณธ์ ์ธ ํ•จ์ˆ˜, ํƒ€์ž…, ๊ตฌ์กฐ์ฒด ๋“ฑ์„ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
// ์ด๊ฑธ ์‚ฌ์šฉํ•˜๋ฉด app, vec2, hsla ๋“ฑ nannou์˜ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
use nannou::prelude::*;

// --- ์ƒ์ˆ˜ ์ •์˜ ---
// ์ƒ์ˆ˜๋Š” ํ”„๋กœ๊ทธ๋žจ ์ „์ฒด์—์„œ ๋ณ€ํ•˜์ง€ ์•Š๋Š” ๊ฐ’์„ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// ์ƒ์„ฑ๋  ์ฐฝ(window)์˜ ๋„ˆ๋น„๋ฅผ 800.0 ํ”ฝ์…€๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
// f32๋Š” 32๋น„ํŠธ ๋ถ€๋™์†Œ์ˆ˜์  ์ˆซ์ž ํƒ€์ž…์œผ๋กœ, ์†Œ์ˆ˜์ ์„ ํฌํ•จํ•˜๋Š” ์ˆซ์ž๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
const WIDTH: f32 = 800.0;
// ์ƒ์„ฑ๋  ์ฐฝ์˜ ๋†’์ด๋ฅผ 800.0 ํ”ฝ์…€๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
const HEIGHT: f32 = 800.0;
// ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๊ณ  ์›€์ง์ผ ์ ์˜ ๊ฐœ์ˆ˜๋ฅผ 20๊ฐœ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
// usize๋Š” ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค๋‚˜ ํฌ๊ธฐ๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ถ€ํ˜ธ ์—†๋Š” ์ •์ˆ˜ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
const NUM_POINTS: usize = 20;

// --- main ํ•จ์ˆ˜ ---
// ๋ชจ๋“  Rust ํ”„๋กœ๊ทธ๋žจ์˜ ์‹œ์ž‘์ ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๋ฉด ์ด ํ•จ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋จผ์ € ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
fn main() {
    // nannou ์•ฑ์„ ์„ค์ •ํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    nannou::app(model) // 'model' ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์•ฑ์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        .update(update)    // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค 'update' ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์•ฑ์˜ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
        .event(event)      // ํ‚ค๋ณด๋“œ ์ž…๋ ฅ, ๋งˆ์šฐ์Šค ์›€์ง์ž„ ๋“ฑ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด 'event' ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
        .run();            // ์œ„์—์„œ ์„ค์ •ํ•œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์•ฑ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
}

// --- ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด ์ •์˜ ---
// struct(๊ตฌ์กฐ์ฒด)๋Š” ๊ด€๋ จ๋œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋ฌถ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.

// ์›€์ง์ด๋Š” ์  ํ•˜๋‚˜๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
struct MovingPoint {
    // pos๋Š” ์ ์˜ ํ˜„์žฌ ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. Vec2๋Š” 2์ฐจ์› ๋ฒกํ„ฐ(x, y ์ขŒํ‘œ)๋ฅผ ์ €์žฅํ•˜๋Š” ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
    pos: Vec2,
    // vel์€ ์ ์˜ ์†๋„(velocity)๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. x์ถ•๊ณผ y์ถ• ๋ฐฉํ–ฅ์œผ๋กœ ์–ผ๋งˆ๋‚˜ ๋น ๋ฅด๊ฒŒ ์›€์ง์ด๋Š”์ง€๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    vel: Vec2,
}

// ์ด ํ”„๋กœ๊ทธ๋žจ์˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฉ”์ธ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
// ์•ฑ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋Š” ์ด 'Model' ์•ˆ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
struct Model {
    // ํ™”๋ฉด์— ์žˆ๋Š” ๋ชจ๋“  ์ ๋“ค์„ ์ €์žฅํ•˜๋Š” ๋ฒกํ„ฐ(๋™์  ๋ฐฐ์—ด)์ž…๋‹ˆ๋‹ค.
    points: Vec<MovingPoint>,
    // ์ฐฝ์˜ ๋„ˆ๋น„๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    width: f32,
    // ์ฐฝ์˜ ๋†’์ด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    height: f32,
    // ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ์ง€(true) ์•„๋‹ˆ๋ฉด ๋ฉˆ์ถฐ์žˆ๋Š”์ง€(false) ์ƒํƒœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    running: bool,
    // ์Šคํฌ๋ฆฐ์ƒท์„ ์ €์žฅํ•  ๋•Œ ํŒŒ์ผ ์ด๋ฆ„์ด ๊ฒน์น˜์ง€ ์•Š๋„๋ก ์ˆซ์ž๋ฅผ ์„ธ๋Š” ์นด์šดํ„ฐ์ž…๋‹ˆ๋‹ค.
    screenshot_count: u32,
}

// --- nannou ํ•จ์ˆ˜๋“ค ---
// ์ด ํ•จ์ˆ˜๋“ค์€ nannou ํ”„๋ ˆ์ž„์›Œํฌ์— ์˜ํ•ด ํŠน์ • ์‹œ์ ์— ์ž๋™์œผ๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

// 1. model ํ•จ์ˆ˜: ์•ฑ์ด ์ฒ˜์Œ ์‹œ์ž‘๋  ๋•Œ ๋”ฑ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
// ์•ฑ์˜ ์ดˆ๊ธฐ ์ƒํƒœ(Model ๊ตฌ์กฐ์ฒด)๋ฅผ ์„ค์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn model(app: &App) -> Model {
    // ์•ฑ์˜ ๋ฉ”์ธ ์ฐฝ์„ ์ƒ์„ฑํ•˜๊ณ  ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    app.new_window()
        .size(WIDTH as u32, HEIGHT as u32) // ์ฐฝ ํฌ๊ธฐ๋ฅผ ์ƒ์ˆ˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. u32๋กœ ํƒ€์ž… ๋ณ€ํ™˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
        .title("Constellation Bloom")      // ์ฐฝ์˜ ์ œ๋ชฉ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        .view(view)                        // ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ์—ญํ• ์€ 'view' ํ•จ์ˆ˜๊ฐ€ ๋‹ด๋‹นํ•˜๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
        .build()                           // ์„ค์ •์„ ๋ฐ”ํƒ•์œผ๋กœ ์ฐฝ์„ ์‹ค์ œ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
        .unwrap();                         // ์ฐฝ ์ƒ์„ฑ์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ํ”„๋กœ๊ทธ๋žจ์„ ์ค‘๋‹จ์‹œํ‚ต๋‹ˆ๋‹ค.

    // NUM_POINTS ๊ฐœ์ˆ˜๋งŒํผ MovingPoint๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฒกํ„ฐ์— ๋‹ด์Šต๋‹ˆ๋‹ค.
    let points = (0..NUM_POINTS) // 0๋ถ€ํ„ฐ NUM_POINTS-1 ๊นŒ์ง€ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.
        .map(|_| {
            // ๊ฐ ์ ์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
            MovingPoint {
                pos: vec2(0.0, 0.0), // ์ดˆ๊ธฐ ์œ„์น˜๋Š” ํ™”๋ฉด ์ค‘์•™(0, 0)์ž…๋‹ˆ๋‹ค.
                vel: vec2(0.0, 0.0), // ์ดˆ๊ธฐ ์†๋„๋Š” 0์ด๋ฏ€๋กœ ์ฒ˜์Œ์—๋Š” ์›€์ง์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
            }
        })
        .collect(); // map์„ ํ†ตํ•ด ์ƒ์„ฑ๋œ ๋ชจ๋“  MovingPoint๋“ค์„ ๋ชจ์•„ Vec<MovingPoint> ๋ฒกํ„ฐ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

    // ์™„์„ฑ๋œ Model ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์•ฑ์˜ ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
    Model {
        points, // ์œ„์—์„œ ์ƒ์„ฑํ•œ ์ ๋“ค์˜ ๋ฒกํ„ฐ
        width: WIDTH,
        height: HEIGHT,
        running: false, // ์ฒ˜์Œ์—๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ฉˆ์ถฐ์žˆ๋„๋ก false๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        screenshot_count: 0, // ์Šคํฌ๋ฆฐ์ƒท ์นด์šดํ„ฐ ์ดˆ๊ธฐํ™”
    }
}

// 2. update ํ•จ์ˆ˜: ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. (๋ณดํ†ต 1์ดˆ์— 60๋ฒˆ)
// ์•ฑ์˜ ์ƒํƒœ(๋ฐ์ดํ„ฐ)๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: ์ ์˜ ์œ„์น˜ ์ด๋™)
fn update(_app: &App, model: &mut Model, update: Update) {
    // model.running์ด false์ด๋ฉด (์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ฉˆ์ถ˜ ์ƒํƒœ๋ผ๋ฉด)
    // ์•„๋ฌด๊ฒƒ๋„ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š๊ณ  ํ•จ์ˆ˜๋ฅผ ์ฆ‰์‹œ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
    if !model.running {
        return;
    }

    // ์ด์ „ ํ”„๋ ˆ์ž„๊ณผ ํ˜„์žฌ ํ”„๋ ˆ์ž„ ์‚ฌ์ด์˜ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ(์ดˆ ๋‹จ์œ„)์„ ๊ตฌํ•ฉ๋‹ˆ๋‹ค.
    // ์ด ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํ“จํ„ฐ ์„ฑ๋Šฅ(ํ”„๋ ˆ์ž„๋ฅ )์— ์ƒ๊ด€์—†์ด ์ผ์ •ํ•œ ์†๋„๋กœ ์›€์ง์ด๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    let dt = update.since_last.as_secs_f32();

    // ํ™”๋ฉด ๊ฒฝ๊ณ„ ๊ณ„์‚ฐ์„ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋„ˆ๋น„์™€ ๋†’์ด์˜ ์ ˆ๋ฐ˜ ๊ฐ’์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•ด ๋‘ก๋‹ˆ๋‹ค.
    // nannou์˜ ์ขŒํ‘œ๊ณ„๋Š” ์ค‘์•™์ด (0,0)์ด๋ฏ€๋กœ, ๊ฒฝ๊ณ„๋Š” -width/2 ~ +width/2 ์ž…๋‹ˆ๋‹ค.
    let half_w = model.width / 2.0;
    let half_h = model.height / 2.0;

    // model์— ์žˆ๋Š” ๋ชจ๋“  ์ ๋“ค์„ ์ˆœํšŒํ•˜๋ฉด์„œ ์œ„์น˜๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
    // '&mut model.points'๋Š” ์ ๋“ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ '์ˆ˜์ • ๊ฐ€๋Šฅํ•˜๋„๋ก' ๋นŒ๋ ค์˜ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
    for mp in &mut model.points {
        // ํ˜„์žฌ ์œ„์น˜(pos)์— ์†๋„(vel)์™€ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ(dt)์„ ๊ณฑํ•œ ๊ฐ’์„ ๋”ํ•ด ๋‹ค์Œ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        // (์ด๋™ ๊ฑฐ๋ฆฌ = ์†๋„ * ์‹œ๊ฐ„)
        mp.pos += mp.vel * dt;

        // --- ํ™”๋ฉด ๊ฒฝ๊ณ„ ์ถฉ๋Œ ์ฒ˜๋ฆฌ ---

        // X์ถ• ์˜ค๋ฅธ์ชฝ ๊ฒฝ๊ณ„์— ๋ถ€๋”ชํ˜”์„ ๋•Œ
        if mp.pos.x > half_w {
            let overflow = mp.pos.x - half_w;  // ๊ฒฝ๊ณ„๋ฅผ ์–ผ๋งˆ๋‚˜ ๋„˜์–ด๊ฐ”๋Š”์ง€ ๊ณ„์‚ฐ
            mp.pos.x = half_w - overflow;      // ๋„˜์–ด๊ฐ„ ๋งŒํผ ์•ˆ์ชฝ์œผ๋กœ ์œ„์น˜๋ฅผ ์กฐ์ •
            mp.vel.x *= -1.0;                  // x์ถ• ์ด๋™ ๋ฐฉํ–ฅ์„ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค (๋ฐ˜์‚ฌ).
        }
        // X์ถ• ์™ผ์ชฝ ๊ฒฝ๊ณ„์— ๋ถ€๋”ชํ˜”์„ ๋•Œ
        else if mp.pos.x < -half_w {
            let overflow = -half_w - mp.pos.x; // ๊ฒฝ๊ณ„๋ฅผ ์–ผ๋งˆ๋‚˜ ๋„˜์–ด๊ฐ”๋Š”์ง€ ๊ณ„์‚ฐ
            mp.pos.x = -half_w + overflow;     // ๋„˜์–ด๊ฐ„ ๋งŒํผ ์•ˆ์ชฝ์œผ๋กœ ์œ„์น˜๋ฅผ ์กฐ์ •
            mp.vel.x *= -1.0;                  // x์ถ• ์ด๋™ ๋ฐฉํ–ฅ์„ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
        }

        // Y์ถ• ์œ„์ชฝ ๊ฒฝ๊ณ„์— ๋ถ€๋”ชํ˜”์„ ๋•Œ
        if mp.pos.y > half_h {
            let overflow = mp.pos.y - half_h;  // ๊ฒฝ๊ณ„๋ฅผ ์–ผ๋งˆ๋‚˜ ๋„˜์–ด๊ฐ”๋Š”์ง€ ๊ณ„์‚ฐ
            mp.pos.y = half_h - overflow;      // ๋„˜์–ด๊ฐ„ ๋งŒํผ ์•ˆ์ชฝ์œผ๋กœ ์œ„์น˜๋ฅผ ์กฐ์ •
            mp.vel.y *= -1.0;                  // y์ถ• ์ด๋™ ๋ฐฉํ–ฅ์„ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
        }
        // Y์ถ• ์•„๋ž˜์ชฝ ๊ฒฝ๊ณ„์— ๋ถ€๋”ชํ˜”์„ ๋•Œ
        else if mp.pos.y < -half_h {
            let overflow = -half_h - mp.pos.y; // ๊ฒฝ๊ณ„๋ฅผ ์–ผ๋งˆ๋‚˜ ๋„˜์–ด๊ฐ”๋Š”์ง€ ๊ณ„์‚ฐ
            mp.pos.y = -half_h + overflow;     // ๋„˜์–ด๊ฐ„ ๋งŒํผ ์•ˆ์ชฝ์œผ๋กœ ์œ„์น˜๋ฅผ ์กฐ์ •
            mp.vel.y *= -1.0;                  // y์ถ• ์ด๋™ ๋ฐฉํ–ฅ์„ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.
        }
    }
}

// 3. event ํ•จ์ˆ˜: ํ‚ค๋ณด๋“œ, ๋งˆ์šฐ์Šค ๋“ฑ ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
// ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋”ฐ๋ผ ์•ฑ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn event(app: &App, model: &mut Model, event: Event) {
    // ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๊ฐ€ '์ฐฝ(Window)๊ณผ ๊ด€๋ จ๋œ ์ด๋ฒคํŠธ'์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    if let Event::WindowEvent { simple, .. } = event {
        // 'simple' ๋ณ€์ˆ˜์— ํ‚ค๋ณด๋“œ/๋งˆ์šฐ์Šค ์ž…๋ ฅ ๊ฐ™์€ ๋‹จ์ˆœํ™”๋œ ์ด๋ฒคํŠธ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        if let Some(e) = simple {
            // 'e'์— ๋‹ด๊ธด ์ด๋ฒคํŠธ์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
            match e {
                // ์ŠคํŽ˜์ด์Šค๋ฐ”๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ
                KeyPressed(Key::Space) => {
                    // ๋ชจ๋“  ์ ๋“ค์„ ์ˆœํšŒํ•˜๋ฉด์„œ
                    for mp in &mut model.points {
                        // ๊ฐ ์ ์˜ ์†๋„(vel)๋ฅผ x, y ๊ฐ๊ฐ -150 ~ 150 ์‚ฌ์ด์˜ ๋ฌด์ž‘์œ„ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
                        mp.vel = vec2(
                            random_range(-150.0, 150.0),
                            random_range(-150.0, 150.0),
                        );
                    }
                    // ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹œ์ž‘ํ•˜๋„๋ก running ์ƒํƒœ๋ฅผ true๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
                    model.running = true;
                }
                // 'R' ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ (Reset)
                KeyPressed(Key::R) => {
                    // ๋ชจ๋“  ์ ๋“ค์„ ์ˆœํšŒํ•˜๋ฉด์„œ
                    for mp in &mut model.points {
                        // ์œ„์น˜๋ฅผ ๋‹ค์‹œ ํ™”๋ฉด ์ค‘์•™(0,0)์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
                        mp.pos = vec2(0.0, 0.0);
                        // ์†๋„๋ฅผ 0์œผ๋กœ ๋งŒ๋“ค์–ด ์›€์ง์ž„์„ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.
                        mp.vel = vec2(0.0, 0.0);
                    }
                    // ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋ฉˆ์ถ”๋„๋ก running ์ƒํƒœ๋ฅผ false๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
                    model.running = false;
                }
                // 'S' ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ (Screenshot)
                KeyPressed(Key::S) => {
                    // ์Šคํฌ๋ฆฐ์ƒท ์นด์šดํ„ฐ๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
                    model.screenshot_count += 1;
                    // "artwork_001.png", "artwork_002.png" ์™€ ๊ฐ™์€ ํ˜•์‹์˜ ํŒŒ์ผ ์ด๋ฆ„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
                    let filename = format!("artwork_{:03}.png", model.screenshot_count);
                    // ํ˜„์žฌ ์ฐฝ์˜ ํ™”๋ฉด์„ ์บก์ฒ˜ํ•˜์—ฌ ํ•ด๋‹น ํŒŒ์ผ ์ด๋ฆ„์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
                    app.main_window().capture_frame(filename);
                    // ์ €์žฅ ์™„๋ฃŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
                    println!("๐Ÿ“ธ Screenshot saved!");
                }
                // ๊ทธ ์™ธ ๋‹ค๋ฅธ ํ‚ค๊ฐ€ ๋ˆŒ๋ ธ์„ ๋•Œ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
                _ => {}
            }
        }
    }
}

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

    // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋ฐ˜ํˆฌ๋ช…ํ•œ ๊ฒ€์€์ƒ‰ ์‚ฌ๊ฐํ˜•์„ ํ™”๋ฉด ์ „์ฒด์— ๋ฎ์–ด์”Œ์›๋‹ˆ๋‹ค.
    // alpha(ํˆฌ๋ช…๋„) ๊ฐ’์ด 0.04๋กœ ๋งค์šฐ ๋‚ฎ๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ „ ํ”„๋ ˆ์ž„์˜ ์ž”์ƒ์ด ํฌ๋ฏธํ•˜๊ฒŒ ๋‚จ๋Š” ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.
    let overlay = hsla(0.0, 0.0, 0.0, 0.04); // ์ƒ‰์ƒ(H), ์ฑ„๋„(S), ๋ช…๋„(L), ํˆฌ๋ช…๋„(A)
    draw.rect() // ์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
        .w_h(model.width, model.height) // ์‚ฌ๊ฐํ˜•์˜ ๋„ˆ๋น„์™€ ๋†’์ด๋ฅผ ์ฐฝ ํฌ๊ธฐ์™€ ๊ฐ™๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
        .color(overlay); // ์œ„์—์„œ ์ •์˜ํ•œ ๋ฐ˜ํˆฌ๋ช… ๊ฒ€์€์ƒ‰์„ ์น ํ•ฉ๋‹ˆ๋‹ค.

    // ์„ ์„ ๊ทธ๋ฆด ๋•Œ ์‚ฌ์šฉํ•  ํฐ์ƒ‰์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
    let white = hsla(0.0, 0.0, 1.0, 1.0); // ๋ช…๋„๊ฐ€ 1.0์ด๋ฉด ํฐ์ƒ‰, ํˆฌ๋ช…๋„๋Š” 1.0(๋ถˆํˆฌ๋ช…)

    // 'windows(2)'๋Š” ์  ๋ชฉ๋ก์—์„œ ์—ฐ์†๋œ ๋‘ ๊ฐœ์˜ ์ ์„ ๋ฌถ์–ด์„œ ์ˆœํšŒํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
    // ์˜ˆ๋ฅผ ๋“ค์–ด ์ ์ด [p1, p2, p3, p4] ๋ผ๋ฉด, [p1,p2], [p2,p3], [p3,p4] ์ˆœ์„œ๋กœ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.
    // ์ด๋ฅผ ํ†ตํ•ด ๊ฐ ์ ๊ณผ ๊ทธ ๋‹ค์Œ ์ ์„ ์„ ์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    for win in model.points.windows(2) {
        let p1 = win[0].pos; // ์ฒซ ๋ฒˆ์งธ ์ ์˜ ์œ„์น˜
        let p2 = win[1].pos; // ๋‘ ๋ฒˆ์งธ ์ ์˜ ์œ„์น˜
        draw.line() // ์„ ์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
            .start(p1) // ์„ ์˜ ์‹œ์ž‘์ ์„ p1์œผ๋กœ ์„ค์ •
            .end(p2)   // ์„ ์˜ ๋์ ์„ p2๋กœ ์„ค์ •
            .stroke_weight(3.0) // ์„ ์˜ ๋‘๊ป˜๋ฅผ 3.0 ํ”ฝ์…€๋กœ ์„ค์ •
            .color(white);      // ์„ ์˜ ์ƒ‰์ƒ์„ ํฐ์ƒ‰์œผ๋กœ ์„ค์ •
    }

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

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

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