use std::fmt::{Display, Formatter};
use bincode::{Decode, Encode};
#[cfg(feature = "render")]
use graphics::{Mesh, Vertex};
use lin_alg::f32::Vec3;
use mcubes::{MarchingCubes, MeshSide};
pub type MeshColors = Vec<Option<(u8, u8, u8, u8)>>;
pub const SOLVENT_RAD: f32 = 1.4;
#[derive(Clone, Copy, Debug, PartialEq, Default, Encode, Decode)]
pub enum MeshColoring {
#[default]
Solid, Element,
PartialCharge,
Lipophilicity,
}
impl Display for MeshColoring {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let v = match self {
Self::Solid => "Solid",
Self::Element => "Element",
Self::PartialCharge => "Charge",
Self::Lipophilicity => "Lipophilicity",
};
write!(f, "{v}")
}
}
pub fn make_sas_mesh_mc(atoms: &[(Vec3, f32)], radius: f32, precision: f32) -> mcubes::Mesh {
if atoms.is_empty() {
return mcubes::Mesh {
vertices: Vec::new(),
indices: Vec::new(),
};
}
let mut bb_min = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
let mut bb_max = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
let mut r_max: f32 = 0.0;
for (posit, vdw_radius) in atoms {
let r = vdw_radius + radius;
r_max = r_max.max(r);
bb_min = Vec3::new(
bb_min.x.min(posit.x),
bb_min.y.min(posit.y),
bb_min.z.min(posit.z),
);
bb_max = Vec3::new(
bb_max.x.max(posit.x),
bb_max.y.max(posit.y),
bb_max.z.max(posit.z),
);
}
bb_min -= Vec3::splat(r_max + precision);
bb_max += Vec3::splat(r_max + precision);
let dim_v = (bb_max - bb_min) / precision;
let grid_dim = (
dim_v.x.ceil() as usize + 1,
dim_v.y.ceil() as usize + 1,
dim_v.z.ceil() as usize + 1,
);
let n_voxels = grid_dim.0 * grid_dim.1 * grid_dim.2;
let far_val = (r_max + precision).powi(2) + 1.0;
let mut field = vec![far_val; n_voxels];
let idx = |x: usize, y: usize, z: usize| -> usize { (z * grid_dim.1 + y) * grid_dim.0 + x };
for (center, vdw_radius) in atoms {
let rad = *vdw_radius + radius;
let rad2 = rad * rad;
let lo = ((*center - Vec3::splat(rad)) - bb_min) / precision;
let hi = ((*center + Vec3::splat(rad)) - bb_min) / precision;
let (xi0, yi0, zi0) = (
lo.x.floor().max(0.0) as usize,
lo.y.floor().max(0.0) as usize,
lo.z.floor().max(0.0) as usize,
);
let (xi1, yi1, zi1) = (
hi.x.ceil().min((grid_dim.0 - 1) as f32) as usize,
hi.y.ceil().min((grid_dim.1 - 1) as f32) as usize,
hi.z.ceil().min((grid_dim.2 - 1) as f32) as usize,
);
for z in zi0..=zi1 {
for y in yi0..=yi1 {
for x in xi0..=xi1 {
let p = bb_min + Vec3::new(x as f32, y as f32, z as f32) * precision;
let d2 = (p - *center).magnitude_squared();
let v = d2 - rad2;
let f = &mut field[idx(x, y, z)];
if v < *f {
*f = v;
}
}
}
}
}
let sampling_interval = (
grid_dim.0 as f32 - 1.0,
grid_dim.1 as f32 - 1.0,
grid_dim.2 as f32 - 1.0,
);
let size = (
sampling_interval.0 * precision,
sampling_interval.1 * precision,
sampling_interval.2 * precision,
);
let mc = MarchingCubes::new(grid_dim, size, sampling_interval, bb_min, field, 0.)
.expect("marching cubes init");
let mc_mesh = mc.generate(MeshSide::InsideOnly);
mc_mesh
}
#[cfg(feature = "render")]
pub fn make_sas_mesh(atoms: &[(Vec3, f32)], radius: f32, precision: f32) -> Mesh {
let mc_mesh = make_sas_mesh_mc(atoms, radius, precision);
let vertices: Vec<Vertex> = mc_mesh
.vertices
.iter()
.map(|v| Vertex::new([v.posit.x, v.posit.y, v.posit.z], -v.normal))
.collect();
Mesh {
vertices,
indices: mc_mesh.indices,
material: 0,
}
}