molgfx-core 0.3.0

The semantic scene graph: columnar tables, GPU record layouts, the borrowed coordinate seam.
Documentation
use crate::{
    AnalyticSphere, AnalyticTemplate, AttributeColumn, AttributeValues,
    GenericSceneDescriptionSources, InstanceBatch, PointBatch, PointGlyph, PointStyle, Relation,
    RelationBatch, RelationStyle, RigidInstance, RowDomain, RowEntityRef, Scene,
    SceneDescriptionSources, SourceNamespace, SourceRows, SpatialAnchor, TemplatePartRef,
    VisualDescriptor, VisualProgramBuilder, VisualStyle,
};
use molgfx_math::{Quat, Vec3};
use std::sync::Arc;

#[test]
fn schema_eight_round_trips_generic_identity_payloads_and_visuals() {
    let mut scene = Scene::new();
    let point_batch = point_batch(10, &[101, 103]);
    let point_handle = scene.add_point_batch(point_batch);
    let instance_batch = instance_batch();
    let instance_handle = scene.add_instance_batch(instance_batch);

    let attribute = AttributeColumn::new(
        RowDomain::Points(point_handle),
        "priority",
        AttributeValues::Scalar(Arc::from([0.25, 0.75])),
    )
    .unwrap_or_else(|error| panic!("attribute validates: {error}"));
    let attribute_handle = scene
        .add_attribute(attribute)
        .unwrap_or_else(|error| panic!("attribute attaches: {error}"));
    let relation_batch = relations(point_handle, instance_handle);
    let relation_handle = scene
        .add_relation_batch(relation_batch)
        .unwrap_or_else(|error| panic!("relations attach: {error}"));

    let mut builder = VisualProgramBuilder::new();
    let priority = builder
        .scalar_attribute(attribute_handle)
        .unwrap_or_else(|error| panic!("attribute input builds: {error}"));
    builder
        .set_radius_scale(priority)
        .unwrap_or_else(|error| panic!("visual output builds: {error}"));
    let descriptor = VisualDescriptor::new(VisualStyle::new(
        builder
            .finish()
            .unwrap_or_else(|error| panic!("visual program builds: {error}")),
    ))
    .with_order(7);
    scene
        .set_domain_visual(RowDomain::Points(point_handle), descriptor)
        .unwrap_or_else(|error| panic!("visual attaches: {error}"));

    let description = scene.describe();
    let point_sources: Vec<_> = scene
        .point_batches()
        .map(|(_, value)| value.clone())
        .collect();
    let instance_sources: Vec<_> = scene
        .instance_batches()
        .map(|(_, value)| value.clone())
        .collect();
    let attribute_sources: Vec<_> = scene.attributes().map(|(_, value)| value.clone()).collect();
    let relation_sources: Vec<_> = scene
        .relation_batches()
        .map(|(_, value)| value.clone())
        .collect();
    let manifest = scene.manifest(Vec::new());
    assert_eq!(manifest.payloads.len(), 4);

    let rebuilt = Scene::from_description_with_generic(
        &description,
        empty_sources(),
        GenericSceneDescriptionSources {
            point_batches: &point_sources,
            instance_batches: &instance_sources,
            attributes: &attribute_sources,
            relation_batches: &relation_sources,
        },
    )
    .unwrap_or_else(|error| panic!("generic scene rehydrates: {error}"));

    assert_eq!(rebuilt.describe(), description);
    assert_eq!(
        rebuilt
            .point_batch(point_handle)
            .and_then(|value| value.source_rows().key(1)),
        Some(103)
    );
    assert_eq!(
        rebuilt
            .instance_batch(instance_handle)
            .and_then(|value| value.template().source_rows().key(1)),
        Some(502)
    );
    assert_eq!(
        rebuilt
            .relation_batch(relation_handle)
            .and_then(|value| value.source_rows().key(1)),
        Some(702)
    );
    assert_eq!(
        rebuilt
            .domain_visual(RowDomain::Points(point_handle))
            .map(VisualDescriptor::order),
        Some(7)
    );
}

#[test]
fn legacy_rehydration_rejects_missing_generic_sources_atomically() {
    let mut scene = Scene::new();
    let _handle = scene.add_point_batch(point_batch(90, &[1, 2]));
    let description = scene.describe();
    assert!(Scene::from_description(&description, empty_sources()).is_err());
}

fn point_batch(namespace: u64, keys: &[u64]) -> PointBatch {
    PointBatch::new(
        Arc::from([[0.0, 0.0, 0.0], [2.0, 0.0, 0.0]]),
        SourceRows::keyed(SourceNamespace(namespace), Arc::from(keys))
            .unwrap_or_else(|error| panic!("point keys validate: {error}")),
        PointGlyph::Sphere,
        PointStyle::default(),
    )
    .unwrap_or_else(|error| panic!("points validate: {error}"))
}

fn instance_batch() -> InstanceBatch {
    let template = Arc::new(
        AnalyticTemplate::new(
            Arc::from([
                AnalyticSphere {
                    center: [0.0; 3],
                    radius: 1.0,
                },
                AnalyticSphere {
                    center: [1.5, 0.0, 0.0],
                    radius: 0.5,
                },
            ]),
            Arc::from([]),
            SourceRows::keyed(SourceNamespace(50), Arc::from([501, 502]))
                .unwrap_or_else(|error| panic!("template keys validate: {error}")),
        )
        .unwrap_or_else(|error| panic!("template validates: {error}")),
    );
    InstanceBatch::new(
        template,
        Arc::from([
            RigidInstance::new(Vec3::ZERO, Quat::IDENTITY, 1.0)
                .unwrap_or_else(|error| panic!("transform validates: {error}")),
            RigidInstance::new(Vec3::splat(4.0), Quat::IDENTITY, 2.0)
                .unwrap_or_else(|error| panic!("transform validates: {error}")),
        ]),
        SourceRows::keyed(SourceNamespace(60), Arc::from([601, 602]))
            .unwrap_or_else(|error| panic!("instance keys validate: {error}")),
    )
    .unwrap_or_else(|error| panic!("instances validate: {error}"))
}

fn relations(
    points: crate::PointBatchHandle,
    instances: crate::InstanceBatchHandle,
) -> RelationBatch {
    RelationBatch::new(
        Arc::from([
            Relation {
                start: SpatialAnchor::entity(RowEntityRef::new(RowDomain::Points(points), 1))
                    .unwrap_or_else(|error| panic!("point anchor validates: {error}")),
                end: SpatialAnchor::world(Vec3::Y)
                    .unwrap_or_else(|error| panic!("world anchor validates: {error}")),
            },
            Relation {
                start: SpatialAnchor::template_part(TemplatePartRef::new(instances, 1, 1)),
                end: SpatialAnchor::world(Vec3::X)
                    .unwrap_or_else(|error| panic!("world anchor validates: {error}")),
            },
        ]),
        SourceRows::keyed(SourceNamespace(70), Arc::from([701, 702]))
            .unwrap_or_else(|error| panic!("relation keys validate: {error}")),
        RelationStyle::default(),
    )
    .unwrap_or_else(|error| panic!("relations validate: {error}"))
}

const fn empty_sources<'a>() -> SceneDescriptionSources<'a> {
    SceneDescriptionSources {
        structures: &[],
        volumes: &[],
        segmentations: &[],
        atom_properties: &[],
        meshes: &[],
    }
}