use molgfx_core::SecondaryStructure;
use molgfx_math::Vec3;
use std::ops::Range;
#[derive(Clone, Debug, Default)]
pub struct PolymerTraces {
pub(super) points: Vec<Vec3>,
pub(super) entities: Vec<u32>,
pub(super) styles: Vec<SecondaryStructure>,
pub(super) properties: Vec<f32>,
pub(super) normals: Vec<Vec3>,
pub(super) ranges: Vec<TraceRange>,
}
impl PolymerTraces {
#[must_use]
pub fn ranges(&self) -> &[TraceRange] {
&self.ranges
}
pub(super) fn rebuild_from(
&mut self,
points: Vec<Vec3>,
entities: Vec<u32>,
normals: Vec<Vec3>,
ranges: Vec<TraceRange>,
) {
self.styles.clear();
self.styles
.resize(points.len(), SecondaryStructure::Unknown);
self.properties.clear();
self.properties.resize(points.len(), f32::NAN);
self.points = points;
self.entities = entities;
self.normals = normals;
self.ranges = ranges;
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct TraceRange {
pub chain: u32,
pub points: Range<usize>,
}
pub const CARTOON_GAP_CUTOFF: f32 = 8.0;
pub fn extract_polymer_traces(
structure: &molframe::Structure,
selection: &molgfx_core::AtomSelection,
secondary: &[SecondaryStructure],
max_gap: f32,
output: &mut PolymerTraces,
) -> Result<(), crate::PackingError> {
output.points.clear();
output.entities.clear();
output.styles.clear();
output.properties.clear();
output.normals.clear();
output.ranges.clear();
let max_gap_sq = max_gap.max(0.0).powi(2);
for chain in structure.chains() {
let chain_id = chain.index().get();
let mut trace_start = output.points.len();
for residue in chain.residues() {
let guide = residue.atom("CA").or_else(|| residue.atom("C4'"));
let Some(atom) = guide else {
finish_trace(
chain_id,
trace_start,
output.points.len(),
&mut output.ranges,
);
trace_start = output.points.len();
continue;
};
if !selection.contains(atom.index().get()) {
finish_trace(
chain_id,
trace_start,
output.points.len(),
&mut output.ranges,
);
trace_start = output.points.len();
continue;
}
let position = atom.position().map(Vec3::from);
let Some(position) = position else {
finish_trace(
chain_id,
trace_start,
output.points.len(),
&mut output.ranges,
);
trace_start = output.points.len();
continue;
};
if output
.points
.last()
.is_some_and(|previous| previous.distance_squared(position) > max_gap_sq)
{
finish_trace(
chain_id,
trace_start,
output.points.len(),
&mut output.ranges,
);
trace_start = output.points.len();
}
let entity = molgfx_core::EntityId::pack(
molgfx_core::EntityKind::Atom,
u64::from(atom.index().get()),
)?;
output.points.push(position);
output.entities.push(entity.0);
let residue_index = residue.index().as_usize();
output.styles.push(match secondary.get(residue_index) {
Some(&style) => style,
None => SecondaryStructure::Unknown,
});
output
.properties
.push(atom.b_factor().into_iter().fold(f32::NAN, |_, value| value));
}
finish_trace(
chain_id,
trace_start,
output.points.len(),
&mut output.ranges,
);
}
Ok(())
}
fn finish_trace(chain: u32, start: usize, end: usize, ranges: &mut Vec<TraceRange>) {
if end.saturating_sub(start) >= 2 {
ranges.push(TraceRange {
chain,
points: start..end,
});
}
}