molgfx-core 0.3.2

The semantic scene graph: columnar tables, GPU record layouts, the borrowed coordinate seam.
Documentation
use super::*;
use crate::{
    AnalyticSphere, AnalyticTemplate, AtomProperty, AtomPropertyMeaning, AttributeColumn,
    AttributeValues, BondTopologyFrame, BondTopologySegment, InstanceBatch, RigidInstance,
    RowDomain, ScalarFieldSemantics, SourceNamespace, SourceRows, TopologyBond, TrajectoryFrame,
    TrajectorySegment,
};
use molgfx_math::{Quat, Vec3};

fn warp(rate: f64, mode: PlaybackMode) -> TimeWarp {
    TimeWarp::new(0.0, 0.0, rate, [0.0, 1.0], mode).unwrap_or_else(|error| panic!("{error}"))
}

#[test]
fn time_warps_clamp_loop_and_ping_pong_without_state() {
    assert_eq!(warp(1.0, PlaybackMode::Clamp).sample(1.5), Some(1.0));
    assert_eq!(warp(1.0, PlaybackMode::Loop).sample(1.25), Some(0.25));
    assert_eq!(warp(1.0, PlaybackMode::PingPong).sample(1.25), Some(0.75));
    assert_eq!(warp(-1.0, PlaybackMode::Loop).sample(0.25), Some(0.75));
}

