-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdiffusion_2.rs
More file actions
234 lines (209 loc) · 6.75 KB
/
diffusion_2.rs
File metadata and controls
234 lines (209 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// implementation of deconbatch's "Poor man's DLA" in Rust
// https://www.deconbatch.com/2019/10/the-poor-mans-dla-diffusion-limited.html
// cargo run --release --example diffusion_2 -- --radius 50 --init 12 --weight 2.5 --size 4 --max-dist 1.3 --loops 7500 --palette pastel1
// cargo run --release --example diffusion_2 -- --radius 50 --init 12 --weight 2.5 --size 4 --max-dist 3.3 --loops 1800 --palette pastel1 --animate true
// cargo run --release --example diffusion_2 -- --radius 150 --init 20 --weight 3 --size 8 --max-dist 1.1 --loops 100 --animate true
extern crate chrono;
extern crate nannou;
use nannou::color::*;
use nannou::draw::Draw;
use nannou::prelude::*;
mod util;
use util::args::ArgParser;
use util::color::*;
use util::{captured_frame_path, oversample, smooth_by};
const HEIGHT: u32 = 1024;
const WIDTH: u32 = 1024;
const TWO_PI: f32 = PI * 2.;
struct Tree {
pub x: f32,
pub y: f32,
pub children: Vec<Tree>,
}
impl Tree {
pub fn new(x: f32, y: f32) -> Self {
Tree {
x,
y,
children: Vec::<Tree>::new(),
}
}
}
fn main() {
nannou::app(model).update(update).run();
}
struct Model {
loops: usize,
animation_rate: u64,
point_size: f32,
animate: bool,
min_dist: f32,
max_dist: f32,
start_radius: f32,
trees: Vec<Tree>,
stroke_weight: f32,
palette: String,
}
fn model(app: &App) -> Model {
let args = ArgParser::new();
let loops = args.get("loops", 7200);
let point_size = args.get("size", 4.0);
// min/max dist control how close together the dots can be.
// They scale the point_size, so 1.0 means "1x point_size"
let min_dist = args.get("min-dist", 1.0);
let max_dist = args.get("max-dist", 1.2);
// how many points around the circle should be initialize?
let n_initial_points = args.get("init", random_range(2, 10));
// should the drawing animate (as opposed to just drawing the final frame)?
// and if so, how frequently should it update?
let animate = args.get("animate", false);
let animation_rate = args.get("rate", 50);
// this is the size of the starting "circle" on which the points are placed
let start_radius = args.get("radius", 200.);
// weight of the line
let stroke_weight = args.get("weight", 1.0);
let palette = args.get_string("palette", "eric2");
// initialize the cluster with some dots in the center
let mut trees = Vec::new();
for i in 0..n_initial_points {
let factor = i as f32 / n_initial_points as f32;
trees.push(Tree::new(
start_radius * (TWO_PI * factor).cos(),
start_radius * (TWO_PI * factor).sin(),
));
}
app
.new_window()
.title(app.exe_name().unwrap())
.view(view)
.size(WIDTH, HEIGHT)
.build()
.unwrap();
app.set_loop_mode(LoopMode::loop_ntimes(loops));
Model {
loops,
point_size,
animate,
animation_rate,
min_dist,
max_dist,
start_radius,
trees,
stroke_weight,
palette,
}
}
fn update(_app: &App, model: &mut Model, _update: Update) {
let Model {
trees,
point_size,
min_dist,
max_dist,
..
} = model;
// "original" version by deconbatch continuously modifies entry_radius with the following formula
// *entry_radius = (*entry_radius * 2.) % TWO_PI;
//
// note from eric: I think modulus works in kind of a peudo-random way many times,
// so I'm just going to go straight up random!
let entry_radius = random_f32() * TWO_PI;
let mut x = WIDTH as f32 * entry_radius.cos();
let mut y = HEIGHT as f32 * entry_radius.sin();
for _attempt_to_find_collision in 0..(HEIGHT as i32 * 2) {
// walk to the center
x -= entry_radius.cos();
y -= entry_radius.sin();
// Uncomment to avoid putting dots in the "center" of the circle.
// if x.hypot(y) < model.start_radius {
// break;
// }
if check_tree_collision(trees, pt2(x, y), *point_size, *min_dist, *max_dist) {
break;
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
// Prepare to draw.
let draw = app.draw();
// let cream = hsl(47. / 360., 1., 0.98);
let dark_blue = hsl(241. / 360., 0.58, 0.01);
draw.background().color(dark_blue);
// draw every `rate` frames if animating
println!("{}", frame.nth()); // kind of nice to know things are running smoothly if it isn't animating
if model.animate && frame.nth() > 0 && frame.nth() % model.animation_rate == 0 {
draw_tree(&model.trees, model.stroke_weight, &model.palette, &draw);
draw.to_frame(app, &frame).unwrap();
}
// draw final frame
if frame.nth() == model.loops as u64 - 1 {
draw_tree(&model.trees, model.stroke_weight, &model.palette, &draw);
draw.to_frame(app, &frame).unwrap();
app
.main_window()
.capture_frame(captured_frame_path(app, &frame));
}
}
type Line = Vec<Point2>;
fn draw_tree(trees: &Vec<Tree>, stroke_weight: f32, palette: &String, draw: &Draw) {
let palette = get_palette(&palette);
for tree in trees {
let line = vec![pt2(tree.x, tree.y)];
let paths = build_paths(&line, &tree.children);
for path in paths {
draw
.polyline()
.color(random_color(palette))
.caps_round()
.stroke_weight(stroke_weight)
.points(smooth_by(10, &oversample(5, &path)));
}
}
}
// I don't think this is a great pathfinding algorithm,
// but it seems to work, and I'm OK with that.
// Hopefully I will revisit this in the future.
fn build_paths(line: &Line, children: &Vec<Tree>) -> Vec<Line> {
let mut paths = vec![];
// for each child,
// create a new line by concatenating `line` and the child point.
// Then, recursively build all the paths from it's descendants.
// If there are descendant paths, push those into the paths array.
// If they are not descendant paths, just push the new line onto the array
for child in children {
let path = [&line[..], &[pt2(child.x, child.y)]].concat();
// I would love a more elegant solve for this...
if child.children.len() == 0 {
paths.push(path);
} else {
for path in build_paths(&path, &child.children) {
paths.push(path);
}
}
}
paths
}
/// check collision between a point and the cluster.
/// This becomes VERY inefficient as the tree becomes more deeply nested.
/// Would be prudent to consider a new way of determining "collision",
/// or a new data structure for determining collision.
fn check_tree_collision(
trees: &mut Vec<Tree>,
point: Point2,
size: f32,
min_dist: f32,
max_dist: f32,
) -> bool {
for tree in trees {
if pt2(tree.x, tree.y).distance(pt2(point.x, point.y)) < size * min_dist {
return false;
}
if pt2(tree.x, tree.y).distance(pt2(point.x, point.y)) < size * max_dist {
tree.children.push(Tree::new(point.x, point.y));
return true;
}
if check_tree_collision(&mut tree.children, point, size, min_dist, max_dist) {
return true;
}
}
false
}