๐Ÿ”ฎ :: Circle Array Animation

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

use nannou::prelude::*;

const SCREEN_SIZE: u32 = 800;
const GRID_COUNT: usize = 20;

fn main() {
    nannou::app(model).update(update).simple_window(view).size(SCREEN_SIZE, SCREEN_SIZE).run();
}

struct Model;

fn model(_app: &App) -> Model {
    Model
}

fn update(_app: &App, _model: &mut Model, _update: Update) {
    // ์—…๋ฐ์ดํŠธ ๋กœ์ง์ด ํ•„์š”ํ•˜๋ฉด ์—ฌ๊ธฐ์— ์ถ”๊ฐ€
}

fn ease_in_out(t: f32) -> f32 {
    3.0 * t.powi(2) - 2.0 * t.powi(3)
}

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

    draw.background().color(rgba(0.05, 0.05, 0.08, 1.0));

    let color = rgb(0.1, 0.8, 0.7);

    // ๊ทธ๋ฆฌ๋“œ ์…€ ํฌ๊ธฐ์™€ ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„ ๊ณ„์‚ฐ
    let grid_cell_size = (SCREEN_SIZE as f32) / (GRID_COUNT as f32);
    let max_radius = grid_cell_size * 0.5;

    let delay_per_circle = 10.0 / 60.0;

    for row in 0..GRID_COUNT {
        for col in 0..GRID_COUNT {
            // ๊ฐ ์›์˜ ์œ„์น˜๋ฅผ ๊ทธ๋ฆฌ๋“œ ์ค‘์•™์— ๋ฐฐ์น˜
            let x = ((col as f32) + 0.5) * grid_cell_size - (SCREEN_SIZE as f32) * 0.5;
            let y = ((row as f32) + 0.5) * grid_cell_size - (SCREEN_SIZE as f32) * 0.5;

            // ๊ฐ ์›๋งˆ๋‹ค ๊ณ ์œ ํ•œ ์ธ๋ฑ์Šค๋กœ ์ง€์—ฐ์‹œ๊ฐ„ ๊ณ„์‚ฐ
            let circle_index = row * GRID_COUNT + col;
            let t = app.time - (circle_index as f32) * delay_per_circle;
            let cycle = t.rem_euclid(4.0) / 4.0;
            let raw = if cycle < 0.5 { cycle * 2.0 } else { (1.0 - cycle) * 2.0 };

            let eased = ease_in_out(raw);
            let radius = max_radius * eased;

            draw.ellipse().x_y(x, y).radius(radius).color(color);
        }
    }
    draw.to_frame(app, &frame).unwrap();
}

๐Ÿ“ Rust Code + Interaction Ver.

// `nannou` ํฌ๋ ˆ์ดํŠธ์˜ `prelude` ๋ชจ๋“ˆ์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํƒ€์ž…๊ณผ ํ•จ์ˆ˜๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// ์ด๋Š” ๋ฒกํ„ฐ, ์ƒ‰์ƒ, ์•ฑ ์„ค์ • ๋“ฑ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
use nannou::prelude::*;

// ํ™”๋ฉด์˜ ํฌ๊ธฐ๋ฅผ ํ”ฝ์…€ ๋‹จ์œ„๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์ •์‚ฌ๊ฐํ˜• ํ™”๋ฉด ๊ธฐ์ค€.
const SCREEN_SIZE: u32 = 800;

// ๊ทธ๋ฆฌ๋“œ(๊ฒฉ์ž)์˜ ํ–‰/์—ด ๊ฐœ์ˆ˜. ์ด GRID_COUNT ร— GRID_COUNT ๊ฐœ์˜ ์›์ด ๊ทธ๋ ค์ง‘๋‹ˆ๋‹ค.
const GRID_COUNT: usize = 20;

// ๊ฐ ์›์ด ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ์ž‘ ์‹œ์ ์—์„œ ์–ผ๋งˆ๋‚˜ ์ง€์—ฐ๋ ์ง€๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
// 10ํ”„๋ ˆ์ž„(60fps ๊ธฐ์ค€) ๋งŒํผ ์ง€์—ฐ๋˜๋ฏ€๋กœ, ์›๋“ค์ด ์ˆœ์ฐจ์ ์œผ๋กœ ์›€์ง์ž…๋‹ˆ๋‹ค.
const DELAY_PER_CIRCLE: f32 = 10.0 / 60.0;

