re_view_spatial 0.31.4

Views that show entities in a 2D or 3D spatial relationship.
Documentation
use std::iter;

use re_chunk_store::external::re_chunk::ChunkComponentIterItem;
use re_sdk_types::archetypes::Cylinders3D;
use re_sdk_types::components::{ClassId, Color, FillMode, HalfSize3D, Length, Radius, ShowLabels};
use re_sdk_types::reflection::Enum as _;
use re_sdk_types::{Archetype as _, ArrowString, components};
use re_view::clamped_or_else;
use re_viewer_context::{
    IdentifiedViewSystem, QueryContext, ViewContext, ViewContextCollection, ViewQuery,
    ViewSystemExecutionError, VisualizerExecutionOutput, VisualizerQueryInfo, VisualizerSystem,
    typed_fallback_for,
};

use super::SpatialViewVisualizerData;
use super::utilities::{ProcMeshBatch, ProcMeshDrawableBuilder};
use crate::contexts::SpatialSceneVisualizerInstructionContext;
use crate::proc_mesh;
use crate::view_kind::SpatialViewKind;

// ---
pub struct Cylinders3DVisualizer(SpatialViewVisualizerData);

impl Default for Cylinders3DVisualizer {
    fn default() -> Self {
        Self(SpatialViewVisualizerData::new(Some(
            SpatialViewKind::ThreeD,
        )))
    }
}

// NOTE: Do not put profile scopes in these methods. They are called for all entities and all
// timestamps within a time range -- it's _a lot_.
impl Cylinders3DVisualizer {
    fn process_data<'a>(
        builder: &mut ProcMeshDrawableBuilder<'_>,
        query_context: &QueryContext<'_>,
        ent_context: &SpatialSceneVisualizerInstructionContext<'_>,
        batches: impl Iterator<Item = Cylinders3DComponentData<'a>>,
    ) -> Result<(), ViewSystemExecutionError> {
        for batch in batches {
            // Number of instances is determined by whichever is *longer* of `lengths` and `radii`.
            // The other component is clamped (last value repeated) to match.
            let num_instances = batch.radii.len().max(batch.lengths.len());
            let lengths_iter = clamped_or_else(batch.lengths, || {
                typed_fallback_for::<Length>(
                    query_context,
                    Cylinders3D::descriptor_lengths().component,
                )
            })
            .take(num_instances);
            let radii_iter = clamped_or_else(batch.radii, || {
                typed_fallback_for::<Radius>(
                    query_context,
                    Cylinders3D::descriptor_radii().component,
                )
            })
            .take(num_instances);

            let half_sizes: Vec<HalfSize3D> = lengths_iter
                .zip(radii_iter)
                .map(|(Length(length), Radius(radius))| {
                    let radius = clean_length(radius.0);
                    // Cylinder radius is already half the diameter, so we can use it directly.
                    // Length is the full length, so we divide by 2 to get the half size.
                    HalfSize3D::new(radius, radius, length.0 / 2.0)
                })
                .collect();

            let subdivisions = match batch.fill_mode {
                FillMode::DenseWireframe => 3, // Don't make it too crowded - let the user see inside the mesh.
                FillMode::Solid | FillMode::TransparentFillMajorWireframe => 4, // Smooth, but not too CPU/GPU intensive
                FillMode::MajorWireframe => 10,
            };

            let axes_only = batch.fill_mode.axes_only();

            let proc_mesh_key = proc_mesh::ProcMeshKey::Cylinder {
                subdivisions,
                axes_only,
            };

            builder.add_batch(
                query_context,
                ent_context,
                Cylinders3D::descriptor_colors().component,
                Cylinders3D::descriptor_line_radii().component,
                Cylinders3D::descriptor_show_labels().component,
                glam::Affine3A::IDENTITY,
                ProcMeshBatch {
                    half_sizes: &half_sizes,
                    centers: batch.centers,
                    rotation_axis_angles: batch.rotation_axis_angles.as_slice(),
                    quaternions: batch.quaternions,
                    meshes: iter::repeat(proc_mesh_key),
                    fill_modes: iter::repeat(batch.fill_mode),
                    line_radii: batch.line_radii,
                    colors: batch.colors,
                    labels: &batch.labels,
                    show_labels: batch.show_labels,
                    class_ids: batch.class_ids,
                },
            )?;
        }

        Ok(())
    }
}

// ---

struct Cylinders3DComponentData<'a> {
    // Point of views
    lengths: &'a [Length],

    // Clamped to edge
    radii: &'a [Radius],
    centers: &'a [components::Translation3D],
    rotation_axis_angles: ChunkComponentIterItem<components::RotationAxisAngle>,
    quaternions: &'a [components::RotationQuat],
    colors: &'a [Color],
    labels: Vec<ArrowString>,
    line_radii: &'a [Radius],
    class_ids: &'a [ClassId],

    // Non-repeated
    show_labels: Option<ShowLabels>,
    fill_mode: FillMode,
}

impl IdentifiedViewSystem for Cylinders3DVisualizer {
    fn identifier() -> re_viewer_context::ViewSystemIdentifier {
        "Cylinders3D".into()
    }
}

