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, Rect, Size,
6    space::{Camera, Mat4, MeshSurface, Ray, SurfaceHit, Vec3},
7};
8
9use crate::{
10    graph::{LayerNode, ProjectiveTransform},
11    space::{EyeView, SpaceTree},
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    // The rect of its content in its layer, which the mesh's texture
69    // spans. The pick's content points lie in the layer.
70    content: Rect,
71    /// The seen space the surface lies in.
72    seen: usize,
73    /// The triangle the last ray that met this surface met, on whose plane
74    /// a drag that leaves the mesh goes on.
75    held: Cell<Option<u32>>,
76}
77
78impl PickSurface {
79    // The content point `point`, from the corner of the texture, in the
80    // surface's layer.
81    fn in_layer(&self, point: Point) -> Point {
82        Point::new(point.x + self.content.x, point.y + self.content.y)
83    }
84
85    // Where `ray` meets the surface, its content point in the layer.
86    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    /// The pick of the scene that `layer` shows, with `to_window` mapping
95    /// the layer to the window; `None` when the layer shows no scene. Its
96    /// surfaces are those the window's eye sees.
97    pub fn of(layer: &LayerNode, to_window: ProjectiveTransform) -> Option<Self> {
98        // A hit walk asks every layer: one that opens no scene stops here,
99        // before any transform work.
100        let tree = SpaceTree::of(layer)?;
101        // The scene fills its layer's bounds.
102        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    /// The node of the scene the window shows.
152    pub fn scene(&self) -> Option<NodeId> {
153        self.scene
154    }
155
156    /// Whether `layer` shows one of this scene's spaces, a scene behind one
157    /// of its doorways, whose surfaces this pick finds.
158    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    /// Whether the eye sees the space of the scene `layer` shows, through
165    /// the doorways before it.
166    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    /// The index among the scene's surfaces of the surface layer `layer`.
173    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    /// The content point of surface `index` that the window point shows,
181    /// when that surface is the nearest one there.
182    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    /// The surface `ray`, in the space the window's eye stands in, meets
188    /// first, by its index, and where.
189    pub fn nearest_along(&self, ray: Ray) -> Option<(usize, SurfaceHit)> {
190        self.nearest_in(ray, 0, f32::NEG_INFINITY)
191    }
192
193    /// The content point of surface `index` under the window point for a
194    /// gesture that surface holds: where the ray meets it, or past its edge,
195    /// where the ray meets the plane of the triangle it last met.
196    pub fn drag_point(&self, window: Point, index: usize) -> Option<Point> {
197        self.drag_point_along(self.ray(window)?, index)
198    }
199
200    /// [`Self::drag_point`] for a ray in the space the window's eye stands
201    /// in.
202    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    /// Where the content point `content` of surface `index` shows in the
215    /// window, also for a surface behind a doorway; `None` when it lies
216    /// nearer than the camera's near plane or behind the eye.
217    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    /// Where `point`, in the space the window's eye stands in, shows in the
228    /// window; `None` behind the eye.
229    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    /// The nearest surface of seen space `seen` that `ray` meets past
254    /// `beyond`; where it first passes through the doorway of a space seen
255    /// through this one, the nearest surface on the doorway's other side,
256    /// or this space's own where that side shows none.
257    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
292/// The mesh surface layer a child of a scene layer is, or holds as the
293/// wrapper of its outer draws.
294pub 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}