diff --git a/Cargo.lock b/Cargo.lock index a009d59d4387..e647b74f02ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1177,6 +1177,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const_soft_float" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" + +[[package]] +name = "constgebra" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" +dependencies = [ + "const_soft_float", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -2488,6 +2503,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexasphere" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d70ed6295005e2bc5a6f624300cfd6d49908da76b3654c4cbdb1d4222705" +dependencies = [ + "constgebra", + "glam", +] + [[package]] name = "hexf-parse" version = "0.2.1" @@ -4919,6 +4944,7 @@ dependencies = [ "criterion", "egui", "glam", + "hexasphere", "itertools 0.13.0", "mimalloc", "nohash-hasher", @@ -6411,9 +6437,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.15" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tempfile" diff --git a/Cargo.toml b/Cargo.toml index e549281cfa31..570b0a0a23e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,6 +172,7 @@ glam = "0.28" glob = "0.3" gltf = "1.1" half = "2.3.1" +hexasphere = "14.0.0" image = { version = "0.25", default-features = false } indent = "0.1" indexmap = "2.1" # Version chosen to align with other dependencies diff --git a/crates/store/re_types/definitions/rerun/archetypes.fbs b/crates/store/re_types/definitions/rerun/archetypes.fbs index 4ddccbed02f3..c529f04c4652 100644 --- a/crates/store/re_types/definitions/rerun/archetypes.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes.fbs @@ -8,6 +8,7 @@ include "./archetypes/boxes3d.fbs"; include "./archetypes/clear.fbs"; include "./archetypes/depth_image.fbs"; include "./archetypes/disconnected_space.fbs"; +include "./archetypes/ellipsoids.fbs"; include "./archetypes/image.fbs"; include "./archetypes/line_strips2d.fbs"; include "./archetypes/line_strips3d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs new file mode 100644 index 000000000000..6bd5b04cf8a1 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs @@ -0,0 +1,63 @@ +include "fbs/attributes.fbs"; +include "rust/attributes.fbs"; +include "cpp/attributes.fbs"; + +include "rerun/datatypes.fbs"; +include "rerun/components.fbs"; + +namespace rerun.archetypes; + +// --- + +/// 3D ellipsoids or spheres. +/// +/// This archetype is for ellipsoids or spheres whose size is a key part of the data +/// (e.g. a bounding sphere). +/// For points whose radii are for the sake of visualization, use `Points3D` instead. +/// +/// Currently, ellipsoids are always rendered as wireframes. +/// Opaque and transparent rendering will be supported later. +/// +/// \example archetypes/ellipsoid_batch !api title="Batch of ellipsoids" +table Ellipsoids ( + "attr.rust.derive": "PartialEq", + "attr.rust.new_pub_crate", + "attr.cpp.no_field_ctors", + "attr.docs.category": "Spatial 3D", + "attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection" +) { + // --- Required --- + + /// For each ellipsoid, half of its size on its three axes. + /// + /// If all components are equal, then it is a sphere with that radius. + half_sizes: [rerun.components.HalfSize3D] ("attr.rerun.component_required", order: 1000); + + // --- Recommended --- + + /// Optional center positions of the ellipsoids. + /// + /// If not specified, the centers will be at (0, 0, 0). + centers: [rerun.components.Position3D] ("attr.rerun.component_recommended", nullable, order: 2000); + + /// Optional rotations of the ellipsoids. + /// + /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + rotations: [rerun.components.Rotation3D] ("attr.rerun.component_recommended", nullable, order: 2100); + + /// Optional colors for the ellipsoids. + colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2200); + + // --- Optional --- + + /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + line_radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); + + /// Optional text labels for the ellipsoids. + labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3100); + + /// Optional `ClassId`s for the ellipsoids. + /// + /// The class ID provides colors and labels if not specified explicitly. + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200); +} diff --git a/crates/store/re_types/src/archetypes/.gitattributes b/crates/store/re_types/src/archetypes/.gitattributes index 56544ae96577..b299bfcf0b66 100644 --- a/crates/store/re_types/src/archetypes/.gitattributes +++ b/crates/store/re_types/src/archetypes/.gitattributes @@ -10,6 +10,7 @@ boxes2d.rs linguist-generated=true boxes3d.rs linguist-generated=true depth_image.rs linguist-generated=true disconnected_space.rs linguist-generated=true +ellipsoids.rs linguist-generated=true image.rs linguist-generated=true line_strips2d.rs linguist-generated=true line_strips3d.rs linguist-generated=true diff --git a/crates/store/re_types/src/archetypes/ellipsoids.rs b/crates/store/re_types/src/archetypes/ellipsoids.rs new file mode 100644 index 000000000000..8f3fc89f3920 --- /dev/null +++ b/crates/store/re_types/src/archetypes/ellipsoids.rs @@ -0,0 +1,388 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Archetype**: 3D ellipsoids or spheres. +/// +/// This archetype is for ellipsoids or spheres whose size is a key part of the data +/// (e.g. a bounding sphere). +/// For points whose radii are for the sake of visualization, use `Points3D` instead. +/// +/// Currently, ellipsoids are always rendered as wireframes. +/// Opaque and transparent rendering will be supported later. +#[derive(Clone, Debug, PartialEq)] +pub struct Ellipsoids { + /// For each ellipsoid, half of its size on its three axes. + /// + /// If all components are equal, then it is a sphere with that radius. + pub half_sizes: Vec, + + /// Optional center positions of the ellipsoids. + /// + /// If not specified, the centers will be at (0, 0, 0). + pub centers: Option>, + + /// Optional rotations of the ellipsoids. + /// + /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + pub rotations: Option>, + + /// Optional colors for the ellipsoids. + pub colors: Option>, + + /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + pub line_radii: Option>, + + /// Optional text labels for the ellipsoids. + pub labels: Option>, + + /// Optional `ClassId`s for the ellipsoids. + /// + /// The class ID provides colors and labels if not specified explicitly. + pub class_ids: Option>, +} + +impl ::re_types_core::SizeBytes for Ellipsoids { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.half_sizes.heap_size_bytes() + + self.centers.heap_size_bytes() + + self.rotations.heap_size_bytes() + + self.colors.heap_size_bytes() + + self.line_radii.heap_size_bytes() + + self.labels.heap_size_bytes() + + self.class_ids.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + >::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + && >>::is_pod() + } +} + +static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = + once_cell::sync::Lazy::new(|| ["rerun.components.HalfSize3D".into()]); + +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.components.Position3D".into(), + "rerun.components.Rotation3D".into(), + "rerun.components.Color".into(), + "rerun.components.EllipsoidsIndicator".into(), + ] + }); + +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.components.Radius".into(), + "rerun.components.Text".into(), + "rerun.components.ClassId".into(), + ] + }); + +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.components.HalfSize3D".into(), + "rerun.components.Position3D".into(), + "rerun.components.Rotation3D".into(), + "rerun.components.Color".into(), + "rerun.components.EllipsoidsIndicator".into(), + "rerun.components.Radius".into(), + "rerun.components.Text".into(), + "rerun.components.ClassId".into(), + ] + }); + +impl Ellipsoids { + /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional + pub const NUM_COMPONENTS: usize = 8usize; +} + +/// Indicator component for the [`Ellipsoids`] [`::re_types_core::Archetype`] +pub type EllipsoidsIndicator = ::re_types_core::GenericIndicatorComponent; + +impl ::re_types_core::Archetype for Ellipsoids { + type Indicator = EllipsoidsIndicator; + + #[inline] + fn name() -> ::re_types_core::ArchetypeName { + "rerun.archetypes.Ellipsoids".into() + } + + #[inline] + fn display_name() -> &'static str { + "Ellipsoids" + } + + #[inline] + fn indicator() -> MaybeOwnedComponentBatch<'static> { + static INDICATOR: EllipsoidsIndicator = EllipsoidsIndicator::DEFAULT; + MaybeOwnedComponentBatch::Ref(&INDICATOR) + } + + #[inline] + fn required_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + REQUIRED_COMPONENTS.as_slice().into() + } + + #[inline] + fn recommended_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + RECOMMENDED_COMPONENTS.as_slice().into() + } + + #[inline] + fn optional_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + OPTIONAL_COMPONENTS.as_slice().into() + } + + #[inline] + fn all_components() -> ::std::borrow::Cow<'static, [ComponentName]> { + ALL_COMPONENTS.as_slice().into() + } + + #[inline] + fn from_arrow_components( + arrow_data: impl IntoIterator)>, + ) -> DeserializationResult { + re_tracing::profile_function!(); + use ::re_types_core::{Loggable as _, ResultExt as _}; + let arrays_by_name: ::std::collections::HashMap<_, _> = arrow_data + .into_iter() + .map(|(name, array)| (name.full_name(), array)) + .collect(); + let half_sizes = { + let array = arrays_by_name + .get("rerun.components.HalfSize3D") + .ok_or_else(DeserializationError::missing_data) + .with_context("rerun.archetypes.Ellipsoids#half_sizes")?; + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#half_sizes")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#half_sizes")? + }; + let centers = if let Some(array) = arrays_by_name.get("rerun.components.Position3D") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#centers")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#centers")? + }) + } else { + None + }; + let rotations = if let Some(array) = arrays_by_name.get("rerun.components.Rotation3D") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#rotations")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#rotations")? + }) + } else { + None + }; + let colors = if let Some(array) = arrays_by_name.get("rerun.components.Color") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#colors")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#colors")? + }) + } else { + None + }; + let line_radii = if let Some(array) = arrays_by_name.get("rerun.components.Radius") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#line_radii")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#line_radii")? + }) + } else { + None + }; + let labels = if let Some(array) = arrays_by_name.get("rerun.components.Text") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#labels")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#labels")? + }) + } else { + None + }; + let class_ids = if let Some(array) = arrays_by_name.get("rerun.components.ClassId") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#class_ids")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#class_ids")? + }) + } else { + None + }; + Ok(Self { + half_sizes, + centers, + rotations, + colors, + line_radii, + labels, + class_ids, + }) + } +} + +impl ::re_types_core::AsComponents for Ellipsoids { + fn as_component_batches(&self) -> Vec> { + re_tracing::profile_function!(); + use ::re_types_core::Archetype as _; + [ + Some(Self::indicator()), + Some((&self.half_sizes as &dyn ComponentBatch).into()), + self.centers + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.rotations + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.colors + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.line_radii + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.labels + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.class_ids + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + ] + .into_iter() + .flatten() + .collect() + } +} + +impl Ellipsoids { + /// Create a new `Ellipsoids`. + #[inline] + pub(crate) fn new( + half_sizes: impl IntoIterator>, + ) -> Self { + Self { + half_sizes: half_sizes.into_iter().map(Into::into).collect(), + centers: None, + rotations: None, + colors: None, + line_radii: None, + labels: None, + class_ids: None, + } + } + + /// Optional center positions of the ellipsoids. + /// + /// If not specified, the centers will be at (0, 0, 0). + #[inline] + pub fn with_centers( + mut self, + centers: impl IntoIterator>, + ) -> Self { + self.centers = Some(centers.into_iter().map(Into::into).collect()); + self + } + + /// Optional rotations of the ellipsoids. + /// + /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + #[inline] + pub fn with_rotations( + mut self, + rotations: impl IntoIterator>, + ) -> Self { + self.rotations = Some(rotations.into_iter().map(Into::into).collect()); + self + } + + /// Optional colors for the ellipsoids. + #[inline] + pub fn with_colors( + mut self, + colors: impl IntoIterator>, + ) -> Self { + self.colors = Some(colors.into_iter().map(Into::into).collect()); + self + } + + /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + #[inline] + pub fn with_line_radii( + mut self, + line_radii: impl IntoIterator>, + ) -> Self { + self.line_radii = Some(line_radii.into_iter().map(Into::into).collect()); + self + } + + /// Optional text labels for the ellipsoids. + #[inline] + pub fn with_labels( + mut self, + labels: impl IntoIterator>, + ) -> Self { + self.labels = Some(labels.into_iter().map(Into::into).collect()); + self + } + + /// Optional `ClassId`s for the ellipsoids. + /// + /// The class ID provides colors and labels if not specified explicitly. + #[inline] + pub fn with_class_ids( + mut self, + class_ids: impl IntoIterator>, + ) -> Self { + self.class_ids = Some(class_ids.into_iter().map(Into::into).collect()); + self + } +} diff --git a/crates/store/re_types/src/archetypes/ellipsoids_ext.rs b/crates/store/re_types/src/archetypes/ellipsoids_ext.rs new file mode 100644 index 000000000000..b4b152d532b2 --- /dev/null +++ b/crates/store/re_types/src/archetypes/ellipsoids_ext.rs @@ -0,0 +1,42 @@ +use crate::components::{HalfSize3D, Position3D}; + +use super::Ellipsoids; + +impl Ellipsoids { + /// Creates a new [`Ellipsoids`] for spheres with the given radii. + // Note: This is not a `Radius` component because the `Radius` component is for + // the on-screen sizes of lines and points. + #[inline] + #[doc(alias = "sphere")] + pub fn from_radii(radii: impl IntoIterator) -> Self { + Self::new(radii.into_iter().map(HalfSize3D::splat)) + } + + /// Creates a new [`Ellipsoids`] for spheres with the given [`Self::centers`], and + /// [`Self::half_sizes`] all equal to the given radii. + // Note: This is not a `Radius` component because the `Radius` component is for + // the on-screen sizes of lines and points. + #[doc(alias = "sphere")] + #[inline] + pub fn from_centers_and_radii( + centers: impl IntoIterator>, + radii: impl IntoIterator, + ) -> Self { + Self::new(radii.into_iter().map(HalfSize3D::splat)).with_centers(centers) + } + + /// Creates a new [`Ellipsoids`] with [`Self::half_sizes`]. + #[inline] + pub fn from_half_sizes(half_sizes: impl IntoIterator>) -> Self { + Self::new(half_sizes) + } + + /// Creates a new [`Ellipsoids`] with [`Self::centers`] and [`Self::half_sizes`]. + #[inline] + pub fn from_centers_and_half_sizes( + centers: impl IntoIterator>, + half_sizes: impl IntoIterator>, + ) -> Self { + Self::new(half_sizes).with_centers(centers) + } +} diff --git a/crates/store/re_types/src/archetypes/mod.rs b/crates/store/re_types/src/archetypes/mod.rs index a29b3b0e4e24..aa035642ddd9 100644 --- a/crates/store/re_types/src/archetypes/mod.rs +++ b/crates/store/re_types/src/archetypes/mod.rs @@ -15,6 +15,8 @@ mod boxes3d_ext; mod depth_image; mod depth_image_ext; mod disconnected_space; +mod ellipsoids; +mod ellipsoids_ext; mod image; mod image_ext; mod line_strips2d; @@ -50,6 +52,7 @@ pub use self::boxes2d::Boxes2D; pub use self::boxes3d::Boxes3D; pub use self::depth_image::DepthImage; pub use self::disconnected_space::DisconnectedSpace; +pub use self::ellipsoids::Ellipsoids; pub use self::image::Image; pub use self::line_strips2d::LineStrips2D; pub use self::line_strips3d::LineStrips3D; diff --git a/crates/store/re_types/src/components/half_size3d_ext.rs b/crates/store/re_types/src/components/half_size3d_ext.rs index b63c909bc98c..905c127c5064 100644 --- a/crates/store/re_types/src/components/half_size3d_ext.rs +++ b/crates/store/re_types/src/components/half_size3d_ext.rs @@ -9,6 +9,12 @@ impl HalfSize3D { Self(Vec3D::new(half_width, half_height, half_depth)) } + /// Create a new half-extent with all the same sizes (a radius, perhaps). + #[inline] + pub const fn splat(half_size: f32) -> Self { + Self(Vec3D::new(half_size, half_size, half_size)) + } + /// Width of a box using this half-extent. #[inline] pub fn width(self) -> f32 { diff --git a/crates/viewer/re_space_view_spatial/Cargo.toml b/crates/viewer/re_space_view_spatial/Cargo.toml index 6e4caf3cc20c..10d53674e49d 100644 --- a/crates/viewer/re_space_view_spatial/Cargo.toml +++ b/crates/viewer/re_space_view_spatial/Cargo.toml @@ -46,6 +46,7 @@ bitflags.workspace = true bytemuck.workspace = true egui = { workspace = true, features = ["serde"] } glam.workspace = true +hexasphere.workspace = true itertools.workspace = true nohash-hasher.workspace = true once_cell.workspace = true diff --git a/crates/viewer/re_space_view_spatial/src/lib.rs b/crates/viewer/re_space_view_spatial/src/lib.rs index b6c609b35cfa..7d81a4653562 100644 --- a/crates/viewer/re_space_view_spatial/src/lib.rs +++ b/crates/viewer/re_space_view_spatial/src/lib.rs @@ -14,6 +14,7 @@ mod mesh_cache; mod mesh_loader; mod pickable_image; mod picking; +mod proc_mesh; mod scene_bounding_boxes; mod space_camera_3d; mod spatial_topology; diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs new file mode 100644 index 000000000000..2551d15f04b4 --- /dev/null +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -0,0 +1,140 @@ +use std::sync::Arc; + +use ahash::HashSet; +use glam::Vec3; + +use re_renderer::RenderContext; +use re_viewer_context::Cache; + +// ---------------------------------------------------------------------------- + +/// Description of a mesh that can be procedurally generated. +/// +/// Obtain the actual mesh by passing this to [`WireframeCache`]. +/// In the future, it will be possible to produce solid triangle meshes too. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ProcMeshKey { + /// A sphere of unit radius. + Sphere { subdivisions: usize }, +} + +/// A renderable mesh generated from a [`ProcMeshKey`] by the [`WireframeCache`], +/// which is to be drawn as lines rather than triangles. +pub struct WireframeMesh { + pub bbox: re_math::BoundingBox, + + pub vertex_count: usize, + + /// Collection of line strips making up the wireframe. + /// + /// TODO(kpreid): This should instead be a GPU buffer, but we don’t yet have a + /// `re_renderer::Renderer` implementation that takes instanced meshes and applies + /// the line shader to them, instead of doing immediate-mode accumulation of line strips. + pub line_strips: Vec>, +} + +// ---------------------------------------------------------------------------- + +/// Cache for the computation of wireframe meshes from [`ProcMeshKey`]s. +/// These meshes may then be rendered as instances of the cached +/// mesh. +#[derive(Default)] +pub struct WireframeCache(ahash::HashMap>>); + +impl WireframeCache { + pub fn entry( + &mut self, + key: ProcMeshKey, + render_ctx: &RenderContext, + ) -> Option> { + re_tracing::profile_function!(); + + self.0 + .entry(key) + .or_insert_with(|| { + re_log::debug!("Generating mesh {key:?}…"); + + let mesh = generate_wireframe(&key, render_ctx); + + // Right now, this can never return None, but in the future + // it will perform GPU allocations which can fail. + + Some(Arc::new(mesh)) + }) + .clone() + } +} + +impl Cache for WireframeCache { + fn begin_frame(&mut self) {} + + fn purge_memory(&mut self) { + self.0.clear(); + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +/// Generate a wireframe mesh without caching. +fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> WireframeMesh { + // In the future, render_ctx will be used to allocate GPU memory for the mesh. + _ = render_ctx; + + match *key { + ProcMeshKey::Sphere { subdivisions } => { + let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); + + let sphere_points = subdiv.raw_points(); + + // TODO(kpreid): There is a bug in `hexasphere` where it fails to return lines which + // reach the original corners of the shape. This will be fixed as part of + // , + // which is merged but not yet published on crates.io. + // When hexasphere 15.0 or 14.0.1 is available, update, then keep the first branch + // of this `if` only. + let line_strips: Vec> = if false { + subdiv + .get_all_line_indices(1, |v| v.push(0)) + .split(|&i| i == 0) + .map(|strip| -> Vec { + strip + .iter() + .map(|&i| sphere_points[i as usize - 1].into()) + .collect() + }) + .collect() + } else { + // Gather edges from the triangles, deduplicating. + let lines: HashSet<(u32, u32)> = subdiv + .get_all_indices() + .chunks(3) + .flat_map(|triangle| { + let [i1, i2, i3] = <[u32; 3]>::try_from(triangle).unwrap(); + [(i1, i2), (i2, i3), (i3, i1)] + }) + .map(|(i1, i2)| if i1 > i2 { (i2, i1) } else { (i1, i2) }) + .collect(); + + lines + .into_iter() + .map(|(i1, i2)| { + vec![ + sphere_points[i1 as usize].into(), + sphere_points[i2 as usize].into(), + ] + }) + .collect() + }; + WireframeMesh { + // sphere’s radius is 1, so its size is 2 + bbox: re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)), + vertex_count: line_strips.iter().map(|v| v.len()).sum(), + line_strips, + } + } + } +} + +// TODO(kpreid): A solid (rather than wireframe) mesh cache implementation should live here. diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs new file mode 100644 index 000000000000..48b7bc3f7d70 --- /dev/null +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -0,0 +1,343 @@ +use re_entity_db::{EntityPath, InstancePathHash}; +use re_log_types::Instance; +use re_query::range_zip_1x7; +use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, RenderContext}; +use re_types::{ + archetypes::Ellipsoids, + components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, +}; +use re_viewer_context::{ + auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, + ResolvedAnnotationInfos, SpaceViewSystemExecutionError, TypedComponentFallbackProvider, + ViewContext, ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, + VisualizerQueryInfo, VisualizerSystem, +}; + +use crate::{ + contexts::SpatialSceneEntityContext, + proc_mesh::{ProcMeshKey, WireframeCache}, + view_kind::SpatialSpaceViewKind, + visualizers::{UiLabel, UiLabelTarget}, +}; + +use super::{ + entity_iterator::clamped_or, filter_visualizable_3d_entities, + process_annotation_and_keypoint_slices, process_color_slice, process_radius_slice, + SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, +}; + +// --- + +pub struct EllipsoidsVisualizer(SpatialViewVisualizerData); + +impl Default for EllipsoidsVisualizer { + fn default() -> Self { + Self(SpatialViewVisualizerData::new(Some( + SpatialSpaceViewKind::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 EllipsoidsVisualizer { + fn process_labels<'a>( + entity_path: &'a EntityPath, + centers: &'a [Position3D], + labels: &'a [Text], + colors: &'a [egui::Color32], + annotation_infos: &'a ResolvedAnnotationInfos, + world_from_entity: glam::Affine3A, + ) -> impl Iterator + 'a { + let labels = annotation_infos + .iter() + .zip(labels.iter().map(Some).chain(std::iter::repeat(None))) + .map(|(annotation_info, label)| annotation_info.label(label.map(|l| l.as_str()))); + + itertools::izip!(centers, labels, colors) + .enumerate() + .filter_map(move |(i, (center, label, color))| { + label.map(|label| UiLabel { + text: label, + color: *color, + target: UiLabelTarget::Position3D( + world_from_entity.transform_point3(center.0.into()), + ), + labeled_instance: InstancePathHash::instance( + entity_path, + Instance::from(i as u64), + ), + }) + }) + } + + fn process_data<'a>( + &mut self, + ctx: &QueryContext<'_>, + line_builder: &mut LineDrawableBuilder<'_>, + query: &ViewQuery<'_>, + ent_context: &SpatialSceneEntityContext<'_>, + data: impl Iterator>, + render_ctx: &RenderContext, + ) -> Result<(), SpaceViewSystemExecutionError> { + let entity_path = ctx.target_entity_path; + + for data in data { + let num_instances = data.half_sizes.len(); + if num_instances == 0 { + continue; + } + + let (annotation_infos, _) = process_annotation_and_keypoint_slices( + query.latest_at, + num_instances, + data.half_sizes.iter().map(|_| glam::Vec3::ZERO), + data.keypoint_ids, + data.class_ids, + &ent_context.annotations, + ); + + // Has not custom fallback for radius, so we use the default. + // TODO(andreas): It would be nice to have this handle this fallback as part of the query. + let radii = process_radius_slice( + entity_path, + num_instances, + data.line_radii, + Radius::default(), + ); + let colors = + process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + + let centers = clamped_or(data.centers, &Position3D::ZERO); + let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); + + self.0.ui_labels.extend(Self::process_labels( + entity_path, + data.centers, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + + let mut line_batch = line_builder + .batch("ellipsoids") + .depth_offset(ent_context.depth_offset) + .world_from_obj(ent_context.world_from_entity) + .outline_mask_ids(ent_context.highlight.overall) + .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); + + let mut bounding_box = re_math::BoundingBox::NOTHING; + + for (i, (half_size, ¢er, rotation, radius, color)) in + itertools::izip!(data.half_sizes, centers, rotations, radii, colors).enumerate() + { + let transform = glam::Affine3A::from_scale_rotation_translation( + glam::Vec3::from(*half_size), + rotation.0.into(), + center.into(), + ); + + // TODO(kpreid): subdivisions should be configurable, and possibly dynamic based on + // either world size or screen size (depending on application). + let subdivisions = 2; + + let Some(sphere_mesh) = ctx.viewer_ctx.cache.entry(|c: &mut WireframeCache| { + c.entry(ProcMeshKey::Sphere { subdivisions }, render_ctx) + }) else { + // TODO(kpreid): Should this be just returning nothing instead? + // If we do, there won't be any error report, just missing data. + + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate wireframe mesh".into(), + )); + }; + + bounding_box = bounding_box.union(sphere_mesh.bbox.transform_affine3(&transform)); + + for strip in &sphere_mesh.line_strips { + let box3d = line_batch + .add_strip(strip.iter().map(|&point| transform.transform_point3(point))) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(i as _)); + + if let Some(outline_mask_ids) = ent_context + .highlight + .instances + .get(&Instance::from(i as u64)) + { + box3d.outline_mask_ids(*outline_mask_ids); + } + } + } + + self.0.add_bounding_box( + entity_path.hash(), + bounding_box, + ent_context.world_from_entity, + ); + } + + Ok(()) + } +} + +// --- + +struct EllipsoidsComponentData<'a> { + // Point of views + half_sizes: &'a [HalfSize3D], + + // Clamped to edge + centers: &'a [Position3D], + rotations: &'a [Rotation3D], + colors: &'a [Color], + line_radii: &'a [Radius], + labels: &'a [Text], + keypoint_ids: &'a [KeypointId], + class_ids: &'a [ClassId], +} + +impl IdentifiedViewSystem for EllipsoidsVisualizer { + fn identifier() -> re_viewer_context::ViewSystemIdentifier { + "Ellipsoids".into() + } +} + +impl VisualizerSystem for EllipsoidsVisualizer { + fn visualizer_query_info(&self) -> VisualizerQueryInfo { + VisualizerQueryInfo::from_archetype::() + } + + fn filter_visualizable_entities( + &self, + entities: ApplicableEntities, + context: &dyn VisualizableFilterContext, + ) -> VisualizableEntities { + re_tracing::profile_function!(); + filter_visualizable_3d_entities(entities, context) + } + + fn execute( + &mut self, + ctx: &ViewContext<'_>, + view_query: &ViewQuery<'_>, + context_systems: &ViewContextCollection, + ) -> Result, SpaceViewSystemExecutionError> { + let Some(render_ctx) = ctx.viewer_ctx.render_ctx else { + return Err(SpaceViewSystemExecutionError::NoRenderContextError); + }; + + // TODO(kpreid): Should be using instanced meshes kept in GPU buffers + // instead of this immediate-mode strategy that copies every vertex every frame. + let mut line_builder = LineDrawableBuilder::new(render_ctx); + line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + + super::entity_iterator::process_archetype::( + ctx, + view_query, + context_systems, + |ctx, spatial_ctx, results| { + use re_space_view::RangeResultsExt as _; + + let resolver = ctx.recording().resolver(); + + let half_sizes = match results.get_required_component_dense::(resolver) + { + Some(vectors) => vectors?, + _ => return Ok(()), + }; + + let num_ellipsoids = half_sizes + .range_indexed() + .map(|(_, vectors)| vectors.len()) + .sum::(); + if num_ellipsoids == 0 { + return Ok(()); + } + + // Ideally we would reserve space here, but we don't know the mesh subdivision yet, + // and this will become moot when we switch to instanced meshes. + // line_builder.reserve_strips(num_ellipsoids * sphere_mesh.line_strips.len())?; + // line_builder.reserve_vertices(num_ellipsoids * sphere_mesh.vertex_count)?; + + let centers = results.get_or_empty_dense(resolver)?; + let rotations = results.get_or_empty_dense(resolver)?; + let colors = results.get_or_empty_dense(resolver)?; + let line_radii = results.get_or_empty_dense(resolver)?; + let labels = results.get_or_empty_dense(resolver)?; + let class_ids = results.get_or_empty_dense(resolver)?; + let keypoint_ids = results.get_or_empty_dense(resolver)?; + + let data = range_zip_1x7( + half_sizes.range_indexed(), + centers.range_indexed(), + rotations.range_indexed(), + colors.range_indexed(), + line_radii.range_indexed(), + labels.range_indexed(), + class_ids.range_indexed(), + keypoint_ids.range_indexed(), + ) + .map( + |( + _index, + half_sizes, + centers, + rotations, + colors, + line_radii, + labels, + class_ids, + keypoint_ids, + )| { + EllipsoidsComponentData { + half_sizes, + centers: centers.unwrap_or_default(), + rotations: rotations.unwrap_or_default(), + colors: colors.unwrap_or_default(), + line_radii: line_radii.unwrap_or_default(), + labels: labels.unwrap_or_default(), + class_ids: class_ids.unwrap_or_default(), + keypoint_ids: keypoint_ids.unwrap_or_default(), + } + }, + ); + + self.process_data( + ctx, + &mut line_builder, + view_query, + spatial_ctx, + data, + render_ctx, + )?; + + Ok(()) + }, + )?; + + Ok(vec![(line_builder.into_draw_data()?.into())]) + } + + fn data(&self) -> Option<&dyn std::any::Any> { + Some(self.0.as_any()) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_fallback_provider(&self) -> &dyn re_viewer_context::ComponentFallbackProvider { + self + } +} + +impl TypedComponentFallbackProvider for EllipsoidsVisualizer { + fn fallback_for(&self, ctx: &QueryContext<'_>) -> Color { + auto_color_for_entity_path(ctx.target_entity_path) + } +} + +re_viewer_context::impl_component_fallback_provider!(EllipsoidsVisualizer => [Color]); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs index 4074a7c6d29a..6425ba43cde4 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs @@ -7,6 +7,7 @@ mod boxes2d; mod boxes3d; mod cameras; mod depth_images; +mod ellipsoids; mod images; mod lines2d; mod lines3d; @@ -102,6 +103,7 @@ pub fn register_3d_spatial_visualizers( system_registry.register_visualizer::()?; system_registry.register_visualizer::()?; system_registry.register_visualizer::()?; + system_registry.register_visualizer::()?; system_registry.register_visualizer::()?; system_registry.register_visualizer::()?; Ok(()) diff --git a/docs/content/reference/types/archetypes.md b/docs/content/reference/types/archetypes.md index 2b9fa3cca4ef..9e6dc545a532 100644 --- a/docs/content/reference/types/archetypes.md +++ b/docs/content/reference/types/archetypes.md @@ -38,6 +38,7 @@ This page lists all built-in archetypes. * [`Arrows3D`](archetypes/arrows3d.md): 3D arrows with optional colors, radii, labels, etc. * [`Asset3D`](archetypes/asset3d.md): A prepacked 3D asset (`.gltf`, `.glb`, `.obj`, `.stl`, etc.). * [`Boxes3D`](archetypes/boxes3d.md): 3D boxes with half-extents and optional center, rotations, colors etc. +* [`Ellipsoids`](archetypes/ellipsoids.md): 3D ellipsoids or spheres. * [`LineStrips3D`](archetypes/line_strips3d.md): 3D line strips with positions and optional colors, radii, labels, etc. * [`Mesh3D`](archetypes/mesh3d.md): A 3D triangle mesh as specified by its per-mesh and per-vertex properties. * [`Pinhole`](archetypes/pinhole.md): Camera perspective projection (a.k.a. intrinsics). diff --git a/docs/content/reference/types/archetypes/.gitattributes b/docs/content/reference/types/archetypes/.gitattributes index c8dbda543c26..a548f7dc5a01 100644 --- a/docs/content/reference/types/archetypes/.gitattributes +++ b/docs/content/reference/types/archetypes/.gitattributes @@ -11,6 +11,7 @@ boxes3d.md linguist-generated=true clear.md linguist-generated=true depth_image.md linguist-generated=true disconnected_space.md linguist-generated=true +ellipsoids.md linguist-generated=true image.md linguist-generated=true line_strips2d.md linguist-generated=true line_strips3d.md linguist-generated=true diff --git a/docs/content/reference/types/archetypes/ellipsoids.md b/docs/content/reference/types/archetypes/ellipsoids.md new file mode 100644 index 000000000000..a43b72d9c378 --- /dev/null +++ b/docs/content/reference/types/archetypes/ellipsoids.md @@ -0,0 +1,37 @@ +--- +title: "Ellipsoids" +--- + + +3D ellipsoids or spheres. + +This archetype is for ellipsoids or spheres whose size is a key part of the data +(e.g. a bounding sphere). +For points whose radii are for the sake of visualization, use `Points3D` instead. + +Currently, ellipsoids are always rendered as wireframes. +Opaque and transparent rendering will be supported later. + +## Components + +**Required**: [`HalfSize3D`](../components/half_size3d.md) + +**Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) + +**Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) + +## Shown in +* [Spatial3DView](../views/spatial3d_view.md) +* [Spatial2DView](../views/spatial2d_view.md) (if logged above active projection) + +## API reference links + * 🌊 [C++ API docs for `Ellipsoids`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1archetypes_1_1Ellipsoids.html) + * 🐍 [Python API docs for `Ellipsoids`](https://ref.rerun.io/docs/python/stable/common/archetypes#rerun.archetypes.Ellipsoids) + * 🦀 [Rust API docs for `Ellipsoids`](https://docs.rs/rerun/latest/rerun/archetypes/struct.Ellipsoids.html) + +## Example + +### Batch of ellipsoids + +snippet: archetypes/ellipsoid_batch + diff --git a/docs/content/reference/types/components/class_id.md b/docs/content/reference/types/components/class_id.md index 5163b88d1498..e282465c550b 100644 --- a/docs/content/reference/types/components/class_id.md +++ b/docs/content/reference/types/components/class_id.md @@ -21,6 +21,7 @@ A 16-bit ID representing a type of semantic class. * [`Arrows3D`](../archetypes/arrows3d.md) * [`Boxes2D`](../archetypes/boxes2d.md) * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`LineStrips2D`](../archetypes/line_strips2d.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Mesh3D`](../archetypes/mesh3d.md) diff --git a/docs/content/reference/types/components/color.md b/docs/content/reference/types/components/color.md index c27b2a2ff556..0fdb7bff4283 100644 --- a/docs/content/reference/types/components/color.md +++ b/docs/content/reference/types/components/color.md @@ -25,6 +25,7 @@ byte is `R` and the least significant byte is `A`. * [`BarChart`](../archetypes/bar_chart.md) * [`Boxes2D`](../archetypes/boxes2d.md) * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`LineStrips2D`](../archetypes/line_strips2d.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Mesh3D`](../archetypes/mesh3d.md) diff --git a/docs/content/reference/types/components/half_size3d.md b/docs/content/reference/types/components/half_size3d.md index 1b915a394103..5db4e0f80b7f 100644 --- a/docs/content/reference/types/components/half_size3d.md +++ b/docs/content/reference/types/components/half_size3d.md @@ -23,3 +23,4 @@ Negative sizes indicate that the box is flipped along the respective axis, but t ## Used by * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) diff --git a/docs/content/reference/types/components/position3d.md b/docs/content/reference/types/components/position3d.md index 94461a066f08..286d6f7b7fd3 100644 --- a/docs/content/reference/types/components/position3d.md +++ b/docs/content/reference/types/components/position3d.md @@ -19,5 +19,6 @@ A position in 3D space. * [`Arrows3D`](../archetypes/arrows3d.md) * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`Mesh3D`](../archetypes/mesh3d.md) * [`Points3D`](../archetypes/points3d.md) diff --git a/docs/content/reference/types/components/radius.md b/docs/content/reference/types/components/radius.md index 1e903fb0a93a..0b023869e1d8 100644 --- a/docs/content/reference/types/components/radius.md +++ b/docs/content/reference/types/components/radius.md @@ -28,6 +28,7 @@ The Viewer's UI scaling defaults to the OS scaling which typically is 100% for f * [`Arrows3D`](../archetypes/arrows3d.md) * [`Boxes2D`](../archetypes/boxes2d.md) * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`LineStrips2D`](../archetypes/line_strips2d.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Points2D`](../archetypes/points2d.md) diff --git a/docs/content/reference/types/components/rotation3d.md b/docs/content/reference/types/components/rotation3d.md index be0011da2848..a4491bd3c67f 100644 --- a/docs/content/reference/types/components/rotation3d.md +++ b/docs/content/reference/types/components/rotation3d.md @@ -18,3 +18,4 @@ A 3D rotation, represented either by a quaternion or a rotation around axis. ## Used by * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) diff --git a/docs/content/reference/types/components/text.md b/docs/content/reference/types/components/text.md index 7224b79fddcb..384ae791ecf2 100644 --- a/docs/content/reference/types/components/text.md +++ b/docs/content/reference/types/components/text.md @@ -21,6 +21,7 @@ A string of text, e.g. for labels and text documents. * [`Arrows3D`](../archetypes/arrows3d.md) * [`Boxes2D`](../archetypes/boxes2d.md) * [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`LineStrips2D`](../archetypes/line_strips2d.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Points2D`](../archetypes/points2d.md) diff --git a/docs/content/reference/types/views/spatial2d_view.md b/docs/content/reference/types/views/spatial2d_view.md index 48774402817e..16754e3fc6a0 100644 --- a/docs/content/reference/types/views/spatial2d_view.md +++ b/docs/content/reference/types/views/spatial2d_view.md @@ -58,6 +58,7 @@ snippet: views/spatial2d * [`Arrows3D`](../archetypes/arrows3d.md) (if logged above active projection) * [`Asset3D`](../archetypes/asset3d.md) (if logged above active projection) * [`Boxes3D`](../archetypes/boxes3d.md) (if logged above active projection) +* [`Ellipsoids`](../archetypes/ellipsoids.md) (if logged above active projection) * [`LineStrips3D`](../archetypes/line_strips3d.md) (if logged above active projection) * [`Mesh3D`](../archetypes/mesh3d.md) (if logged above active projection) * [`Points3D`](../archetypes/points3d.md) (if logged above active projection) diff --git a/docs/content/reference/types/views/spatial3d_view.md b/docs/content/reference/types/views/spatial3d_view.md index 90ab22d654ef..7741ae401efe 100644 --- a/docs/content/reference/types/views/spatial3d_view.md +++ b/docs/content/reference/types/views/spatial3d_view.md @@ -44,6 +44,7 @@ snippet: views/spatial3d * [`Boxes3D`](../archetypes/boxes3d.md) * [`Clear`](../archetypes/clear.md) * [`DisconnectedSpace`](../archetypes/disconnected_space.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md) * [`LineStrips3D`](../archetypes/line_strips3d.md) * [`Mesh3D`](../archetypes/mesh3d.md) * [`Points3D`](../archetypes/points3d.md) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.cpp b/docs/snippets/all/archetypes/ellipsoid_batch.cpp new file mode 100644 index 000000000000..dd0340520810 --- /dev/null +++ b/docs/snippets/all/archetypes/ellipsoid_batch.cpp @@ -0,0 +1,38 @@ +// Log a batch of ellipsoids. + +#include + +int main() { + const auto rec = rerun::RecordingStream("rerun_example_ellipsoid_batch"); + rec.spawn().exit_on_failure(); + + // Let's build a snowman! + float belly_z = 2.5; + float head_z = 4.5; + rec.log( + "batch", + rerun::Ellipsoids::from_centers_and_half_sizes( + { + {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, belly_z}, + {0.0f, 0.0f, head_z}, + {-0.6f, -0.77f, head_z}, + {0.6f, -0.77f, head_z}, + }, + { + {2.0f, 2.0f, 2.0f}, + {1.5f, 1.5f, 1.5f}, + {1.0f, 1.0f, 1.0f}, + {0.15f, 0.15f, 0.15f}, + {0.15f, 0.15f, 0.15f}, + } + ) + .with_colors({ + rerun::Rgba32(255, 255, 255), + rerun::Rgba32(255, 255, 255), + rerun::Rgba32(255, 255, 255), + rerun::Rgba32(0, 0, 0), + rerun::Rgba32(0, 0, 0), + }) + ); +} diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.py b/docs/snippets/all/archetypes/ellipsoid_batch.py new file mode 100644 index 000000000000..b8c1aa3db06b --- /dev/null +++ b/docs/snippets/all/archetypes/ellipsoid_batch.py @@ -0,0 +1,35 @@ +"""Log a batch of ellipsoids.""" + +import rerun as rr + +rr.init("rerun_example_ellipsoid_batch", spawn=True) + +# Let's build a snowman! +belly_z = 2.5 +head_z = 4.5 +rr.log( + "batch", + rr.Ellipsoids( + centers=[ + [0.0, 0.0, 0.0], + [0.0, 0.0, belly_z], + [0.0, 0.0, head_z], + [-0.6, -0.77, head_z], + [0.6, -0.77, head_z], + ], + half_sizes=[ + [2.0, 2.0, 2.0], + [1.5, 1.5, 1.5], + [1.0, 1.0, 1.0], + [0.15, 0.15, 0.15], + [0.15, 0.15, 0.15], + ], + colors=[ + (255, 255, 255), + (255, 255, 255), + (255, 255, 255), + (0, 0, 0), + (0, 0, 0), + ], + ), +) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.rs b/docs/snippets/all/archetypes/ellipsoid_batch.rs new file mode 100644 index 000000000000..b6f02af7e1d0 --- /dev/null +++ b/docs/snippets/all/archetypes/ellipsoid_batch.rs @@ -0,0 +1,37 @@ +//! Log a batch of `Ellipsoids`. + +fn main() -> Result<(), Box> { + let rec = rerun::RecordingStreamBuilder::new("rerun_example_ellipsoid_batch").spawn()?; + + // Let's build a snowman! + let belly_z = 2.5; + let head_z = 4.5; + rec.log( + "batch", + &rerun::Ellipsoids::from_centers_and_half_sizes( + [ + (0.0, 0.0, 0.0), + (0.0, 0.0, belly_z), + (0.0, 0.0, head_z), + (-0.6, -0.77, head_z), + (0.6, -0.77, head_z), + ], + [ + (2.0, 2.0, 2.0), + (1.5, 1.5, 1.5), + (1.0, 1.0, 1.0), + (0.15, 0.15, 0.15), + (0.15, 0.15, 0.15), + ], + ) + .with_colors([ + rerun::Color::from_rgb(255, 255, 255), + rerun::Color::from_rgb(255, 255, 255), + rerun::Color::from_rgb(255, 255, 255), + rerun::Color::from_rgb(0, 0, 0), + rerun::Color::from_rgb(0, 0, 0), + ]), + )?; + + Ok(()) +} diff --git a/rerun_cpp/src/rerun/archetypes.hpp b/rerun_cpp/src/rerun/archetypes.hpp index 1075260a8335..2a160744ff06 100644 --- a/rerun_cpp/src/rerun/archetypes.hpp +++ b/rerun_cpp/src/rerun/archetypes.hpp @@ -12,6 +12,7 @@ #include "archetypes/clear.hpp" #include "archetypes/depth_image.hpp" #include "archetypes/disconnected_space.hpp" +#include "archetypes/ellipsoids.hpp" #include "archetypes/image.hpp" #include "archetypes/line_strips2d.hpp" #include "archetypes/line_strips3d.hpp" diff --git a/rerun_cpp/src/rerun/archetypes/.gitattributes b/rerun_cpp/src/rerun/archetypes/.gitattributes index bb3c86280a87..8ab0c080f3e2 100644 --- a/rerun_cpp/src/rerun/archetypes/.gitattributes +++ b/rerun_cpp/src/rerun/archetypes/.gitattributes @@ -21,6 +21,8 @@ depth_image.cpp linguist-generated=true depth_image.hpp linguist-generated=true disconnected_space.cpp linguist-generated=true disconnected_space.hpp linguist-generated=true +ellipsoids.cpp linguist-generated=true +ellipsoids.hpp linguist-generated=true image.cpp linguist-generated=true image.hpp linguist-generated=true line_strips2d.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp new file mode 100644 index 000000000000..6baf7232881c --- /dev/null +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp @@ -0,0 +1,63 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs". + +#include "ellipsoids.hpp" + +#include "../collection_adapter_builtins.hpp" + +namespace rerun::archetypes {} + +namespace rerun { + + Result> AsComponents::serialize( + const archetypes::Ellipsoids& archetype + ) { + using namespace archetypes; + std::vector cells; + cells.reserve(8); + + { + auto result = DataCell::from_loggable(archetype.half_sizes); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.centers.has_value()) { + auto result = DataCell::from_loggable(archetype.centers.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.rotations.has_value()) { + auto result = DataCell::from_loggable(archetype.rotations.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.colors.has_value()) { + auto result = DataCell::from_loggable(archetype.colors.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.line_radii.has_value()) { + auto result = DataCell::from_loggable(archetype.line_radii.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.labels.has_value()) { + auto result = DataCell::from_loggable(archetype.labels.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.class_ids.has_value()) { + auto result = DataCell::from_loggable(archetype.class_ids.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + { + auto indicator = Ellipsoids::IndicatorComponent(); + auto result = DataCell::from_loggable(indicator); + RR_RETURN_NOT_OK(result.error); + cells.emplace_back(std::move(result.value)); + } + + return cells; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp new file mode 100644 index 000000000000..e22d516be6bd --- /dev/null +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp @@ -0,0 +1,170 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs". + +#pragma once + +#include "../collection.hpp" +#include "../compiler_utils.hpp" +#include "../components/class_id.hpp" +#include "../components/color.hpp" +#include "../components/half_size3d.hpp" +#include "../components/position3d.hpp" +#include "../components/radius.hpp" +#include "../components/rotation3d.hpp" +#include "../components/text.hpp" +#include "../data_cell.hpp" +#include "../indicator_component.hpp" +#include "../result.hpp" + +#include +#include +#include +#include + +namespace rerun::archetypes { + /// **Archetype**: 3D ellipsoids or spheres. + /// + /// This archetype is for ellipsoids or spheres whose size is a key part of the data + /// (e.g. a bounding sphere). + /// For points whose radii are for the sake of visualization, use `Points3D` instead. + /// + /// Currently, ellipsoids are always rendered as wireframes. + /// Opaque and transparent rendering will be supported later. + struct Ellipsoids { + /// For each ellipsoid, half of its size on its three axes. + /// + /// If all components are equal, then it is a sphere with that radius. + Collection half_sizes; + + /// Optional center positions of the ellipsoids. + /// + /// If not specified, the centers will be at (0, 0, 0). + std::optional> centers; + + /// Optional rotations of the ellipsoids. + /// + /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + std::optional> rotations; + + /// Optional colors for the ellipsoids. + std::optional> colors; + + /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + std::optional> line_radii; + + /// Optional text labels for the ellipsoids. + std::optional> labels; + + /// Optional `ClassId`s for the ellipsoids. + /// + /// The class ID provides colors and labels if not specified explicitly. + std::optional> class_ids; + + public: + static constexpr const char IndicatorComponentName[] = + "rerun.components.EllipsoidsIndicator"; + + /// Indicator component, used to identify the archetype when converting to a list of components. + using IndicatorComponent = rerun::components::IndicatorComponent; + + public: + // Extensions to generated type defined in 'ellipsoids_ext.cpp' + + /// Creates new `Ellipsoids` that are spheres, with `half_sizes` created from radii. + // + // TODO(andreas): This should not take an std::vector. + static Ellipsoids from_radii(const std::vector& sizes); + + /// Creates new `Ellipsoids` that are spheres, with `half_sizes` and `centers` created + /// from centers and radii. + // + // TODO(andreas): This should not take an std::vector. + static Ellipsoids from_centers_and_radii( + const std::vector& centers, const std::vector& radii + ); + + /// Creates new `Ellipsoids` with `half_sizes` centered around the local origin. + static Ellipsoids from_half_sizes(Collection half_sizes) { + Ellipsoids ellipsoids; + ellipsoids.half_sizes = std::move(half_sizes); + return ellipsoids; + } + + /// Creates new `Ellipsoids` with `centers` and `half_sizes`. + static Ellipsoids from_centers_and_half_sizes( + Collection centers, + Collection half_sizes + ) { + Ellipsoids ellipsoids; + ellipsoids.half_sizes = std::move(half_sizes); + ellipsoids.centers = std::move(centers); + return ellipsoids; + } + + public: + Ellipsoids() = default; + Ellipsoids(Ellipsoids&& other) = default; + + /// Optional center positions of the ellipsoids. + /// + /// If not specified, the centers will be at (0, 0, 0). + Ellipsoids with_centers(Collection _centers) && { + centers = std::move(_centers); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional rotations of the ellipsoids. + /// + /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + Ellipsoids with_rotations(Collection _rotations) && { + rotations = std::move(_rotations); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional colors for the ellipsoids. + Ellipsoids with_colors(Collection _colors) && { + colors = std::move(_colors); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + Ellipsoids with_line_radii(Collection _line_radii) && { + line_radii = std::move(_line_radii); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional text labels for the ellipsoids. + Ellipsoids with_labels(Collection _labels) && { + labels = std::move(_labels); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional `ClassId`s for the ellipsoids. + /// + /// The class ID provides colors and labels if not specified explicitly. + Ellipsoids with_class_ids(Collection _class_ids) && { + class_ids = std::move(_class_ids); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + }; + +} // namespace rerun::archetypes + +namespace rerun { + /// \private + template + struct AsComponents; + + /// \private + template <> + struct AsComponents { + /// Serialize all set component batches. + static Result> serialize(const archetypes::Ellipsoids& archetype); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids_ext.cpp b/rerun_cpp/src/rerun/archetypes/ellipsoids_ext.cpp new file mode 100644 index 000000000000..7fd7c8a4126a --- /dev/null +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids_ext.cpp @@ -0,0 +1,77 @@ +#include "ellipsoids.hpp" + +#include "../collection_adapter_builtins.hpp" + +// #define EDIT_EXTENSION + +namespace rerun { + namespace archetypes { + +#ifdef EDIT_EXTENSION + // + + /// Creates new `Ellipsoids` that are spheres, with `half_sizes` created from radii. + // + // TODO(andreas): This should not take an std::vector. + static Ellipsoids from_radii(const std::vector& sizes); + + /// Creates new `Ellipsoids` that are spheres, with `half_sizes` and `centers` created + /// from centers and radii. + // + // TODO(andreas): This should not take an std::vector. + static Ellipsoids from_centers_and_radii( + const std::vector& centers, const std::vector& radii + ); + + /// Creates new `Ellipsoids` with `half_sizes` centered around the local origin. + static Ellipsoids from_half_sizes(Collection half_sizes) { + Ellipsoids ellipsoids; + ellipsoids.half_sizes = std::move(half_sizes); + return ellipsoids; + } + + /// Creates new `Ellipsoids` with `centers` and `half_sizes`. + static Ellipsoids from_centers_and_half_sizes( + Collection centers, + Collection half_sizes + ) { + Ellipsoids ellipsoids; + ellipsoids.half_sizes = std::move(half_sizes); + ellipsoids.centers = std::move(centers); + return ellipsoids; + } + + // +#endif + Ellipsoids Ellipsoids::from_radii(const std::vector& radii) { + std::vector half_sizes; + half_sizes.reserve(radii.size()); + for (const auto& radius : radii) { + half_sizes.emplace_back(radius, radius, radius); + } + + // Move the vector into a component batch. + return Ellipsoids::from_half_sizes(std::move(half_sizes)); + } + + Ellipsoids Ellipsoids::from_centers_and_radii( + const std::vector& centers, const std::vector& radii + ) { + auto num_components = std::min(centers.size(), radii.size()); + + std::vector half_sizes; + half_sizes.reserve(num_components); + + for (size_t i = 0; i < num_components; ++i) { + float radius = radii[i]; + half_sizes.emplace_back(radius, radius, radius); + } + + // We only transformed the radii; the centers are good as-is. + Ellipsoids ellipsoids; + ellipsoids.half_sizes = std::move(half_sizes); + ellipsoids.centers = std::move(centers); + return ellipsoids; + } + } // namespace archetypes +} // namespace rerun diff --git a/rerun_py/docs/gen_common_index.py b/rerun_py/docs/gen_common_index.py index 92aeaa0e2267..9e3d919ed3bd 100755 --- a/rerun_py/docs/gen_common_index.py +++ b/rerun_py/docs/gen_common_index.py @@ -183,6 +183,7 @@ class Section: "archetypes.Asset3D", "archetypes.Boxes2D", "archetypes.Boxes3D", + "archetypes.Ellipsoids", "archetypes.LineStrips2D", "archetypes.LineStrips3D", "archetypes.Mesh3D", diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index fb91da4e20b7..c3fb2a806ad8 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -48,6 +48,7 @@ Clear as Clear, DepthImage as DepthImage, DisconnectedSpace as DisconnectedSpace, + Ellipsoids as Ellipsoids, Image as Image, LineStrips2D as LineStrips2D, LineStrips3D as LineStrips3D, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes index 689720b6cbf0..a12d51c77170 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/archetypes/.gitattributes @@ -12,6 +12,7 @@ boxes3d.py linguist-generated=true clear.py linguist-generated=true depth_image.py linguist-generated=true disconnected_space.py linguist-generated=true +ellipsoids.py linguist-generated=true image.py linguist-generated=true line_strips2d.py linguist-generated=true line_strips3d.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/archetypes/__init__.py b/rerun_py/rerun_sdk/rerun/archetypes/__init__.py index 890f04d766bb..9547a265e712 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/__init__.py @@ -12,6 +12,7 @@ from .clear import Clear from .depth_image import DepthImage from .disconnected_space import DisconnectedSpace +from .ellipsoids import Ellipsoids from .image import Image from .line_strips2d import LineStrips2D from .line_strips3d import LineStrips3D @@ -40,6 +41,7 @@ "Clear", "DepthImage", "DisconnectedSpace", + "Ellipsoids", "Image", "LineStrips2D", "LineStrips3D", diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py new file mode 100644 index 000000000000..7a228344874d --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py @@ -0,0 +1,124 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs". + +# You can extend this class by creating a "EllipsoidsExt" class in "ellipsoids_ext.py". + +from __future__ import annotations + +from attrs import define, field + +from .. import components +from .._baseclasses import ( + Archetype, +) +from .ellipsoids_ext import EllipsoidsExt + +__all__ = ["Ellipsoids"] + + +@define(str=False, repr=False, init=False) +class Ellipsoids(EllipsoidsExt, Archetype): + """ + **Archetype**: 3D ellipsoids or spheres. + + This archetype is for ellipsoids or spheres whose size is a key part of the data + (e.g. a bounding sphere). + For points whose radii are for the sake of visualization, use `Points3D` instead. + + Currently, ellipsoids are always rendered as wireframes. + Opaque and transparent rendering will be supported later. + """ + + # __init__ can be found in ellipsoids_ext.py + + def __attrs_clear__(self) -> None: + """Convenience method for calling `__attrs_init__` with all `None`s.""" + self.__attrs_init__( + half_sizes=None, # type: ignore[arg-type] + centers=None, # type: ignore[arg-type] + rotations=None, # type: ignore[arg-type] + colors=None, # type: ignore[arg-type] + line_radii=None, # type: ignore[arg-type] + labels=None, # type: ignore[arg-type] + class_ids=None, # type: ignore[arg-type] + ) + + @classmethod + def _clear(cls) -> Ellipsoids: + """Produce an empty Ellipsoids, bypassing `__init__`.""" + inst = cls.__new__(cls) + inst.__attrs_clear__() + return inst + + half_sizes: components.HalfSize3DBatch = field( + metadata={"component": "required"}, + converter=components.HalfSize3DBatch._required, # type: ignore[misc] + ) + # For each ellipsoid, half of its size on its three axes. + # + # If all components are equal, then it is a sphere with that radius. + # + # (Docstring intentionally commented out to hide this field from the docs) + + centers: components.Position3DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.Position3DBatch._optional, # type: ignore[misc] + ) + # Optional center positions of the ellipsoids. + # + # If not specified, the centers will be at (0, 0, 0). + # + # (Docstring intentionally commented out to hide this field from the docs) + + rotations: components.Rotation3DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.Rotation3DBatch._optional, # type: ignore[misc] + ) + # Optional rotations of the ellipsoids. + # + # If not specified, the axes of the ellipsoid align with the axes of the coordinate system. + # + # (Docstring intentionally commented out to hide this field from the docs) + + colors: components.ColorBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.ColorBatch._optional, # type: ignore[misc] + ) + # Optional colors for the ellipsoids. + # + # (Docstring intentionally commented out to hide this field from the docs) + + line_radii: components.RadiusBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.RadiusBatch._optional, # type: ignore[misc] + ) + # Optional radii for the lines used when the ellipsoid is rendered as a wireframe. + # + # (Docstring intentionally commented out to hide this field from the docs) + + labels: components.TextBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.TextBatch._optional, # type: ignore[misc] + ) + # Optional text labels for the ellipsoids. + # + # (Docstring intentionally commented out to hide this field from the docs) + + class_ids: components.ClassIdBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.ClassIdBatch._optional, # type: ignore[misc] + ) + # Optional `ClassId`s for the ellipsoids. + # + # The class ID provides colors and labels if not specified explicitly. + # + # (Docstring intentionally commented out to hide this field from the docs) + + __str__ = Archetype.__str__ + __repr__ = Archetype.__repr__ # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py new file mode 100644 index 000000000000..aa8a9e9b0da5 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import Any + +import numpy as np + +from .. import datatypes +from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions + + +class EllipsoidsExt: + """Extension for [Ellipsoids][rerun.archetypes.Ellipsoids].""" + + def __init__( + self: Any, + *, + half_sizes: datatypes.Vec3DArrayLike | None = None, + radii: datatypes.Float32ArrayLike | None = None, + centers: datatypes.Vec3DArrayLike | None = None, + rotations: datatypes.Rotation3DArrayLike | None = None, + colors: datatypes.Rgba32ArrayLike | None = None, + line_radii: datatypes.Float32ArrayLike | None = None, + labels: datatypes.Utf8ArrayLike | None = None, + class_ids: datatypes.ClassIdArrayLike | None = None, + ) -> None: + """ + Create a new instance of the Ellipsoids archetype. + + Parameters + ---------- + half_sizes: + All half-extents that make up the batch of ellipsoids. + Specify this instead of `radii` + radii: + All radii that make up this batch of spheres. + Specify this instead of `half_sizes` + centers: + Optional center positions of the ellipsoids. + rotations: + Optional rotations of the ellipsoids. + colors: + Optional colors for the ellipsoids. + line_radii: + Optional radii for the lines that make up the ellipsoids. + labels: + Optional text labels for the ellipsoids. + class_ids: + Optional `ClassId`s for the ellipsoids. + + The class ID provides colors and labels if not specified explicitly. + + """ + + with catch_and_log_exceptions(context=self.__class__.__name__): + if radii is not None: + if half_sizes is not None: + _send_warning_or_raise("Cannot specify both `radii` and `half_sizes` at the same time.", 1) + + radii = np.asarray(radii, dtype=np.float32) + # Duplicate [r1, r2, ...] to [[r1, r1, r1], [r2, r2, r2], ...] + half_sizes = np.repeat(np.expand_dims(radii, axis=1), 3, axis=1) + + self.__attrs_init__( + half_sizes=half_sizes, + centers=centers, + rotations=rotations, + colors=colors, + line_radii=line_radii, + labels=labels, + class_ids=class_ids, + ) + return + + self.__attrs_clear__()