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