use std::collections::BTreeMap;
use ahash::HashMap;
use itertools::Itertools;
use nohash_hasher::IntSet;
use re_arrow_store::{DataStore, LatestAtQuery, Timeline};
use re_data_store::{query_latest_single, ComponentName, EntityPath};
use re_log_types::{component_types::Tensor, Component};
use re_viewer_context::ViewerContext;
use crate::{
misc::space_info::SpaceInfoCollection,
ui::{view_category::categorize_entity_path, ViewCategory},
};
use super::{view_category::ViewCategorySet, SpaceView};
pub fn all_possible_space_views(
ctx: &ViewerContext<'_>,
spaces_info: &SpaceInfoCollection,
) -> Vec<SpaceView> {
crate::profile_function!();
let root_children = &ctx.log_db.entity_db.tree.children;
let candidate_space_paths = spaces_info
.iter()
.map(|info| &info.path)
.chain(root_children.values().map(|sub_tree| &sub_tree.path))
.unique();
candidate_space_paths
.flat_map(|candidate_space_path| {
default_queried_entities_by_category(ctx, candidate_space_path, spaces_info)
.iter()
.map(|(category, entity_paths)| {
SpaceView::new(*category, candidate_space_path, entity_paths)
})
.collect::<Vec<_>>()
})
.collect()
}
fn contains_any_image(
entity_path: &EntityPath,
data_store: &re_arrow_store::DataStore,
query: &LatestAtQuery,
) -> bool {
if let Some(tensor) = query_latest_single::<Tensor>(data_store, entity_path, query) {
tensor.is_shaped_like_an_image()
} else {
false
}
}
fn is_interesting_space_view_at_root(
data_store: &re_arrow_store::DataStore,
candidate: &SpaceView,
query: &LatestAtQuery,
) -> bool {
if candidate.data_blueprint.root_group().entities.is_empty() {
return false;
}
for entity_path in &candidate.data_blueprint.root_group().entities {
if contains_any_image(entity_path, data_store, query) {
return false;
}
}
true
}
fn is_interesting_space_view_not_at_root(
data_store: &re_arrow_store::DataStore,
candidate: &SpaceView,
categories_with_interesting_roots: &ViewCategorySet,
query: &LatestAtQuery,
) -> bool {
if candidate.space_path.len() == 1
&& !categories_with_interesting_roots.contains(candidate.category)
{
return true;
}
if candidate.category == ViewCategory::Spatial {
if let Some(transform) = query_latest_single(data_store, &candidate.space_path, query) {
match transform {
re_log_types::Transform::Rigid3(_) => {}
re_log_types::Transform::Pinhole(_) | re_log_types::Transform::Unknown => {
return true;
}
}
}
}
false
}
pub fn default_created_space_views(
ctx: &ViewerContext<'_>,
spaces_info: &SpaceInfoCollection,
) -> Vec<SpaceView> {
let candidates = all_possible_space_views(ctx, spaces_info);
default_created_space_views_from_candidates(&ctx.log_db.entity_db.data_store, candidates)
}
fn default_created_space_views_from_candidates(
data_store: &re_arrow_store::DataStore,
candidates: Vec<SpaceView>,
) -> Vec<SpaceView> {
crate::profile_function!();
let query = LatestAtQuery::new(Timeline::log_time(), re_arrow_store::TimeInt::MAX);
let categories_with_interesting_roots = candidates
.iter()
.filter_map(|space_view_candidate| {
(space_view_candidate.space_path.is_root()
&& is_interesting_space_view_at_root(data_store, space_view_candidate, &query))
.then_some(space_view_candidate.category)
})
.collect::<ViewCategorySet>();
let mut space_views = Vec::new();
for candidate in candidates {
if candidate.space_path.is_root() {
if !categories_with_interesting_roots.contains(candidate.category) {
continue;
}
} else if !is_interesting_space_view_not_at_root(
data_store,
&candidate,
&categories_with_interesting_roots,
&query,
) {
continue;
}
if candidate.category == ViewCategory::Tensor {
for entity_path in candidate.data_blueprint.entity_paths() {
let mut space_view =
SpaceView::new(ViewCategory::Tensor, entity_path, &[entity_path.clone()]);
space_view.entities_determined_by_user = true; space_views.push(space_view);
}
continue;
}
if candidate.category == ViewCategory::Spatial {
#[derive(Hash, PartialEq, Eq)]
enum ImageBucketing {
BySize((u64, u64)),
ExplicitDrawOrder,
}
let mut images_by_bucket: HashMap<ImageBucketing, Vec<EntityPath>> = HashMap::default();
for entity_path in &candidate.data_blueprint.root_group().entities {
if let Some(tensor) = query_latest_single::<Tensor>(data_store, entity_path, &query)
{
if let Some([height, width, _]) = tensor.image_height_width_channels() {
if query_latest_single::<re_log_types::DrawOrder>(
data_store,
entity_path,
&query,
)
.is_some()
{
images_by_bucket
.entry(ImageBucketing::ExplicitDrawOrder)
.or_default()
.push(entity_path.clone());
} else {
images_by_bucket
.entry(ImageBucketing::BySize((height, width)))
.or_default()
.push(entity_path.clone());
}
}
}
}
if images_by_bucket.len() > 1 {
for bucket in images_by_bucket.keys() {
let images_of_different_size = images_by_bucket
.iter()
.filter_map(|(other_bucket, images)| {
(bucket != other_bucket).then_some(images)
})
.flatten()
.cloned()
.collect::<IntSet<_>>();
let entities = candidate
.data_blueprint
.entity_paths()
.iter()
.filter(|path| !images_of_different_size.contains(path))
.cloned()
.collect_vec();
let mut space_view =
SpaceView::new(candidate.category, &candidate.space_path, &entities);
space_view.entities_determined_by_user = true; space_views.push(space_view);
}
continue;
}
}
space_views.push(candidate);
}
space_views
}
fn has_any_component_except(
entity_path: &EntityPath,
data_store: &DataStore,
timeline: Timeline,
excluded_components: &[ComponentName],
) -> bool {
data_store
.all_components(&timeline, entity_path)
.map_or(false, |all_components| {
all_components
.iter()
.any(|comp| !excluded_components.contains(comp))
})
}
fn is_default_added_to_space_view(
entity_path: &EntityPath,
space_path: &EntityPath,
data_store: &DataStore,
timeline: Timeline,
) -> bool {
let ignored_components = [
re_log_types::Transform::name(),
re_log_types::ViewCoordinates::name(),
re_log_types::component_types::InstanceKey::name(),
re_log_types::component_types::KeypointId::name(),
DataStore::insert_id_key(),
];
entity_path.is_descendant_of(space_path)
|| (entity_path == space_path
&& has_any_component_except(entity_path, data_store, timeline, &ignored_components))
}
pub fn default_queried_entities(
ctx: &ViewerContext<'_>,
space_path: &EntityPath,
spaces_info: &SpaceInfoCollection,
category: ViewCategory,
) -> Vec<EntityPath> {
crate::profile_function!();
let timeline = Timeline::log_time();
let log_db = &ctx.log_db;
let data_store = &log_db.entity_db.data_store;
let mut entities = Vec::new();
let space_info = spaces_info.get_first_parent_with_info(space_path);
space_info.visit_descendants_with_reachable_transform(spaces_info, &mut |space_info| {
entities.extend(
space_info
.descendants_without_transform
.iter()
.filter(|entity_path| {
is_default_added_to_space_view(entity_path, space_path, data_store, timeline)
&& categorize_entity_path(timeline, log_db, entity_path).contains(category)
})
.cloned(),
);
});
entities
}
fn default_queried_entities_by_category(
ctx: &ViewerContext<'_>,
space_path: &EntityPath,
space_info_collection: &SpaceInfoCollection,
) -> BTreeMap<ViewCategory, Vec<EntityPath>> {
crate::profile_function!();
let timeline = Timeline::log_time();
let log_db = &ctx.log_db;
let data_store = &log_db.entity_db.data_store;
let mut groups: BTreeMap<ViewCategory, Vec<EntityPath>> = BTreeMap::default();
let space_info = space_info_collection.get_first_parent_with_info(space_path);
space_info.visit_descendants_with_reachable_transform(
space_info_collection,
&mut |space_info| {
for entity_path in &space_info.descendants_without_transform {
if is_default_added_to_space_view(entity_path, space_path, data_store, timeline) {
for category in categorize_entity_path(timeline, log_db, entity_path) {
groups
.entry(category)
.or_default()
.push(entity_path.clone());
}
}
}
},
);
groups
}