‹ 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;
  }
}