1use glam::Vec3;
33
34use crate::util::dot_simd;
35
36#[derive(Debug, Clone, Copy)]
40pub(crate) struct OctNode {
41 min: Vec3,
42 max: Vec3,
43 mass: f32,
44 com: Vec3,
45 body: Option<u32>,
46 children: [u32; 8], has_children: bool,
48}
49
50impl OctNode {
51 fn empty(min: Vec3, max: Vec3) -> Self {
52 Self {
53 min,
54 max,
55 mass: 0.0,
56 com: Vec3::ZERO,
57 body: None,
58 children: [u32::MAX; 8],
59 has_children: false,
60 }
61 }
62
63 #[cfg(test)]
65 pub(crate) fn mass(&self) -> f32 {
66 self.mass
67 }
68
69 #[cfg(test)]
71 pub(crate) fn com(&self) -> Vec3 {
72 self.com
73 }
74
75 #[inline]
76 fn size(&self) -> f32 {
77 (self.max - self.min).max_element()
78 }
79
80 #[inline]
81 fn center(&self) -> Vec3 {
82 (self.min + self.max) * 0.5
83 }
84
85 #[inline]
89 fn octant_of(&self, p: Vec3) -> usize {
90 let c = self.center();
91 let east = (p.x >= c.x) as usize;
92 let north = (p.y >= c.y) as usize;
93 let up = (p.z >= c.z) as usize;
94 (up << 2) | (north << 1) | east
95 }
96
97 #[inline]
98 fn child_bounds(&self, q: usize) -> (Vec3, Vec3) {
99 let c = self.center();
100 let (x_min, x_max) = if q & 1 == 1 {
101 (c.x, self.max.x)
102 } else {
103 (self.min.x, c.x)
104 };
105 let (y_min, y_max) = if q & 2 == 2 {
106 (c.y, self.max.y)
107 } else {
108 (self.min.y, c.y)
109 };
110 let (z_min, z_max) = if q & 4 == 4 {
111 (c.z, self.max.z)
112 } else {
113 (self.min.z, c.z)
114 };
115 (
116 Vec3::new(x_min, y_min, z_min),
117 Vec3::new(x_max, y_max, z_max),
118 )
119 }
120}
121
122pub(crate) fn build_octree(arena: &mut Vec<OctNode>, positions: &[Vec3], min_cell_size: f32) {
128 arena.clear();
129 let n = positions.len();
130 if n == 0 {
131 return;
132 }
133
134 let mut min = positions[0];
135 let mut max = positions[0];
136 for p in &positions[1..] {
137 min = min.min(*p);
138 max = max.max(*p);
139 }
140 let extent = (max - min).max_element().max(min_cell_size);
143 let center = (min + max) * 0.5;
144 let half = Vec3::splat(extent * 0.5);
145 let min = center - half;
146 let max = center + half;
147
148 arena.push(OctNode::empty(min, max));
149 for (i, p) in positions.iter().enumerate() {
150 insert(arena, 0, i as u32, *p, min_cell_size);
151 }
152 finalise(arena, 0);
153}
154
155fn insert(tree: &mut Vec<OctNode>, idx: usize, body: u32, pos: Vec3, min_cell_size: f32) {
156 if !tree[idx].has_children && tree[idx].body.is_none() && tree[idx].mass == 0.0 {
158 tree[idx].body = Some(body);
159 tree[idx].com = pos;
160 tree[idx].mass = 1.0;
161 return;
162 }
163
164 if !tree[idx].has_children {
166 if tree[idx].size() <= min_cell_size {
167 let new_mass = tree[idx].mass + 1.0;
168 tree[idx].com = (tree[idx].com * tree[idx].mass + pos) / new_mass;
169 tree[idx].mass = new_mass;
170 return;
171 }
172 let existing = tree[idx].body.take().expect("leaf without a body");
173 let existing_pos = tree[idx].com;
174 tree[idx].mass = 0.0;
175 tree[idx].com = Vec3::ZERO;
176 tree[idx].has_children = true;
177
178 let q_existing = tree[idx].octant_of(existing_pos);
179 let c_existing = create_or_get_child(tree, idx, q_existing);
180 insert(tree, c_existing, existing, existing_pos, min_cell_size);
181
182 let q_new = tree[idx].octant_of(pos);
183 let c_new = create_or_get_child(tree, idx, q_new);
184 insert(tree, c_new, body, pos, min_cell_size);
185 return;
186 }
187
188 let q = tree[idx].octant_of(pos);
190 let c = create_or_get_child(tree, idx, q);
191 insert(tree, c, body, pos, min_cell_size);
192}
193
194fn create_or_get_child(tree: &mut Vec<OctNode>, parent: usize, octant: usize) -> usize {
195 let existing = tree[parent].children[octant];
196 if existing != u32::MAX {
197 return existing as usize;
198 }
199 let (cmin, cmax) = tree[parent].child_bounds(octant);
200 let idx = tree.len();
201 tree.push(OctNode::empty(cmin, cmax));
202 tree[parent].children[octant] = idx as u32;
203 idx
204}
205
206fn finalise(tree: &mut [OctNode], idx: usize) {
207 if !tree[idx].has_children {
208 return;
209 }
210 let children = tree[idx].children;
211 let mut mass = 0.0;
212 let mut com = Vec3::ZERO;
213 for &c in &children {
214 if c == u32::MAX {
215 continue;
216 }
217 finalise(tree, c as usize);
218 let child = tree[c as usize];
219 mass += child.mass;
220 com += child.com * child.mass;
221 }
222 if mass > 0.0 {
223 com /= mass;
224 }
225 tree[idx].mass = mass;
226 tree[idx].com = com;
227}
228
229pub(crate) fn accumulate_repulsion_3d(
234 tree: &[OctNode],
235 idx: usize,
236 target_pos: Vec3,
237 target_idx: u32,
238 theta2: f32,
239 repulsion: f32,
240) -> Vec3 {
241 let node = &tree[idx];
242 if node.mass <= 0.0 {
243 return Vec3::ZERO;
244 }
245
246 if !node.has_children {
247 if let Some(body) = node.body {
248 if body == target_idx {
249 let residual_mass = node.mass - 1.0;
250 if residual_mass <= 0.0 {
251 return Vec3::ZERO;
252 }
253 return pair_force_3d(target_pos, node.com, residual_mass, repulsion);
254 }
255 }
256 return pair_force_3d(target_pos, node.com, node.mass, repulsion);
257 }
258
259 let delta = node.com - target_pos;
260 let dist2 = delta.length_squared();
261 let size = node.size();
262 if size * size < theta2 * dist2 {
263 return pair_force_3d(target_pos, node.com, node.mass, repulsion);
264 }
265
266 let mut total = Vec3::ZERO;
267 for &c in &node.children {
268 if c == u32::MAX {
269 continue;
270 }
271 total +=
272 accumulate_repulsion_3d(tree, c as usize, target_pos, target_idx, theta2, repulsion);
273 }
274 total
275}
276
277#[inline]
280fn pair_force_3d(from: Vec3, to: Vec3, mass: f32, repulsion: f32) -> Vec3 {
281 let delta = to - from;
282 let dist2 = delta.length_squared() + 0.01;
283 let dist = dist2.sqrt();
284 let dir = delta / dist;
285 let magnitude = repulsion * mass / dist2;
286 dir * magnitude
287}
288
289#[allow(dead_code)]
296pub(crate) fn sum_positions_sqnorm(positions: &[Vec3]) -> f32 {
297 if positions.is_empty() {
298 return 0.0;
299 }
300 let flat: &[f32] = unsafe {
304 std::slice::from_raw_parts(positions.as_ptr() as *const f32, positions.len() * 3)
305 };
306 dot_simd(flat, flat)
307}