#[test]
fn one_tick_updates_trajectory_and_scalar_tracks_at_their_own_rates() {
    let structure = crate::fixture::structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let Some((owner, placed)) = scene.structures().next() else {
        panic!("owner exists")
    };
    let start_positions: Arc<[[f32; 3]]> = Arc::from(placed.atoms.coords().slice());
    let mut end_positions = start_positions.to_vec();
    for position in &mut end_positions {
        position[0] += 1.0;
    }
    let start = TrajectoryFrame::new(0, 0.0, start_positions, "start")
        .unwrap_or_else(|error| panic!("{error}"));
    let end = TrajectoryFrame::new(1, 1.0, Arc::from(end_positions), "end")
        .unwrap_or_else(|error| panic!("{error}"));
    scene
        .set_trajectory_segment(
            owner,
            TrajectorySegment::new(start, end, 0.0).unwrap_or_else(|error| panic!("{error}")),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    let count = scene
        .structure(owner)
        .map_or(0, |value| value.atoms.len() as usize);
    let property = AtomProperty::new(
        owner,
        "energy",
        Arc::from(vec![0.0; count]),
        AtomPropertyMeaning::Charge,
        ScalarFieldSemantics::UncalibratedRank,
    )
    .unwrap_or_else(|error| panic!("{error}"));
    let property = scene
        .add_atom_property(property)
        .unwrap_or_else(|error| panic!("{error}"));
    let mut timeline = Timeline::new();
    timeline
        .bind_trajectory(&scene, owner, warp(1.0, PlaybackMode::Clamp))
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .bind_atom_property(
            &mut scene,
            property,
            Arc::from(vec![0.0; count]),
            Arc::from(vec![8.0; count]),
            warp(0.5, PlaybackMode::Clamp),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .apply(&mut scene, 0.5)
        .unwrap_or_else(|error| panic!("{error}"));
    assert_eq!(
        scene.presentation_time_seconds().to_bits(),
        0.5f32.to_bits()
    );

    let trajectory_time = scene
        .structure(owner)
        .and_then(|value| value.trajectory())
        .map(TrajectorySegment::sample_seconds);
    assert_eq!(trajectory_time.map(f32::to_bits), Some(0.5f32.to_bits()));
    let property_value = scene
        .atom_property(property)
        .and_then(|value| value.values().first())
        .copied();
    assert_eq!(property_value.map(f32::to_bits), Some(2.0f32.to_bits()));
}

#[test]
fn a_stale_track_prevents_every_update_in_the_tick() {
    let structure = crate::fixture::structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let Some((owner, placed)) = scene.structures().next() else {
        panic!("owner exists")
    };
    let positions: Arc<[[f32; 3]]> = Arc::from(placed.atoms.coords().slice());
    let count = usize::try_from(placed.atoms.len()).unwrap_or(0);
    let start = TrajectoryFrame::new(0, 0.0, Arc::clone(&positions), "start")
        .unwrap_or_else(|error| panic!("{error}"));
    let end =
        TrajectoryFrame::new(1, 1.0, positions, "end").unwrap_or_else(|error| panic!("{error}"));
    scene
        .set_trajectory_segment(
            owner,
            TrajectorySegment::new(start, end, 0.0).unwrap_or_else(|error| panic!("{error}")),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    let property = AtomProperty::new(
        owner,
        "energy",
        Arc::from(vec![0.0; count]),
        AtomPropertyMeaning::Charge,
        ScalarFieldSemantics::UncalibratedRank,
    )
    .unwrap_or_else(|error| panic!("{error}"));
    let property = scene
        .add_atom_property(property)
        .unwrap_or_else(|error| panic!("{error}"));
    let mut timeline = Timeline::new();
    timeline
        .bind_trajectory(&scene, owner, warp(1.0, PlaybackMode::Clamp))
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .bind_atom_property(
            &mut scene,
            property,
            Arc::from(vec![0.0; count]),
            Arc::from(vec![1.0; count]),
            warp(1.0, PlaybackMode::Clamp),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    scene.remove_atom_property(property);

    assert!(matches!(
        timeline.apply(&mut scene, 0.5),
        Err(CoreError::StaleHandle)
    ));
    let time = scene
        .structure(owner)
        .and_then(|value| value.trajectory())
        .map(TrajectorySegment::sample_seconds);
    assert_eq!(time.map(f32::to_bits), Some(0.0f32.to_bits()));
    assert_eq!(
        scene.presentation_time_seconds().to_bits(),
        0.0f32.to_bits()
    );
}

#[test]
fn one_clock_drives_dynamic_connectivity_at_an_independent_rate() {
    let structure = crate::fixture::structure();
    let mut scene = Scene::from_structure(&structure).unwrap_or_else(|error| panic!("{error}"));
    let owner = scene
        .structures()
        .next()
        .map_or_else(|| panic!("owner"), |(handle, _)| handle);
    let atom_count = scene.structure(owner).map_or(0, |value| value.atoms.len());
    let from = TopologyBond::new(0, 1, false).unwrap_or_else(|error| panic!("{error}"));
    let to = TopologyBond::new(1, 2, false).unwrap_or_else(|error| panic!("{error}"));
    let start = BondTopologyFrame::new(0, 0.0, atom_count, Arc::from([from]), "start")
        .unwrap_or_else(|error| panic!("{error}"));
    let end = BondTopologyFrame::new(1, 1.0, atom_count, Arc::from([to]), "end")
        .unwrap_or_else(|error| panic!("{error}"));
    scene
        .set_bond_topology_segment(
            owner,
            BondTopologySegment::new(start, end, 0.0).unwrap_or_else(|error| panic!("{error}")),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    let mut timeline = Timeline::new();
    timeline
        .bind_bond_topology(&scene, owner, warp(0.5, PlaybackMode::Clamp))
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .apply(&mut scene, 0.5)
        .unwrap_or_else(|error| panic!("{error}"));
    let weights = scene
        .structure(owner)
        .and_then(crate::PlacedStructure::bond_topology)
        .map(|segment| {
            segment
                .bonds()
                .map(|bond| bond.weight())
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    assert_eq!(weights, [0.75, 0.25]);
}

#[test]
fn generic_frames_remain_shared_and_only_their_sampling_revision_changes() {
    let mut scene = Scene::new();
    let template = Arc::new(
        AnalyticTemplate::new(
            Arc::from([AnalyticSphere {
                center: [0.0; 3],
                radius: 1.0,
            }]),
            Arc::from([]),
            SourceRows::ordered(SourceNamespace(80), 1),
        )
        .unwrap_or_else(|error| panic!("{error}")),
    );
    let source = Arc::from([RigidInstance::new(Vec3::ZERO, Quat::IDENTITY, 1.0)
        .unwrap_or_else(|error| panic!("{error}"))]);
    let target =
        Arc::from([RigidInstance::new(Vec3::X, Quat::IDENTITY, 2.0)
            .unwrap_or_else(|error| panic!("{error}"))]);
    let batch = scene.add_instance_batch(
        InstanceBatch::new(
            template,
            Arc::clone(&source),
            SourceRows::ordered(SourceNamespace(81), 1),
        )
        .unwrap_or_else(|error| panic!("{error}")),
    );
    let attribute = scene
        .add_attribute(
            AttributeColumn::new(
                RowDomain::Instances(batch),
                "weight",
                AttributeValues::Scalar(Arc::from([0.0])),
            )
            .unwrap_or_else(|error| panic!("{error}")),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    let mut timeline = Timeline::new();
    let instances = timeline
        .bind_instances(
            &mut scene,
            batch,
            Arc::clone(&source),
            Arc::clone(&target),
            warp(1.0, PlaybackMode::Clamp),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .bind_attribute(
            &mut scene,
            attribute,
            AttributeValues::Scalar(Arc::from([2.0])),
            AttributeValues::Scalar(Arc::from([6.0])),
            warp(1.0, PlaybackMode::Clamp),
        )
        .unwrap_or_else(|error| panic!("{error}"));
    timeline
        .apply(&mut scene, 0.25)
        .unwrap_or_else(|error| panic!("{error}"));

    let Some((resident_start, resident_end, alpha, _)) = scene.instance_frames(batch) else {
        panic!("instance frames remain resident")
    };
    assert!(Arc::ptr_eq(resident_start, &source));
    assert!(Arc::ptr_eq(resident_end, &target));
    assert_eq!(alpha.to_bits(), 0.25f32.to_bits());
    assert!(timeline.unbind(&mut scene, instances));
    assert!(scene.instance_frames(batch).is_none());
}

#[test]
fn deformable_point_frames_remain_twelve_byte_shared_rows() {
    let mut scene = Scene::new();
    let start: Arc<[[f32; 3]]> = Arc::from([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]);
    let end: Arc<[[f32; 3]]> = Arc::from([[0.0, 0.2, 0.0], [1.0, -0.2, 0.0]]);
    let points = scene.add_point_batch(
        crate::PointBatch::new(
            Arc::clone(&start),
            SourceRows::ordered(SourceNamespace(82), 2),
            crate::PointGlyph::Sphere,
            crate::PointStyle::default(),
        )
        .unwrap_or_else(|error| panic!("points validate: {error}")),
    );
    let mut timeline = Timeline::new();
    let track = timeline
        .bind_points(
            &mut scene,
            points,
            Arc::clone(&start),
            Arc::clone(&end),
            warp(1.0, PlaybackMode::PingPong),
        )
        .unwrap_or_else(|error| panic!("point timeline binds: {error}"));
    timeline
        .apply(&mut scene, 0.25)
        .unwrap_or_else(|error| panic!("point timeline samples: {error}"));

    let Some((resident_start, resident_end, alpha, _)) = scene.point_frames(points) else {
        panic!("point frames remain resident")
    };
    assert!(Arc::ptr_eq(resident_start, &start));
    assert!(Arc::ptr_eq(resident_end, &end));
    assert_eq!(alpha.to_bits(), 0.25f32.to_bits());
    assert_eq!(std::mem::size_of_val(resident_start.as_ref()), 24);
    assert!(timeline.unbind(&mut scene, track));
    assert!(scene.point_frames(points).is_none());
}