diff --git a/crates/re_space_view_spatial/src/visualizers/arrows2d.rs b/crates/re_space_view_spatial/src/visualizers/arrows2d.rs index 1a9f1190d68d..54c8a2d1fa2a 100644 --- a/crates/re_space_view_spatial/src/visualizers/arrows2d.rs +++ b/crates/re_space_view_spatial/src/visualizers/arrows2d.rs @@ -1,4 +1,3 @@ -use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x6; use re_renderer::{renderer::LineStripFlags, LineDrawableBuilder, PickingLayerInstanceId}; @@ -8,34 +7,31 @@ use re_types::{ }; 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, - view_kind::SpatialSpaceViewKind, - visualizers::{filter_visualizable_2d_entities, UiLabel, UiLabelTarget}, + contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind, + visualizers::filter_visualizable_2d_entities, }; use super::{ entity_iterator::clamped, process_annotation_and_keypoint_slices, process_color_slice, - process_radius_slice, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, + process_labels_2d, process_radius_slice, SpatialViewVisualizerData, + SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- pub struct Arrows2DVisualizer { - /// If the number of arrows in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Arrows2DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::TwoD)), } } @@ -44,45 +40,6 @@ impl Default for Arrows2DVisualizer { // 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 Arrows2DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - vectors: &'a [Vector2D], - origins: impl Iterator + 'a, - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_obj: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = clamped(labels, vectors.len()); - let origins = origins.chain(std::iter::repeat(&Position2D::ZERO)); - itertools::izip!(annotation_infos.iter(), vectors, origins, labels, colors) - .enumerate() - .filter_map( - move |(i, (annotation_info, vector, origin, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (vector, label) { - (vector, Some(label)) => { - let midpoint = - // `0.45` rather than `0.5` to account for cap and such - glam::Vec2::from(origin.0) + glam::Vec2::from(vector.0) * 0.45; - let midpoint = world_from_obj.transform_point3(midpoint.extend(0.0)); - - Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Point2D(egui::pos2(midpoint.x, midpoint.y)), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - } - _ => None, - } - }, - ) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -115,31 +72,18 @@ impl Arrows2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - if num_instances <= self.max_labels { - let origins = clamped(data.origins, num_instances); - self.data.ui_labels.extend(Self::process_labels( - entity_path, - data.vectors, - origins, - data.labels, - &colors, - &annotation_infos, - ent_context.world_from_entity, - )); - } - let mut line_batch = line_builder .batch(entity_path.to_string()) .world_from_obj(ent_context.world_from_entity) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); let origins = clamped(data.origins, num_instances).chain(std::iter::repeat(&Position2D::ZERO)); - for (i, (vector, origin, radius, color)) in - itertools::izip!(data.vectors, origins, radii, colors).enumerate() + for (i, (vector, origin, radius, &color)) in + itertools::izip!(data.vectors, origins, radii, &colors).enumerate() { let vector: glam::Vec2 = vector.0.into(); let origin: glam::Vec2 = origin.0.into(); @@ -164,15 +108,44 @@ impl Arrows2DVisualizer { segment.outline_mask_ids(*outline_mask_ids); } - bounding_box.extend(origin.extend(0.0)); - bounding_box.extend(end.extend(0.0)); + obj_space_bounding_box.extend(origin.extend(0.0)); + obj_space_bounding_box.extend(end.extend(0.0)); } self.data.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 arrows 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().truncate(), + )) + } else { + // Take middle point of every arrow. + let origins = clamped(data.origins, num_instances) + .chain(std::iter::repeat(&Position2D::ZERO)); + itertools::Either::Right(data.vectors.iter().zip(origins).map( + |(vector, origin)| { + // `0.45` rather than `0.5` to account for cap and such + (glam::Vec2::from(origin.0) + glam::Vec2::from(vector.0)) * 0.45 + }, + )) + }; + + self.data.ui_labels.extend(process_labels_2d( + entity_path, + label_positions, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/arrows3d.rs b/crates/re_space_view_spatial/src/visualizers/arrows3d.rs index 277ea7156ec3..1ec600e3847d 100644 --- a/crates/re_space_view_spatial/src/visualizers/arrows3d.rs +++ b/crates/re_space_view_spatial/src/visualizers/arrows3d.rs @@ -1,4 +1,3 @@ -use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x6; use re_renderer::{renderer::LineStripFlags, LineDrawableBuilder, PickingLayerInstanceId}; @@ -8,34 +7,31 @@ use re_types::{ }; 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, - view_kind::SpatialSpaceViewKind, - visualizers::{filter_visualizable_3d_entities, UiLabel, UiLabelTarget}, + contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind, + visualizers::filter_visualizable_3d_entities, }; use super::{ entity_iterator::clamped, process_annotation_and_keypoint_slices, process_color_slice, - process_radius_slice, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, + process_labels_3d, process_radius_slice, SpatialViewVisualizerData, + SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- pub struct Arrows3DVisualizer { - /// If the number of arrows in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Arrows3DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::ThreeD)), } } @@ -44,47 +40,6 @@ impl Default for Arrows3DVisualizer { // 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 Arrows3DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - vectors: &'a [Vector3D], - origins: impl Iterator + 'a, - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_obj: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = clamped(labels, vectors.len()); - let origins = origins.chain(std::iter::repeat(&Position3D::ZERO)); - itertools::izip!(annotation_infos.iter(), vectors, origins, labels, colors) - .enumerate() - .filter_map( - move |(i, (annotation_info, vector, origin, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (vector, label) { - (vector, Some(label)) => { - let midpoint = - // `0.45` rather than `0.5` to account for cap and such - (glam::Vec3::from(origin.0) + glam::Vec3::from(vector.0)) * 0.45; - let midpoint = world_from_obj.transform_point3(midpoint); - - Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Position3D( - world_from_obj.transform_point3(midpoint), - ), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - } - _ => None, - } - }, - ) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -117,31 +72,18 @@ impl Arrows3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - if num_instances <= self.max_labels { - let origins = clamped(data.origins, num_instances); - self.data.ui_labels.extend(Self::process_labels( - entity_path, - data.vectors, - origins, - data.labels, - &colors, - &annotation_infos, - ent_context.world_from_entity, - )); - } - let mut line_batch = line_builder .batch(entity_path.to_string()) .world_from_obj(ent_context.world_from_entity) .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); let origins = clamped(data.origins, num_instances).chain(std::iter::repeat(&Position3D::ZERO)); - for (i, (vector, origin, radius, color)) in - itertools::izip!(data.vectors, origins, radii, colors).enumerate() + for (i, (vector, origin, radius, &color)) in + itertools::izip!(data.vectors, origins, radii, &colors).enumerate() { let vector: glam::Vec3 = vector.0.into(); let origin: glam::Vec3 = origin.0.into(); @@ -167,15 +109,43 @@ impl Arrows3DVisualizer { segment.outline_mask_ids(*outline_mask_ids); } - bounding_box.extend(origin); - bounding_box.extend(end); + obj_space_bounding_box.extend(origin); + obj_space_bounding_box.extend(end); } self.data.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 arrows 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 middle point of every arrow. + let origins = clamped(data.origins, num_instances) + .chain(std::iter::repeat(&Position3D::ZERO)); + + itertools::Either::Right(data.vectors.iter().zip(origins).map( + |(vector, origin)| { + // `0.45` rather than `0.5` to account for cap and such + (glam::Vec3::from(origin.0) + glam::Vec3::from(vector.0)) * 0.45 + }, + )) + }; + + self.data.ui_labels.extend(process_labels_3d( + entity_path, + label_positions, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/boxes2d.rs b/crates/re_space_view_spatial/src/visualizers/boxes2d.rs index 369c24782c09..837ff59b76c6 100644 --- a/crates/re_space_view_spatial/src/visualizers/boxes2d.rs +++ b/crates/re_space_view_spatial/src/visualizers/boxes2d.rs @@ -21,22 +21,19 @@ use crate::{ use super::{ entity_iterator::clamped, filter_visualizable_2d_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_2d, + process_radius_slice, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- pub struct Boxes2DVisualizer { - /// If the number of points in the batch is > max_labels, don't render box labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Boxes2DVisualizer { fn default() -> Self { Self { - max_labels: 20, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::TwoD)), } } @@ -45,6 +42,11 @@ impl Default for Boxes2DVisualizer { // 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 Boxes2DVisualizer { + /// Produces 2D rect ui labels from component data. + /// + /// Does nothing if there's no positions or no labels passed. + /// Assumes that there's at least a single color in `colors`. + /// Otherwise, produces one label per center position passed. fn process_labels<'a>( entity_path: &'a EntityPath, half_sizes: &'a [HalfSizes2D], @@ -53,32 +55,35 @@ impl Boxes2DVisualizer { colors: &'a [egui::Color32], annotation_infos: &'a ResolvedAnnotationInfos, ) -> impl Iterator + 'a { - let labels = clamped(labels, half_sizes.len()); - let centers = centers.chain(std::iter::repeat(&Position2D::ZERO)); + debug_assert!( + labels.is_empty() || !colors.is_empty(), + "Cannot add labels without colors" + ); + + let labels = clamped(labels, annotation_infos.len()); + let colors = clamped(colors, annotation_infos.len()); + itertools::izip!(annotation_infos.iter(), half_sizes, centers, labels, colors) .enumerate() .filter_map( move |(i, (annotation_info, half_size, center, label, color))| { let label = annotation_info.label(Some(label.as_str())); - match (half_size, label) { - (half_size, Some(label)) => { - let min = half_size.box_min(*center); - let max = half_size.box_max(*center); - Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Rect(egui::Rect::from_min_max( - egui::pos2(min.x, min.y), - egui::pos2(max.x, max.y), - )), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) + label.map(|label| { + let min = half_size.box_min(*center); + let max = half_size.box_max(*center); + UiLabel { + text: label, + color: *color, + target: UiLabelTarget::Rect(egui::Rect::from_min_max( + egui::pos2(min.x, min.y), + egui::pos2(max.x, max.y), + )), + labeled_instance: InstancePathHash::instance( + entity_path, + Instance::from(i as u64), + ), } - _ => None, - } + }) }, ) } @@ -115,18 +120,6 @@ impl Boxes2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - if num_instances <= self.max_labels { - let centers = clamped(data.centers, num_instances); - self.data.ui_labels.extend(Self::process_labels( - entity_path, - data.half_sizes, - centers, - data.labels, - &colors, - &annotation_infos, - )); - } - let mut line_batch = line_builder .batch("boxes2d") .depth_offset(ent_context.depth_offset) @@ -134,17 +127,17 @@ impl Boxes2DVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); let centers = clamped(data.centers, num_instances).chain(std::iter::repeat(&Position2D::ZERO)); - for (i, (half_size, center, radius, color)) in - itertools::izip!(data.half_sizes, centers, radii, colors).enumerate() + for (i, (half_size, center, radius, &color)) in + itertools::izip!(data.half_sizes, centers, radii, &colors).enumerate() { let min = half_size.box_min(*center); let max = half_size.box_max(*center); - bounding_box.extend(min.extend(0.0)); - bounding_box.extend(max.extend(0.0)); + obj_space_bounding_box.extend(min.extend(0.0)); + obj_space_bounding_box.extend(max.extend(0.0)); let rectangle = line_batch .add_rectangle_outline_2d( @@ -166,9 +159,35 @@ impl Boxes2DVisualizer { self.data.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 data.labels.len() == 1 && num_instances > 1 { + // If there's many boxes but only a single label, place the single label at the middle of the visualization. + // TODO(andreas): A smoothed over time (+ discontinuity detection) bounding box would be great. + self.data.ui_labels.extend(process_labels_2d( + entity_path, + std::iter::once(obj_space_bounding_box.center().truncate()), + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } else { + let centers = clamped(data.centers, num_instances) + .chain(std::iter::repeat(&Position2D::ZERO)); + self.data.ui_labels.extend(Self::process_labels( + entity_path, + data.half_sizes, + centers, + data.labels, + &colors, + &annotation_infos, + )); + } + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/boxes3d.rs b/crates/re_space_view_spatial/src/visualizers/boxes3d.rs index fe39677789ed..abe15cd09979 100644 --- a/crates/re_space_view_spatial/src/visualizers/boxes3d.rs +++ b/crates/re_space_view_spatial/src/visualizers/boxes3d.rs @@ -1,4 +1,3 @@ -use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x7; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; @@ -8,21 +7,17 @@ use re_types::{ }; 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, - view_kind::SpatialSpaceViewKind, - visualizers::{UiLabel, UiLabelTarget}, -}; +use crate::{contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind}; use super::{ entity_iterator::clamped, 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, }; // --- @@ -40,35 +35,6 @@ 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 { - fn process_labels<'a>( - entity_path: &'a EntityPath, - half_sizes: &'a [HalfSizes3D], - centers: impl Iterator + 'a, - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_entity: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = clamped(labels, half_sizes.len()); - let centers = centers.chain(std::iter::repeat(&Position3D::ZERO)); - itertools::izip!(annotation_infos.iter(), centers, labels, colors) - .enumerate() - .filter_map(move |(i, (annotation_info, center, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - label.map(|label| UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Position3D( - world_from_entity.transform_point3(center.0.into()), - ), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - }) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -101,17 +67,6 @@ impl Boxes3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - let centers = clamped(data.centers, num_instances); - self.0.ui_labels.extend(Self::process_labels( - entity_path, - data.half_sizes, - centers, - data.labels, - &colors, - &annotation_infos, - ent_context.world_from_entity, - )); - let mut line_batch = line_builder .batch("boxes3d") .depth_offset(ent_context.depth_offset) @@ -119,17 +74,17 @@ impl Boxes3DVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); let centers = clamped(data.centers, num_instances).chain(std::iter::repeat(&Position3D::ZERO)); let rotations = clamped(data.rotations, num_instances) .chain(std::iter::repeat(&Rotation3D::IDENTITY)); - for (i, (half_size, ¢er, rotation, radius, color)) in - itertools::izip!(data.half_sizes, centers, rotations, radii, colors).enumerate() + for (i, (half_size, ¢er, rotation, radius, &color)) in + itertools::izip!(data.half_sizes, centers, rotations, radii, &colors).enumerate() { - bounding_box.extend(half_size.box_min(center)); - bounding_box.extend(half_size.box_max(center)); + obj_space_bounding_box.extend(half_size.box_min(center)); + obj_space_bounding_box.extend(half_size.box_max(center)); let center = center.into(); @@ -156,9 +111,33 @@ impl Boxes3DVisualizer { 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 boxes 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 box. + itertools::Either::Right( + clamped(data.centers, num_instances) + .chain(std::iter::repeat(&Position3D::ZERO)) + .map(|&c| c.into()), + ) + }; + + self.0.ui_labels.extend(process_labels_3d( + entity_path, + label_positions, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/depth_images.rs b/crates/re_space_view_spatial/src/visualizers/depth_images.rs index 4c1520f277f6..907922b7b5b4 100644 --- a/crates/re_space_view_spatial/src/visualizers/depth_images.rs +++ b/crates/re_space_view_spatial/src/visualizers/depth_images.rs @@ -27,10 +27,7 @@ use crate::{ PickableImageRect, SpatialSpaceView2D, SpatialSpaceView3D, }; -use super::{ - tensor_to_textured_rect, textured_rect_utils::bounding_box_for_textured_rect, - SpatialViewVisualizerData, -}; +use super::{bounding_box_for_textured_rect, tensor_to_textured_rect, SpatialViewVisualizerData}; pub struct DepthImageVisualizer { pub data: SpatialViewVisualizerData, diff --git a/crates/re_space_view_spatial/src/visualizers/images.rs b/crates/re_space_view_spatial/src/visualizers/images.rs index 7fedce26172f..543cedf4048c 100644 --- a/crates/re_space_view_spatial/src/visualizers/images.rs +++ b/crates/re_space_view_spatial/src/visualizers/images.rs @@ -20,10 +20,7 @@ use crate::{ PickableImageRect, SpatialSpaceView2D, }; -use super::{ - tensor_to_textured_rect, textured_rect_utils::bounding_box_for_textured_rect, - SpatialViewVisualizerData, -}; +use super::{bounding_box_for_textured_rect, tensor_to_textured_rect, SpatialViewVisualizerData}; pub struct ImageVisualizer { pub data: SpatialViewVisualizerData, diff --git a/crates/re_space_view_spatial/src/visualizers/lines2d.rs b/crates/re_space_view_spatial/src/visualizers/lines2d.rs index ac025fbbc2a4..6f59df26369c 100644 --- a/crates/re_space_view_spatial/src/visualizers/lines2d.rs +++ b/crates/re_space_view_spatial/src/visualizers/lines2d.rs @@ -1,4 +1,3 @@ -use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x5; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId}; @@ -8,35 +7,28 @@ use re_types::{ }; 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, - view_kind::SpatialSpaceViewKind, - visualizers::{UiLabel, UiLabelTarget}, -}; +use crate::{contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind}; use super::{ - entity_iterator::clamped, filter_visualizable_2d_entities, - process_annotation_and_keypoint_slices, process_color_slice, process_radius_slice, - SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, + filter_visualizable_2d_entities, process_annotation_and_keypoint_slices, process_color_slice, + process_labels_2d, process_radius_slice, SpatialViewVisualizerData, + SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- pub struct Lines2DVisualizer { - /// If the number of arrows in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Lines2DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::TwoD)), } } @@ -45,43 +37,6 @@ impl Default for Lines2DVisualizer { // 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 Lines2DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - strips: &'a [LineStrip2D], - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - ) -> impl Iterator + 'a { - let labels = clamped(labels, strips.len()); - itertools::izip!(annotation_infos.iter(), strips, labels, colors,) - .enumerate() - .filter_map(move |(i, (annotation_info, strip, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (strip, label) { - (strip, Some(label)) => { - let midpoint = strip - .0 - .iter() - .copied() - .map(glam::Vec2::from) - .sum::() - / (strip.0.len() as f32); - - Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Point2D(egui::pos2(midpoint.x, midpoint.y)), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - } - _ => None, - } - }) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -114,16 +69,6 @@ impl Lines2DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - if num_instances <= self.max_labels { - self.data.ui_labels.extend(Self::process_labels( - entity_path, - data.strips, - data.labels, - &colors, - &annotation_infos, - )); - } - let mut line_batch = line_builder .batch(entity_path.to_string()) .depth_offset(ent_context.depth_offset) @@ -131,9 +76,9 @@ impl Lines2DVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); - for (i, (strip, radius, color)) in - itertools::izip!(data.strips, radii, colors).enumerate() + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); + for (i, (strip, radius, &color)) in + itertools::izip!(data.strips, radii, &colors).enumerate() { let lines = line_batch .add_strip_2d(strip.0.iter().copied().map(Into::into)) @@ -150,15 +95,45 @@ impl Lines2DVisualizer { } for p in &strip.0 { - bounding_box.extend(glam::vec3(p.x(), p.y(), 0.0)); + obj_space_bounding_box.extend(glam::vec3(p.x(), p.y(), 0.0)); } } self.data.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 strips but only a single label, place the single label at the middle of the visualization. + let label_positions = if data.labels.len() == 1 && data.strips.len() > 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().truncate(), + )) + } else { + // Take middle point of every strip. + itertools::Either::Right(data.strips.iter().map(|strip| { + strip + .0 + .iter() + .copied() + .map(glam::Vec2::from) + .sum::() + / (strip.0.len() as f32) + })) + }; + + self.data.ui_labels.extend(process_labels_2d( + entity_path, + label_positions, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/lines3d.rs b/crates/re_space_view_spatial/src/visualizers/lines3d.rs index 7a351ee739e1..89971cf5b0e6 100644 --- a/crates/re_space_view_spatial/src/visualizers/lines3d.rs +++ b/crates/re_space_view_spatial/src/visualizers/lines3d.rs @@ -1,4 +1,3 @@ -use re_entity_db::{EntityPath, InstancePathHash}; use re_log_types::Instance; use re_query::range_zip_1x5; use re_renderer::PickingLayerInstanceId; @@ -8,35 +7,30 @@ use re_types::{ }; 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, - view_kind::SpatialSpaceViewKind, - visualizers::{UiLabel, UiLabelTarget}, + contexts::SpatialSceneEntityContext, view_kind::SpatialSpaceViewKind, + visualizers::process_labels_3d, }; use super::{ - entity_iterator::clamped, filter_visualizable_3d_entities, - process_annotation_and_keypoint_slices, process_color_slice, process_radius_slice, - SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, + filter_visualizable_3d_entities, process_annotation_and_keypoint_slices, process_color_slice, + process_radius_slice, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, }; // --- pub struct Lines3DVisualizer { - /// If the number of arrows in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Lines3DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::ThreeD)), } } @@ -45,46 +39,6 @@ impl Default for Lines3DVisualizer { // 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 Lines3DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - strips: &'a [LineStrip3D], - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_obj: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = clamped(labels, strips.len()); - itertools::izip!(annotation_infos.iter(), strips, labels, colors,) - .enumerate() - .filter_map(move |(i, (annotation_info, strip, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (strip, label) { - (strip, Some(label)) => { - let midpoint = strip - .0 - .iter() - .copied() - .map(glam::Vec3::from) - .sum::() - / (strip.0.len() as f32); - - Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Position3D( - world_from_obj.transform_point3(midpoint), - ), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }) - } - _ => None, - } - }) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -117,17 +71,6 @@ impl Lines3DVisualizer { let colors = process_color_slice(ctx, self, num_instances, &annotation_infos, data.colors); - if num_instances <= self.max_labels { - self.data.ui_labels.extend(Self::process_labels( - entity_path, - data.strips, - data.labels, - &colors, - &annotation_infos, - ent_context.world_from_entity, - )); - } - let mut line_batch = line_builder .batch(entity_path.to_string()) .depth_offset(ent_context.depth_offset) @@ -135,11 +78,11 @@ impl Lines3DVisualizer { .outline_mask_ids(ent_context.highlight.overall) .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - let mut bounding_box = macaw::BoundingBox::nothing(); + let mut obj_space_bounding_box = macaw::BoundingBox::nothing(); let mut num_rendered_strips = 0usize; - for (i, (strip, radius, color)) in - itertools::izip!(data.strips, radii, colors).enumerate() + for (i, (strip, radius, &color)) in + itertools::izip!(data.strips, radii, &colors).enumerate() { let lines = line_batch .add_strip(strip.0.iter().copied().map(Into::into)) @@ -156,7 +99,7 @@ impl Lines3DVisualizer { } for p in &strip.0 { - bounding_box.extend((*p).into()); + obj_space_bounding_box.extend((*p).into()); } num_rendered_strips += 1; @@ -165,9 +108,37 @@ impl Lines3DVisualizer { self.data.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 strips but only a single label, place the single label at the middle of the visualization. + let label_positions = if data.labels.len() == 1 && data.strips.len() > 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 middle point of every strip. + itertools::Either::Right(data.strips.iter().map(|strip| { + strip + .0 + .iter() + .copied() + .map(glam::Vec3::from) + .sum::() + / (strip.0.len() as f32) + })) + }; + + self.data.ui_labels.extend(process_labels_3d( + entity_path, + label_positions, + data.labels, + &colors, + &annotation_infos, + ent_context.world_from_entity, + )); + } } } } diff --git a/crates/re_space_view_spatial/src/visualizers/mod.rs b/crates/re_space_view_spatial/src/visualizers/mod.rs index 309faa10a34e..b1c1f247599d 100644 --- a/crates/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/re_space_view_spatial/src/visualizers/mod.rs @@ -7,7 +7,6 @@ mod boxes2d; mod boxes3d; mod cameras; mod depth_images; -mod entity_iterator; mod images; mod lines2d; mod lines3d; @@ -15,23 +14,25 @@ mod meshes; mod points2d; mod points3d; mod segmentation_images; -mod spatial_view_visualizer; -mod textured_rect_utils; mod transform3d_arrows; +mod utilities; pub use cameras::CamerasVisualizer; pub use depth_images::DepthImageVisualizer; pub use images::ImageVisualizer; pub use segmentation_images::SegmentationImageVisualizer; -pub use spatial_view_visualizer::SpatialViewVisualizerData; -pub use textured_rect_utils::tensor_to_textured_rect; pub use transform3d_arrows::{add_axis_arrows, AxisLengthDetector, Transform3DArrowsVisualizer}; +pub use utilities::{ + bounding_box_for_textured_rect, entity_iterator, process_labels_2d, process_labels_3d, + tensor_to_textured_rect, SpatialViewVisualizerData, UiLabel, UiLabelTarget, + MAX_NUM_LABELS_PER_ENTITY, +}; // --- use ahash::HashMap; -use re_entity_db::{EntityPath, InstancePathHash}; +use re_entity_db::EntityPath; use re_types::{ components::Color, datatypes::{KeypointId, KeypointPair}, @@ -277,30 +278,6 @@ fn process_annotation_and_keypoint_slices( } } -#[derive(Clone)] -pub enum UiLabelTarget { - /// Labels a given rect (in scene coordinates) - Rect(egui::Rect), - - /// Labels a given point (in scene coordinates) - Point2D(egui::Pos2), - - /// A point in space. - Position3D(glam::Vec3), -} - -#[derive(Clone)] -pub struct UiLabel { - pub text: String, - pub color: egui::Color32, - - /// The shape/position being labeled. - pub target: UiLabelTarget, - - /// What is hovered if this label is hovered. - pub labeled_instance: InstancePathHash, -} - pub fn load_keypoint_connections( line_builder: &mut re_renderer::LineDrawableBuilder<'_>, ent_context: &SpatialSceneEntityContext<'_>, diff --git a/crates/re_space_view_spatial/src/visualizers/points2d.rs b/crates/re_space_view_spatial/src/visualizers/points2d.rs index 1ec63c315666..f313fd99d35a 100644 --- a/crates/re_space_view_spatial/src/visualizers/points2d.rs +++ b/crates/re_space_view_spatial/src/visualizers/points2d.rs @@ -1,7 +1,5 @@ use itertools::Itertools as _; -use re_entity_db::{EntityPath, InstancePathHash}; -use re_log_types::Instance; use re_query::range_zip_1x5; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder}; use re_types::{ @@ -10,8 +8,8 @@ use re_types::{ }; 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, }; @@ -20,27 +18,24 @@ use crate::{ view_kind::SpatialSpaceViewKind, visualizers::{ load_keypoint_connections, process_annotation_and_keypoint_slices, process_color_slice, - process_radius_slice, UiLabel, UiLabelTarget, + process_radius_slice, }, }; use super::{ - entity_iterator::clamped, filter_visualizable_2d_entities, SpatialViewVisualizerData, + filter_visualizable_2d_entities, process_labels_2d, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES, }; // --- pub struct Points2DVisualizer { - /// If the number of points in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Points2DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::TwoD)), } } @@ -49,33 +44,6 @@ impl Default for Points2DVisualizer { // 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 Points2DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - positions: &'a [glam::Vec3], - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - ) -> impl Iterator + 'a { - let labels = clamped(labels, positions.len()); - itertools::izip!(annotation_infos.iter(), positions, labels, colors) - .enumerate() - .filter_map(move |(i, (annotation_info, point, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (point, label) { - (point, Some(label)) => Some(UiLabel { - text: label, - color: *color, - target: UiLabelTarget::Point2D(egui::pos2(point.x, point.y)), - labeled_instance: InstancePathHash::instance( - entity_path, - Instance::from(i as u64), - ), - }), - _ => None, - } - }) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -150,21 +118,35 @@ impl Points2DVisualizer { } } - self.data.add_bounding_box_from_points( + let obj_space_bounding_box = macaw::BoundingBox::from_points(positions.iter().copied()); + self.data.add_bounding_box( entity_path.hash(), - positions.iter().copied(), + obj_space_bounding_box, ent_context.world_from_entity, ); load_keypoint_connections(line_builder, ent_context, entity_path, &keypoints)?; - if num_instances <= self.max_labels { - self.data.ui_labels.extend(Self::process_labels( + if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { + // If there's many points but only a single label, place the single label at the middle of the visualization. + let label_positions = if data.labels.len() == 1 && data.positions.len() > 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().truncate(), + )) + } else { + itertools::Either::Right( + data.positions.iter().map(|p| glam::vec2(p.x(), p.y())), + ) + }; + + self.data.ui_labels.extend(process_labels_2d( entity_path, - &positions, + label_positions, data.labels, &colors, &annotation_infos, + ent_context.world_from_entity, )); } } diff --git a/crates/re_space_view_spatial/src/visualizers/points3d.rs b/crates/re_space_view_spatial/src/visualizers/points3d.rs index 2f06d005eea8..669221cac753 100644 --- a/crates/re_space_view_spatial/src/visualizers/points3d.rs +++ b/crates/re_space_view_spatial/src/visualizers/points3d.rs @@ -1,7 +1,5 @@ use itertools::Itertools as _; -use re_entity_db::{EntityPath, InstancePathHash}; -use re_log_types::Instance; use re_query::range_zip_1x5; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder}; use re_types::{ @@ -10,8 +8,8 @@ use re_types::{ }; 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, }; @@ -20,27 +18,24 @@ use crate::{ view_kind::SpatialSpaceViewKind, visualizers::{ load_keypoint_connections, process_annotation_and_keypoint_slices, process_color_slice, - process_radius_slice, UiLabel, UiLabelTarget, + process_radius_slice, }, }; use super::{ - entity_iterator::clamped, filter_visualizable_3d_entities, SpatialViewVisualizerData, + filter_visualizable_3d_entities, process_labels_3d, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES, }; // --- pub struct Points3DVisualizer { - /// If the number of points in the batch is > max_labels, don't render point labels. - pub max_labels: usize, pub data: SpatialViewVisualizerData, } impl Default for Points3DVisualizer { fn default() -> Self { Self { - max_labels: 10, data: SpatialViewVisualizerData::new(Some(SpatialSpaceViewKind::ThreeD)), } } @@ -61,34 +56,6 @@ struct Points3DComponentData<'a> { // 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 Points3DVisualizer { - fn process_labels<'a>( - entity_path: &'a EntityPath, - positions: &'a [glam::Vec3], - labels: &'a [Text], - colors: &'a [egui::Color32], - annotation_infos: &'a ResolvedAnnotationInfos, - world_from_obj: glam::Affine3A, - ) -> impl Iterator + 'a { - let labels = clamped(labels, positions.len()); - itertools::izip!(annotation_infos.iter(), positions, labels, colors) - .enumerate() - .filter_map(move |(i, (annotation_info, point, label, color))| { - let label = annotation_info.label(Some(label.as_str())); - match (point, label) { - (point, Some(label)) => Some(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), - ), - }), - _ => None, - } - }) - } - fn process_data<'a>( &mut self, ctx: &QueryContext<'_>, @@ -156,18 +123,29 @@ impl Points3DVisualizer { } } - self.data.add_bounding_box_from_points( + let obj_space_bounding_box = macaw::BoundingBox::from_points(positions.iter().copied()); + self.data.add_bounding_box( entity_path.hash(), - positions.iter().copied(), + obj_space_bounding_box, ent_context.world_from_entity, ); load_keypoint_connections(line_builder, ent_context, entity_path, &keypoints)?; - if num_instances <= self.max_labels { - self.data.ui_labels.extend(Self::process_labels( + if data.labels.len() == 1 || num_instances <= super::MAX_NUM_LABELS_PER_ENTITY { + // If there's many points but only a single label, place the single label at the middle of the visualization. + let obj_space_bbox_center; + let label_positions = if data.labels.len() == 1 && positions.len() > 1 { + // TODO(andreas): A smoothed over time (+ discontinuity detection) bounding box would be great. + obj_space_bbox_center = [obj_space_bounding_box.center()]; + &obj_space_bbox_center + } else { + positions + }; + + self.data.ui_labels.extend(process_labels_3d( entity_path, - positions, + label_positions.iter().copied(), data.labels, &colors, &annotation_infos, diff --git a/crates/re_space_view_spatial/src/visualizers/segmentation_images.rs b/crates/re_space_view_spatial/src/visualizers/segmentation_images.rs index e8284e1db594..9a0b63e1d6fa 100644 --- a/crates/re_space_view_spatial/src/visualizers/segmentation_images.rs +++ b/crates/re_space_view_spatial/src/visualizers/segmentation_images.rs @@ -20,10 +20,7 @@ use crate::{ visualizers::filter_visualizable_2d_entities, PickableImageRect, SpatialSpaceView2D, }; -use super::{ - tensor_to_textured_rect, textured_rect_utils::bounding_box_for_textured_rect, - SpatialViewVisualizerData, -}; +use super::{bounding_box_for_textured_rect, tensor_to_textured_rect, SpatialViewVisualizerData}; pub struct SegmentationImageVisualizer { pub data: SpatialViewVisualizerData, diff --git a/crates/re_space_view_spatial/src/visualizers/entity_iterator.rs b/crates/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs similarity index 100% rename from crates/re_space_view_spatial/src/visualizers/entity_iterator.rs rename to crates/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs diff --git a/crates/re_space_view_spatial/src/visualizers/utilities/labels.rs b/crates/re_space_view_spatial/src/visualizers/utilities/labels.rs new file mode 100644 index 000000000000..c1522cad0676 --- /dev/null +++ b/crates/re_space_view_spatial/src/visualizers/utilities/labels.rs @@ -0,0 +1,108 @@ +use re_entity_db::InstancePathHash; +use re_log_types::{EntityPath, Instance}; +use re_viewer_context::ResolvedAnnotationInfos; + +use super::entity_iterator::clamped; + +#[derive(Clone)] +pub enum UiLabelTarget { + /// Labels a given rect (in scene coordinates) + Rect(egui::Rect), + + /// Labels a given point (in scene coordinates) + Point2D(egui::Pos2), + + /// A point in space. + Position3D(glam::Vec3), +} + +#[derive(Clone)] +pub struct UiLabel { + pub text: String, + pub color: egui::Color32, + + /// The shape/position being labeled. + pub target: UiLabelTarget, + + /// What is hovered if this label is hovered. + pub labeled_instance: InstancePathHash, +} + +/// Maximum number of labels after which we stop displaying labels for that entity all together. +/// +/// TODO(#4451): Hiding of labels should be configurable. This can be the heuristic for it. +pub const MAX_NUM_LABELS_PER_ENTITY: usize = 30; + +/// Produces 3D ui labels from component data. +/// +/// Does nothing if there's no positions or no labels passed. +/// Assumes that there's at least a single color in `colors`. +/// Otherwise, produces one label per position passed. +pub fn process_labels_3d<'a>( + entity_path: &'a EntityPath, + positions: impl Iterator + 'a, + labels: &'a [re_types::components::Text], + colors: &'a [egui::Color32], + annotation_infos: &'a ResolvedAnnotationInfos, + world_from_obj: glam::Affine3A, +) -> impl Iterator + 'a { + debug_assert!( + labels.is_empty() || !colors.is_empty(), + "Cannot add labels without colors" + ); + + let labels = clamped(labels, annotation_infos.len()); + let colors = clamped(colors, annotation_infos.len()); + + itertools::izip!(annotation_infos.iter(), positions, labels, colors) + .enumerate() + .filter_map(move |(i, (annotation_info, point, label, color))| { + let label = annotation_info.label(Some(label.as_str())); + 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)), + }) + }) +} + +/// Produces 2D ui labels from component data. +/// +/// Does nothing if there's no positions or no labels passed. +/// Assumes that there's at least a single color in `colors`. +/// Otherwise, produces one label per position passed. +pub fn process_labels_2d<'a>( + entity_path: &'a EntityPath, + positions: impl Iterator + 'a, + labels: &'a [re_types::components::Text], + colors: &'a [egui::Color32], + annotation_infos: &'a ResolvedAnnotationInfos, + world_from_obj: glam::Affine3A, +) -> impl Iterator + 'a { + debug_assert!( + labels.is_empty() || !colors.is_empty(), + "Cannot add labels without colors" + ); + + let labels = clamped(labels, annotation_infos.len()); + let colors = clamped(colors, annotation_infos.len()); + + itertools::izip!(annotation_infos.iter(), positions, labels, colors) + .enumerate() + .filter_map(move |(i, (annotation_info, point, label, color))| { + let label = annotation_info.label(Some(label.as_str())); + label.map(|label| { + let point = world_from_obj.transform_point3(glam::Vec3::new(point.x, point.y, 0.0)); + UiLabel { + text: label, + color: *color, + target: UiLabelTarget::Point2D(egui::pos2(point.x, point.y)), + labeled_instance: InstancePathHash::instance( + entity_path, + Instance::from(i as u64), + ), + } + }) + }) +} diff --git a/crates/re_space_view_spatial/src/visualizers/utilities/mod.rs b/crates/re_space_view_spatial/src/visualizers/utilities/mod.rs new file mode 100644 index 000000000000..176f925cd2cd --- /dev/null +++ b/crates/re_space_view_spatial/src/visualizers/utilities/mod.rs @@ -0,0 +1,10 @@ +pub mod entity_iterator; +mod labels; +mod spatial_view_visualizer; +mod textured_rect; + +pub use labels::{ + process_labels_2d, process_labels_3d, UiLabel, UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, +}; +pub use spatial_view_visualizer::SpatialViewVisualizerData; +pub use textured_rect::{bounding_box_for_textured_rect, tensor_to_textured_rect}; diff --git a/crates/re_space_view_spatial/src/visualizers/spatial_view_visualizer.rs b/crates/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs similarity index 95% rename from crates/re_space_view_spatial/src/visualizers/spatial_view_visualizer.rs rename to crates/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs index 59f46bbe226d..0cd0fbb02ed8 100644 --- a/crates/re_space_view_spatial/src/visualizers/spatial_view_visualizer.rs +++ b/crates/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs @@ -1,6 +1,7 @@ use re_log_types::EntityPathHash; -use crate::{view_kind::SpatialSpaceViewKind, visualizers::UiLabel}; +use super::UiLabel; +use crate::view_kind::SpatialSpaceViewKind; /// Common data struct for all spatial scene elements. /// diff --git a/crates/re_space_view_spatial/src/visualizers/textured_rect_utils.rs b/crates/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs similarity index 100% rename from crates/re_space_view_spatial/src/visualizers/textured_rect_utils.rs rename to crates/re_space_view_spatial/src/visualizers/utilities/textured_rect.rs diff --git a/crates/re_types/definitions/rerun/archetypes/arrows2d.fbs b/crates/re_types/definitions/rerun/archetypes/arrows2d.fbs index fb14e4c7bdfa..9058caf84c47 100644 --- a/crates/re_types/definitions/rerun/archetypes/arrows2d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/arrows2d.fbs @@ -43,6 +43,9 @@ table Arrows2D ( colors: [rerun.components.Color] ("attr.rerun.component_optional", nullable, order: 3100); /// Optional text labels for the arrows. + /// + /// 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: 3200); /// An optional floating point value that specifies the 2D drawing order. diff --git a/crates/re_types/definitions/rerun/archetypes/arrows3d.fbs b/crates/re_types/definitions/rerun/archetypes/arrows3d.fbs index 6168e0497e69..9efcdba20ba3 100644 --- a/crates/re_types/definitions/rerun/archetypes/arrows3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/arrows3d.fbs @@ -43,6 +43,9 @@ table Arrows3D ( colors: [rerun.components.Color] ("attr.rerun.component_optional", nullable, order: 3100); /// Optional text labels for the arrows. + /// + /// 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: 3200); /// Optional class Ids for the points. diff --git a/crates/re_types/definitions/rerun/archetypes/boxes2d.fbs b/crates/re_types/definitions/rerun/archetypes/boxes2d.fbs index 2f5fe36574e4..042a259f9f61 100644 --- a/crates/re_types/definitions/rerun/archetypes/boxes2d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/boxes2d.fbs @@ -42,6 +42,9 @@ table Boxes2D ( radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 2500); /// 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: 3000); /// An optional floating point value that specifies the 2D drawing order. diff --git a/crates/re_types/definitions/rerun/archetypes/boxes3d.fbs b/crates/re_types/definitions/rerun/archetypes/boxes3d.fbs index a7f6141c5ad6..2d20345f8d21 100644 --- a/crates/re_types/definitions/rerun/archetypes/boxes3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/boxes3d.fbs @@ -42,6 +42,9 @@ table Boxes3D ( radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3000); /// 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); /// Optional `ClassId`s for the boxes. diff --git a/crates/re_types/definitions/rerun/archetypes/line_strips2d.fbs b/crates/re_types/definitions/rerun/archetypes/line_strips2d.fbs index 5179dfe4697e..05d30ef7e364 100644 --- a/crates/re_types/definitions/rerun/archetypes/line_strips2d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/line_strips2d.fbs @@ -34,6 +34,9 @@ table LineStrips2D ( // --- Optional --- /// Optional text labels for the line strips. + /// + /// 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: 3000); /// An optional floating point value that specifies the 2D drawing order of each line strip. diff --git a/crates/re_types/definitions/rerun/archetypes/line_strips3d.fbs b/crates/re_types/definitions/rerun/archetypes/line_strips3d.fbs index f8abe82bb49d..2b5c6f012bd0 100644 --- a/crates/re_types/definitions/rerun/archetypes/line_strips3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/line_strips3d.fbs @@ -34,6 +34,9 @@ table LineStrips3D ( // --- Optional --- /// Optional text labels for the line strips. + /// + /// 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: 3000); /// Optional `ClassId`s for the lines. diff --git a/crates/re_types/definitions/rerun/archetypes/points2d.fbs b/crates/re_types/definitions/rerun/archetypes/points2d.fbs index 48f729476dfe..86f79d9c0f21 100644 --- a/crates/re_types/definitions/rerun/archetypes/points2d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/points2d.fbs @@ -38,6 +38,9 @@ table Points2D ( // --- Optional --- /// Optional text labels for the points. + /// + /// 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: 3000); /// An optional floating point value that specifies the 2D drawing order. diff --git a/crates/re_types/definitions/rerun/archetypes/points3d.fbs b/crates/re_types/definitions/rerun/archetypes/points3d.fbs index d192e685cc17..ba1047777390 100644 --- a/crates/re_types/definitions/rerun/archetypes/points3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/points3d.fbs @@ -36,6 +36,9 @@ table Points3D ( // --- Optional --- /// Optional text labels for the points. + /// + /// 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: 3000); /// Optional class Ids for the points. diff --git a/crates/re_types/src/archetypes/arrows2d.rs b/crates/re_types/src/archetypes/arrows2d.rs index b039def222d9..817ae17132c7 100644 --- a/crates/re_types/src/archetypes/arrows2d.rs +++ b/crates/re_types/src/archetypes/arrows2d.rs @@ -72,6 +72,9 @@ pub struct Arrows2D { pub colors: Option>, /// Optional text labels for the arrows. + /// + /// 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. pub labels: Option>, /// An optional floating point value that specifies the 2D drawing order. @@ -380,6 +383,9 @@ impl Arrows2D { } /// Optional text labels for the arrows. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/arrows3d.rs b/crates/re_types/src/archetypes/arrows3d.rs index eaa3bdcf75cb..d9156774ae89 100644 --- a/crates/re_types/src/archetypes/arrows3d.rs +++ b/crates/re_types/src/archetypes/arrows3d.rs @@ -85,6 +85,9 @@ pub struct Arrows3D { pub colors: Option>, /// Optional text labels for the arrows. + /// + /// 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. pub labels: Option>, /// Optional class Ids for the points. @@ -370,6 +373,9 @@ impl Arrows3D { } /// Optional text labels for the arrows. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/boxes2d.rs b/crates/re_types/src/archetypes/boxes2d.rs index 9ea14074d538..db597c8996d5 100644 --- a/crates/re_types/src/archetypes/boxes2d.rs +++ b/crates/re_types/src/archetypes/boxes2d.rs @@ -63,6 +63,9 @@ pub struct Boxes2D { pub radii: Option>, /// 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. pub labels: Option>, /// An optional floating point value that specifies the 2D drawing order. @@ -368,6 +371,9 @@ impl Boxes2D { } /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/boxes3d.rs b/crates/re_types/src/archetypes/boxes3d.rs index bb3a580f58a2..83717e27740f 100644 --- a/crates/re_types/src/archetypes/boxes3d.rs +++ b/crates/re_types/src/archetypes/boxes3d.rs @@ -81,6 +81,9 @@ pub struct Boxes3D { pub radii: Option>, /// 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. pub labels: Option>, /// Optional `ClassId`s for the boxes. @@ -392,6 +395,9 @@ impl Boxes3D { } /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/line_strips2d.rs b/crates/re_types/src/archetypes/line_strips2d.rs index 651f473adc6b..3be0d151450a 100644 --- a/crates/re_types/src/archetypes/line_strips2d.rs +++ b/crates/re_types/src/archetypes/line_strips2d.rs @@ -99,6 +99,9 @@ pub struct LineStrips2D { pub colors: Option>, /// Optional text labels for the line strips. + /// + /// 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. pub labels: Option>, /// An optional floating point value that specifies the 2D drawing order of each line strip. @@ -371,6 +374,9 @@ impl LineStrips2D { } /// Optional text labels for the line strips. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/line_strips3d.rs b/crates/re_types/src/archetypes/line_strips3d.rs index 386c4a410b26..04e1066b395e 100644 --- a/crates/re_types/src/archetypes/line_strips3d.rs +++ b/crates/re_types/src/archetypes/line_strips3d.rs @@ -114,6 +114,9 @@ pub struct LineStrips3D { pub colors: Option>, /// Optional text labels for the line strips. + /// + /// 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. pub labels: Option>, /// Optional `ClassId`s for the lines. @@ -363,6 +366,9 @@ impl LineStrips3D { } /// Optional text labels for the line strips. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/points2d.rs b/crates/re_types/src/archetypes/points2d.rs index 64eef93ba595..9636269ab576 100644 --- a/crates/re_types/src/archetypes/points2d.rs +++ b/crates/re_types/src/archetypes/points2d.rs @@ -112,6 +112,9 @@ pub struct Points2D { pub colors: Option>, /// Optional text labels for the points. + /// + /// 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. pub labels: Option>, /// An optional floating point value that specifies the 2D drawing order. @@ -415,6 +418,9 @@ impl Points2D { } /// Optional text labels for the points. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_types/src/archetypes/points3d.rs b/crates/re_types/src/archetypes/points3d.rs index 3e1657f894c0..2b0e3ef05806 100644 --- a/crates/re_types/src/archetypes/points3d.rs +++ b/crates/re_types/src/archetypes/points3d.rs @@ -110,6 +110,9 @@ pub struct Points3D { pub colors: Option>, /// Optional text labels for the points. + /// + /// 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. pub labels: Option>, /// Optional class Ids for the points. @@ -390,6 +393,9 @@ impl Points3D { } /// Optional text labels for the points. + /// + /// 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. #[inline] pub fn with_labels( mut self, diff --git a/crates/re_viewer_context/src/annotations.rs b/crates/re_viewer_context/src/annotations.rs index 8951e017cc31..0fd6ca04e081 100644 --- a/crates/re_viewer_context/src/annotations.rs +++ b/crates/re_viewer_context/src/annotations.rs @@ -197,6 +197,23 @@ impl ResolvedAnnotationInfos { Self::Many(infos) => Either::Right(infos.iter()), } } + + #[inline] + pub fn len(&self) -> usize { + match self { + Self::Same(n, _) => *n, + Self::Many(infos) => infos.len(), + } + } + + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + match self { + Self::Same(n, _) => *n == 0, + Self::Many(infos) => infos.is_empty(), + } + } } // ---------------------------------------------------------------------------- diff --git a/rerun_cpp/src/rerun/archetypes/arrows2d.hpp b/rerun_cpp/src/rerun/archetypes/arrows2d.hpp index 7d2fe4606bc2..7307d0715ddd 100644 --- a/rerun_cpp/src/rerun/archetypes/arrows2d.hpp +++ b/rerun_cpp/src/rerun/archetypes/arrows2d.hpp @@ -65,6 +65,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the arrows. + /// + /// 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. std::optional> labels; /// An optional floating point value that specifies the 2D drawing order. @@ -124,6 +127,9 @@ namespace rerun::archetypes { } /// Optional text labels for the arrows. + /// + /// 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. Arrows2D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/arrows3d.hpp b/rerun_cpp/src/rerun/archetypes/arrows3d.hpp index b801d3996cca..96cf48cf1f3b 100644 --- a/rerun_cpp/src/rerun/archetypes/arrows3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/arrows3d.hpp @@ -80,6 +80,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the arrows. + /// + /// 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. std::optional> labels; /// Optional class Ids for the points. @@ -135,6 +138,9 @@ namespace rerun::archetypes { } /// Optional text labels for the arrows. + /// + /// 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. Arrows3D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/boxes2d.hpp b/rerun_cpp/src/rerun/archetypes/boxes2d.hpp index 8742f92b6cfc..d4cb5a04e728 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes2d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes2d.hpp @@ -53,6 +53,9 @@ namespace rerun::archetypes { std::optional> radii; /// 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. std::optional> labels; /// An optional floating point value that specifies the 2D drawing order. @@ -148,6 +151,9 @@ namespace rerun::archetypes { } /// 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. Boxes2D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp index 195eb5421a94..b2f493ec15b2 100644 --- a/rerun_cpp/src/rerun/archetypes/boxes3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/boxes3d.hpp @@ -75,6 +75,9 @@ namespace rerun::archetypes { std::optional> radii; /// 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. std::optional> labels; /// Optional `ClassId`s for the boxes. @@ -173,6 +176,9 @@ namespace rerun::archetypes { } /// 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. Boxes3D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/line_strips2d.hpp b/rerun_cpp/src/rerun/archetypes/line_strips2d.hpp index e4188ff6b2cc..4067d2113bc2 100644 --- a/rerun_cpp/src/rerun/archetypes/line_strips2d.hpp +++ b/rerun_cpp/src/rerun/archetypes/line_strips2d.hpp @@ -98,6 +98,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the line strips. + /// + /// 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. std::optional> labels; /// An optional floating point value that specifies the 2D drawing order of each line strip. @@ -139,6 +142,9 @@ namespace rerun::archetypes { } /// Optional text labels for the line strips. + /// + /// 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. LineStrips2D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/line_strips3d.hpp b/rerun_cpp/src/rerun/archetypes/line_strips3d.hpp index 8cda61271eba..b930e4b76096 100644 --- a/rerun_cpp/src/rerun/archetypes/line_strips3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/line_strips3d.hpp @@ -110,6 +110,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the line strips. + /// + /// 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. std::optional> labels; /// Optional `ClassId`s for the lines. @@ -146,6 +149,9 @@ namespace rerun::archetypes { } /// Optional text labels for the line strips. + /// + /// 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. LineStrips3D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/points2d.hpp b/rerun_cpp/src/rerun/archetypes/points2d.hpp index dd9ff2b8e054..c207744475f6 100644 --- a/rerun_cpp/src/rerun/archetypes/points2d.hpp +++ b/rerun_cpp/src/rerun/archetypes/points2d.hpp @@ -114,6 +114,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the points. + /// + /// 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. std::optional> labels; /// An optional floating point value that specifies the 2D drawing order. @@ -164,6 +167,9 @@ namespace rerun::archetypes { } /// Optional text labels for the points. + /// + /// 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. Points2D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_cpp/src/rerun/archetypes/points3d.hpp b/rerun_cpp/src/rerun/archetypes/points3d.hpp index a3ec21c61c23..9a9b3f12874c 100644 --- a/rerun_cpp/src/rerun/archetypes/points3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/points3d.hpp @@ -109,6 +109,9 @@ namespace rerun::archetypes { std::optional> colors; /// Optional text labels for the points. + /// + /// 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. std::optional> labels; /// Optional class Ids for the points. @@ -154,6 +157,9 @@ namespace rerun::archetypes { } /// Optional text labels for the points. + /// + /// 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. Points3D with_labels(Collection _labels) && { labels = std::move(_labels); // See: https://github.com/rerun-io/rerun/issues/4027 diff --git a/rerun_py/rerun_sdk/rerun/archetypes/arrows2d.py b/rerun_py/rerun_sdk/rerun/archetypes/arrows2d.py index 045251637770..805d65b312e8 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/arrows2d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/arrows2d.py @@ -120,6 +120,9 @@ def _clear(cls) -> Arrows2D: ) # Optional text labels for the arrows. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) draw_order: components.DrawOrderBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/arrows3d.py b/rerun_py/rerun_sdk/rerun/archetypes/arrows3d.py index 136032200874..c8d9ae04ae7e 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/arrows3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/arrows3d.py @@ -119,6 +119,9 @@ def _clear(cls) -> Arrows3D: ) # Optional text labels for the arrows. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) class_ids: components.ClassIdBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes2d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes2d.py index 4263784bb717..09af5d869226 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes2d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes2d.py @@ -106,6 +106,9 @@ def _clear(cls) -> Boxes2D: ) # 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. + # # (Docstring intentionally commented out to hide this field from the docs) draw_order: components.DrawOrderBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py index c5cdf206da10..3823585f7259 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/boxes3d.py @@ -130,6 +130,9 @@ def _clear(cls) -> Boxes3D: ) # 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. + # # (Docstring intentionally commented out to hide this field from the docs) class_ids: components.ClassIdBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/line_strips2d.py b/rerun_py/rerun_sdk/rerun/archetypes/line_strips2d.py index d700518ad521..e98c3e729218 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/line_strips2d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/line_strips2d.py @@ -116,6 +116,9 @@ def __init__( Optional colors for the line strips. labels: Optional text labels for the line strips. + + 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. draw_order: An optional floating point value that specifies the 2D drawing order of each line strip. @@ -186,6 +189,9 @@ def _clear(cls) -> LineStrips2D: ) # Optional text labels for the line strips. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) draw_order: components.DrawOrderBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/line_strips3d.py b/rerun_py/rerun_sdk/rerun/archetypes/line_strips3d.py index 7b266e522e80..5de092c0d477 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/line_strips3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/line_strips3d.py @@ -134,6 +134,9 @@ def __init__( Optional colors for the line strips. labels: Optional text labels for the line strips. + + 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. class_ids: Optional `ClassId`s for the lines. @@ -197,6 +200,9 @@ def _clear(cls) -> LineStrips3D: ) # Optional text labels for the line strips. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) class_ids: components.ClassIdBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/points2d.py b/rerun_py/rerun_sdk/rerun/archetypes/points2d.py index ebbf205bee35..fc5ea795de6b 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/points2d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/points2d.py @@ -154,6 +154,9 @@ def _clear(cls) -> Points2D: ) # Optional text labels for the points. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) draw_order: components.DrawOrderBatch | None = field( diff --git a/rerun_py/rerun_sdk/rerun/archetypes/points3d.py b/rerun_py/rerun_sdk/rerun/archetypes/points3d.py index 873a6f2a0145..b30955ac4748 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/points3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/points3d.py @@ -145,6 +145,9 @@ def _clear(cls) -> Points3D: ) # Optional text labels for the points. # + # 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. + # # (Docstring intentionally commented out to hide this field from the docs) class_ids: components.ClassIdBatch | None = field(