Skip to main content

cranpose_render_common/
scene_pick.rs

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
14/// How much past a doorway a ray that goes through it looks for what lies
15/// beyond, against its distance: the surface the doorway lies on is not
16/// beyond it.
17const PAST_DOORWAY: f32 = 1.0e-4;
18
19/// Finds which mesh surface of a scene a window point, or any ray, shows,
20/// and the point of the surface's content there: the nearest surface along
21/// the ray in the space the window's eye stands in, or behind a doorway the
22/// ray meets first, in the spaces the eye sees.
23pub struct ScenePick {
24    /// The node of the scene the window shows.
25    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    /// The scene nodes of every space of the scene, seen or not.
33    scenes: Vec<NodeId>,
34    /// The scene nodes of the spaces the eye sees.
35    seen_scenes: Vec<NodeId>,
36    /// The last window point picked, and what it met.
37    last: Cell<Option<LastPick>>,
38}
39
40/// A space the eye sees, as the pick goes into it: the seen space whose
41/// doorway it shows through, and the map onto that doorway's unit square.
42struct PickSpace {
43    through: Option<usize>,
44    to_door: Mat4,
45}
46
47impl PickSpace {
48    /// How far along `ray` it passes through the doorway, if it does.
49    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
62/// A window point and the surface it met, if any, with where.
63type LastPick = (Point, Option<(usize, SurfaceHit)>);
64
65struct PickSurface {
66    node_id: Option<NodeId>,
67    surface: MeshSurface,
68    size: Size,
69    /// The seen space the surface lies in.
70    seen: usize,
71    /// The triangle the last ray that met this surface met, on whose plane
72    /// a drag that leaves the mesh goes on.
73    held: Cell<Option<u32>>,
74}
75
76impl ScenePick {
77    /// The pick of the scene that `layer` shows, with `to_window` mapping
78    /// the layer to the window; `None` when the layer shows no scene. Its
79    /// surfaces are those the window's eye sees.
80    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    /// The node of the scene the window shows.
130    pub fn scene(&self) -> Option<NodeId> {
131        self.scene
132    }
133
134    /// Whether `layer` shows one of this scene's spaces, a scene behind one
135    /// of its doorways, whose surfaces this pick finds.
136    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    /// Whether the eye sees the space of the scene `layer` shows, through
143    /// the doorways before it.
144    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    /// The index among the scene's surfaces of the surface layer `layer`.
151    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    /// The content point of surface `index` that the window point shows,
159    /// when that surface is the nearest one there.
160    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    /// The surface `ray`, in the space the window's eye stands in, meets
166    /// first, by its index, and where.
167    pub fn nearest_along(&self, ray: Ray) -> Option<(usize, SurfaceHit)> {
168        self.nearest_in(ray, 0, f32::NEG_INFINITY)
169    }
170
171    /// The content point of surface `index` under the window point for a
172    /// gesture that surface holds: where the ray meets it, or past its edge,
173    /// where the ray meets the plane of the triangle it last met.
174    pub fn drag_point(&self, window: Point, index: usize) -> Option<Point> {
175        self.drag_point_along(self.ray(window)?, index)
176    }
177
178    /// [`Self::drag_point`] for a ray in the space the window's eye stands
179    /// in.
180    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    /// Where the content point `content` of surface `index` shows in the
192    /// window, also for a surface behind a doorway; `None` when it lies
193    /// nearer than the camera's near plane or behind the eye.
194    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    /// Where `point`, in the space the window's eye stands in, shows in the
202    /// window; `None` behind the eye.
203    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    /// The nearest surface of seen space `seen` that `ray` meets past
228    /// `beyond`; where it first passes through the doorway of a space seen
229    /// through this one, the nearest surface on the doorway's other side,
230    /// or this space's own where that side shows none.
231    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
267/// The mesh surface layer a child of a scene layer is, or holds as the
268/// wrapper of its outer draws.
269pub 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}