// ๋งˆ์šฐ์Šค ์˜ํ–ฅ ๋ฒ”์œ„: ๋งˆ์šฐ์Šค ์œ„์น˜์—์„œ ์ด ๊ฑฐ๋ฆฌ ๋‚ด์— ์žˆ๋Š” ์›๋งŒ ์˜ํ–ฅ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
const MOUSE_INFLUENCE_RANGE: f32 = 300.0;

// ๋งˆ์šฐ์Šค ๊ทผ์ฒ˜์— ์žˆ์„ ๋•Œ ์›์˜ ํฌ๊ธฐ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ปค์งˆ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฐ์œจ.
// 0.5์ด๋ฉด ์ตœ๋Œ€ 50% ๋” ์ปค์ง‘๋‹ˆ๋‹ค.
const MOUSE_INFLUENCE_SCALE: f32 = 0.5;

// ๋งˆ์šฐ์Šค ํด๋ฆญ ์‹œ ์‹œ๊ฐ„ ์˜คํ”„์…‹(time_offset)์— ๋”ํ•ด์ง€๋Š” ๊ฐ’.
// ์ด ๊ฐ’์ด ํด์ˆ˜๋ก ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ์ „์ฒด ํƒ€์ด๋ฐ์ด ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.
const TIME_OFFSET_INCREMENT: f32 = 0.05;

// ๋งˆ์šฐ์Šค๋ฅผ ๋–ผ๋ฉด ์‹œ๊ฐ„ ์˜คํ”„์…‹์ด ์„œ์„œํžˆ ๊ฐ์†Œํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฐ์‡  ๋น„์œจ.
// 0.95๋Š” ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค 5%์”ฉ ์ค„์–ด๋“ ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
const TIME_OFFSET_DECAY: f32 = 0.95;

// ํ”„๋กœ๊ทธ๋žจ ์ง„์ž…์ : nannou ์•ฑ์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
// `model` ํ•จ์ˆ˜๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค๊ณ , `update`๋กœ ๋งค ํ”„๋ ˆ์ž„ ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฉฐ, `run()`์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
fn main() {
    nannou::app(model).update(update).run();
}

// ์•ฑ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ตฌ์กฐ์ฒด.
// ๋งˆ์šฐ์Šค ์œ„์น˜, ๋งˆ์šฐ์Šค ํด๋ฆญ ์—ฌ๋ถ€, ์‹œ๊ฐ„ ์˜คํ”„์…‹์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
struct Model {
    mouse_pos: Vec2,        // ํ˜„์žฌ ๋งˆ์šฐ์Šค ์ขŒํ‘œ (ํ™”๋ฉด ์ค‘์•™ ๊ธฐ์ค€)
    mouse_pressed: bool,    // ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ ค ์žˆ๋Š”์ง€ ์—ฌ๋ถ€
    time_offset: f32,       // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ด๋ฐ์„ ์กฐ์ ˆํ•˜๋Š” ์˜คํ”„์…‹ ๊ฐ’
}

// Model ๊ตฌ์กฐ์ฒด์— ๋Œ€ํ•œ ๋ฉ”์„œ๋“œ ๊ตฌํ˜„
impl Model {
    // ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
    fn new() -> Self {
        Self {
            mouse_pos: Vec2::ZERO,   // ์ดˆ๊ธฐ ๋งˆ์šฐ์Šค ์œ„์น˜๋Š” (0, 0) โ†’ ํ™”๋ฉด ์ค‘์•™
            mouse_pressed: false,    // ์ฒ˜์Œ์—” ๋งˆ์šฐ์Šค ์•ˆ ๋ˆŒ๋ฆผ
            time_offset: 0.0,        // ์‹œ๊ฐ„ ์˜คํ”„์…‹๋„ 0๋ถ€ํ„ฐ ์‹œ์ž‘
        }
    }

    // ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ๋งˆ์šฐ์Šค ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํ•จ์ˆ˜
    fn update_mouse_state(&mut self, app: &App) {
        // ํ˜„์žฌ ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ๊ฐ€์ ธ์˜ด (ํ™”๋ฉด ์ค‘์•™์ด (0, 0)์ธ ์ขŒํ‘œ๊ณ„)
        self.mouse_pos = app.mouse.position();
        
        // ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ ค ์žˆ๋Š”์ง€ ํ™•์ธ
        // `is_down()`์€ nannou์˜ MouseButtonState ๋ฉ”์„œ๋“œ๋กœ,
        // ํ•ด๋‹น ๋ฒ„ํŠผ์ด ํ˜„์žฌ ๋ˆŒ๋ ค ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ bool๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        if app.mouse.buttons.left().is_down() {
            self.mouse_pressed = true;
            // ๋งˆ์šฐ์Šค๋ฅผ ๋ˆ„๋ฅด๋ฉด time_offset ์ฆ๊ฐ€ โ†’ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ€์†
            self.time_offset += TIME_OFFSET_INCREMENT;
        } else {
            self.mouse_pressed = false;
            // ๋งˆ์šฐ์Šค๋ฅผ ๋–ผ๋ฉด time_offset์ด ์„œ์„œํžˆ ๊ฐ์†Œ (๊ฐ์‡  ํšจ๊ณผ)
            self.time_offset *= TIME_OFFSET_DECAY;
        }
    }

    // ๋งˆ์šฐ์Šค X ์ขŒํ‘œ์— ๋”ฐ๋ผ ์ƒ‰์ƒ์„ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜
    fn calculate_color(&self) -> Hsl {
        // `map_range`: ๊ฐ’ ํ•˜๋‚˜๋ฅผ ํŠน์ • ๋ฒ”์œ„์—์„œ ๋‹ค๋ฅธ ๋ฒ”์œ„๋กœ ์„ ํ˜• ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
        // ์—ฌ๊ธฐ์„œ๋Š” ๋งˆ์šฐ์Šค X์ขŒํ‘œ(-400 ~ 400)๋ฅผ ์ƒ‰์ƒ ํœด(hue) ๋ฒ”์œ„(0.0 ~ 1.0)๋กœ ๋งคํ•‘.
        let hue = map_range(self.mouse_pos.x, -400.0, 400.0, 0.0, 1.0);
        
        // `fract()`: ์‹ค์ˆ˜์˜ ์†Œ์ˆ˜ ๋ถ€๋ถ„๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: 1.7 โ†’ 0.7)
        // hue๊ฐ€ 0~1 ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ด๋ฅผ ์ˆœํ™˜์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.
        // ์ด๋Š” ์ƒ‰์ƒ์ด ๋นจ๊ฐ• โ†’ ์ดˆ๋ก โ†’ ํŒŒ๋ž‘ โ†’ ๋นจ๊ฐ•... ์ˆœํ™˜๋˜๋„๋ก ๋งŒ๋“ญ๋‹ˆ๋‹ค.
        hsl(hue.fract(), 0.8, 0.6) // ์ฑ„๋„ 80%, ๋ช…๋„ 60% ๊ณ ์ •
    }

    // ๋งˆ์šฐ์Šค์™€ ์› ์‚ฌ์ด ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์›์˜ ๋ฐ˜์ง€๋ฆ„์„ ์กฐ์ •ํ•˜๋Š” ํ•จ์ˆ˜
    fn calculate_circle_radius(&self, position: Vec2, base_radius: f32) -> f32 {
        // `distance()`: ๋‘ Vec2 ์‚ฌ์ด์˜ ์œ ํด๋ฆฌ๋“œ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
        let dist = self.mouse_pos.distance(position);
        
        // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์˜ํ–ฅ๋„(influence) ๊ณ„์‚ฐ: ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์ˆ˜๋ก 0์— ๊ฐ€๊นŒ์›Œ์ง
        // `max(0.0)`์€ ์Œ์ˆ˜๊ฐ€ ๋˜์ง€ ์•Š๋„๋ก ๋ณด์žฅ (๊ฑฐ๋ฆฌ๊ฐ€ ๋ฒ”์œ„ ๋ฐ–์ด๋ฉด ์˜ํ–ฅ ์—†์Œ)
        let influence = (1.0 - (dist / MOUSE_INFLUENCE_RANGE)).max(0.0);
        
        // ๊ธฐ๋ณธ ๋ฐ˜์ง€๋ฆ„์— ์˜ํ–ฅ๋„๋ฅผ ๊ณฑํ•ด ํ™•์žฅ๋œ ๋ฐ˜์ง€๋ฆ„ ๋ฐ˜ํ™˜
        base_radius * (1.0 + influence * MOUSE_INFLUENCE_SCALE)
    }
}

