‹ spiral

trails

Cargo.toml

See /software/p5js/trails/ for a JavaScript version.

// some old processing code ported to nannou
// click to explode

//TODO proper 30fps

mod agent;
use agent::*;
use nannou::prelude::*;

const NUM_AGENTS: usize = 50;

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

#[derive(PartialEq)]
enum Reset {
    No,
    Trig,
    Clear,
}

struct Model {
    agents: Vec<Agent3D>,
    reset: Reset,
    t: Vec2,
    s: Vec3,
    drift: Vec3,
    r: Vec3,
    rot: Vec3,
}
impl Model {
    fn new_trans(&mut self) {
        self.t = vec2(random_range(-1.0, 1.0) / 3.0, random_range(-1.0, 1.0) / 3.0)
    }
    fn new_scale(&mut self) {
        if random_range(0.0, 10.0) > 7.0 {
            self.s = vec3(
                random_range(0.6, 1.2),
                random_range(0.6, 1.2),
                random_range(0.1, 2.0),
            );
            self.drift = Vec3::ZERO;
            if random_range(0.0, 10.0) > 8.0 {
                self.drift.x = random_range(-0.1, 0.1);
            }
            if random_range(0.0, 10.0) > 8.0 {
                self.drift.y = random_range(-0.1, 0.1);
            }
            if random_range(0.0, 10.0) > 8.0 {
                self.drift.z = random_range(-0.1, 0.1);
            }
        } else {
            if random_range(0.0, 10.0) > 5.0 {
                self.s = vec3(
                    random_range(0.3, 0.7),
                    random_range(0.2, 0.6),
                    random_range(0.01, 0.25),
                );
            } else {
                self.s = Vec3::ONE;
            }
        }
    }
    fn new_rotation(&mut self) {
        self.r = Vec3::ZERO;
        self.rot = Vec3::ZERO;
        if random_range(0.0, 10.0) > 7.0 {
            self.r.y = random_range(-4.0, 4.0);
        }
        if random_range(0.0, 10.0) > 7.0 {
            self.r.z = random_range(-4.0, 4.0);
        }
        if random_range(0.0, 10.0) > 4.0 {
            self.r.x = random_range(-4.0, 4.0);
        }
        if self.r.x + self.r.y + self.r.z == 0.0 {
            // make sure always some rotation
            self.r.x = random_range(0.5, 3.0);
        }
    }
    fn init_agents(&mut self) {
        for i in 0..NUM_AGENTS {
            self.agents[i].loc = vec3(
                random_range(-0.1, 0.1),
                random_range(-0.1, 0.1),
                random_range(-0.1, 0.1),
            );
            self.agents[i].dir = vec3(
                random_range(-0.05, 0.05),
                random_range(-0.05, 0.05),
                random_range(-0.05, 0.05),
            );
        }
    }
}

fn model(app: &App) -> Model {
    app.new_window()
        .title("trails")
        .size(800, 600)
        .mouse_pressed(mouse_pressed)
        .view(view)
        .build()
        .unwrap();

    let mut agents = Vec::with_capacity(NUM_AGENTS);
    for _i in 0..NUM_AGENTS {
        let agent = Agent3D::new(Vec3::ZERO, Vec3::ZERO, RED, BLACK, 5.0);
        agents.push(agent);
    }

    Model {
        agents,
        reset: Reset::Trig,
        t: Vec2::ZERO,
        s: Vec3::ONE,
        drift: Vec3::ZERO,
        r: Vec3::ZERO,
        rot: Vec3::ZERO,
    }
}

fn mouse_pressed(_app: &App, model: &mut Model, button: MouseButton) {
    if button == MouseButton::Left {
        model.reset = Reset::Trig;
    }
}

fn update(app: &App, model: &mut Model, _update: Update) {
    if app.elapsed_frames() % 2 == 1 {
        // 30fps TODO improve
        return;
    }
    if model.reset == Reset::Trig {
        model.init_agents();
        model.new_trans();
        model.new_scale();
        model.new_rotation();
        model.reset = Reset::Clear;
    } else {
        model.reset = Reset::No;
    }
    model.s += model.drift;
    model.rot += model.r / 360.0 * PI * 2.0;

    for agent in model.agents.iter_mut() {
        agent.update(0.95); // damping
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    if app.elapsed_frames() % 2 == 1 {
        // 30fps TODO improve
        return;
    }
    if model.reset == Reset::Clear {
        frame.clear(rgb8(150, 150, 120));
    }
    let win_rect = app.window_rect().wh();
    let draw = app
        .draw()
        .xy(model.t * win_rect)
        .scale_axes(model.s)
        .radians(model.rot);
    for agent in model.agents.iter() {
        agent.draw(&draw, &win_rect);
    }
    draw.to_frame(app, &frame).unwrap();
}

save as agent.rs

use nannou::prelude::*;

#[derive(Debug)]
pub struct Agent3D {
    pub loc: Vec3,
    pub dir: Vec3,
    col: Rgb8,
    border_col: Rgb8,
    agent_size: f32,
    cuboid: Cuboid,
    wireframe: Vec<Cuboid>,
}
impl Agent3D {
    pub fn new(loc: Vec3, dir: Vec3, col: Rgb8, border_col: Rgb8, agent_size: f32) -> Self {
        let a = agent_size;
        let aa = 0.5 * a;
        let cuboid = Cuboid::from_x_y_z_w_h_d(0.0, 0.0, 0.0, a, a, a);
        let w = 1.0; // wire width
        let wireframe = vec![
            //top
            Cuboid::from_x_y_z_w_h_d(-aa, aa, 0.0, w, w, a),
            Cuboid::from_x_y_z_w_h_d(0.0, aa, aa, a, w, w),
            Cuboid::from_x_y_z_w_h_d(aa, aa, 0.0, w, w, a),
            Cuboid::from_x_y_z_w_h_d(0.0, aa, -aa, a, w, w),
            //bottom
            Cuboid::from_x_y_z_w_h_d(-aa, -aa, 0.0, w, w, a),
            Cuboid::from_x_y_z_w_h_d(0.0, -aa, aa, a, w, w),
            Cuboid::from_x_y_z_w_h_d(aa, -aa, 0.0, w, w, a),
            Cuboid::from_x_y_z_w_h_d(0.0, -aa, -aa, a, w, w),
            //sides
            Cuboid::from_x_y_z_w_h_d(-aa, 0.0, -aa, w, a, w),
            Cuboid::from_x_y_z_w_h_d(-aa, 0.0, aa, w, a, w),
            Cuboid::from_x_y_z_w_h_d(aa, 0.0, aa, w, a, w),
            Cuboid::from_x_y_z_w_h_d(aa, 0.0, -aa, w, a, w),
        ];
        Agent3D {
            loc,
            dir,
            col,
            border_col,
            agent_size,
            cuboid,
            wireframe,
        }
    }

    pub fn draw(&self, draw: &Draw, win_rect: &Vec2) {
        let loc = self.loc.truncate() * *win_rect;
        let scl = self.loc.z * self.agent_size;

        let p = self.cuboid.triangles_iter().flat_map(geom::Tri::vertices);
        draw.xy(loc).scale(scl).mesh().points(p).color(self.col);

        for c in &self.wireframe {
            let p = c.triangles_iter().flat_map(geom::Tri::vertices);
            draw.xy(loc)
                .scale(scl)
                .mesh()
                .points(p)
                .color(self.border_col);
        }
    }

    pub fn update(&mut self, damp: f32) {
        self.loc += self.dir;
        self.dir *= damp;
    }
}