From 4dabd765c1f3a9b2c2b857b5f199b1807041167e Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 13:56:41 -0700 Subject: [PATCH 01/20] Add `SolidColor` component type. This is identical to `Color` except that it has the semantics of specifically applying to surfaces (triangles) and not lines. --- .../re_types/definitions/rerun/components.fbs | 1 + .../rerun/components/solid_color.fbs | 19 +++ .../re_types/src/components/.gitattributes | 1 + crates/store/re_types/src/components/mod.rs | 3 + .../re_types/src/components/solid_color.rs | 117 ++++++++++++++++++ .../src/components/solid_color_ext.rs | 81 ++++++++++++ crates/top/rerun/src/sdk.rs | 4 +- crates/viewer/re_edit_ui/src/lib.rs | 3 +- crates/viewer/re_viewer/src/reflection/mod.rs | 7 ++ docs/content/reference/types/components.md | 1 + .../reference/types/components/.gitattributes | 1 + .../reference/types/components/solid_color.md | 21 ++++ .../reference/types/datatypes/rgba32.md | 1 + rerun_cpp/src/rerun/archetypes/boxes3d.hpp | 3 +- rerun_cpp/src/rerun/components.hpp | 1 + rerun_cpp/src/rerun/components/.gitattributes | 1 + .../src/rerun/components/solid_color.hpp | 87 +++++++++++++ .../src/rerun/components/solid_color_ext.cpp | 40 ++++++ .../rerun_sdk/rerun/components/.gitattributes | 1 + .../rerun_sdk/rerun/components/__init__.py | 4 + .../rerun_sdk/rerun/components/solid_color.py | 42 +++++++ 21 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 crates/store/re_types/definitions/rerun/components/solid_color.fbs create mode 100644 crates/store/re_types/src/components/solid_color.rs create mode 100644 crates/store/re_types/src/components/solid_color_ext.rs create mode 100644 docs/content/reference/types/components/solid_color.md create mode 100644 rerun_cpp/src/rerun/components/solid_color.hpp create mode 100644 rerun_cpp/src/rerun/components/solid_color_ext.cpp create mode 100644 rerun_py/rerun_sdk/rerun/components/solid_color.py diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 75661d1f84db..0f8a73ff1700 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -39,6 +39,7 @@ include "./components/resolution2d.fbs"; include "./components/rotation3d.fbs"; include "./components/scalar.fbs"; include "./components/scale3d.fbs"; +include "./components/solid_color.fbs"; include "./components/stroke_width.fbs"; include "./components/tensor_data.fbs"; include "./components/tensor_dimension_selection.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/solid_color.fbs b/crates/store/re_types/definitions/rerun/components/solid_color.fbs new file mode 100644 index 000000000000..e5adf8b40aef --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/solid_color.fbs @@ -0,0 +1,19 @@ +namespace rerun.components; + +// --- + +/// An RGBA color for the surface of an object. +/// +/// In representation and color space, this is identical to [components.Color]. +/// Unlike that component, it is used specifically to request that this color should be +/// applied to the entire surface of the object (as opposed to the lines of a wireframe). +table SolidColor ( + "attr.arrow.transparent", + "attr.python.aliases": "int, Sequence[int], npt.NDArray[Union[np.uint8, np.float32, np.float64]]", + "attr.python.array_aliases": "int, Sequence[Sequence[int]], npt.NDArray[Union[np.uint8, np.uint32, np.float32, np.float64]]", + "attr.rust.derive": "Copy, PartialEq, Eq, PartialOrd, Ord, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent", + "attr.docs.unreleased" +) { + rgba: rerun.datatypes.Rgba32 (order: 100); +} diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index c3fb38da0562..de55dc163660 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -40,6 +40,7 @@ resolution2d.rs linguist-generated=true rotation3d.rs linguist-generated=true scalar.rs linguist-generated=true scale3d.rs linguist-generated=true +solid_color.rs linguist-generated=true stroke_width.rs linguist-generated=true tensor_data.rs linguist-generated=true tensor_dimension_index_selection.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 8b19349ae5f3..2c8c7e29040d 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -70,6 +70,8 @@ mod scalar; mod scalar_ext; mod scale3d; mod scale3d_ext; +mod solid_color; +mod solid_color_ext; mod stroke_width; mod stroke_width_ext; mod tensor_data; @@ -136,6 +138,7 @@ pub use self::resolution2d::Resolution2D; pub use self::rotation3d::Rotation3D; pub use self::scalar::Scalar; pub use self::scale3d::Scale3D; +pub use self::solid_color::SolidColor; pub use self::stroke_width::StrokeWidth; pub use self::tensor_data::TensorData; pub use self::tensor_dimension_index_selection::TensorDimensionIndexSelection; diff --git a/crates/store/re_types/src/components/solid_color.rs b/crates/store/re_types/src/components/solid_color.rs new file mode 100644 index 000000000000..995d83302880 --- /dev/null +++ b/crates/store/re_types/src/components/solid_color.rs @@ -0,0 +1,117 @@ +// 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/solid_color.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 RGBA color for the surface of an object. +/// +/// In representation and color space, this is identical to [`components::Color`][crate::components::Color]. +/// Unlike that component, it is used specifically to request that this color should be +/// applied to the entire surface of the object (as opposed to the lines of a wireframe). +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct SolidColor(pub crate::datatypes::Rgba32); + +impl ::re_types_core::SizeBytes for SolidColor { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for SolidColor { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for SolidColor { + #[inline] + fn borrow(&self) -> &crate::datatypes::Rgba32 { + &self.0 + } +} + +impl std::ops::Deref for SolidColor { + type Target = crate::datatypes::Rgba32; + + #[inline] + fn deref(&self) -> &crate::datatypes::Rgba32 { + &self.0 + } +} + +impl std::ops::DerefMut for SolidColor { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Rgba32 { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(SolidColor); + +impl ::re_types_core::Loggable for SolidColor { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.SolidColor".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Rgba32::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Rgba32::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_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Rgba32::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Rgba32::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/solid_color_ext.rs b/crates/store/re_types/src/components/solid_color_ext.rs new file mode 100644 index 000000000000..71724fe29885 --- /dev/null +++ b/crates/store/re_types/src/components/solid_color_ext.rs @@ -0,0 +1,81 @@ +use super::SolidColor; + +impl SolidColor { + /// Black and opaque. + pub const BLACK: Self = Self(crate::datatypes::Rgba32::BLACK); + + /// White and opaque. + pub const WHITE: Self = Self(crate::datatypes::Rgba32::WHITE); + + /// Fully transparent (invisible). + pub const TRANSPARENT: Self = Self(crate::datatypes::Rgba32::TRANSPARENT); + + /// From gamma-space sRGB values. + #[inline] + pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self::from([r, g, b, 255]) + } + + /// From gamma-space sRGB values, with a separate/unmultiplied alpha in linear-space. + #[inline] + pub fn from_unmultiplied_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + Self::from(crate::datatypes::Rgba32::from_unmultiplied_rgba(r, g, b, a)) + } + + /// Most significant byte is `r`, least significant byte is `a`. + #[inline] + pub fn from_u32(rgba: u32) -> Self { + Self(rgba.into()) + } + + /// `[r, g, b, a]` + #[inline] + pub fn to_array(self) -> [u8; 4] { + [ + (self.0 .0 >> 24) as u8, + (self.0 .0 >> 16) as u8, + (self.0 .0 >> 8) as u8, + self.0 .0 as u8, + ] + } + + /// Most significant byte is `r`, least significant byte is `a`. + #[inline] + pub fn to_u32(self) -> u32 { + self.0 .0 + } +} + +impl SolidColor { + /// Create a new color. + #[inline] + pub fn new(value: impl Into) -> Self { + Self(value.into()) + } +} + +#[cfg(feature = "ecolor")] +impl From for ecolor::Color32 { + fn from(color: SolidColor) -> Self { + let [r, g, b, a] = color.to_array(); + Self::from_rgba_unmultiplied(r, g, b, a) + } +} + +#[cfg(feature = "ecolor")] +impl From for ecolor::Rgba { + fn from(color: SolidColor) -> Self { + let color: ecolor::Color32 = color.into(); + color.into() + } +} + +impl Default for SolidColor { + #[inline] + fn default() -> Self { + // Pretty hard to pick a good default value. + // White is best since multiplicative it does nothing and is visible in more cases than black would be. + // Most of the time, a `ComponentFallbackProvider` should provide a better value. + Self::WHITE + } +} diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs index 1277be52e29e..5ed98654ef38 100644 --- a/crates/top/rerun/src/sdk.rs +++ b/crates/top/rerun/src/sdk.rs @@ -24,8 +24,8 @@ mod prelude { pub use re_chunk::ChunkTimeline; pub use re_types::components::{ AlbedoFactor, Color, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, - OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel, - TriangleIndices, Vector2D, Vector3D, + OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, SolidColor, Text, + TextLogLevel, TriangleIndices, Vector2D, Vector3D, }; pub use re_types::datatypes::{ Angle, AnnotationInfo, ClassDescription, Float32, KeypointPair, Mat3x3, Quaternion, Rgba32, diff --git a/crates/viewer/re_edit_ui/src/lib.rs b/crates/viewer/re_edit_ui/src/lib.rs index cc8c8e756ecc..c9371b636616 100644 --- a/crates/viewer/re_edit_ui/src/lib.rs +++ b/crates/viewer/re_edit_ui/src/lib.rs @@ -25,7 +25,7 @@ use re_types::{ components::{ AggregationPolicy, AlbedoFactor, AxisLength, ChannelDataType, Color, ColorModel, Colormap, DepthMeter, DrawOrder, FillRatio, GammaCorrection, ImagePlaneDistance, MagnificationFilter, - MarkerSize, Name, Opacity, Scale3D, StrokeWidth, Text, Translation3D, + MarkerSize, Name, Opacity, Scale3D, SolidColor, StrokeWidth, Text, Translation3D, }, Loggable as _, }; @@ -38,6 +38,7 @@ use re_viewer_context::gpu_bridge::colormap_edit_or_view_ui; /// This crate is meant to be a leaf crate in the viewer ecosystem and should only be used by the `re_viewer` crate itself. pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) { registry.add_singleline_edit_or_view::(color::edit_rgba32); + registry.add_singleline_edit_or_view::(color::edit_rgba32); registry.add_singleline_edit_or_view(radius::edit_radius_ui); diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 0204fbd0bc9a..2c80869e8083 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -510,6 +510,13 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "An RGBA color for the surface of an object.\n\nIn representation and color space, this is identical to [`components.Color`](https://rerun.io/docs/reference/types/components/color).\nUnlike that component, it is used specifically to request that this color should be\napplied to the entire surface of the object (as opposed to the lines of a wireframe).", + placeholder: Some(SolidColor::default().to_arrow()?), + }, + ), ( ::name(), ComponentReflection { diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index ead8906d4967..867439c1b28d 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -52,6 +52,7 @@ on [Entities and Components](../../concepts/entity-component.md). * [`Rotation3D`](components/rotation3d.md): A 3D rotation, represented either by a quaternion or a rotation around axis. * [`Scalar`](components/scalar.md): A scalar value, encoded as a 64-bit floating point. * [`Scale3D`](components/scale3d.md): A 3D scale factor. +* [`SolidColor`](components/solid_color.md): An RGBA color for the surface of an object. * [`StrokeWidth`](components/stroke_width.md): The width of a stroke specified in UI points. * [`TensorData`](components/tensor_data.md): An N-dimensional array of numbers. * [`TensorDimensionIndexSelection`](components/tensor_dimension_index_selection.md): Specifies a concrete index on a tensor dimension. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 21d7f361a6fd..b15ee4c2151b 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -40,6 +40,7 @@ resolution2d.md linguist-generated=true rotation3d.md linguist-generated=true scalar.md linguist-generated=true scale3d.md linguist-generated=true +solid_color.md linguist-generated=true stroke_width.md linguist-generated=true tensor_data.md linguist-generated=true tensor_dimension_index_selection.md linguist-generated=true diff --git a/docs/content/reference/types/components/solid_color.md b/docs/content/reference/types/components/solid_color.md new file mode 100644 index 000000000000..0b226d5540fb --- /dev/null +++ b/docs/content/reference/types/components/solid_color.md @@ -0,0 +1,21 @@ +--- +title: "SolidColor" +--- + + +An RGBA color for the surface of an object. + +In representation and color space, this is identical to [`components.Color`](https://rerun.io/docs/reference/types/components/color). +Unlike that component, it is used specifically to request that this color should be +applied to the entire surface of the object (as opposed to the lines of a wireframe). + +## Fields + +* rgba: [`Rgba32`](../datatypes/rgba32.md) + +## API reference links + * šŸŒŠ [C++ API docs for `SolidColor`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1SolidColor.html) + * šŸ [Python API docs for `SolidColor`](https://ref.rerun.io/docs/python/stable/common/components#rerun.components.SolidColor) + * šŸ¦€ [Rust API docs for `SolidColor`](https://docs.rs/rerun/latest/rerun/components/struct.SolidColor.html) + + diff --git a/docs/content/reference/types/datatypes/rgba32.md b/docs/content/reference/types/datatypes/rgba32.md index 45865db43719..1d083b2d7b7a 100644 --- a/docs/content/reference/types/datatypes/rgba32.md +++ b/docs/content/reference/types/datatypes/rgba32.md @@ -23,3 +23,4 @@ byte is `R` and the least significant byte is `A`. * [`AlbedoFactor`](../components/albedo_factor.md?speculative-link) * [`AnnotationInfo`](../datatypes/annotation_info.md) * [`Color`](../components/color.md) +* [`SolidColor`](../components/solid_color.md) diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 7bbf1c4f3dcf..948912acca3d 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -49,7 +49,8 @@ namespace rerun::archetypes { /// rerun::RotationAxisAngle({0.0f, 1.0f, 0.0f}, rerun::Angle::degrees(30.0f)), /// }) /// .with_radii({0.025f}) - /// .with_colors({ + /// .with_colors({rerun::Rgba32(255, 255, 255)}) + /// .with_solid_colors({ /// rerun::Rgba32(255, 0, 0), /// rerun::Rgba32(0, 255, 0), /// rerun::Rgba32(0, 0, 255), diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index a5d523c875ec..f45c3211e112 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -41,6 +41,7 @@ #include "components/rotation3d.hpp" #include "components/scalar.hpp" #include "components/scale3d.hpp" +#include "components/solid_color.hpp" #include "components/stroke_width.hpp" #include "components/tensor_data.hpp" #include "components/tensor_dimension_index_selection.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index a014022f9846..c620a31a9677 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -49,6 +49,7 @@ resolution2d.hpp linguist-generated=true rotation3d.hpp linguist-generated=true scalar.hpp linguist-generated=true scale3d.hpp linguist-generated=true +solid_color.hpp linguist-generated=true stroke_width.hpp linguist-generated=true tensor_data.hpp linguist-generated=true tensor_dimension_index_selection.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/solid_color.hpp b/rerun_cpp/src/rerun/components/solid_color.hpp new file mode 100644 index 000000000000..d362888c68b3 --- /dev/null +++ b/rerun_cpp/src/rerun/components/solid_color.hpp @@ -0,0 +1,87 @@ +// 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/solid_color.fbs". + +#pragma once + +#include "../datatypes/rgba32.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: An RGBA color for the surface of an object. + /// + /// In representation and color space, this is identical to `components::Color`. + /// Unlike that component, it is used specifically to request that this color should be + /// applied to the entire surface of the object (as opposed to the lines of a wireframe). + struct SolidColor { + rerun::datatypes::Rgba32 rgba; + + public: + // Extensions to generated type defined in 'solid_color_ext.cpp' + + /// Construct SolidColor from unmultiplied RGBA values. + SolidColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) : rgba(r, g, b, a) {} + + uint8_t r() const { + return rgba.r(); + } + + uint8_t g() const { + return rgba.g(); + } + + uint8_t b() const { + return rgba.b(); + } + + uint8_t a() const { + return rgba.a(); + } + + public: + SolidColor() = default; + + SolidColor(rerun::datatypes::Rgba32 rgba_) : rgba(rgba_) {} + + SolidColor& operator=(rerun::datatypes::Rgba32 rgba_) { + rgba = rgba_; + return *this; + } + + SolidColor(uint32_t rgba_) : rgba(rgba_) {} + + SolidColor& operator=(uint32_t rgba_) { + rgba = rgba_; + return *this; + } + + /// Cast to the underlying Rgba32 datatype + operator rerun::datatypes::Rgba32() const { + return rgba; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Rgba32) == sizeof(components::SolidColor)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.SolidColor"; + + /// 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::SolidColor` into an arrow array. + static Result> to_arrow( + const components::SolidColor* instances, size_t num_instances + ) { + return Loggable::to_arrow(&instances->rgba, num_instances); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/solid_color_ext.cpp b/rerun_cpp/src/rerun/components/solid_color_ext.cpp new file mode 100644 index 000000000000..524dd653a6dc --- /dev/null +++ b/rerun_cpp/src/rerun/components/solid_color_ext.cpp @@ -0,0 +1,40 @@ +#include "solid_color.hpp" + +// Uncomment for better auto-complete while editing the extension. +// #define EDIT_EXTENSION + +namespace rerun { + namespace components { + +#ifdef EDIT_EXTENSION + struct SolidColorExt : public SolidColor { + SolidColorExt(uint32_t _rgba) : SolidColor(_rgba) {} + +#define SolidColor SolidColorExt + + // + + /// Construct SolidColor from unmultiplied RGBA values. + SolidColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) : rgba(r, g, b, a) {} + + uint8_t r() const { + return rgba.r(); + } + + uint8_t g() const { + return rgba.g(); + } + + uint8_t b() const { + return rgba.b(); + } + + uint8_t a() const { + return rgba.a(); + } + + // + }; +#endif + } // namespace components +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 84e018387160..55f24d3e214d 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -41,6 +41,7 @@ resolution2d.py linguist-generated=true rotation3d.py linguist-generated=true scalar.py linguist-generated=true scale3d.py linguist-generated=true +solid_color.py linguist-generated=true stroke_width.py linguist-generated=true tensor_data.py linguist-generated=true tensor_dimension_index_selection.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 0a7d0888d25c..98de80ea486d 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -65,6 +65,7 @@ from .rotation3d import Rotation3D, Rotation3DBatch, Rotation3DType from .scalar import Scalar, ScalarBatch, ScalarType from .scale3d import Scale3D, Scale3DBatch, Scale3DType +from .solid_color import SolidColor, SolidColorBatch, SolidColorType from .stroke_width import StrokeWidth, StrokeWidthBatch, StrokeWidthType from .tensor_data import TensorData, TensorDataBatch, TensorDataType from .tensor_dimension_index_selection import ( @@ -221,6 +222,9 @@ "Scale3D", "Scale3DBatch", "Scale3DType", + "SolidColor", + "SolidColorBatch", + "SolidColorType", "StrokeWidth", "StrokeWidthBatch", "StrokeWidthType", diff --git a/rerun_py/rerun_sdk/rerun/components/solid_color.py b/rerun_py/rerun_sdk/rerun/components/solid_color.py new file mode 100644 index 000000000000..c7e2da7d8705 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/solid_color.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/solid_color.fbs". + +# You can extend this class by creating a "SolidColorExt" class in "solid_color_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["SolidColor", "SolidColorBatch", "SolidColorType"] + + +class SolidColor(datatypes.Rgba32, ComponentMixin): + """ + **Component**: An RGBA color for the surface of an object. + + In representation and color space, this is identical to [`components.Color`][rerun.components.Color]. + Unlike that component, it is used specifically to request that this color should be + applied to the entire surface of the object (as opposed to the lines of a wireframe). + """ + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of SolidColorExt in solid_color_ext.py + + # Note: there are no fields here because SolidColor delegates to datatypes.Rgba32 + pass + + +class SolidColorType(datatypes.Rgba32Type): + _TYPE_NAME: str = "rerun.components.SolidColor" + + +class SolidColorBatch(datatypes.Rgba32Batch, ComponentBatchMixin): + _ARROW_TYPE = SolidColorType() + + +# This is patched in late to avoid circular dependencies. +SolidColor._BATCH_TYPE = SolidColorBatch # type: ignore[assignment] From 3816943ac127dedf01d0fa36742767501af12ac6 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 14:22:17 -0700 Subject: [PATCH 02/20] Make `process_color_slice()` take any component containing a color. --- .../src/visualizers/mod.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs index 5f4c19ce1021..baee4d9e5f9f 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs @@ -33,13 +33,12 @@ pub use utilities::{ // --- +use core::ops::Deref; + use ahash::HashMap; use re_entity_db::EntityPath; -use re_types::{ - components::Color, - datatypes::{KeypointId, KeypointPair}, -}; +use re_types::datatypes::{KeypointId, KeypointPair, Rgba32}; use re_viewer_context::{ auto_color_egui, Annotations, ApplicableEntities, IdentifiedViewSystem, QueryContext, ResolvedAnnotationInfos, SpaceViewClassRegistryError, SpaceViewSystemExecutionError, @@ -141,14 +140,18 @@ pub fn collect_ui_labels(visualizers: &VisualizerCollection) -> Vec { ui_labels } -/// Process [`Color`] components using annotations and default colors. -pub fn process_color_slice<'a>( +/// Process [`Color`] or equivalent components using annotations and default colors. +pub fn process_color_slice<'a, C>( ctx: &QueryContext<'_>, - fallback_provider: &'a dyn re_viewer_context::TypedComponentFallbackProvider, + fallback_provider: &'a dyn re_viewer_context::TypedComponentFallbackProvider, num_instances: usize, annotation_infos: &'a ResolvedAnnotationInfos, - colors: &'a [Color], -) -> Vec { + // accept any of the multiple components that contain colors + colors: &'a [C], +) -> Vec +where + C: re_types::Component + Deref, +{ // NOTE: Do not put tracing scopes here, this is called for every entity/timestamp in a frame. if let Some(last_color) = colors.last() { @@ -170,12 +173,12 @@ pub fn process_color_slice<'a>( re_tracing::profile_scope!("no colors, same annotation"); let color = annotation_info .color() - .unwrap_or_else(|| fallback_provider.fallback_for(ctx).into()); + .unwrap_or_else(|| to_egui_color(&fallback_provider.fallback_for(ctx))); vec![color; *count] } ResolvedAnnotationInfos::Many(annotation_info) => { re_tracing::profile_scope!("no-colors, many annotations"); - let fallback = fallback_provider.fallback_for(ctx).into(); + let fallback = to_egui_color(&fallback_provider.fallback_for(ctx)); annotation_info .iter() .map(|annotation_info| annotation_info.color().unwrap_or(fallback)) @@ -186,8 +189,8 @@ pub fn process_color_slice<'a>( } #[inline] -fn to_egui_color(color: &Color) -> egui::Color32 { - let [r, g, b, a] = color.to_array(); +fn to_egui_color(color: &impl Deref) -> egui::Color32 { + let [r, g, b, a] = (*color).to_array(); egui::Color32::from_rgba_unmultiplied(r, g, b, a) } From a4ebd4786a1025d3287d02e76eb87ee8b190d161 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 16 Jul 2024 14:20:06 -0700 Subject: [PATCH 03/20] Add `SolidCache` and use it in `EllipsoidsVisualizer`. --- .../re_space_view_spatial/src/proc_mesh.rs | 154 ++++++++++++++++- .../src/visualizers/ellipsoids.rs | 162 ++++++++++++++---- 2 files changed, 273 insertions(+), 43 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index 2551d15f04b4..04ae861d0da6 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -1,8 +1,16 @@ +//! Procedurally-generated meshes for rendering objects that are +//! specified geometrically, and have nontrivial numbers of vertices each, +//! such as a sphere or cylinder. + use std::sync::Arc; use ahash::HashSet; use glam::Vec3; +use itertools::Itertools as _; +use smallvec::smallvec; +use re_renderer::mesh; +use re_renderer::resource_managers::{GpuMeshHandle, ResourceManagerError}; use re_renderer::RenderContext; use re_viewer_context::Cache; @@ -10,16 +18,32 @@ use re_viewer_context::Cache; /// Description of a mesh that can be procedurally generated. /// -/// Obtain the actual mesh by passing this to [`WireframeCache`]. -/// In the future, it will be possible to produce solid triangle meshes too. +/// Obtain the actual mesh by passing this to [`WireframeCache`] or [`SolidCache`]. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ProcMeshKey { /// A sphere of unit radius. + /// + /// The resulting mesh may be scaled to represent spheres and ellipsoids + /// of other sizes. Sphere { subdivisions: usize }, } +impl ProcMeshKey { + /// Returns the bounding box which can be computed from the mathematical shape, + /// without regard for its exact approximation as a mesh. + fn simple_bounding_box(&self) -> re_math::BoundingBox { + match self { + Self::Sphere { subdivisions: _ } => { + // sphereā€™s radius is 1, so its size is 2 + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)) + } + } + } +} + /// A renderable mesh generated from a [`ProcMeshKey`] by the [`WireframeCache`], /// which is to be drawn as lines rather than triangles. +#[derive(Debug)] pub struct WireframeMesh { pub bbox: re_math::BoundingBox, @@ -33,6 +57,19 @@ pub struct WireframeMesh { pub line_strips: Vec>, } +/// A renderable mesh generated from a [`ProcMeshKey`] by the [`SolidCache`], +/// which is to be drawn as triangles rather than lines. +/// +/// This type is cheap to clone. +#[derive(Debug, Clone)] +pub struct SolidMesh { + pub bbox: re_math::BoundingBox, + + /// Mesh to render. Note that its colors are set to black, so that the + /// `MeshInstance::additive_tint` can be used to set the color per instance. + pub gpu_mesh: GpuMeshHandle, +} + // ---------------------------------------------------------------------------- /// Cache for the computation of wireframe meshes from [`ProcMeshKey`]s. @@ -79,6 +116,8 @@ impl Cache for WireframeCache { /// Generate a wireframe mesh without caching. fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> WireframeMesh { + re_tracing::profile_function!(); + // In the future, render_ctx will be used to allocate GPU memory for the mesh. _ = render_ctx; @@ -128,8 +167,7 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram .collect() }; WireframeMesh { - // sphereā€™s radius is 1, so its size is 2 - bbox: re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)), + bbox: key.simple_bounding_box(), vertex_count: line_strips.iter().map(|v| v.len()).sum(), line_strips, } @@ -137,4 +175,110 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram } } -// TODO(kpreid): A solid (rather than wireframe) mesh cache implementation should live here. +// ---------------------------------------------------------------------------- + +/// Cache for the computation of triangle meshes from [`ProcMeshKey`]s that depict the +/// shape as a solid object. +#[derive(Default)] +pub struct SolidCache(ahash::HashMap>); + +impl SolidCache { + pub fn entry(&mut self, key: ProcMeshKey, render_ctx: &RenderContext) -> Option { + re_tracing::profile_function!(); + + self.0 + .entry(key) + .or_insert_with(|| { + re_log::debug!("Generating mesh {key:?}ā€¦"); + + match generate_solid(&key, render_ctx) { + Ok(mesh) => Some(mesh), + Err(err) => { + re_log::warn!( + "Failed to generate mesh {key:?}: {}", + re_error::format_ref(&err) + ); + None + } + } + }) + .clone() + } +} + +impl Cache for SolidCache { + fn begin_frame(&mut self) {} + + fn purge_memory(&mut self) { + self.0.clear(); + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +/// Generate a solid triangle mesh without caching. +fn generate_solid( + key: &ProcMeshKey, + render_ctx: &RenderContext, +) -> Result { + re_tracing::profile_function!(); + + let mesh: mesh::Mesh = match *key { + ProcMeshKey::Sphere { subdivisions } => { + let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); + + let vertex_positions: Vec = + subdiv.raw_points().iter().map(|&p| p.into()).collect(); + // A unit sphere's normals are its positions. + let vertex_normals = vertex_positions.clone(); + let num_vertices = vertex_positions.len(); + + let triangle_indices = subdiv.get_all_indices(); + let triangle_indices: Vec = triangle_indices + .into_iter() + .tuples() + .map(|(i1, i2, i3)| glam::uvec3(i1, i2, i3)) + .collect(); + + let materials = smallvec![mesh::Material { + label: "default material".into(), + index_range: 0..(triangle_indices.len() * 3) as u32, + albedo: render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone(), + albedo_factor: re_renderer::Rgba::BLACK, + }]; + + mesh::Mesh { + label: format!("{key:?}").into(), + + // bytemuck is re-grouping the indices into triples without realloc + triangle_indices, + + vertex_positions, + vertex_normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + + materials, + } + } + }; + + mesh.sanity_check()?; + + let gpu_mesh = render_ctx.mesh_manager.write().create( + render_ctx, + &mesh, + re_renderer::resource_managers::ResourceLifeTime::LongLived, + )?; + + Ok(SolidMesh { + bbox: key.simple_bounding_box(), + gpu_mesh, + }) +} diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index 48b7bc3f7d70..b6fa70c6122c 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -1,7 +1,10 @@ +use egui::Color32; use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, RenderContext}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Ellipsoids, components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, @@ -15,7 +18,8 @@ use re_viewer_context::{ use crate::{ contexts::SpatialSceneEntityContext, - proc_mesh::{ProcMeshKey, WireframeCache}, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, + proc_mesh, view_kind::SpatialSpaceViewKind, visualizers::{UiLabel, UiLabelTarget}, }; @@ -45,7 +49,7 @@ impl EllipsoidsVisualizer { entity_path: &'a EntityPath, centers: &'a [Position3D], labels: &'a [Text], - colors: &'a [egui::Color32], + colors: &'a [Color32], annotation_infos: &'a ResolvedAnnotationInfos, world_from_entity: glam::Affine3A, ) -> impl Iterator + 'a { @@ -71,10 +75,12 @@ impl EllipsoidsVisualizer { }) } + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, @@ -105,8 +111,20 @@ impl EllipsoidsVisualizer { data.line_radii, Radius::default(), ); - let colors = - process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let surface_colors = process_color_slice( + ctx, + self, + num_instances, + &annotation_infos, + data.surface_colors, + ); + let line_colors = process_color_slice( + ctx, + self, + num_instances, + &annotation_infos, + data.line_colors, + ); let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); @@ -115,7 +133,7 @@ impl EllipsoidsVisualizer { entity_path, data.centers, data.labels, - &colors, + &line_colors, // TODO: fall back to surface color? &annotation_infos, ent_context.world_from_entity, )); @@ -129,9 +147,20 @@ impl EllipsoidsVisualizer { let mut bounding_box = re_math::BoundingBox::NOTHING; - for (i, (half_size, ¢er, rotation, radius, color)) in - itertools::izip!(data.half_sizes, centers, rotations, radii, colors).enumerate() + for ( + instance_index, + (half_size, ¢er, rotation, radius, surface_color, line_color), + ) in itertools::izip!( + data.half_sizes, + centers, + rotations, + radii, + surface_colors, + line_colors + ) + .enumerate() { + let instance = Instance::from(instance_index as u64); let transform = glam::Affine3A::from_scale_rotation_translation( glam::Vec3::from(*half_size), rotation.0.into(), @@ -141,35 +170,68 @@ impl EllipsoidsVisualizer { // TODO(kpreid): subdivisions should be configurable, and possibly dynamic based on // either world size or screen size (depending on application). let subdivisions = 2; - - let Some(sphere_mesh) = ctx.viewer_ctx.cache.entry(|c: &mut WireframeCache| { - c.entry(ProcMeshKey::Sphere { subdivisions }, render_ctx) - }) else { - // TODO(kpreid): Should this be just returning nothing instead? - // If we do, there won't be any error report, just missing data. - - return Err(SpaceViewSystemExecutionError::DrawDataCreationError( - "Failed to allocate wireframe mesh".into(), - )); - }; - - bounding_box = bounding_box.union(sphere_mesh.bbox.transform_affine3(&transform)); - - for strip in &sphere_mesh.line_strips { - let box3d = line_batch - .add_strip(strip.iter().map(|&point| transform.transform_point3(point))) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + let proc_mesh_key = proc_mesh::ProcMeshKey::Sphere { subdivisions }; + + if line_color != Color32::TRANSPARENT { + let Some(wireframe_mesh) = + ctx.viewer_ctx + .cache + .entry(|c: &mut proc_mesh::WireframeCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate wireframe mesh".into(), + )); + }; + + bounding_box = + bounding_box.union(wireframe_mesh.bbox.transform_affine3(&transform)); + + for strip in &wireframe_mesh.line_strips { + let strip_builder = line_batch + .add_strip(strip.iter().map(|&point| transform.transform_point3(point))) + .color(line_color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + if let Some(outline_mask_ids) = ent_context + .highlight + .instances + .get(&Instance::from(instance_index as u64)) + { + // Not using ent_context.highlight.index_outline_mask() because + // that's already handled when the builder was created. + strip_builder.outline_mask_ids(*outline_mask_ids); + } } } + + if surface_color != Color32::TRANSPARENT { + let Some(solid_mesh) = ctx + .viewer_ctx + .cache + .entry(|c: &mut proc_mesh::SolidCache| c.entry(proc_mesh_key, render_ctx)) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + bounding_box = + bounding_box.union(solid_mesh.bbox.transform_affine3(&transform)); + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: transform, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: surface_color, + }); + } } self.0.add_bounding_box( @@ -192,7 +254,8 @@ struct EllipsoidsComponentData<'a> { // Clamped to edge centers: &'a [Position3D], rotations: &'a [Rotation3D], - colors: &'a [Color], + line_colors: &'a [Color], + surface_colors: &'a [Color], line_radii: &'a [Radius], labels: &'a [Text], keypoint_ids: &'a [KeypointId], @@ -234,6 +297,9 @@ impl VisualizerSystem for EllipsoidsVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -296,7 +362,9 @@ impl VisualizerSystem for EllipsoidsVisualizer { half_sizes, centers: centers.unwrap_or_default(), rotations: rotations.unwrap_or_default(), - colors: colors.unwrap_or_default(), + // TODO(kpreid): separate these colors + line_colors: colors.unwrap_or_default(), + surface_colors: colors.unwrap_or_default(), line_radii: line_radii.unwrap_or_default(), labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), @@ -308,6 +376,7 @@ impl VisualizerSystem for EllipsoidsVisualizer { self.process_data( ctx, &mut line_builder, + &mut solid_instances, view_query, spatial_ctx, data, @@ -318,7 +387,24 @@ impl VisualizerSystem for EllipsoidsVisualizer { }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { From 4e30473043c61b278bfdb9370d25f01d6212fe21 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 14:08:23 -0700 Subject: [PATCH 04/20] Split color in `Ellipsoids` to `solid_colors` and `line_colors`. Also update `EllipsoidsVisualizer` to use the new `process_labels_3d` which was added later, and update `process_labels_3d` to handle two colors. --- .../rerun/archetypes/ellipsoids.fbs | 11 +- .../re_types/src/archetypes/ellipsoids.rs | 76 ++++++++--- .../src/visualizers/arrows3d.rs | 1 + .../src/visualizers/boxes3d.rs | 1 + .../src/visualizers/ellipsoids.rs | 126 ++++++++---------- .../src/visualizers/lines3d.rs | 1 + .../src/visualizers/points3d.rs | 1 + .../src/visualizers/utilities/labels.rs | 33 +++-- .../reference/types/archetypes/ellipsoids.md | 2 +- .../reference/types/components/solid_color.md | 3 + .../all/archetypes/ellipsoid_batch.rs | 12 +- rerun_cpp/src/rerun/archetypes/ellipsoids.cpp | 11 +- rerun_cpp/src/rerun/archetypes/ellipsoids.hpp | 29 +++- .../rerun_sdk/rerun/archetypes/ellipsoids.py | 20 ++- .../rerun/archetypes/ellipsoids_ext.py | 12 +- 15 files changed, 220 insertions(+), 119 deletions(-) diff --git a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs index b051bea57430..3fb5cda82fca 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs @@ -39,8 +39,15 @@ table Ellipsoids ( /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. rotations: [rerun.components.Rotation3D] ("attr.rerun.component_recommended", nullable, order: 2100); - /// Optional colors for the ellipsoids. - colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2200); + /// Optional colors for the ellipsoids' surfaces. + /// + /// This color may be transparent to render the ellipsoid as a wireframe alone. + solid_colors: [rerun.components.SolidColor] ("attr.rerun.component_recommended", nullable, order: 2200); + + /// Optional colors for the ellipsoids' wireframe lines. + /// + /// This color may be transparent to render the ellipsoid as a colored surface alone. + line_colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2300); // --- Optional --- diff --git a/crates/store/re_types/src/archetypes/ellipsoids.rs b/crates/store/re_types/src/archetypes/ellipsoids.rs index 8f3fc89f3920..e560c8a37702 100644 --- a/crates/store/re_types/src/archetypes/ellipsoids.rs +++ b/crates/store/re_types/src/archetypes/ellipsoids.rs @@ -43,8 +43,15 @@ pub struct Ellipsoids { /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. pub rotations: Option>, - /// Optional colors for the ellipsoids. - pub colors: Option>, + /// Optional colors for the ellipsoids' surfaces. + /// + /// This color may be transparent to render the ellipsoid as a wireframe alone. + pub solid_colors: Option>, + + /// Optional colors for the ellipsoids' wireframe lines. + /// + /// This color may be transparent to render the ellipsoid as a colored surface alone. + pub line_colors: Option>, /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. pub line_radii: Option>, @@ -64,7 +71,8 @@ impl ::re_types_core::SizeBytes for Ellipsoids { self.half_sizes.heap_size_bytes() + self.centers.heap_size_bytes() + self.rotations.heap_size_bytes() - + self.colors.heap_size_bytes() + + self.solid_colors.heap_size_bytes() + + self.line_colors.heap_size_bytes() + self.line_radii.heap_size_bytes() + self.labels.heap_size_bytes() + self.class_ids.heap_size_bytes() @@ -75,6 +83,7 @@ impl ::re_types_core::SizeBytes for Ellipsoids { >::is_pod() && >>::is_pod() && >>::is_pod() + && >>::is_pod() && >>::is_pod() && >>::is_pod() && >>::is_pod() @@ -85,11 +94,12 @@ impl ::re_types_core::SizeBytes for Ellipsoids { static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = once_cell::sync::Lazy::new(|| ["rerun.components.HalfSize3D".into()]); -static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 5usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Position3D".into(), "rerun.components.Rotation3D".into(), + "rerun.components.SolidColor".into(), "rerun.components.Color".into(), "rerun.components.EllipsoidsIndicator".into(), ] @@ -104,12 +114,13 @@ static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), "rerun.components.Position3D".into(), "rerun.components.Rotation3D".into(), + "rerun.components.SolidColor".into(), "rerun.components.Color".into(), "rerun.components.EllipsoidsIndicator".into(), "rerun.components.Radius".into(), @@ -119,8 +130,8 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = }); impl Ellipsoids { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 5 recommended, 3 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Ellipsoids`] [`::re_types_core::Archetype`] @@ -211,14 +222,26 @@ impl ::re_types_core::Archetype for Ellipsoids { } else { None }; - let colors = if let Some(array) = arrays_by_name.get("rerun.components.Color") { + let solid_colors = if let Some(array) = arrays_by_name.get("rerun.components.SolidColor") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#solid_colors")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Ellipsoids#solid_colors")? + }) + } else { + None + }; + let line_colors = if let Some(array) = arrays_by_name.get("rerun.components.Color") { Some({ ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Ellipsoids#colors")? + .with_context("rerun.archetypes.Ellipsoids#line_colors")? .into_iter() .map(|v| v.ok_or_else(DeserializationError::missing_data)) .collect::>>() - .with_context("rerun.archetypes.Ellipsoids#colors")? + .with_context("rerun.archetypes.Ellipsoids#line_colors")? }) } else { None @@ -263,7 +286,8 @@ impl ::re_types_core::Archetype for Ellipsoids { half_sizes, centers, rotations, - colors, + solid_colors, + line_colors, line_radii, labels, class_ids, @@ -284,7 +308,10 @@ impl ::re_types_core::AsComponents for Ellipsoids { self.rotations .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), - self.colors + self.solid_colors + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.line_colors .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), self.line_radii @@ -313,7 +340,8 @@ impl Ellipsoids { half_sizes: half_sizes.into_iter().map(Into::into).collect(), centers: None, rotations: None, - colors: None, + solid_colors: None, + line_colors: None, line_radii: None, labels: None, class_ids: None, @@ -344,13 +372,27 @@ impl Ellipsoids { self } - /// Optional colors for the ellipsoids. + /// Optional colors for the ellipsoids' surfaces. + /// + /// This color may be transparent to render the ellipsoid as a wireframe alone. + #[inline] + pub fn with_solid_colors( + mut self, + solid_colors: impl IntoIterator>, + ) -> Self { + self.solid_colors = Some(solid_colors.into_iter().map(Into::into).collect()); + self + } + + /// Optional colors for the ellipsoids' wireframe lines. + /// + /// This color may be transparent to render the ellipsoid as a colored surface alone. #[inline] - pub fn with_colors( + pub fn with_line_colors( mut self, - colors: impl IntoIterator>, + line_colors: impl IntoIterator>, ) -> Self { - self.colors = Some(colors.into_iter().map(Into::into).collect()); + self.line_colors = Some(line_colors.into_iter().map(Into::into).collect()); self } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs index 0242fc0ff2cf..1fd6469afc37 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/arrows3d.rs @@ -141,6 +141,7 @@ impl Arrows3DVisualizer { label_positions, data.labels, &colors, + &colors, &annotation_infos, ent_context.world_from_entity, )); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index 1533dfbe9b49..bbcdd7ea2464 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -130,6 +130,7 @@ impl Boxes3DVisualizer { label_positions, data.labels, &colors, + &colors, &annotation_infos, ent_context.world_from_entity, )); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index b6fa70c6122c..71ad37df351b 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -1,33 +1,32 @@ use egui::Color32; -use re_entity_db::{EntityPath, InstancePathHash}; +use re_entity_db::InstancePathHash; use re_log_types::Instance; -use re_query::range_zip_1x7; use re_renderer::{ renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, }; use re_types::{ archetypes::Ellipsoids, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, SolidColor, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, - ResolvedAnnotationInfos, SpaceViewSystemExecutionError, TypedComponentFallbackProvider, - ViewContext, ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, + SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, + ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, }; use crate::{ contexts::SpatialSceneEntityContext, - instance_hash_conversions::picking_layer_id_from_instance_path_hash, - proc_mesh, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, proc_mesh, view_kind::SpatialSpaceViewKind, - visualizers::{UiLabel, UiLabelTarget}, }; use super::{ entity_iterator::clamped_or, filter_visualizable_3d_entities, - process_annotation_and_keypoint_slices, process_color_slice, process_radius_slice, - SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, + process_annotation_and_keypoint_slices, process_color_slice, process_labels_3d, + process_radius_slice, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- @@ -45,36 +44,6 @@ impl Default for EllipsoidsVisualizer { // NOTE: Do not put profile scopes in these methods. They are called for all entities and all // timestamps within a time range -- it's _a lot_. impl EllipsoidsVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - centers: &'a [Position3D], - labels: &'a [Text], - colors: &'a [Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_entity: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = annotation_infos - .iter() - .zip(labels.iter().map(Some).chain(std::iter::repeat(None))) - .map(|(annotation_info, label)| annotation_info.label(label.map(|l| l.as_str()))); - - itertools::izip!(centers, labels, colors) - .enumerate() - .filter_map(move |(i, (center, label, color))| { - label.map(|label| UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Position3D( - world_from_entity.transform_point3(center.0.into()), - ), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - }) - } - #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, @@ -116,7 +85,7 @@ impl EllipsoidsVisualizer { self, num_instances, &annotation_infos, - data.surface_colors, + data.solid_colors, ); let line_colors = process_color_slice( ctx, @@ -129,15 +98,6 @@ impl EllipsoidsVisualizer { let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); - self.0.ui_labels.extend(Self::process_labels( - entity_path, - data.centers, - data.labels, - &line_colors, // TODO: fall back to surface color? - &annotation_infos, - ent_context.world_from_entity, - )); - let mut line_batch = line_builder .batch("ellipsoids") .depth_offset(ent_context.depth_offset) @@ -145,18 +105,18 @@ impl EllipsoidsVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = re_math::BoundingBox::NOTHING; + let mut obj_space_bounding_box = re_math::BoundingBox::NOTHING; for ( instance_index, - (half_size, ¢er, rotation, radius, surface_color, line_color), + (half_size, ¢er, rotation, radius, &surface_color, &line_color), ) in itertools::izip!( data.half_sizes, centers, rotations, radii, - surface_colors, - line_colors + &surface_colors, + &line_colors ) .enumerate() { @@ -185,8 +145,8 @@ impl EllipsoidsVisualizer { )); }; - bounding_box = - bounding_box.union(wireframe_mesh.bbox.transform_affine3(&transform)); + obj_space_bounding_box = obj_space_bounding_box + .union(wireframe_mesh.bbox.transform_affine3(&transform)); for strip in &wireframe_mesh.line_strips { let strip_builder = line_batch @@ -218,8 +178,8 @@ impl EllipsoidsVisualizer { )); }; - bounding_box = - bounding_box.union(solid_mesh.bbox.transform_affine3(&transform)); + obj_space_bounding_box = + obj_space_bounding_box.union(solid_mesh.bbox.transform_affine3(&transform)); mesh_instances.push(MeshInstance { gpu_mesh: solid_mesh.gpu_mesh, @@ -236,9 +196,32 @@ impl EllipsoidsVisualizer { self.0.add_bounding_box( entity_path.hash(), - bounding_box, + obj_space_bounding_box, ent_context.world_from_entity, ); + + if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { + // If there's many ellipsoids but only a single label, place the single label at the middle of the visualization. + let label_positions = if data.labels.len() == 1 && num_instances > 1 { + // TODO(andreas): A smoothed over time (+ discontinuity detection) bounding box would be great. + itertools::Either::Left(std::iter::once(obj_space_bounding_box.center())) + } else { + // Take center point of every ellipsoid. + itertools::Either::Right( + clamped_or(data.centers, &Position3D::ZERO).map(|&c| c.into()), + ) + }; + + self.0.ui_labels.extend(process_labels_3d( + entity_path, + label_positions, + data.labels, + &line_colors, + &surface_colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } Ok(()) @@ -254,8 +237,8 @@ struct EllipsoidsComponentData<'a> { // Clamped to edge centers: &'a [Position3D], rotations: &'a [Rotation3D], + solid_colors: &'a [SolidColor], line_colors: &'a [Color], - surface_colors: &'a [Color], line_radii: &'a [Radius], labels: &'a [Text], keypoint_ids: &'a [KeypointId], @@ -330,17 +313,19 @@ impl VisualizerSystem for EllipsoidsVisualizer { let centers = results.get_or_empty_dense(resolver)?; let rotations = results.get_or_empty_dense(resolver)?; - let colors = results.get_or_empty_dense(resolver)?; + let solid_colors = results.get_or_empty_dense(resolver)?; + let line_colors = results.get_or_empty_dense(resolver)?; let line_radii = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + let data = re_query::range_zip_1x8( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), - colors.range_indexed(), + solid_colors.range_indexed(), + line_colors.range_indexed(), line_radii.range_indexed(), labels.range_indexed(), class_ids.range_indexed(), @@ -352,7 +337,8 @@ impl VisualizerSystem for EllipsoidsVisualizer { half_sizes, centers, rotations, - colors, + solid_colors, + line_colors, line_radii, labels, class_ids, @@ -362,9 +348,8 @@ impl VisualizerSystem for EllipsoidsVisualizer { half_sizes, centers: centers.unwrap_or_default(), rotations: rotations.unwrap_or_default(), - // TODO(kpreid): separate these colors - line_colors: colors.unwrap_or_default(), - surface_colors: colors.unwrap_or_default(), + solid_colors: solid_colors.unwrap_or_default(), + line_colors: line_colors.unwrap_or_default(), line_radii: line_radii.unwrap_or_default(), labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), @@ -426,4 +411,11 @@ impl TypedComponentFallbackProvider for EllipsoidsVisualizer { } } +impl TypedComponentFallbackProvider for EllipsoidsVisualizer { + fn fallback_for(&self, _: &QueryContext<'_>) -> SolidColor { + // By default, use wireframe visualization only + SolidColor::TRANSPARENT + } +} + re_viewer_context::impl_component_fallback_provider!(EllipsoidsVisualizer => [Color]); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs index c533b72f79cf..175eef4accbf 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/lines3d.rs @@ -135,6 +135,7 @@ impl Lines3DVisualizer { label_positions, data.labels, &colors, + &colors, &annotation_infos, ent_context.world_from_entity, )); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs index 50873b163273..7f43b0e3d0c7 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs @@ -149,6 +149,7 @@ impl Points3DVisualizer { label_positions.iter().copied(), data.labels, &colors, + &colors, &annotation_infos, ent_context.world_from_entity, )); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs index 031c7ed15dfa..45b705ca6718 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs @@ -43,7 +43,8 @@ pub fn process_labels_3d<'a>( entity_path: &'a EntityPath, positions: impl Iterator + 'a, labels: &'a [re_types::components::Text], - colors: &'a [egui::Color32], + colors_primary: &'a [egui::Color32], + colors_secondary: &'a [egui::Color32], annotation_infos: &'a ResolvedAnnotationInfos, world_from_obj: glam::Affine3A, ) -> impl Iterator + 'a { @@ -53,18 +54,28 @@ pub fn process_labels_3d<'a>( ) .map(|(annotation_info, label)| annotation_info.label(label.map(|l| l.as_str()))); - let colors = clamped_or(colors, &egui::Color32::WHITE); + let colors_primary = clamped_or(colors_primary, &egui::Color32::TRANSPARENT); + let colors_secondary = clamped_or(colors_secondary, &egui::Color32::WHITE); - itertools::izip!(positions, labels, colors) + itertools::izip!(positions, labels, colors_primary, colors_secondary) .enumerate() - .filter_map(move |(i, (point, label, color))| { - label.map(|label| UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Position3D(world_from_obj.transform_point3(point)), - labeled_instance: InstancePathHash::instance(entity_path, Instance::from(i as u64)), - }) - }) + .filter_map( + move |(i, (point, label, &color_primary, &color_secondary))| { + label.map(|label| UiLabel { + text: label, + color: if color_primary == egui::Color32::TRANSPARENT { + color_secondary + } else { + color_primary + }, + target: UiLabelTarget::Position3D(world_from_obj.transform_point3(point)), + labeled_instance: InstancePathHash::instance( + entity_path, + Instance::from(i as u64), + ), + }) + }, + ) } /// Produces 2D ui labels from component data. diff --git a/docs/content/reference/types/archetypes/ellipsoids.md b/docs/content/reference/types/archetypes/ellipsoids.md index c03c7d7ee13a..0f51d1fe8fd0 100644 --- a/docs/content/reference/types/archetypes/ellipsoids.md +++ b/docs/content/reference/types/archetypes/ellipsoids.md @@ -16,7 +16,7 @@ Opaque and transparent rendering will be supported later. **Required**: [`HalfSize3D`](../components/half_size3d.md) -**Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) +**Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`SolidColor`](../components/solid_color.md), [`Color`](../components/color.md) **Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) diff --git a/docs/content/reference/types/components/solid_color.md b/docs/content/reference/types/components/solid_color.md index 0b226d5540fb..6938b7f3be68 100644 --- a/docs/content/reference/types/components/solid_color.md +++ b/docs/content/reference/types/components/solid_color.md @@ -19,3 +19,6 @@ applied to the entire surface of the object (as opposed to the lines of a wirefr * šŸ¦€ [Rust API docs for `SolidColor`](https://docs.rs/rerun/latest/rerun/components/struct.SolidColor.html) +## Used by + +* [`Ellipsoids`](../archetypes/ellipsoids.md?speculative-link) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.rs b/docs/snippets/all/archetypes/ellipsoid_batch.rs index b6f02af7e1d0..67c03edbfca0 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.rs +++ b/docs/snippets/all/archetypes/ellipsoid_batch.rs @@ -24,12 +24,12 @@ fn main() -> Result<(), Box> { (0.15, 0.15, 0.15), ], ) - .with_colors([ - rerun::Color::from_rgb(255, 255, 255), - rerun::Color::from_rgb(255, 255, 255), - rerun::Color::from_rgb(255, 255, 255), - rerun::Color::from_rgb(0, 0, 0), - rerun::Color::from_rgb(0, 0, 0), + .with_solid_colors([ + rerun::SolidColor::from_rgb(255, 255, 255), + rerun::SolidColor::from_rgb(255, 255, 255), + rerun::SolidColor::from_rgb(255, 255, 255), + rerun::SolidColor::from_rgb(0, 0, 0), + rerun::SolidColor::from_rgb(0, 0, 0), ]), )?; diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp index 6baf7232881c..773a3a0fdfc6 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -31,8 +31,13 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } - if (archetype.colors.has_value()) { - auto result = DataCell::from_loggable(archetype.colors.value()); + if (archetype.solid_colors.has_value()) { + auto result = DataCell::from_loggable(archetype.solid_colors.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.line_colors.has_value()) { + auto result = DataCell::from_loggable(archetype.line_colors.value()); RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp index e22d516be6bd..ef097c73b3b8 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp @@ -11,6 +11,7 @@ #include "../components/position3d.hpp" #include "../components/radius.hpp" #include "../components/rotation3d.hpp" +#include "../components/solid_color.hpp" #include "../components/text.hpp" #include "../data_cell.hpp" #include "../indicator_component.hpp" @@ -46,8 +47,15 @@ namespace rerun::archetypes { /// If not specified, the axes of the ellipsoid align with the axes of the coordinate system. std::optional> rotations; - /// Optional colors for the ellipsoids. - std::optional> colors; + /// Optional colors for the ellipsoids' surfaces. + /// + /// This color may be transparent to render the ellipsoid as a wireframe alone. + std::optional> solid_colors; + + /// Optional colors for the ellipsoids' wireframe lines. + /// + /// This color may be transparent to render the ellipsoid as a colored surface alone. + std::optional> line_colors; /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. std::optional> line_radii; @@ -123,9 +131,20 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } - /// Optional colors for the ellipsoids. - Ellipsoids with_colors(Collection _colors) && { - colors = std::move(_colors); + /// Optional colors for the ellipsoids' surfaces. + /// + /// This color may be transparent to render the ellipsoid as a wireframe alone. + Ellipsoids with_solid_colors(Collection _solid_colors) && { + solid_colors = std::move(_solid_colors); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional colors for the ellipsoids' wireframe lines. + /// + /// This color may be transparent to render the ellipsoid as a colored surface alone. + Ellipsoids with_line_colors(Collection _line_colors) && { + line_colors = std::move(_line_colors); // See: https://github.com/rerun-io/rerun/issues/4027 RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py index 7a228344874d..09bb9b56923f 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py @@ -37,7 +37,8 @@ def __attrs_clear__(self) -> None: half_sizes=None, # type: ignore[arg-type] centers=None, # type: ignore[arg-type] rotations=None, # type: ignore[arg-type] - colors=None, # type: ignore[arg-type] + solid_colors=None, # type: ignore[arg-type] + line_colors=None, # type: ignore[arg-type] line_radii=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] @@ -82,12 +83,25 @@ def _clear(cls) -> Ellipsoids: # # (Docstring intentionally commented out to hide this field from the docs) - colors: components.ColorBatch | None = field( + solid_colors: components.SolidColorBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.SolidColorBatch._optional, # type: ignore[misc] + ) + # Optional colors for the ellipsoids' surfaces. + # + # This color may be transparent to render the ellipsoid as a wireframe alone. + # + # (Docstring intentionally commented out to hide this field from the docs) + + line_colors: components.ColorBatch | None = field( metadata={"component": "optional"}, default=None, converter=components.ColorBatch._optional, # type: ignore[misc] ) - # Optional colors for the ellipsoids. + # Optional colors for the ellipsoids' wireframe lines. + # + # This color may be transparent to render the ellipsoid as a colored surface alone. # # (Docstring intentionally commented out to hide this field from the docs) diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py index aa8a9e9b0da5..5f519e8d373e 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py @@ -18,7 +18,8 @@ def __init__( radii: datatypes.Float32ArrayLike | None = None, centers: datatypes.Vec3DArrayLike | None = None, rotations: datatypes.Rotation3DArrayLike | None = None, - colors: datatypes.Rgba32ArrayLike | None = None, + solid_colors: datatypes.Rgba32ArrayLike | None = None, + line_colors: datatypes.Rgba32ArrayLike | None = None, line_radii: datatypes.Float32ArrayLike | None = None, labels: datatypes.Utf8ArrayLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, @@ -38,8 +39,10 @@ def __init__( Optional center positions of the ellipsoids. rotations: Optional rotations of the ellipsoids. - colors: - Optional colors for the ellipsoids. + solid_colors: + Optional colors for the ellipsoids' surface. + line_colors: + Optional colors for the ellipsoids' wireframes. line_radii: Optional radii for the lines that make up the ellipsoids. labels: @@ -64,7 +67,8 @@ def __init__( half_sizes=half_sizes, centers=centers, rotations=rotations, - colors=colors, + solid_colors=solid_colors, + line_colors=line_colors, line_radii=line_radii, labels=labels, class_ids=class_ids, From 8a96a1cdb5d5d8e3c113d414d771ebd4eb2a6c99 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 14:57:59 -0700 Subject: [PATCH 05/20] Add `solid_colors` to `Boxes3D`. --- .../definitions/rerun/archetypes/boxes3d.fbs | 7 ++- .../store/re_types/src/archetypes/boxes3d.rs | 46 ++++++++++++++++--- .../reference/types/archetypes/boxes3d.md | 2 +- .../reference/types/components/solid_color.md | 7 +-- .../reference/types/datatypes/rgba32.md | 2 +- rerun_cpp/src/rerun/archetypes/boxes3d.cpp | 7 ++- rerun_cpp/src/rerun/archetypes/boxes3d.hpp | 15 +++++- .../rerun_sdk/rerun/archetypes/boxes3d.py | 12 ++++- 8 files changed, 81 insertions(+), 17 deletions(-) diff --git a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs index b1c42744a833..14227b43a215 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs @@ -26,8 +26,11 @@ table Boxes3D ( /// Optional rotations of the boxes. rotations: [rerun.components.Rotation3D] ("attr.rerun.component_recommended", nullable, order: 2100); - /// Optional colors for the boxes. - colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2200); + /// Optional colors for the boxes' surfaces. + solid_colors: [rerun.components.SolidColor] ("attr.rerun.component_recommended", nullable, order: 2200); + + /// Optional colors for the lines that make up the boxes. + colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2300); // --- Optional --- diff --git a/crates/store/re_types/src/archetypes/boxes3d.rs b/crates/store/re_types/src/archetypes/boxes3d.rs index 85aa7c59f1ed..86d48e6e43cc 100644 --- a/crates/store/re_types/src/archetypes/boxes3d.rs +++ b/crates/store/re_types/src/archetypes/boxes3d.rs @@ -70,7 +70,10 @@ pub struct Boxes3D { /// Optional rotations of the boxes. pub rotations: Option>, - /// Optional colors for the boxes. + /// Optional colors for the boxes' surfaces. + pub solid_colors: Option>, + + /// Optional colors for the lines that make up the boxes. pub colors: Option>, /// Optional radii for the lines that make up the boxes. @@ -94,6 +97,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { self.half_sizes.heap_size_bytes() + self.centers.heap_size_bytes() + self.rotations.heap_size_bytes() + + self.solid_colors.heap_size_bytes() + self.colors.heap_size_bytes() + self.radii.heap_size_bytes() + self.labels.heap_size_bytes() @@ -105,6 +109,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { >::is_pod() && >>::is_pod() && >>::is_pod() + && >>::is_pod() && >>::is_pod() && >>::is_pod() && >>::is_pod() @@ -115,11 +120,12 @@ impl ::re_types_core::SizeBytes for Boxes3D { static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = once_cell::sync::Lazy::new(|| ["rerun.components.HalfSize3D".into()]); -static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = +static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 5usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Position3D".into(), "rerun.components.Rotation3D".into(), + "rerun.components.SolidColor".into(), "rerun.components.Color".into(), "rerun.components.Boxes3DIndicator".into(), ] @@ -134,12 +140,13 @@ static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), "rerun.components.Position3D".into(), "rerun.components.Rotation3D".into(), + "rerun.components.SolidColor".into(), "rerun.components.Color".into(), "rerun.components.Boxes3DIndicator".into(), "rerun.components.Radius".into(), @@ -149,8 +156,8 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = }); impl Boxes3D { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 5 recommended, 3 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Boxes3D`] [`::re_types_core::Archetype`] @@ -241,6 +248,18 @@ impl ::re_types_core::Archetype for Boxes3D { } else { None }; + let solid_colors = if let Some(array) = arrays_by_name.get("rerun.components.SolidColor") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Boxes3D#solid_colors")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Boxes3D#solid_colors")? + }) + } else { + None + }; let colors = if let Some(array) = arrays_by_name.get("rerun.components.Color") { Some({ ::from_arrow_opt(&**array) @@ -293,6 +312,7 @@ impl ::re_types_core::Archetype for Boxes3D { half_sizes, centers, rotations, + solid_colors, colors, radii, labels, @@ -314,6 +334,9 @@ impl ::re_types_core::AsComponents for Boxes3D { self.rotations .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.solid_colors + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), self.colors .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -343,6 +366,7 @@ impl Boxes3D { half_sizes: half_sizes.into_iter().map(Into::into).collect(), centers: None, rotations: None, + solid_colors: None, colors: None, radii: None, labels: None, @@ -370,7 +394,17 @@ impl Boxes3D { self } - /// Optional colors for the boxes. + /// Optional colors for the boxes' surfaces. + #[inline] + pub fn with_solid_colors( + mut self, + solid_colors: impl IntoIterator>, + ) -> Self { + self.solid_colors = Some(solid_colors.into_iter().map(Into::into).collect()); + self + } + + /// Optional colors for the lines that make up the boxes. #[inline] pub fn with_colors( mut self, diff --git a/docs/content/reference/types/archetypes/boxes3d.md b/docs/content/reference/types/archetypes/boxes3d.md index 60c997849804..7ecfdbcd1d6a 100644 --- a/docs/content/reference/types/archetypes/boxes3d.md +++ b/docs/content/reference/types/archetypes/boxes3d.md @@ -9,7 +9,7 @@ title: "Boxes3D" **Required**: [`HalfSize3D`](../components/half_size3d.md) -**Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) +**Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`SolidColor`](../components/solid_color.md), [`Color`](../components/color.md) **Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) diff --git a/docs/content/reference/types/components/solid_color.md b/docs/content/reference/types/components/solid_color.md index 6938b7f3be68..583fb0ffa92a 100644 --- a/docs/content/reference/types/components/solid_color.md +++ b/docs/content/reference/types/components/solid_color.md @@ -14,11 +14,12 @@ applied to the entire surface of the object (as opposed to the lines of a wirefr * rgba: [`Rgba32`](../datatypes/rgba32.md) ## API reference links - * šŸŒŠ [C++ API docs for `SolidColor`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1SolidColor.html) - * šŸ [Python API docs for `SolidColor`](https://ref.rerun.io/docs/python/stable/common/components#rerun.components.SolidColor) - * šŸ¦€ [Rust API docs for `SolidColor`](https://docs.rs/rerun/latest/rerun/components/struct.SolidColor.html) + * šŸŒŠ [C++ API docs for `SolidColor`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1SolidColor.html?speculative-link) + * šŸ [Python API docs for `SolidColor`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.SolidColor) + * šŸ¦€ [Rust API docs for `SolidColor`](https://docs.rs/rerun/latest/rerun/components/struct.SolidColor.html?speculative-link) ## Used by +* [`Boxes3D`](../archetypes/boxes3d.md) * [`Ellipsoids`](../archetypes/ellipsoids.md?speculative-link) diff --git a/docs/content/reference/types/datatypes/rgba32.md b/docs/content/reference/types/datatypes/rgba32.md index 1d083b2d7b7a..8f18d6ad3196 100644 --- a/docs/content/reference/types/datatypes/rgba32.md +++ b/docs/content/reference/types/datatypes/rgba32.md @@ -23,4 +23,4 @@ byte is `R` and the least significant byte is `A`. * [`AlbedoFactor`](../components/albedo_factor.md?speculative-link) * [`AnnotationInfo`](../datatypes/annotation_info.md) * [`Color`](../components/color.md) -* [`SolidColor`](../components/solid_color.md) +* [`SolidColor`](../components/solid_color.md?speculative-link) diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp index 6e4ec3433d85..126dec06a7df 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -31,6 +31,11 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.solid_colors.has_value()) { + auto result = DataCell::from_loggable(archetype.solid_colors.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.colors.has_value()) { auto result = DataCell::from_loggable(archetype.colors.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 948912acca3d..4899b3ba4855 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -11,6 +11,7 @@ #include "../components/position3d.hpp" #include "../components/radius.hpp" #include "../components/rotation3d.hpp" +#include "../components/solid_color.hpp" #include "../components/text.hpp" #include "../data_cell.hpp" #include "../indicator_component.hpp" @@ -69,7 +70,10 @@ namespace rerun::archetypes { /// Optional rotations of the boxes. std::optional> rotations; - /// Optional colors for the boxes. + /// Optional colors for the boxes' surfaces. + std::optional> solid_colors; + + /// Optional colors for the lines that make up the boxes. std::optional> colors; /// Optional radii for the lines that make up the boxes. @@ -162,7 +166,14 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } - /// Optional colors for the boxes. + /// Optional colors for the boxes' surfaces. + Boxes3D with_solid_colors(Collection _solid_colors) && { + solid_colors = std::move(_solid_colors); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Optional colors for the lines that make up the boxes. Boxes3D with_colors(Collection _colors) && { colors = std::move(_colors); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index 1989506ee9fb..b5ee2b7f7118 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -66,6 +66,7 @@ def __attrs_clear__(self) -> None: half_sizes=None, # type: ignore[arg-type] centers=None, # type: ignore[arg-type] rotations=None, # type: ignore[arg-type] + solid_colors=None, # type: ignore[arg-type] colors=None, # type: ignore[arg-type] radii=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] @@ -105,12 +106,21 @@ def _clear(cls) -> Boxes3D: # # (Docstring intentionally commented out to hide this field from the docs) + solid_colors: components.SolidColorBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.SolidColorBatch._optional, # type: ignore[misc] + ) + # Optional colors for the boxes' surfaces. + # + # (Docstring intentionally commented out to hide this field from the docs) + colors: components.ColorBatch | None = field( metadata={"component": "optional"}, default=None, converter=components.ColorBatch._optional, # type: ignore[misc] ) - # Optional colors for the boxes. + # Optional colors for the lines that make up the boxes. # # (Docstring intentionally commented out to hide this field from the docs) From 3a185a7f25ef03a4c8e763a9ee358bdcf98f5f51 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 16:10:20 -0700 Subject: [PATCH 06/20] Add `ProcMeshKey::Cube`. --- .../re_space_view_spatial/src/proc_mesh.rs | 121 ++++++++++++++++-- 1 file changed, 110 insertions(+), 11 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index 04ae861d0da6..2b4be4e303a4 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use ahash::HashSet; -use glam::Vec3; +use glam::{uvec3, vec3, Vec3}; use itertools::Itertools as _; use smallvec::smallvec; @@ -21,6 +21,9 @@ use re_viewer_context::Cache; /// Obtain the actual mesh by passing this to [`WireframeCache`] or [`SolidCache`]. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ProcMeshKey { + /// A unit cube, centered; its bounds are Ā±0.5. + Cube, + /// A sphere of unit radius. /// /// The resulting mesh may be scaled to represent spheres and ellipsoids @@ -37,6 +40,9 @@ impl ProcMeshKey { // sphereā€™s radius is 1, so its size is 2 re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)) } + Self::Cube => { + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(1.0)) + } } } } @@ -122,6 +128,44 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram _ = render_ctx; match *key { + ProcMeshKey::Cube => { + let corners = [ + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, 0.5), + vec3(0.5, -0.5, -0.5), + vec3(0.5, -0.5, 0.5), + vec3(0.5, 0.5, -0.5), + vec3(0.5, 0.5, 0.5), + ]; + let line_strips: Vec> = vec![ + // bottom: + vec![ + // bottom loop + corners[0b000], + corners[0b001], + corners[0b011], + corners[0b010], + corners[0b000], + // joined to top loop + corners[0b100], + corners[0b101], + corners[0b111], + corners[0b110], + corners[0b100], + ], + // remaining side edges + vec![corners[0b001], corners[0b101]], + vec![corners[0b010], corners[0b110]], + vec![corners[0b011], corners[0b111]], + ]; + WireframeMesh { + bbox: key.simple_bounding_box(), + vertex_count: line_strips.iter().map(|v| v.len()).sum(), + line_strips, + } + } ProcMeshKey::Sphere { subdivisions } => { let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); @@ -226,10 +270,58 @@ fn generate_solid( re_tracing::profile_function!(); let mesh: mesh::Mesh = match *key { + ProcMeshKey::Cube => { + let (vertex_positions, vertex_normals): (Vec, Vec) = [ + (Vec3::X, Vec3::Y), + (Vec3::Y, Vec3::Z), + (Vec3::Z, Vec3::X), + (Vec3::NEG_X, Vec3::NEG_Y), + (Vec3::NEG_Y, Vec3::NEG_Z), + (Vec3::NEG_Z, Vec3::NEG_X), + ] + .into_iter() + .flat_map(|(normal, tangent)| { + // pos2 is always 90Ā° counterclockwise from pos1. + let pos1 = tangent * 0.5; + let pos2 = normal.cross(pos1); + + // Generate vertices of the face. + // Must be in "S" (not "Z" or circular) order to match the index list. + [-pos1 - pos2, -pos1 + pos2, pos1 - pos2, pos1 + pos2] + .into_iter() + .map(move |pos| (pos + normal * 0.5, normal)) + }) + .unzip(); + + let num_vertices = 24; + debug_assert_eq!(num_vertices, vertex_positions.len()); + + let triangle_indices: Vec = (0..6) + .flat_map(|i| { + let face_first_ix = i * 4; + [ + uvec3(face_first_ix, face_first_ix + 1, face_first_ix + 2), + uvec3(face_first_ix + 1, face_first_ix + 2, face_first_ix + 3), + ] + }) + .collect(); + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); + + mesh::Mesh { + label: format!("{key:?}").into(), + materials, + triangle_indices, + vertex_positions, + vertex_normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + } + } ProcMeshKey::Sphere { subdivisions } => { let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); - let vertex_positions: Vec = + let vertex_positions: Vec = subdiv.raw_points().iter().map(|&p| p.into()).collect(); // A unit sphere's normals are its positions. let vertex_normals = vertex_positions.clone(); @@ -242,15 +334,7 @@ fn generate_solid( .map(|(i1, i2, i3)| glam::uvec3(i1, i2, i3)) .collect(); - let materials = smallvec![mesh::Material { - label: "default material".into(), - index_range: 0..(triangle_indices.len() * 3) as u32, - albedo: render_ctx - .texture_manager_2d - .white_texture_unorm_handle() - .clone(), - albedo_factor: re_renderer::Rgba::BLACK, - }]; + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); mesh::Mesh { label: format!("{key:?}").into(), @@ -282,3 +366,18 @@ fn generate_solid( gpu_mesh, }) } + +fn materials_for_uncolored_mesh( + render_ctx: &RenderContext, + num_triangles: usize, +) -> smallvec::SmallVec<[mesh::Material; 1]> { + smallvec![mesh::Material { + label: "default material".into(), + index_range: 0..(num_triangles * 3) as u32, + albedo: render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone(), + albedo_factor: re_renderer::Rgba::BLACK, + }] +} From da3cb0565497663a676cedcd7640e05920c93d96 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 15:01:28 -0700 Subject: [PATCH 07/20] Add solid color support to `Boxes3DVisualizer`. --- .../src/visualizers/boxes3d.rs | 188 ++++++++++++++---- 1 file changed, 147 insertions(+), 41 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index bbcdd7ea2464..e7ea60bc39f4 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -1,9 +1,14 @@ +use egui::Color32; +use re_entity_db::InstancePathHash; use re_log_types::Instance; -use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Boxes3D, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, SolidColor, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -12,7 +17,11 @@ use re_viewer_context::{ VisualizerQueryInfo, VisualizerSystem, }; -use crate::{contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind}; +use crate::{ + contexts::SpatialSceneEntityContext, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, proc_mesh, + view_kind::SpatialSpaceViewKind, +}; use super::{ entity_iterator::clamped_or, filter_visualizable_3d_entities, @@ -35,14 +44,17 @@ impl Default for Boxes3DVisualizer { // NOTE: Do not put profile scopes in these methods. They are called for all entities and all // timestamps within a time range -- it's _a lot_. impl Boxes3DVisualizer { + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, - ) { + render_ctx: &RenderContext, + ) -> Result<(), SpaceViewSystemExecutionError> { let entity_path = ctx.target_entity_path; for data in data { @@ -64,8 +76,20 @@ impl Boxes3DVisualizer { // TODO(andreas): It would be nice to have this handle this fallback as part of the query. let radii = process_radius_slice(entity_path, num_instances, data.radii, Radius::default()); - let colors = - process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); + let surface_colors = process_color_slice( + ctx, + self, + num_instances, + &annotation_infos, + data.solid_colors, + ); + let line_colors = process_color_slice( + ctx, + self, + num_instances, + &annotation_infos, + data.line_colors, + ); let mut line_batch = line_builder .batch("boxes3d") @@ -78,32 +102,67 @@ impl Boxes3DVisualizer { let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); - for (i, (half_size, ¢er, rotation, radius, &color)) in - itertools::izip!(data.half_sizes, centers, rotations, radii, &colors).enumerate() + for ( + instance_index, + (half_size, ¢er, rotation, radius, &surface_color, &line_color), + ) in itertools::izip!( + data.half_sizes, + centers, + rotations, + radii, + &surface_colors, + &line_colors + ) + .enumerate() { + let instance = Instance::from(instance_index as u64); + // Transform from a centered unit cube to this box. + let transform = glam::Affine3A::from_scale_rotation_translation( + glam::Vec3::from(*half_size) * 2.0, + rotation.0.into(), + center.into(), + ); + obj_space_bounding_box.extend(half_size.box_min(center)); obj_space_bounding_box.extend(half_size.box_max(center)); - let center = center.into(); + if line_color != Color32::TRANSPARENT { + let box3d = line_batch + .add_box_outline_from_transform(transform) + .color(line_color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + if let Some(outline_mask_ids) = ent_context.highlight.instances.get(&instance) { + box3d.outline_mask_ids(*outline_mask_ids); + } + } - let box3d = line_batch - .add_box_outline_from_transform( - glam::Affine3A::from_scale_rotation_translation( - glam::Vec3::from(*half_size) * 2.0, - rotation.0.into(), - center, + if surface_color != Color32::TRANSPARENT { + let proc_mesh_key = proc_mesh::ProcMeshKey::Cube; + let Some(solid_mesh) = ctx + .viewer_ctx + .cache + .entry(|c: &mut proc_mesh::SolidCache| c.entry(proc_mesh_key, render_ctx)) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + obj_space_bounding_box = + obj_space_bounding_box.union(solid_mesh.bbox.transform_affine3(&transform)); + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: transform, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), ), - ) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + additive_tint: surface_color, + }); } } @@ -129,13 +188,15 @@ impl Boxes3DVisualizer { entity_path, label_positions, data.labels, - &colors, - &colors, + &line_colors, + &surface_colors, &annotation_infos, ent_context.world_from_entity, )); } } + + Ok(()) } } @@ -148,7 +209,8 @@ struct Boxes3DComponentData<'a> { // Clamped to edge centers: &'a [Position3D], rotations: &'a [Rotation3D], - colors: &'a [Color], + solid_colors: &'a [SolidColor], + line_colors: &'a [Color], radii: &'a [Radius], labels: &'a [Text], keypoint_ids: &'a [KeypointId], @@ -188,6 +250,13 @@ impl VisualizerSystem for Boxes3DVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + // + // Why do we draw solid surfaces using instanced meshes and wireframes using instances? + // No good reason; only, those are the types of renderers that have been implemented so far. + // This code should be revisited with an eye on performance. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -211,23 +280,27 @@ impl VisualizerSystem for Boxes3DVisualizer { return Ok(()); } - // Each box consists of 12 independent lines with 2 vertices each. - line_builder.reserve_strips(num_boxes * 12)?; - line_builder.reserve_vertices(num_boxes * 12 * 2)?; - let centers = results.get_or_empty_dense(resolver)?; let rotations = results.get_or_empty_dense(resolver)?; - let colors = results.get_or_empty_dense(resolver)?; + let solid_colors = results.get_or_empty_dense(resolver)?; + let line_colors = results.get_or_empty_dense(resolver)?; let radii = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + // Each box consists of 12 independent lines with 2 vertices each. + // TODO(kpreid): This is an over-estimate if some or all of the boxes don't have + // lines. + line_builder.reserve_strips(num_boxes * 12)?; + line_builder.reserve_vertices(num_boxes * 12 * 2)?; + + let data = re_query::range_zip_1x8( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), - colors.range_indexed(), + solid_colors.range_indexed(), + line_colors.range_indexed(), radii.range_indexed(), labels.range_indexed(), class_ids.range_indexed(), @@ -239,7 +312,8 @@ impl VisualizerSystem for Boxes3DVisualizer { half_sizes, centers, rotations, - colors, + solid_colors, + line_colors, radii, labels, class_ids, @@ -249,7 +323,8 @@ impl VisualizerSystem for Boxes3DVisualizer { half_sizes, centers: centers.unwrap_or_default(), rotations: rotations.unwrap_or_default(), - colors: colors.unwrap_or_default(), + solid_colors: solid_colors.unwrap_or_default(), + line_colors: line_colors.unwrap_or_default(), radii: radii.unwrap_or_default(), labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), @@ -258,13 +333,38 @@ impl VisualizerSystem for Boxes3DVisualizer { }, ); - self.process_data(ctx, &mut line_builder, view_query, spatial_ctx, data); + self.process_data( + ctx, + &mut line_builder, + &mut solid_instances, + view_query, + spatial_ctx, + data, + render_ctx, + )?; Ok(()) }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { @@ -285,5 +385,11 @@ impl TypedComponentFallbackProvider for Boxes3DVisualizer { auto_color_for_entity_path(ctx.target_entity_path) } } +impl TypedComponentFallbackProvider for Boxes3DVisualizer { + fn fallback_for(&self, _: &QueryContext<'_>) -> SolidColor { + // By default, use wireframe visualization only + SolidColor::TRANSPARENT + } +} re_viewer_context::impl_component_fallback_provider!(Boxes3DVisualizer => [Color]); From f5ac705cf10a4edf0f2482c07882b06459bebb9a Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 16:37:03 -0700 Subject: [PATCH 08/20] Change `box3d_batch` example to use solid colors. --- crates/store/re_types/src/archetypes/boxes3d.rs | 9 +++++---- docs/snippets/all/archetypes/box3d_batch.cpp | 3 ++- docs/snippets/all/archetypes/box3d_batch.py | 3 ++- docs/snippets/all/archetypes/box3d_batch.rs | 9 +++++---- rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/crates/store/re_types/src/archetypes/boxes3d.rs b/crates/store/re_types/src/archetypes/boxes3d.rs index 86d48e6e43cc..f07b065359b8 100644 --- a/crates/store/re_types/src/archetypes/boxes3d.rs +++ b/crates/store/re_types/src/archetypes/boxes3d.rs @@ -39,10 +39,11 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// rerun::RotationAxisAngle::new((0.0, 1.0, 0.0), rerun::Angle::from_degrees(30.0)).into(), /// ]) /// .with_radii([0.025]) -/// .with_colors([ -/// rerun::Color::from_rgb(255, 0, 0), -/// rerun::Color::from_rgb(0, 255, 0), -/// rerun::Color::from_rgb(0, 0, 255), +/// .with_colors([rerun::Color::WHITE]) +/// .with_solid_colors([ +/// rerun::SolidColor::from_rgb(255, 0, 0), +/// rerun::SolidColor::from_rgb(0, 255, 0), +/// rerun::SolidColor::from_rgb(0, 0, 255), /// ]) /// .with_labels(["red", "green", "blue"]), /// )?; diff --git a/docs/snippets/all/archetypes/box3d_batch.cpp b/docs/snippets/all/archetypes/box3d_batch.cpp index 1bb32e2ef276..7adab893c097 100644 --- a/docs/snippets/all/archetypes/box3d_batch.cpp +++ b/docs/snippets/all/archetypes/box3d_batch.cpp @@ -19,7 +19,8 @@ int main() { rerun::RotationAxisAngle({0.0f, 1.0f, 0.0f}, rerun::Angle::degrees(30.0f)), }) .with_radii({0.025f}) - .with_colors({ + .with_colors({rerun::Rgba32(255, 255, 255)}) + .with_solid_colors({ rerun::Rgba32(255, 0, 0), rerun::Rgba32(0, 255, 0), rerun::Rgba32(0, 0, 255), diff --git a/docs/snippets/all/archetypes/box3d_batch.py b/docs/snippets/all/archetypes/box3d_batch.py index 272d895b371a..5d11fae33c7f 100644 --- a/docs/snippets/all/archetypes/box3d_batch.py +++ b/docs/snippets/all/archetypes/box3d_batch.py @@ -16,7 +16,8 @@ RotationAxisAngle(axis=[0, 1, 0], angle=Angle(deg=30)), ], radii=0.025, - colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + colors=(255, 255, 255), + solid_colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], labels=["red", "green", "blue"], ), ) diff --git a/docs/snippets/all/archetypes/box3d_batch.rs b/docs/snippets/all/archetypes/box3d_batch.rs index 4bd135abcf1a..ab294d280186 100644 --- a/docs/snippets/all/archetypes/box3d_batch.rs +++ b/docs/snippets/all/archetypes/box3d_batch.rs @@ -15,10 +15,11 @@ fn main() -> Result<(), Box> { rerun::RotationAxisAngle::new((0.0, 1.0, 0.0), rerun::Angle::from_degrees(30.0)).into(), ]) .with_radii([0.025]) - .with_colors([ - rerun::Color::from_rgb(255, 0, 0), - rerun::Color::from_rgb(0, 255, 0), - rerun::Color::from_rgb(0, 0, 255), + .with_colors([rerun::Color::WHITE]) + .with_solid_colors([ + rerun::SolidColor::from_rgb(255, 0, 0), + rerun::SolidColor::from_rgb(0, 255, 0), + rerun::SolidColor::from_rgb(0, 0, 255), ]) .with_labels(["red", "green", "blue"]), )?; diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index b5ee2b7f7118..6f6a1e2edd48 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -41,7 +41,8 @@ class Boxes3D(Boxes3DExt, Archetype): RotationAxisAngle(axis=[0, 1, 0], angle=Angle(deg=30)), ], radii=0.025, - colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + colors=(255, 255, 255), + solid_colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], labels=["red", "green", "blue"], ), ) From 32a35177f6f3f1822f37e26d1a38f27178c96816 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sat, 20 Jul 2024 17:32:40 -0700 Subject: [PATCH 09/20] Don't use annotation info to set solid colors (for now). --- .../re_space_view_spatial/src/visualizers/boxes3d.rs | 10 ++++++---- .../src/visualizers/ellipsoids.rs | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index e7ea60bc39f4..e695385b7a95 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -12,9 +12,9 @@ use re_types::{ }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, - SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, - ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, - VisualizerQueryInfo, VisualizerSystem, + ResolvedAnnotationInfo, ResolvedAnnotationInfos, SpaceViewSystemExecutionError, + TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery, + VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, }; use crate::{ @@ -80,7 +80,9 @@ impl Boxes3DVisualizer { ctx, self, num_instances, - &annotation_infos, + // Don't use annotation-sourced colors for surface (only wireframe). + // TODO(kpreid): how *should* the annotation context interact with surface colors? + &ResolvedAnnotationInfos::Same(num_instances, ResolvedAnnotationInfo::default()), data.solid_colors, ); let line_colors = process_color_slice( diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index 71ad37df351b..66ebf12bfb83 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -12,9 +12,9 @@ use re_types::{ }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, - SpaceViewSystemExecutionError, TypedComponentFallbackProvider, ViewContext, - ViewContextCollection, ViewQuery, VisualizableEntities, VisualizableFilterContext, - VisualizerQueryInfo, VisualizerSystem, + ResolvedAnnotationInfo, ResolvedAnnotationInfos, SpaceViewSystemExecutionError, + TypedComponentFallbackProvider, ViewContext, ViewContextCollection, ViewQuery, + VisualizableEntities, VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, }; use crate::{ @@ -84,7 +84,9 @@ impl EllipsoidsVisualizer { ctx, self, num_instances, - &annotation_infos, + // Don't use annotation-sourced colors for surface (only wireframe). + // TODO(kpreid): how *should* the annotation context interact with surface colors? + &ResolvedAnnotationInfos::Same(num_instances, ResolvedAnnotationInfo::default()), data.solid_colors, ); let line_colors = process_color_slice( From 355e2bb565212abbb1a29769fcc7eb9b9d19e352 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sun, 21 Jul 2024 21:09:49 -0700 Subject: [PATCH 10/20] Update archetype test and snippets for new components. --- crates/store/re_types/tests/box3d.rs | 6 ++++++ docs/snippets/all/archetypes/ellipsoid_batch.cpp | 2 +- docs/snippets/all/archetypes/ellipsoid_batch.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/store/re_types/tests/box3d.rs b/crates/store/re_types/tests/box3d.rs index bfc3e33c33fd..e47e9f316cdb 100644 --- a/crates/store/re_types/tests/box3d.rs +++ b/crates/store/re_types/tests/box3d.rs @@ -24,6 +24,10 @@ fn roundtrip() { components::Color::from_unmultiplied_rgba(0xAA, 0x00, 0x00, 0xCC), // components::Color::from_unmultiplied_rgba(0x00, 0xBB, 0x00, 0xDD), ]), + solid_colors: Some(vec![ + components::SolidColor::from_unmultiplied_rgba(0x11, 0x00, 0x00, 0x33), // + components::SolidColor::from_unmultiplied_rgba(0x00, 0x22, 0x00, 0x44), + ]), radii: Some(vec![ components::Radius::from(42.0), // components::Radius::from(43.0), @@ -48,6 +52,7 @@ fn roundtrip() { )), ]) .with_colors([0xAA0000CC, 0x00BB00DD]) + .with_solid_colors([0x11000033, 0x00220044]) .with_radii([42.0, 43.0]) .with_labels(["hello", "friend"]) .with_class_ids([126, 127]); @@ -57,6 +62,7 @@ fn roundtrip() { ("half_sizes", vec!["rerun.components.HalfSize2D"]), ("centers", vec!["rerun.components.Position2D"]), ("colors", vec!["rerun.components.Color"]), + ("solid_colors", vec!["rerun.components.SolidColor"]), ("radii", vec!["rerun.components.Radius"]), ("labels", vec!["rerun.components.Label"]), ("draw_order", vec!["rerun.components.DrawOrder"]), diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.cpp b/docs/snippets/all/archetypes/ellipsoid_batch.cpp index dd0340520810..c79bca15ec4f 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.cpp +++ b/docs/snippets/all/archetypes/ellipsoid_batch.cpp @@ -27,7 +27,7 @@ int main() { {0.15f, 0.15f, 0.15f}, } ) - .with_colors({ + .with_solid_colors({ rerun::Rgba32(255, 255, 255), rerun::Rgba32(255, 255, 255), rerun::Rgba32(255, 255, 255), diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.py b/docs/snippets/all/archetypes/ellipsoid_batch.py index b8c1aa3db06b..a71f938e841e 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.py +++ b/docs/snippets/all/archetypes/ellipsoid_batch.py @@ -24,7 +24,7 @@ [0.15, 0.15, 0.15], [0.15, 0.15, 0.15], ], - colors=[ + solid_colors=[ (255, 255, 255), (255, 255, 255), (255, 255, 255), From 9b2b1d2bb70c70f99b7edfe5cb0e580633df1b2f Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Sun, 21 Jul 2024 21:30:19 -0700 Subject: [PATCH 11/20] Add newline for lint --- crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index e695385b7a95..d31935a8eb21 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -387,6 +387,7 @@ impl TypedComponentFallbackProvider for Boxes3DVisualizer { auto_color_for_entity_path(ctx.target_entity_path) } } + impl TypedComponentFallbackProvider for Boxes3DVisualizer { fn fallback_for(&self, _: &QueryContext<'_>) -> SolidColor { // By default, use wireframe visualization only From 1927ae355c652a3d7450e44e70c708c5484f6bea Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 23 Jul 2024 14:39:02 -0700 Subject: [PATCH 12/20] Add `FillMode` component, for `Boxes3D` and `Ellipsoids`. --- .../definitions/rerun/archetypes/boxes3d.fbs | 7 +- .../rerun/archetypes/ellipsoids.fbs | 7 +- .../re_types/definitions/rerun/components.fbs | 1 + .../rerun/components/fill_mode.fbs | 16 ++ .../store/re_types/src/archetypes/boxes3d.rs | 36 +++- .../re_types/src/archetypes/ellipsoids.rs | 36 +++- .../re_types/src/components/.gitattributes | 1 + .../re_types/src/components/fill_mode.rs | 171 ++++++++++++++++++ crates/store/re_types/src/components/mod.rs | 2 + crates/store/re_types/tests/box3d.rs | 3 + crates/top/rerun/src/sdk.rs | 2 +- crates/viewer/re_viewer/src/reflection/mod.rs | 7 + .../reference/types/archetypes/boxes3d.md | 2 +- .../reference/types/archetypes/ellipsoids.md | 2 +- docs/content/reference/types/components.md | 1 + .../reference/types/components/.gitattributes | 1 + .../reference/types/components/fill_mode.md | 22 +++ rerun_cpp/src/rerun/archetypes/boxes3d.cpp | 7 +- rerun_cpp/src/rerun/archetypes/boxes3d.hpp | 11 ++ rerun_cpp/src/rerun/archetypes/ellipsoids.cpp | 7 +- rerun_cpp/src/rerun/archetypes/ellipsoids.hpp | 11 ++ rerun_cpp/src/rerun/components.hpp | 1 + rerun_cpp/src/rerun/components/.gitattributes | 2 + rerun_cpp/src/rerun/components/fill_mode.cpp | 61 +++++++ rerun_cpp/src/rerun/components/fill_mode.hpp | 56 ++++++ .../rerun_sdk/rerun/archetypes/boxes3d.py | 10 + .../rerun_sdk/rerun/archetypes/boxes3d_ext.py | 6 +- .../rerun_sdk/rerun/archetypes/ellipsoids.py | 10 + .../rerun/archetypes/ellipsoids_ext.py | 6 +- .../rerun_sdk/rerun/components/.gitattributes | 1 + .../rerun_sdk/rerun/components/__init__.py | 6 + .../rerun_sdk/rerun/components/fill_mode.py | 101 +++++++++++ 32 files changed, 594 insertions(+), 19 deletions(-) create mode 100644 crates/store/re_types/definitions/rerun/components/fill_mode.fbs create mode 100644 crates/store/re_types/src/components/fill_mode.rs create mode 100644 docs/content/reference/types/components/fill_mode.md create mode 100644 rerun_cpp/src/rerun/components/fill_mode.cpp create mode 100644 rerun_cpp/src/rerun/components/fill_mode.hpp create mode 100644 rerun_py/rerun_sdk/rerun/components/fill_mode.py diff --git a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs index b1c42744a833..97b430877b37 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/boxes3d.fbs @@ -34,14 +34,17 @@ table Boxes3D ( /// Optional radii for the lines that make up the boxes. radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); + /// Optionally choose whether the boxes are drawn with lines or solid. + fill_mode: rerun.components.FillMode ("attr.rerun.component_optional", nullable, order: 3100); + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. /// Otherwise, each instance will have its own label. - labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3100); + labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200); /// Optional [components.ClassId]s for the boxes. /// /// The [components.ClassId] provides colors and labels if not specified explicitly. - class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200); + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3300); } diff --git a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs index b051bea57430..5e1e7b5a990f 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/ellipsoids.fbs @@ -47,11 +47,14 @@ table Ellipsoids ( /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. line_radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + fill_mode: rerun.components.FillMode ("attr.rerun.component_optional", nullable, order: 3100); + /// Optional text labels for the ellipsoids. - labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3100); + labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200); /// Optional `ClassId`s for the ellipsoids. /// /// The class ID provides colors and labels if not specified explicitly. - class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3200); + class_ids: [rerun.components.ClassId] ("attr.rerun.component_optional", nullable, order: 3300); } diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 331527356a79..06093a49364f 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -14,6 +14,7 @@ include "./components/colormap.fbs"; include "./components/depth_meter.fbs"; include "./components/disconnected_space.fbs"; include "./components/draw_order.fbs"; +include "./components/fill_mode.fbs"; include "./components/fill_ratio.fbs"; include "./components/gamma_correction.fbs"; include "./components/half_size2d.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/fill_mode.fbs b/crates/store/re_types/definitions/rerun/components/fill_mode.fbs new file mode 100644 index 000000000000..08642668ef25 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/fill_mode.fbs @@ -0,0 +1,16 @@ +namespace rerun.components; + +/// How a geometric shape is drawn and colored. +enum FillMode: byte( + "attr.docs.unreleased" +) { + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + Wireframe (default), + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid, +} diff --git a/crates/store/re_types/src/archetypes/boxes3d.rs b/crates/store/re_types/src/archetypes/boxes3d.rs index 85aa7c59f1ed..ae315fc10e78 100644 --- a/crates/store/re_types/src/archetypes/boxes3d.rs +++ b/crates/store/re_types/src/archetypes/boxes3d.rs @@ -76,6 +76,9 @@ pub struct Boxes3D { /// Optional radii for the lines that make up the boxes. pub radii: Option>, + /// Optionally choose whether the boxes are drawn with lines or solid. + pub fill_mode: Option, + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. @@ -96,6 +99,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { + self.rotations.heap_size_bytes() + self.colors.heap_size_bytes() + self.radii.heap_size_bytes() + + self.fill_mode.heap_size_bytes() + self.labels.heap_size_bytes() + self.class_ids.heap_size_bytes() } @@ -107,6 +111,7 @@ impl ::re_types_core::SizeBytes for Boxes3D { && >>::is_pod() && >>::is_pod() && >>::is_pod() + && >::is_pod() && >>::is_pod() && >>::is_pod() } @@ -125,16 +130,17 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), @@ -143,14 +149,15 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = "rerun.components.Color".into(), "rerun.components.Boxes3DIndicator".into(), "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); impl Boxes3D { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 4 recommended, 4 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Boxes3D`] [`::re_types_core::Archetype`] @@ -265,6 +272,15 @@ impl ::re_types_core::Archetype for Boxes3D { } else { None }; + let fill_mode = if let Some(array) = arrays_by_name.get("rerun.components.FillMode") { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Boxes3D#fill_mode")? + .into_iter() + .next() + .flatten() + } else { + None + }; let labels = if let Some(array) = arrays_by_name.get("rerun.components.Text") { Some({ ::from_arrow_opt(&**array) @@ -295,6 +311,7 @@ impl ::re_types_core::Archetype for Boxes3D { rotations, colors, radii, + fill_mode, labels, class_ids, }) @@ -320,6 +337,9 @@ impl ::re_types_core::AsComponents for Boxes3D { self.radii .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.fill_mode + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), self.labels .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -345,6 +365,7 @@ impl Boxes3D { rotations: None, colors: None, radii: None, + fill_mode: None, labels: None, class_ids: None, } @@ -390,6 +411,13 @@ impl Boxes3D { self } + /// Optionally choose whether the boxes are drawn with lines or solid. + #[inline] + pub fn with_fill_mode(mut self, fill_mode: impl Into) -> Self { + self.fill_mode = Some(fill_mode.into()); + self + } + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. diff --git a/crates/store/re_types/src/archetypes/ellipsoids.rs b/crates/store/re_types/src/archetypes/ellipsoids.rs index 8f3fc89f3920..f0842e113556 100644 --- a/crates/store/re_types/src/archetypes/ellipsoids.rs +++ b/crates/store/re_types/src/archetypes/ellipsoids.rs @@ -49,6 +49,9 @@ pub struct Ellipsoids { /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. pub line_radii: Option>, + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + pub fill_mode: Option, + /// Optional text labels for the ellipsoids. pub labels: Option>, @@ -66,6 +69,7 @@ impl ::re_types_core::SizeBytes for Ellipsoids { + self.rotations.heap_size_bytes() + self.colors.heap_size_bytes() + self.line_radii.heap_size_bytes() + + self.fill_mode.heap_size_bytes() + self.labels.heap_size_bytes() + self.class_ids.heap_size_bytes() } @@ -77,6 +81,7 @@ impl ::re_types_core::SizeBytes for Ellipsoids { && >>::is_pod() && >>::is_pod() && >>::is_pod() + && >::is_pod() && >>::is_pod() && >>::is_pod() } @@ -95,16 +100,17 @@ static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = ] }); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 9usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.HalfSize3D".into(), @@ -113,14 +119,15 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = "rerun.components.Color".into(), "rerun.components.EllipsoidsIndicator".into(), "rerun.components.Radius".into(), + "rerun.components.FillMode".into(), "rerun.components.Text".into(), "rerun.components.ClassId".into(), ] }); impl Ellipsoids { - /// The total number of components in the archetype: 1 required, 4 recommended, 3 optional - pub const NUM_COMPONENTS: usize = 8usize; + /// The total number of components in the archetype: 1 required, 4 recommended, 4 optional + pub const NUM_COMPONENTS: usize = 9usize; } /// Indicator component for the [`Ellipsoids`] [`::re_types_core::Archetype`] @@ -235,6 +242,15 @@ impl ::re_types_core::Archetype for Ellipsoids { } else { None }; + let fill_mode = if let Some(array) = arrays_by_name.get("rerun.components.FillMode") { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Ellipsoids#fill_mode")? + .into_iter() + .next() + .flatten() + } else { + None + }; let labels = if let Some(array) = arrays_by_name.get("rerun.components.Text") { Some({ ::from_arrow_opt(&**array) @@ -265,6 +281,7 @@ impl ::re_types_core::Archetype for Ellipsoids { rotations, colors, line_radii, + fill_mode, labels, class_ids, }) @@ -290,6 +307,9 @@ impl ::re_types_core::AsComponents for Ellipsoids { self.line_radii .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.fill_mode + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), self.labels .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -315,6 +335,7 @@ impl Ellipsoids { rotations: None, colors: None, line_radii: None, + fill_mode: None, labels: None, class_ids: None, } @@ -364,6 +385,13 @@ impl Ellipsoids { self } + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + #[inline] + pub fn with_fill_mode(mut self, fill_mode: impl Into) -> Self { + self.fill_mode = Some(fill_mode.into()); + self + } + /// Optional text labels for the ellipsoids. #[inline] pub fn with_labels( diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index 35018e8aa5e8..ab6662dfba01 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -14,6 +14,7 @@ colormap.rs linguist-generated=true depth_meter.rs linguist-generated=true disconnected_space.rs linguist-generated=true draw_order.rs linguist-generated=true +fill_mode.rs linguist-generated=true fill_ratio.rs linguist-generated=true gamma_correction.rs linguist-generated=true half_size2d.rs linguist-generated=true diff --git a/crates/store/re_types/src/components/fill_mode.rs b/crates/store/re_types/src/components/fill_mode.rs new file mode 100644 index 000000000000..5dd188759d79 --- /dev/null +++ b/crates/store/re_types/src/components/fill_mode.rs @@ -0,0 +1,171 @@ +// 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/fill_mode.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**: How a geometric shape is drawn and colored. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)] +pub enum FillMode { + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + #[default] + Wireframe = 1, + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid = 2, +} + +impl ::re_types_core::reflection::Enum for FillMode { + #[inline] + fn variants() -> &'static [Self] { + &[Self::Wireframe, Self::Solid] + } + + #[inline] + fn docstring_md(self) -> &'static str { + match self { + Self::Wireframe => { + "Lines are drawn around the edges of the shape.\n\nThe interior (2D) or surface (3D) are not drawn." + } + Self::Solid => { + "The interior (2D) or surface (3D) is filled with a single color.\n\nLines are not drawn." + } + } + } +} + +impl ::re_types_core::SizeBytes for FillMode { + #[inline] + fn heap_size_bytes(&self) -> u64 { + 0 + } + + #[inline] + fn is_pod() -> bool { + true + } +} + +impl std::fmt::Display for FillMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Wireframe => write!(f, "Wireframe"), + Self::Solid => write!(f, "Solid"), + } + } +} + +::re_types_core::macros::impl_into_cow!(FillMode); + +impl ::re_types_core::Loggable for FillMode { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.FillMode".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + #![allow(clippy::wildcard_imports)] + use arrow2::datatypes::*; + DataType::Union( + std::sync::Arc::new(vec![ + Field::new("_null_markers", DataType::Null, true), + Field::new("Wireframe", DataType::Null, true), + Field::new("Solid", DataType::Null, true), + ]), + Some(std::sync::Arc::new(vec![0i32, 1i32, 2i32])), + UnionMode::Sparse, + ) + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, datatypes::*}; + Ok({ + // Sparse Arrow union + let data: Vec<_> = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + datum + }) + .collect(); + let num_variants = 2usize; + let types = data + .iter() + .map(|a| match a.as_deref() { + None => 0, + Some(value) => *value as i8, + }) + .collect(); + let fields: Vec<_> = + std::iter::repeat(NullArray::new(DataType::Null, data.len()).boxed()) + .take(1 + num_variants) + .collect(); + UnionArray::new(Self::arrow_datatype(), types, fields, None).boxed() + }) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + #![allow(clippy::wildcard_imports)] + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + 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.components.FillMode")?; + let arrow_data_types = arrow_data.types(); + arrow_data_types + .iter() + .map(|typ| match typ { + 0 => Ok(None), + 1 => Ok(Some(Self::Wireframe)), + 2 => Ok(Some(Self::Solid)), + _ => Err(DeserializationError::missing_union_arm( + Self::arrow_datatype(), + "", + *typ as _, + )), + }) + .collect::>>() + .with_context("rerun.components.FillMode")? + }) + } +} diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index fe677d12c0a3..d52e2550b0f0 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -22,6 +22,7 @@ mod disconnected_space; mod disconnected_space_ext; mod draw_order; mod draw_order_ext; +mod fill_mode; mod fill_ratio; mod fill_ratio_ext; mod gamma_correction; @@ -116,6 +117,7 @@ pub use self::colormap::Colormap; pub use self::depth_meter::DepthMeter; pub use self::disconnected_space::DisconnectedSpace; pub use self::draw_order::DrawOrder; +pub use self::fill_mode::FillMode; pub use self::fill_ratio::FillRatio; pub use self::gamma_correction::GammaCorrection; pub use self::half_size2d::HalfSize2D; diff --git a/crates/store/re_types/tests/box3d.rs b/crates/store/re_types/tests/box3d.rs index bfc3e33c33fd..fe4d431dc89a 100644 --- a/crates/store/re_types/tests/box3d.rs +++ b/crates/store/re_types/tests/box3d.rs @@ -28,6 +28,7 @@ fn roundtrip() { components::Radius::from(42.0), // components::Radius::from(43.0), ]), + fill_mode: Some(components::FillMode::Solid), labels: Some(vec![ "hello".into(), // "friend".into(), // @@ -49,6 +50,7 @@ fn roundtrip() { ]) .with_colors([0xAA0000CC, 0x00BB00DD]) .with_radii([42.0, 43.0]) + .with_fill_mode(components::FillMode::Solid) .with_labels(["hello", "friend"]) .with_class_ids([126, 127]); similar_asserts::assert_eq!(expected, arch); @@ -58,6 +60,7 @@ fn roundtrip() { ("centers", vec!["rerun.components.Position2D"]), ("colors", vec!["rerun.components.Color"]), ("radii", vec!["rerun.components.Radius"]), + ("fill_mode", vec!["rerun.components.FillMode"]), ("labels", vec!["rerun.components.Label"]), ("draw_order", vec!["rerun.components.DrawOrder"]), ("class_ids", vec!["rerun.components.ClassId"]), diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs index c8ba61d467d3..8c91db5388a0 100644 --- a/crates/top/rerun/src/sdk.rs +++ b/crates/top/rerun/src/sdk.rs @@ -26,7 +26,7 @@ mod prelude { // Also import any component or datatype that has a unique name: pub use re_chunk::ChunkTimeline; pub use re_types::components::{ - AlbedoFactor, Color, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, + AlbedoFactor, Color, FillMode, HalfSize2D, HalfSize3D, LineStrip2D, LineStrip3D, MediaType, OutOfTreeTransform3D, Position2D, Position3D, Radius, Scale3D, Text, TextLogLevel, TransformRelation, TriangleIndices, Vector2D, Vector3D, }; diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 7265920beed3..4c0a6860fdda 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -335,6 +335,13 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "How a geometric shape is drawn and colored.", + placeholder: Some(FillMode::default().to_arrow()?), + }, + ), ( ::name(), ComponentReflection { diff --git a/docs/content/reference/types/archetypes/boxes3d.md b/docs/content/reference/types/archetypes/boxes3d.md index 60c997849804..344704145f9e 100644 --- a/docs/content/reference/types/archetypes/boxes3d.md +++ b/docs/content/reference/types/archetypes/boxes3d.md @@ -11,7 +11,7 @@ title: "Boxes3D" **Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) -**Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) +**Optional**: [`Radius`](../components/radius.md), [`FillMode`](../components/fill_mode.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) ## Shown in * [Spatial3DView](../views/spatial3d_view.md) diff --git a/docs/content/reference/types/archetypes/ellipsoids.md b/docs/content/reference/types/archetypes/ellipsoids.md index c03c7d7ee13a..196cf8a60b62 100644 --- a/docs/content/reference/types/archetypes/ellipsoids.md +++ b/docs/content/reference/types/archetypes/ellipsoids.md @@ -18,7 +18,7 @@ Opaque and transparent rendering will be supported later. **Recommended**: [`Position3D`](../components/position3d.md), [`Rotation3D`](../components/rotation3d.md), [`Color`](../components/color.md) -**Optional**: [`Radius`](../components/radius.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) +**Optional**: [`Radius`](../components/radius.md), [`FillMode`](../components/fill_mode.md), [`Text`](../components/text.md), [`ClassId`](../components/class_id.md) ## Shown in * [Spatial3DView](../views/spatial3d_view.md) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index 2b6ab9934ac5..c7e5090e2211 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -27,6 +27,7 @@ on [Entities and Components](../../concepts/entity-component.md). * [`DepthMeter`](components/depth_meter.md): The world->depth map scaling factor. * [`DisconnectedSpace`](components/disconnected_space.md): Spatially disconnect this entity from its parent. * [`DrawOrder`](components/draw_order.md): Draw order of 2D elements. Higher values are drawn on top of lower values. +* [`FillMode`](components/fill_mode.md): How a geometric shape is drawn and colored. * [`FillRatio`](components/fill_ratio.md): How much a primitive fills out the available space. * [`GammaCorrection`](components/gamma_correction.md): A gamma correction value to be used with a scalar value or color. * [`HalfSize2D`](components/half_size2d.md): Half-size (radius) of a 2D box. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 57a0ce5cfa2e..a54341eb5dbf 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -15,6 +15,7 @@ colormap.md linguist-generated=true depth_meter.md linguist-generated=true disconnected_space.md linguist-generated=true draw_order.md linguist-generated=true +fill_mode.md linguist-generated=true fill_ratio.md linguist-generated=true gamma_correction.md linguist-generated=true half_size2d.md linguist-generated=true diff --git a/docs/content/reference/types/components/fill_mode.md b/docs/content/reference/types/components/fill_mode.md new file mode 100644 index 000000000000..d128a3ee76af --- /dev/null +++ b/docs/content/reference/types/components/fill_mode.md @@ -0,0 +1,22 @@ +--- +title: "FillMode" +--- + + +How a geometric shape is drawn and colored. + +## Variants + +* Wireframe +* Solid + +## API reference links + * šŸŒŠ [C++ API docs for `FillMode`](https://ref.rerun.io/docs/cpp/stable/namespacererun_1_1components.html?speculative-link) + * šŸ [Python API docs for `FillMode`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.FillMode) + * šŸ¦€ [Rust API docs for `FillMode`](https://docs.rs/rerun/latest/rerun/components/enum.FillMode.html?speculative-link) + + +## Used by + +* [`Boxes3D`](../archetypes/boxes3d.md) +* [`Ellipsoids`](../archetypes/ellipsoids.md?speculative-link) diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp index 6e4ec3433d85..18d0e9695954 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -41,6 +41,11 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.fill_mode.has_value()) { + auto result = DataCell::from_loggable(archetype.fill_mode.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.labels.has_value()) { auto result = DataCell::from_loggable(archetype.labels.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 7bbf1c4f3dcf..98ca2f8984fc 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -7,6 +7,7 @@ #include "../compiler_utils.hpp" #include "../components/class_id.hpp" #include "../components/color.hpp" +#include "../components/fill_mode.hpp" #include "../components/half_size3d.hpp" #include "../components/position3d.hpp" #include "../components/radius.hpp" @@ -74,6 +75,9 @@ namespace rerun::archetypes { /// Optional radii for the lines that make up the boxes. std::optional> radii; + /// Optionally choose whether the boxes are drawn with lines or solid. + std::optional fill_mode; + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. @@ -175,6 +179,13 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Optionally choose whether the boxes are drawn with lines or solid. + Boxes3D with_fill_mode(rerun::components::FillMode _fill_mode) && { + fill_mode = std::move(_fill_mode); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional text labels for the boxes. /// /// If there's a single label present, it will be placed at the center of the entity. diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp index 6baf7232881c..cc4109b1a12b 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(8); + cells.reserve(9); { auto result = DataCell::from_loggable(archetype.half_sizes); @@ -41,6 +41,11 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.fill_mode.has_value()) { + auto result = DataCell::from_loggable(archetype.fill_mode.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.labels.has_value()) { auto result = DataCell::from_loggable(archetype.labels.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp index e22d516be6bd..6451374d5a3d 100644 --- a/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp +++ b/rerun_cpp/src/rerun/archetypes/ellipsoids.hpp @@ -7,6 +7,7 @@ #include "../compiler_utils.hpp" #include "../components/class_id.hpp" #include "../components/color.hpp" +#include "../components/fill_mode.hpp" #include "../components/half_size3d.hpp" #include "../components/position3d.hpp" #include "../components/radius.hpp" @@ -52,6 +53,9 @@ namespace rerun::archetypes { /// Optional radii for the lines used when the ellipsoid is rendered as a wireframe. std::optional> line_radii; + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + std::optional fill_mode; + /// Optional text labels for the ellipsoids. std::optional> labels; @@ -137,6 +141,13 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Optionally choose whether the ellipsoids are drawn with lines or solid. + Ellipsoids with_fill_mode(rerun::components::FillMode _fill_mode) && { + fill_mode = std::move(_fill_mode); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Optional text labels for the ellipsoids. Ellipsoids with_labels(Collection _labels) && { labels = std::move(_labels); diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index 56668bc637f8..fd92e24cdec3 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -16,6 +16,7 @@ #include "components/depth_meter.hpp" #include "components/disconnected_space.hpp" #include "components/draw_order.hpp" +#include "components/fill_mode.hpp" #include "components/fill_ratio.hpp" #include "components/gamma_correction.hpp" #include "components/half_size2d.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index 042f2627d074..3b125ebb1e59 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -20,6 +20,8 @@ colormap.hpp linguist-generated=true depth_meter.hpp linguist-generated=true disconnected_space.hpp linguist-generated=true draw_order.hpp linguist-generated=true +fill_mode.cpp linguist-generated=true +fill_mode.hpp linguist-generated=true fill_ratio.hpp linguist-generated=true gamma_correction.hpp linguist-generated=true half_size2d.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/fill_mode.cpp b/rerun_cpp/src/rerun/components/fill_mode.cpp new file mode 100644 index 000000000000..bc96f19799eb --- /dev/null +++ b/rerun_cpp/src/rerun/components/fill_mode.cpp @@ -0,0 +1,61 @@ +// 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/fill_mode.fbs". + +#include "fill_mode.hpp" + +#include +#include + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = arrow::sparse_union({ + arrow::field("_null_markers", arrow::null(), true, nullptr), + arrow::field("Wireframe", arrow::null(), true), + arrow::field("Solid", arrow::null(), true), + }); + return datatype; + } + + Result> Loggable::to_arrow( + const components::FillMode* 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::SparseUnionBuilder* builder, const components::FillMode* 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." + ); + } + + ARROW_RETURN_NOT_OK(builder->Reserve(static_cast(num_elements))); + for (size_t elem_idx = 0; elem_idx < num_elements; elem_idx += 1) { + const auto variant = elements[elem_idx]; + ARROW_RETURN_NOT_OK(builder->Append(static_cast(variant))); + } + + return Error::ok(); + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/fill_mode.hpp b/rerun_cpp/src/rerun/components/fill_mode.hpp new file mode 100644 index 000000000000..c13ced274c83 --- /dev/null +++ b/rerun_cpp/src/rerun/components/fill_mode.hpp @@ -0,0 +1,56 @@ +// 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/fill_mode.fbs". + +#pragma once + +#include "../result.hpp" + +#include +#include + +namespace arrow { + class Array; + class DataType; + class SparseUnionBuilder; +} // namespace arrow + +namespace rerun::components { + /// **Component**: How a geometric shape is drawn and colored. + enum class FillMode : uint8_t { + + /// Lines are drawn around the edges of the shape. + /// + /// The interior (2D) or surface (3D) are not drawn. + Wireframe = 1, + + /// The interior (2D) or surface (3D) is filled with a single color. + /// + /// Lines are not drawn. + Solid = 2, + }; +} // namespace rerun::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.FillMode"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Serializes an array of `rerun::components::FillMode` into an arrow array. + static Result> to_arrow( + const components::FillMode* instances, size_t num_instances + ); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::SparseUnionBuilder* builder, const components::FillMode* elements, + size_t num_elements + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index 1989506ee9fb..7598ab7ec10e 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -68,6 +68,7 @@ def __attrs_clear__(self) -> None: rotations=None, # type: ignore[arg-type] colors=None, # type: ignore[arg-type] radii=None, # type: ignore[arg-type] + fill_mode=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] ) @@ -123,6 +124,15 @@ def _clear(cls) -> Boxes3D: # # (Docstring intentionally commented out to hide this field from the docs) + fill_mode: components.FillModeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.FillModeBatch._optional, # type: ignore[misc] + ) + # Optionally choose whether the boxes are drawn with lines or solid. + # + # (Docstring intentionally commented out to hide this field from the docs) + labels: components.TextBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py index 7048ce103dbf..ce48e1a2bc76 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d_ext.py @@ -4,7 +4,7 @@ import numpy as np -from .. import datatypes +from .. import components, datatypes from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions @@ -21,6 +21,7 @@ def __init__( rotations: datatypes.Rotation3DArrayLike | None = None, colors: datatypes.Rgba32ArrayLike | None = None, radii: datatypes.Float32ArrayLike | None = None, + fill_mode: components.FillMode | None = None, labels: datatypes.Utf8ArrayLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, ) -> None: @@ -45,6 +46,8 @@ def __init__( Optional colors for the boxes. radii: Optional radii for the lines that make up the boxes. + fill_mode: + Optionally choose whether the boxes are drawn with lines or solid. labels: Optional text labels for the boxes. class_ids: @@ -81,6 +84,7 @@ def __init__( rotations=rotations, colors=colors, radii=radii, + fill_mode=fill_mode, labels=labels, class_ids=class_ids, ) diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py index 7a228344874d..953c72fa7838 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids.py @@ -39,6 +39,7 @@ def __attrs_clear__(self) -> None: rotations=None, # type: ignore[arg-type] colors=None, # type: ignore[arg-type] line_radii=None, # type: ignore[arg-type] + fill_mode=None, # type: ignore[arg-type] labels=None, # type: ignore[arg-type] class_ids=None, # type: ignore[arg-type] ) @@ -100,6 +101,15 @@ def _clear(cls) -> Ellipsoids: # # (Docstring intentionally commented out to hide this field from the docs) + fill_mode: components.FillModeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.FillModeBatch._optional, # type: ignore[misc] + ) + # Optionally choose whether the ellipsoids are drawn with lines or solid. + # + # (Docstring intentionally commented out to hide this field from the docs) + labels: components.TextBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py index aa8a9e9b0da5..750e6a2c6927 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/ellipsoids_ext.py @@ -4,7 +4,7 @@ import numpy as np -from .. import datatypes +from .. import components, datatypes from ..error_utils import _send_warning_or_raise, catch_and_log_exceptions @@ -20,6 +20,7 @@ def __init__( rotations: datatypes.Rotation3DArrayLike | None = None, colors: datatypes.Rgba32ArrayLike | None = None, line_radii: datatypes.Float32ArrayLike | None = None, + fill_mode: components.FillMode | None = None, labels: datatypes.Utf8ArrayLike | None = None, class_ids: datatypes.ClassIdArrayLike | None = None, ) -> None: @@ -42,6 +43,8 @@ def __init__( Optional colors for the ellipsoids. line_radii: Optional radii for the lines that make up the ellipsoids. + fill_mode: + Optionally choose whether the ellipsoids are drawn with lines or solid. labels: Optional text labels for the ellipsoids. class_ids: @@ -66,6 +69,7 @@ def __init__( rotations=rotations, colors=colors, line_radii=line_radii, + fill_mode=fill_mode, labels=labels, class_ids=class_ids, ) diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 19b26b523b0e..97a701b83707 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -16,6 +16,7 @@ colormap.py linguist-generated=true depth_meter.py linguist-generated=true disconnected_space.py linguist-generated=true draw_order.py linguist-generated=true +fill_mode.py linguist-generated=true fill_ratio.py linguist-generated=true gamma_correction.py linguist-generated=true half_size2d.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 e398376d2fdd..85d82926cc28 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -34,6 +34,7 @@ from .depth_meter import DepthMeter, DepthMeterBatch, DepthMeterType from .disconnected_space import DisconnectedSpace, DisconnectedSpaceBatch, DisconnectedSpaceType from .draw_order import DrawOrder, DrawOrderBatch, DrawOrderType +from .fill_mode import FillMode, FillModeArrayLike, FillModeBatch, FillModeLike, FillModeType from .fill_ratio import FillRatio, FillRatioBatch, FillRatioType from .gamma_correction import GammaCorrection, GammaCorrectionBatch, GammaCorrectionType from .half_size2d import HalfSize2D, HalfSize2DBatch, HalfSize2DType @@ -147,6 +148,11 @@ "DrawOrder", "DrawOrderBatch", "DrawOrderType", + "FillMode", + "FillModeArrayLike", + "FillModeBatch", + "FillModeLike", + "FillModeType", "FillRatio", "FillRatioBatch", "FillRatioType", diff --git a/rerun_py/rerun_sdk/rerun/components/fill_mode.py b/rerun_py/rerun_sdk/rerun/components/fill_mode.py new file mode 100644 index 000000000000..df3c91c5cf6c --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/fill_mode.py @@ -0,0 +1,101 @@ +# 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/fill_mode.fbs". + +# You can extend this class by creating a "FillModeExt" class in "fill_mode_ext.py". + +from __future__ import annotations + +from typing import Literal, Sequence, Union + +import pyarrow as pa + +from .._baseclasses import ( + BaseBatch, + BaseExtensionType, + ComponentBatchMixin, +) + +__all__ = ["FillMode", "FillModeArrayLike", "FillModeBatch", "FillModeLike", "FillModeType"] + + +from enum import Enum + + +class FillMode(Enum): + """**Component**: How a geometric shape is drawn and colored.""" + + Wireframe = 1 + """ + Lines are drawn around the edges of the shape. + + The interior (2D) or surface (3D) are not drawn. + """ + + Solid = 2 + """ + The interior (2D) or surface (3D) is filled with a single color. + + Lines are not drawn. + """ + + +FillModeLike = Union[FillMode, Literal["wireframe", "solid"]] +FillModeArrayLike = Union[FillModeLike, Sequence[FillModeLike]] + + +class FillModeType(BaseExtensionType): + _TYPE_NAME: str = "rerun.components.FillMode" + + def __init__(self) -> None: + pa.ExtensionType.__init__( + self, + pa.sparse_union([ + pa.field("_null_markers", pa.null(), nullable=True, metadata={}), + pa.field("Wireframe", pa.null(), nullable=True, metadata={}), + pa.field("Solid", pa.null(), nullable=True, metadata={}), + ]), + self._TYPE_NAME, + ) + + +class FillModeBatch(BaseBatch[FillModeArrayLike], ComponentBatchMixin): + _ARROW_TYPE = FillModeType() + + @staticmethod + def _native_to_pa_array(data: FillModeArrayLike, data_type: pa.DataType) -> pa.Array: + if isinstance(data, (FillMode, int, str)): + data = [data] + + types: list[int] = [] + + for value in data: + if value is None: + types.append(0) + elif isinstance(value, FillMode): + types.append(value.value) # Actual enum value + elif isinstance(value, int): + types.append(value) # By number + elif isinstance(value, str): + if hasattr(FillMode, value): + types.append(FillMode[value].value) # fast path + elif value.lower() == "wireframe": + types.append(FillMode.Wireframe.value) + elif value.lower() == "solid": + types.append(FillMode.Solid.value) + else: + raise ValueError(f"Unknown FillMode kind: {value}") + else: + raise ValueError(f"Unknown FillMode kind: {value}") + + buffers = [ + None, + pa.array(types, type=pa.int8()).buffers()[1], + ] + children = (1 + 2) * [pa.nulls(len(data))] + + return pa.UnionArray.from_buffers( + type=data_type, + length=len(data), + buffers=buffers, + children=children, + ) From 6e2a5be5f8bcc6bbcb64c6c3e883dfda6ab7b704 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 16 Jul 2024 14:20:06 -0700 Subject: [PATCH 13/20] Add `SolidCache`. --- .../re_space_view_spatial/src/proc_mesh.rs | 154 +++++++++++++++++- 1 file changed, 149 insertions(+), 5 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index 2551d15f04b4..04ae861d0da6 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -1,8 +1,16 @@ +//! Procedurally-generated meshes for rendering objects that are +//! specified geometrically, and have nontrivial numbers of vertices each, +//! such as a sphere or cylinder. + use std::sync::Arc; use ahash::HashSet; use glam::Vec3; +use itertools::Itertools as _; +use smallvec::smallvec; +use re_renderer::mesh; +use re_renderer::resource_managers::{GpuMeshHandle, ResourceManagerError}; use re_renderer::RenderContext; use re_viewer_context::Cache; @@ -10,16 +18,32 @@ use re_viewer_context::Cache; /// Description of a mesh that can be procedurally generated. /// -/// Obtain the actual mesh by passing this to [`WireframeCache`]. -/// In the future, it will be possible to produce solid triangle meshes too. +/// Obtain the actual mesh by passing this to [`WireframeCache`] or [`SolidCache`]. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ProcMeshKey { /// A sphere of unit radius. + /// + /// The resulting mesh may be scaled to represent spheres and ellipsoids + /// of other sizes. Sphere { subdivisions: usize }, } +impl ProcMeshKey { + /// Returns the bounding box which can be computed from the mathematical shape, + /// without regard for its exact approximation as a mesh. + fn simple_bounding_box(&self) -> re_math::BoundingBox { + match self { + Self::Sphere { subdivisions: _ } => { + // sphereā€™s radius is 1, so its size is 2 + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)) + } + } + } +} + /// A renderable mesh generated from a [`ProcMeshKey`] by the [`WireframeCache`], /// which is to be drawn as lines rather than triangles. +#[derive(Debug)] pub struct WireframeMesh { pub bbox: re_math::BoundingBox, @@ -33,6 +57,19 @@ pub struct WireframeMesh { pub line_strips: Vec>, } +/// A renderable mesh generated from a [`ProcMeshKey`] by the [`SolidCache`], +/// which is to be drawn as triangles rather than lines. +/// +/// This type is cheap to clone. +#[derive(Debug, Clone)] +pub struct SolidMesh { + pub bbox: re_math::BoundingBox, + + /// Mesh to render. Note that its colors are set to black, so that the + /// `MeshInstance::additive_tint` can be used to set the color per instance. + pub gpu_mesh: GpuMeshHandle, +} + // ---------------------------------------------------------------------------- /// Cache for the computation of wireframe meshes from [`ProcMeshKey`]s. @@ -79,6 +116,8 @@ impl Cache for WireframeCache { /// Generate a wireframe mesh without caching. fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> WireframeMesh { + re_tracing::profile_function!(); + // In the future, render_ctx will be used to allocate GPU memory for the mesh. _ = render_ctx; @@ -128,8 +167,7 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram .collect() }; WireframeMesh { - // sphereā€™s radius is 1, so its size is 2 - bbox: re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)), + bbox: key.simple_bounding_box(), vertex_count: line_strips.iter().map(|v| v.len()).sum(), line_strips, } @@ -137,4 +175,110 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram } } -// TODO(kpreid): A solid (rather than wireframe) mesh cache implementation should live here. +// ---------------------------------------------------------------------------- + +/// Cache for the computation of triangle meshes from [`ProcMeshKey`]s that depict the +/// shape as a solid object. +#[derive(Default)] +pub struct SolidCache(ahash::HashMap>); + +impl SolidCache { + pub fn entry(&mut self, key: ProcMeshKey, render_ctx: &RenderContext) -> Option { + re_tracing::profile_function!(); + + self.0 + .entry(key) + .or_insert_with(|| { + re_log::debug!("Generating mesh {key:?}ā€¦"); + + match generate_solid(&key, render_ctx) { + Ok(mesh) => Some(mesh), + Err(err) => { + re_log::warn!( + "Failed to generate mesh {key:?}: {}", + re_error::format_ref(&err) + ); + None + } + } + }) + .clone() + } +} + +impl Cache for SolidCache { + fn begin_frame(&mut self) {} + + fn purge_memory(&mut self) { + self.0.clear(); + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +/// Generate a solid triangle mesh without caching. +fn generate_solid( + key: &ProcMeshKey, + render_ctx: &RenderContext, +) -> Result { + re_tracing::profile_function!(); + + let mesh: mesh::Mesh = match *key { + ProcMeshKey::Sphere { subdivisions } => { + let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); + + let vertex_positions: Vec = + subdiv.raw_points().iter().map(|&p| p.into()).collect(); + // A unit sphere's normals are its positions. + let vertex_normals = vertex_positions.clone(); + let num_vertices = vertex_positions.len(); + + let triangle_indices = subdiv.get_all_indices(); + let triangle_indices: Vec = triangle_indices + .into_iter() + .tuples() + .map(|(i1, i2, i3)| glam::uvec3(i1, i2, i3)) + .collect(); + + let materials = smallvec![mesh::Material { + label: "default material".into(), + index_range: 0..(triangle_indices.len() * 3) as u32, + albedo: render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone(), + albedo_factor: re_renderer::Rgba::BLACK, + }]; + + mesh::Mesh { + label: format!("{key:?}").into(), + + // bytemuck is re-grouping the indices into triples without realloc + triangle_indices, + + vertex_positions, + vertex_normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + + materials, + } + } + }; + + mesh.sanity_check()?; + + let gpu_mesh = render_ctx.mesh_manager.write().create( + render_ctx, + &mesh, + re_renderer::resource_managers::ResourceLifeTime::LongLived, + )?; + + Ok(SolidMesh { + bbox: key.simple_bounding_box(), + gpu_mesh, + }) +} From b34e797c64c3e7f03bbafcc17409611432122641 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 19 Jul 2024 16:10:20 -0700 Subject: [PATCH 14/20] Add `ProcMeshKey::Cube`. --- .../re_space_view_spatial/src/proc_mesh.rs | 98 ++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index 04ae861d0da6..eb2013873c7e 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use ahash::HashSet; -use glam::Vec3; +use glam::{uvec3, vec3, Vec3}; use itertools::Itertools as _; use smallvec::smallvec; @@ -21,6 +21,9 @@ use re_viewer_context::Cache; /// Obtain the actual mesh by passing this to [`WireframeCache`] or [`SolidCache`]. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ProcMeshKey { + /// A unit cube, centered; its bounds are Ā±0.5. + Cube, + /// A sphere of unit radius. /// /// The resulting mesh may be scaled to represent spheres and ellipsoids @@ -37,6 +40,9 @@ impl ProcMeshKey { // sphereā€™s radius is 1, so its size is 2 re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(2.0)) } + Self::Cube => { + re_math::BoundingBox::from_center_size(Vec3::splat(0.0), Vec3::splat(1.0)) + } } } } @@ -122,6 +128,44 @@ fn generate_wireframe(key: &ProcMeshKey, render_ctx: &RenderContext) -> Wirefram _ = render_ctx; match *key { + ProcMeshKey::Cube => { + let corners = [ + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, 0.5), + vec3(0.5, -0.5, -0.5), + vec3(0.5, -0.5, 0.5), + vec3(0.5, 0.5, -0.5), + vec3(0.5, 0.5, 0.5), + ]; + let line_strips: Vec> = vec![ + // bottom: + vec![ + // bottom loop + corners[0b000], + corners[0b001], + corners[0b011], + corners[0b010], + corners[0b000], + // joined to top loop + corners[0b100], + corners[0b101], + corners[0b111], + corners[0b110], + corners[0b100], + ], + // remaining side edges + vec![corners[0b001], corners[0b101]], + vec![corners[0b010], corners[0b110]], + vec![corners[0b011], corners[0b111]], + ]; + WireframeMesh { + bbox: key.simple_bounding_box(), + vertex_count: line_strips.iter().map(|v| v.len()).sum(), + line_strips, + } + } ProcMeshKey::Sphere { subdivisions } => { let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); @@ -226,10 +270,35 @@ fn generate_solid( re_tracing::profile_function!(); let mesh: mesh::Mesh = match *key { + ProcMeshKey::Cube => { + let mut mg = re_math::MeshGen::new(); + mg.push_cube(Vec3::splat(0.5), re_math::IsoTransform::IDENTITY); + + let num_vertices = mg.positions.len(); + + let triangle_indices: Vec = mg + .indices + .into_iter() + .tuples() + .map(|(i1, i2, i3)| uvec3(i1, i2, i3)) + .collect(); + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); + + mesh::Mesh { + label: format!("{key:?}").into(), + materials, + triangle_indices, + vertex_positions: mg.positions, + vertex_normals: mg.normals, + // Colors are black so that the instance `additive_tint` can set per-instance color. + vertex_colors: vec![re_renderer::Rgba32Unmul::BLACK; num_vertices], + vertex_texcoords: vec![glam::Vec2::ZERO; num_vertices], + } + } ProcMeshKey::Sphere { subdivisions } => { let subdiv = hexasphere::shapes::IcoSphere::new(subdivisions, |_| ()); - let vertex_positions: Vec = + let vertex_positions: Vec = subdiv.raw_points().iter().map(|&p| p.into()).collect(); // A unit sphere's normals are its positions. let vertex_normals = vertex_positions.clone(); @@ -242,15 +311,7 @@ fn generate_solid( .map(|(i1, i2, i3)| glam::uvec3(i1, i2, i3)) .collect(); - let materials = smallvec![mesh::Material { - label: "default material".into(), - index_range: 0..(triangle_indices.len() * 3) as u32, - albedo: render_ctx - .texture_manager_2d - .white_texture_unorm_handle() - .clone(), - albedo_factor: re_renderer::Rgba::BLACK, - }]; + let materials = materials_for_uncolored_mesh(render_ctx, triangle_indices.len()); mesh::Mesh { label: format!("{key:?}").into(), @@ -282,3 +343,18 @@ fn generate_solid( gpu_mesh, }) } + +fn materials_for_uncolored_mesh( + render_ctx: &RenderContext, + num_triangles: usize, +) -> smallvec::SmallVec<[mesh::Material; 1]> { + smallvec![mesh::Material { + label: "default material".into(), + index_range: 0..(num_triangles * 3) as u32, + albedo: render_ctx + .texture_manager_2d + .white_texture_unorm_handle() + .clone(), + albedo_factor: re_renderer::Rgba::BLACK, + }] +} From b0a97f471b513fd4986a36f4d55052caa5254b99 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 23 Jul 2024 17:13:45 -0700 Subject: [PATCH 15/20] Add `FillMode::Solid` support to `Boxes3D` and `Ellipsoids` visualizers. --- .../re_space_view_spatial/src/proc_mesh.rs | 2 +- .../src/visualizers/boxes3d.rs | 168 ++++++++++++++---- .../src/visualizers/ellipsoids.rs | 153 ++++++++++++---- 3 files changed, 249 insertions(+), 74 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs index eb2013873c7e..a976a3ebc249 100644 --- a/crates/viewer/re_space_view_spatial/src/proc_mesh.rs +++ b/crates/viewer/re_space_view_spatial/src/proc_mesh.rs @@ -34,7 +34,7 @@ pub enum ProcMeshKey { impl ProcMeshKey { /// Returns the bounding box which can be computed from the mathematical shape, /// without regard for its exact approximation as a mesh. - fn simple_bounding_box(&self) -> re_math::BoundingBox { + pub fn simple_bounding_box(&self) -> re_math::BoundingBox { match self { Self::Sphere { subdivisions: _ } => { // sphereā€™s radius is 1, so its size is 2 diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs index 1533dfbe9b49..bcbd75e16a46 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -1,9 +1,13 @@ +use re_entity_db::InstancePathHash; use re_log_types::Instance; -use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Boxes3D, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, FillMode, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -12,7 +16,11 @@ use re_viewer_context::{ VisualizerQueryInfo, VisualizerSystem, }; -use crate::{contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind}; +use crate::{ + contexts::SpatialSceneEntityContext, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, proc_mesh, + view_kind::SpatialSpaceViewKind, +}; use super::{ entity_iterator::clamped_or, filter_visualizable_3d_entities, @@ -35,14 +43,17 @@ impl Default for Boxes3DVisualizer { // NOTE: Do not put profile scopes in these methods. They are called for all entities and all // timestamps within a time range -- it's _a lot_. impl Boxes3DVisualizer { + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, - ) { + render_ctx: &RenderContext, + ) -> Result<(), SpaceViewSystemExecutionError> { let entity_path = ctx.target_entity_path; for data in data { @@ -78,32 +89,64 @@ impl Boxes3DVisualizer { let centers = clamped_or(data.centers, &Position3D::ZERO); let rotations = clamped_or(data.rotations, &Rotation3D::IDENTITY); - for (i, (half_size, ¢er, rotation, radius, &color)) in + for (instance_index, (half_size, ¢er, rotation, radius, &color)) in itertools::izip!(data.half_sizes, centers, rotations, radii, &colors).enumerate() { - obj_space_bounding_box.extend(half_size.box_min(center)); - obj_space_bounding_box.extend(half_size.box_max(center)); - - let center = center.into(); - - let box3d = line_batch - .add_box_outline_from_transform( - glam::Affine3A::from_scale_rotation_translation( - glam::Vec3::from(*half_size) * 2.0, - rotation.0.into(), - center, - ), - ) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + let instance = Instance::from(instance_index as u64); + // Transform from a centered unit cube to this box in the entity's + // coordinate system. + let entity_from_mesh = glam::Affine3A::from_scale_rotation_translation( + glam::Vec3::from(*half_size) * 2.0, + rotation.0.into(), + center.into(), + ); + + let proc_mesh_key = proc_mesh::ProcMeshKey::Cube; + + obj_space_bounding_box = obj_space_bounding_box.union( + // We must perform this transform to fully account for the per-instance + // transform, which is separate from the entity's transform. + proc_mesh_key + .simple_bounding_box() + .transform_affine3(&entity_from_mesh), + ); + + match data.fill_mode { + FillMode::Wireframe => { + let box3d = line_batch + .add_box_outline_from_transform(entity_from_mesh) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + if let Some(outline_mask_ids) = + ent_context.highlight.instances.get(&instance) + { + box3d.outline_mask_ids(*outline_mask_ids); + } + } + FillMode::Solid => { + let Some(solid_mesh) = + ctx.viewer_ctx.cache.entry(|c: &mut proc_mesh::SolidCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: entity_from_mesh, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: color, + }); + } } } @@ -135,6 +178,8 @@ impl Boxes3DVisualizer { )); } } + + Ok(()) } } @@ -152,6 +197,8 @@ struct Boxes3DComponentData<'a> { labels: &'a [Text], keypoint_ids: &'a [KeypointId], class_ids: &'a [ClassId], + + fill_mode: FillMode, } impl IdentifiedViewSystem for Boxes3DVisualizer { @@ -187,6 +234,13 @@ impl VisualizerSystem for Boxes3DVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + // + // Why do we draw solid surfaces using instanced meshes and wireframes using instances? + // No good reason; only, those are the types of renderers that have been implemented so far. + // This code should be revisited with an eye on performance. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -210,19 +264,34 @@ impl VisualizerSystem for Boxes3DVisualizer { return Ok(()); } - // Each box consists of 12 independent lines with 2 vertices each. - line_builder.reserve_strips(num_boxes * 12)?; - line_builder.reserve_vertices(num_boxes * 12 * 2)?; - let centers = results.get_or_empty_dense(resolver)?; let rotations = results.get_or_empty_dense(resolver)?; let colors = results.get_or_empty_dense(resolver)?; + let fill_mode = results.get_or_empty_dense(resolver)?; let radii = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + // fill mode is currently a non-repeated component + let fill_mode: FillMode = fill_mode + .range_indexed() + .next() + .and_then(|(_, fill_modes)| fill_modes.first().copied()) + .unwrap_or_default(); + + match fill_mode { + FillMode::Wireframe => { + // Each box consists of 12 independent lines with 2 vertices each. + line_builder.reserve_strips(num_boxes * 12)?; + line_builder.reserve_vertices(num_boxes * 12 * 2)?; + } + FillMode::Solid => { + // No lines. + } + } + + let data = re_query::range_zip_1x7( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), @@ -250,6 +319,8 @@ impl VisualizerSystem for Boxes3DVisualizer { rotations: rotations.unwrap_or_default(), colors: colors.unwrap_or_default(), radii: radii.unwrap_or_default(), + // fill mode is currently a non-repeated component + fill_mode, labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), keypoint_ids: keypoint_ids.unwrap_or_default(), @@ -257,13 +328,38 @@ impl VisualizerSystem for Boxes3DVisualizer { }, ); - self.process_data(ctx, &mut line_builder, view_query, spatial_ctx, data); + self.process_data( + ctx, + &mut line_builder, + &mut solid_instances, + view_query, + spatial_ctx, + data, + render_ctx, + )?; Ok(()) }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs index 48b7bc3f7d70..b5a6fbdcd9ac 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/ellipsoids.rs @@ -1,10 +1,13 @@ use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; -use re_query::range_zip_1x7; -use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, RenderContext}; +use re_renderer::{ + renderer::MeshInstance, LineDrawableBuilder, PickingLayerInstanceId, RenderContext, +}; use re_types::{ archetypes::Ellipsoids, - components::{ClassId, Color, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text}, + components::{ + ClassId, Color, FillMode, HalfSize3D, KeypointId, Position3D, Radius, Rotation3D, Text, + }, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -15,7 +18,8 @@ use re_viewer_context::{ use crate::{ contexts::SpatialSceneEntityContext, - proc_mesh::{ProcMeshKey, WireframeCache}, + instance_hash_conversions::picking_layer_id_from_instance_path_hash, + proc_mesh, view_kind::SpatialSpaceViewKind, visualizers::{UiLabel, UiLabelTarget}, }; @@ -71,10 +75,12 @@ impl EllipsoidsVisualizer { }) } + #[allow(clippy::too_many_arguments)] fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, line_builder: &mut LineDrawableBuilder<'_>, + mesh_instances: &mut Vec, query: &ViewQuery<'_>, ent_context: &SpatialSceneEntityContext<'_>, data: impl Iterator>, @@ -127,12 +133,15 @@ impl EllipsoidsVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = re_math::BoundingBox::NOTHING; + let mut obj_space_bounding_box = re_math::BoundingBox::NOTHING; - for (i, (half_size, ¢er, rotation, radius, color)) in + for (instance_index, (half_size, ¢er, rotation, radius, color)) in itertools::izip!(data.half_sizes, centers, rotations, radii, colors).enumerate() { - let transform = glam::Affine3A::from_scale_rotation_translation( + let instance = Instance::from(instance_index as u64); + // Transform from a centered unit sphere to this ellipsoid in the entity's + // coordinate system. + let entity_from_mesh = glam::Affine3A::from_scale_rotation_translation( glam::Vec3::from(*half_size), rotation.0.into(), center.into(), @@ -141,40 +150,78 @@ impl EllipsoidsVisualizer { // TODO(kpreid): subdivisions should be configurable, and possibly dynamic based on // either world size or screen size (depending on application). let subdivisions = 2; - - let Some(sphere_mesh) = ctx.viewer_ctx.cache.entry(|c: &mut WireframeCache| { - c.entry(ProcMeshKey::Sphere { subdivisions }, render_ctx) - }) else { - // TODO(kpreid): Should this be just returning nothing instead? - // If we do, there won't be any error report, just missing data. - - return Err(SpaceViewSystemExecutionError::DrawDataCreationError( - "Failed to allocate wireframe mesh".into(), - )); - }; - - bounding_box = bounding_box.union(sphere_mesh.bbox.transform_affine3(&transform)); - - for strip in &sphere_mesh.line_strips { - let box3d = line_batch - .add_strip(strip.iter().map(|&point| transform.transform_point3(point))) - .color(color) - .radius(radius) - .picking_instance_id(PickingLayerInstanceId(i as _)); - - if let Some(outline_mask_ids) = ent_context - .highlight - .instances - .get(&Instance::from(i as u64)) - { - box3d.outline_mask_ids(*outline_mask_ids); + let proc_mesh_key = proc_mesh::ProcMeshKey::Sphere { subdivisions }; + + match data.fill_mode { + FillMode::Wireframe => { + let Some(wireframe_mesh) = + ctx.viewer_ctx + .cache + .entry(|c: &mut proc_mesh::WireframeCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate wireframe mesh".into(), + )); + }; + + obj_space_bounding_box = obj_space_bounding_box + .union(wireframe_mesh.bbox.transform_affine3(&entity_from_mesh)); + + for strip in &wireframe_mesh.line_strips { + let strip_builder = line_batch + .add_strip( + strip + .iter() + .map(|&point| entity_from_mesh.transform_point3(point)), + ) + .color(color) + .radius(radius) + .picking_instance_id(PickingLayerInstanceId(instance_index as _)); + + if let Some(outline_mask_ids) = ent_context + .highlight + .instances + .get(&Instance::from(instance_index as u64)) + { + // Not using ent_context.highlight.index_outline_mask() because + // that's already handled when the builder was created. + strip_builder.outline_mask_ids(*outline_mask_ids); + } + } + } + FillMode::Solid => { + let Some(solid_mesh) = + ctx.viewer_ctx.cache.entry(|c: &mut proc_mesh::SolidCache| { + c.entry(proc_mesh_key, render_ctx) + }) + else { + return Err(SpaceViewSystemExecutionError::DrawDataCreationError( + "Failed to allocate solid mesh".into(), + )); + }; + + obj_space_bounding_box = obj_space_bounding_box + .union(solid_mesh.bbox.transform_affine3(&entity_from_mesh)); + + mesh_instances.push(MeshInstance { + gpu_mesh: solid_mesh.gpu_mesh, + mesh: None, + world_from_mesh: entity_from_mesh, + outline_mask_ids: ent_context.highlight.index_outline_mask(instance), + picking_layer_id: picking_layer_id_from_instance_path_hash( + InstancePathHash::instance(entity_path, instance), + ), + additive_tint: color, + }); } } } self.0.add_bounding_box( entity_path.hash(), - bounding_box, + obj_space_bounding_box, ent_context.world_from_entity, ); } @@ -197,6 +244,8 @@ struct EllipsoidsComponentData<'a> { labels: &'a [Text], keypoint_ids: &'a [KeypointId], class_ids: &'a [ClassId], + + fill_mode: FillMode, } impl IdentifiedViewSystem for EllipsoidsVisualizer { @@ -234,6 +283,9 @@ impl VisualizerSystem for EllipsoidsVisualizer { let mut line_builder = LineDrawableBuilder::new(render_ctx); line_builder.radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES); + // Collects solid (that is, triangles rather than wireframe) instances to be drawn. + let mut solid_instances: Vec = Vec::new(); + super::entity_iterator::process_archetype::( ctx, view_query, @@ -266,16 +318,18 @@ impl VisualizerSystem for EllipsoidsVisualizer { let rotations = results.get_or_empty_dense(resolver)?; let colors = results.get_or_empty_dense(resolver)?; let line_radii = results.get_or_empty_dense(resolver)?; + let fill_mode = results.get_or_empty_dense(resolver)?; let labels = results.get_or_empty_dense(resolver)?; let class_ids = results.get_or_empty_dense(resolver)?; let keypoint_ids = results.get_or_empty_dense(resolver)?; - let data = range_zip_1x7( + let data = re_query::range_zip_1x8( half_sizes.range_indexed(), centers.range_indexed(), rotations.range_indexed(), colors.range_indexed(), line_radii.range_indexed(), + fill_mode.range_indexed(), labels.range_indexed(), class_ids.range_indexed(), keypoint_ids.range_indexed(), @@ -288,6 +342,7 @@ impl VisualizerSystem for EllipsoidsVisualizer { rotations, colors, line_radii, + fill_mode, labels, class_ids, keypoint_ids, @@ -298,6 +353,12 @@ impl VisualizerSystem for EllipsoidsVisualizer { rotations: rotations.unwrap_or_default(), colors: colors.unwrap_or_default(), line_radii: line_radii.unwrap_or_default(), + // fill mode is currently a non-repeated component + fill_mode: fill_mode + .unwrap_or_default() + .first() + .copied() + .unwrap_or_default(), labels: labels.unwrap_or_default(), class_ids: class_ids.unwrap_or_default(), keypoint_ids: keypoint_ids.unwrap_or_default(), @@ -308,6 +369,7 @@ impl VisualizerSystem for EllipsoidsVisualizer { self.process_data( ctx, &mut line_builder, + &mut solid_instances, view_query, spatial_ctx, data, @@ -318,7 +380,24 @@ impl VisualizerSystem for EllipsoidsVisualizer { }, )?; - Ok(vec![(line_builder.into_draw_data()?.into())]) + let wireframe_draw_data: re_renderer::QueueableDrawData = + line_builder.into_draw_data()?.into(); + + let solid_draw_data: Option = + match re_renderer::renderer::MeshDrawData::new(render_ctx, &solid_instances) { + Ok(draw_data) => Some(draw_data.into()), + Err(err) => { + re_log::error_once!( + "Failed to create mesh draw data from mesh instances: {err}" + ); + None + } + }; + + Ok([solid_draw_data, Some(wireframe_draw_data)] + .into_iter() + .flatten() + .collect()) } fn data(&self) -> Option<&dyn std::any::Any> { From eb48eb686d8bedb2fb2d5fb0afac69219d125a44 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 23 Jul 2024 16:12:07 -0700 Subject: [PATCH 16/20] Add `FillMode` to snowman snippets. --- docs/snippets/all/archetypes/ellipsoid_batch.cpp | 1 + docs/snippets/all/archetypes/ellipsoid_batch.py | 1 + docs/snippets/all/archetypes/ellipsoid_batch.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.cpp b/docs/snippets/all/archetypes/ellipsoid_batch.cpp index dd0340520810..68f8a372908e 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.cpp +++ b/docs/snippets/all/archetypes/ellipsoid_batch.cpp @@ -34,5 +34,6 @@ int main() { rerun::Rgba32(0, 0, 0), rerun::Rgba32(0, 0, 0), }) + .with_fill_mode(rerun::FillMode::Solid) ); } diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.py b/docs/snippets/all/archetypes/ellipsoid_batch.py index b8c1aa3db06b..b83f5637ab9d 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.py +++ b/docs/snippets/all/archetypes/ellipsoid_batch.py @@ -31,5 +31,6 @@ (0, 0, 0), (0, 0, 0), ], + fill_mode="solid", ), ) diff --git a/docs/snippets/all/archetypes/ellipsoid_batch.rs b/docs/snippets/all/archetypes/ellipsoid_batch.rs index b6f02af7e1d0..838fac677769 100644 --- a/docs/snippets/all/archetypes/ellipsoid_batch.rs +++ b/docs/snippets/all/archetypes/ellipsoid_batch.rs @@ -30,7 +30,8 @@ fn main() -> Result<(), Box> { rerun::Color::from_rgb(255, 255, 255), rerun::Color::from_rgb(0, 0, 0), rerun::Color::from_rgb(0, 0, 0), - ]), + ]) + .with_fill_mode(rerun::FillMode::Solid), )?; Ok(()) From 58d509faa1328df031a8d99d90dea08d045e9c77 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 23 Jul 2024 16:19:34 -0700 Subject: [PATCH 17/20] Add `FillMode` to `box3d_batch` snippets. --- crates/store/re_types/src/archetypes/boxes3d.rs | 1 + docs/snippets/all/archetypes/box3d_batch.cpp | 1 + docs/snippets/all/archetypes/box3d_batch.py | 1 + docs/snippets/all/archetypes/box3d_batch.rs | 1 + rerun_cpp/src/rerun/archetypes/boxes3d.hpp | 1 + rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py | 1 + 6 files changed, 6 insertions(+) diff --git a/crates/store/re_types/src/archetypes/boxes3d.rs b/crates/store/re_types/src/archetypes/boxes3d.rs index ae315fc10e78..2797a7840720 100644 --- a/crates/store/re_types/src/archetypes/boxes3d.rs +++ b/crates/store/re_types/src/archetypes/boxes3d.rs @@ -44,6 +44,7 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// rerun::Color::from_rgb(0, 255, 0), /// rerun::Color::from_rgb(0, 0, 255), /// ]) +/// .with_fill_mode(rerun::FillMode::Solid) /// .with_labels(["red", "green", "blue"]), /// )?; /// diff --git a/docs/snippets/all/archetypes/box3d_batch.cpp b/docs/snippets/all/archetypes/box3d_batch.cpp index 1bb32e2ef276..f54144ea472a 100644 --- a/docs/snippets/all/archetypes/box3d_batch.cpp +++ b/docs/snippets/all/archetypes/box3d_batch.cpp @@ -24,6 +24,7 @@ int main() { rerun::Rgba32(0, 255, 0), rerun::Rgba32(0, 0, 255), }) + .with_fill_mode(rerun::FillMode::Solid) .with_labels({"red", "green", "blue"}) ); } diff --git a/docs/snippets/all/archetypes/box3d_batch.py b/docs/snippets/all/archetypes/box3d_batch.py index 272d895b371a..ae6964e5a59c 100644 --- a/docs/snippets/all/archetypes/box3d_batch.py +++ b/docs/snippets/all/archetypes/box3d_batch.py @@ -17,6 +17,7 @@ ], radii=0.025, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + fill_mode="solid", labels=["red", "green", "blue"], ), ) diff --git a/docs/snippets/all/archetypes/box3d_batch.rs b/docs/snippets/all/archetypes/box3d_batch.rs index 4bd135abcf1a..54c6e4e07dda 100644 --- a/docs/snippets/all/archetypes/box3d_batch.rs +++ b/docs/snippets/all/archetypes/box3d_batch.rs @@ -20,6 +20,7 @@ fn main() -> Result<(), Box> { rerun::Color::from_rgb(0, 255, 0), rerun::Color::from_rgb(0, 0, 255), ]) + .with_fill_mode(rerun::FillMode::Solid) .with_labels(["red", "green", "blue"]), )?; diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 98ca2f8984fc..0a40990d5a74 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -55,6 +55,7 @@ namespace rerun::archetypes { /// rerun::Rgba32(0, 255, 0), /// rerun::Rgba32(0, 0, 255), /// }) + /// .with_fill_mode(rerun::FillMode::Solid) /// .with_labels({"red", "green", "blue"}) /// ); /// } diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index 7598ab7ec10e..babb131aab04 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -42,6 +42,7 @@ class Boxes3D(Boxes3DExt, Archetype): ], radii=0.025, colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)], + fill_mode="solid", labels=["red", "green", "blue"], ), ) From 56329b10013819ddf73cb84755095b079f6f55b9 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Tue, 23 Jul 2024 19:04:12 -0700 Subject: [PATCH 18/20] Add missing `FillMode` reexport in C++. --- rerun_cpp/src/rerun.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rerun_cpp/src/rerun.hpp b/rerun_cpp/src/rerun.hpp index 2d7220ed5031..261d1c7ef43e 100644 --- a/rerun_cpp/src/rerun.hpp +++ b/rerun_cpp/src/rerun.hpp @@ -32,6 +32,7 @@ namespace rerun { // Also import any component or datatype that has a unique name: using components::AlbedoFactor; using components::Color; + using components::FillMode; using components::HalfSize2D; using components::HalfSize3D; using components::LineStrip2D; From 1a44f2f817cc2e1227407ab8de42058c0cfa0a2d Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 24 Jul 2024 10:14:01 +0200 Subject: [PATCH 19/20] add editor for FillMode --- crates/viewer/re_edit_ui/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_edit_ui/src/lib.rs b/crates/viewer/re_edit_ui/src/lib.rs index 9d013b1309c7..d3fc507ad1c7 100644 --- a/crates/viewer/re_edit_ui/src/lib.rs +++ b/crates/viewer/re_edit_ui/src/lib.rs @@ -24,8 +24,9 @@ use re_types::{ }, components::{ AggregationPolicy, AlbedoFactor, AxisLength, ChannelDataType, Color, ColorModel, Colormap, - DepthMeter, DrawOrder, FillRatio, GammaCorrection, ImagePlaneDistance, MagnificationFilter, - MarkerSize, Name, Opacity, Scale3D, StrokeWidth, Text, TransformRelation, Translation3D, + DepthMeter, DrawOrder, FillMode, FillRatio, GammaCorrection, ImagePlaneDistance, + MagnificationFilter, MarkerSize, Name, Opacity, Scale3D, StrokeWidth, Text, + TransformRelation, Translation3D, }, Loggable as _, }; @@ -71,6 +72,7 @@ pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) { colormap_edit_or_view_ui(ctx.render_ctx, ui, value) }); + // TODO(#6974): Enums editors trivial and always the same, provide them automatically! 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); @@ -78,6 +80,7 @@ pub fn register_editors(registry: &mut 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); registry.add_singleline_edit_or_view::(edit_view_enum); registry.add_singleline_edit_or_view::(edit_view_enum); From 00a51d9edfc885d388089581822fd4a2313353ca Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Wed, 24 Jul 2024 10:19:48 +0200 Subject: [PATCH 20/20] remove defunct py-test from contributor ci (pixi.toml says it's not supposed to run on ci anyways) --- .github/workflows/contrib_rerun_py.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/contrib_rerun_py.yml b/.github/workflows/contrib_rerun_py.yml index 93ac0b767e52..ab742d368e9a 100644 --- a/.github/workflows/contrib_rerun_py.yml +++ b/.github/workflows/contrib_rerun_py.yml @@ -56,9 +56,6 @@ jobs: pixi-version: v0.25.0 environments: wheel-test-min - - name: Run Python unit-tests - run: pixi run -e wheel-test-min py-test - - name: Run e2e test run: pixi run -e wheel-test-min RUST_LOG=debug scripts/run_python_e2e_test.py --no-build # rerun-sdk is already built and installed