diff --git a/Cargo.lock b/Cargo.lock index f732eaafaa51..a3983b9840b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4272,6 +4272,7 @@ dependencies = [ "itertools", "macaw", "nohash-hasher", + "parking_lot 0.12.1", "re_arrow_store", "re_components", "re_data_store", @@ -4288,6 +4289,7 @@ dependencies = [ "re_viewer_context", "serde", "smallvec", + "vec1", "wgpu", ] @@ -4304,6 +4306,8 @@ dependencies = [ "re_log", "re_log_types", "re_query", + "re_renderer", + "re_space_view", "re_tracing", "re_ui", "re_viewer_context", @@ -4319,6 +4323,8 @@ dependencies = [ "re_components", "re_log", "re_query", + "re_renderer", + "re_space_view", "re_ui", "re_viewer_context", "vec1", @@ -4486,6 +4492,7 @@ dependencies = [ "serde", "slotmap", "thiserror", + "tinyvec", "uuid", "vec1", "wgpu", diff --git a/crates/re_data_ui/src/lib.rs b/crates/re_data_ui/src/lib.rs index 6ff26917fe23..286fc0053a31 100644 --- a/crates/re_data_ui/src/lib.rs +++ b/crates/re_data_ui/src/lib.rs @@ -159,6 +159,7 @@ pub fn annotations( let entity_paths: nohash_hasher::IntSet<_> = std::iter::once(entity_path.clone()).collect(); let entity_props_map = re_data_store::EntityPropertyMap::default(); let scene_query = re_viewer_context::SceneQuery { + space_origin: entity_path, entity_paths: &entity_paths, timeline: query.timeline, latest_at: query.at, diff --git a/crates/re_space_view/src/empty_scene_context.rs b/crates/re_space_view/src/empty_scene_context.rs new file mode 100644 index 000000000000..2e43c66318c3 --- /dev/null +++ b/crates/re_space_view/src/empty_scene_context.rs @@ -0,0 +1,19 @@ +use re_viewer_context::{SceneContext, SceneContextPart}; + +/// Implementation of an empty scene context. +#[derive(Default)] +pub struct EmptySceneContext; + +impl SceneContext for EmptySceneContext { + fn vec_mut(&mut self) -> Vec<&mut dyn SceneContextPart> { + Vec::new() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} diff --git a/crates/re_space_view/src/empty_space_view_state.rs b/crates/re_space_view/src/empty_space_view_state.rs new file mode 100644 index 000000000000..671dfa29ca70 --- /dev/null +++ b/crates/re_space_view/src/empty_space_view_state.rs @@ -0,0 +1,15 @@ +use re_viewer_context::SpaceViewState; + +/// Space view state without any contents. +#[derive(Default)] +pub struct EmptySpaceViewState; + +impl SpaceViewState for EmptySpaceViewState { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} diff --git a/crates/re_space_view/src/lib.rs b/crates/re_space_view/src/lib.rs index 8ccc845b334d..1ac9cc4cb7aa 100644 --- a/crates/re_space_view/src/lib.rs +++ b/crates/re_space_view/src/lib.rs @@ -4,9 +4,11 @@ pub mod controls; mod data_blueprint; -mod highlights; +mod empty_scene_context; +mod empty_space_view_state; mod screenshot; pub use data_blueprint::{DataBlueprintGroup, DataBlueprintTree}; -pub use highlights::{SpaceViewEntityHighlight, SpaceViewHighlights, SpaceViewOutlineMasks}; +pub use empty_scene_context::EmptySceneContext; +pub use empty_space_view_state::EmptySpaceViewState; pub use screenshot::ScreenshotMode; diff --git a/crates/re_space_view_spatial/Cargo.toml b/crates/re_space_view_spatial/Cargo.toml index aac374867e03..4390f337537c 100644 --- a/crates/re_space_view_spatial/Cargo.toml +++ b/crates/re_space_view_spatial/Cargo.toml @@ -34,12 +34,14 @@ re_space_view.workspace = true ahash.workspace = true anyhow.workspace = true bytemuck.workspace = true -egui.workspace = true eframe.workspace = true +egui.workspace = true glam.workspace = true itertools.workspace = true macaw = { workspace = true, features = ["with_serde"] } nohash-hasher.workspace = true +parking_lot.workspace = true serde = "1" smallvec = { workspace = true, features = ["serde"] } +vec1.workspace = true wgpu.workspace = true diff --git a/crates/re_space_view_spatial/src/lib.rs b/crates/re_space_view_spatial/src/lib.rs index 49e97d6463ac..0b3caeb53360 100644 --- a/crates/re_space_view_spatial/src/lib.rs +++ b/crates/re_space_view_spatial/src/lib.rs @@ -8,14 +8,14 @@ mod mesh_cache; mod mesh_loader; mod scene; mod space_camera_3d; -mod transform_cache; +mod space_view_class; mod ui; mod ui_2d; mod ui_3d; mod ui_renderer_bridge; // TODO(andreas) should only make the main type public +pub use space_view_class::SpatialSpaceViewClass; -pub use scene::SceneSpatial; -pub use transform_cache::{TransformCache, UnreachableTransform}; +pub use scene::{SceneSpatial, TransformContext, UnreachableTransform}; pub use ui::{SpatialNavigationMode, ViewSpatialState}; diff --git a/crates/re_space_view_spatial/src/scene/contexts/annotation_context.rs b/crates/re_space_view_spatial/src/scene/contexts/annotation_context.rs new file mode 100644 index 000000000000..665535ab6195 --- /dev/null +++ b/crates/re_space_view_spatial/src/scene/contexts/annotation_context.rs @@ -0,0 +1,21 @@ +use re_components::AnnotationContext; +use re_log_types::Component; +use re_viewer_context::{AnnotationMap, ArchetypeDefinition, SceneContextPart}; + +#[derive(Default)] +pub struct AnnotationSceneContext(pub AnnotationMap); + +impl SceneContextPart for AnnotationSceneContext { + fn archetypes(&self) -> Vec { + vec![vec1::vec1![AnnotationContext::name()]] + } + + fn populate( + &mut self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + query: &re_viewer_context::SceneQuery<'_>, + _space_view_state: &dyn re_viewer_context::SpaceViewState, + ) { + self.0.load(ctx, query); + } +} diff --git a/crates/re_space_view_spatial/src/scene/contexts/depth_offsets.rs b/crates/re_space_view_spatial/src/scene/contexts/depth_offsets.rs new file mode 100644 index 000000000000..ad5b4468b977 --- /dev/null +++ b/crates/re_space_view_spatial/src/scene/contexts/depth_offsets.rs @@ -0,0 +1,120 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use nohash_hasher::IntMap; +use re_components::DrawOrder; +use re_log_types::{Component, EntityPathHash}; +use re_viewer_context::{ArchetypeDefinition, SceneContextPart}; + +/// Context for creating a mapping from [`DrawOrder`] to [`re_renderer::DepthOffset`]. +#[derive(Default)] +pub struct EntityDepthOffsets { + // TODO(wumpf): Given that archetypes (should) contain DrawData, we should have a map of DrawData to DepthOffset. + // Mapping entities to depth offset instead is inconsistent with the archetype queries which are + // expected to care about DepthOffset iff they can make use of it. + pub per_entity: IntMap, + pub box2d: re_renderer::DepthOffset, + pub lines2d: re_renderer::DepthOffset, + pub image: re_renderer::DepthOffset, + pub points: re_renderer::DepthOffset, +} + +impl SceneContextPart for EntityDepthOffsets { + fn archetypes(&self) -> Vec { + vec![vec1::vec1![DrawOrder::name()]] + } + + fn populate( + &mut self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + query: &re_viewer_context::SceneQuery<'_>, + _space_view_state: &dyn re_viewer_context::SpaceViewState, + ) { + re_tracing::profile_function!(); + + #[derive(PartialEq, PartialOrd, Eq, Ord)] + enum DrawOrderTarget { + Entity(EntityPathHash), + DefaultBox2D, + DefaultLines2D, + DefaultImage, + DefaultPoints, + } + + let store = &ctx.store_db.entity_db.data_store; + + // Use a BTreeSet for entity hashes to get a stable order. + let mut entities_per_draw_order = BTreeMap::>::new(); + for (ent_path, _) in query.iter_entities() { + if let Some(draw_order) = store.query_latest_component::( + ent_path, + &ctx.rec_cfg.time_ctrl.current_query(), + ) { + entities_per_draw_order + .entry(draw_order) + .or_default() + .insert(DrawOrderTarget::Entity(ent_path.hash())); + } + } + + // Push in default draw orders. All of them using the none hash. + entities_per_draw_order.insert( + DrawOrder::DEFAULT_BOX2D, + [DrawOrderTarget::DefaultBox2D].into(), + ); + entities_per_draw_order.insert( + DrawOrder::DEFAULT_IMAGE, + [DrawOrderTarget::DefaultImage].into(), + ); + entities_per_draw_order.insert( + DrawOrder::DEFAULT_LINES2D, + [DrawOrderTarget::DefaultLines2D].into(), + ); + entities_per_draw_order.insert( + DrawOrder::DEFAULT_POINTS2D, + [DrawOrderTarget::DefaultPoints].into(), + ); + + // Determine re_renderer draw order from this. + // + // We give objects with the same `DrawOrder` still a different depth offset + // in order to avoid z-fighting artifacts when rendering in 3D. + // (for pure 2D this isn't necessary) + // + // We want to be as tightly around 0 as possible. + let num_entities_with_draw_order: usize = entities_per_draw_order + .values() + .map(|entities| entities.len()) + .sum(); + let mut draw_order = -((num_entities_with_draw_order / 2) as re_renderer::DepthOffset); + self.per_entity = entities_per_draw_order + .into_values() + .flat_map(|targets| { + targets + .into_iter() + .filter_map(|target| { + draw_order += 1; + match target { + DrawOrderTarget::Entity(entity) => Some((entity, draw_order)), + DrawOrderTarget::DefaultBox2D => { + self.box2d = draw_order; + None + } + DrawOrderTarget::DefaultLines2D => { + self.lines2d = draw_order; + None + } + DrawOrderTarget::DefaultImage => { + self.image = draw_order; + None + } + DrawOrderTarget::DefaultPoints => { + self.points = draw_order; + None + } + } + }) + .collect::>() + }) + .collect(); + } +} diff --git a/crates/re_space_view_spatial/src/scene/contexts/mod.rs b/crates/re_space_view_spatial/src/scene/contexts/mod.rs new file mode 100644 index 000000000000..0cf396d954f1 --- /dev/null +++ b/crates/re_space_view_spatial/src/scene/contexts/mod.rs @@ -0,0 +1,85 @@ +mod annotation_context; +mod depth_offsets; +mod shared_render_builders; +mod transform_context; + +use std::sync::atomic::AtomicUsize; + +pub use annotation_context::AnnotationSceneContext; +pub use depth_offsets::EntityDepthOffsets; +pub use shared_render_builders::SharedRenderBuilders; +pub use transform_context::{TransformContext, UnreachableTransform}; + +// ----------------------------------------------------------------------------- + +use re_log_types::EntityPath; +use re_renderer::DepthOffset; +use re_viewer_context::{Annotations, SceneContext}; + +#[derive(Default)] +pub struct SpatialSceneContext { + pub transforms: TransformContext, + pub depth_offsets: EntityDepthOffsets, + pub annotations: AnnotationSceneContext, + pub shared_render_builders: SharedRenderBuilders, + + pub num_primitives: AtomicUsize, + pub num_3d_primitives: AtomicUsize, +} + +impl SceneContext for SpatialSceneContext { + fn vec_mut(&mut self) -> Vec<&mut dyn re_viewer_context::SceneContextPart> { + let Self { + transforms, + depth_offsets, + annotations, + shared_render_builders, + num_3d_primitives: _, + num_primitives: _, + } = self; + vec![ + transforms, + depth_offsets, + annotations, + shared_render_builders, + ] + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +impl SpatialSceneContext { + pub fn lookup_entity_context<'a>( + &'a self, + ent_path: &EntityPath, + highlights: &'a re_viewer_context::SpaceViewHighlights, + default_depth_offset: DepthOffset, + ) -> Option> { + Some(SpatialSceneEntityContext { + world_from_obj: self.transforms.reference_from_entity(ent_path)?, + depth_offset: *self + .depth_offsets + .per_entity + .get(&ent_path.hash()) + .unwrap_or(&default_depth_offset), + annotations: self.annotations.0.find(ent_path), + shared_render_builders: &self.shared_render_builders, + highlight: highlights.entity_outline_mask(ent_path.hash()), + }) + } +} + +/// Context objects for a single entity in a spatial scene. +pub struct SpatialSceneEntityContext<'a> { + pub world_from_obj: glam::Affine3A, + pub depth_offset: DepthOffset, + pub annotations: std::sync::Arc, + pub shared_render_builders: &'a SharedRenderBuilders, + pub highlight: &'a re_viewer_context::SpaceViewOutlineMasks, // Not part of the context, but convenient to have here. +} diff --git a/crates/re_space_view_spatial/src/scene/contexts/shared_render_builders.rs b/crates/re_space_view_spatial/src/scene/contexts/shared_render_builders.rs new file mode 100644 index 000000000000..efbde262dcec --- /dev/null +++ b/crates/re_space_view_spatial/src/scene/contexts/shared_render_builders.rs @@ -0,0 +1,47 @@ +use parking_lot::{Mutex, MutexGuard}; +use re_renderer::{LineStripSeriesBuilder, PointCloudBuilder}; +use re_viewer_context::{ArchetypeDefinition, SceneContextPart}; + +use crate::scene::{ + SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES, +}; + +// TODO(wumpf): Workaround for Point & Line builder taking up too much memory to emit them on every scene element that as points/lines. +// If these builders/draw-data would allocate space more dynamically, this would not be necessary! +#[derive(Default)] +pub struct SharedRenderBuilders { + pub lines: Option>, + pub points: Option>, +} + +impl SharedRenderBuilders { + pub fn lines(&self) -> MutexGuard<'_, LineStripSeriesBuilder> { + self.lines.as_ref().unwrap().lock() + } + + pub fn points(&self) -> MutexGuard<'_, PointCloudBuilder> { + self.points.as_ref().unwrap().lock() + } +} + +impl SceneContextPart for SharedRenderBuilders { + fn archetypes(&self) -> Vec { + Vec::new() + } + + fn populate( + &mut self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + _query: &re_viewer_context::SceneQuery<'_>, + _space_view_state: &dyn re_viewer_context::SpaceViewState, + ) { + self.lines = Some(Mutex::new( + LineStripSeriesBuilder::new(ctx.render_ctx) + .radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES), + )); + self.points = Some(Mutex::new( + PointCloudBuilder::new(ctx.render_ctx) + .radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES), + )); + } +} diff --git a/crates/re_space_view_spatial/src/transform_cache.rs b/crates/re_space_view_spatial/src/scene/contexts/transform_context.rs similarity index 88% rename from crates/re_space_view_spatial/src/transform_cache.rs rename to crates/re_space_view_spatial/src/scene/contexts/transform_context.rs index 1fa1960ef7ca..2dbd977bdb35 100644 --- a/crates/re_space_view_spatial/src/transform_cache.rs +++ b/crates/re_space_view_spatial/src/scene/contexts/transform_context.rs @@ -1,9 +1,10 @@ use nohash_hasher::IntMap; + use re_arrow_store::LatestAtQuery; use re_components::{DisconnectedSpace, Pinhole, Transform3D}; -use re_data_store::{store_db::EntityDb, EntityPath, EntityPropertyMap, EntityTree}; -use re_log_types::EntityPathHash; -use re_viewer_context::TimeControl; +use re_data_store::{EntityPath, EntityPropertyMap, EntityTree}; +use re_log_types::{Component, EntityPathHash}; +use re_viewer_context::{ArchetypeDefinition, SceneContextPart}; #[derive(Clone)] struct TransformInfo { @@ -25,9 +26,9 @@ struct TransformInfo { /// /// Should be recomputed every frame. #[derive(Clone)] -pub struct TransformCache { +pub struct TransformContext { /// All transforms provided are relative to this reference path. - reference_path: EntityPath, + space_origin: EntityPath, /// All reachable entities. transform_per_entity: IntMap, @@ -39,6 +40,17 @@ pub struct TransformCache { first_unreachable_parent: Option<(EntityPath, UnreachableTransform)>, } +impl Default for TransformContext { + fn default() -> Self { + Self { + space_origin: EntityPath::root(), + transform_per_entity: Default::default(), + unreachable_descendants: Default::default(), + first_unreachable_parent: None, + } + } +} + #[derive(Clone, Copy)] pub enum UnreachableTransform { /// `SpaceInfoCollection` is outdated and can't find a corresponding space info for the given path. @@ -71,39 +83,46 @@ impl std::fmt::Display for UnreachableTransform { } } -impl TransformCache { +impl SceneContextPart for TransformContext { + fn archetypes(&self) -> Vec { + vec![ + vec1::vec1![Transform3D::name()], + vec1::vec1![Pinhole::name()], + vec1::vec1![DisconnectedSpace::name()], + ] + } + /// Determines transforms for all entities relative to a space path which serves as the "reference". /// I.e. the resulting transforms are "reference from scene" /// /// This means that the entities in `reference_space` get the identity transform and all other /// entities are transformed relative to it. - pub fn determine_transforms( - entity_db: &EntityDb, - time_ctrl: &TimeControl, - space_path: &EntityPath, - entity_prop_map: &EntityPropertyMap, - ) -> Self { + fn populate( + &mut self, + ctx: &mut re_viewer_context::ViewerContext<'_>, + scene_query: &re_viewer_context::SceneQuery<'_>, + _space_view_state: &dyn re_viewer_context::SpaceViewState, + ) { re_tracing::profile_function!(); - let mut transforms = TransformCache { - reference_path: space_path.clone(), - transform_per_entity: Default::default(), - unreachable_descendants: Default::default(), - first_unreachable_parent: None, - }; + let entity_db = &ctx.store_db.entity_db; + let time_ctrl = &ctx.rec_cfg.time_ctrl; + let entity_prop_map = scene_query.entity_props_map; + + self.space_origin = scene_query.space_origin.clone(); // Find the entity path tree for the root. - let Some(mut current_tree) = &entity_db.tree.subtree(space_path) else { + let Some(mut current_tree) = &entity_db.tree.subtree(scene_query.space_origin) else { // It seems the space path is not part of the object tree! // This happens frequently when the viewer remembers space views from a previous run that weren't shown yet. // Naturally, in this case we don't have any transforms yet. - return transforms; + return; }; let query = time_ctrl.current_query(); // Child transforms of this space - transforms.gather_descendants_transforms( + self.gather_descendants_transforms( current_tree, &entity_db.data_store, &query, @@ -120,9 +139,9 @@ impl TransformCache { // Unlike not having the space path in the hierarchy, this should be impossible. re_log::error_once!( "Path {} is not part of the global Entity tree whereas its child {} is", - parent_path, space_path + parent_path, scene_query.space_origin ); - return transforms; + return; }; // Note that the transform at the reference is the first that needs to be inverted to "break out" of its hierarchy. @@ -137,7 +156,7 @@ impl TransformCache { &mut encountered_pinhole, ) { Err(unreachable_reason) => { - transforms.first_unreachable_parent = + self.first_unreachable_parent = Some((parent_tree.path.clone(), unreachable_reason)); break; } @@ -148,7 +167,7 @@ impl TransformCache { } // (skip over everything at and under `current_tree` automatically) - transforms.gather_descendants_transforms( + self.gather_descendants_transforms( parent_tree, &entity_db.data_store, &query, @@ -159,10 +178,10 @@ impl TransformCache { current_tree = parent_tree; } - - transforms } +} +impl TransformContext { fn gather_descendants_transforms( &mut self, tree: &EntityTree, @@ -213,7 +232,7 @@ impl TransformCache { } pub fn reference_path(&self) -> &EntityPath { - &self.reference_path + &self.space_origin } /// Retrieves the transform of on entity from its local system to the space of the reference. diff --git a/crates/re_space_view_spatial/src/scene/mod.rs b/crates/re_space_view_spatial/src/scene/mod.rs index 73f60439b96b..a9be0711a00b 100644 --- a/crates/re_space_view_spatial/src/scene/mod.rs +++ b/crates/re_space_view_spatial/src/scene/mod.rs @@ -1,39 +1,34 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - sync::Arc, -}; +mod contexts; +mod parts; +mod picking; +mod primitives; + +pub use contexts::{SpatialSceneContext, TransformContext, UnreachableTransform}; +pub use parts::{SpatialScenePartCollection, SpatialScenePartData}; +pub use picking::{PickingContext, PickingHitType, PickingRayHit, PickingResult}; +pub use primitives::SceneSpatialPrimitives; use ahash::HashMap; -use nohash_hasher::IntMap; use re_components::{ClassId, DecodedTensor, DrawOrder, InstanceKey, KeypointId}; use re_data_store::{EntityPath, InstancePathHash}; use re_log_types::EntityPathHash; -use re_renderer::{renderer::TexturedRect, Color32, OutlineMaskPreference, Size}; -use re_space_view::SpaceViewHighlights; -use re_viewer_context::{auto_color, AnnotationMap, Annotations, SceneQuery, ViewerContext}; +use re_renderer::{renderer::TexturedRect, Color32, Size}; +use re_viewer_context::{ + auto_color, AnnotationMap, Scene, ScenePartCollection, SceneQuery, SpaceViewHighlights, + TypedScene, ViewerContext, +}; + +use crate::{space_camera_3d::SpaceCamera3D, SpatialSpaceViewClass}; use super::SpatialNavigationMode; -use crate::{ - mesh_loader::LoadedMesh, space_camera_3d::SpaceCamera3D, transform_cache::TransformCache, -}; -mod picking; -mod primitives; -mod scene_part; - -pub use self::picking::{PickingContext, PickingHitType, PickingRayHit, PickingResult}; -pub use self::primitives::SceneSpatialPrimitives; -use scene_part::ScenePart; - -/// TODO(andreas): Scene should only care about converted rendering primitive. -pub struct MeshSource { - pub picking_instance_hash: InstancePathHash, - // TODO(andreas): Make this Conformal3 once glow is gone? - pub world_from_mesh: macaw::Affine3A, - pub mesh: Arc, - pub outline_mask_ids: OutlineMaskPreference, -} +use self::contexts::SpatialSceneEntityContext; + +use contexts::EntityDepthOffsets; + +const SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES: f32 = 1.5; +const SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES: f32 = 2.5; pub struct Image { /// Path to the image (note image instance ids would refer to pixels!) @@ -51,6 +46,7 @@ pub struct Image { pub draw_order: DrawOrder, } +#[derive(Clone)] pub enum UiLabelTarget { /// Labels a given rect (in scene coordinates) Rect(egui::Rect), @@ -62,6 +58,7 @@ pub enum UiLabelTarget { Position3D(glam::Vec3), } +#[derive(Clone)] pub struct UiLabel { pub text: String, pub color: Color32, @@ -88,28 +85,13 @@ pub struct SceneSpatial { pub primitives: SceneSpatialPrimitives, pub ui: SceneSpatialUiData, - /// Number of 2d primitives logged, used for heuristics. - num_logged_2d_objects: usize, - - /// Number of 3d primitives logged, used for heuristics. - num_logged_3d_objects: usize, - - /// All space cameras in this scene. - /// TODO(andreas): Does this belong to `SceneSpatialUiData`? - pub space_cameras: Vec, + // TODO(andreas): Temporary field. The hosting struct will be removed once SpatialScene is fully ported to the SpaceViewClass framework. + pub scene: TypedScene, + pub draw_data: Vec, } pub type Keypoints = HashMap<(ClassId, i64), HashMap>; -#[derive(Default)] -pub struct EntityDepthOffsets { - pub per_entity: IntMap, - pub box2d: re_renderer::DepthOffset, - pub lines2d: re_renderer::DepthOffset, - pub image: re_renderer::DepthOffset, - pub points: re_renderer::DepthOffset, -} - impl EntityDepthOffsets { pub fn get(&self, ent_path: &EntityPath) -> Option { self.per_entity.get(&ent_path.hash()).cloned() @@ -122,197 +104,99 @@ impl SceneSpatial { annotation_map: Default::default(), primitives: SceneSpatialPrimitives::new(re_ctx), ui: Default::default(), - num_logged_2d_objects: Default::default(), - num_logged_3d_objects: Default::default(), - space_cameras: Default::default(), + // TODO(andreas): Workaround for not having default on `Scene`. Soon not needed anyways + scene: Default::default(), + draw_data: Default::default(), } } - fn determine_depth_offsets( - ctx: &mut ViewerContext<'_>, - query: &SceneQuery<'_>, - ) -> EntityDepthOffsets { - re_tracing::profile_function!(); - - #[derive(PartialEq, PartialOrd, Eq, Ord)] - enum DrawOrderTarget { - Entity(EntityPathHash), - DefaultBox2D, - DefaultLines2D, - DefaultImage, - DefaultPoints, - } - - let store = &ctx.store_db.entity_db.data_store; - - // Use a BTreeSet for entity hashes to get a stable order. - let mut entities_per_draw_order = BTreeMap::>::new(); - for (ent_path, _) in query.iter_entities() { - if let Some(draw_order) = store.query_latest_component::( - ent_path, - &ctx.rec_cfg.time_ctrl.current_query(), - ) { - entities_per_draw_order - .entry(draw_order) - .or_default() - .insert(DrawOrderTarget::Entity(ent_path.hash())); - } - } - - // Push in default draw orders. All of them using the none hash. - entities_per_draw_order.insert( - DrawOrder::DEFAULT_BOX2D, - [DrawOrderTarget::DefaultBox2D].into(), - ); - entities_per_draw_order.insert( - DrawOrder::DEFAULT_IMAGE, - [DrawOrderTarget::DefaultImage].into(), - ); - entities_per_draw_order.insert( - DrawOrder::DEFAULT_LINES2D, - [DrawOrderTarget::DefaultLines2D].into(), - ); - entities_per_draw_order.insert( - DrawOrder::DEFAULT_POINTS2D, - [DrawOrderTarget::DefaultPoints].into(), - ); - - // Determine re_renderer draw order from this. - // - // We give objects with the same `DrawOrder` still a different depth offset - // in order to avoid z-fighting artifacts when rendering in 3D. - // (for pure 2D this isn't necessary) - // - // We want to be as tightly around 0 as possible. - let mut offsets = EntityDepthOffsets::default(); - let num_entities_with_draw_order: usize = entities_per_draw_order - .values() - .map(|entities| entities.len()) - .sum(); - let mut draw_order = -((num_entities_with_draw_order / 2) as re_renderer::DepthOffset); - offsets.per_entity = entities_per_draw_order - .into_values() - .flat_map(|targets| { - targets - .into_iter() - .filter_map(|target| { - draw_order += 1; - match target { - DrawOrderTarget::Entity(entity) => Some((entity, draw_order)), - DrawOrderTarget::DefaultBox2D => { - offsets.box2d = draw_order; - None - } - DrawOrderTarget::DefaultLines2D => { - offsets.lines2d = draw_order; - None - } - DrawOrderTarget::DefaultImage => { - offsets.image = draw_order; - None - } - DrawOrderTarget::DefaultPoints => { - offsets.points = draw_order; - None - } - } - }) - .collect::>() - }) - .collect(); - - offsets - } - /// Loads all 3D objects into the scene according to the given query. pub fn load( &mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>, - transforms: &TransformCache, - highlights: &SpaceViewHighlights, + highlights: SpaceViewHighlights, ) { re_tracing::profile_function!(); self.annotation_map.load(ctx, query); - let parts: Vec<&dyn ScenePart> = vec![ - &scene_part::Points3DPart { max_labels: 10 }, - // -- - &scene_part::Boxes3DPart, - &scene_part::Lines3DPart, - &scene_part::Arrows3DPart, - &scene_part::MeshPart, - &scene_part::ImagesPart, - // -- - &scene_part::Boxes2DPart, - // -- - // Note: Lines2DPart handles both Segments and LinesPaths since they are unified on the logging-side. - &scene_part::Lines2DPart, - &scene_part::Points2DPart { max_labels: 10 }, - // --- - &scene_part::CamerasPart, - ]; - - let depth_offsets = Self::determine_depth_offsets(ctx, query); + let parts: Vec<&dyn parts::ScenePart> = vec![&parts::ImagesPart]; + + // TODO(wumpf): Temporary build up of scene. This will be handled by the SpaceViewClass framework later. + let mut scene = TypedScene:: { + context: SpatialSceneContext::default(), + parts: SpatialScenePartCollection::default(), + highlights: Default::default(), + }; + self.draw_data = + scene.populate(ctx, query, &re_space_view::EmptySpaceViewState, highlights); for part in parts { - part.load(self, ctx, query, transforms, highlights, &depth_offsets); + part.load( + self, + ctx, + query, + &scene.context.transforms, + &scene.highlights, + &scene.context.depth_offsets, + ); } - self.primitives.any_outlines = highlights.any_outlines(); + self.primitives.any_outlines = scene.highlights.any_outlines(); self.primitives.recalculate_bounding_box(); - } - const CAMERA_COLOR: Color32 = Color32::from_rgb(150, 150, 150); + for scene_part in scene.parts.vec_mut() { + if let Some(data) = scene_part.data() { + self.ui.labels.extend(data.ui_labels.iter().cloned()); + self.primitives.bounding_box = + self.primitives.bounding_box.union(data.bounding_box); + } + } - fn load_keypoint_connections( - &mut self, - entity_path: &re_data_store::EntityPath, - keypoints: Keypoints, - annotations: &Arc, - ) { - // Generate keypoint connections if any. - let mut line_batch = self - .primitives - .line_strips - .batch("keypoint connections") - .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); - - for ((class_id, _time), keypoints_in_class) in keypoints { - let Some(class_description) = annotations.context.class_map.get(&class_id) else { - continue; - }; + self.draw_data.extend( + scene + .context + .shared_render_builders + .lines + .take() + .and_then(|l| match l.into_inner().to_draw_data(ctx.render_ctx) { + Ok(d) => Some(d.into()), + Err(err) => { + re_log::error_once!("Failed to build line strip draw data: {err}"); + None + } + }), + ); + self.draw_data.extend( + scene + .context + .shared_render_builders + .points + .take() + .and_then(|l| match l.into_inner().to_draw_data(ctx.render_ctx) { + Ok(d) => Some(d.into()), + Err(err) => { + re_log::error_once!("Failed to build point draw data: {err}"); + None + } + }), + ); - let color = class_description.info.color.map_or_else( - || auto_color(class_description.info.id), - |color| color.into(), - ); + self.scene = scene; + } - for (a, b) in &class_description.keypoint_connections { - let (Some(a), Some(b)) = (keypoints_in_class.get(a), keypoints_in_class.get(b)) else { - re_log::warn_once!( - "Keypoint connection from index {:?} to {:?} could not be resolved in object {:?}", - a, b, entity_path - ); - continue; - }; - line_batch - .add_segment(*a, *b) - .radius(Size::AUTO) - .color(color) - .flags(re_renderer::renderer::LineStripFlags::FLAG_COLOR_GRADIENT) - // Select the entire object when clicking any of the lines. - .picking_instance_id(re_renderer::PickingLayerInstanceId(InstanceKey::SPLAT.0)); - } - } + const CAMERA_COLOR: Color32 = Color32::from_rgb(150, 150, 150); + + pub fn space_cameras(&self) -> &[SpaceCamera3D] { + &self.scene.parts.cameras.space_cameras } /// Heuristic whether the default way of looking at this scene should be 2d or 3d. pub fn preferred_navigation_mode(&self, space_info_path: &EntityPath) -> SpatialNavigationMode { // If there's any space cameras that are not the root, we need to go 3D, otherwise we can't display them. if self - .space_cameras + .space_cameras() .iter() .any(|camera| &camera.ent_path != space_info_path) { @@ -322,10 +206,61 @@ impl SceneSpatial { if !self.primitives.images.is_empty() { return SpatialNavigationMode::TwoD; } - if self.num_logged_3d_objects == 0 { + + if self + .scene + .context + .num_3d_primitives + .load(std::sync::atomic::Ordering::Relaxed) + == 0 + { return SpatialNavigationMode::TwoD; } SpatialNavigationMode::ThreeD } } + +pub fn load_keypoint_connections( + ent_context: &SpatialSceneEntityContext<'_>, + entity_path: &re_data_store::EntityPath, + keypoints: Keypoints, +) { + if keypoints.is_empty() { + return; + } + + // Generate keypoint connections if any. + let mut line_builder = ent_context.shared_render_builders.lines(); + let mut line_batch = line_builder + .batch("keypoint connections") + .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); + + for ((class_id, _time), keypoints_in_class) in keypoints { + let Some(class_description) = ent_context.annotations.context.class_map.get(&class_id) else { + continue; + }; + + let color = class_description.info.color.map_or_else( + || auto_color(class_description.info.id), + |color| color.into(), + ); + + for (a, b) in &class_description.keypoint_connections { + let (Some(a), Some(b)) = (keypoints_in_class.get(a), keypoints_in_class.get(b)) else { + re_log::warn_once!( + "Keypoint connection from index {:?} to {:?} could not be resolved in object {:?}", + a, b, entity_path + ); + continue; + }; + line_batch + .add_segment(*a, *b) + .radius(Size::AUTO) + .color(color) + .flags(re_renderer::renderer::LineStripFlags::FLAG_COLOR_GRADIENT) + // Select the entire object when clicking any of the lines. + .picking_instance_id(re_renderer::PickingLayerInstanceId(InstanceKey::SPLAT.0)); + } + } +} diff --git a/crates/re_space_view_spatial/src/scene/parts/arrows3d.rs b/crates/re_space_view_spatial/src/scene/parts/arrows3d.rs new file mode 100644 index 000000000000..42d5ed4126d4 --- /dev/null +++ b/crates/re_space_view_spatial/src/scene/parts/arrows3d.rs @@ -0,0 +1,138 @@ +use re_components::{Arrow3D, ColorRGBA, Component as _, InstanceKey, Label, Radius}; +use re_data_store::EntityPath; +use re_query::{EntityView, QueryError}; +use re_renderer::{renderer::LineStripFlags, Size}; +use re_viewer_context::{ + ArchetypeDefinition, DefaultColor, ScenePart, SceneQuery, SpaceViewHighlights, ViewerContext, +}; + +use super::{instance_key_to_picking_id, SpatialScenePartData, SpatialSpaceViewState}; +use crate::{ + scene::{ + contexts::{SpatialSceneContext, SpatialSceneEntityContext}, + parts::entity_iterator::process_entity_views, + }, + SpatialSpaceViewClass, +}; + +#[derive(Default)] +pub struct Arrows3DPart(SpatialScenePartData); + +impl Arrows3DPart { + fn process_entity_view( + &mut self, + _query: &SceneQuery<'_>, + ent_view: &EntityView, + ent_path: &EntityPath, + ent_context: &SpatialSceneEntityContext<'_>, + ) -> Result<(), QueryError> { + let default_color = DefaultColor::EntityPath(ent_path); + + let mut line_builder = ent_context.shared_render_builders.lines(); + let mut line_batch = line_builder + .batch("arrows") + .world_from_obj(ent_context.world_from_obj) + .outline_mask_ids(ent_context.highlight.overall) + .picking_object_id(re_renderer::PickingLayerObjectId(ent_path.hash64())); + + let mut bounding_box = macaw::BoundingBox::nothing(); + + ent_view.visit4( + |instance_key: InstanceKey, + arrow: Arrow3D, + color: Option, + radius: Option, + _label: Option