#![allow(non_ascii_idents)]
#![allow(mixed_script_confusables)]
use std::{fmt, fmt::Formatter};
#[cfg(feature = "encode")]
use bincode::{Decode, Encode};
use lin_alg::f64::Vec3;
use rayon::prelude::*;
#[derive(Clone, 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)]
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
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 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>,
pub mass: f64,
pub center_of_mass: Vec3,
pub body_ids: Vec<usize>,
}
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, Default)]
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();
let body_ids_init: Vec<usize> = body_refs.iter().enumerate().map(|(id, _)| id).collect();
stack.push((body_refs.to_vec(), body_ids_init, bb.clone(), None, 0));
while let Some((bodies_, body_ids, 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(),
body_ids: body_ids.clone(), });
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_, &body_ids, &bb_);
for (i, octant) in octants.into_iter().enumerate() {
if !bodies_by_octant[i].is_empty() {
let mut bto = Vec::with_capacity(bodies_by_octant[i].len());
let mut ids_this_octant = Vec::with_capacity(bodies_by_octant[i].len());
for (body, id) in &bodies_by_octant[i] {
bto.push(*body);
ids_this_octant.push(*id);
}
stack.push((bto, ids_this_octant, 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, 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 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],
body_ids: &[usize],
bb: &Cube,
) -> [Vec<(&'a T, usize)>; 8] {
let mut result: [Vec<(&'a T, usize)>; 8] = Default::default();
for (i, body) in bodies.iter().enumerate() {
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, body_ids[i]));
}
result
}
pub fn run_bh<F>(
posit_target: Vec3,
id_target: usize,
tree: &Tree,
config: &BhConfig,
force_fn: &F,
) -> Vec3
where
F: Fn(Vec3, f64, f64) -> Vec3 + Send + Sync,
{
tree.leaves(posit_target, config)
.par_iter()
.filter_map(|leaf| {
if leaf.body_ids.contains(&id_target) {
return None;
}
let acc_diff = leaf.center_of_mass - posit_target;
let dist = acc_diff.magnitude();
let acc_dir = acc_diff / dist;
Some(force_fn(acc_dir, leaf.mass, dist))
})
.reduce(Vec3::new_zero, |acc, elem| acc + elem)
}