ðŸ”Ū :: Complex Particle Controller

BamgasiJM·2025년 10ė›” 23ėž

Nannou <Generative Art>

ëŠĐ록 ëģīęļ°
48/55
post-thumbnail

📝 Rust Code

use nannou::prelude::*;
use nannou_egui::{self, egui, Egui};
use std::fmt;

fn main() {
    nannou::app(model).update(update).run();
}
enum Formula {
    First,
    Second,
    Third,
    Fourth,
    Fifth,
    Sixth,
    Seventh,
    Eighth,
    Ninth,
}

enum Movie{
    Tru,
    Fal,
}

enum Color{
    Stati,
    Dynamic,
}

struct Model {
    egui: Egui,
    movie: Movie,
    formula: Formula,
    color: Color,
    t: f32,
    settings: Settings,
    x: f32,
    y: f32,
    scale: f32,
}
struct Settings {
    a: f32,
    b: f32,
    c: f32,
    d: f32,
    trail_length: f32,
    time: f32,
    radius: f32,
    t_factor: f32,
    alpha: f32,
    show_ui: bool,
}
fn model(app: &App) -> Model {
    let window_id = app
        .new_window()
        .view(view)
        .raw_event(raw_window_event)
        .build()
        .unwrap();
    let window = app.window(window_id).unwrap();
    let egui = Egui::from_window(&window);
    Model {
        t: 0.0,
        egui,
        x: 0.0,
        y: 0.0,
        movie: Movie::Tru,
        formula: Formula::Eighth,
        color: Color::Stati,
        scale: 1.0,

        settings: Settings {
            a: -0.45,
            b: -0.80,
            c: -1.60,
            d: 2.0,
            trail_length: 300.0,
            time: 200.0,
            radius: 1.0,
            t_factor: 400.05,
            alpha: 0.01,
            show_ui: true,
        },
    }
}
fn update(_app: &App, model: &mut Model, _update: Update) {
    let egui = &mut model.egui;
    let _settings = &model.settings;
    egui.set_elapsed_time(_update.since_start);
    if _app.keys.down.contains(&Key::H) {
        model.settings.show_ui = !model.settings.show_ui;
    }
    let ctx = egui.begin_frame();
    egui::Window::new("Settings").show(&ctx, |ui| {   
        ui.label("a:");
        ui.add(egui::Slider::new(&mut model.settings.a, -2.5..=2.5));
        ui.label("b:");
        ui.add(egui::Slider::new(&mut model.settings.b, -2.5..=2.5));
        ui.label("c:");
        ui.add(egui::Slider::new(&mut model.settings.c, -2.5..=2.5));
        ui.label("d:");
        ui.add(egui::Slider::new(&mut model.settings.d, -2.5..=2.5));
        ui.label("trail_length:");
        ui.add(egui::Slider::new(&mut model.settings.trail_length, 0.0..=5000.0));
        ui.label("time:");
        ui.add(egui::Slider::new(&mut model.settings.time, 0.0..=1000.0));
        ui.label("radius:");
        ui.add(egui::Slider::new(&mut model.settings.radius, 0.0..=2.0));
        ui.label("pattern:");
        ui.add(egui::Slider::new(&mut model.settings.t_factor, -0.0..=5000.0)); 
        ui.label("alpha:");
        ui.add(egui::Slider::new(&mut model.settings.alpha, 0.0..=1.0));

        ui.label("random:");
        if ui.button("random").clicked() {
            model.settings.a = random_range(-2.0, 2.0);
            model.settings.b = random_range(-2.0, 2.0);
            model.settings.c = random_range(-2.0, 2.0);
            model.settings.d = random_range(-2.0, 2.0);
            model.settings.t_factor = random_range(-0.0, 5000.0);
        };
        ui.label("animation");
        if ui.button("movie").clicked() {
            model.movie = match model.movie {
                Movie::Tru => Movie::Fal,
                Movie::Fal => Movie::Tru,
            };
        }

        ui.label("Switch formula:");
        if ui.button("Next formula").clicked() {
            model.formula = match model.formula {
                Formula::First => Formula::Second,
                Formula::Second => Formula::Third,
                Formula::Third => Formula::Fourth,
                Formula::Fourth => Formula::Fifth,
                Formula::Fifth => Formula::Sixth,
                Formula::Sixth => Formula::Seventh,
                Formula::Seventh => Formula::Eighth,
                Formula::Eighth => Formula::Ninth,
                Formula::Ninth => Formula::First,
            };
        }

        ui.label(format!("Current formula: {}", model.formula));

        impl fmt::Display for Formula {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                let name = match self {
                    Formula::First => "First",
                    Formula::Second => "Second",
                    Formula::Third => "Third",
                    Formula::Fourth => "Fourth",
                    Formula::Fifth => "Fifth",
                    Formula::Sixth => "Sixth",
                    Formula::Seventh => "Seventh",
                    Formula::Eighth => "Eighth",
                    Formula::Ninth => "Ninth",
                };
                write!(f, "{}", name)
            }
        }
        
        ui.label("color:");
        if ui.button("color pattern").clicked() {
            model.color = match model.color {
                Color::Stati => Color::Dynamic,
                Color::Dynamic => Color::Stati,
            };        
        }
    });
        model.t = _app.elapsed_frames() as f32 / model.settings.time;
}
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw().scale(model.scale);
    let trail_length = model.settings.trail_length;
    draw.background().color(BLACK);
    let mut x = model.x;
    let mut y = model.y;
    
    for i in 0..trail_length as usize {
        let color = match model.color {
            Color::Stati => hsla(model.t * 0.1, 0.5, 0.5, model.settings.alpha),
            Color::Dynamic => {
                let progress = i as f32 / trail_length;
                let hue: f32 = progress.sin(); 
                let saturation: f32 = progress.cos();
                let lightness: f32 = 0.5;
                hsla(hue, saturation, lightness, model.settings.alpha)
            },
        };
         let x_prev = x;
         let y_prev = y;
         let t = match model.movie { 
            Movie::Tru => model.t, 
            Movie::Fal => model.t - (i as f32 * model.settings.t_factor).sin(),
         };
        match model.formula {
            Formula::First => {
                x = (model.settings.a * y_prev + t).sin() + model.settings.c * (model.settings.a * x_prev + t).cos() * (model.settings.b * x_prev + t).sin();
                y = (model.settings.b * x_prev + t).sin() + model.settings.d * (model.settings.b * y_prev + t).cos() * (model.settings.a * y_prev + t).sin();
            }
            Formula::Second => {
                x = (model.settings.a * y_prev+t).sin() - model.settings.c * (model.settings.a * x_prev+t).cos();
                y = (model.settings.b * x_prev+t).sin() - model.settings.d * (model.settings.b * y_prev+t).cos();
            }
            Formula::Third => {
                x = x_prev.sin() * model.settings.a + model.settings.c * x_prev.cos() * t.sin();
                y = y_prev.sin() * model.settings.b  +model.settings.d * y_prev.cos() * t.sin();
            }
            Formula::Fourth => {
                x = (model.settings.a * y_prev + t).sin() * (model.settings.a * x_prev + t).cos() + model.settings.c * (model.settings.a * x_prev + t).cos() * (model.settings.b * x_prev + t).sin();
                y = (model.settings.b * x_prev + t).sin() * (model.settings.b * y_prev + t).cos() + model.settings.d * (model.settings.b * y_prev + t).cos() * (model.settings.a * y_prev + t).sin();
            }
            Formula::Fifth => {
                x = x_prev.sin() * model.settings.a + model.settings.c * x_prev.cos() * t.sin();
                y = model.settings.c+y_prev.sin() * model.settings.b  +model.settings.d * y_prev.cos() * t.sin();
            }
            Formula::Sixth => {
                x = (model.settings.a * y_prev + t).sin() * (model.settings.a * x_prev + t).cos() + model.settings.c * (model.settings.a * x_prev + t).cos();
                y = (model.settings.b * x_prev + t).sin() * (model.settings.b * y_prev + t).cos() + model.settings.d * (model.settings.b * y_prev + t).cos();
            }
            Formula::Seventh => {
                x = (model.settings.a * y_prev + t).sin() * (model.settings.a * x_prev + t).sin() + model.settings.c * (model.settings.a * x_prev + t).sin() * (model.settings.b * x_prev + t).sin();
                y = (model.settings.b * x_prev + t).sin() * (model.settings.b * y_prev + t).cos() + model.settings.d * (model.settings.b * y_prev + t).cos() * (model.settings.a * y_prev + t).sin();
            }
            Formula::Eighth => {
                x = (model.settings.a * y_prev + t).sin() - (model.settings.b * x_prev + t).cos();
                y = (model.settings.c * x_prev + t).sin() - (model.settings.d * y_prev + t).cos();
            }
            Formula::Ninth => {
                x =(model.settings.a+model.settings.b)*(x_prev+t).sin() + (model.settings.c+model.settings.d+t.sin())*y_prev.sin();
                y= (model.settings.c)*y_prev.cos() + (model.settings.d+t.sin())*x_prev.cos();
            }
        }
        
        let x_mapped = map_range(x, -1.0, 1.0, -150.0, 150.0); 
        let y_mapped = map_range(y, -1.0, 1.0, -150.0, 150.0);
        draw.ellipse()
            .x_y(x_mapped, y_mapped)
            .w_h(1.0, 1.0)
            .radius(model.settings.radius)
            .color(color);

        draw.ellipse()
            .x_y(-x_mapped, y_mapped)
            .w_h(1.0, 1.0)
            .radius(model.settings.radius)
            .color(color);

    }
    draw.to_frame(app, &frame).unwrap();
    if model.settings.show_ui {
        model.egui.draw_to_frame(&frame).unwrap();
    }
    if app.keys.down.contains(&Key::Space) {
        let file_path = app
            .project_path()
            .expect("failed to locate project directory")
            .join("frames")
            .join(format!("{:0}.png", app.elapsed_frames()));
        app.main_window().capture_frame(file_path);
    }
}
fn raw_window_event(_app: &App, model: &mut Model, event: &nannou::winit::event::WindowEvent) {
    model.egui.handle_raw_event(event);
    if let nannou::winit::event::WindowEvent::MouseWheel { delta, .. } = event {
        let cursor_over_egui = model.egui.ctx().wants_pointer_input();
        if !cursor_over_egui {
            match delta {
                nannou::winit::event::MouseScrollDelta::LineDelta(_, y) => {
                    model.scale *= 1.0 + *y * 0.05;
                    model.scale = model.scale.max(0.1).min(10.0);
                }
                _ => (),
            }
        }
    }
    if let nannou::winit::event::WindowEvent::KeyboardInput { input, .. } = event {
        if let (Some(nannou::winit::event::VirtualKeyCode::F), true) =
            (input.virtual_keycode, input.state == nannou::winit::event::ElementState::Pressed)
        {
            let window = _app.main_window();
            let fullscreen = window.fullscreen().is_some();
            window.set_fullscreen(!fullscreen);
        }
    }
}
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0ę°œė˜ 댓ęļ€