๐Ÿ”ฎ :: Monotone Dots based on a image

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

// nannou ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๊ฑฐ์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํฌํ•จํ•˜๋Š” prelude๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// ์•ฑ ์ƒ์„ฑ, ๊ทธ๋ฆฌ๊ธฐ, ์ƒ‰์ƒ ๋“ฑ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์— ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
use nannou::prelude::*;
// image ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ open ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์™€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
use image::open;

// --- ์ƒ์ˆ˜ ์ •์˜ ---
// ์ด๋ฏธ์ง€๋ฅผ ์ƒ˜ํ”Œ๋งํ•  ๊ฒฉ์ž์˜ ํฌ๊ธฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. 100x100 ๊ฒฉ์ž๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
const GRID_SIZE: usize = 100;
// ์บ”๋ฒ„์Šค(์ฐฝ)์˜ ๊ฐ€๋กœ/์„ธ๋กœ ํฌ๊ธฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
const CANVAS_SIZE: f32 = 1080.0;

// --- ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜ ---
// ์•ฑ์˜ ์ƒํƒœ(state)๋ฅผ ์ €์žฅํ•  ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
// ์ด ์˜ˆ์ œ์—์„œ๋Š” ์ด๋ฏธ์ง€ ๊ฐ ํ”ฝ์…€์˜ ๋ฐ๊ธฐ ๊ฐ’์„ 0.0 ~ 1.0 ์‚ฌ์ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
struct Model {
    brightness_grid: Vec<f32>,
}

// --- ์ฃผ ์‹คํ–‰ ํ•จ์ˆ˜ ---
// ํ”„๋กœ๊ทธ๋žจ์˜ ์‹œ์ž‘์ (entry point)์ž…๋‹ˆ๋‹ค.
fn main() {
    // nannou ์•ฑ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  model ํ•จ์ˆ˜๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    // .update()๋Š” ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋  ๋กœ์ง์„, .run()์€ ์•ฑ์„ ์‹คํ–‰์‹œํ‚ต๋‹ˆ๋‹ค.
    nannou::app(model).update(update).run();
}

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

    // 2. ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ • (์ด์‹ ๊ฐ€๋Šฅํ•œ ๋ฐฉ์‹)
    // app.assets_path()๋Š” 'assets' ํด๋”์˜ ๊ฒฝ๋กœ๋ฅผ ๋™์ ์œผ๋กœ ์ฐพ์•„์ค๋‹ˆ๋‹ค.
    // ์ด๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์ปดํ“จํ„ฐ์—์„œ๋„ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋งŒ ๊ฐ™๋‹ค๋ฉด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ • ์—†์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    let assets = app.assets_path().expect("assets ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
    let img_path = assets.join("input.jpg"); // 'assets/input.jpg' ๊ฒฝ๋กœ ์ƒ์„ฑ

    // 3. ์ด๋ฏธ์ง€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๋ฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    let img = match open(&img_path) {
        Ok(img) => img, // ์„ฑ๊ณต ์‹œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜
        Err(e) => {
            // ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ„ฐ๋ฏธ๋„์— ์ถœ๋ ฅํ•˜๊ณ  ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
            eprintln!("์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค: {:?}. ๊ฒฝ๋กœ: {:?}", e, img_path);
            std::process::exit(1);
        }
    };

    // 4. ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ ๋ฐ ํ”ฝ์…€ ๋ฐ์ดํ„ฐ ์ถ”์ถœ
    // ์ด๋ฏธ์ง€๋ฅผ GRID_SIZE x GRID_SIZE ํฌ๊ธฐ๋กœ ๋ฆฌ์‚ฌ์ด์ฆˆํ•ฉ๋‹ˆ๋‹ค.
    // Lanczos3 ํ•„ํ„ฐ๋Š” ํ’ˆ์งˆ ์ €ํ•˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ณ ํ’ˆ์งˆ ๋ฆฌ์ƒ˜ํ”Œ๋ง ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค.
    // to_rgba8()์„ ํ†ตํ•ด ๊ฐ ํ”ฝ์…€์„ R, G, B, Alpha(ํˆฌ๋ช…๋„) ์ฑ„๋„๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    let resized = img
        .resize_to_fill(
            GRID_SIZE as u32,
            GRID_SIZE as u32,
            image::imageops::FilterType::Lanczos3,
        )
        .to_rgba8();

    // 5. ๋ฐ๊ธฐ ๊ฐ’ ๊ณ„์‚ฐ ๋ฐ ์ €์žฅ
    // GRID_SIZE * GRID_SIZE ๋งŒํผ์˜ ๊ณต๊ฐ„์„ ๋ฏธ๋ฆฌ ํ• ๋‹นํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.
    let mut brightness_grid = Vec::with_capacity(GRID_SIZE * GRID_SIZE);
    
    // ๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ด๋ฏธ์ง€์˜ ๋ชจ๋“  ํ”ฝ์…€์„ ์ˆœํšŒํ•ฉ๋‹ˆ๋‹ค.
    for pixel in resized.pixels() {
        // ๊ฐ ํ”ฝ์…€์˜ R, G, B ๊ฐ’์„ f32 ํƒ€์ž…์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
        let r = pixel[0] as f32;
        let g = pixel[1] as f32;
        let b = pixel[2] as f32;
        
        // ๊ฐ„๋‹จํ•œ ํ‰๊ท ๋ฒ•์œผ๋กœ ํ‘๋ฐฑ(grayscale) ๋ฐ๊ธฐ ๊ฐ’์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. (R+G+B)/3
        // 255.0์œผ๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ’์„ 0.0 ~ 1.0 ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”(normalize)ํ•ฉ๋‹ˆ๋‹ค.
        let gray = (r + g + b) / 3.0 / 255.0;
        brightness_grid.push(gray);
    }

    // ๊ณ„์‚ฐ๋œ ๋ฐ๊ธฐ ๊ฐ’๋“ค์ด ๋‹ด๊ธด grid๋ฅผ Model ๊ตฌ์กฐ์ฒด์— ๋‹ด์•„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    Model { brightness_grid }
}