// ๊ทธ๋ฆฌ๋“œ์— ๋ฐฐ์น˜๋œ ๊ฐ ์›์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ตฌ์กฐ์ฒด
struct GridCircle {
    position: Vec2, // ์›์˜ ์ค‘์‹ฌ ์ขŒํ‘œ
    index: usize,   // ๊ทธ๋ฆฌ๋“œ ๋‚ด ๊ณ ์œ  ์ธ๋ฑ์Šค (0๋ถ€ํ„ฐ ์‹œ์ž‘)
}

impl GridCircle {
    // ์ƒˆ GridCircle ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
    fn new(row: usize, col: usize, grid_cell_size: f32, screen_size: f32) -> Self {
        // ๊ฐ ์…€์˜ ์ค‘์‹ฌ์— ์›์„ ๋ฐฐ์น˜: (col + 0.5) * ์…€ ํฌ๊ธฐ
        // ํ™”๋ฉด ์ค‘์•™์ด (0,0)์ด ๋˜๋„๋ก screen_size * 0.5๋ฅผ ๋บŒ
        let x = ((col as f32) + 0.5) * grid_cell_size - screen_size * 0.5;
        let y = ((row as f32) + 0.5) * grid_cell_size - screen_size * 0.5;
        
        // 1์ฐจ์› ์ธ๋ฑ์Šค ๊ณ„์‚ฐ: row * GRID_COUNT + col
        let index = row * GRID_COUNT + col;

        Self {
            position: Vec2::new(x, y),
            index,
        }
    }

    // ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํ™•์žฅ-์ˆ˜์ถ•ํ•˜๋Š” ๋ฐ˜์ง€๋ฆ„์„ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜
    fn calculate_animated_radius(&self, time: f32, time_offset: f32, max_radius: f32) -> f32 {
        // ๊ฐ ์›๋งˆ๋‹ค ์ง€์—ฐ์„ ์ฃผ๊ธฐ ์œ„ํ•ด ์ธ๋ฑ์Šค์— ๋”ฐ๋ผ ์‹œ๊ฐ„์„ ์˜คํ”„์…‹
        let t = time - (self.index as f32) * DELAY_PER_CIRCLE + time_offset;
        
        // `rem_euclid()`: ์œ ํด๋ฆฌ๋“œ ๋‚˜๋จธ์ง€ ์—ฐ์‚ฐ. ์Œ์ˆ˜๋„ ์–‘์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์žฅ.
        // ์˜ˆ: -0.5.rem_euclid(4.0) โ†’ 3.5
        // ์ด๋ฅผ ํ†ตํ•ด t๋ฅผ 0~4 ์‚ฌ์ด์˜ ์ฃผ๊ธฐ์  ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ โ†’ 4์ดˆ ์ฃผ๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
        let cycle = t.rem_euclid(4.0) / 4.0; // 0.0 ~ 1.0 ์‚ฌ์ด๋กœ ์ •๊ทœํ™”

        // ์‚ผ๊ฐํŒŒ(โ–ฒ ๋ชจ์–‘) ํ˜•ํƒœ์˜ raw ์‹ ํ˜ธ ์ƒ์„ฑ:
        // 0~0.5: ์ฆ๊ฐ€ (0 โ†’ 1), 0.5~1.0: ๊ฐ์†Œ (1 โ†’ 0)
        let raw = if cycle < 0.5 {
            cycle * 2.0
        } else {
            (1.0 - cycle) * 2.0
        };

        // `ease_in_out`: ๋ถ€๋“œ๋Ÿฌ์šด ์ž…/์ถœ๋ ฅ ์ด์ง• ํ•จ์ˆ˜ (์•„๋ž˜ ์ •์˜๋จ)
        let eased = ease_in_out(raw);
        
        // ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„์— ์ด์ง• ์ ์šฉ
        max_radius * eased
    }
}

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

    // ์ดˆ๊ธฐ ๋ชจ๋ธ ๋ฐ˜ํ™˜
    Model::new()
}

// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ˜ธ์ถœ๋˜์–ด ๋ชจ๋ธ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํ•จ์ˆ˜
fn update(app: &App, model: &mut Model, _update: Update) {
    // ๋งˆ์šฐ์Šค ์ƒํƒœ ๊ฐฑ์‹ 
    model.update_mouse_state(app);
}

// ๋ถ€๋“œ๋Ÿฌ์šด ์ž…/์ถœ๋ ฅ ์ด์ง•(easing) ํ•จ์ˆ˜: t โˆˆ [0,1] โ†’ [0,1]
// ์‹œ์ž‘๊ณผ ๋์—์„œ ๊ฐ€์†/๊ฐ์† ํšจ๊ณผ๋ฅผ ์คŒ (์ž์—ฐ์Šค๋Ÿฌ์šด ์›€์ง์ž„)
fn ease_in_out(t: f32) -> f32 {
    // 3tยฒ - 2tยณ: ํ—ˆ๋ฐ‹(Hermite) ๋ณด๊ฐ„ ๊ธฐ๋ฐ˜์˜ ๋ถ€๋“œ๋Ÿฌ์šด ์ด์ง•
    3.0 * t.powi(2) - 2.0 * t.powi(3)
}

// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜
fn view(app: &App, model: &Model, frame: Frame) {
    // ๊ทธ๋ฆฌ๊ธฐ ๊ฐ์ฒด ์ƒ์„ฑ
    let draw = app.draw();
    
    // ๋ฐฐ๊ฒฝ์ƒ‰ ์„ค์ •: ์ง„ํ•œ ๋‚จ์ƒ‰ ๊ณ„์—ด
    draw.background().color(rgba(0.05, 0.05, 0.08, 1.0));

    // ํ˜„์žฌ ๋งˆ์šฐ์Šค ์œ„์น˜์— ๋”ฐ๋ผ ์ƒ‰์ƒ ๊ณ„์‚ฐ
    let color = model.calculate_color();
    
    // ๊ทธ๋ฆฌ๋“œ ์…€ ํฌ๊ธฐ ๊ณ„์‚ฐ (ํ™”๋ฉด ํฌ๊ธฐ / ๊ทธ๋ฆฌ๋“œ ์ˆ˜)
    let grid_cell_size = (SCREEN_SIZE as f32) / (GRID_COUNT as f32);
    
    // ์›์˜ ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„: ์…€ ํฌ๊ธฐ์˜ ์ ˆ๋ฐ˜ (์…€ ์•ˆ์— ๊ฝ‰ ์ฐจ๊ฒŒ)
    let max_radius = grid_cell_size * 0.5;

    // ๊ทธ๋ฆฌ๋“œ ์ „์ฒด ์ˆœํšŒ
    for row in 0..GRID_COUNT {
        for col in 0..GRID_COUNT {
            // ํ˜„์žฌ ์…€์— ๋Œ€ํ•œ GridCircle ์ƒ์„ฑ
            let circle = GridCircle::new(row, col, grid_cell_size, SCREEN_SIZE as f32);
            
            // ์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐ˜์ง€๋ฆ„ ๊ณ„์‚ฐ
            let base_radius = circle.calculate_animated_radius(
                app.time,           // ์ „์ฒด ๊ฒฝ๊ณผ ์‹œ๊ฐ„ (์ดˆ ๋‹จ์œ„)
                model.time_offset,  // ๋งˆ์šฐ์Šค ํด๋ฆญ์— ๋”ฐ๋ฅธ ์‹œ๊ฐ„ ์˜คํ”„์…‹
                max_radius,
            );
            
            // ๋งˆ์šฐ์Šค ์œ„์น˜์— ๋”ฐ๋ผ ๋ฐ˜์ง€๋ฆ„ ์กฐ์ •
            let adjusted_radius = model.calculate_circle_radius(circle.position, base_radius);

            // ์› ๊ทธ๋ฆฌ๊ธฐ
            draw.ellipse()
                .xy(circle.position)     // ์ค‘์‹ฌ ์ขŒํ‘œ
                .radius(adjusted_radius) // ์กฐ์ •๋œ ๋ฐ˜์ง€๋ฆ„
                .color(color);           // ๋™์  ์ƒ‰์ƒ
        }
    }

    // ๊ทธ๋ฆฐ ๋‚ด์šฉ์„ ํ”„๋ ˆ์ž„์— ์ถœ๋ ฅ
    draw.to_frame(app, &frame).unwrap();
}

