1#[derive(Clone, Debug)]
10#[non_exhaustive]
11pub struct ForceConfig {
12 pub link_distance: f32,
14 pub charge: f32,
16 pub theta: f32,
18 pub charge_distance_max: f32,
20 pub collide_padding: f32,
22 pub collide_strength: f32,
23 pub gravity: f32,
25 pub group_strength: f32,
28 pub velocity_decay: f32,
29 pub alpha_min: f32,
30 pub alpha_decay: f32,
31}
32
33impl Default for ForceConfig {
34 fn default() -> Self {
35 let alpha_min: f32 = 0.001;
36 Self {
37 link_distance: 60.,
38 charge: -220.,
39 theta: 0.9,
40 charge_distance_max: 2000.,
41 collide_padding: 4.,
42 collide_strength: 0.7,
43 gravity: 0.04,
44 group_strength: 0.12,
45 velocity_decay: 0.4,
46 alpha_min,
47 alpha_decay: 1. - alpha_min.powf(1. / 300.),
48 }
49 }
50}
51
52macro_rules! with_fields {
53 ($ty:ty { $($field:ident: $t:ty => $with:ident),* $(,)? }) => {
54 impl $ty {
55 $(
56 pub fn $with(mut self, value: $t) -> Self {
57 self.$field = value;
58 self
59 }
60 )*
61 }
62 };
63}
64pub(crate) use with_fields;
65
66with_fields!(ForceConfig {
67 link_distance: f32 => with_link_distance,
68 charge: f32 => with_charge,
69 theta: f32 => with_theta,
70 charge_distance_max: f32 => with_charge_distance_max,
71 collide_padding: f32 => with_collide_padding,
72 collide_strength: f32 => with_collide_strength,
73 gravity: f32 => with_gravity,
74 group_strength: f32 => with_group_strength,
75 velocity_decay: f32 => with_velocity_decay,
76});
77
78impl ForceConfig {
79 pub fn with_settle_ticks(mut self, ticks: u32, alpha_min: f32) -> Self {
81 self.alpha_min = alpha_min;
82 self.alpha_decay = 1. - alpha_min.powf(1. / ticks.max(1) as f32);
83 self
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default, PartialEq)]
88pub struct Vec2 {
89 pub x: f32,
90 pub y: f32,
91}
92
93impl Vec2 {
94 pub const fn new(x: f32, y: f32) -> Self {
95 Self { x, y }
96 }
97}
98
99#[derive(Clone, Debug, Default)]
100pub struct Body {
101 pub pos: Vec2,
102 pub vel: Vec2,
103 pub fixed: Option<Vec2>,
105 pub radius: f32,
106}
107
108pub struct Simulation {
109 pub config: ForceConfig,
110 pub alpha: f32,
111 pub alpha_target: f32,
112 quadtree: QuadTree,
113 rng: u32,
114}
115
116impl Simulation {
117 pub fn new(config: ForceConfig) -> Self {
118 Self {
119 config,
120 alpha: 1.,
121 alpha_target: 0.,
122 quadtree: QuadTree::default(),
123 rng: 0x9e37_79b9,
124 }
125 }
126
127 pub fn is_active(&self) -> bool {
128 self.alpha >= self.config.alpha_min || self.alpha_target > self.config.alpha_min
129 }
130
131 pub fn reheat(&mut self, alpha: f32) {
133 self.alpha = self.alpha.max(alpha);
134 }
135
136 pub fn initialize_positions(bodies: &mut [Body], start: usize) {
138 const INITIAL_RADIUS: f32 = 10.;
139 let angle = std::f32::consts::PI * (3. - 5f32.sqrt());
140 for (i, body) in bodies.iter_mut().enumerate().skip(start) {
141 let r = INITIAL_RADIUS * (0.5 + i as f32).sqrt();
142 let a = i as f32 * angle;
143 body.pos = Vec2::new(r * a.cos(), r * a.sin());
144 body.vel = Vec2::default();
145 }
146 }
147
148 pub fn tick(
150 &mut self,
151 bodies: &mut [Body],
152 links: &[(usize, usize)],
153 degrees: &[usize],
154 groups: &[Vec<usize>],
155 ) {
156 if bodies.is_empty() {
157 return;
158 }
159 self.alpha += (self.alpha_target - self.alpha) * self.config.alpha_decay;
160 let alpha = self.alpha;
161
162 self.apply_links(bodies, links, degrees, alpha);
163 self.apply_many_body(bodies, alpha);
164 self.apply_collide(bodies);
165 self.apply_groups(bodies, groups, alpha);
166 let gravity = self.config.gravity * alpha;
167 for b in bodies.iter_mut() {
168 b.vel.x -= b.pos.x * gravity;
169 b.vel.y -= b.pos.y * gravity;
170 }
171
172 let keep = 1. - self.config.velocity_decay;
173 for b in bodies.iter_mut() {
174 if let Some(fixed) = b.fixed {
175 b.pos = fixed;
176 b.vel = Vec2::default();
177 } else {
178 b.vel.x *= keep;
179 b.vel.y *= keep;
180 b.pos.x += b.vel.x;
181 b.pos.y += b.vel.y;
182 }
183 }
184 }
185
186 fn jiggle(&mut self) -> f32 {
187 self.rng ^= self.rng << 13;
189 self.rng ^= self.rng >> 17;
190 self.rng ^= self.rng << 5;
191 (self.rng as f32 / u32::MAX as f32 - 0.5) * 1e-6
192 }
193
194 fn apply_links(&mut self, bodies: &mut [Body], links: &[(usize, usize)], degrees: &[usize], alpha: f32) {
195 for &(s, t) in links {
196 if s == t {
197 continue;
198 }
199 let (ds, dt) = (degrees[s].max(1) as f32, degrees[t].max(1) as f32);
200 let strength = 1. / ds.min(dt);
201 let bias = ds / (ds + dt);
202 let (bs, bt) = (&bodies[s], &bodies[t]);
203 let mut x = bt.pos.x + bt.vel.x - bs.pos.x - bs.vel.x;
204 let mut y = bt.pos.y + bt.vel.y - bs.pos.y - bs.vel.y;
205 if x == 0. {
206 x = self.jiggle();
207 }
208 if y == 0. {
209 y = self.jiggle();
210 }
211 let len = (x * x + y * y).sqrt();
212 let l = (len - self.config.link_distance) / len * alpha * strength;
213 x *= l;
214 y *= l;
215 bodies[t].vel.x -= x * bias;
216 bodies[t].vel.y -= y * bias;
217 bodies[s].vel.x += x * (1. - bias);
218 bodies[s].vel.y += y * (1. - bias);
219 }
220 }
221
222 fn apply_groups(&self, bodies: &mut [Body], groups: &[Vec<usize>], alpha: f32) {
223 let k = self.config.group_strength * alpha;
224 if k == 0. {
225 return;
226 }
227 for members in groups.iter().filter(|m| m.len() > 1) {
228 let n = members.len() as f32;
229 let (cx, cy) = members.iter().fold((0., 0.), |(x, y), &i| (x + bodies[i].pos.x, y + bodies[i].pos.y));
230 let (cx, cy) = (cx / n, cy / n);
231 for &i in members {
232 bodies[i].vel.x += (cx - bodies[i].pos.x) * k;
233 bodies[i].vel.y += (cy - bodies[i].pos.y) * k;
234 }
235 }
236 }
237
238 fn apply_many_body(&mut self, bodies: &mut [Body], alpha: f32) {
239 self.quadtree.build(bodies);
240 let theta2 = self.config.theta * self.config.theta;
241 let max2 = self.config.charge_distance_max * self.config.charge_distance_max;
242 let strength = self.config.charge;
243 for i in 0..bodies.len() {
244 let (fx, fy) = self.quadtree.force(i, strength * alpha, theta2, max2);
245 bodies[i].vel.x += fx;
246 bodies[i].vel.y += fy;
247 }
248 }
249
250 fn apply_collide(&mut self, bodies: &mut [Body]) {
252 let pad = self.config.collide_padding;
253 let max_r = bodies.iter().map(|b| b.radius).fold(0., f32::max) + pad;
254 let cell = (max_r * 2.).max(1.);
255 let mut grid: std::collections::HashMap<(i32, i32), Vec<usize>> = Default::default();
256 for (i, b) in bodies.iter().enumerate() {
257 let key = ((b.pos.x / cell).floor() as i32, (b.pos.y / cell).floor() as i32);
258 grid.entry(key).or_default().push(i);
259 }
260 let strength = self.config.collide_strength;
261 for i in 0..bodies.len() {
262 let (cx, cy) = (
263 (bodies[i].pos.x / cell).floor() as i32,
264 (bodies[i].pos.y / cell).floor() as i32,
265 );
266 for gx in cx - 1..=cx + 1 {
267 for gy in cy - 1..=cy + 1 {
268 let Some(cell_items) = grid.get(&(gx, gy)) else { continue };
269 for &j in cell_items {
270 if j <= i {
271 continue;
272 }
273 let (a, b) = (&bodies[i], &bodies[j]);
274 let r = a.radius + b.radius + pad * 2.;
275 let mut x = a.pos.x + a.vel.x - b.pos.x - b.vel.x;
276 let mut y = a.pos.y + a.vel.y - b.pos.y - b.vel.y;
277 let mut l = x * x + y * y;
278 if l >= r * r {
279 continue;
280 }
281 if x == 0. {
282 x = self.jiggle();
283 l += x * x;
284 }
285 if y == 0. {
286 y = self.jiggle();
287 l += y * y;
288 }
289 let len = l.sqrt();
290 let k = (r - len) / len * strength;
291 let (ra, rb) = (a.radius * a.radius, b.radius * b.radius);
292 let w = rb / (ra + rb).max(f32::EPSILON);
293 x *= k;
294 y *= k;
295 bodies[i].vel.x += x * w;
296 bodies[i].vel.y += y * w;
297 bodies[j].vel.x -= x * (1. - w);
298 bodies[j].vel.y -= y * (1. - w);
299 }
300 }
301 }
302 }
303 }
304}
305
306const EMPTY: u32 = u32::MAX;
307
308#[derive(Clone, Copy)]
309struct Cell {
310 x0: f32,
311 y0: f32,
312 size: f32,
313 count: f32,
315 cx: f32,
316 cy: f32,
317 body: u32,
319 children: u32,
321}
322
323impl Cell {
324 fn new(x0: f32, y0: f32, size: f32) -> Self {
325 Self { x0, y0, size, count: 0., cx: 0., cy: 0., body: EMPTY, children: EMPTY }
326 }
327
328 fn is_leaf(&self) -> bool {
329 self.children == EMPTY
330 }
331
332 fn quadrant(&self, p: Vec2) -> u32 {
333 let half = self.size / 2.;
334 let right = (p.x >= self.x0 + half) as u32;
335 let bottom = (p.y >= self.y0 + half) as u32;
336 right | (bottom << 1)
337 }
338}
339
340#[derive(Default)]
342struct QuadTree {
343 cells: Vec<Cell>,
344 next: Vec<u32>,
346 pos: Vec<Vec2>,
347 stack: Vec<u32>,
348}
349
350impl QuadTree {
351 const MAX_DEPTH: u32 = 24;
352
353 fn build(&mut self, bodies: &[Body]) {
354 self.cells.clear();
355 self.next.clear();
356 self.next.resize(bodies.len(), EMPTY);
357 self.pos.clear();
358 self.pos.extend(bodies.iter().map(|b| b.pos));
359 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
360 for p in &self.pos {
361 x0 = x0.min(p.x);
362 y0 = y0.min(p.y);
363 x1 = x1.max(p.x);
364 y1 = y1.max(p.y);
365 }
366 let size = (x1 - x0).max(y1 - y0).max(1.) * 1.0001;
367 self.cells.push(Cell::new(x0, y0, size));
368 for i in 0..self.pos.len() as u32 {
369 self.insert(i);
370 }
371 self.accumulate(0);
372 }
373
374 fn insert(&mut self, body: u32) {
375 let p = self.pos[body as usize];
376 let mut cell = 0usize;
377 let mut depth = 0;
378 loop {
379 let c = self.cells[cell];
380 if c.is_leaf() {
381 if c.body == EMPTY || depth >= Self::MAX_DEPTH {
382 self.next[body as usize] = c.body;
384 self.cells[cell].body = body;
385 return;
386 }
387 self.split(cell);
388 }
389 let c = self.cells[cell];
390 cell = (c.children + c.quadrant(p)) as usize;
391 depth += 1;
392 }
393 }
394
395 fn split(&mut self, cell: usize) {
397 let c = self.cells[cell];
398 let half = c.size / 2.;
399 let first = self.cells.len() as u32;
400 for q in 0..4 {
401 let x = c.x0 + if q & 1 == 1 { half } else { 0. };
402 let y = c.y0 + if q & 2 == 2 { half } else { 0. };
403 self.cells.push(Cell::new(x, y, half));
404 }
405 self.cells[cell].children = first;
406 self.cells[cell].body = EMPTY;
407 let mut b = c.body;
408 while b != EMPTY {
409 let following = self.next[b as usize];
410 let child = (first + c.quadrant(self.pos[b as usize])) as usize;
411 self.next[b as usize] = self.cells[child].body;
412 self.cells[child].body = b;
413 b = following;
414 }
415 }
416
417 fn accumulate(&mut self, cell: usize) -> (f32, f32, f32) {
418 let c = self.cells[cell];
419 let (mut n, mut sx, mut sy) = (0., 0., 0.);
420 if c.is_leaf() {
421 let mut b = c.body;
422 while b != EMPTY {
423 let p = self.pos[b as usize];
424 n += 1.;
425 sx += p.x;
426 sy += p.y;
427 b = self.next[b as usize];
428 }
429 } else {
430 for q in 0..4 {
431 let (cn, cx, cy) = self.accumulate((c.children + q) as usize);
432 n += cn;
433 sx += cx * cn;
434 sy += cy * cn;
435 }
436 }
437 let (cx, cy) = if n > 0. { (sx / n, sy / n) } else { (0., 0.) };
438 let cell = &mut self.cells[cell];
439 cell.count = n;
440 cell.cx = cx;
441 cell.cy = cy;
442 (n, cx, cy)
443 }
444
445 fn force(&mut self, i: usize, strength: f32, theta2: f32, max2: f32) -> (f32, f32) {
447 const DISTANCE_MIN2: f32 = 1.;
448 let p = self.pos[i];
449 let (mut fx, mut fy) = (0., 0.);
450 self.stack.clear();
451 self.stack.push(0);
452 while let Some(ix) = self.stack.pop() {
453 let c = self.cells[ix as usize];
454 if c.count == 0. {
455 continue;
456 }
457 if !c.is_leaf() {
458 let (x, y) = (c.cx - p.x, c.cy - p.y);
459 let l = x * x + y * y;
460 if c.size * c.size / theta2 < l {
461 if l < max2 {
463 let k = strength * c.count / l.max(DISTANCE_MIN2);
464 fx += x * k;
465 fy += y * k;
466 }
467 } else {
468 self.stack.extend((0..4).map(|q| c.children + q));
469 }
470 continue;
471 }
472 let mut b = c.body;
473 while b != EMPTY {
474 if b as usize != i {
475 let q = self.pos[b as usize];
476 let (x, y) = (q.x - p.x, q.y - p.y);
477 let l = x * x + y * y;
478 if l < max2 {
479 let k = strength / l.max(DISTANCE_MIN2);
480 fx += x * k;
481 fy += y * k;
482 }
483 }
484 b = self.next[b as usize];
485 }
486 }
487 (fx, fy)
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 fn bodies(n: usize) -> Vec<Body> {
496 let mut b = vec![Body { radius: 8., ..Default::default() }; n];
497 Simulation::initialize_positions(&mut b, 0);
498 b
499 }
500
501 #[test]
502 fn barnes_hut_matches_brute_force() {
503 let b = bodies(300);
504 let mut tree = QuadTree::default();
505 tree.build(&b);
506 for i in [0, 17, 150, 299] {
507 let exact = tree.force(i, -1., 0., f32::MAX);
509 let (mut fx, mut fy, mut scale) = (0., 0., 0.);
510 for (j, o) in b.iter().enumerate() {
511 if j != i {
512 let (x, y) = (o.pos.x - b[i].pos.x, o.pos.y - b[i].pos.y);
513 let l = (x * x + y * y).max(1.);
514 fx -= x / l;
515 fy -= y / l;
516 scale += x.hypot(y) / l;
517 }
518 }
519 assert!((exact.0 - fx).abs() < 1e-3 && (exact.1 - fy).abs() < 1e-3);
520 let approx = tree.force(i, -1., 0.81, f32::MAX);
521 let err = (approx.0 - fx).hypot(approx.1 - fy) / scale;
523 assert!(err < 0.02, "relative error {err}");
524 }
525 }
526
527 #[test]
528 fn coincident_points_do_not_overflow() {
529 let mut b = vec![Body { radius: 5., ..Default::default() }; 50];
530 let mut sim = Simulation::new(ForceConfig::default());
531 let degrees = vec![0; 50];
532 for _ in 0..20 {
533 sim.tick(&mut b, &[], °rees, &[]);
534 }
535 assert!(b.iter().all(|b| b.pos.x.is_finite() && b.pos.y.is_finite()));
536 }
537
538 #[test]
539 fn simulation_settles_with_links_near_rest_length() {
540 let n = 60;
541 let mut b = bodies(n);
542 let links: Vec<_> = (1..n).map(|i| (i, (i - 1) / 2)).collect();
543 let mut degrees = vec![0; n];
544 for &(s, t) in &links {
545 degrees[s] += 1;
546 degrees[t] += 1;
547 }
548 let mut sim = Simulation::new(ForceConfig::default());
549 let mut ticks = 0;
550 while sim.is_active() {
551 sim.tick(&mut b, &links, °rees, &[]);
552 ticks += 1;
553 }
554 assert!(ticks < 400);
555 let mean: f32 = links
556 .iter()
557 .map(|&(s, t)| (b[s].pos.x - b[t].pos.x).hypot(b[s].pos.y - b[t].pos.y))
558 .sum::<f32>()
559 / links.len() as f32;
560 assert!(mean > 30. && mean < 150., "mean link length {mean}");
561 }
562}