// --- ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ ---
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค view ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ง์ „์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
// ๋ชจ๋ธ์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง์„ ๋„ฃ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
// ์ด ์˜ˆ์ œ๋Š” ์ •์ ์ด๋ฏ€๋กœ ์•„๋ฌด๋Ÿฐ ์ž‘์—…์„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
fn update(_app: &App, _model: &mut Model, _update: Update) {}

// --- ๋ทฐ(๊ทธ๋ฆฌ๊ธฐ) ํ•จ์ˆ˜ ---
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ™”๋ฉด์— ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn view(app: &App, model: &Model, frame: Frame) {
    // 'draw' ์ธ์Šคํ„ด์Šค๋Š” ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
    let draw = app.draw();

    // ๋ฐฐ๊ฒฝ์„ ๊ฒ€์€์ƒ‰์œผ๋กœ ์น ํ•ฉ๋‹ˆ๋‹ค.
    draw.background().color(BLACK);

    // ๊ทธ๋ฆฌ๋“œ์˜ ํ•œ ์นธ(cell)์˜ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
    let cell_size = CANVAS_SIZE / GRID_SIZE as f32;
    // ์›์ด ๊ทธ๋ ค์งˆ ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. (์…€ ํฌ๊ธฐ์˜ ์ ˆ๋ฐ˜)
    let max_radius = cell_size / 2.0;

    // ์ €์žฅ๋œ ๋ชจ๋“  ๋ฐ๊ธฐ ๊ฐ’์„ ์ˆœํšŒํ•˜๋ฉด์„œ ์ธ๋ฑ์Šค(i)์™€ ๋ฐ๊ธฐ ๊ฐ’(brightness)์„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    for (i, &brightness) in model.brightness_grid.iter().enumerate() {
        // 1์ฐจ์› ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค(i)๋ฅผ 2์ฐจ์› ๊ทธ๋ฆฌ๋“œ์˜ ํ–‰(row)๊ณผ ์—ด(col)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        let row = i / GRID_SIZE;
        let col = i % GRID_SIZE;

        // nannou์˜ ์ขŒํ‘œ๊ณ„(-CANVAS_SIZE/2 ~ +CANVAS_SIZE/2)์— ๋งž์ถฐ ์›์˜ ์ค‘์‹ฌ (x, y)๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        let x = col as f32 * cell_size + cell_size / 2.0 - CANVAS_SIZE / 2.0;
        let y = -(row as f32 * cell_size + cell_size / 2.0 - CANVAS_SIZE / 2.0); // y ์ขŒํ‘œ๋ฅผ ๋’ค์ง‘์–ด ์ด๋ฏธ์ง€์˜ ์œ„์ชฝ์ด ์บ”๋ฒ„์Šค ์œ„์ชฝ๊ณผ ๋งž๊ฒŒ ํ•จ

        // ์ด๋ฏธ์ง€์˜ ๋ฐ๊ธฐ(brightness)์— ๋”ฐ๋ผ ์›์˜ ๋ฐ˜์ง€๋ฆ„(radius)์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
        // ๋ฐ์„์ˆ˜๋ก(1.0) ๋ฐ˜์ง€๋ฆ„์ด ์ปค์ง€๊ณ , ์–ด๋‘์šธ์ˆ˜๋ก(0.0) ์ž‘์•„์ง‘๋‹ˆ๋‹ค.
        let radius = brightness * max_radius;

        // ๊ณ„์‚ฐ๋œ ์œ„์น˜์™€ ๋ฐ˜์ง€๋ฆ„์œผ๋กœ ํฐ์ƒ‰ ์›์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
        draw.ellipse()
            .x_y(x, y)
            .radius(radius)
            .color(WHITE);
    }

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