1. fract()

  • f32 ๋˜๋Š” f64 ํƒ€์ž…์˜ ๋ฉ”์„œ๋“œ
  • ๊ธฐ๋Šฅ: ์‹ค์ˆ˜์˜ ์†Œ์ˆ˜ ๋ถ€๋ถ„๋งŒ ๋ฐ˜ํ™˜. ์ •์ˆ˜ ๋ถ€๋ถ„์„ ์ œ๊ฑฐ.
  • ์˜ˆ: 3.7.fract() โ†’ 0.7, -1.3.fract() โ†’ 0.7 (Rust๋Š” ์Œ์ˆ˜์—์„œ๋„ ์–‘์˜ ์†Œ์ˆ˜ ๋ฐ˜ํ™˜)
  • ์šฉ๋„: ์ƒ‰์ƒ ํœด(hue)๋ฅผ 0~1 ์‚ฌ์ด๋กœ ์ˆœํ™˜์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.

2. distance()

  • Vec2 (2D ๋ฒกํ„ฐ)์˜ ๋ฉ”์„œ๋“œ
  • ๊ธฐ๋Šฅ: ๋‘ ์  ์‚ฌ์ด์˜ ์œ ํด๋ฆฌ๋“œ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ: โˆš((x2-x1)ยฒ + (y2-y1)ยฒ)
  • ์šฉ๋„: ๋งˆ์šฐ์Šค์™€ ์› ์‚ฌ์ด ๊ฑฐ๋ฆฌ ์ธก์ • โ†’ ์˜ํ–ฅ๋„ ๊ณ„์‚ฐ.

3. rem_euclid()

  • f32 ๋˜๋Š” f64์˜ ๋ฉ”์„œ๋“œ
  • ๊ธฐ๋Šฅ: ์œ ํด๋ฆฌ๋“œ ๋‚˜๋จธ์ง€ ์—ฐ์‚ฐ. ํ•ญ์ƒ 0 ์ด์ƒ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜.
  • ์ผ๋ฐ˜ %์™€ ์ฐจ์ด: -1.5 % 4.0 = -1.5์ง€๋งŒ, -1.5.rem_euclid(4.0) = 2.5
  • ์šฉ๋„: ์‹œ๊ฐ„์„ ์ฃผ๊ธฐ์  ์‹ ํ˜ธ๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์Œ์ˆ˜ ์‹œ๊ฐ„๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ.

4. is_down()

  • MouseButtonState (์˜ˆ: app.mouse.buttons.left())
  • ๊ธฐ๋Šฅ: ํ•ด๋‹น ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์ด ํ˜„์žฌ ๋ˆŒ๋ ค ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ bool๋กœ ๋ฐ˜ํ™˜.
  • ์šฉ๋„: ๋งˆ์šฐ์Šค ํด๋ฆญ ์ƒํƒœ ๊ฐ์ง€.

5. map_range()

  • nannou ์ œ๊ณต ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
  • ๊ธฐ๋Šฅ: ๊ฐ’ ํ•˜๋‚˜๋ฅผ ํ•œ ๋ฒ”์œ„์—์„œ ๋‹ค๋ฅธ ๋ฒ”์œ„๋กœ ์„ ํ˜• ๋งคํ•‘.
  • ํ˜•์‹: map_range(value, in_min, in_max, out_min, out_max)
  • ์šฉ๋„: ๋งˆ์šฐ์Šค ์ขŒํ‘œ๋ฅผ ์ƒ‰์ƒ ํœด ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜.

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

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