๐Ÿ”ฎ :: Radial Dreams

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

use nannou::prelude::*;
use std::path::Path;

struct Circle {
    radius: f32,        // ์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ
    angle: f32,         // ํ˜„์žฌ ๊ฐ๋„
    size: f32,          // ์›์˜ ํฌ๊ธฐ
    hue: f32,           // ์ƒ‰์ƒ
    speed_factor: f32,  // ์†๋„ ๊ณ„์ˆ˜
}

impl Circle {
    fn new(radius: f32, initial_angle: f32, hue: f32) -> Self {
        // ์ค‘์‹ฌ์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ๋น ๋ฅธ ์†๋„, ์™ธ๊ณฝ์— ๊ฐˆ์ˆ˜๋ก ๋А๋ฆฐ ์†๋„
        let speed_factor = (300.0 - radius) / 300.0;
        let size = map_range(radius, 0.0, 300.0, 15.0, 3.0); // ์ค‘์‹ฌ์€ ํฌ๊ฒŒ, ์™ธ๊ณฝ์€ ์ž‘๊ฒŒ
        
        Circle {
            radius,
            angle: initial_angle,
            size,
            hue,
            speed_factor,
        }
    }
    
    fn update(&mut self, base_speed: f32) {
        // ์†๋„ ๊ณ„์ˆ˜๋ฅผ ์ ์šฉํ•œ ํšŒ์ „
        self.angle += base_speed * self.speed_factor;
    }
    
    fn position(&self) -> Vec2 {
        vec2(
            self.radius * self.angle.cos(),
            self.radius * self.angle.sin(),
        )
    }
}

struct Model {
    circles: Vec<Circle>,
    counter: u32,
    base_rotation_speed: f32,
    trail_alpha: f32, // ๊ถค์ ์˜ ํˆฌ๋ช…๋„
}

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

fn model(app: &App) -> Model {
    app.new_window().size(800, 800).view(view).build().unwrap();
    
    let mut circles = Vec::new();
    
    // 100๊ฐœ์˜ ์›์„ ๋‹ค์–‘ํ•œ ๋ฐ˜์ง€๋ฆ„๊ณผ ๊ฐ๋„๋กœ ์ƒ์„ฑ
    for i in 0..100 {
        let radius = random_range(20.0, 300.0);
        let initial_angle = random_range(0.0, TAU); // TAU = 2ฯ€
        let hue = random_range(0.0, 1.0);
        
        circles.push(Circle::new(radius, initial_angle, hue));
    }
    
    Model {
        circles,
        counter: 1,
        base_rotation_speed: 0.015,
        trail_alpha: 0.05, // ๋‚ฎ์€ ๊ฐ’์œผ๋กœ ๊ธด ๊ถค์  ํšจ๊ณผ
    }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    // ๋ชจ๋“  ์›๋“ค์˜ ์œ„์น˜ ์—…๋ฐ์ดํŠธ
    for circle in &mut model.circles {
        circle.update(model.base_rotation_speed);
    }
}

fn event(app: &App, model: &mut Model, event: Event) {
    if let Event::WindowEvent { simple: Some(WindowEvent::KeyPressed(key)), .. } = event {
        match key {
            Key::P => {
                save_frame(app, model, "png");
            }
            Key::J => {
                save_frame(app, model, "jpg");
            }
            Key::C => {
                // ์›๋“ค์„ ๋‹ค์‹œ ์ดˆ๊ธฐํ™” (์ƒˆ๋กœ์šด ํŒจํ„ด)
                reset_circles(model);
            }
            Key::Plus | Key::Equals => {
                // ์†๋„ ์ฆ๊ฐ€
                model.base_rotation_speed += 0.005;
                println!("์†๋„ ์ฆ๊ฐ€: {:.3}", model.base_rotation_speed);
            }
            Key::Minus => {
                // ์†๋„ ๊ฐ์†Œ
                model.base_rotation_speed = (model.base_rotation_speed - 0.005).max(0.001);
                println!("์†๋„ ๊ฐ์†Œ: {:.3}", model.base_rotation_speed);
            }
            Key::T => {
                // ๊ถค์  ๊ธธ์ด ์กฐ์ ˆ
                model.trail_alpha = if model.trail_alpha < 0.1 { 0.3 } else { 0.05 };
                println!("๊ถค์  ํˆฌ๋ช…๋„: {:.2}", model.trail_alpha);
            }
            _ => {}
        }
    }
}

fn reset_circles(model: &mut Model) {
    model.circles.clear();
    
    for _ in 0..100 {
        let radius = random_range(20.0, 300.0);
        let initial_angle = random_range(0.0, TAU);
        let hue = random_range(0.0, 1.0);
        
        model.circles.push(Circle::new(radius, initial_angle, hue));
    }
    
    println!("์ƒˆ๋กœ์šด ํŒจํ„ด ์ƒ์„ฑ๋จ");
}

fn save_frame(app: &App, model: &mut Model, format: &str) {
    let filename = format!("output_{}.{}", model.counter, format);
    let path = Path::new(&filename);
    let window = app.main_window();
    window.capture_frame(path);
    
    println!("ํ”„๋ ˆ์ž„ ์ €์žฅ: {}", filename);
    model.counter += 1;
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    
    // ๊ถค์  ํšจ๊ณผ๋ฅผ ์œ„ํ•œ ๋ฐ˜ํˆฌ๋ช… ๋ฐฐ๊ฒฝ
    draw.rect()
        .w_h(800.0, 800.0)
        .color(srgba(0.0, 0.0, 0.0, model.trail_alpha));
    
    // ๋ชจ๋“  ์›๋“ค ๊ทธ๋ฆฌ๊ธฐ
    for circle in &model.circles {
        let pos = circle.position();
        
        // HSL ์ƒ‰์ƒ์œผ๋กœ ๋‹ค์ฑ„๋กœ์šด ํšจ๊ณผ
        let saturation = map_range(circle.radius, 20.0, 300.0, 1.0, 0.6);
        let lightness = map_range(circle.speed_factor, 0.0, 1.0, 0.3, 0.8);
        
        draw.ellipse()
            .xy(pos)
            .radius(circle.size)
            .color(hsl(circle.hue, saturation, lightness))
            .stroke_weight(0.5)
            .stroke_color(hsla(circle.hue, saturation, lightness + 0.2, 0.8));
    }
    
    // ์ค‘์‹ฌ์  ํ‘œ์‹œ (์„ ํƒ์ )
    draw.ellipse()
        .xy(vec2(0.0, 0.0))
        .radius(2.0)
        .color(WHITE);
    
    // Title and UI info display
    draw.text("Radial Dreams")
        .xy(vec2(0.0, 350.0))
        .font_size(24)
        .color(hsla(0.15, 0.8, 0.9, 0.8))
        .center_justify();
    
    let info_text = format!(
        "Speed: {:.3} | Trail: {:.2}\nP: Save PNG, J: Save JPG, C: New Pattern\n+/-: Speed Control, T: Trail Toggle", 
        model.base_rotation_speed, 
        model.trail_alpha
    );
    
    draw.text(&info_text)
        .xy(vec2(-380.0, 320.0))
        .font_size(12)
        .color(WHITE)
        .left_justify();
    
    draw.to_frame(app, &frame).unwrap();
}

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

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