diff --git a/Cargo.lock b/Cargo.lock index 1e04517c3353..9fa132792d8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6499,6 +6499,7 @@ dependencies = [ "re_format", "re_log", "re_log_types", + "re_math", "re_tracing", "re_types_builder", "re_types_core", diff --git a/crates/store/re_types/Cargo.toml b/crates/store/re_types/Cargo.toml index 94caec927202..46a740e3d6b6 100644 --- a/crates/store/re_types/Cargo.toml +++ b/crates/store/re_types/Cargo.toml @@ -33,8 +33,8 @@ ecolor = ["dep:ecolor"] ## Enable conversions to plot primitives egui_plot = ["dep:egui_plot"] -## Add support for some math operations using [`glam`](https://crates.io/crates/glam/). -glam = ["dep:glam"] +## Add support for some math operations using [`glam`](https://crates.io/crates/glam/) and [`re_math`](https://crates.io/crates/re_math/). +glam = ["dep:glam", "dep:re_math"] ## Integration with the [`image`](https://crates.io/crates/image/) crate, plus JPEG support. image = ["dep:ecolor", "dep:image"] @@ -92,6 +92,7 @@ image = { workspace = true, optional = true, default-features = false, features "jpeg", ] } mint = { workspace = true, optional = true } +re_math = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive", "rc"] } diff --git a/crates/store/re_types/definitions/rerun/archetypes/instance_poses3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/instance_poses3d.fbs index 4e753cc21e21..d7ffbabe01ea 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/instance_poses3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/instance_poses3d.fbs @@ -13,7 +13,7 @@ namespace rerun.archetypes; /// /// Currently, many visualizers support only a single instance transform per entity. /// Check archetype documentations for details - if not otherwise specified, only the first instance transform is applied. -/// Some visualizers like the mesh visualizer used for [`archetype.Mesh3D`], +/// Some visualizers like the mesh visualizer used for [archetypes.Mesh3D], /// will draw an object for every pose, a behavior also known as "instancing". /// /// \example archetypes/instance_poses3d_combined title="Regular & instance transforms in tandem" image="https://static.rerun.io/leaf_transform3d/41674f0082d6de489f8a1cd1583f60f6b5820ddf/1200w.png" diff --git a/crates/store/re_types/definitions/rerun/archetypes/pinhole.fbs b/crates/store/re_types/definitions/rerun/archetypes/pinhole.fbs index 65ad644ef7bc..e5071aacb00f 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/pinhole.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/pinhole.fbs @@ -32,7 +32,7 @@ table Pinhole ( /// Sets the view coordinates for the camera. /// - /// All common values are available as constants on the `components.ViewCoordinates` class. + /// All common values are available as constants on the [components.ViewCoordinates] class. /// /// The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. /// This means that the camera frustum will point along the positive Z axis of the parent space, diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes.fbs index 47eb842fc7c0..448b93b16a60 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/archetypes.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes.fbs @@ -3,6 +3,7 @@ include "./archetypes/background.fbs"; include "./archetypes/container_blueprint.fbs"; include "./archetypes/dataframe_query.fbs"; +include "./archetypes/line_grid3d.fbs"; include "./archetypes/map_background.fbs"; include "./archetypes/map_zoom.fbs"; include "./archetypes/panel_blueprint.fbs"; diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes/line_grid3d.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes/line_grid3d.fbs new file mode 100644 index 000000000000..3143471766f1 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes/line_grid3d.fbs @@ -0,0 +1,33 @@ +namespace rerun.blueprint.archetypes; + +/// Configuration for the 3D line grid. +table LineGrid3D ( + "attr.rerun.scope": "blueprint" +) { + /// Whether the grid is visible. + /// + /// Defaults to true. + visible: rerun.blueprint.components.Visible ("attr.rerun.component_optional", nullable, order: 1000); + + /// Space between grid lines spacing of one line to the next in scene units. + /// + /// As you zoom out, successively only every tenth line is shown. + /// This controls the closest zoom level. + spacing: rerun.blueprint.components.GridSpacing ("attr.rerun.component_optional", nullable, order: 2000); + + /// In what plane the grid is drawn. + /// + /// Defaults to whatever plane is determined as the plane at zero units up/down as defined by [components.ViewCoordinates] if present. + plane: rerun.components.Plane3D ("attr.rerun.component_optional", nullable, order: 3000); + + /// How thick the lines should be in ui units. + /// + /// Default is 1.0 ui unit. + stroke_width: rerun.components.StrokeWidth ("attr.rerun.component_optional", nullable, order: 5000); + + /// Color used for the grid. + /// + /// Transparency via alpha channel is supported. + /// Defaults to a slightly transparent light gray. + color: rerun.components.Color ("attr.rerun.component_optional", nullable, order: 6000); +} diff --git a/crates/store/re_types/definitions/rerun/blueprint/components.fbs b/crates/store/re_types/definitions/rerun/blueprint/components.fbs index f52c3ae986f1..1ee15c8acbe8 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/components.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/components.fbs @@ -12,6 +12,7 @@ include "./components/corner_2d.fbs"; include "./components/filter_by_range.fbs"; include "./components/filter_is_not_null.fbs"; include "./components/grid_columns.fbs"; +include "./components/grid_spacing.fbs"; include "./components/included_content.fbs"; include "./components/interactive.fbs"; include "./components/lock_range_during_zoom.fbs"; diff --git a/crates/store/re_types/definitions/rerun/blueprint/components/grid_spacing.fbs b/crates/store/re_types/definitions/rerun/blueprint/components/grid_spacing.fbs new file mode 100644 index 000000000000..79c680679c47 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/blueprint/components/grid_spacing.fbs @@ -0,0 +1,11 @@ +namespace rerun.blueprint.components; + +/// Space between grid lines of one line to the next in scene units. +table GridSpacing ( + "attr.python.aliases": "float", + "attr.python.array_aliases": "npt.ArrayLike", + "attr.rerun.scope": "blueprint" +) { + /// Space between grid lines of one line to the next in scene units. + distance: rerun.datatypes.Float32 (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/blueprint/views/spatial3d.fbs b/crates/store/re_types/definitions/rerun/blueprint/views/spatial3d.fbs index 53d18994433b..ba69f61302cc 100644 --- a/crates/store/re_types/definitions/rerun/blueprint/views/spatial3d.fbs +++ b/crates/store/re_types/definitions/rerun/blueprint/views/spatial3d.fbs @@ -9,6 +9,9 @@ table Spatial3DView ( /// Configuration for the background of the view. background: rerun.blueprint.archetypes.Background (order: 1000); + /// Configuration for the 3D line grid. + line_grid: rerun.blueprint.archetypes.LineGrid3D (order: 2000); + /// Configures which range on each timeline is shown by this view (unless specified differently per entity). /// /// If not specified, the default is to show the latest state of each component. diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 57a3c7a1b851..4dc580313dc2 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -37,6 +37,7 @@ include "./components/media_type.fbs"; include "./components/name.fbs"; include "./components/opacity.fbs"; include "./components/pinhole_projection.fbs"; +include "./components/plane3d.fbs"; include "./components/position2d.fbs"; include "./components/position3d.fbs"; include "./components/radius.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/plane3d.fbs b/crates/store/re_types/definitions/rerun/components/plane3d.fbs new file mode 100644 index 000000000000..e60aa4efdf58 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/plane3d.fbs @@ -0,0 +1,18 @@ +namespace rerun.components; + +/// An infinite 3D plane represented by a unit normal vector and a distance. +/// +/// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +/// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +/// This representation is also known as the Hesse normal form. +/// +/// Note: although the normal will be passed through to the +/// datastore as provided, when used in the Viewer, planes will always be normalized. +/// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 +struct Plane3D ( + "attr.docs.unreleased", + "attr.rust.derive": "Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + xyzd: rerun.datatypes.Plane3D (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/datatypes.fbs b/crates/store/re_types/definitions/rerun/datatypes.fbs index c56691a867b2..5772f4c3b8d5 100644 --- a/crates/store/re_types/definitions/rerun/datatypes.fbs +++ b/crates/store/re_types/definitions/rerun/datatypes.fbs @@ -19,6 +19,7 @@ include "./datatypes/keypoint_pair.fbs"; include "./datatypes/mat3x3.fbs"; include "./datatypes/mat4x4.fbs"; include "./datatypes/pixel_format.fbs"; +include "./datatypes/plane3d.fbs"; include "./datatypes/quaternion.fbs"; include "./datatypes/range1d.fbs"; include "./datatypes/range2d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/datatypes/plane3d.fbs b/crates/store/re_types/definitions/rerun/datatypes/plane3d.fbs new file mode 100644 index 000000000000..a490f8450481 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/datatypes/plane3d.fbs @@ -0,0 +1,22 @@ +namespace rerun.datatypes; + +/// An infinite 3D plane represented by a unit normal vector and a distance. +/// +/// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +/// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +/// This representation is also known as the Hesse normal form. +/// +/// Note: although the normal will be passed through to the +/// datastore as provided, when used in the Viewer, planes will always be normalized. +/// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 +struct Plane3D ( + "attr.docs.unreleased", + "attr.arrow.transparent", + "attr.python.array_aliases": "npt.NDArray[Any], npt.ArrayLike, Sequence[Sequence[float]]", + "attr.rust.derive": "Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.tuple_struct", + "attr.rust.repr": "C", + "attr.cpp.no_field_ctors" // Always be explicit about the values of the fields. +) { + xyzd: [float: 4] (order: 100); +} diff --git a/crates/store/re_types/src/archetypes/instance_poses3d.rs b/crates/store/re_types/src/archetypes/instance_poses3d.rs index 0903a53dfb68..08afec086b9e 100644 --- a/crates/store/re_types/src/archetypes/instance_poses3d.rs +++ b/crates/store/re_types/src/archetypes/instance_poses3d.rs @@ -30,7 +30,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// Currently, many visualizers support only a single instance transform per entity. /// Check archetype documentations for details - if not otherwise specified, only the first instance transform is applied. -/// Some visualizers like the mesh visualizer used for [`archetype.Mesh3D`], +/// Some visualizers like the mesh visualizer used for [`archetypes::Mesh3D`][crate::archetypes::Mesh3D], /// will draw an object for every pose, a behavior also known as "instancing". /// /// ## Example diff --git a/crates/store/re_types/src/archetypes/pinhole.rs b/crates/store/re_types/src/archetypes/pinhole.rs index 95d09610101d..1ccdbb8fa87b 100644 --- a/crates/store/re_types/src/archetypes/pinhole.rs +++ b/crates/store/re_types/src/archetypes/pinhole.rs @@ -103,7 +103,7 @@ pub struct Pinhole { /// Sets the view coordinates for the camera. /// - /// All common values are available as constants on the `components.ViewCoordinates` class. + /// All common values are available as constants on the [`components::ViewCoordinates`][crate::components::ViewCoordinates] class. /// /// The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. /// This means that the camera frustum will point along the positive Z axis of the parent space, @@ -345,7 +345,7 @@ impl Pinhole { /// Sets the view coordinates for the camera. /// - /// All common values are available as constants on the `components.ViewCoordinates` class. + /// All common values are available as constants on the [`components::ViewCoordinates`][crate::components::ViewCoordinates] class. /// /// The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. /// This means that the camera frustum will point along the positive Z axis of the parent space, diff --git a/crates/store/re_types/src/blueprint/archetypes/.gitattributes b/crates/store/re_types/src/blueprint/archetypes/.gitattributes index 158c53d4bb45..0053d35aa081 100644 --- a/crates/store/re_types/src/blueprint/archetypes/.gitattributes +++ b/crates/store/re_types/src/blueprint/archetypes/.gitattributes @@ -3,6 +3,7 @@ .gitattributes linguist-generated=true background.rs linguist-generated=true dataframe_query.rs linguist-generated=true +line_grid3d.rs linguist-generated=true map_background.rs linguist-generated=true map_zoom.rs linguist-generated=true mod.rs linguist-generated=true diff --git a/crates/store/re_types/src/blueprint/archetypes/line_grid3d.rs b/crates/store/re_types/src/blueprint/archetypes/line_grid3d.rs new file mode 100644 index 000000000000..a5121afe618c --- /dev/null +++ b/crates/store/re_types/src/blueprint/archetypes/line_grid3d.rs @@ -0,0 +1,313 @@ +// 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/blueprint/archetypes/line_grid3d.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**: Configuration for the 3D line grid. +#[derive(Clone, Debug)] +pub struct LineGrid3D { + /// Whether the grid is visible. + /// + /// Defaults to true. + pub visible: Option, + + /// Space between grid lines spacing of one line to the next in scene units. + /// + /// As you zoom out, successively only every tenth line is shown. + /// This controls the closest zoom level. + pub spacing: Option, + + /// In what plane the grid is drawn. + /// + /// Defaults to whatever plane is determined as the plane at zero units up/down as defined by [`components::ViewCoordinates`][crate::components::ViewCoordinates] if present. + pub plane: Option, + + /// How thick the lines should be in ui units. + /// + /// Default is 1.0 ui unit. + pub stroke_width: Option, + + /// Color used for the grid. + /// + /// Transparency via alpha channel is supported. + /// Defaults to a slightly transparent light gray. + pub color: Option, +} + +impl ::re_types_core::SizeBytes for LineGrid3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.visible.heap_size_bytes() + + self.spacing.heap_size_bytes() + + self.plane.heap_size_bytes() + + self.stroke_width.heap_size_bytes() + + self.color.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + >::is_pod() + && >::is_pod() + && >::is_pod() + && >::is_pod() + && >::is_pod() + } +} + +static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = + once_cell::sync::Lazy::new(|| []); + +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = + once_cell::sync::Lazy::new(|| ["rerun.blueprint.components.LineGrid3DIndicator".into()]); + +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 5usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.Visible".into(), + "rerun.blueprint.components.GridSpacing".into(), + "rerun.components.Plane3D".into(), + "rerun.components.StrokeWidth".into(), + "rerun.components.Color".into(), + ] + }); + +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 6usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.LineGrid3DIndicator".into(), + "rerun.blueprint.components.Visible".into(), + "rerun.blueprint.components.GridSpacing".into(), + "rerun.components.Plane3D".into(), + "rerun.components.StrokeWidth".into(), + "rerun.components.Color".into(), + ] + }); + +impl LineGrid3D { + /// The total number of components in the archetype: 0 required, 1 recommended, 5 optional + pub const NUM_COMPONENTS: usize = 6usize; +} + +/// Indicator component for the [`LineGrid3D`] [`::re_types_core::Archetype`] +pub type LineGrid3DIndicator = ::re_types_core::GenericIndicatorComponent; + +impl ::re_types_core::Archetype for LineGrid3D { + type Indicator = LineGrid3DIndicator; + + #[inline] + fn name() -> ::re_types_core::ArchetypeName { + "rerun.blueprint.archetypes.LineGrid3D".into() + } + + #[inline] + fn display_name() -> &'static str { + "Line grid 3D" + } + + #[inline] + fn indicator() -> MaybeOwnedComponentBatch<'static> { + static INDICATOR: LineGrid3DIndicator = LineGrid3DIndicator::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_arrow2_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 visible = if let Some(array) = arrays_by_name.get("rerun.blueprint.components.Visible") + { + ::from_arrow2_opt(&**array) + .with_context("rerun.blueprint.archetypes.LineGrid3D#visible")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let spacing = + if let Some(array) = arrays_by_name.get("rerun.blueprint.components.GridSpacing") { + ::from_arrow2_opt(&**array) + .with_context("rerun.blueprint.archetypes.LineGrid3D#spacing")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let plane = if let Some(array) = arrays_by_name.get("rerun.components.Plane3D") { + ::from_arrow2_opt(&**array) + .with_context("rerun.blueprint.archetypes.LineGrid3D#plane")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let stroke_width = if let Some(array) = arrays_by_name.get("rerun.components.StrokeWidth") { + ::from_arrow2_opt(&**array) + .with_context("rerun.blueprint.archetypes.LineGrid3D#stroke_width")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let color = if let Some(array) = arrays_by_name.get("rerun.components.Color") { + ::from_arrow2_opt(&**array) + .with_context("rerun.blueprint.archetypes.LineGrid3D#color")? + .into_iter() + .next() + .flatten() + } else { + None + }; + Ok(Self { + visible, + spacing, + plane, + stroke_width, + color, + }) + } +} + +impl ::re_types_core::AsComponents for LineGrid3D { + fn as_component_batches(&self) -> Vec> { + re_tracing::profile_function!(); + use ::re_types_core::Archetype as _; + [ + Some(Self::indicator()), + self.visible + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.spacing + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.plane + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.stroke_width + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.color + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + ] + .into_iter() + .flatten() + .collect() + } +} + +impl ::re_types_core::ArchetypeReflectionMarker for LineGrid3D {} + +impl LineGrid3D { + /// Create a new `LineGrid3D`. + #[inline] + pub fn new() -> Self { + Self { + visible: None, + spacing: None, + plane: None, + stroke_width: None, + color: None, + } + } + + /// Whether the grid is visible. + /// + /// Defaults to true. + #[inline] + pub fn with_visible( + mut self, + visible: impl Into, + ) -> Self { + self.visible = Some(visible.into()); + self + } + + /// Space between grid lines spacing of one line to the next in scene units. + /// + /// As you zoom out, successively only every tenth line is shown. + /// This controls the closest zoom level. + #[inline] + pub fn with_spacing( + mut self, + spacing: impl Into, + ) -> Self { + self.spacing = Some(spacing.into()); + self + } + + /// In what plane the grid is drawn. + /// + /// Defaults to whatever plane is determined as the plane at zero units up/down as defined by [`components::ViewCoordinates`][crate::components::ViewCoordinates] if present. + #[inline] + pub fn with_plane(mut self, plane: impl Into) -> Self { + self.plane = Some(plane.into()); + self + } + + /// How thick the lines should be in ui units. + /// + /// Default is 1.0 ui unit. + #[inline] + pub fn with_stroke_width( + mut self, + stroke_width: impl Into, + ) -> Self { + self.stroke_width = Some(stroke_width.into()); + self + } + + /// Color used for the grid. + /// + /// Transparency via alpha channel is supported. + /// Defaults to a slightly transparent light gray. + #[inline] + pub fn with_color(mut self, color: impl Into) -> Self { + self.color = Some(color.into()); + self + } +} diff --git a/crates/store/re_types/src/blueprint/archetypes/mod.rs b/crates/store/re_types/src/blueprint/archetypes/mod.rs index 360c1b887939..4b5aa896e251 100644 --- a/crates/store/re_types/src/blueprint/archetypes/mod.rs +++ b/crates/store/re_types/src/blueprint/archetypes/mod.rs @@ -2,6 +2,7 @@ mod background; mod dataframe_query; +mod line_grid3d; mod map_background; mod map_zoom; mod plot_legend; @@ -17,6 +18,7 @@ mod visual_bounds2d; pub use self::background::Background; pub use self::dataframe_query::DataframeQuery; +pub use self::line_grid3d::LineGrid3D; pub use self::map_background::MapBackground; pub use self::map_zoom::MapZoom; pub use self::plot_legend::PlotLegend; diff --git a/crates/store/re_types/src/blueprint/components/.gitattributes b/crates/store/re_types/src/blueprint/components/.gitattributes index 4a1915dc979d..238359328a19 100644 --- a/crates/store/re_types/src/blueprint/components/.gitattributes +++ b/crates/store/re_types/src/blueprint/components/.gitattributes @@ -9,6 +9,7 @@ component_column_selector.rs linguist-generated=true corner2d.rs linguist-generated=true filter_by_range.rs linguist-generated=true filter_is_not_null.rs linguist-generated=true +grid_spacing.rs linguist-generated=true included_content.rs linguist-generated=true interactive.rs linguist-generated=true lock_range_during_zoom.rs linguist-generated=true diff --git a/crates/store/re_types/src/blueprint/components/grid_spacing.rs b/crates/store/re_types/src/blueprint/components/grid_spacing.rs new file mode 100644 index 000000000000..c729b3234385 --- /dev/null +++ b/crates/store/re_types/src/blueprint/components/grid_spacing.rs @@ -0,0 +1,116 @@ +// 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/blueprint/components/grid_spacing.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}; + +/// **Component**: Space between grid lines of one line to the next in scene units. +#[derive(Clone, Debug)] +pub struct GridSpacing( + /// Space between grid lines of one line to the next in scene units. + pub crate::datatypes::Float32, +); + +impl ::re_types_core::SizeBytes for GridSpacing { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for GridSpacing { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for GridSpacing { + #[inline] + fn borrow(&self) -> &crate::datatypes::Float32 { + &self.0 + } +} + +impl std::ops::Deref for GridSpacing { + type Target = crate::datatypes::Float32; + + #[inline] + fn deref(&self) -> &crate::datatypes::Float32 { + &self.0 + } +} + +impl std::ops::DerefMut for GridSpacing { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Float32 { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(GridSpacing); + +impl ::re_types_core::Loggable for GridSpacing { + #[inline] + fn arrow_datatype() -> arrow::datatypes::DataType { + crate::datatypes::Float32::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult + where + Self: Clone + 'a, + { + crate::datatypes::Float32::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow2_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Float32::from_arrow2_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow2(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Float32::from_arrow2(arrow_data) + .map(|v| v.into_iter().map(Self).collect()) + } +} + +impl ::re_types_core::Component for GridSpacing { + #[inline] + fn name() -> ComponentName { + "rerun.blueprint.components.GridSpacing".into() + } +} diff --git a/crates/store/re_types/src/blueprint/components/grid_spacing_ext.rs b/crates/store/re_types/src/blueprint/components/grid_spacing_ext.rs new file mode 100644 index 000000000000..75ff4c8e9974 --- /dev/null +++ b/crates/store/re_types/src/blueprint/components/grid_spacing_ext.rs @@ -0,0 +1,9 @@ +use super::GridSpacing; + +impl Default for GridSpacing { + #[inline] + fn default() -> Self { + // Default to a unit grid. + 1.0.into() + } +} diff --git a/crates/store/re_types/src/blueprint/components/mod.rs b/crates/store/re_types/src/blueprint/components/mod.rs index a915957fd392..ca655d4e56b3 100644 --- a/crates/store/re_types/src/blueprint/components/mod.rs +++ b/crates/store/re_types/src/blueprint/components/mod.rs @@ -12,6 +12,8 @@ mod filter_by_range; mod filter_by_range_ext; mod filter_is_not_null; mod filter_is_not_null_ext; +mod grid_spacing; +mod grid_spacing_ext; mod included_content; mod interactive; mod interactive_ext; @@ -48,6 +50,7 @@ pub use self::component_column_selector::ComponentColumnSelector; pub use self::corner2d::Corner2D; pub use self::filter_by_range::FilterByRange; pub use self::filter_is_not_null::FilterIsNotNull; +pub use self::grid_spacing::GridSpacing; pub use self::included_content::IncludedContent; pub use self::interactive::Interactive; pub use self::lock_range_during_zoom::LockRangeDuringZoom; diff --git a/crates/store/re_types/src/blueprint/views/spatial3d_view.rs b/crates/store/re_types/src/blueprint/views/spatial3d_view.rs index 7eb491d3334f..2c1987c0c473 100644 --- a/crates/store/re_types/src/blueprint/views/spatial3d_view.rs +++ b/crates/store/re_types/src/blueprint/views/spatial3d_view.rs @@ -24,6 +24,9 @@ pub struct Spatial3DView { /// Configuration for the background of the view. pub background: crate::blueprint::archetypes::Background, + /// Configuration for the 3D line grid. + pub line_grid: crate::blueprint::archetypes::LineGrid3D, + /// Configures which range on each timeline is shown by this view (unless specified differently per entity). /// /// If not specified, the default is to show the latest state of each component. @@ -34,12 +37,15 @@ pub struct Spatial3DView { impl ::re_types_core::SizeBytes for Spatial3DView { #[inline] fn heap_size_bytes(&self) -> u64 { - self.background.heap_size_bytes() + self.time_ranges.heap_size_bytes() + self.background.heap_size_bytes() + + self.line_grid.heap_size_bytes() + + self.time_ranges.heap_size_bytes() } #[inline] fn is_pod() -> bool { ::is_pod() + && ::is_pod() && ::is_pod() } } diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index f45278457d5c..3cf54dbb31cb 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -38,6 +38,7 @@ mod.rs linguist-generated=true name.rs linguist-generated=true opacity.rs linguist-generated=true pinhole_projection.rs linguist-generated=true +plane3d.rs linguist-generated=true pose_rotation_axis_angle.rs linguist-generated=true pose_rotation_quat.rs linguist-generated=true pose_scale3d.rs linguist-generated=true diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index 44993beca085..c085b8954e44 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -62,6 +62,8 @@ mod opacity; mod opacity_ext; mod pinhole_projection; mod pinhole_projection_ext; +mod plane3d; +mod plane3d_ext; mod pose_rotation_axis_angle; mod pose_rotation_axis_angle_ext; mod pose_rotation_quat; @@ -161,6 +163,7 @@ pub use self::media_type::MediaType; pub use self::name::Name; pub use self::opacity::Opacity; pub use self::pinhole_projection::PinholeProjection; +pub use self::plane3d::Plane3D; pub use self::pose_rotation_axis_angle::PoseRotationAxisAngle; pub use self::pose_rotation_quat::PoseRotationQuat; pub use self::pose_scale3d::PoseScale3D; diff --git a/crates/store/re_types/src/components/plane3d.rs b/crates/store/re_types/src/components/plane3d.rs new file mode 100644 index 000000000000..d0addd824006 --- /dev/null +++ b/crates/store/re_types/src/components/plane3d.rs @@ -0,0 +1,121 @@ +// 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/components/plane3d.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}; + +/// **Component**: An infinite 3D plane represented by a unit normal vector and a distance. +/// +/// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +/// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +/// This representation is also known as the Hesse normal form. +/// +/// Note: although the normal will be passed through to the +/// datastore as provided, when used in the Viewer, planes will always be normalized. +/// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 +#[derive(Clone, Debug, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct Plane3D(pub crate::datatypes::Plane3D); + +impl ::re_types_core::SizeBytes for Plane3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for Plane3D { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for Plane3D { + #[inline] + fn borrow(&self) -> &crate::datatypes::Plane3D { + &self.0 + } +} + +impl std::ops::Deref for Plane3D { + type Target = crate::datatypes::Plane3D; + + #[inline] + fn deref(&self) -> &crate::datatypes::Plane3D { + &self.0 + } +} + +impl std::ops::DerefMut for Plane3D { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Plane3D { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(Plane3D); + +impl ::re_types_core::Loggable for Plane3D { + #[inline] + fn arrow_datatype() -> arrow::datatypes::DataType { + crate::datatypes::Plane3D::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult + where + Self: Clone + 'a, + { + crate::datatypes::Plane3D::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow2_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Plane3D::from_arrow2_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow2(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Plane3D::from_arrow2(arrow_data).map(bytemuck::cast_vec) + } +} + +impl ::re_types_core::Component for Plane3D { + #[inline] + fn name() -> ComponentName { + "rerun.components.Plane3D".into() + } +} diff --git a/crates/store/re_types/src/components/plane3d_ext.rs b/crates/store/re_types/src/components/plane3d_ext.rs new file mode 100644 index 000000000000..f8abe92faf26 --- /dev/null +++ b/crates/store/re_types/src/components/plane3d_ext.rs @@ -0,0 +1,32 @@ +use super::Plane3D; + +impl Plane3D { + /// The Y^Z plane with normal = +X. + pub const YZ: Self = Self(crate::datatypes::Plane3D([1.0, 0.0, 0.0, 0.0])); + + /// The Z^X plane with normal = +Y. + pub const ZX: Self = Self(crate::datatypes::Plane3D([0.0, 1.0, 0.0, 0.0])); + + /// The X^Y plane with normal = +Z. + pub const XY: Self = Self(crate::datatypes::Plane3D([0.0, 0.0, 1.0, 0.0])); + + /// Create a new plane from a normal and distance. + /// + /// The plane will not be normalized upon creation. + #[inline] + pub fn new(normal: impl Into, distance: f32) -> Self { + Self(crate::datatypes::Plane3D::new(normal, distance)) + } +} + +#[cfg(feature = "glam")] +impl From for re_math::Plane3 { + #[inline] + fn from(plane: Plane3D) -> Self { + Self { + normal: glam::vec3(plane.0 .0[0], plane.0 .0[1], plane.0 .0[2]), + d: plane.0 .0[3], + } + .normalized() + } +} diff --git a/crates/store/re_types/src/datatypes/.gitattributes b/crates/store/re_types/src/datatypes/.gitattributes index 978b395e02a9..289cbd45793c 100644 --- a/crates/store/re_types/src/datatypes/.gitattributes +++ b/crates/store/re_types/src/datatypes/.gitattributes @@ -17,6 +17,7 @@ mat3x3.rs linguist-generated=true mat4x4.rs linguist-generated=true mod.rs linguist-generated=true pixel_format.rs linguist-generated=true +plane3d.rs linguist-generated=true quaternion.rs linguist-generated=true range1d.rs linguist-generated=true range2d.rs linguist-generated=true diff --git a/crates/store/re_types/src/datatypes/mod.rs b/crates/store/re_types/src/datatypes/mod.rs index 69cada396e56..d1edf377eb08 100644 --- a/crates/store/re_types/src/datatypes/mod.rs +++ b/crates/store/re_types/src/datatypes/mod.rs @@ -30,6 +30,8 @@ mod mat4x4; mod mat4x4_ext; mod pixel_format; mod pixel_format_ext; +mod plane3d; +mod plane3d_ext; mod quaternion; mod quaternion_ext; mod range1d; @@ -85,6 +87,7 @@ pub use self::keypoint_pair::KeypointPair; pub use self::mat3x3::Mat3x3; pub use self::mat4x4::Mat4x4; pub use self::pixel_format::PixelFormat; +pub use self::plane3d::Plane3D; pub use self::quaternion::Quaternion; pub use self::range1d::Range1D; pub use self::range2d::Range2D; diff --git a/crates/store/re_types/src/datatypes/plane3d.rs b/crates/store/re_types/src/datatypes/plane3d.rs new file mode 100644 index 000000000000..54e8303158c1 --- /dev/null +++ b/crates/store/re_types/src/datatypes/plane3d.rs @@ -0,0 +1,257 @@ +// 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/datatypes/plane3d.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}; + +/// **Datatype**: An infinite 3D plane represented by a unit normal vector and a distance. +/// +/// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +/// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +/// This representation is also known as the Hesse normal form. +/// +/// Note: although the normal will be passed through to the +/// datastore as provided, when used in the Viewer, planes will always be normalized. +/// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 +#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Plane3D(pub [f32; 4usize]); + +impl ::re_types_core::SizeBytes for Plane3D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + <[f32; 4usize]>::is_pod() + } +} + +impl From<[f32; 4usize]> for Plane3D { + #[inline] + fn from(xyzd: [f32; 4usize]) -> Self { + Self(xyzd) + } +} + +impl From for [f32; 4usize] { + #[inline] + fn from(value: Plane3D) -> Self { + value.0 + } +} + +::re_types_core::macros::impl_into_cow!(Plane3D); + +impl ::re_types_core::Loggable for Plane3D { + #[inline] + fn arrow_datatype() -> arrow::datatypes::DataType { + #![allow(clippy::wildcard_imports)] + use arrow::datatypes::*; + DataType::FixedSizeList( + std::sync::Arc::new(Field::new("item", DataType::Float32, false)), + 4, + ) + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult + where + Self: Clone + 'a, + { + #![allow(clippy::wildcard_imports)] + #![allow(clippy::manual_is_variant_and)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow::{array::*, buffer::*, datatypes::*}; + + #[allow(unused)] + fn as_array_ref(t: T) -> ArrayRef { + std::sync::Arc::new(t) as ArrayRef + } + Ok({ + let (somes, data0): (Vec<_>, Vec<_>) = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + let datum = datum.map(|datum| datum.into_owned().0); + (datum.is_some(), datum) + }) + .unzip(); + let data0_validity: Option = { + let any_nones = somes.iter().any(|some| !*some); + any_nones.then(|| somes.into()) + }; + { + let data0_inner_data: Vec<_> = data0 + .into_iter() + .flat_map(|v| match v { + Some(v) => itertools::Either::Left(v.into_iter()), + None => itertools::Either::Right( + std::iter::repeat(Default::default()).take(4usize), + ), + }) + .collect(); + let data0_inner_validity: Option = + data0_validity.as_ref().map(|validity| { + validity + .iter() + .map(|b| std::iter::repeat(b).take(4usize)) + .flatten() + .collect::>() + .into() + }); + as_array_ref(FixedSizeListArray::new( + std::sync::Arc::new(Field::new("item", DataType::Float32, false)), + 4, + as_array_ref(PrimitiveArray::::new( + ScalarBuffer::from(data0_inner_data.into_iter().collect::>()), + data0_inner_validity, + )), + data0_validity, + )) + } + }) + } + + fn from_arrow2_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow::datatypes::*; + use arrow2::{array::*, buffer::*}; + Ok({ + let arrow_data = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + let expected = Self::arrow_datatype(); + let actual = arrow_data.data_type().clone(); + DeserializationError::datatype_mismatch(expected, actual) + }) + .with_context("rerun.datatypes.Plane3D#xyzd")?; + if arrow_data.is_empty() { + Vec::new() + } else { + let offsets = (0..) + .step_by(4usize) + .zip((4usize..).step_by(4usize).take(arrow_data.len())); + let arrow_data_inner = { + let arrow_data_inner = &**arrow_data.values(); + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + let expected = DataType::Float32; + let actual = arrow_data_inner.data_type().clone(); + DeserializationError::datatype_mismatch(expected, actual) + }) + .with_context("rerun.datatypes.Plane3D#xyzd")? + .into_iter() + .map(|opt| opt.copied()) + .collect::>() + }; + arrow2::bitmap::utils::ZipValidity::new_with_validity( + offsets, + arrow_data.validity(), + ) + .map(|elem| { + elem.map(|(start, end): (usize, usize)| { + debug_assert!(end - start == 4usize); + if end > arrow_data_inner.len() { + return Err(DeserializationError::offset_slice_oob( + (start, end), + arrow_data_inner.len(), + )); + } + + #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + let data = unsafe { arrow_data_inner.get_unchecked(start..end) }; + let data = data.iter().cloned().map(Option::unwrap_or_default); + + // NOTE: Unwrapping cannot fail: the length must be correct. + #[allow(clippy::unwrap_used)] + Ok(array_init::from_iter(data).unwrap()) + }) + .transpose() + }) + .collect::>>>()? + } + .into_iter() + } + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .map(|res| res.map(|v| Some(Self(v)))) + .collect::>>>() + .with_context("rerun.datatypes.Plane3D#xyzd") + .with_context("rerun.datatypes.Plane3D")?) + } + + #[inline] + fn from_arrow2(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow::datatypes::*; + use arrow2::{array::*, buffer::*}; + if let Some(validity) = arrow_data.validity() { + if validity.unset_bits() != 0 { + return Err(DeserializationError::missing_data()); + } + } + Ok({ + let slice = { + let arrow_data = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + let expected = DataType::FixedSizeList( + std::sync::Arc::new(Field::new("item", DataType::Float32, false)), + 4, + ); + let actual = arrow_data.data_type().clone(); + DeserializationError::datatype_mismatch(expected, actual) + }) + .with_context("rerun.datatypes.Plane3D#xyzd")?; + let arrow_data_inner = &**arrow_data.values(); + bytemuck::cast_slice::<_, [_; 4usize]>( + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + let expected = DataType::Float32; + let actual = arrow_data_inner.data_type().clone(); + DeserializationError::datatype_mismatch(expected, actual) + }) + .with_context("rerun.datatypes.Plane3D#xyzd")? + .values() + .as_slice(), + ) + }; + { + slice.iter().copied().map(Self).collect::>() + } + }) + } +} diff --git a/crates/store/re_types/src/datatypes/plane3d_ext.rs b/crates/store/re_types/src/datatypes/plane3d_ext.rs new file mode 100644 index 000000000000..3bcd454f45f5 --- /dev/null +++ b/crates/store/re_types/src/datatypes/plane3d_ext.rs @@ -0,0 +1,53 @@ +use super::Plane3D; + +impl Plane3D { + /// The Y^Z plane with normal = +X. + pub const YZ: Self = Self([1.0, 0.0, 0.0, 0.0]); + + /// The Z^X plane with normal = +Y. + pub const ZX: Self = Self([0.0, 1.0, 0.0, 0.0]); + + /// The X^Y plane with normal = +Z. + pub const XY: Self = Self([0.0, 0.0, 1.0, 0.0]); + + /// The normal of the plane (unnormalized if the plane is unnormalized). + #[inline] + pub const fn normal(&self) -> super::Vec3D { + super::Vec3D([self.0[0], self.0[1], self.0[2]]) + } + + /// The distance of the plane from the origin (in multiples of the normal if the normal is unnormalized). + #[inline] + pub const fn distance(&self) -> f32 { + self.0[3] + } + + /// Create a new plane from a normal and distance. + /// + /// The plane will not be normalized upon creation. + #[inline] + pub fn new(normal: impl Into, distance: f32) -> Self { + let normal = normal.into(); + Self([normal.0[0], normal.0[1], normal.0[2], distance]) + } +} + +#[cfg(feature = "glam")] +impl From for Plane3D { + #[inline] + fn from(plane: re_math::Plane3) -> Self { + Self([plane.normal.x, plane.normal.y, plane.normal.z, plane.d]) + } +} + +#[cfg(feature = "glam")] +impl From for re_math::Plane3 { + #[inline] + fn from(plane: Plane3D) -> Self { + Self { + normal: glam::vec3(plane.0[0], plane.0[1], plane.0[2]), + d: plane.0[3], + } + .normalized() + } +} diff --git a/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs b/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs index 4ab88fe24ce8..b9aa5fa87b9b 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/float_drag.rs @@ -6,15 +6,34 @@ use re_viewer_context::MaybeMutRef; /// Generic editor for a [`re_types::datatypes::Float32`] value from zero to max float. pub fn edit_f32_zero_to_max( + ctx: &re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, +) -> egui::Response { + edit_f32_zero_to_max_with_suffix(ctx, ui, value, "") +} + +/// Generic editor for a [`re_types::datatypes::Float32`] value from zero to max float with a suffix. +pub fn edit_f32_zero_to_max_with_suffix( _ctx: &re_viewer_context::ViewerContext<'_>, ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, + suffix: &str, ) -> egui::Response { let mut value: MaybeMutRef<'_, f32> = match value { MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.deref_mut().0), }; - edit_f32_float_raw_impl(ui, &mut value, 0.0..=f32::MAX) + edit_f32_float_raw(ui, &mut value, 0.0..=f32::MAX, suffix) +} + +/// Generic editor for a [`re_types::datatypes::Float32`] value representing a ui points value. +pub fn edit_ui_points( + ctx: &re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, +) -> egui::Response { + edit_f32_zero_to_max_with_suffix(ctx, ui, value, "pt") } /// Generic editor for a [`re_types::datatypes::Float32`] value from min to max float. @@ -27,17 +46,18 @@ pub fn edit_f32_min_to_max_float( MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.deref_mut().0), }; - edit_f32_float_raw_impl(ui, &mut value, f32::MIN..=f32::MAX) + edit_f32_float_raw(ui, &mut value, f32::MIN..=f32::MAX, "") } /// Non monomorphized implementation for f32 float editing. -pub fn edit_f32_float_raw_impl( +pub fn edit_f32_float_raw( ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, f32>, range: RangeInclusive, + suffix: &str, ) -> egui::Response { let speed = (value.abs() * 0.01).at_least(0.001); - edit_f32_float_raw_with_speed_impl(ui, value, range, speed) + edit_f32_float_raw_with_speed_impl(ui, value, range, speed, suffix) } /// Non monomorphized implementation for f32 float editing with a given speed. @@ -46,16 +66,18 @@ pub fn edit_f32_float_raw_with_speed_impl( value: &mut MaybeMutRef<'_, f32>, range: RangeInclusive, speed: f32, + suffix: &str, ) -> egui::Response { if let Some(value) = value.as_mut() { ui.add( egui::DragValue::new(value) .clamp_existing_to_range(false) .range(range) - .speed(speed), + .speed(speed) + .suffix(suffix), ) } else { - ui.label(re_format::format_f32(**value)) + ui.label(format!("{}{}", re_format::format_f32(**value), suffix)) } } diff --git a/crates/viewer/re_component_ui/src/datatype_uis/mod.rs b/crates/viewer/re_component_ui/src/datatype_uis/mod.rs index 54d66fbacfd3..9a8db3c714f9 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/mod.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/mod.rs @@ -13,13 +13,13 @@ pub use enum_combobox::{ VariantAvailableProvider, }; pub use float_drag::{ - edit_f32_min_to_max_float, edit_f32_zero_to_max, edit_f32_zero_to_one, - edit_f64_float_raw_with_speed_impl, + edit_f32_float_raw, edit_f32_min_to_max_float, edit_f32_zero_to_max, edit_f32_zero_to_one, + edit_f64_float_raw_with_speed_impl, edit_ui_points, }; pub use range1d::edit_view_range1d; pub use singleline_string::{ display_name_ui, display_text_ui, edit_multiline_string, edit_singleline_string, }; -pub use vec::edit_or_view_vec3d; +pub use vec::{edit_or_view_vec3d, edit_or_view_vec3d_raw}; pub use view_id::view_view_id; pub use view_uuid::view_uuid; diff --git a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs index ff78227b6ca4..2c06dd16c505 100644 --- a/crates/viewer/re_component_ui/src/datatype_uis/vec.rs +++ b/crates/viewer/re_component_ui/src/datatype_uis/vec.rs @@ -1,7 +1,7 @@ use re_types::datatypes; use re_viewer_context::MaybeMutRef; -use super::float_drag::edit_f32_float_raw_impl; +use super::float_drag::edit_f32_float_raw; pub fn edit_or_view_vec3d( _ctx: &re_viewer_context::ViewerContext<'_>, @@ -12,10 +12,34 @@ pub fn edit_or_view_vec3d( MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(value), }; - edit_or_view_vec3d_impl(ui, &mut value) + edit_or_view_vec3d_raw_immutable(ui, &mut value) } -fn edit_or_view_vec3d_impl( +// TODO(#6743): Since overrides are not yet taken into account, editing this value has no effect. +//MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value[i]), +fn edit_or_view_vec3d_raw_immutable( + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, datatypes::Vec3D>, +) -> egui::Response { + edit_or_view_vector_component_immutable(ui, value, 0) + | edit_or_view_vector_component_immutable(ui, value, 1) + | edit_or_view_vector_component_immutable(ui, value, 2) +} + +fn edit_or_view_vector_component_immutable( + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, datatypes::Vec3D>, + i: usize, +) -> egui::Response { + let mut value: MaybeMutRef<'_, f32> = match value { + MaybeMutRef::Ref(value) => MaybeMutRef::Ref(&value[i]), + + MaybeMutRef::MutRef(value) => MaybeMutRef::Ref(&value[i]), + }; + edit_f32_float_raw(ui, &mut value, f32::MIN..=f32::MAX, "") +} + +pub fn edit_or_view_vec3d_raw( ui: &mut egui::Ui, value: &mut MaybeMutRef<'_, datatypes::Vec3D>, ) -> egui::Response { @@ -31,9 +55,7 @@ fn edit_or_view_vector_component( ) -> egui::Response { let mut value: MaybeMutRef<'_, f32> = match value { MaybeMutRef::Ref(value) => MaybeMutRef::Ref(&value[i]), - // TODO(#6743): Since overrides are not yet taken into account, editing this value has no effect. - //MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value[i]), - MaybeMutRef::MutRef(value) => MaybeMutRef::Ref(&value[i]), + MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value[i]), }; - edit_f32_float_raw_impl(ui, &mut value, f32::MIN..=f32::MAX) + edit_f32_float_raw(ui, &mut value, f32::MIN..=f32::MAX, "") } diff --git a/crates/viewer/re_component_ui/src/lib.rs b/crates/viewer/re_component_ui/src/lib.rs index 9ee313c198b7..55b7da2d4730 100644 --- a/crates/viewer/re_component_ui/src/lib.rs +++ b/crates/viewer/re_component_ui/src/lib.rs @@ -14,6 +14,7 @@ mod line_strip; mod map_provider; mod marker_shape; mod pinhole; +mod plane3d; mod radius; mod recording_uri; mod resolution; @@ -28,13 +29,13 @@ mod zoom_level; use datatype_uis::{ display_name_ui, display_text_ui, edit_bool, edit_f32_min_to_max_float, edit_f32_zero_to_max, edit_f32_zero_to_one, edit_multiline_string, edit_or_view_vec3d, edit_singleline_string, - edit_view_enum, edit_view_enum_with_variant_available, edit_view_range1d, view_uuid, - view_view_id, + edit_ui_points, edit_view_enum, edit_view_enum_with_variant_available, edit_view_range1d, + view_uuid, view_view_id, }; use re_types::{ blueprint::components::{ - BackgroundKind, Corner2D, LockRangeDuringZoom, MapProvider, ViewFit, Visible, ZoomLevel, + BackgroundKind, Corner2D, GridSpacing, LockRangeDuringZoom, MapProvider, ViewFit, Visible, }, components::{ AggregationPolicy, AlbedoFactor, AxisLength, Color, DepthMeter, DrawOrder, FillMode, @@ -71,10 +72,10 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); + registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); - registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); - registry.add_singleline_edit_or_view::(edit_f32_zero_to_max); - registry.add_singleline_edit_or_view::(zoom_level::edit_zoom_level); + registry.add_singleline_edit_or_view::(edit_ui_points); + registry.add_singleline_edit_or_view::(edit_ui_points); // float min-max components: registry.add_singleline_edit_or_view::(edit_f32_min_to_max_float); @@ -103,13 +104,13 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::(edit_view_enum); - registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::( edit_view_enum_with_variant_available::< MapProvider, crate::map_provider::MapProviderVariantAvailable, >, ); + registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::(edit_view_enum); @@ -167,5 +168,10 @@ pub fn create_component_ui_registry() -> re_viewer_context::ComponentUiRegistry registry.add_singleline_edit_or_view(lat_lon::singleline_view_lat_lon); + registry.add_singleline_edit_or_view(zoom_level::edit_zoom_level); + + registry.add_singleline_edit_or_view(plane3d::edit_or_view_plane3d); + registry.add_multiline_edit_or_view(plane3d::multiline_edit_or_view_plane3d); + registry } diff --git a/crates/viewer/re_component_ui/src/marker_shape.rs b/crates/viewer/re_component_ui/src/marker_shape.rs index b55c474d3d6e..5c08682ee1f4 100644 --- a/crates/viewer/re_component_ui/src/marker_shape.rs +++ b/crates/viewer/re_component_ui/src/marker_shape.rs @@ -55,7 +55,8 @@ pub(crate) fn edit_marker_shape_ui( ) } else { let marker: MarkerShape = *marker.as_ref(); - ui.list_item_flat_noninteractive( + ui.list_item().interactive(false).show_hierarchical( + ui, re_ui::list_item::LabelContent::new(marker.to_string()) .min_desired_width(item_width) .with_icon_fn(|ui, rect, visuals| { diff --git a/crates/viewer/re_component_ui/src/plane3d.rs b/crates/viewer/re_component_ui/src/plane3d.rs new file mode 100644 index 000000000000..2b3484f60e19 --- /dev/null +++ b/crates/viewer/re_component_ui/src/plane3d.rs @@ -0,0 +1,171 @@ +use re_types::{components, external::glam}; +use re_ui::UiExt as _; +use re_viewer_context::MaybeMutRef; + +use crate::{ + datatype_uis::{edit_f32_float_raw, edit_or_view_vec3d_raw}, + response_utils::response_with_changes_of_inner, +}; + +#[derive(PartialEq, Eq, Copy, Clone)] +enum AxisDirection { + PosX, + PosY, + PosZ, + NegX, + NegY, + NegZ, + + /// Not along any axis. + Other, +} + +impl AxisDirection { + const VARIANTS: [Self; 6] = [ + Self::PosX, + Self::PosY, + Self::PosZ, + Self::NegX, + Self::NegY, + Self::NegZ, + ]; +} + +impl std::fmt::Display for AxisDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PosX => write!(f, "+X"), + Self::PosY => write!(f, "+Y"), + Self::PosZ => write!(f, "+Z"), + Self::NegX => write!(f, "-X"), + Self::NegY => write!(f, "-Y"), + Self::NegZ => write!(f, "-Z"), + Self::Other => write!(f, "-"), + } + } +} + +impl From for AxisDirection { + fn from(value: glam::Vec3) -> Self { + match value { + glam::Vec3::X => Self::PosX, + glam::Vec3::Y => Self::PosY, + glam::Vec3::Z => Self::PosZ, + glam::Vec3::NEG_X => Self::NegX, + glam::Vec3::NEG_Y => Self::NegY, + glam::Vec3::NEG_Z => Self::NegZ, + _ => Self::Other, + } + } +} + +impl TryFrom for glam::Vec3 { + type Error = (); + + fn try_from(value: AxisDirection) -> Result { + match value { + AxisDirection::PosX => Ok(Self::X), + AxisDirection::PosY => Ok(Self::Y), + AxisDirection::PosZ => Ok(Self::Z), + AxisDirection::NegX => Ok(Self::NEG_X), + AxisDirection::NegY => Ok(Self::NEG_Y), + AxisDirection::NegZ => Ok(Self::NEG_Z), + AxisDirection::Other => Err(()), + } + } +} + +pub fn edit_or_view_plane3d( + _ctx: &re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, components::Plane3D>, +) -> egui::Response { + let distance = value.distance(); + + ui.label("n"); + // Show simplified combobox if this is axis aligned. + let mut axis_dir = AxisDirection::from(glam::Vec3::from(value.normal())); + let normal_response = response_with_changes_of_inner( + egui::ComboBox::from_id_salt("plane_normal") + .selected_text(format!("{axis_dir}")) + .height(250.0) + .show_ui(ui, |ui| { + let mut variants = AxisDirection::VARIANTS.iter(); + #[allow(clippy::unwrap_used)] // We know there's more than zero variants. + let variant = variants.next().unwrap(); + + let mut response = + ui.selectable_value(&mut axis_dir, *variant, variant.to_string()); + for variant in variants { + response |= ui.selectable_value(&mut axis_dir, *variant, variant.to_string()); + } + + if let MaybeMutRef::MutRef(value) = value { + if let Ok(new_dir) = glam::Vec3::try_from(axis_dir) { + **value = components::Plane3D::new(new_dir, distance); + } + } + response + }), + ) + .on_hover_text(format!( + "{} {} {}", + re_format::format_f32(value.normal().x()), + re_format::format_f32(value.normal().y()), + re_format::format_f32(value.normal().z()), + )); + + ui.label("d"); + let mut maybe_mut_distance = match value { + MaybeMutRef::Ref(value) => MaybeMutRef::Ref(&value.0 .0[3]), + MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.0 .0[3]), + }; + let distance_response = + edit_f32_float_raw(ui, &mut maybe_mut_distance, f32::MIN..=f32::MAX, ""); + + normal_response | distance_response +} + +pub fn multiline_edit_or_view_plane3d( + _ctx: &re_viewer_context::ViewerContext<'_>, + ui: &mut egui::Ui, + value: &mut MaybeMutRef<'_, components::Plane3D>, +) -> egui::Response { + let mut any_edit = false; + + let response_normal = ui.list_item().interactive(false).show_hierarchical( + ui, + re_ui::list_item::PropertyContent::new("Normal").value_fn(|ui, _| { + let mut normal = value.normal(); + let mut maybe_mut_normal = match value { + MaybeMutRef::Ref(_) => MaybeMutRef::Ref(&normal), + MaybeMutRef::MutRef(_) => MaybeMutRef::MutRef(&mut normal), + }; + + any_edit |= edit_or_view_vec3d_raw(ui, &mut maybe_mut_normal).changed(); + + if let MaybeMutRef::MutRef(value) = value { + **value = components::Plane3D::new(normal, value.distance()); + } + }), + ); + + let response_distance = ui.list_item().interactive(false).show_hierarchical( + ui, + re_ui::list_item::PropertyContent::new("Distance").value_fn(|ui, _| { + let mut maybe_mut_distance = match value { + MaybeMutRef::Ref(value) => MaybeMutRef::Ref(&value.0 .0[3]), + MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.0 .0[3]), + }; + + any_edit |= + edit_f32_float_raw(ui, &mut maybe_mut_distance, f32::MIN..=f32::MAX, "").changed(); + }), + ); + + let mut response = response_normal | response_distance; + if any_edit { + response.mark_changed(); + } + response +} diff --git a/crates/viewer/re_component_ui/src/transforms.rs b/crates/viewer/re_component_ui/src/transforms.rs index 17ff1b0346f0..5312470215ca 100644 --- a/crates/viewer/re_component_ui/src/transforms.rs +++ b/crates/viewer/re_component_ui/src/transforms.rs @@ -23,8 +23,9 @@ pub fn multiline_view_transform_mat3x3( let col1 = value.0.col(1); let col2 = value.0.col(2); - ui.list_item_flat_noninteractive(re_ui::list_item::PropertyContent::new("matrix").value_fn( - |ui, _| { + ui.list_item().interactive(false).show_hierarchical( + ui, + re_ui::list_item::PropertyContent::new("matrix").value_fn(|ui, _| { egui::Grid::new("matrix").num_columns(3).show(ui, |ui| { ui.monospace(re_format::format_f32(col0[0])); ui.monospace(re_format::format_f32(col1[0])); @@ -41,6 +42,6 @@ pub fn multiline_view_transform_mat3x3( ui.monospace(re_format::format_f32(col2[2])); ui.end_row(); }); - }, - )) + }), + ) } diff --git a/crates/viewer/re_component_ui/src/visual_bounds2d.rs b/crates/viewer/re_component_ui/src/visual_bounds2d.rs index 47c1a121e9c1..dea74164070e 100644 --- a/crates/viewer/re_component_ui/src/visual_bounds2d.rs +++ b/crates/viewer/re_component_ui/src/visual_bounds2d.rs @@ -11,7 +11,8 @@ pub fn multiline_edit_visual_bounds2d( ) -> egui::Response { let mut any_edit = false; - let response_x = ui.list_item_flat_noninteractive( + let response_x = ui.list_item().interactive(false).show_hierarchical( + ui, re_ui::list_item::PropertyContent::new("x").value_fn(|ui, _| { if let Some(value) = value.as_mut() { any_edit |= range_mut_ui(ui, &mut value.x_range.0).changed(); @@ -21,7 +22,8 @@ pub fn multiline_edit_visual_bounds2d( }), ); - let response_y = ui.list_item_flat_noninteractive( + let response_y = ui.list_item().interactive(false).show_hierarchical( + ui, re_ui::list_item::PropertyContent::new("y").value_fn(|ui, _| { if let Some(value) = value.as_mut() { any_edit |= range_mut_ui(ui, &mut value.y_range.0).changed(); diff --git a/crates/viewer/re_component_ui/src/zoom_level.rs b/crates/viewer/re_component_ui/src/zoom_level.rs index b03af554ea6d..0b086d46dd99 100644 --- a/crates/viewer/re_component_ui/src/zoom_level.rs +++ b/crates/viewer/re_component_ui/src/zoom_level.rs @@ -1,4 +1,4 @@ -use re_types::datatypes; +use re_types::blueprint::components::ZoomLevel; use re_viewer_context::MaybeMutRef; // TODO(#7876): move this to `re_space_view_map` when the crate is no longer behind a Cargo feature. @@ -11,11 +11,11 @@ const MAX_ZOOM_LEVEL: f64 = 19.0; pub fn edit_zoom_level( _ctx: &re_viewer_context::ViewerContext<'_>, ui: &mut egui::Ui, - value: &mut MaybeMutRef<'_, impl std::ops::DerefMut>, + value: &mut MaybeMutRef<'_, ZoomLevel>, ) -> egui::Response { let mut value: MaybeMutRef<'_, f64> = match value { MaybeMutRef::Ref(value) => MaybeMutRef::Ref(value), - MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.deref_mut().0), + MaybeMutRef::MutRef(value) => MaybeMutRef::MutRef(&mut value.0), }; super::datatype_uis::edit_f64_float_raw_with_speed_impl( diff --git a/crates/viewer/re_renderer/shader/utils/plane.wgsl b/crates/viewer/re_renderer/shader/utils/plane.wgsl index ead2e2ec9945..6bf9d6cf01cb 100644 --- a/crates/viewer/re_renderer/shader/utils/plane.wgsl +++ b/crates/viewer/re_renderer/shader/utils/plane.wgsl @@ -1,3 +1,8 @@ +// A plane defined by `dot(normal, X) - distance = 0`. +// +// This is known as Hesse normal form. +// Note that we're subtracting distance, in some literature this term is added instead. +// This form is more usually more convenient as it expresses "above the plane" as `dot(normal, X) > distance`. struct Plane { normal: vec3f, distance: f32, @@ -5,5 +10,5 @@ struct Plane { /// How far away is a given point from the plane? fn distance_to_plane(plane: Plane, point: vec3f) -> f32 { - return dot(plane.normal, point) + plane.distance; + return dot(plane.normal, point) - plane.distance; } diff --git a/crates/viewer/re_renderer/shader/world_grid.wgsl b/crates/viewer/re_renderer/shader/world_grid.wgsl index 49f0a0b8fb8b..0f78a2699964 100644 --- a/crates/viewer/re_renderer/shader/world_grid.wgsl +++ b/crates/viewer/re_renderer/shader/world_grid.wgsl @@ -12,25 +12,24 @@ struct WorldGridUniformBuffer { /// How thick the lines are in UI units. thickness_ui: f32, + + /// Offset of the grid along its normal. + normal_offset: f32, } @group(1) @binding(0) var config: WorldGridUniformBuffer; struct VertexOutput { - @builtin(position)W + @builtin(position) position: vec4f, @location(0) scaled_world_plane_position: vec2f, -}; -// We have to make up some world space geometry which then necessarily gets a limited size. -// Putting a too high number here makes things break down because of floating point inaccuracies. -// But arguably at that point we're potentially doomed either way since precision will break down in other parts of the rendering as well. -// -// This is the main drawback of the plane approach over the screen space filling one. -const PLANE_GEOMETRY_SIZE: f32 = 10000.0; + @location(1) @interpolate(flat) // Result doesn't differ per vertex. + next_cardinality_interpolation: f32, +}; // Spans a large quad where centered around the camera. // @@ -40,8 +39,14 @@ const PLANE_GEOMETRY_SIZE: f32 = 10000.0; @vertex fn main_vs(@builtin(vertex_index) v_idx: u32) -> VertexOutput { var out: VertexOutput; + let camera_plane_distance_world = distance_to_plane(config.plane, frame.camera_position); - var plane_position = (vec2f(f32(v_idx / 2u), f32(v_idx % 2u)) * 2.0 - 1.0) * PLANE_GEOMETRY_SIZE; + // Scale the plane geometry based on the distance to the camera. + // This preserves relative precision MUCH better than a fixed scale. + let plane_geometry_size = 1000.0 * camera_plane_distance_world; + + // 2D position on the plane. + let plane_position = (vec2f(f32(v_idx / 2u), f32(v_idx % 2u)) * 2.0 - 1.0) * plane_geometry_size; // Make up x and y axis for the plane. let plane_y_axis = normalize(cross(config.plane.normal, select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), config.plane.normal.x != 0.0))); @@ -52,10 +57,16 @@ fn main_vs(@builtin(vertex_index) v_idx: u32) -> VertexOutput { let shifted_plane_position = plane_position + camera_on_plane; // Compute world position from shifted plane position. - let world_position = config.plane.normal * -config.plane.distance + plane_x_axis * shifted_plane_position.x + plane_y_axis * shifted_plane_position.y; - + let world_position = config.plane.normal * config.plane.distance + plane_x_axis * shifted_plane_position.x + plane_y_axis * shifted_plane_position.y; out.position = frame.projection_from_world * vec4f(world_position, 1.0); - out.scaled_world_plane_position = shifted_plane_position / config.spacing; + + // Determine which "scales" of the grid we want to show. We want to show factor 1, 10, 100, 1000, etc. + let camera_plane_distance_grid_units = camera_plane_distance_world / config.spacing; + let line_cardinality = max(log2(camera_plane_distance_grid_units) / log2(10.0) - 0.9, 0.0); // -0.9 instead of 1.0 so we always see a little bit of the next level even if we're very close. + let line_base_cardinality = floor(line_cardinality); + let line_spacing_factor = pow(10.0, line_base_cardinality); + out.scaled_world_plane_position = shifted_plane_position / (config.spacing * line_spacing_factor); + out.next_cardinality_interpolation = line_cardinality - line_base_cardinality; return out; } @@ -68,19 +79,20 @@ fn calc_distance_to_grid_line(scaled_world_plane_position: vec2f) -> vec2f { @fragment fn main_fs(in: VertexOutput) -> @location(0) vec4f { - // Most basics are very well explained by Ben Golus here: https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8 + // Most basics of determining a basic pixel space grid are very well explained by Ben Golus here: https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8 // We're not actually implementing the "pristine grid shader" which is a grid with world space thickness, // but rather the pixel space grid, which is a lot simpler, but happens to be also described very well in this article. // Distance to a grid line in x and y ranging from 0 to 1. - let distance_to_grid_line = calc_distance_to_grid_line(in.scaled_world_plane_position); + let distance_to_grid_line_base = calc_distance_to_grid_line(in.scaled_world_plane_position); // Figure out the how wide the lines are in this "draw space". let plane_unit_pixel_derivative = fwidthFine(in.scaled_world_plane_position); let line_anti_alias = plane_unit_pixel_derivative; let width_in_pixels = config.thickness_ui * frame.pixels_from_point; let width_in_grid_units = width_in_pixels * plane_unit_pixel_derivative; - var intensity_regular = linearstep2(width_in_grid_units + line_anti_alias, width_in_grid_units - line_anti_alias, distance_to_grid_line); + var intensity_base = linearstep2(width_in_grid_units + line_anti_alias, width_in_grid_units - line_anti_alias, + distance_to_grid_line_base); // Fade lines that get too close to each other. // Once the number of pixels per line (== from one line to the next) is below a threshold fade them out. @@ -92,31 +104,30 @@ fn main_fs(in: VertexOutput) -> @location(0) vec4f { // // Tried smoothstep here, but didn't feel right even with lots of range tweaking. let screen_space_line_spacing = 1.0 / max(width_in_grid_units.x, width_in_grid_units.y); - let grid_closeness_fade = linearstep(1.0, 10.0, screen_space_line_spacing); - intensity_regular *= grid_closeness_fade; + let grid_closeness_fade = linearstep(2.0, 10.0, screen_space_line_spacing); + intensity_base *= grid_closeness_fade; // Every tenth line is a more intense, we call those "cardinal" lines. // Experimented previously with more levels of cardinal lines, but it gets too busy: // It seems that if we want to go down this path, we should ensure that there's only two levels of lines on screen at a time. - const CARDINAL_LINE_FACTOR: f32 = 10.0; - let distance_to_grid_line_cardinal = calc_distance_to_grid_line(in.scaled_world_plane_position * (1.0 / CARDINAL_LINE_FACTOR)); - var cardinal_line_intensity = linearstep2(width_in_grid_units + line_anti_alias, width_in_grid_units - line_anti_alias, - distance_to_grid_line_cardinal * CARDINAL_LINE_FACTOR); - let cardinal_grid_closeness_fade = linearstep(2.0, 10.0, screen_space_line_spacing * CARDINAL_LINE_FACTOR); // Fade cardinal lines a little bit earlier (because it looks nicer) - cardinal_line_intensity *= cardinal_grid_closeness_fade; + let distance_to_grid_line_cardinal = calc_distance_to_grid_line(in.scaled_world_plane_position * 0.1); + var intensity_cardinal = linearstep2(width_in_grid_units + line_anti_alias, width_in_grid_units - line_anti_alias, + distance_to_grid_line_cardinal * 10.0); + let cardinal_grid_closeness_fade = linearstep(2.0, 10.0, screen_space_line_spacing * 10.0); // Fade cardinal lines a little bit earlier (because it looks nicer) + intensity_cardinal *= cardinal_grid_closeness_fade; // Combine all lines. // // Lerp for cardinal & regular. // This way we don't break anti-aliasing (as addition would!), mute the regular lines, and make cardinals weaker when there's no regular to support them. - let cardinal_and_regular = mix(intensity_regular, cardinal_line_intensity, 0.4); + let cardinal_and_regular = mix(intensity_base, intensity_cardinal, in.next_cardinality_interpolation); // X and Y are combined like akin to premultiplied alpha operations. let intensity_combined = saturate(cardinal_and_regular.x * (1.0 - cardinal_and_regular.y) + cardinal_and_regular.y); - return config.color * intensity_combined; // Useful debugging visualizations: + //return vec4f(line_cardinality - line_base_cardinality, 0.0, 0.0, 1.0); //return vec4f(intensity_combined); //return vec4f(grid_closeness_fade, cardinal_grid_closeness_fade, 0.0, 1.0); } diff --git a/crates/viewer/re_space_view_spatial/src/ui_3d.rs b/crates/viewer/re_space_view_spatial/src/ui_3d.rs index 1a82a5794846..e5e5d5e104c9 100644 --- a/crates/viewer/re_space_view_spatial/src/ui_3d.rs +++ b/crates/viewer/re_space_view_spatial/src/ui_3d.rs @@ -13,7 +13,12 @@ use re_space_view::controls::{ ROTATE3D_BUTTON, SPEED_UP_3D_MODIFIER, TRACKED_OBJECT_RESTORE_KEY, }; use re_types::{ - blueprint::archetypes::Background, components::ViewCoordinates, view_coordinates::SignedAxis3, + blueprint::{ + archetypes::{Background, LineGrid3D}, + components::{GridSpacing, Visible}, + }, + components::ViewCoordinates, + view_coordinates::SignedAxis3, }; use re_ui::{ContextExt, ModifiersMarkdown, MouseButtonMarkdown}; use re_viewer_context::{ @@ -54,7 +59,7 @@ pub struct View3DState { /// Last known view coordinates. /// Used to detect changes in view coordinates, in which case we reset the camera eye. - scene_view_coordinates: Option, + pub scene_view_coordinates: Option, // options: spin: bool, @@ -660,6 +665,16 @@ impl SpatialSpaceView3D { view_builder.queue_draw(draw_data); } + // Optional 3D line grid. + let grid_config = ViewProperty::from_archetype::( + ctx.blueprint_db(), + ctx.blueprint_query, + query.space_view_id, + ); + if let Some(draw_data) = self.setup_grid_3d(ctx, &grid_config, state)? { + view_builder.queue_draw(draw_data); + } + // Commit ui induced lines. view_builder.queue_draw(line_builder.into_draw_data()?); @@ -694,6 +709,40 @@ impl SpatialSpaceView3D { Ok(()) } + + fn setup_grid_3d( + &self, + ctx: &ViewerContext<'_>, + grid_config: &ViewProperty, + state: &SpatialSpaceViewState, + ) -> Result, SpaceViewSystemExecutionError> + { + if !**grid_config.component_or_fallback::(ctx, self, state)? { + return Ok(None); + } + + let spacing = **grid_config.component_or_fallback::(ctx, self, state)?; + let thickness_ui = **grid_config + .component_or_fallback::(ctx, self, state)?; + let color = + grid_config.component_or_fallback::(ctx, self, state)?; + let plane = + grid_config.component_or_fallback::(ctx, self, state)?; + + let Some(render_ctx) = ctx.render_ctx else { + return Ok(None); + }; + + Ok(Some(re_renderer::renderer::WorldGridDrawData::new( + render_ctx, + &re_renderer::renderer::WorldGridConfiguration { + color: color.into(), + plane: plane.into(), + spacing, + thickness_ui, + }, + ))) + } } /// Show center of orbit camera when interacting with camera (it's quite helpful). diff --git a/crates/viewer/re_space_view_spatial/src/view_3d.rs b/crates/viewer/re_space_view_spatial/src/view_3d.rs index f5a30cc85fd3..3beb4cc28ff8 100644 --- a/crates/viewer/re_space_view_spatial/src/view_3d.rs +++ b/crates/viewer/re_space_view_spatial/src/view_3d.rs @@ -5,12 +5,12 @@ use nohash_hasher::IntSet; use re_entity_db::EntityDb; use re_log_types::EntityPath; use re_space_view::view_property_ui; -use re_types::View; +use re_types::blueprint::archetypes::LineGrid3D; use re_types::{ blueprint::archetypes::Background, components::ViewCoordinates, Component, - SpaceViewClassIdentifier, + SpaceViewClassIdentifier, View, }; -use re_ui::UiExt as _; +use re_ui::{list_item, UiExt as _}; use re_viewer_context::{ ApplicableEntities, IdentifiedViewSystem, IndicatedEntities, PerVisualizer, RecommendedSpaceView, SmallVisualizerSet, SpaceViewClass, SpaceViewClassRegistryError, @@ -18,6 +18,7 @@ use re_viewer_context::{ SpaceViewSystemExecutionError, ViewQuery, ViewSystemIdentifier, ViewerContext, VisualizableEntities, VisualizableFilterContext, }; +use re_viewport_blueprint::ViewProperty; use crate::visualizers::{AxisLengthDetector, CamerasVisualizer, Transform3DArrowsVisualizer}; use crate::{ @@ -421,6 +422,7 @@ impl SpaceViewClass for SpatialSpaceView3D { re_ui::list_item::list_item_scope(ui, "spatial_view3d_selection_ui", |ui| { view_property_ui::(ctx, ui, view_id, self, state); + view_property_ui_grid3d(ctx, ui, view_id, self, state); }); Ok(()) @@ -443,3 +445,87 @@ impl SpaceViewClass for SpatialSpaceView3D { self.view_3d(ctx, ui, state, query, system_output) } } + +// The generic ui (via `view_property_ui::(ctx, ui, view_id, self, state);`) +// is suitable for the most part. However, as of writing the alpha color picker doesn't handle alpha +// which we need here. +fn view_property_ui_grid3d( + ctx: &ViewerContext<'_>, + ui: &mut egui::Ui, + view_id: SpaceViewId, + fallback_provider: &dyn re_viewer_context::ComponentFallbackProvider, + view_state: &dyn SpaceViewState, +) { + let property = ViewProperty::from_archetype::( + ctx.blueprint_db(), + ctx.blueprint_query, + view_id, + ); + let Some(reflection) = ctx.reflection.archetypes.get(&property.archetype_name) else { + ui.error_label(format!( + "Missing reflection data for archetype {:?}.", + property.archetype_name + )); + return; + }; + + let query_ctx = property.query_context(ctx, view_state); + let sub_prop_ui = |ui: &mut egui::Ui| { + for field in &reflection.fields { + // TODO(#1611): The color picker for the color component doesn't show alpha values so far since alpha is almost never supported. + // Here however, we need that alpha color picker! + if field.component_name == re_types::components::Color::name() { + re_space_view::view_property_component_ui_custom( + &query_ctx, + ui, + &property, + field.display_name, + field, + &|ui| { + let Ok(color) = property + .component_or_fallback::( + ctx, + fallback_provider, + view_state, + ) + else { + ui.error_label("Failed to query color component"); + return; + }; + let mut edit_color = egui::Color32::from(*color); + if egui::color_picker::color_edit_button_srgba( + ui, + &mut edit_color, + egui::color_picker::Alpha::OnlyBlend, + ) + .changed() + { + let color = re_types::components::Color::from(edit_color); + property.save_blueprint_component(ctx, &[color]); + } + }, + None, // No multiline editor. + ); + } else { + re_space_view::view_property_component_ui( + &query_ctx, + ui, + &property, + field.display_name, + field, + fallback_provider, + ); + } + } + }; + + ui.list_item() + .interactive(false) + .show_hierarchical_with_children( + ui, + ui.make_persistent_id(property.archetype_name.full_name()), + true, + list_item::LabelContent::new(reflection.display_name), + sub_prop_ui, + ); +} diff --git a/crates/viewer/re_space_view_spatial/src/view_3d_properties.rs b/crates/viewer/re_space_view_spatial/src/view_3d_properties.rs index 949fc00e1f65..60721a944df4 100644 --- a/crates/viewer/re_space_view_spatial/src/view_3d_properties.rs +++ b/crates/viewer/re_space_view_spatial/src/view_3d_properties.rs @@ -1,17 +1,22 @@ use re_types::{ - blueprint::{archetypes::Background, components::BackgroundKind}, - components::Color, + blueprint::{ + archetypes::{Background, LineGrid3D}, + components::BackgroundKind, + }, + components::{Color, Plane3D, StrokeWidth}, Archetype, }; -use re_viewer_context::TypedComponentFallbackProvider; +use re_viewer_context::{SpaceViewStateExt as _, TypedComponentFallbackProvider}; -use crate::SpatialSpaceView3D; +use crate::{ui::SpatialSpaceViewState, SpatialSpaceView3D}; impl TypedComponentFallbackProvider for SpatialSpaceView3D { fn fallback_for(&self, ctx: &re_viewer_context::QueryContext<'_>) -> Color { // Color is a fairly common component, make sure this is the right context. if ctx.archetype_name == Some(Background::name()) { Color::WHITE + } else if ctx.archetype_name == Some(LineGrid3D::name()) { + Color::from_unmultiplied_rgba(128, 128, 128, 60) } else { Color::default() } @@ -24,4 +29,26 @@ impl TypedComponentFallbackProvider for SpatialSpaceView3D { } } -re_viewer_context::impl_component_fallback_provider!(SpatialSpaceView3D => [BackgroundKind, Color]); +impl TypedComponentFallbackProvider for SpatialSpaceView3D { + fn fallback_for(&self, _ctx: &re_viewer_context::QueryContext<'_>) -> StrokeWidth { + 1.0.into() + } +} + +impl TypedComponentFallbackProvider for SpatialSpaceView3D { + fn fallback_for(&self, ctx: &re_viewer_context::QueryContext<'_>) -> Plane3D { + const DEFAULT_PLANE: Plane3D = Plane3D::XY; + + let Ok(view_state) = ctx.view_state.downcast_ref::() else { + return DEFAULT_PLANE; + }; + + view_state + .state_3d + .scene_view_coordinates + .and_then(|view_coordinates| view_coordinates.up()) + .map_or(DEFAULT_PLANE, |up| Plane3D::new(up.as_vec3(), 0.0)) + } +} + +re_viewer_context::impl_component_fallback_provider!(SpatialSpaceView3D => [BackgroundKind, Color, StrokeWidth, Plane3D]); diff --git a/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs b/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs index ac256bc847d4..85bc964e87a8 100644 --- a/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs +++ b/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs @@ -9,6 +9,7 @@ pub use re_types::blueprint::components::ComponentColumnSelector; pub use re_types::blueprint::components::Corner2D; pub use re_types::blueprint::components::FilterByRange; pub use re_types::blueprint::components::FilterIsNotNull; +pub use re_types::blueprint::components::GridSpacing; pub use re_types::blueprint::components::IncludedContent; pub use re_types::blueprint::components::Interactive; pub use re_types::blueprint::components::LockRangeDuringZoom; @@ -51,6 +52,7 @@ pub fn is_valid_blueprint(blueprint: &EntityDb) -> bool { && validate_component::(blueprint) && validate_component::(blueprint) && validate_component::(blueprint) + && validate_component::(blueprint) && validate_component::(blueprint) && validate_component::(blueprint) && validate_component::(blueprint) diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 21424df3e255..ed5806fd9f5b 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -132,6 +132,14 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "Space between grid lines of one line to the next in scene units.", + custom_placeholder: Some(GridSpacing::default().to_arrow2()?), + datatype: GridSpacing::arrow2_datatype(), + }, + ), ( ::name(), ComponentReflection { @@ -600,6 +608,14 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "An infinite 3D plane represented by a unit normal vector and a distance.\n\nAny point P on the plane fulfills the equation `dot(xyz, P) - d = 0`,\nwhere `xyz` is the plane's normal and `d` the distance of the plane from the origin.\nThis representation is also known as the Hesse normal form.\n\nNote: although the normal will be passed through to the\ndatastore as provided, when used in the Viewer, planes will always be normalized.\nI.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5", + custom_placeholder: None, + datatype: Plane3D::arrow2_datatype(), + }, + ), ( ::name(), ComponentReflection { @@ -1559,7 +1575,7 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { is_required : false, }, ArchetypeFieldReflection { component_name : "rerun.components.ViewCoordinates".into(), display_name : "Camera xyz", docstring_md : - "Sets the view coordinates for the camera.\n\nAll common values are available as constants on the `components.ViewCoordinates` class.\n\nThe default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting.\nThis means that the camera frustum will point along the positive Z axis of the parent space,\nand the cameras \"up\" direction will be along the negative Y axis of the parent space.\n\nThe camera frustum will point whichever axis is set to `F` (or the opposite of `B`).\nWhen logging a depth image under this entity, this is the direction the point cloud will be projected.\nWith `RDF`, the default forward is +Z.\n\nThe frustum's \"up\" direction will be whichever axis is set to `U` (or the opposite of `D`).\nThis will match the negative Y direction of pixel space (all images are assumed to have xyz=RDF).\nWith `RDF`, the default is up is -Y.\n\nThe frustum's \"right\" direction will be whichever axis is set to `R` (or the opposite of `L`).\nThis will match the positive X direction of pixel space (all images are assumed to have xyz=RDF).\nWith `RDF`, the default right is +x.\n\nOther common formats are `RUB` (X=Right, Y=Up, Z=Back) and `FLU` (X=Forward, Y=Left, Z=Up).\n\nNOTE: setting this to something else than `RDF` (the default) will change the orientation of the camera frustum,\nand make the pinhole matrix not match up with the coordinate system of the pinhole entity.\n\nThe pinhole matrix (the `image_from_camera` argument) always project along the third (Z) axis,\nbut will be re-oriented to project along the forward axis of the `camera_xyz` argument.", + "Sets the view coordinates for the camera.\n\nAll common values are available as constants on the [`components.ViewCoordinates`](https://rerun.io/docs/reference/types/components/view_coordinates) class.\n\nThe default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting.\nThis means that the camera frustum will point along the positive Z axis of the parent space,\nand the cameras \"up\" direction will be along the negative Y axis of the parent space.\n\nThe camera frustum will point whichever axis is set to `F` (or the opposite of `B`).\nWhen logging a depth image under this entity, this is the direction the point cloud will be projected.\nWith `RDF`, the default forward is +Z.\n\nThe frustum's \"up\" direction will be whichever axis is set to `U` (or the opposite of `D`).\nThis will match the negative Y direction of pixel space (all images are assumed to have xyz=RDF).\nWith `RDF`, the default is up is -Y.\n\nThe frustum's \"right\" direction will be whichever axis is set to `R` (or the opposite of `L`).\nThis will match the positive X direction of pixel space (all images are assumed to have xyz=RDF).\nWith `RDF`, the default right is +x.\n\nOther common formats are `RUB` (X=Right, Y=Up, Z=Back) and `FLU` (X=Forward, Y=Left, Z=Up).\n\nNOTE: setting this to something else than `RDF` (the default) will change the orientation of the camera frustum,\nand make the pinhole matrix not match up with the coordinate system of the pinhole entity.\n\nThe pinhole matrix (the `image_from_camera` argument) always project along the third (Z) axis,\nbut will be re-oriented to project along the forward axis of the `camera_xyz` argument.", is_required : false, }, ArchetypeFieldReflection { component_name : "rerun.components.ImagePlaneDistance".into(), display_name : "Image plane distance", docstring_md : @@ -1930,6 +1946,36 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { ], }, ), + ( + ArchetypeName::new("rerun.blueprint.archetypes.LineGrid3D"), + ArchetypeReflection { + display_name: "Line grid 3D", + view_types: &[], + fields: vec![ + ArchetypeFieldReflection { component_name : + "rerun.blueprint.components.Visible".into(), display_name : + "Visible", docstring_md : + "Whether the grid is visible.\n\nDefaults to true.", is_required : + false, }, ArchetypeFieldReflection { component_name : + "rerun.blueprint.components.GridSpacing".into(), display_name : + "Spacing", docstring_md : + "Space between grid lines spacing of one line to the next in scene units.\n\nAs you zoom out, successively only every tenth line is shown.\nThis controls the closest zoom level.", + is_required : false, }, ArchetypeFieldReflection { component_name : + "rerun.components.Plane3D".into(), display_name : "Plane", + docstring_md : + "In what plane the grid is drawn.\n\nDefaults to whatever plane is determined as the plane at zero units up/down as defined by [`components.ViewCoordinates`](https://rerun.io/docs/reference/types/components/view_coordinates) if present.", + is_required : false, }, ArchetypeFieldReflection { component_name : + "rerun.components.StrokeWidth".into(), display_name : "Stroke width", + docstring_md : + "How thick the lines should be in ui units.\n\nDefault is 1.0 ui unit.", + is_required : false, }, ArchetypeFieldReflection { component_name : + "rerun.components.Color".into(), display_name : "Color", docstring_md + : + "Color used for the grid.\n\nTransparency via alpha channel is supported.\nDefaults to a slightly transparent light gray.", + is_required : false, }, + ], + }, + ), ( ArchetypeName::new("rerun.blueprint.archetypes.MapBackground"), ArchetypeReflection { diff --git a/crates/viewer/re_viewport_blueprint/src/view_properties.rs b/crates/viewer/re_viewport_blueprint/src/view_properties.rs index 1accc8427470..9c77f8ebf272 100644 --- a/crates/viewer/re_viewport_blueprint/src/view_properties.rs +++ b/crates/viewer/re_viewport_blueprint/src/view_properties.rs @@ -90,7 +90,7 @@ impl ViewProperty { /// Get the value of a specific component or its fallback if the component is not present. // TODO(andreas): Unfortunately we can't use TypedComponentFallbackProvider here because it may not be implemented for all components of interest. // This sadly means that there's a bit of unnecessary back and forth between arrow array and untyped that could be avoided otherwise. - pub fn component_or_fallback( + pub fn component_or_fallback( &self, ctx: &ViewerContext<'_>, fallback_provider: &dyn ComponentFallbackProvider, @@ -103,7 +103,7 @@ impl ViewProperty { } /// Get the component array for a given type or its fallback if the component is not present or empty. - pub fn component_array_or_fallback( + pub fn component_array_or_fallback( &self, ctx: &ViewerContext<'_>, fallback_provider: &dyn ComponentFallbackProvider, diff --git a/docs/content/reference/types/archetypes/instance_poses3d.md b/docs/content/reference/types/archetypes/instance_poses3d.md index 99ceac0f1243..89f7e9846421 100644 --- a/docs/content/reference/types/archetypes/instance_poses3d.md +++ b/docs/content/reference/types/archetypes/instance_poses3d.md @@ -15,7 +15,7 @@ the 3x3 matrix is applied first, followed by the translation. Currently, many visualizers support only a single instance transform per entity. Check archetype documentations for details - if not otherwise specified, only the first instance transform is applied. -Some visualizers like the mesh visualizer used for [`archetype.Mesh3D`], +Some visualizers like the mesh visualizer used for [`archetypes.Mesh3D`](https://rerun.io/docs/reference/types/archetypes/mesh3d), will draw an object for every pose, a behavior also known as "instancing". ## Components diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index 84907d1b19b5..895232baec05 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -50,6 +50,7 @@ on [Entities and Components](../../concepts/entity-component.md). * [`Name`](components/name.md): A display name, typically for an entity or a item like a plot series. * [`Opacity`](components/opacity.md): Degree of transparency ranging from 0.0 (fully transparent) to 1.0 (fully opaque). * [`PinholeProjection`](components/pinhole_projection.md): Camera projection, from image coordinates to view coordinates. +* [`Plane3D`](components/plane3d.md): An infinite 3D plane represented by a unit normal vector and a distance. * [`PoseRotationAxisAngle`](components/pose_rotation_axis_angle.md): 3D rotation represented by a rotation around a given axis that doesn't propagate in the transform hierarchy. * [`PoseRotationQuat`](components/pose_rotation_quat.md): A 3D rotation expressed as a quaternion that doesn't propagate in the transform hierarchy. * [`PoseScale3D`](components/pose_scale3d.md): A 3D scale factor that doesn't propagate in the transform hierarchy. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 4cb988eea78b..ecb4df1c74f1 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -38,6 +38,7 @@ media_type.md linguist-generated=true name.md linguist-generated=true opacity.md linguist-generated=true pinhole_projection.md linguist-generated=true +plane3d.md linguist-generated=true pose_rotation_axis_angle.md linguist-generated=true pose_rotation_quat.md linguist-generated=true pose_scale3d.md linguist-generated=true diff --git a/docs/content/reference/types/components/plane3d.md b/docs/content/reference/types/components/plane3d.md new file mode 100644 index 000000000000..bdb085650e7a --- /dev/null +++ b/docs/content/reference/types/components/plane3d.md @@ -0,0 +1,30 @@ +--- +title: "Plane3D" +--- + + +An infinite 3D plane represented by a unit normal vector and a distance. + +Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +This representation is also known as the Hesse normal form. + +Note: although the normal will be passed through to the +datastore as provided, when used in the Viewer, planes will always be normalized. +I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + +## Rerun datatype +[`Plane3D`](../datatypes/plane3d.md) + + +## Arrow datatype +``` +FixedSizeList<4, float32> +``` + +## API reference links + * 🌊 [C++ API docs for `Plane3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1Plane3D.html?speculative-link) + * 🐍 [Python API docs for `Plane3D`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.Plane3D) + * 🦀 [Rust API docs for `Plane3D`](https://docs.rs/rerun/latest/rerun/components/struct.Plane3D.html?speculative-link) + + diff --git a/docs/content/reference/types/datatypes.md b/docs/content/reference/types/datatypes.md index 653d0db2c963..750df8e173f8 100644 --- a/docs/content/reference/types/datatypes.md +++ b/docs/content/reference/types/datatypes.md @@ -26,6 +26,7 @@ Data types are the lowest layer of the data model hierarchy. They are re-usable * [`Mat3x3`](datatypes/mat3x3.md): A 3x3 Matrix. * [`Mat4x4`](datatypes/mat4x4.md): A 4x4 Matrix. * [`PixelFormat`](datatypes/pixel_format.md): Specifieds a particular format of an [`archetypes.Image`](https://rerun.io/docs/reference/types/archetypes/image). +* [`Plane3D`](datatypes/plane3d.md): An infinite 3D plane represented by a unit normal vector and a distance. * [`Quaternion`](datatypes/quaternion.md): A Quaternion represented by 4 real numbers. * [`Range1D`](datatypes/range1d.md): A 1D range, specifying a lower and upper bound. * [`Range2D`](datatypes/range2d.md): An Axis-Aligned Bounding Box in 2D space, implemented as the minimum and maximum corners. diff --git a/docs/content/reference/types/datatypes/.gitattributes b/docs/content/reference/types/datatypes/.gitattributes index 16fee72d8210..3454eebe12d8 100644 --- a/docs/content/reference/types/datatypes/.gitattributes +++ b/docs/content/reference/types/datatypes/.gitattributes @@ -20,6 +20,7 @@ keypoint_pair.md linguist-generated=true mat3x3.md linguist-generated=true mat4x4.md linguist-generated=true pixel_format.md linguist-generated=true +plane3d.md linguist-generated=true quaternion.md linguist-generated=true range1d.md linguist-generated=true range2d.md linguist-generated=true diff --git a/docs/content/reference/types/datatypes/plane3d.md b/docs/content/reference/types/datatypes/plane3d.md new file mode 100644 index 000000000000..9f9e45a7f47c --- /dev/null +++ b/docs/content/reference/types/datatypes/plane3d.md @@ -0,0 +1,30 @@ +--- +title: "Plane3D" +--- + + +An infinite 3D plane represented by a unit normal vector and a distance. + +Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, +where `xyz` is the plane's normal and `d` the distance of the plane from the origin. +This representation is also known as the Hesse normal form. + +Note: although the normal will be passed through to the +datastore as provided, when used in the Viewer, planes will always be normalized. +I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + + +## Arrow datatype +``` +FixedSizeList<4, float32> +``` + +## API reference links + * 🌊 [C++ API docs for `Plane3D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1datatypes_1_1Plane3D.html?speculative-link) + * 🐍 [Python API docs for `Plane3D`](https://ref.rerun.io/docs/python/stable/common/datatypes?speculative-link#rerun.datatypes.Plane3D) + * 🦀 [Rust API docs for `Plane3D`](https://docs.rs/rerun/latest/rerun/datatypes/struct.Plane3D.html?speculative-link) + + +## Used by + +* [`Plane3D`](../components/plane3d.md?speculative-link) diff --git a/docs/content/reference/types/views/spatial3d_view.md b/docs/content/reference/types/views/spatial3d_view.md index 95f5ecbb2068..a6efe92e9ac1 100644 --- a/docs/content/reference/types/views/spatial3d_view.md +++ b/docs/content/reference/types/views/spatial3d_view.md @@ -12,6 +12,14 @@ Configuration for the background of the view. * `kind`: The type of the background. * `color`: Color used for the `SolidColor` background type. +### `line_grid` +Configuration for the 3D line grid. + +* `visible`: Whether the grid is visible. +* `spacing`: Space between grid lines spacing of one line to the next in scene units. +* `plane`: In what plane the grid is drawn. +* `stroke_width`: How thick the lines should be in ui units. +* `color`: Color used for the grid. ### `time_ranges` Configures which range on each timeline is shown by this view (unless specified differently per entity). diff --git a/docs/snippets/all/views/spatial3d.py b/docs/snippets/all/views/spatial3d.py index 8a1adb18a271..714d228976d9 100644 --- a/docs/snippets/all/views/spatial3d.py +++ b/docs/snippets/all/views/spatial3d.py @@ -22,6 +22,15 @@ name="3D Scene", # Set the background color to light blue. background=[100, 149, 237], + # Configure the line grid. + line_grid=rrb.archetypes.LineGrid3D( + visible=True, # The grid is enabled by default, but you can hide it with this property. + spacing=0.1, # Makes the grid more fine-grained. + # By default, the plane is inferred from view coordinates setup, but you can set arbitrary planes. + plane=rr.components.Plane3D.XY.with_distance(-5.0), + stroke_width=2.0, # Makes the grid lines twice as thick as usual. + color=[255, 255, 255, 128], # Colors the grid a half-transparent white. + ), ), collapse_panels=True, ) diff --git a/rerun_cpp/src/rerun/archetypes/instance_poses3d.hpp b/rerun_cpp/src/rerun/archetypes/instance_poses3d.hpp index 3bfc4c2292f2..f85c85c565f3 100644 --- a/rerun_cpp/src/rerun/archetypes/instance_poses3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/instance_poses3d.hpp @@ -32,7 +32,7 @@ namespace rerun::archetypes { /// /// Currently, many visualizers support only a single instance transform per entity. /// Check archetype documentations for details - if not otherwise specified, only the first instance transform is applied. - /// Some visualizers like the mesh visualizer used for [`archetype.Mesh3D`], + /// Some visualizers like the mesh visualizer used for `archetypes::Mesh3D`, /// will draw an object for every pose, a behavior also known as "instancing". /// /// ## Example diff --git a/rerun_cpp/src/rerun/archetypes/pinhole.hpp b/rerun_cpp/src/rerun/archetypes/pinhole.hpp index 9b8486706602..61da282b23c3 100644 --- a/rerun_cpp/src/rerun/archetypes/pinhole.hpp +++ b/rerun_cpp/src/rerun/archetypes/pinhole.hpp @@ -92,7 +92,7 @@ namespace rerun::archetypes { /// Sets the view coordinates for the camera. /// - /// All common values are available as constants on the `components.ViewCoordinates` class. + /// All common values are available as constants on the `components::ViewCoordinates` class. /// /// The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. /// This means that the camera frustum will point along the positive Z axis of the parent space, @@ -208,7 +208,7 @@ namespace rerun::archetypes { /// Sets the view coordinates for the camera. /// - /// All common values are available as constants on the `components.ViewCoordinates` class. + /// All common values are available as constants on the `components::ViewCoordinates` class. /// /// The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. /// This means that the camera frustum will point along the positive Z axis of the parent space, diff --git a/rerun_cpp/src/rerun/blueprint/archetypes.hpp b/rerun_cpp/src/rerun/blueprint/archetypes.hpp index 2fd779424b1f..09042a682126 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes.hpp +++ b/rerun_cpp/src/rerun/blueprint/archetypes.hpp @@ -5,6 +5,7 @@ #include "blueprint/archetypes/background.hpp" #include "blueprint/archetypes/container_blueprint.hpp" #include "blueprint/archetypes/dataframe_query.hpp" +#include "blueprint/archetypes/line_grid3d.hpp" #include "blueprint/archetypes/map_background.hpp" #include "blueprint/archetypes/map_zoom.hpp" #include "blueprint/archetypes/panel_blueprint.hpp" diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes index 26e1a60b89a6..ec736e25d263 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes @@ -7,6 +7,8 @@ container_blueprint.cpp linguist-generated=true container_blueprint.hpp linguist-generated=true dataframe_query.cpp linguist-generated=true dataframe_query.hpp linguist-generated=true +line_grid3d.cpp linguist-generated=true +line_grid3d.hpp linguist-generated=true map_background.cpp linguist-generated=true map_background.hpp linguist-generated=true map_zoom.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/line_grid3d.cpp b/rerun_cpp/src/rerun/blueprint/archetypes/line_grid3d.cpp new file mode 100644 index 000000000000..c7661a7cd130 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/line_grid3d.cpp @@ -0,0 +1,53 @@ +// 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/blueprint/archetypes/line_grid3d.fbs". + +#include "line_grid3d.hpp" + +#include "../../collection_adapter_builtins.hpp" + +namespace rerun::blueprint::archetypes {} + +namespace rerun { + + Result> AsComponents::serialize( + const blueprint::archetypes::LineGrid3D& archetype + ) { + using namespace blueprint::archetypes; + std::vector cells; + cells.reserve(6); + + if (archetype.visible.has_value()) { + auto result = ComponentBatch::from_loggable(archetype.visible.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.spacing.has_value()) { + auto result = ComponentBatch::from_loggable(archetype.spacing.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.plane.has_value()) { + auto result = ComponentBatch::from_loggable(archetype.plane.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.stroke_width.has_value()) { + auto result = ComponentBatch::from_loggable(archetype.stroke_width.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.color.has_value()) { + auto result = ComponentBatch::from_loggable(archetype.color.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + { + auto indicator = LineGrid3D::IndicatorComponent(); + auto result = ComponentBatch::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/blueprint/archetypes/line_grid3d.hpp b/rerun_cpp/src/rerun/blueprint/archetypes/line_grid3d.hpp new file mode 100644 index 000000000000..6cc5e342dc38 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/line_grid3d.hpp @@ -0,0 +1,126 @@ +// 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/blueprint/archetypes/line_grid3d.fbs". + +#pragma once + +#include "../../blueprint/components/grid_spacing.hpp" +#include "../../blueprint/components/visible.hpp" +#include "../../collection.hpp" +#include "../../compiler_utils.hpp" +#include "../../component_batch.hpp" +#include "../../components/color.hpp" +#include "../../components/plane3d.hpp" +#include "../../components/stroke_width.hpp" +#include "../../indicator_component.hpp" +#include "../../result.hpp" + +#include +#include +#include +#include + +namespace rerun::blueprint::archetypes { + /// **Archetype**: Configuration for the 3D line grid. + struct LineGrid3D { + /// Whether the grid is visible. + /// + /// Defaults to true. + std::optional visible; + + /// Space between grid lines spacing of one line to the next in scene units. + /// + /// As you zoom out, successively only every tenth line is shown. + /// This controls the closest zoom level. + std::optional spacing; + + /// In what plane the grid is drawn. + /// + /// Defaults to whatever plane is determined as the plane at zero units up/down as defined by `components::ViewCoordinates` if present. + std::optional plane; + + /// How thick the lines should be in ui units. + /// + /// Default is 1.0 ui unit. + std::optional stroke_width; + + /// Color used for the grid. + /// + /// Transparency via alpha channel is supported. + /// Defaults to a slightly transparent light gray. + std::optional color; + + public: + static constexpr const char IndicatorComponentName[] = + "rerun.blueprint.components.LineGrid3DIndicator"; + + /// Indicator component, used to identify the archetype when converting to a list of components. + using IndicatorComponent = rerun::components::IndicatorComponent; + + public: + LineGrid3D() = default; + LineGrid3D(LineGrid3D&& other) = default; + + /// Whether the grid is visible. + /// + /// Defaults to true. + LineGrid3D with_visible(rerun::blueprint::components::Visible _visible) && { + visible = std::move(_visible); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Space between grid lines spacing of one line to the next in scene units. + /// + /// As you zoom out, successively only every tenth line is shown. + /// This controls the closest zoom level. + LineGrid3D with_spacing(rerun::blueprint::components::GridSpacing _spacing) && { + spacing = std::move(_spacing); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// In what plane the grid is drawn. + /// + /// Defaults to whatever plane is determined as the plane at zero units up/down as defined by `components::ViewCoordinates` if present. + LineGrid3D with_plane(rerun::components::Plane3D _plane) && { + plane = std::move(_plane); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// How thick the lines should be in ui units. + /// + /// Default is 1.0 ui unit. + LineGrid3D with_stroke_width(rerun::components::StrokeWidth _stroke_width) && { + stroke_width = std::move(_stroke_width); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Color used for the grid. + /// + /// Transparency via alpha channel is supported. + /// Defaults to a slightly transparent light gray. + LineGrid3D with_color(rerun::components::Color _color) && { + color = std::move(_color); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + }; + +} // namespace rerun::blueprint::archetypes + +namespace rerun { + /// \private + template + struct AsComponents; + + /// \private + template <> + struct AsComponents { + /// Serialize all set component batches. + static Result> serialize( + const blueprint::archetypes::LineGrid3D& archetype + ); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/blueprint/components.hpp b/rerun_cpp/src/rerun/blueprint/components.hpp index e426e3a2b486..6dfe128cab3c 100644 --- a/rerun_cpp/src/rerun/blueprint/components.hpp +++ b/rerun_cpp/src/rerun/blueprint/components.hpp @@ -14,6 +14,7 @@ #include "blueprint/components/filter_by_range.hpp" #include "blueprint/components/filter_is_not_null.hpp" #include "blueprint/components/grid_columns.hpp" +#include "blueprint/components/grid_spacing.hpp" #include "blueprint/components/included_content.hpp" #include "blueprint/components/interactive.hpp" #include "blueprint/components/lock_range_during_zoom.hpp" diff --git a/rerun_cpp/src/rerun/blueprint/components/.gitattributes b/rerun_cpp/src/rerun/blueprint/components/.gitattributes index 7b2410956aec..50a1a60a3463 100644 --- a/rerun_cpp/src/rerun/blueprint/components/.gitattributes +++ b/rerun_cpp/src/rerun/blueprint/components/.gitattributes @@ -16,6 +16,7 @@ corner2d.hpp linguist-generated=true filter_by_range.hpp linguist-generated=true filter_is_not_null.hpp linguist-generated=true grid_columns.hpp linguist-generated=true +grid_spacing.hpp linguist-generated=true included_content.hpp linguist-generated=true interactive.hpp linguist-generated=true lock_range_during_zoom.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/blueprint/components/grid_spacing.hpp b/rerun_cpp/src/rerun/blueprint/components/grid_spacing.hpp new file mode 100644 index 000000000000..7cd2209a30f3 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/components/grid_spacing.hpp @@ -0,0 +1,74 @@ +// 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/blueprint/components/grid_spacing.fbs". + +#pragma once + +#include "../../datatypes/float32.hpp" +#include "../../result.hpp" + +#include +#include + +namespace rerun::blueprint::components { + /// **Component**: Space between grid lines of one line to the next in scene units. + struct GridSpacing { + /// Space between grid lines of one line to the next in scene units. + rerun::datatypes::Float32 distance; + + public: + GridSpacing() = default; + + GridSpacing(rerun::datatypes::Float32 distance_) : distance(distance_) {} + + GridSpacing& operator=(rerun::datatypes::Float32 distance_) { + distance = distance_; + return *this; + } + + GridSpacing(float value_) : distance(value_) {} + + GridSpacing& operator=(float value_) { + distance = value_; + return *this; + } + + /// Cast to the underlying Float32 datatype + operator rerun::datatypes::Float32() const { + return distance; + } + }; +} // namespace rerun::blueprint::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Float32) == sizeof(blueprint::components::GridSpacing)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.blueprint.components.GridSpacing"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::blueprint:: components::GridSpacing` into an arrow array. + static Result> to_arrow( + const blueprint::components::GridSpacing* instances, size_t num_instances + ) { + if (num_instances == 0) { + return Loggable::to_arrow(nullptr, 0); + } else if (instances == nullptr) { + return rerun::Error( + ErrorCode::UnexpectedNullArgument, + "Passed array instances is null when num_elements> 0." + ); + } else { + return Loggable::to_arrow( + &instances->distance, + num_instances + ); + } + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index 57eab0f62570..17ff34250df4 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -39,6 +39,7 @@ #include "components/name.hpp" #include "components/opacity.hpp" #include "components/pinhole_projection.hpp" +#include "components/plane3d.hpp" #include "components/pose_rotation_axis_angle.hpp" #include "components/pose_rotation_quat.hpp" #include "components/pose_scale3d.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index 312f4f62e67b..57a1800528b7 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -48,6 +48,7 @@ media_type.hpp linguist-generated=true name.hpp linguist-generated=true opacity.hpp linguist-generated=true pinhole_projection.hpp linguist-generated=true +plane3d.hpp linguist-generated=true pose_rotation_axis_angle.hpp linguist-generated=true pose_rotation_quat.hpp linguist-generated=true pose_scale3d.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/plane3d.hpp b/rerun_cpp/src/rerun/components/plane3d.hpp new file mode 100644 index 000000000000..431226ab80fd --- /dev/null +++ b/rerun_cpp/src/rerun/components/plane3d.hpp @@ -0,0 +1,74 @@ +// 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/components/plane3d.fbs". + +#pragma once + +#include "../datatypes/plane3d.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: An infinite 3D plane represented by a unit normal vector and a distance. + /// + /// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, + /// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. + /// This representation is also known as the Hesse normal form. + /// + /// Note: although the normal will be passed through to the + /// datastore as provided, when used in the Viewer, planes will always be normalized. + /// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + struct Plane3D { + rerun::datatypes::Plane3D xyzd; + + public: + Plane3D() = default; + + Plane3D(rerun::datatypes::Plane3D xyzd_) : xyzd(xyzd_) {} + + Plane3D& operator=(rerun::datatypes::Plane3D xyzd_) { + xyzd = xyzd_; + return *this; + } + + /// Cast to the underlying Plane3D datatype + operator rerun::datatypes::Plane3D() const { + return xyzd; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Plane3D) == sizeof(components::Plane3D)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.Plane3D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::Plane3D` into an arrow array. + static Result> to_arrow( + const components::Plane3D* instances, size_t num_instances + ) { + if (num_instances == 0) { + return Loggable::to_arrow(nullptr, 0); + } else if (instances == nullptr) { + return rerun::Error( + ErrorCode::UnexpectedNullArgument, + "Passed array instances is null when num_elements> 0." + ); + } else { + return Loggable::to_arrow( + &instances->xyzd, + num_instances + ); + } + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/datatypes.hpp b/rerun_cpp/src/rerun/datatypes.hpp index e18b3815ecec..0d41f3d62a23 100644 --- a/rerun_cpp/src/rerun/datatypes.hpp +++ b/rerun_cpp/src/rerun/datatypes.hpp @@ -21,6 +21,7 @@ #include "datatypes/mat3x3.hpp" #include "datatypes/mat4x4.hpp" #include "datatypes/pixel_format.hpp" +#include "datatypes/plane3d.hpp" #include "datatypes/quaternion.hpp" #include "datatypes/range1d.hpp" #include "datatypes/range2d.hpp" diff --git a/rerun_cpp/src/rerun/datatypes/.gitattributes b/rerun_cpp/src/rerun/datatypes/.gitattributes index fea5857aed33..46c43435cc8b 100644 --- a/rerun_cpp/src/rerun/datatypes/.gitattributes +++ b/rerun_cpp/src/rerun/datatypes/.gitattributes @@ -39,6 +39,8 @@ mat4x4.cpp linguist-generated=true mat4x4.hpp linguist-generated=true pixel_format.cpp linguist-generated=true pixel_format.hpp linguist-generated=true +plane3d.cpp linguist-generated=true +plane3d.hpp linguist-generated=true quaternion.cpp linguist-generated=true quaternion.hpp linguist-generated=true range1d.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/datatypes/plane3d.cpp b/rerun_cpp/src/rerun/datatypes/plane3d.cpp new file mode 100644 index 000000000000..659c98f13e00 --- /dev/null +++ b/rerun_cpp/src/rerun/datatypes/plane3d.cpp @@ -0,0 +1,64 @@ +// 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/datatypes/plane3d.fbs". + +#include "plane3d.hpp" + +#include +#include + +namespace rerun::datatypes {} + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = + arrow::fixed_size_list(arrow::field("item", arrow::float32(), false), 4); + return datatype; + } + + Result> Loggable::to_arrow( + const datatypes::Plane3D* instances, size_t num_instances + ) { + // TODO(andreas): Allow configuring the memory pool. + arrow::MemoryPool* pool = arrow::default_memory_pool(); + auto datatype = arrow_datatype(); + + ARROW_ASSIGN_OR_RAISE(auto builder, arrow::MakeBuilder(datatype, pool)) + if (instances && num_instances > 0) { + RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder( + static_cast(builder.get()), + instances, + num_instances + )); + } + std::shared_ptr array; + ARROW_RETURN_NOT_OK(builder->Finish(&array)); + return array; + } + + rerun::Error Loggable::fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const datatypes::Plane3D* elements, + size_t num_elements + ) { + if (builder == nullptr) { + return rerun::Error(ErrorCode::UnexpectedNullArgument, "Passed array builder is null."); + } + if (elements == nullptr) { + return rerun::Error( + ErrorCode::UnexpectedNullArgument, + "Cannot serialize null pointer to arrow array." + ); + } + + auto value_builder = static_cast(builder->value_builder()); + + ARROW_RETURN_NOT_OK(builder->AppendValues(static_cast(num_elements))); + static_assert(sizeof(elements[0].xyzd) == sizeof(elements[0])); + ARROW_RETURN_NOT_OK(value_builder->AppendValues( + elements[0].xyzd.data(), + static_cast(num_elements * 4), + nullptr + )); + + return Error::ok(); + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/datatypes/plane3d.hpp b/rerun_cpp/src/rerun/datatypes/plane3d.hpp new file mode 100644 index 000000000000..4f3da86993d4 --- /dev/null +++ b/rerun_cpp/src/rerun/datatypes/plane3d.hpp @@ -0,0 +1,59 @@ +// 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/datatypes/plane3d.fbs". + +#pragma once + +#include "../result.hpp" + +#include +#include +#include + +namespace arrow { + class Array; + class DataType; + class FixedSizeListBuilder; +} // namespace arrow + +namespace rerun::datatypes { + /// **Datatype**: An infinite 3D plane represented by a unit normal vector and a distance. + /// + /// Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, + /// where `xyz` is the plane's normal and `d` the distance of the plane from the origin. + /// This representation is also known as the Hesse normal form. + /// + /// Note: although the normal will be passed through to the + /// datastore as provided, when used in the Viewer, planes will always be normalized. + /// I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + struct Plane3D { + std::array xyzd; + + public: + Plane3D() = default; + }; +} // namespace rerun::datatypes + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.datatypes.Plane3D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Serializes an array of `rerun::datatypes::Plane3D` into an arrow array. + static Result> to_arrow( + const datatypes::Plane3D* instances, size_t num_instances + ); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const datatypes::Plane3D* elements, + size_t num_elements + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/instance_poses3d.py b/rerun_py/rerun_sdk/rerun/archetypes/instance_poses3d.py index 80fc89d76d1f..0532c2968c76 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/instance_poses3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/instance_poses3d.py @@ -33,7 +33,7 @@ class InstancePoses3D(Archetype): Currently, many visualizers support only a single instance transform per entity. Check archetype documentations for details - if not otherwise specified, only the first instance transform is applied. - Some visualizers like the mesh visualizer used for [`archetype.Mesh3D`], + Some visualizers like the mesh visualizer used for [`archetypes.Mesh3D`][rerun.archetypes.Mesh3D], will draw an object for every pose, a behavior also known as "instancing". Example diff --git a/rerun_py/rerun_sdk/rerun/archetypes/pinhole.py b/rerun_py/rerun_sdk/rerun/archetypes/pinhole.py index 8facb8bd7048..b32c37db390a 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/pinhole.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/pinhole.py @@ -119,7 +119,7 @@ def _clear(cls) -> Pinhole: ) # Sets the view coordinates for the camera. # - # All common values are available as constants on the `components.ViewCoordinates` class. + # All common values are available as constants on the [`components.ViewCoordinates`][rerun.components.ViewCoordinates] class. # # The default is `ViewCoordinates::RDF`, i.e. X=Right, Y=Down, Z=Forward, and this is also the recommended setting. # This means that the camera frustum will point along the positive Z axis of the parent space, diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes index bb528bfbaf9a..45d2c2e0e797 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes @@ -5,6 +5,7 @@ __init__.py linguist-generated=true background.py linguist-generated=true container_blueprint.py linguist-generated=true dataframe_query.py linguist-generated=true +line_grid3d.py linguist-generated=true map_background.py linguist-generated=true map_zoom.py linguist-generated=true panel_blueprint.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py index 018ec6c34d5e..de30db55093f 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py @@ -5,6 +5,7 @@ from .background import Background from .container_blueprint import ContainerBlueprint from .dataframe_query import DataframeQuery +from .line_grid3d import LineGrid3D from .map_background import MapBackground from .map_zoom import MapZoom from .panel_blueprint import PanelBlueprint @@ -23,6 +24,7 @@ "Background", "ContainerBlueprint", "DataframeQuery", + "LineGrid3D", "MapBackground", "MapZoom", "PanelBlueprint", diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/line_grid3d.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/line_grid3d.py new file mode 100644 index 000000000000..a34c8f217b9c --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/line_grid3d.py @@ -0,0 +1,146 @@ +# 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/blueprint/archetypes/line_grid3d.fbs". + +# You can extend this class by creating a "LineGrid3DExt" class in "line_grid3d_ext.py". + +from __future__ import annotations + +from typing import Any + +from attrs import define, field + +from ... import components, datatypes +from ..._baseclasses import ( + Archetype, +) +from ...blueprint import components as blueprint_components +from ...error_utils import catch_and_log_exceptions + +__all__ = ["LineGrid3D"] + + +@define(str=False, repr=False, init=False) +class LineGrid3D(Archetype): + """**Archetype**: Configuration for the 3D line grid.""" + + def __init__( + self: Any, + *, + visible: datatypes.BoolLike | None = None, + spacing: datatypes.Float32Like | None = None, + plane: datatypes.Plane3DLike | None = None, + stroke_width: datatypes.Float32Like | None = None, + color: datatypes.Rgba32Like | None = None, + ): + """ + Create a new instance of the LineGrid3D archetype. + + Parameters + ---------- + visible: + Whether the grid is visible. + + Defaults to true. + spacing: + Space between grid lines spacing of one line to the next in scene units. + + As you zoom out, successively only every tenth line is shown. + This controls the closest zoom level. + plane: + In what plane the grid is drawn. + + Defaults to whatever plane is determined as the plane at zero units up/down as defined by [`components.ViewCoordinates`][rerun.components.ViewCoordinates] if present. + stroke_width: + How thick the lines should be in ui units. + + Default is 1.0 ui unit. + color: + Color used for the grid. + + Transparency via alpha channel is supported. + Defaults to a slightly transparent light gray. + + """ + + # You can define your own __init__ function as a member of LineGrid3DExt in line_grid3d_ext.py + with catch_and_log_exceptions(context=self.__class__.__name__): + self.__attrs_init__(visible=visible, spacing=spacing, plane=plane, stroke_width=stroke_width, color=color) + return + self.__attrs_clear__() + + def __attrs_clear__(self) -> None: + """Convenience method for calling `__attrs_init__` with all `None`s.""" + self.__attrs_init__( + visible=None, # type: ignore[arg-type] + spacing=None, # type: ignore[arg-type] + plane=None, # type: ignore[arg-type] + stroke_width=None, # type: ignore[arg-type] + color=None, # type: ignore[arg-type] + ) + + @classmethod + def _clear(cls) -> LineGrid3D: + """Produce an empty LineGrid3D, bypassing `__init__`.""" + inst = cls.__new__(cls) + inst.__attrs_clear__() + return inst + + visible: blueprint_components.VisibleBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=blueprint_components.VisibleBatch._optional, # type: ignore[misc] + ) + # Whether the grid is visible. + # + # Defaults to true. + # + # (Docstring intentionally commented out to hide this field from the docs) + + spacing: blueprint_components.GridSpacingBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=blueprint_components.GridSpacingBatch._optional, # type: ignore[misc] + ) + # Space between grid lines spacing of one line to the next in scene units. + # + # As you zoom out, successively only every tenth line is shown. + # This controls the closest zoom level. + # + # (Docstring intentionally commented out to hide this field from the docs) + + plane: components.Plane3DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.Plane3DBatch._optional, # type: ignore[misc] + ) + # In what plane the grid is drawn. + # + # Defaults to whatever plane is determined as the plane at zero units up/down as defined by [`components.ViewCoordinates`][rerun.components.ViewCoordinates] if present. + # + # (Docstring intentionally commented out to hide this field from the docs) + + stroke_width: components.StrokeWidthBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.StrokeWidthBatch._optional, # type: ignore[misc] + ) + # How thick the lines should be in ui units. + # + # Default is 1.0 ui unit. + # + # (Docstring intentionally commented out to hide this field from the docs) + + color: components.ColorBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.ColorBatch._optional, # type: ignore[misc] + ) + # Color used for the grid. + # + # Transparency via alpha channel is supported. + # Defaults to a slightly transparent light gray. + # + # (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/blueprint/components/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes index 57164211651c..320328225835 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes @@ -14,6 +14,7 @@ corner2d.py linguist-generated=true filter_by_range.py linguist-generated=true filter_is_not_null.py linguist-generated=true grid_columns.py linguist-generated=true +grid_spacing.py linguist-generated=true included_content.py linguist-generated=true interactive.py linguist-generated=true lock_range_during_zoom.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py index 2f4943b58c8d..8f7f6182b390 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py @@ -14,6 +14,7 @@ from .filter_by_range import FilterByRange, FilterByRangeBatch from .filter_is_not_null import FilterIsNotNull, FilterIsNotNullBatch from .grid_columns import GridColumns, GridColumnsBatch +from .grid_spacing import GridSpacing, GridSpacingBatch from .included_content import IncludedContent, IncludedContentBatch from .interactive import Interactive, InteractiveBatch from .lock_range_during_zoom import LockRangeDuringZoom, LockRangeDuringZoomBatch @@ -67,6 +68,8 @@ "FilterIsNotNullBatch", "GridColumns", "GridColumnsBatch", + "GridSpacing", + "GridSpacingBatch", "IncludedContent", "IncludedContentBatch", "Interactive", diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/grid_spacing.py b/rerun_py/rerun_sdk/rerun/blueprint/components/grid_spacing.py new file mode 100644 index 000000000000..7b5c98387176 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/grid_spacing.py @@ -0,0 +1,32 @@ +# 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/blueprint/components/grid_spacing.fbs". + +# You can extend this class by creating a "GridSpacingExt" class in "grid_spacing_ext.py". + +from __future__ import annotations + +from ... import datatypes +from ..._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["GridSpacing", "GridSpacingBatch"] + + +class GridSpacing(datatypes.Float32, ComponentMixin): + """**Component**: Space between grid lines of one line to the next in scene units.""" + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of GridSpacingExt in grid_spacing_ext.py + + # Note: there are no fields here because GridSpacing delegates to datatypes.Float32 + pass + + +class GridSpacingBatch(datatypes.Float32Batch, ComponentBatchMixin): + _COMPONENT_NAME: str = "rerun.blueprint.components.GridSpacing" + + +# This is patched in late to avoid circular dependencies. +GridSpacing._BATCH_TYPE = GridSpacingBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py index f2684b5493d1..0fac6a2391d2 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/views/spatial3d_view.py @@ -45,6 +45,15 @@ class Spatial3DView(SpaceView): name="3D Scene", # Set the background color to light blue. background=[100, 149, 237], + # Configure the line grid. + line_grid=rrb.archetypes.LineGrid3D( + visible=True, # The grid is enabled by default, but you can hide it with this property. + spacing=0.1, # Makes the grid more fine-grained. + # By default, the plane is inferred from view coordinates setup, but you can set arbitrary planes. + plane=rr.components.Plane3D.XY.with_distance(-5.0), + stroke_width=2.0, # Makes the grid lines twice as thick as usual. + color=[255, 255, 255, 128], # Colors the grid a half-transparent white. + ), ), collapse_panels=True, ) @@ -76,6 +85,7 @@ def __init__( | datatypes.Rgba32Like | blueprint_components.BackgroundKindLike | None = None, + line_grid: blueprint_archetypes.LineGrid3D | None = None, time_ranges: blueprint_archetypes.VisibleTimeRanges | datatypes.VisibleTimeRangeLike | Sequence[datatypes.VisibleTimeRangeLike] @@ -112,6 +122,8 @@ def __init__( This will be addressed in . background: Configuration for the background of the view. + line_grid: + Configuration for the 3D line grid. time_ranges: Configures which range on each timeline is shown by this view (unless specified differently per entity). @@ -126,6 +138,11 @@ def __init__( background = blueprint_archetypes.Background(background) properties["Background"] = background + if line_grid is not None: + if not isinstance(line_grid, blueprint_archetypes.LineGrid3D): + line_grid = blueprint_archetypes.LineGrid3D(line_grid) + properties["LineGrid3D"] = line_grid + if time_ranges is not None: if not isinstance(time_ranges, blueprint_archetypes.VisibleTimeRanges): time_ranges = blueprint_archetypes.VisibleTimeRanges(time_ranges) diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index d9db90a272aa..bc78f211dc27 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -39,6 +39,7 @@ media_type.py linguist-generated=true name.py linguist-generated=true opacity.py linguist-generated=true pinhole_projection.py linguist-generated=true +plane3d.py linguist-generated=true pose_rotation_axis_angle.py linguist-generated=true pose_rotation_quat.py linguist-generated=true pose_scale3d.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/rerun/components/__init__.py index 0b3141a8eb38..1bc230a70e79 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -54,6 +54,7 @@ from .name import Name, NameBatch from .opacity import Opacity, OpacityBatch from .pinhole_projection import PinholeProjection, PinholeProjectionBatch +from .plane3d import Plane3D, Plane3DBatch from .pose_rotation_axis_angle import PoseRotationAxisAngle, PoseRotationAxisAngleBatch from .pose_rotation_quat import PoseRotationQuat, PoseRotationQuatBatch from .pose_scale3d import PoseScale3D, PoseScale3DBatch @@ -188,6 +189,8 @@ "OpacityBatch", "PinholeProjection", "PinholeProjectionBatch", + "Plane3D", + "Plane3DBatch", "PoseRotationAxisAngle", "PoseRotationAxisAngleBatch", "PoseRotationQuat", diff --git a/rerun_py/rerun_sdk/rerun/components/plane3d.py b/rerun_py/rerun_sdk/rerun/components/plane3d.py new file mode 100644 index 000000000000..7423979f2ebe --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/plane3d.py @@ -0,0 +1,42 @@ +# 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/components/plane3d.fbs". + +# You can extend this class by creating a "Plane3DExt" class in "plane3d_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["Plane3D", "Plane3DBatch"] + + +class Plane3D(datatypes.Plane3D, ComponentMixin): + """ + **Component**: An infinite 3D plane represented by a unit normal vector and a distance. + + Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, + where `xyz` is the plane's normal and `d` the distance of the plane from the origin. + This representation is also known as the Hesse normal form. + + Note: although the normal will be passed through to the + datastore as provided, when used in the Viewer, planes will always be normalized. + I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + """ + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of Plane3DExt in plane3d_ext.py + + # Note: there are no fields here because Plane3D delegates to datatypes.Plane3D + pass + + +class Plane3DBatch(datatypes.Plane3DBatch, ComponentBatchMixin): + _COMPONENT_NAME: str = "rerun.components.Plane3D" + + +# This is patched in late to avoid circular dependencies. +Plane3D._BATCH_TYPE = Plane3DBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/datatypes/.gitattributes b/rerun_py/rerun_sdk/rerun/datatypes/.gitattributes index 988472fc7b8a..dcfe4dfeec99 100644 --- a/rerun_py/rerun_sdk/rerun/datatypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/datatypes/.gitattributes @@ -21,6 +21,7 @@ keypoint_pair.py linguist-generated=true mat3x3.py linguist-generated=true mat4x4.py linguist-generated=true pixel_format.py linguist-generated=true +plane3d.py linguist-generated=true quaternion.py linguist-generated=true range1d.py linguist-generated=true range2d.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/datatypes/__init__.py b/rerun_py/rerun_sdk/rerun/datatypes/__init__.py index 9a6dfb584c78..1cf1156f07c8 100644 --- a/rerun_py/rerun_sdk/rerun/datatypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/datatypes/__init__.py @@ -26,6 +26,7 @@ from .mat3x3 import Mat3x3, Mat3x3ArrayLike, Mat3x3Batch, Mat3x3Like from .mat4x4 import Mat4x4, Mat4x4ArrayLike, Mat4x4Batch, Mat4x4Like from .pixel_format import PixelFormat, PixelFormatArrayLike, PixelFormatBatch, PixelFormatLike +from .plane3d import Plane3D, Plane3DArrayLike, Plane3DBatch, Plane3DLike from .quaternion import Quaternion, QuaternionArrayLike, QuaternionBatch, QuaternionLike from .range1d import Range1D, Range1DArrayLike, Range1DBatch, Range1DLike from .range2d import Range2D, Range2DArrayLike, Range2DBatch, Range2DLike @@ -152,6 +153,10 @@ "PixelFormatArrayLike", "PixelFormatBatch", "PixelFormatLike", + "Plane3D", + "Plane3DArrayLike", + "Plane3DBatch", + "Plane3DLike", "Quaternion", "QuaternionArrayLike", "QuaternionBatch", diff --git a/rerun_py/rerun_sdk/rerun/datatypes/plane3d.py b/rerun_py/rerun_sdk/rerun/datatypes/plane3d.py new file mode 100644 index 000000000000..6bfd4c5151cb --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/datatypes/plane3d.py @@ -0,0 +1,61 @@ +# 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/datatypes/plane3d.fbs". + +# You can extend this class by creating a "Plane3DExt" class in "plane3d_ext.py". + +from __future__ import annotations + +from typing import Any, Sequence, Union + +import numpy as np +import numpy.typing as npt +import pyarrow as pa +from attrs import define, field + +from .._baseclasses import ( + BaseBatch, +) +from .._converters import ( + to_np_float32, +) +from .plane3d_ext import Plane3DExt + +__all__ = ["Plane3D", "Plane3DArrayLike", "Plane3DBatch", "Plane3DLike"] + + +@define(init=False) +class Plane3D(Plane3DExt): + """ + **Datatype**: An infinite 3D plane represented by a unit normal vector and a distance. + + Any point P on the plane fulfills the equation `dot(xyz, P) - d = 0`, + where `xyz` is the plane's normal and `d` the distance of the plane from the origin. + This representation is also known as the Hesse normal form. + + Note: although the normal will be passed through to the + datastore as provided, when used in the Viewer, planes will always be normalized. + I.e. the plane with xyz = (2, 0, 0), d = 1 is equivalent to xyz = (1, 0, 0), d = 0.5 + """ + + # __init__ can be found in plane3d_ext.py + + xyzd: npt.NDArray[np.float32] = field(converter=to_np_float32) + + def __array__(self, dtype: npt.DTypeLike = None) -> npt.NDArray[Any]: + # You can define your own __array__ function as a member of Plane3DExt in plane3d_ext.py + return np.asarray(self.xyzd, dtype=dtype) + + +Plane3DLike = Plane3D +Plane3DArrayLike = Union[Plane3D, Sequence[Plane3DLike], npt.NDArray[Any], npt.ArrayLike, Sequence[Sequence[float]]] + + +class Plane3DBatch(BaseBatch[Plane3DArrayLike]): + _ARROW_DATATYPE = pa.list_(pa.field("item", pa.float32(), nullable=False, metadata={}), 4) + + @staticmethod + def _native_to_pa_array(data: Plane3DArrayLike, data_type: pa.DataType) -> pa.Array: + return Plane3DExt.native_to_pa_array_override(data, data_type) + + +Plane3DExt.deferred_patch_class(Plane3D) diff --git a/rerun_py/rerun_sdk/rerun/datatypes/plane3d_ext.py b/rerun_py/rerun_sdk/rerun/datatypes/plane3d_ext.py new file mode 100644 index 000000000000..11c4859c11ba --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/datatypes/plane3d_ext.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Union, cast + +import numpy as np +import numpy.typing as npt +import pyarrow as pa + +from .._validators import flat_np_float32_array_from_array_like + +if TYPE_CHECKING: + from . import Plane3D, Plane3DArrayLike, Vec3DLike + + +class Plane3DExt: + """Extension for [Plane3D][rerun.datatypes.Plane3D].""" + + # The Y^Z plane with normal = +X. + YZ: Plane3D = None # type: ignore[assignment] + + # The Z^X plane with normal = +Y. + ZX: Plane3D = None # type: ignore[assignment] + + # The X^Y plane with normal = +Z. + XY: Plane3D = None # type: ignore[assignment] + + @staticmethod + def deferred_patch_class(cls: Any) -> None: + cls.YZ = cls([1.0, 0.0, 0.0]) + cls.ZX = cls([0.0, 1.0, 0.0]) + cls.XY = cls([0.0, 0.0, 1.0]) + + def __init__(self: Any, normal: Vec3DLike, distance: Union[float, int, None] = None) -> None: + """ + Create a new instance of the Plane3D datatype. + + Does *not* normalize the plane. + + Parameters + ---------- + normal: + Normal vector of the plane. + distance: + Distance of the plane from the origin. + Defaults to zero. + + """ + + normal_np = flat_np_float32_array_from_array_like(normal, 3) + if distance is None: + distance_np = np.array([0.0], dtype=np.float32) + else: + distance_np = np.array([distance], dtype=np.float32) + + self.__attrs_init__(xyzd=np.concatenate((normal_np, distance_np))) + + def normal(self: Any) -> npt.NDArray[np.float32]: + """Returns the normal vector of the plane.""" + return cast(npt.NDArray[np.float32], self.xyzd[:3]) + + def distance(self: Any) -> float: + """Returns the distance of the plane from the origin.""" + return cast(float, self.xyzd[3]) + + def with_distance(self: Any, distance: float) -> Plane3D: + """Returns a new plane with the same normal but with the distance set to the given amount.""" + from . import Plane3D + + return Plane3D(self.normal(), distance) + + @staticmethod + def native_to_pa_array_override(data: Plane3DArrayLike, data_type: pa.DataType) -> pa.Array: + planes = flat_np_float32_array_from_array_like(data, 4) + return pa.FixedSizeListArray.from_arrays(planes, type=data_type) diff --git a/tests/python/release_checklist/check_all_components_ui.py b/tests/python/release_checklist/check_all_components_ui.py index cd9cab6cfecc..b9106703631f 100644 --- a/tests/python/release_checklist/check_all_components_ui.py +++ b/tests/python/release_checklist/check_all_components_ui.py @@ -184,6 +184,7 @@ def alternatives(self) -> list[Any] | None: "NameBatch": TestCase(batch=["Hello World", "Foo Bar", "Baz Qux"]), "OpacityBatch": TestCase(0.5), "PinholeProjectionBatch": TestCase([(0, 1, 2), (3, 4, 5), (6, 7, 8)]), + "Plane3DBatch": TestCase(rr.datatypes.Plane3D(normal=(1, 2, 3), distance=4)), "PoseRotationAxisAngleBatch": TestCase( rr.datatypes.RotationAxisAngle(axis=(1, 0, 0), angle=rr.datatypes.Angle(rad=math.pi)) ),