visualizing the pulsating radii

์ค‘์•™์—์„œ๋ถ€ํ„ฐ ๋ฌผ๊ฒฐ์ด ํผ์ ธ๋‚˜๊ฐ€๋Š” ๋“ฏํ•œ ํŒจํ„ด์„ ๋งŒ๋“ค๋ ค๋ฉด ์‹œ๊ฐ„๊ณผ ๊ฐ ์ ์˜ ์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์‹œ๊ฐ„ (time): update ํ•จ์ˆ˜์—์„œ ๊ณ„์† ์ฆ๊ฐ€ํ•˜๋Š” ๋ณ€์ˆ˜๋ฅผ ๋‘์–ด ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ผ์Šต๋‹ˆ๋‹ค.

  • ๊ฑฐ๋ฆฌ (dist): ๊ฐ ์ ์ด ์บ”๋ฒ„์Šค ์ค‘์•™(0, 0)์—์„œ ์–ผ๋งˆ๋‚˜ ๋–จ์–ด์ ธ ์žˆ๋Š”์ง€ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

  • ์‚ฌ์ธ ํ•จ์ˆ˜ (sin)์˜ ํ™œ์šฉ
    sin(์‹œ๊ฐ„ - ๊ฑฐ๋ฆฌ) ์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์‚ฌ์ธ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ์‹œ๊ฐ„์— ๋”ฐ๋ผ ์ง„๋™ํ•˜๋ฉด์„œ๋„ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์œ„์ƒ(phase)์ด ๋‹ฌ๋ผ์ง€๋Š” ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๋ฌผ๊ฒฐ ํŒจํ„ด์˜ ์›๋ฆฌ์ž…๋‹ˆ๋‹ค.

๐Ÿ“ Rust Code @Animation

use nannou::prelude::*;
use image::open;

// --- ์ƒ์ˆ˜ ์ •์˜ ---
const GRID_SIZE: usize = 100;
const CANVAS_SIZE: f32 = 1080.0;

