Skip to main content

gpui_graph/
layout.rs

1//! A d3-force style simulation: link, many-body (Barnes–Hut), collide and
2//! centering forces, integrated with velocity Verlet and an annealing `alpha`.
3
4/// Tuning knobs, named after their d3-force counterparts.
5///
6/// ```ignore
7/// ForceConfig::default().with_link_distance(90.).with_charge(-400.)
8/// ```
9#[derive(Clone, Debug)]
10#[non_exhaustive]
11pub struct ForceConfig {
12    /// `forceLink().distance()`: rest length of an edge, in world units.
13    pub link_distance: f32,
14    /// `forceManyBody().strength()`: negative repels.
15    pub charge: f32,
16    /// `forceManyBody().theta()`: Barnes–Hut accuracy, lower is more exact.
17    pub theta: f32,
18    /// Ignore charge beyond this distance; keeps far components from drifting apart.
19    pub charge_distance_max: f32,
20    /// `forceCollide()` padding added to each node's radius.
21    pub collide_padding: f32,
22    pub collide_strength: f32,
23    /// `forceX/forceY` pull toward the origin; holds disconnected components together.
24    pub gravity: f32,
25    /// Pull of each group (community) member toward its group's centroid, so
26    /// communities form visible clusters. 0 disables.
27    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    /// Sets `alpha_min` and derives `alpha_decay` so the layout settles in about `ticks` steps.
80    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    /// Pinned position (while dragged, or pinned by the user).
104    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    /// Restart the annealing from `alpha` (d3 `simulation.alpha(a).restart()`).
132    pub fn reheat(&mut self, alpha: f32) {
133        self.alpha = self.alpha.max(alpha);
134    }
135
136    /// Place bodies without a position on a phyllotaxis spiral, like d3 does.
137    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    /// Advance one step. `groups` lists body indices per cluster for the group force.
149    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        // xorshift; only used to separate coincident points.
188        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    /// Spatial-hash collision, so large result sets stay O(n).
259    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    /// Number of bodies below this cell (each has unit charge).
327    count: f32,
328    cx: f32,
329    cy: f32,
330    /// Leaf payload: first body, chained through `QuadTree::next`.
331    body: u32,
332    /// Index of the first of four consecutive children, or `EMPTY` for a leaf.
333    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/// Barnes–Hut quadtree over unit charges. Rebuilt every tick; buffers are reused.
363#[derive(Default)]
364struct QuadTree {
365    cells: Vec<Cell>,
366    /// Intrusive linked list of bodies sharing a leaf.
367    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                    // Empty leaf, or (nearly) coincident points: chain here.
405                    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    /// Turn a leaf into an inner cell, moving its bodies into the new children.
418    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    /// Velocity change on body `i` from all other charges (d3 `forceManyBody`).
468    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                    // Far enough: treat the whole cell as one aggregate charge.
484                    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            // theta = 0 forces exact summation through the tree.
536            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            // Relative to the summed pair magnitudes: the net force can cancel to ~0.
550            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, &[], &degrees, &[]);
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, &degrees, &[]);
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}