1use std::cell::Cell;
2
3use cranpose_core::NodeId;
4use cranpose_ui_graphics::{
5 Point, Rect, Size,
6 space::{Camera, Mat4, MeshSurface, Ray, SurfaceHit, Vec3},
7};
8
9use crate::{
10 graph::{LayerNode, ProjectiveTransform},
11 space::{EyeView, SpaceTree},
12};
13
14const PAST_DOORWAY: f32 = 1.0e-4;
18
19pub struct ScenePick {
24 scene: Option<NodeId>,
26 camera: Camera,
27 size: Size,
28 window_to_scene: ProjectiveTransform,
29 scene_to_window: ProjectiveTransform,
30 surfaces: Vec<PickSurface>,
31 seen: Vec<PickSpace>,
32 scenes: Vec<NodeId>,
34 seen_scenes: Vec<NodeId>,
36 last: Cell<Option<LastPick>>,
38}
39
40struct PickSpace {
43 through: Option<usize>,
44 to_door: Mat4,
45}
46
47impl PickSpace {
48 fn meet(&self, ray: Ray) -> Option<f32> {
50 let origin = self.to_door.transform_point(ray.origin);
51 let direction = self.to_door.transform_vector(ray.direction);
52 if direction.z.abs() < f32::EPSILON {
53 return None;
54 }
55 let distance = -origin.z / direction.z;
56 let at = origin + direction * distance;
57 let inside = (0.0..=1.0).contains(&at.x) && (0.0..=1.0).contains(&at.y);
58 (inside && distance > 0.0).then_some(distance)
59 }
60}
61
62type LastPick = (Point, Option<(usize, SurfaceHit)>);
64
65struct PickSurface {
66 node_id: Option<NodeId>,
67 surface: MeshSurface,
68 content: Rect,
71 seen: usize,
73 held: Cell<Option<u32>>,
76}
77
78impl PickSurface {
79 fn in_layer(&self, point: Point) -> Point {
82 Point::new(point.x + self.content.x, point.y + self.content.y)
83 }
84
85 fn cast(&self, ray: Ray) -> Option<SurfaceHit> {
87 let mut hit = self.surface.cast(ray, self.content.size())?;
88 hit.point = self.in_layer(hit.point);
89 Some(hit)
90 }
91}
92
93impl ScenePick {
94 pub fn of(layer: &LayerNode, to_window: ProjectiveTransform) -> Option<Self> {
98 let tree = SpaceTree::of(layer)?;
101 let bounds = layer.local_bounds;
103 let to_window = ProjectiveTransform::translation(bounds.x, bounds.y).then(to_window);
104 let window_to_scene = to_window.inverse()?;
105 let view = EyeView::of(&tree, bounds.size());
106 let seen_scenes = view
107 .seen
108 .iter()
109 .filter_map(|seen| tree.cells[seen.cell].layer.node_id)
110 .collect();
111 let seen = view
112 .seen
113 .iter()
114 .map(|space| PickSpace {
115 through: space.through,
116 to_door: space
117 .door
118 .and_then(|door| door.quad.inverse())
119 .unwrap_or(Mat4::IDENTITY),
120 })
121 .collect();
122 let surfaces = view
123 .surfaces
124 .into_iter()
125 .map(|seen| PickSurface {
126 node_id: seen.layer.node_id,
127 surface: seen.surface,
128 content: seen.content,
129 seen: seen.seen,
130 held: Cell::new(None),
131 })
132 .collect();
133 Some(Self {
134 scene: layer.node_id,
135 camera: view.camera,
136 size: view.size,
137 window_to_scene,
138 scene_to_window: to_window,
139 surfaces,
140 seen,
141 scenes: tree
142 .cells
143 .iter()
144 .filter_map(|cell| cell.layer.node_id)
145 .collect(),
146 seen_scenes,
147 last: Cell::new(None),
148 })
149 }
150
151 pub fn scene(&self) -> Option<NodeId> {
153 self.scene
154 }
155
156 pub fn holds_scene(&self, layer: &LayerNode) -> bool {
159 layer
160 .node_id
161 .is_some_and(|node_id| self.scenes.contains(&node_id))
162 }
163
164 pub fn sees_scene(&self, layer: &LayerNode) -> bool {
167 layer
168 .node_id
169 .is_some_and(|node_id| self.seen_scenes.contains(&node_id))
170 }
171
172 pub fn index_of(&self, layer: &LayerNode) -> Option<usize> {
174 let node_id = layer.node_id?;
175 self.surfaces
176 .iter()
177 .position(|surface| surface.node_id == Some(node_id))
178 }
179
180 pub fn content_point(&self, window: Point, index: usize) -> Option<Point> {
183 let (nearest, hit) = self.nearest(window)?;
184 (nearest == index).then_some(hit.point)
185 }
186
187 pub fn nearest_along(&self, ray: Ray) -> Option<(usize, SurfaceHit)> {
190 self.nearest_in(ray, 0, f32::NEG_INFINITY)
191 }
192
193 pub fn drag_point(&self, window: Point, index: usize) -> Option<Point> {
197 self.drag_point_along(self.ray(window)?, index)
198 }
199
200 pub fn drag_point_along(&self, ray: Ray, index: usize) -> Option<Point> {
203 let target = self.surfaces.get(index)?;
204 if let Some(hit) = target.cast(ray) {
205 target.held.set(Some(hit.triangle));
206 return Some(hit.point);
207 }
208 target
209 .surface
210 .cast_plane(ray, target.held.get()?, target.content.size())
211 .map(|point| target.in_layer(point))
212 }
213
214 pub fn window_point(&self, index: usize, content: Point) -> Option<Point> {
218 let target = self.surfaces.get(index)?;
219 let from_corner = Point::new(content.x - target.content.x, content.y - target.content.y);
220 let at = target
221 .surface
222 .point_at(from_corner, target.content.size())?;
223 let shown = self.camera.project(at, self.size)?;
224 Some(self.scene_to_window.map_point(shown))
225 }
226
227 pub fn window_point_of(&self, point: Vec3) -> Option<Point> {
230 let shown = self.camera.project(point, self.size)?;
231 Some(self.scene_to_window.map_point(shown))
232 }
233
234 fn ray(&self, window: Point) -> Option<Ray> {
235 let local = self.window_to_scene.map_point(window);
236 self.camera.ray(local, self.size)
237 }
238
239 fn nearest(&self, window: Point) -> Option<(usize, SurfaceHit)> {
240 if let Some((point, nearest)) = self.last.get()
241 && point == window
242 {
243 return nearest;
244 }
245 let nearest = self.ray(window).and_then(|ray| self.nearest_along(ray));
246 if let Some((index, hit)) = nearest {
247 self.surfaces[index].held.set(Some(hit.triangle));
248 }
249 self.last.set(Some((window, nearest)));
250 nearest
251 }
252
253 fn nearest_in(&self, ray: Ray, seen: usize, beyond: f32) -> Option<(usize, SurfaceHit)> {
258 let surface = self
259 .surfaces
260 .iter()
261 .enumerate()
262 .filter(|(_, target)| target.seen == seen)
263 .filter_map(|(index, target)| {
264 target
265 .cast(ray)
266 .filter(|hit| hit.distance > beyond)
267 .map(|hit| (index, hit))
268 })
269 .min_by(|(_, a), (_, b)| a.distance.total_cmp(&b.distance));
270 let doorway = self
271 .seen
272 .iter()
273 .enumerate()
274 .filter(|(_, space)| space.through == Some(seen))
275 .filter_map(|(index, space)| {
276 space
277 .meet(ray)
278 .filter(|distance| *distance > beyond)
279 .map(|distance| (index, distance))
280 })
281 .min_by(|(_, a), (_, b)| a.total_cmp(b));
282 let Some((door, distance)) = doorway.filter(|(_, distance)| {
283 surface.is_none_or(|(_, hit)| *distance <= hit.distance * (1.0 + PAST_DOORWAY))
284 }) else {
285 return surface;
286 };
287 self.nearest_in(ray, door, distance * (1.0 + PAST_DOORWAY))
288 .or(surface)
289 }
290}
291
292pub fn surface_layer(child: &LayerNode) -> Option<&LayerNode> {
295 let layer = match child.wraps {
296 None => child,
297 Some(node_id) => child.children.iter().find_map(|inner| match inner {
298 crate::graph::RenderNode::Layer(inner) if inner.node_id == Some(node_id) => {
299 Some(&**inner)
300 }
301 _ => None,
302 })?,
303 };
304 cranpose_ui_graphics::layer_transform::layer_surface(&layer.graphics_layer).map(|_| layer)
305}