// --- ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜ ---
// ์•ฑ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•  ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.
// ์ด๋ฏธ์ง€ ๋ฐ๊ธฐ ๊ฐ’๊ณผ ํ•จ๊ป˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•œ 'time' ๋ณ€์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
struct Model {
    brightness_grid: Vec<f32>,
    time: f32, // ์‹œ๊ฐ„์˜ ํ๋ฆ„์„ ์ €์žฅํ•  ๋ณ€์ˆ˜
}

// --- ์ฃผ ์‹คํ–‰ ํ•จ์ˆ˜ ---
fn main() {
    nannou::app(model).update(update).run();
}

// --- ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ---
fn model(app: &App) -> Model {
    app.new_window()
        .size(CANVAS_SIZE as u32, CANVAS_SIZE as u32)
        .view(view)
        .build()
        .unwrap();

    let assets = app.assets_path().expect("assets ํด๋”๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
    let img_path = assets.join("input.jpg");

    let img = match open(&img_path) {
        Ok(img) => img,
        Err(e) => {
            eprintln!("์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค: {:?}. ๊ฒฝ๋กœ: {:?}", e, img_path);
            std::process::exit(1);
        }
    };

    let resized = img
        .resize_to_fill(
            GRID_SIZE as u32,
            GRID_SIZE as u32,
            image::imageops::FilterType::Lanczos3,
        )
        .to_rgba8();

    let mut brightness_grid = Vec::with_capacity(GRID_SIZE * GRID_SIZE);
    for pixel in resized.pixels() {
        let r = pixel[0] as f32;
        let g = pixel[1] as f32;
        let b = pixel[2] as f32;
        let gray = (r + g + b) / 3.0 / 255.0;
        brightness_grid.push(gray);
    }

    // Model์„ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ time ๊ฐ’์„ 0.0์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    Model {
        brightness_grid,
        time: 0.0,
    }
}

// --- ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ ---
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋˜์–ด ๋ชจ๋ธ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
// ์ด์ œ ์ด ํ•จ์ˆ˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ํ•ต์‹ฌ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
fn update(_app: &App, model: &mut Model, _update: Update) {
    // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค time ๊ฐ’์„ ์กฐ๊ธˆ์”ฉ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
    // ์ด ๊ฐ’์„ ์กฐ์ ˆํ•˜์—ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ์ „์ฒด ์†๋„๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    model.time += 0.03;
}

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

    let cell_size = CANVAS_SIZE / GRID_SIZE as f32;
    let max_radius = cell_size / 2.0;

    for (i, &brightness) in model.brightness_grid.iter().enumerate() {
        let row = i / GRID_SIZE;
        let col = i % GRID_SIZE;

        let x = col as f32 * cell_size + cell_size / 2.0 - CANVAS_SIZE / 2.0;
        let y = -(row as f32 * cell_size + cell_size / 2.0 - CANVAS_SIZE / 2.0);

        // --- ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง ์‹œ์ž‘ ---

        // 1. ์บ”๋ฒ„์Šค ์ค‘์‹ฌ(0,0)์œผ๋กœ๋ถ€ํ„ฐ ํ˜„์žฌ ์ ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. (ํ”ผํƒ€๊ณ ๋ผ์Šค ์ •๋ฆฌ)
        let dist = (x.powi(2) + y.powi(2)).sqrt();

        // 2. ์‹œ๊ฐ„๊ณผ ๊ฑฐ๋ฆฌ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์ธ ํ•จ์ˆ˜์˜ ์ž…๋ ฅ๊ฐ’(์œ„์ƒ, phase)์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        // model.time: ์‹œ๊ฐ„์ด ํ๋ฅด๋ฉด์„œ ์ „์ฒด์ ์ธ ํŒŒ๋™์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        // dist * 0.02: ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ํŒŒ๋™์˜ ์‹œ์ž‘์ ์„ ๋‹ค๋ฅด๊ฒŒ ํ•˜์—ฌ ๋ฌผ๊ฒฐ ํšจ๊ณผ๋ฅผ ์ค๋‹ˆ๋‹ค.
        let phase = model.time - dist * 0.02;

        // 3. ์‚ฌ์ธ ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ(-1.0 ~ 1.0)๋ฅผ ํฌ๊ธฐ ๋ฐฐ์œจ(0.0 ~ 1.0)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        // (sin(phase) + 1.0) -> 0.0 ~ 2.0 ๋ฒ”์œ„๋กœ ๋ณ€ํ™˜
        // * 0.5             -> 0.0 ~ 1.0 ๋ฒ”์œ„๋กœ ์ตœ์ข… ๋ณ€ํ™˜
        let scale = (phase.sin() + 1.0) * 0.5;

        // 4. ์ด๋ฏธ์ง€ ๋ฐ๊ธฐ์— ๋”ฐ๋ฅธ ๊ธฐ๋ณธ ๋ฐ˜์ง€๋ฆ„์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        let base_radius = brightness * max_radius;

        // 5. ๊ธฐ๋ณธ ๋ฐ˜์ง€๋ฆ„์— ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐฐ์œจ(scale)์„ ๊ณฑํ•˜์—ฌ ์ตœ์ข… ๋ฐ˜์ง€๋ฆ„์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
        let radius = base_radius * scale;
        
        // --- ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋กœ์ง ๋ ---

        draw.ellipse()
            .x_y(x, y)
            .radius(radius)
            .color(WHITE);
    }

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

