1const NONE: usize = usize::MAX;
16
17const MAX_DEPTH: u32 = 24;
20
21const EPS2: f32 = 1e-6;
24
25const MIN_DIST2: f32 = 1.0;
32
33#[derive(Clone)]
34struct Cell {
35 center: [f32; 2],
36 half: f32,
37 mass: f32,
39 sum: [f32; 2],
41 kids: [usize; 4],
43 body: usize,
45}
46
47pub struct QuadTree {
48 cells: Vec<Cell>,
49 next: Vec<usize>,
52}
53
54impl QuadTree {
55 pub fn build(pos: &[[f32; 2]]) -> Self {
56 let mut t = QuadTree { cells: Vec::new(), next: vec![NONE; pos.len()] };
57 if pos.is_empty() {
58 return t;
59 }
60 let (mut lo, mut hi) = ([f32::MAX; 2], [f32::MIN; 2]);
65 for p in pos {
66 lo[0] = lo[0].min(p[0]); lo[1] = lo[1].min(p[1]);
67 hi[0] = hi[0].max(p[0]); hi[1] = hi[1].max(p[1]);
68 }
69 let half = ((hi[0] - lo[0]).max(hi[1] - lo[1]) * 0.5).max(1.0);
72 let center = [(lo[0] + hi[0]) * 0.5, (lo[1] + hi[1]) * 0.5];
73 t.cells.push(Cell { center, half, mass: 0.0, sum: [0.0, 0.0], kids: [NONE; 4], body: NONE });
74 for i in 0..pos.len() {
75 t.insert(0, i, pos, 0);
76 }
77 t
78 }
79
80 fn quadrant(center: [f32; 2], p: [f32; 2]) -> usize {
81 (if p[0] >= center[0] { 1 } else { 0 }) | (if p[1] < center[1] { 2 } else { 0 })
83 }
84
85 fn child_cell(parent: &Cell, q: usize) -> Cell {
86 let h = parent.half * 0.5;
87 let dx = if q & 1 == 1 { h } else { -h };
88 let dy = if q & 2 == 2 { -h } else { h };
89 Cell {
90 center: [parent.center[0] + dx, parent.center[1] + dy],
91 half: h,
92 mass: 0.0,
93 sum: [0.0, 0.0],
94 kids: [NONE; 4],
95 body: NONE,
96 }
97 }
98
99 fn insert(&mut self, cell: usize, b: usize, pos: &[[f32; 2]], depth: u32) {
100 self.cells[cell].mass += 1.0;
101 self.cells[cell].sum[0] += pos[b][0];
102 self.cells[cell].sum[1] += pos[b][1];
103
104 let is_internal = self.cells[cell].kids.iter().any(|&k| k != NONE);
105 if is_internal {
106 let q = Self::quadrant(self.cells[cell].center, pos[b]);
107 let kid = self.ensure_kid(cell, q);
108 self.insert(kid, b, pos, depth + 1);
109 return;
110 }
111
112 let existing = self.cells[cell].body;
113 if existing == NONE {
114 self.cells[cell].body = b;
115 return;
116 }
117 if depth >= MAX_DEPTH {
118 self.next[b] = existing;
122 self.cells[cell].body = b;
123 return;
124 }
125
126 self.cells[cell].body = NONE;
128 for who in [existing, b] {
129 let q = Self::quadrant(self.cells[cell].center, pos[who]);
130 let kid = self.ensure_kid(cell, q);
131 self.insert(kid, who, pos, depth + 1);
134 }
135 }
136
137 fn ensure_kid(&mut self, cell: usize, q: usize) -> usize {
138 let existing = self.cells[cell].kids[q];
139 if existing != NONE {
140 return existing;
141 }
142 let c = Self::child_cell(&self.cells[cell], q);
143 self.cells.push(c);
144 let idx = self.cells.len() - 1;
145 self.cells[cell].kids[q] = idx;
146 idx
147 }
148
149 pub fn repulsion(&self, i: usize, pos: &[[f32; 2]], theta: f32, strength: f32) -> [f32; 2] {
156 let mut f = [0.0f32, 0.0];
157 if self.cells.is_empty() || i >= pos.len() {
158 return f;
159 }
160 self.walk(0, i, pos, theta, strength, &mut f);
161 f
162 }
163
164 fn walk(&self, cell: usize, i: usize, pos: &[[f32; 2]], theta: f32, strength: f32, f: &mut [f32; 2]) {
165 let c = &self.cells[cell];
166 if c.mass == 0.0 {
167 return;
168 }
169 let is_internal = c.kids.iter().any(|&k| k != NONE);
170
171 if !is_internal {
172 let mut b = c.body;
174 while b != NONE {
175 if b != i {
176 Self::pair(pos[i], pos[b], i, b, strength, f);
177 }
178 b = self.next[b];
179 }
180 return;
181 }
182
183 let centroid = [c.sum[0] / c.mass, c.sum[1] / c.mass];
184 let dx = centroid[0] - pos[i][0];
185 let dy = centroid[1] - pos[i][1];
186 let d2 = dx * dx + dy * dy;
187 if d2 > 0.0 && (c.half * 2.0) * (c.half * 2.0) < theta * theta * d2 {
191 let d2 = Self::clamp_near(d2);
192 let mag = strength * c.mass / d2;
193 f[0] += dx * mag;
194 f[1] += dy * mag;
195 return;
196 }
197 for &k in &c.kids {
198 if k != NONE {
199 self.walk(k, i, pos, theta, strength, f);
200 }
201 }
202 }
203
204 fn clamp_near(d2: f32) -> f32 {
207 if d2 < MIN_DIST2 { (MIN_DIST2 * d2).sqrt() } else { d2 }
208 }
209
210 fn pair(a: [f32; 2], b: [f32; 2], ai: usize, bi: usize, strength: f32, f: &mut [f32; 2]) {
211 let mut dx = b[0] - a[0];
212 let mut dy = b[1] - a[1];
213 let mut d2 = dx * dx + dy * dy;
214 if d2 < EPS2 {
215 let s = (ai.wrapping_mul(31).wrapping_add(bi)) as f32;
226 let (jx, jy) = ((s * 0.7).sin(), (s * 1.3).cos());
227 let n = (jx * jx + jy * jy).sqrt().max(1e-6);
228 dx = jx / n;
229 dy = jy / n;
230 d2 = MIN_DIST2;
231 }
232 let d2 = Self::clamp_near(d2);
233 let mag = strength / d2;
234 f[0] += dx * mag;
235 f[1] += dy * mag;
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 fn brute_force(pos: &[[f32; 2]], i: usize, strength: f32) -> [f32; 2] {
250 let mut f = [0.0f32, 0.0];
251 for (j, p) in pos.iter().enumerate() {
252 if j == i { continue; }
253 let dx = p[0] - pos[i][0];
254 let dy = p[1] - pos[i][1];
255 let d2 = (dx * dx + dy * dy).max(1e-6);
256 let mag = strength / d2;
257 f[0] += dx * mag;
258 f[1] += dy * mag;
259 }
260 f
261 }
262
263 fn scatter(n: usize) -> Vec<[f32; 2]> {
265 let mut s = 12345u64;
266 (0..n)
267 .map(|_| {
268 let mut nxt = || {
269 s ^= s >> 12; s ^= s << 25; s ^= s >> 27;
270 ((s.wrapping_mul(0x2545F4914F6CDD1D) >> 33) as f32) / (1u64 << 31) as f32
271 };
272 [(nxt() - 0.5) * 1000.0, (nxt() - 0.5) * 1000.0]
273 })
274 .collect()
275 }
276
277 #[test]
278 fn theta_zero_is_exact_because_no_cell_can_be_approximated() {
279 let pos = scatter(60);
283 let t = QuadTree::build(&pos);
284 for i in 0..pos.len() {
285 let got = t.repulsion(i, &pos, 0.0, -100.0);
286 let want = brute_force(&pos, i, -100.0);
287 assert!(
288 (got[0] - want[0]).abs() < 0.05 && (got[1] - want[1]).abs() < 0.05,
289 "body {i}: tree {got:?} vs brute {want:?}"
290 );
291 }
292 }
293
294 #[test]
295 fn default_theta_approximates_brute_force_within_tolerance() {
296 let pos = scatter(200);
297 let t = QuadTree::build(&pos);
298 let mut worst = 0.0f32;
299 for i in 0..pos.len() {
300 let got = t.repulsion(i, &pos, 0.9, -100.0);
301 let want = brute_force(&pos, i, -100.0);
302 let mag = (want[0] * want[0] + want[1] * want[1]).sqrt().max(1e-3);
303 let err = ((got[0] - want[0]).powi(2) + (got[1] - want[1]).powi(2)).sqrt() / mag;
304 worst = worst.max(err);
305 }
306 assert!(worst < 0.30, "worst relative error {worst} exceeds 30% at theta=0.9");
307 }
308
309 #[test]
314 fn accuracy_degrades_monotonically_with_theta() {
315 let pos = scatter(200);
316 let t = QuadTree::build(&pos);
317 let err_at = |theta: f32| {
318 let mut worst = 0.0f32;
319 for i in 0..pos.len() {
320 let got = t.repulsion(i, &pos, theta, -100.0);
321 let want = brute_force(&pos, i, -100.0);
322 let mag = (want[0] * want[0] + want[1] * want[1]).sqrt().max(1e-3);
323 let e = ((got[0] - want[0]).powi(2) + (got[1] - want[1]).powi(2)).sqrt() / mag;
324 worst = worst.max(e);
325 }
326 worst
327 };
328 let (e0, e3, e5, e9) = (err_at(0.0), err_at(0.3), err_at(0.5), err_at(0.9));
329 assert!(e0 < 1e-4, "theta=0 must be exact, got {e0}");
330 assert!(e3 < e5 && e5 < e9, "error must grow with theta: {e3} {e5} {e9}");
331 }
332
333 #[test]
338 fn every_cell_holds_each_body_beneath_it_exactly_once() {
339 let pos = scatter(200);
340 let t = QuadTree::build(&pos);
341 let n = pos.len() as f32;
342 assert_eq!(t.cells[0].mass, n, "root mass must be the body count");
343 let mean = [
344 pos.iter().map(|p| p[0]).sum::<f32>() / n,
345 pos.iter().map(|p| p[1]).sum::<f32>() / n,
346 ];
347 assert!((t.cells[0].sum[0] / n - mean[0]).abs() < 0.01);
348 assert!((t.cells[0].sum[1] / n - mean[1]).abs() < 0.01);
349
350 let mut reachable = 0usize;
351 for c in &t.cells {
352 if c.kids.iter().any(|&k| k != NONE) {
353 let km: f32 = c.kids.iter().filter(|&&k| k != NONE).map(|&k| t.cells[k].mass).sum();
354 assert!((km - c.mass).abs() < 1e-3, "cell mass {} vs kids {km}", c.mass);
355 } else {
356 let mut b = c.body;
358 let mut chain = 0.0f32;
359 while b != NONE {
360 reachable += 1;
361 chain += 1.0;
362 b = t.next[b];
363 }
364 assert!((chain - c.mass).abs() < 1e-3, "leaf mass {} vs chain {chain}", c.mass);
365 }
366 }
367 assert_eq!(reachable, pos.len(), "every body must sit in exactly one leaf");
368 }
369
370 #[test]
371 fn a_body_never_repels_itself() {
372 let pos = vec![[10.0, 10.0]];
375 let t = QuadTree::build(&pos);
376 assert_eq!(t.repulsion(0, &pos, 0.9, -100.0), [0.0, 0.0]);
377 }
378
379 #[test]
380 fn coincident_bodies_do_not_produce_nan() {
381 let pos = vec![[5.0, 5.0], [5.0, 5.0], [5.0, 5.0]];
385 let t = QuadTree::build(&pos);
386 for i in 0..3 {
387 let f = t.repulsion(i, &pos, 0.9, -100.0);
388 assert!(f[0].is_finite() && f[1].is_finite(), "body {i} produced {f:?}");
389 }
390 }
391
392 #[test]
397 fn coincident_bodies_are_nudged_not_launched() {
398 let pos = vec![[5.0, 5.0], [5.0, 5.0], [5.0, 5.0]];
399 let t = QuadTree::build(&pos);
400 for i in 0..3 {
401 let f = t.repulsion(i, &pos, 0.9, -100.0);
402 let mag = (f[0] * f[0] + f[1] * f[1]).sqrt();
403 assert!(mag <= 200.0, "body {i} force {mag} exceeds two bodies' worth of strength");
404 }
405 let t2 = QuadTree::build(&pos);
407 assert_eq!(t.repulsion(0, &pos, 0.9, -100.0), t2.repulsion(0, &pos, 0.9, -100.0));
408 }
409
410 #[test]
417 fn a_near_coincident_pair_is_softened_to_about_the_strength() {
418 let pos = vec![[0.0, 0.0], [0.01, 0.0]];
419 let t = QuadTree::build(&pos);
420 let f = t.repulsion(0, &pos, 0.9, -800.0);
421 let mag = (f[0] * f[0] + f[1] * f[1]).sqrt();
422 assert!(
423 (mag - 800.0).abs() < 1.0,
424 "expected the softened |strength| = 800, got {mag}; unsoftened this is 80000"
425 );
426 assert!(f[0] < 0.0, "and it must still point away from the other body, got {f:?}");
427 }
428
429 #[test]
436 fn coincident_bodies_are_pushed_in_different_directions() {
437 let pos = vec![[5.0, 5.0], [5.0, 5.0]];
438 let t = QuadTree::build(&pos);
439 let f0 = t.repulsion(0, &pos, 0.9, -100.0);
440 let f1 = t.repulsion(1, &pos, 0.9, -100.0);
441 let spread = ((f0[0] - f1[0]).powi(2) + (f0[1] - f1[1]).powi(2)).sqrt();
442 assert!(
443 spread > 1.0,
444 "bodies got near-identical pushes {f0:?} and {f1:?}; a rigid cluster never separates"
445 );
446 }
447
448 #[test]
449 fn deeply_clustered_bodies_terminate() {
450 let pos: Vec<[f32; 2]> = (0..50).map(|i| [1.0 + i as f32 * 1e-7, 1.0]).collect();
452 let t = QuadTree::build(&pos);
453 let f = t.repulsion(0, &pos, 0.9, -100.0);
454 assert!(f[0].is_finite() && f[1].is_finite());
455 }
456
457 #[test]
458 fn an_empty_set_builds_and_answers_nothing() {
459 let pos: Vec<[f32; 2]> = vec![];
460 let t = QuadTree::build(&pos);
461 assert_eq!(t.repulsion(0, &pos, 0.9, -100.0), [0.0, 0.0]);
462 }
463
464 #[test]
465 fn repulsion_pushes_apart_and_attraction_pulls_together() {
466 let pos = vec![[0.0, 0.0], [10.0, 0.0]];
468 let t = QuadTree::build(&pos);
469 let f = t.repulsion(0, &pos, 0.9, -100.0);
470 assert!(f[0] < 0.0, "body 0 should be pushed away from body 1 (leftward), got {f:?}");
471 let a = t.repulsion(0, &pos, 0.9, 100.0);
472 assert!(a[0] > 0.0, "positive strength should attract, got {a:?}");
473 }
474
475 #[test]
480 fn force_magnitude_falls_off_as_inverse_distance() {
481 let near = vec![[0.0, 0.0], [10.0, 0.0]];
482 let far = vec![[0.0, 0.0], [20.0, 0.0]];
483 let fn_ = QuadTree::build(&near).repulsion(0, &near, 0.0, -100.0)[0].abs();
484 let ff = QuadTree::build(&far).repulsion(0, &far, 0.0, -100.0)[0].abs();
485 assert!((fn_ - 10.0).abs() < 1e-3, "expected |strength|/d = 10, got {fn_}");
486 assert!((ff - 5.0).abs() < 1e-3, "expected |strength|/d = 5, got {ff}");
487 }
488}