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(
195 &mut self,
196 bodies: &mut [Body],
197 links: &[(usize, usize)],
198 degrees: &[usize],
199 alpha: f32,
200 ) {
201 for &(s, t) in links {
202 if s == t {
203 continue;
204 }
205 let (ds, dt) = (degrees[s].max(1) as f32, degrees[t].max(1) as f32);
206 let strength = 1. / ds.min(dt);
207 let bias = ds / (ds + dt);
208 let (bs, bt) = (&bodies[s], &bodies[t]);
209 let mut x = bt.pos.x + bt.vel.x - bs.pos.x - bs.vel.x;
210 let mut y = bt.pos.y + bt.vel.y - bs.pos.y - bs.vel.y;
211 if x == 0. {
212 x = self.jiggle();
213 }
214 if y == 0. {
215 y = self.jiggle();
216 }
217 let len = (x * x + y * y).sqrt();
218 let l = (len - self.config.link_distance) / len * alpha * strength;
219 x *= l;
220 y *= l;
221 bodies[t].vel.x -= x * bias;
222 bodies[t].vel.y -= y * bias;
223 bodies[s].vel.x += x * (1. - bias);
224 bodies[s].vel.y += y * (1. - bias);
225 }
226 }
227
228 fn apply_groups(&self, bodies: &mut [Body], groups: &[Vec<usize>], alpha: f32) {
229 let k = self.config.group_strength * alpha;
230 if k == 0. {
231 return;
232 }
233 for members in groups.iter().filter(|m| m.len() > 1) {
234 let n = members.len() as f32;
235 let (cx, cy) = members.iter().fold((0., 0.), |(x, y), &i| {
236 (x + bodies[i].pos.x, y + bodies[i].pos.y)
237 });
238 let (cx, cy) = (cx / n, cy / n);
239 for &i in members {
240 bodies[i].vel.x += (cx - bodies[i].pos.x) * k;
241 bodies[i].vel.y += (cy - bodies[i].pos.y) * k;
242 }
243 }
244 }
245
246 fn apply_many_body(&mut self, bodies: &mut [Body], alpha: f32) {
247 self.quadtree.build(bodies);
248 let theta2 = self.config.theta * self.config.theta;
249 let max2 = self.config.charge_distance_max * self.config.charge_distance_max;
250 let strength = self.config.charge;
251 for (i, body) in bodies.iter_mut().enumerate() {
252 let (fx, fy) = self.quadtree.force(i, strength * alpha, theta2, max2);
253 body.vel.x += fx;
254 body.vel.y += fy;
255 }
256 }
257
258 fn apply_collide(&mut self, bodies: &mut [Body]) {
260 let pad = self.config.collide_padding;
261 let max_r = bodies.iter().map(|b| b.radius).fold(0., f32::max) + pad;
262 let cell = (max_r * 2.).max(1.);
263 let mut grid: std::collections::HashMap<(i32, i32), Vec<usize>> = Default::default();
264 for (i, b) in bodies.iter().enumerate() {
265 let key = (
266 (b.pos.x / cell).floor() as i32,
267 (b.pos.y / cell).floor() as i32,
268 );
269 grid.entry(key).or_default().push(i);
270 }
271 let strength = self.config.collide_strength;
272 for i in 0..bodies.len() {
273 let (cx, cy) = (
274 (bodies[i].pos.x / cell).floor() as i32,
275 (bodies[i].pos.y / cell).floor() as i32,
276 );
277 for gx in cx - 1..=cx + 1 {
278 for gy in cy - 1..=cy + 1 {
279 let Some(cell_items) = grid.get(&(gx, gy)) else {
280 continue;
281 };
282 for &j in cell_items {
283 if j <= i {
284 continue;
285 }
286 let (a, b) = (&bodies[i], &bodies[j]);
287 let r = a.radius + b.radius + pad * 2.;
288 let mut x = a.pos.x + a.vel.x - b.pos.x - b.vel.x;
289 let mut y = a.pos.y + a.vel.y - b.pos.y - b.vel.y;
290 let mut l = x * x + y * y;
291 if l >= r * r {
292 continue;
293 }
294 if x == 0. {
295 x = self.jiggle();
296 l += x * x;
297 }
298 if y == 0. {
299 y = self.jiggle();
300 l += y * y;
301 }
302 let len = l.sqrt();
303 let k = (r - len) / len * strength;
304 let (ra, rb) = (a.radius * a.radius, b.radius * b.radius);
305 let w = rb / (ra + rb).max(f32::EPSILON);
306 x *= k;
307 y *= k;
308 bodies[i].vel.x += x * w;
309 bodies[i].vel.y += y * w;
310 bodies[j].vel.x -= x * (1. - w);
311 bodies[j].vel.y -= y * (1. - w);
312 }
313 }
314 }
315 }
316 }
317}
318
319const EMPTY: u32 = u32::MAX;
320
321#[derive(Clone, Copy)]
322struct Cell {
323 x0: f32,
324 y0: f32,
325 size: f32,
326 count: f32,
328 cx: f32,
329 cy: f32,
330 body: u32,
332 children: u32,
334}
335
336impl Cell {
337 fn new(x0: f32, y0: f32, size: f32) -> Self {
338 Self {
339 x0,
340 y0,
341 size,
342 count: 0.,
343 cx: 0.,
344 cy: 0.,
345 body: EMPTY,
346 children: EMPTY,
347 }
348 }
349
350 fn is_leaf(&self) -> bool {
351 self.children == EMPTY
352 }
353
354 fn quadrant(&self, p: Vec2) -> u32 {
355 let half = self.size / 2.;
356 let right = (p.x >= self.x0 + half) as u32;
357 let bottom = (p.y >= self.y0 + half) as u32;
358 right | (bottom << 1)
359 }
360}
361
362#[derive(Default)]
364struct QuadTree {
365 cells: Vec<Cell>,
366 next: Vec<u32>,
368 pos: Vec<Vec2>,
369 stack: Vec<u32>,
370}
371
372impl QuadTree {
373 const MAX_DEPTH: u32 = 24;
374
375 fn build(&mut self, bodies: &[Body]) {
376 self.cells.clear();
377 self.next.clear();
378 self.next.resize(bodies.len(), EMPTY);
379 self.pos.clear();
380 self.pos.extend(bodies.iter().map(|b| b.pos));
381 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
382 for p in &self.pos {
383 x0 = x0.min(p.x);
384 y0 = y0.min(p.y);
385 x1 = x1.max(p.x);
386 y1 = y1.max(p.y);
387 }
388 let size = (x1 - x0).max(y1 - y0).max(1.) * 1.0001;
389 self.cells.push(Cell::new(x0, y0, size));
390 for i in 0..self.pos.len() as u32 {
391 self.insert(i);
392 }
393 self.accumulate(0);
394 }
395
396 fn insert(&mut self, body: u32) {
397 let p = self.pos[body as usize];
398 let mut cell = 0usize;
399 let mut depth = 0;
400 loop {
401 let c = self.cells[cell];
402 if c.is_leaf() {
403 if c.body == EMPTY || depth >= Self::MAX_DEPTH {
404 self.next[body as usize] = c.body;
406 self.cells[cell].body = body;
407 return;
408 }
409 self.split(cell);
410 }
411 let c = self.cells[cell];
412 cell = (c.children + c.quadrant(p)) as usize;
413 depth += 1;
414 }
415 }
416
417 fn split(&mut self, cell: usize) {
419 let c = self.cells[cell];
420 let half = c.size / 2.;
421 let first = self.cells.len() as u32;
422 for q in 0..4 {
423 let x = c.x0 + if q & 1 == 1 { half } else { 0. };
424 let y = c.y0 + if q & 2 == 2 { half } else { 0. };
425 self.cells.push(Cell::new(x, y, half));
426 }
427 self.cells[cell].children = first;
428 self.cells[cell].body = EMPTY;
429 let mut b = c.body;
430 while b != EMPTY {
431 let following = self.next[b as usize];
432 let child = (first + c.quadrant(self.pos[b as usize])) as usize;
433 self.next[b as usize] = self.cells[child].body;
434 self.cells[child].body = b;
435 b = following;
436 }
437 }
438
439 fn accumulate(&mut self, cell: usize) -> (f32, f32, f32) {
440 let c = self.cells[cell];
441 let (mut n, mut sx, mut sy) = (0., 0., 0.);
442 if c.is_leaf() {
443 let mut b = c.body;
444 while b != EMPTY {
445 let p = self.pos[b as usize];
446 n += 1.;
447 sx += p.x;
448 sy += p.y;
449 b = self.next[b as usize];
450 }
451 } else {
452 for q in 0..4 {
453 let (cn, cx, cy) = self.accumulate((c.children + q) as usize);
454 n += cn;
455 sx += cx * cn;
456 sy += cy * cn;
457 }
458 }
459 let (cx, cy) = if n > 0. { (sx / n, sy / n) } else { (0., 0.) };
460 let cell = &mut self.cells[cell];
461 cell.count = n;
462 cell.cx = cx;
463 cell.cy = cy;
464 (n, cx, cy)
465 }
466
467 fn force(&mut self, i: usize, strength: f32, theta2: f32, max2: f32) -> (f32, f32) {
469 const DISTANCE_MIN2: f32 = 1.;
470 let p = self.pos[i];
471 let (mut fx, mut fy) = (0., 0.);
472 self.stack.clear();
473 self.stack.push(0);
474 while let Some(ix) = self.stack.pop() {
475 let c = self.cells[ix as usize];
476 if c.count == 0. {
477 continue;
478 }
479 if !c.is_leaf() {
480 let (x, y) = (c.cx - p.x, c.cy - p.y);
481 let l = x * x + y * y;
482 if c.size * c.size / theta2 < l {
483 if l < max2 {
485 let k = strength * c.count / l.max(DISTANCE_MIN2);
486 fx += x * k;
487 fy += y * k;
488 }
489 } else {
490 self.stack.extend((0..4).map(|q| c.children + q));
491 }
492 continue;
493 }
494 let mut b = c.body;
495 while b != EMPTY {
496 if b as usize != i {
497 let q = self.pos[b as usize];
498 let (x, y) = (q.x - p.x, q.y - p.y);
499 let l = x * x + y * y;
500 if l < max2 {
501 let k = strength / l.max(DISTANCE_MIN2);
502 fx += x * k;
503 fy += y * k;
504 }
505 }
506 b = self.next[b as usize];
507 }
508 }
509 (fx, fy)
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 fn bodies(n: usize) -> Vec<Body> {
518 let mut b = vec![
519 Body {
520 radius: 8.,
521 ..Default::default()
522 };
523 n
524 ];
525 Simulation::initialize_positions(&mut b, 0);
526 b
527 }
528
529 #[test]
530 fn barnes_hut_matches_brute_force() {
531 let b = bodies(300);
532 let mut tree = QuadTree::default();
533 tree.build(&b);
534 for i in [0, 17, 150, 299] {
535 let exact = tree.force(i, -1., 0., f32::MAX);
537 let (mut fx, mut fy, mut scale) = (0., 0., 0.);
538 for (j, o) in b.iter().enumerate() {
539 if j != i {
540 let (x, y) = (o.pos.x - b[i].pos.x, o.pos.y - b[i].pos.y);
541 let l = (x * x + y * y).max(1.);
542 fx -= x / l;
543 fy -= y / l;
544 scale += x.hypot(y) / l;
545 }
546 }
547 assert!((exact.0 - fx).abs() < 1e-3 && (exact.1 - fy).abs() < 1e-3);
548 let approx = tree.force(i, -1., 0.81, f32::MAX);
549 let err = (approx.0 - fx).hypot(approx.1 - fy) / scale;
551 assert!(err < 0.02, "relative error {err}");
552 }
553 }
554
555 #[test]
556 fn coincident_points_do_not_overflow() {
557 let mut b = vec![
558 Body {
559 radius: 5.,
560 ..Default::default()
561 };
562 50
563 ];
564 let mut sim = Simulation::new(ForceConfig::default());
565 let degrees = vec![0; 50];
566 for _ in 0..20 {
567 sim.tick(&mut b, &[], °rees, &[]);
568 }
569 assert!(b.iter().all(|b| b.pos.x.is_finite() && b.pos.y.is_finite()));
570 }
571
572 #[test]
573 fn simulation_settles_with_links_near_rest_length() {
574 let n = 60;
575 let mut b = bodies(n);
576 let links: Vec<_> = (1..n).map(|i| (i, (i - 1) / 2)).collect();
577 let mut degrees = vec![0; n];
578 for &(s, t) in &links {
579 degrees[s] += 1;
580 degrees[t] += 1;
581 }
582 let mut sim = Simulation::new(ForceConfig::default());
583 let mut ticks = 0;
584 while sim.is_active() {
585 sim.tick(&mut b, &links, °rees, &[]);
586 ticks += 1;
587 }
588 assert!(ticks < 400);
589 let mean: f32 = links
590 .iter()
591 .map(|&(s, t)| (b[s].pos.x - b[t].pos.x).hypot(b[s].pos.y - b[t].pos.y))
592 .sum::<f32>()
593 / links.len() as f32;
594 assert!(mean > 30. && mean < 150., "mean link length {mean}");
595 }
596}