use std::{fmt, fmt::Formatter};
#[cfg(feature = "encode")]
use bincode::{Decode, Encode};
use lin_alg::f64::Vec3;
use rayon::prelude::*;
#[derive(Debug)]
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
pub struct BhConfig {
pub θ: f64,
pub max_bodies_per_node: usize,
pub max_tree_depth: usize,
}
impl Default for BhConfig {
fn default() -> Self {
Self {
θ: 0.5,
max_bodies_per_node: 1,
max_tree_depth: 15, }
}
}
pub trait BodyModel {
fn posit(&self) -> Vec3;
fn mass(&self) -> f64;
}
#[derive(Clone, Debug)]
pub struct Cube {
pub center: Vec3,
pub width: f64,
}
impl Cube {
pub fn from_bodies<T: BodyModel>(bodies: &[T], pad: f64, z_offset: bool) -> Option<Self> {
if bodies.is_empty() {
return None;
}
let mut x_min = f64::MAX;
let mut x_max = f64::MIN;
let mut y_min = f64::MAX;
let mut y_max = f64::MIN;
let mut z_min = f64::MAX;
let mut z_max = f64::MIN;
for body in bodies {
let p = &body.posit();
x_min = x_min.min(p.x);
x_max = x_max.max(p.x);
y_min = y_min.min(p.y);
y_max = y_max.max(p.y);
z_min = z_min.min(p.z);
z_max = z_max.max(p.z);
}
x_min -= pad;
x_max += pad;
y_min -= pad;
y_max += pad;
z_min -= pad;
z_max += pad;
if z_offset {
z_max += 1e-5;
}
let x_size = x_max - x_min;
let y_size = y_max - y_min;
let z_size = z_max - z_min;
let mut width = x_size.max(y_size).max(z_size);
let center = Vec3::new(
(x_max + x_min) / 2.,
(y_max + y_min) / 2.,
(z_max + z_min) / 2.,
);
Some(Self::new(center, width))
}
pub fn new(center: Vec3, width: f64) -> Self {
Self { center, width }
}
pub(crate) fn divide_into_octants(&self) -> [Self; 8] {
let width = self.width / 2.;
let wd2 = self.width / 4.;
[
Self::new(self.center + Vec3::new(-wd2, -wd2, -wd2), width),
Self::new(self.center + Vec3::new(wd2, -wd2, -wd2), width),
Self::new(self.center + Vec3::new(-wd2, wd2, -wd2), width),
Self::new(self.center + Vec3::new(wd2, wd2, -wd2), width),
Self::new(self.center + Vec3::new(-wd2, -wd2, wd2), width),
Self::new(self.center + Vec3::new(wd2, -wd2, wd2), width),
Self::new(self.center + Vec3::new(-wd2, wd2, wd2), width),
Self::new(self.center + Vec3::new(wd2, wd2, wd2), width),
]
}
}
#[derive(Debug)]
pub struct Node {
pub id: usize,
pub bounding_box: Cube,
pub children: Vec<usize>,
mass: f64,
center_of_mass: Vec3,
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Id: {}, Width: {:.3}, Ch: {:?}",
self.id, self.bounding_box.width, self.children
)
}
}
#[derive(Debug)]
pub struct Tree {
pub nodes: Vec<Node>,
}
impl Tree {
pub fn new<T: BodyModel>(bodies: &[T], bb: &Cube, config: &BhConfig) -> Self {
let body_refs: Vec<&T> = bodies.iter().collect();
let mut nodes = Vec::with_capacity(bodies.len() * 7 / 4);
let mut current_node_i: usize = 0;
let mut stack = Vec::new();
stack.push((body_refs.to_vec(), bb.clone(), None, 0));
while let Some((bodies_, bb_, parent_id, depth)) = stack.pop() {
if depth > config.max_tree_depth {
break;
}
let (center_of_mass, mass) = center_of_mass(&bodies_);
let node_id = current_node_i;
nodes.push(Node {
id: node_id,
bounding_box: bb_.clone(),
mass,
center_of_mass,
children: Vec::new(),
});
current_node_i += 1;
if let Some(pid) = parent_id {
let n: &mut Node = &mut nodes[pid];
n.children.push(node_id);
}
if bodies_.len() > config.max_bodies_per_node {
let octants = bb_.divide_into_octants();
let bodies_by_octant = partition(&bodies_, &bb_);
for (i, octant) in octants.into_iter().enumerate() {
if !bodies_by_octant[i].is_empty() {
stack.push((
bodies_by_octant[i].clone(),
octant,
Some(node_id),
depth + 1,
));
}
}
}
}
nodes.sort_by(|l, r| l.id.partial_cmp(&r.id).unwrap());
Self { nodes }
}
pub fn leaves(&self, posit_target: Vec3, id_target: usize, config: &BhConfig) -> Vec<&Node> {
let mut result = Vec::new();
if self.nodes.is_empty() {
return result;
}
let node_i = 0;
let mut stack = Vec::new();
stack.push(node_i);
while let Some(current_node_i) = stack.pop() {
let node = &self.nodes[current_node_i];
if node.children.len() <= config.max_bodies_per_node {
result.push(node);
continue;
}
let dist = (posit_target - node.center_of_mass).magnitude();
if dist < 1e-10 {
continue;
}
if node.bounding_box.width / dist < config.θ {
result.push(node);
} else {
for &child_i in &node.children {
stack.push(child_i);
}
}
}
result
}
}
fn center_of_mass<T: BodyModel>(bodies: &[&T]) -> (Vec3, f64) {
let mut mass = 0.;
let mut center_of_mass = Vec3::new_zero();
for body in bodies {
mass += body.mass();
center_of_mass += body.posit() * body.mass();
}
if mass.abs() > f64::EPSILON {
center_of_mass /= mass;
}
(center_of_mass, mass)
}
fn partition<'a, T: BodyModel>(bodies: &[&'a T], bb: &Cube) -> [Vec<&'a T>; 8] {
let mut result: [Vec<&T>; 8] = Default::default();
for body in bodies {
let mut index = 0;
if body.posit().x > bb.center.x {
index |= 0b001;
}
if body.posit().y > bb.center.y {
index |= 0b010;
}
if body.posit().z > bb.center.z {
index |= 0b100;
}
result[index].push(body);
}
result
}
type AccFn<'a> = Box<dyn Fn(Vec3, f64, f64) -> Vec3 + Send + Sync + 'a>;
pub fn acc_bh(
posit_target: Vec3,
id_target: usize,
tree: &Tree,
config: &BhConfig,
acc_fn: AccFn,
) -> Vec3 {
tree.leaves(posit_target, id_target, config)
.par_iter()
.filter_map(|leaf| {
let acc_diff = leaf.center_of_mass - posit_target;
let dist = acc_diff.magnitude();
if dist < 1e-8 {
return None;
}
let acc_dir = acc_diff / dist;
Some(acc_fn(acc_dir, leaf.mass, dist))
})
.reduce(Vec3::new_zero, |acc, elem| acc + elem)
}