impl VisualizerSystem for Cylinders3DVisualizer {
    fn visualizer_query_info(
        &self,
        _app_options: &re_viewer_context::AppOptions,
    ) -> VisualizerQueryInfo {
        // The required component of the archetype is actually both lengths and radii,
        // But while that makes sense for the user-facing API, we can just use lengths as the required component for visualizability purposes.
        // This makes the requirements easier, and also means that if a user only provides length,
        // they will still get a visualizer with (editable) default radii instead of no visualizer at all.
        VisualizerQueryInfo::single_required_component::<Length>(
            &Cylinders3D::descriptor_lengths(),
            &Cylinders3D::all_components(),
        )
    }

    fn execute(
        &mut self,
        ctx: &ViewContext<'_>,
        view_query: &ViewQuery<'_>,
        context_systems: &ViewContextCollection,
    ) -> Result<VisualizerExecutionOutput, ViewSystemExecutionError> {
        let output = VisualizerExecutionOutput::default();
        let preferred_view_kind = self.0.preferred_view_kind;
        let mut builder = ProcMeshDrawableBuilder::new(
            &mut self.0,
            ctx.viewer_ctx.render_ctx(),
            view_query,
            "cylinders3d",
        );

        use super::entity_iterator::process_archetype;
        process_archetype::<Self, Cylinders3D, _>(
            ctx,
            view_query,
            context_systems,
            &output,
            preferred_view_kind,
            |ctx, spatial_ctx, results| {
                // See comment on `visualizer_query_info` for the rationale of treating only lengths as required.
                // (instead of lengths and radii as noted in the archetype definition).
                let all_lengths =
                    results.iter_required(Cylinders3D::descriptor_lengths().component);

                let all_radii = results.iter_optional(Cylinders3D::descriptor_radii().component);
                let all_centers =
                    results.iter_optional(Cylinders3D::descriptor_centers().component);
                let all_rotation_axis_angles =
                    results.iter_optional(Cylinders3D::descriptor_rotation_axis_angles().component);
                let all_quaternions =
                    results.iter_optional(Cylinders3D::descriptor_quaternions().component);
                let all_colors = results.iter_optional(Cylinders3D::descriptor_colors().component);
                let all_labels = results.iter_optional(Cylinders3D::descriptor_labels().component);
                let all_fill_modes =
                    results.iter_optional(Cylinders3D::descriptor_fill_mode().component);
                let all_line_radii =
                    results.iter_optional(Cylinders3D::descriptor_line_radii().component);
                let all_show_labels =
                    results.iter_optional(Cylinders3D::descriptor_show_labels().component);
                let all_class_ids =
                    results.iter_optional(Cylinders3D::descriptor_class_ids().component);

                let data = re_query::range_zip_1x10(
                    all_lengths.slice::<f32>(),
                    all_radii.slice::<f32>(),
                    all_centers.slice::<[f32; 3]>(),
                    all_rotation_axis_angles.component_slow::<components::RotationAxisAngle>(),
                    all_quaternions.slice::<[f32; 4]>(),
                    all_colors.slice::<u32>(),
                    all_line_radii.slice::<f32>(),
                    all_fill_modes.slice::<u8>(),
                    all_labels.slice::<String>(),
                    all_show_labels.slice::<bool>(),
                    all_class_ids.slice::<u16>(),
                )
                .map(
                    |(
                        _index,
                        lengths,
                        radii,
                        centers,
                        rotation_axis_angles,
                        quaternions,
                        colors,
                        line_radii,
                        fill_modes,
                        labels,
                        show_labels,
                        class_ids,
                    )| {
                        Cylinders3DComponentData {
                            lengths: bytemuck::cast_slice(lengths),
                            radii: radii.map_or(&[], bytemuck::cast_slice),
                            centers: centers.map_or(&[], bytemuck::cast_slice),
                            rotation_axis_angles: rotation_axis_angles.unwrap_or_default(),
                            quaternions: quaternions.map_or(&[], bytemuck::cast_slice),
                            colors: colors.map_or(&[], |colors| bytemuck::cast_slice(colors)),
                            labels: labels.unwrap_or_default(),
                            line_radii: line_radii
                                .map_or(&[], |line_radii| bytemuck::cast_slice(line_radii)),
                            fill_mode: fill_modes
                                .and_then(|s| FillMode::from_integer_slice(s).next()?)
                                .unwrap_or_default(),
                            class_ids: class_ids
                                .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)),
                            show_labels: show_labels
                                .map(|b| !b.is_empty() && b.value(0))
                                .map(Into::into),
                        }
                    },
                );

                Self::process_data(&mut builder, ctx, spatial_ctx, data)?;

                Ok(())
            },
        )?;

        Ok(output.with_draw_data(builder.into_draw_data()?))
    }

    fn data(&self) -> Option<&dyn std::any::Any> {
        Some(self.0.as_any())
    }
}

fn clean_length(suspicious_length: f32) -> f32 {
    if suspicious_length.is_finite() && suspicious_length > 0.0 {
        suspicious_length
    } else {
        // all negatives including negative zero, NaNs, and infinities shall become positive zero
        0.0
    }
}