ํ•ต์‹ฌ ๋กœ์ง ๋ณ€๊ฒฝ์  ๐ŸŽฏ

  1. Model ๊ตฌ์กฐ์ฒด
  • time: f32 ํ•„๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์•ฑ ์ „์—ญ์—์„œ ์‹œ๊ฐ„ ๊ฐ’์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.
  1. update ํ•จ์ˆ˜
  • ๋” ์ด์ƒ ๋น„์–ด์žˆ์ง€ ์•Š๊ณ , model.time ๊ฐ’์„ ๋งค ํ”„๋ ˆ์ž„ ์ฆ๊ฐ€์‹œ์ผœ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๊ตฌ๋™ํ•ฉ๋‹ˆ๋‹ค.
  1. view ํ•จ์ˆ˜ ๋‚ด๋ถ€
  • ๊ฐ ์ ์˜ ์ขŒํ‘œ (x, y)๋ฅผ ์ด์šฉํ•ด ์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ dist๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  • model.time๊ณผ dist๋ฅผ ์กฐํ•ฉํ•ด ์‚ฌ์ธํŒŒ์˜ phase๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ์‚ฌ์ธํŒŒ์˜ ๊ฒฐ๊ณผ๋ฅผ 0.0 ~ 1.0 ์‚ฌ์ด์˜ scale ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์กด์˜ brightness * max_radius์— ์ด scale ๊ฐ’์„ ๊ณฑํ•˜์—ฌ ์ตœ์ข… radius๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ› ๏ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์กฐ์ ˆํ•˜๊ธฐ
์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ฉฐ ๋‹ค๋ฅธ ๋А๋‚Œ์˜ ํŒจํ„ด์„ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ› ๏ธ ์†๋„ ์กฐ์ ˆ
update ํ•จ์ˆ˜์˜ model.time += 0.03; ์—์„œ 0.03์„ ๋” ํฐ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋นจ๋ผ์ง€๊ณ , ์ž‘์€ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๋А๋ ค์ง‘๋‹ˆ๋‹ค.

๐Ÿ› ๏ธ ๋ฌผ๊ฒฐ ๊ฐ„๊ฒฉ ์กฐ์ ˆ
view ํ•จ์ˆ˜์˜ let phase = model.time - dist * 0.02; ์—์„œ 0.02๋ฅผ ๋” ํฐ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๋ฌผ๊ฒฐ์ด ๋” ์ด˜์ด˜ํ•ด์ง€๊ณ (ํŒŒ์žฅ์ด ์งง์•„์ง€๊ณ ), ์ž‘์€ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋ฉด ๋” ๋„“๊ฒŒ ํผ์ง‘๋‹ˆ๋‹ค.



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

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

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

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