#[cfg(test)]
extern crate approx;
#[cxx::bridge]
mod ffi {
#[derive(Debug, Default, PartialEq, Clone)]
struct SimplePoint {
x: f64,
y: f64,
z: f64,
}
#[derive(Debug, Default, PartialEq, Clone)]
struct Ball {
x: f64,
y: f64,
z: f64,
r: f64,
}
#[derive(Debug, Default, PartialEq, Clone)]
struct Contact {
index_a: i32,
index_b: i32,
area: f64,
arc_length: f64,
}
#[derive(Debug, Default, PartialEq, Clone)]
struct Cell {
sas_area: f64,
volume: f64,
included: bool,
}
#[derive(Debug, Default, PartialEq, Clone)]
struct TessellationVertex {
ids_of_spheres: [usize; 4],
position: SimplePoint,
dist_min: f64,
dist_max: f64,
}
#[derive(Debug, Default, PartialEq, Clone)]
struct RadicalTessellation {
probe: f64,
periodic_box_corners: Vec<SimplePoint>,
balls: Vec<Ball>,
contacts: Vec<Contact>,
cells: Vec<Cell>,
vertices: Vec<TessellationVertex>,
pub tessellation_net: bool,
}
unsafe extern "C++" {
include!("voronota/src/interface.h");
fn from_balls(
probe_radius: f64,
balls: &Vec<Ball>,
with_net: bool,
grouping_of_spheres: &Vec<i32>,
) -> RadicalTessellation;
fn from_balls_pbc(
probe_radius: f64,
balls: &Vec<Ball>,
periodic_box_corners: &Vec<SimplePoint>,
with_net: bool,
) -> RadicalTessellation;
}
}
impl RadicalTessellation {
pub fn from_balls(
probe_radius: f64,
balls: &Vec<Ball>,
periodic_box_corners: Option<[SimplePoint; 2]>,
grouping_of_spheres: Option<Vec<isize>>,
with_net: bool,
) -> Self {
let grouping_of_spheres = grouping_of_spheres
.unwrap_or_default()
.into_iter()
.map(|x| x as i32)
.collect();
match periodic_box_corners {
Some(corners) => {
assert!(corners[0].x < corners[1].x);
assert!(corners[0].y < corners[1].y);
assert!(corners[0].z < corners[1].z);
ffi::from_balls_pbc(
probe_radius,
balls,
&vec![corners[0].clone(), corners[1].clone()],
with_net,
)
}
None => ffi::from_balls(probe_radius, balls, with_net, &grouping_of_spheres),
}
}
pub fn cells(&self) -> &[Cell] {
&self.cells
}
pub fn clear(&mut self) {
self.balls.clear();
self.contacts.clear();
self.cells.clear();
}
pub const fn is_empty(&self) -> bool {
self.balls.is_empty()
}
pub fn contact_area(&self, index: usize) -> f64 {
self.contacts
.iter()
.filter(|&c| c.index_a == index as i32 || c.index_b == index as i32)
.map(|contact| contact.area)
.sum()
}
}
pub use ffi::{Ball, Cell, Contact, RadicalTessellation, SimplePoint};
impl From<[f64; 3]> for SimplePoint {
fn from(data: [f64; 3]) -> Self {
Self {
x: data[0],
y: data[1],
z: data[2],
}
}
}
impl From<[f64; 4]> for Ball {
fn from(data: [f64; 4]) -> Self {
Self {
x: data[0],
y: data[1],
z: data[2],
r: data[3],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_balls() {
let balls = vec![
Ball {
x: 0.0,
y: 0.0,
z: 0.0,
r: 2.0,
},
Ball {
x: 1.0,
y: 0.0,
z: 0.0,
r: 2.0,
},
];
let tessellation = RadicalTessellation::from_balls(1.4, &balls, None, None, false);
assert_eq!(tessellation.balls.len(), 2);
assert_eq!(tessellation.contacts.len(), 1);
assert_eq!(tessellation.cells.len(), 2);
approx::assert_relative_eq!(
tessellation.cells[0].sas_area,
83.31503717320129,
epsilon = 1e-6
);
approx::assert_relative_eq!(
tessellation.cells[0].volume,
100.34561094831156,
epsilon = 1e-6
);
assert!(tessellation.cells[0].included);
assert_eq!(tessellation.contacts[0].index_a, 0);
assert_eq!(tessellation.contacts[0].index_b, 1);
approx::assert_relative_eq!(
tessellation.contacts[0].area,
35.53141291210056,
epsilon = 1e-6
);
approx::assert_relative_eq!(
tessellation.contacts[0].arc_length,
21.130567978766745,
epsilon = 1e-6
);
approx::assert_relative_eq!(
tessellation.cells[0].sas_area,
83.31503717320129,
epsilon = 1e-6
);
approx::assert_relative_eq!(
tessellation.cells[1].sas_area,
83.31503717320129,
epsilon = 1e-6
);
let tessellation = RadicalTessellation::from_balls(1.4, &balls, None, None, true);
assert_eq!(tessellation.vertices.len(), 0);
}
#[test]
fn test_from_balls_with_grouping() {
let balls = vec![
Ball {
x: 0.0,
y: 0.0,
z: 0.0,
r: 1.0,
},
Ball {
x: 1.0,
y: 0.0,
z: 0.0,
r: 1.0,
},
Ball {
x: 2.0,
y: 0.0,
z: 0.0,
r: 1.3,
},
];
let grouping_of_spheres = vec![0, 1, 2];
let tessellation =
RadicalTessellation::from_balls(1.4, &balls, None, Some(grouping_of_spheres), true);
assert_eq!(tessellation.balls.len(), 3);
assert_eq!(tessellation.contacts.len(), 2);
approx::assert_relative_eq!(
tessellation.contacts[0].area,
17.310175521279756,
epsilon = 1e-6
);
approx::assert_relative_eq!(
tessellation.contacts[1].area,
17.87495534057886,
epsilon = 1e-6
);
let grouping_of_spheres = vec![0, 0, 1];
let tessellation =
RadicalTessellation::from_balls(1.4, &balls, None, Some(grouping_of_spheres), true);
assert_eq!(tessellation.balls.len(), 3);
assert_eq!(tessellation.contacts.len(), 1);
approx::assert_relative_eq!(
tessellation.contacts[0].area,
17.87495534057886,
epsilon = 1e-6
);
}
}