use crate::atoms::AtomTable;
use crate::hierarchy::Hierarchy;
use crate::{
BondTopologySegment, Column, DatasetId, SecondaryStructure, StructureAsset, TrajectorySegment,
};
use molgfx_math::{Aabb, Bvh, BvhBuildScratch, BvhSource, Mat4, SweptSphereBounds};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct PlacedStructure {
asset: StructureAsset,
pub source: crate::MolecularSource,
pub model_to_world: Mat4,
pub atoms: Arc<AtomTable>,
pub hierarchy: Arc<Hierarchy>,
pub secondary_structure: Column<SecondaryStructure>,
trajectory: Option<TrajectorySegment>,
trajectory_bvh: Option<Bvh>,
trajectory_scratch: BvhBuildScratch,
trajectory_revision: u64,
trajectory_pair_revision: u64,
bond_topology: Option<BondTopologySegment>,
bond_topology_revision: u64,
bond_topology_pair_revision: u64,
bond_break_length: f32,
}
impl PlacedStructure {
#[must_use]
pub fn new(structure: &molframe::Structure) -> Option<Self> {
let Ok(asset) = StructureAsset::new(DatasetId::LEGACY, structure) else {
return None;
};
Some(Self::from_asset(&asset))
}
#[must_use]
pub fn from_asset(asset: &StructureAsset) -> Self {
let atoms = asset.shared_atoms();
let hierarchy = asset.shared_hierarchy();
let secondary_structure =
Column::new(vec![SecondaryStructure::Unknown; hierarchy.residue_count()]);
Self {
asset: asset.clone(),
source: asset.source().clone(),
model_to_world: Mat4::IDENTITY,
hierarchy,
secondary_structure,
atoms,
trajectory: None,
trajectory_bvh: None,
trajectory_scratch: BvhBuildScratch::default(),
trajectory_revision: 0,
trajectory_pair_revision: 0,
bond_topology: None,
bond_topology_revision: 0,
bond_topology_pair_revision: 0,
bond_break_length: 0.0,
}
}
#[must_use]
pub const fn asset(&self) -> &StructureAsset {
&self.asset
}
#[must_use]
pub fn dataset_id(&self) -> DatasetId {
self.asset.dataset_id()
}
#[must_use]
pub const fn bond_break_length(&self) -> f32 {
self.bond_break_length
}
pub(crate) fn set_bond_break_length(&mut self, length: f32) -> Result<(), crate::CoreError> {
if !length.is_finite() || length < 0.0 {
return Err(crate::CoreError::InvalidTrajectory {
reason: "bond break length must be finite and non-negative",
});
}
self.bond_break_length = length;
Ok(())
}
#[must_use]
pub fn world_aabb(&self) -> Aabb {
self.trajectory_bvh
.as_ref()
.map_or(self.asset.spatial_bounds(), Bvh::bounds)
.transform(&self.model_to_world)
}
#[must_use]
pub const fn trajectory(&self) -> Option<&TrajectorySegment> {
self.trajectory.as_ref()
}
#[must_use]
pub const fn trajectory_revision(&self) -> u64 {
self.trajectory_revision
}
#[must_use]
pub const fn trajectory_pair_revision(&self) -> u64 {
self.trajectory_pair_revision
}
#[must_use]
pub const fn bond_topology(&self) -> Option<&BondTopologySegment> {
self.bond_topology.as_ref()
}
#[must_use]
pub const fn bond_topology_revision(&self) -> u64 {
self.bond_topology_revision
}
#[must_use]
pub const fn bond_topology_pair_revision(&self) -> u64 {
self.bond_topology_pair_revision
}
pub fn render_bvh(&self) -> Result<&Bvh, molgfx_math::BvhBuildError> {
match &self.trajectory_bvh {
Some(trajectory) => Ok(trajectory),
None => self.spatial_bvh(),
}
}
pub fn spatial_bvh(&self) -> Result<&Bvh, molgfx_math::BvhBuildError> {
self.asset.spatial_bvh()
}
#[cfg(test)]
pub(crate) fn spatial_bvh_is_ready(&self) -> bool {
self.asset.spatial_bvh_is_ready()
}
pub(crate) fn replace_trajectory(
&mut self,
segment: TrajectorySegment,
) -> Result<(), crate::CoreError> {
let source = SweptSphereBounds::new(
segment.start().positions(),
segment.end().positions(),
self.atoms.radius().values(),
);
let mut hierarchy = self
.trajectory_bvh
.take()
.into_iter()
.fold(Bvh::default(), |_, hierarchy| hierarchy);
let refittable =
!hierarchy.nodes.is_empty() && hierarchy.primitive_indices.len() == source.len();
let outcome = if refittable {
hierarchy.refit(&source)
} else {
hierarchy.rebuild(&source, &mut self.trajectory_scratch)
};
if outcome.is_err() {
return Err(crate::CoreError::InvalidTrajectory {
reason: "trajectory exceeds the compact GPU BVH index space",
});
}
self.trajectory_bvh = Some(hierarchy);
self.trajectory = Some(segment);
self.trajectory_revision = self.trajectory_revision.wrapping_add(1);
self.trajectory_pair_revision = self.trajectory_pair_revision.wrapping_add(1);
Ok(())
}
pub(crate) fn set_trajectory_time(
&mut self,
sample_seconds: f32,
) -> Result<(), crate::CoreError> {
let trajectory = self
.trajectory
.as_mut()
.ok_or(crate::CoreError::InvalidTrajectory {
reason: "structure has no active trajectory segment",
})?;
let previous = trajectory.sample_seconds();
trajectory.set_sample_time(sample_seconds)?;
if trajectory.sample_seconds().to_bits() != previous.to_bits() {
self.trajectory_revision = self.trajectory_revision.wrapping_add(1);
}
Ok(())
}
pub(crate) fn clear_trajectory(&mut self) -> bool {
let changed = self.trajectory.take().is_some();
self.trajectory_bvh = None;
if changed {
self.trajectory_revision = self.trajectory_revision.wrapping_add(1);
self.trajectory_pair_revision = self.trajectory_pair_revision.wrapping_add(1);
}
changed
}
pub(crate) fn replace_bond_topology(&mut self, segment: BondTopologySegment) {
self.bond_topology = Some(segment);
self.bond_topology_revision = self.bond_topology_revision.wrapping_add(1);
self.bond_topology_pair_revision = self.bond_topology_pair_revision.wrapping_add(1);
}
pub(crate) fn set_bond_topology_time(
&mut self,
sample_seconds: f32,
) -> Result<(), crate::CoreError> {
let topology = self
.bond_topology
.as_mut()
.ok_or(crate::CoreError::InvalidTrajectory {
reason: "structure has no active dynamic topology segment",
})?;
let previous = topology.sample_seconds();
topology.set_sample_time(sample_seconds)?;
if topology.sample_seconds().to_bits() != previous.to_bits() {
self.bond_topology_revision = self.bond_topology_revision.wrapping_add(1);
}
Ok(())
}
pub(crate) fn clear_bond_topology(&mut self) -> bool {
let changed = self.bond_topology.take().is_some();
if changed {
self.bond_topology_revision = self.bond_topology_revision.wrapping_add(1);
self.bond_topology_pair_revision = self.bond_topology_pair_revision.wrapping_add(1);
}
changed
}
}