use re_data_store::{EntityPath, EntityProperties};
use re_log_types::{
component_types::InstanceKey,
coordinates::{Handedness, SignedAxis3},
Pinhole, Transform, ViewCoordinates,
};
use re_query::{query_entity_with_primary, EntityView, QueryError};
use re_renderer::renderer::LineStripFlags;
use crate::{
misc::{
instance_hash_conversions::picking_layer_id_from_instance_path_hash,
space_info::query_view_coordinates, SpaceViewHighlights, SpaceViewOutlineMasks,
TransformCache, ViewerContext,
},
ui::{
scene::SceneQuery,
view_spatial::{SceneSpatial, SpaceCamera3D},
},
};
use super::{instance_path_hash_for_picking, ScenePart};
fn determine_view_coordinates(
entity_db: &re_data_store::log_db::EntityDb,
time_ctrl: &crate::misc::TimeControl,
mut entity_path: EntityPath,
) -> ViewCoordinates {
loop {
if let Some(view_coordinates) =
query_view_coordinates(entity_db, &entity_path, &time_ctrl.current_query())
{
return view_coordinates;
}
if let Some(parent) = entity_path.parent() {
entity_path = parent;
} else {
return ViewCoordinates::from_up_and_handedness(
SignedAxis3::POSITIVE_Y,
Handedness::Right,
);
}
}
}
pub struct CamerasPart;
impl CamerasPart {
#[allow(clippy::too_many_arguments)]
fn visit_instance(
scene: &mut SceneSpatial,
entity_view: &EntityView<Transform>,
ent_path: &EntityPath,
instance_key: InstanceKey,
props: &EntityProperties,
transforms: &TransformCache,
pinhole: Pinhole,
view_coordinates: ViewCoordinates,
entity_highlight: &SpaceViewOutlineMasks,
) {
let parent_path = ent_path
.parent()
.expect("root path can't be part of scene query");
let Some(world_from_parent) = transforms.reference_from_entity(&parent_path) else {
return;
};
let Some(world_from_camera) = macaw::IsoTransform::from_mat4(&world_from_parent) else {
return;
};
let frustum_length = *props.pinhole_image_plane_distance.get();
scene.space_cameras.push(SpaceCamera3D {
ent_path: ent_path.clone(),
view_coordinates,
world_from_camera,
pinhole: Some(pinhole),
picture_plane_distance: Some(frustum_length),
});
let fov_y = pinhole.fov_y().unwrap_or(std::f32::consts::FRAC_PI_2);
let fy = (fov_y * 0.5).tan() * frustum_length;
let fx = fy * pinhole.aspect_ratio().unwrap_or(1.0);
let image_center_pixel = pinhole.resolution().unwrap_or(glam::Vec2::ZERO) * 0.5;
let principal_point_offset_pixel = image_center_pixel - pinhole.principal_point();
let principal_point_offset =
principal_point_offset_pixel / pinhole.resolution().unwrap_or(glam::Vec2::ONE);
let offset = principal_point_offset * (fy * 2.0);
let corners = [
(offset + glam::vec2(fx, -fy)).extend(frustum_length),
(offset + glam::vec2(fx, fy)).extend(frustum_length),
(offset + glam::vec2(-fx, fy)).extend(frustum_length),
(offset + glam::vec2(-fx, -fy)).extend(frustum_length),
];
let triangle_frustum_offset = fy * 1.05;
let up_triangle = [
(offset + glam::vec2(-fx * 0.25, -triangle_frustum_offset)).extend(frustum_length),
(offset + glam::vec2(0.0, -fx * 0.25 - triangle_frustum_offset)).extend(frustum_length),
(offset + glam::vec2(fx * 0.25, -triangle_frustum_offset)).extend(frustum_length),
];
let segments = [
(glam::Vec3::ZERO, corners[0]),
(glam::Vec3::ZERO, corners[1]),
(glam::Vec3::ZERO, corners[2]),
(glam::Vec3::ZERO, corners[3]),
(corners[0], corners[1]),
(corners[1], corners[2]),
(corners[2], corners[3]),
(corners[3], corners[0]),
(up_triangle[0], up_triangle[1]),
(up_triangle[1], up_triangle[2]),
(up_triangle[2], up_triangle[0]),
];
let radius = re_renderer::Size::new_points(1.0);
let color = SceneSpatial::CAMERA_COLOR;
let instance_path_for_picking = instance_path_hash_for_picking(
ent_path,
instance_key,
entity_view,
entity_highlight.any_selection_highlight,
);
let instance_layer_id = picking_layer_id_from_instance_path_hash(instance_path_for_picking);
let mut batch = scene
.primitives
.line_strips
.batch("camera frustum")
.world_from_obj(world_from_parent)
.outline_mask_ids(entity_highlight.overall)
.picking_object_id(instance_layer_id.object);
let lines = batch
.add_segments(segments.into_iter())
.radius(radius)
.color(color)
.flags(
LineStripFlags::NO_COLOR_GRADIENT
| LineStripFlags::CAP_END_ROUND
| LineStripFlags::CAP_START_ROUND,
)
.picking_instance_id(instance_layer_id.instance);
if let Some(outline_mask_ids) = entity_highlight.instances.get(&instance_key) {
lines.outline_mask_ids(*outline_mask_ids);
}
}
}
impl ScenePart for CamerasPart {
fn load(
&self,
scene: &mut SceneSpatial,
ctx: &mut ViewerContext<'_>,
query: &SceneQuery<'_>,
transforms: &TransformCache,
highlights: &SpaceViewHighlights,
) {
crate::profile_scope!("CamerasPart");
for (ent_path, props) in query.iter_entities() {
let query = re_arrow_store::LatestAtQuery::new(query.timeline, query.latest_at);
match query_entity_with_primary::<Transform>(
&ctx.log_db.entity_db.data_store,
&query,
ent_path,
&[],
)
.and_then(|entity_view| {
entity_view.visit1(|instance_key, transform| {
let Transform::Pinhole(pinhole) = transform else {
return;
};
let entity_highlight = highlights.entity_outline_mask(ent_path.hash());
let view_coordinates = determine_view_coordinates(
&ctx.log_db.entity_db,
&ctx.rec_cfg.time_ctrl,
ent_path.clone(),
);
Self::visit_instance(
scene,
&entity_view,
ent_path,
instance_key,
&props,
transforms,
pinhole,
view_coordinates,
entity_highlight,
);
})
}) {
Ok(_) | Err(QueryError::PrimaryNotFound) => {}
Err(err) => {
re_log::error_once!("Unexpected error querying {ent_path:?}: {err}");
}
}
}
}
}