1use std::cell::Cell;
2
3use cranpose_core::NodeId;
4use cranpose_ui_graphics::{
5 Point, Size,
6 space::{Camera, Mat4, MeshSurface, Ray, SurfaceHit, Vec3},
7};
8
9use crate::{
10 graph::{LayerNode, ProjectiveTransform},
11 space::{EyeView, SpaceTree, node_size},
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 size: Size,
69 seen: usize,
71 held: Cell<Option<u32>>,
74}
75
76impl ScenePick {
77 pub fn of(layer: &LayerNode, to_window: ProjectiveTransform) -> Option<Self> {
81 let window_to_scene = to_window.inverse()?;
82 let tree = SpaceTree::of(layer)?;
83 let view = EyeView::of(&tree, node_size(layer));
84 let seen_scenes = view
85 .seen
86 .iter()
87 .filter_map(|seen| tree.cells[seen.cell].layer.node_id)
88 .collect();
89 let seen = view
90 .seen
91 .iter()
92 .map(|space| PickSpace {
93 through: space.through,
94 to_door: space
95 .door
96 .and_then(|door| door.quad.inverse())
97 .unwrap_or(Mat4::IDENTITY),
98 })
99 .collect();
100 let surfaces = view
101 .surfaces
102 .into_iter()
103 .map(|seen| PickSurface {
104 node_id: seen.layer.node_id,
105 surface: seen.surface,
106 size: seen.size,
107 seen: seen.seen,
108 held: Cell::new(None),
109 })
110 .collect();
111 Some(Self {
112 scene: layer.node_id,
113 camera: view.camera,
114 size: view.size,
115 window_to_scene,
116 scene_to_window: to_window,
117 surfaces,
118 seen,
119 scenes: tree
120 .cells
121 .iter()
122 .filter_map(|cell| cell.layer.node_id)
123 .collect(),
124 seen_scenes,
125 last: Cell::new(None),
126 })
127 }
128
129 pub fn scene(&self) -> Option<NodeId> {
131 self.scene
132 }
133
134 pub fn holds_scene(&self, layer: &LayerNode) -> bool {
137 layer
138 .node_id
139 .is_some_and(|node_id| self.scenes.contains(&node_id))
140 }
141
142 pub fn sees_scene(&self, layer: &LayerNode) -> bool {
145 layer
146 .node_id
147 .is_some_and(|node_id| self.seen_scenes.contains(&node_id))
148 }
149
150 pub fn index_of(&self, layer: &LayerNode) -> Option<usize> {
152 let node_id = layer.node_id?;
153 self.surfaces
154 .iter()
155 .position(|surface| surface.node_id == Some(node_id))
156 }
157
158 pub fn content_point(&self, window: Point, index: usize) -> Option<Point> {
161 let (nearest, hit) = self.nearest(window)?;
162 (nearest == index).then_some(hit.point)
163 }
164
165 pub fn nearest_along(&self, ray: Ray) -> Option<(usize, SurfaceHit)> {
168 self.nearest_in(ray, 0, f32::NEG_INFINITY)
169 }
170
171 pub fn drag_point(&self, window: Point, index: usize) -> Option<Point> {
175 self.drag_point_along(self.ray(window)?, index)
176 }
177
178 pub fn drag_point_along(&self, ray: Ray, index: usize) -> Option<Point> {
181 let target = self.surfaces.get(index)?;
182 if let Some(hit) = target.surface.cast(ray, target.size) {
183 target.held.set(Some(hit.triangle));
184 return Some(hit.point);
185 }
186 target
187 .surface
188 .cast_plane(ray, target.held.get()?, target.size)
189 }
190
191 pub fn window_point(&self, index: usize, content: Point) -> Option<Point> {
195 let target = self.surfaces.get(index)?;
196 let at = target.surface.point_at(content, target.size)?;
197 let shown = self.camera.project(at, self.size)?;
198 Some(self.scene_to_window.map_point(shown))
199 }
200
201 pub fn window_point_of(&self, point: Vec3) -> Option<Point> {
204 let shown = self.camera.project(point, self.size)?;
205 Some(self.scene_to_window.map_point(shown))
206 }
207
208 fn ray(&self, window: Point) -> Option<Ray> {
209 let local = self.window_to_scene.map_point(window);
210 self.camera.ray(local, self.size)
211 }
212
213 fn nearest(&self, window: Point) -> Option<(usize, SurfaceHit)> {
214 if let Some((point, nearest)) = self.last.get()
215 && point == window
216 {
217 return nearest;
218 }
219 let nearest = self.ray(window).and_then(|ray| self.nearest_along(ray));
220 if let Some((index, hit)) = nearest {
221 self.surfaces[index].held.set(Some(hit.triangle));
222 }
223 self.last.set(Some((window, nearest)));
224 nearest
225 }
226
227 fn nearest_in(&self, ray: Ray, seen: usize, beyond: f32) -> Option<(usize, SurfaceHit)> {
232 let surface = self
233 .surfaces
234 .iter()
235 .enumerate()
236 .filter(|(_, target)| target.seen == seen)
237 .filter_map(|(index, target)| {
238 target
239 .surface
240 .cast(ray, target.size)
241 .filter(|hit| hit.distance > beyond)
242 .map(|hit| (index, hit))
243 })
244 .min_by(|(_, a), (_, b)| a.distance.total_cmp(&b.distance));
245 let doorway = self
246 .seen
247 .iter()
248 .enumerate()
249 .filter(|(_, space)| space.through == Some(seen))
250 .filter_map(|(index, space)| {
251 space
252 .meet(ray)
253 .filter(|distance| *distance > beyond)
254 .map(|distance| (index, distance))
255 })
256 .min_by(|(_, a), (_, b)| a.total_cmp(b));
257 let Some((door, distance)) = doorway.filter(|(_, distance)| {
258 surface.is_none_or(|(_, hit)| *distance <= hit.distance * (1.0 + PAST_DOORWAY))
259 }) else {
260 return surface;
261 };
262 self.nearest_in(ray, door, distance * (1.0 + PAST_DOORWAY))
263 .or(surface)
264 }
265}
266
267pub fn surface_layer(child: &LayerNode) -> Option<&LayerNode> {
270 let layer = match child.wraps {
271 None => child,
272 Some(node_id) => child.children.iter().find_map(|inner| match inner {
273 crate::graph::RenderNode::Layer(inner) if inner.node_id == Some(node_id) => {
274 Some(&**inner)
275 }
276 _ => None,
277 })?,
278 };
279 cranpose_ui_graphics::layer_transform::layer_surface(&layer.graphics_layer).map(|_| layer)
280}