use glam::{EulerRot, Quat, Vec2, Vec3};
use log::debug;
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;
use crate::entities::comp_node::CompNode;
use crate::entities::node_kind::NodeKind;
use crate::entities::space;
use crate::entities::transform::build_inverse_transform;
use super::ViewportState;
#[inline]
fn layer_plane_normal(rotation: [f32; 3]) -> Vec3 {
let quat = Quat::from_euler(
EulerRot::ZYX,
-rotation[2],
-rotation[1],
-rotation[0],
);
quat * Vec3::Z
}
#[derive(Debug, Clone)]
pub struct PickResult {
pub layer_uuid: Option<Uuid>,
}
pub fn pick_layer_at(
screen_pos: eframe::egui::Pos2,
panel_rect: eframe::egui::Rect,
viewport_state: &ViewportState,
comp: &CompNode,
frame_idx: i32,
media: &HashMap<Uuid, Arc<NodeKind>>,
) -> PickResult {
let local = screen_pos - panel_rect.left_top();
debug!("[pick] screen={:?} local={:?}", screen_pos, local);
let Some(image_pos) = viewport_state.screen_to_image(eframe::egui::vec2(local.x, local.y)) else {
debug!("[pick] outside image bounds");
return PickResult { layer_uuid: None };
};
let image_size = (viewport_state.image_size.x as usize, viewport_state.image_size.y as usize);
let comp_pos = space::image_to_frame(Vec2::new(image_pos.x, image_pos.y), image_size);
debug!("[pick] image={:?} comp={:?} zoom={} pan={:?}", image_pos, comp_pos, viewport_state.zoom, viewport_state.pan);
debug!("[pick] checking {} layers", comp.layers.len());
for (i, layer) in comp.layers.iter().enumerate() {
let name = layer.attrs.get_str("name").unwrap_or("?");
if !layer.is_visible() {
debug!("[pick] layer[{}] = {} SKIP (invisible)", i, name);
continue;
}
if !layer.attrs.get_bool("renderable").unwrap_or(true) {
debug!("[pick] layer[{}] = {} SKIP (non-renderable)", i, name);
continue;
}
let (play_start, play_end) = comp.get_layer_work_area(layer, media);
if frame_idx < play_start || frame_idx > play_end {
debug!("[pick] layer[{}] = {} SKIP (frame {} not in {}..{})", i, name, frame_idx, play_start, play_end);
continue;
}
debug!("[pick] layer[{}] = {}", i, name);
let position = layer.attrs.get_vec3("position").unwrap_or([0.0, 0.0, 0.0]);
let rotation_deg = layer.attrs.get_vec3("rotation").unwrap_or([0.0, 0.0, 0.0]);
let scale = layer.attrs.get_vec3("scale").unwrap_or([1.0, 1.0, 1.0]);
let pivot = layer.attrs.get_vec3("pivot").unwrap_or([0.0, 0.0, 0.0]);
let rotation = [
rotation_deg[0].to_radians(),
rotation_deg[1].to_radians(),
rotation_deg[2].to_radians(),
];
let inv_transform = build_inverse_transform(position, rotation, scale, pivot);
let plane_normal = layer_plane_normal(rotation);
let layer_is_tilted = (plane_normal - Vec3::Z).length_squared() > 1e-6;
let obj_pos = if layer_is_tilted {
let plane_point = Vec3::from(position);
let ray_origin = Vec3::new(comp_pos.x, comp_pos.y, 10000.0);
let ray_dir = Vec3::NEG_Z;
let denom = ray_dir.dot(plane_normal);
if denom.abs() < 1e-6 {
debug!("[pick] layer={} SKIP (edge-on)", name);
continue;
}
let t = (plane_point - ray_origin).dot(plane_normal) / denom;
let world_pt = ray_origin + ray_dir * t;
let obj_pt3 = inv_transform.transform_point3(world_pt);
Vec2::new(obj_pt3.x, obj_pt3.y)
} else {
let comp_pos_3d = Vec3::new(comp_pos.x, comp_pos.y, 0.0);
let obj_pos_3d = inv_transform.transform_point3(comp_pos_3d);
Vec2::new(obj_pos_3d.x, obj_pos_3d.y)
};
let layer_w = layer.attrs.get_u32("width").unwrap_or(100) as f32;
let layer_h = layer.attrs.get_u32("height").unwrap_or(100) as f32;
let half_w = layer_w * 0.5;
let half_h = layer_h * 0.5;
let hit = obj_pos.x >= -half_w && obj_pos.x <= half_w
&& obj_pos.y >= -half_h && obj_pos.y <= half_h;
debug!(
"[pick] layer={} pos={:?} rot={:?} scale={:?} obj={:?} bounds=[{:.0}x{:.0}] hit={}",
layer.attrs.get_str("name").unwrap_or("?"),
position, rotation_deg, scale, obj_pos, layer_w, layer_h, hit
);
if hit {
return PickResult {
layer_uuid: Some(layer.uuid()),
};
}
}
debug!("[pick] no hit");
PickResult { layer_uuid: None }
}