#[cfg(test)]
#[path = "point_batch_tests.rs"]
mod tests;
use crate::{CoreError, SourceRows};
use molgfx_math::{Aabb, Rgba8, Vec3};
use std::sync::Arc;
const PARALLEL_BOUNDS_THRESHOLD: usize = molgfx_math::parallel::BLOCK * 4;
#[repr(u32)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub enum PointGlyph {
#[default]
Disc = 0,
Sphere = 1,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PointStyle {
pub radius: f32,
pub color: Rgba8,
}
impl Default for PointStyle {
fn default() -> Self {
Self {
radius: 0.1,
color: Rgba8::WHITE,
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct PointBatch {
positions: Arc<[[f32; 3]]>,
source_rows: SourceRows,
glyph: PointGlyph,
style: PointStyle,
bounds: Aabb,
visible: bool,
}
impl PointBatch {
pub fn new(
positions: Arc<[[f32; 3]]>,
source_rows: SourceRows,
glyph: PointGlyph,
style: PointStyle,
) -> Result<Self, CoreError> {
if positions.is_empty() || positions.len() != source_rows.len() as usize {
return Err(invalid(
"point positions must be non-empty and match their source rows",
));
}
if !style.radius.is_finite() || style.radius <= 0.0 {
return Err(invalid("point radius must be finite and positive"));
}
if !molgfx_math::parallel::all_blocks(&positions, PARALLEL_BOUNDS_THRESHOLD, |block| {
block
.iter()
.all(|position| position.iter().all(|value| value.is_finite()))
}) {
return Err(invalid("point positions must be finite"));
}
let bounds = point_bounds(&positions, style.radius);
Ok(Self {
positions,
source_rows,
glyph,
style,
bounds,
visible: true,
})
}
#[must_use]
pub const fn positions(&self) -> &Arc<[[f32; 3]]> {
&self.positions
}
#[must_use]
pub const fn source_rows(&self) -> &SourceRows {
&self.source_rows
}
#[must_use]
pub const fn glyph(&self) -> PointGlyph {
self.glyph
}
#[must_use]
pub const fn style(&self) -> PointStyle {
self.style
}
#[must_use]
pub const fn bounds(&self) -> Aabb {
self.bounds
}
#[must_use]
pub const fn visible(&self) -> bool {
self.visible
}
pub(crate) fn set_visible(&mut self, visible: bool) -> bool {
if self.visible == visible {
return false;
}
self.visible = visible;
true
}
}
fn point_bounds(positions: &[[f32; 3]], radius: f32) -> Aabb {
molgfx_math::parallel::reduce_blocks(
positions,
PARALLEL_BOUNDS_THRESHOLD,
Aabb::EMPTY,
|block| {
let mut bound = Aabb::EMPTY;
for position in block {
bound.extend_sphere(Vec3::from(position), radius);
}
bound
},
|left, right| left.union(&right),
)
}
const fn invalid(reason: &'static str) -> CoreError {
CoreError::InvalidBatch { reason }
}