Skip to main content

cranpose_render_common/
space.rs

1//! Where the scenes a window shows put their 3D layers, as one map that
2//! drawing, picking, window geometry and the eye's walking all read.
3//!
4//! Inside a scene, a layer takes its 3D frame from its parent's: its place in
5//! the parent's layout, turned into 3D on the parent's plane. A mesh surface
6//! places its mesh in that frame and its children's frames in its content; a
7//! 3D transform moves the frames beneath it; a scene opens a space of its
8//! own, behind a doorway at its layer's bounds. The window's eye stands in
9//! one of these spaces and sees the others through the doorways that show on
10//! screen, as deep as they show.
11//!
12//! A scene and a mesh surface each cover their layer's bounds, as a Compose
13//! graphics layer covers the coordinator after it: a modifier before them,
14//! as a padding or a size the parent does not hold, moves or sizes them
15//! apart from their node.
16
17use std::sync::Arc;
18
19use cranpose_core::NodeId;
20use cranpose_ui_graphics::{
21    Rect, Size,
22    space::{Camera, EyeWorld, LayerSpace, Light, Mat4, MeshSurface, Scene3d, Vec3},
23};
24
25use crate::graph::{LayerNode, ProjectiveTransform, RenderNode};
26
27/// How far past a doorway's plane, against its distance from the eye's
28/// space's origin, the cut lies for a space seen back through the doorway,
29/// so the surface the doorway opens in, which lies on the plane, shows a
30/// hole.
31const BACK_THROUGH_DOORWAY: f32 = 1.0e-4;
32
33/// The smallest area, in logical pixels, a doorway's opening on screen
34/// takes for the eye to see through it.
35const SMALLEST_OPENING: f32 = 0.25;
36
37/// How near the eye a doorway still opens, as a fraction of the distance of
38/// the camera's near plane: a renderer shows the part of a doorway between
39/// this depth and the near plane on the near plane, so an eye that steps
40/// into a doorway keeps seeing through it.
41pub const DOORWAY_NEAR: f32 = 1.0 / 1024.0;
42
43/// A space a scene opens.
44pub struct Cell<'a> {
45    /// The scene's layer.
46    pub layer: &'a LayerNode,
47    /// The scene.
48    pub scene: &'a Scene3d,
49    /// The space its doorway lies in; `None` for the window's scene.
50    pub parent: Option<usize>,
51    /// The surface of the parent space whose content holds the doorway, by
52    /// its number among the tree's surfaces, and the doorway's rect in that
53    /// content, when the doorway lies flat in it.
54    pub hole: Option<(usize, Rect)>,
55    /// Places the unit square on the doorway in the parent space, the space
56    /// lying beyond it along its z; the identity for the window's scene.
57    pub doorway: Mat4,
58    /// Maps the space into its parent's.
59    pub to_parent: Mat4,
60}
61
62/// A mesh surface placed in its space.
63pub struct Placed<'a> {
64    /// The surface's layer.
65    pub layer: &'a LayerNode,
66    /// The surface, its mesh placed in its space.
67    pub surface: MeshSurface,
68    /// Its space.
69    pub cell: usize,
70    /// The rect of its content in its layer: the layer's bounds, which the
71    /// mesh's texture spans.
72    pub content: Rect,
73}
74
75/// The surface whose content a layer lies flat in, by its index, and the
76/// map from the layer into that content.
77type Host = (usize, ProjectiveTransform);
78
79/// The spaces of a window's scene and the scenes behind its doorways, and
80/// their mesh surfaces, in the order the composition holds them.
81pub struct SpaceTree<'a> {
82    /// The spaces; the first is the window scene's own.
83    pub cells: Vec<Cell<'a>>,
84    /// The mesh surfaces, in draw order.
85    pub surfaces: Vec<Placed<'a>>,
86}
87
88/// Where the window's eye stands, with its camera, and what it sees from
89/// there: the spaces it sees through doorways and their surfaces, all in
90/// the coordinates of the space it stands in.
91pub struct EyeView<'a> {
92    /// The eye's camera.
93    pub camera: Camera,
94    /// The size of the window scene's layer bounds, which the camera fills.
95    pub size: Size,
96    /// The space the eye stands in, by its number in the tree.
97    pub cell: usize,
98    /// The spaces the eye sees: its own first, then each through the
99    /// doorway it shows through, before the spaces seen through it.
100    pub seen: Vec<SeenSpace>,
101    /// The surfaces of the spaces it sees, in draw order.
102    pub surfaces: Vec<SeenSurface<'a>>,
103}
104
105/// A space the eye sees.
106pub struct SeenSpace {
107    /// The space, by its number in the tree.
108    pub cell: usize,
109    /// The seen space whose doorway it shows through; `None` for the eye's
110    /// own.
111    pub through: Option<usize>,
112    /// How many doorways deep it shows: 0 for the eye's own space. The
113    /// stencil that marks where each doorway opens counts them in eight
114    /// bits, so the eye sees no deeper than 255.
115    pub depth: u8,
116    /// Maps the space into the eye's.
117    pub to_eye: Mat4,
118    /// The space's light, in the eye's space.
119    pub light: Option<Light>,
120    /// The doorway it shows through; `None` for the eye's own space.
121    pub door: Option<SeenDoor>,
122}
123
124/// A doorway as the eye sees through it.
125#[derive(Clone, Copy, Debug, PartialEq)]
126pub struct SeenDoor {
127    /// Places the unit square on the doorway, in the eye's space.
128    pub quad: Mat4,
129    /// Which way along the quad's z points away from the eye: 1 or -1.
130    pub away: f32,
131    /// The plane past which the space behind the doorway shows, in the eye's
132    /// space: a point `p` shows where `normal . p + offset >= 0`.
133    pub clip: [f32; 4],
134    /// The bounds of the doorway's opening on screen, within those of the
135    /// doorways it shows through, in the window scene node's coordinates.
136    pub opening: Rect,
137}
138
139/// A surface the eye sees.
140pub struct SeenSurface<'a> {
141    /// The surface's layer.
142    pub layer: &'a LayerNode,
143    /// The surface, its mesh placed in the eye's space.
144    pub surface: MeshSurface,
145    /// The rect of its content in its layer, as [`Placed::content`].
146    pub content: Rect,
147    /// The seen space it lies in, by its number among [`EyeView::seen`].
148    pub seen: usize,
149}
150
151impl<'a> SpaceTree<'a> {
152    /// The spaces and surfaces of the scene `scene_layer` shows; `None`
153    /// when it shows none.
154    pub fn of(scene_layer: &'a LayerNode) -> Option<Self> {
155        let scene = scene_of(scene_layer)?;
156        let mut tree = Self {
157            cells: Vec::new(),
158            surfaces: Vec::new(),
159        };
160        tree.open(scene_layer, scene, None);
161        Some(tree)
162    }
163
164    /// Whether `layer` is one of the tree's scenes.
165    pub fn holds_scene(&self, layer: &LayerNode) -> bool {
166        layer.node_id.is_some()
167            && self
168                .cells
169                .iter()
170                .any(|cell| cell.layer.node_id == layer.node_id)
171    }
172
173    /// The space an eye that walked through the doorways of the scene nodes
174    /// `path` stands in: as far along the path as its doorways still lead.
175    pub fn cell_along(&self, path: &[usize]) -> usize {
176        let mut cell = 0;
177        for &node in path {
178            let next = self.cells.iter().position(|inner| {
179                inner.parent == Some(cell) && inner.layer.node_id == Some(NodeId(node))
180            });
181            match next {
182                Some(next) => cell = next,
183                None => break,
184            }
185        }
186        cell
187    }
188
189    /// Maps space `cell` into the window scene's.
190    pub fn to_root(&self, cell: usize) -> Mat4 {
191        let mut transform = Mat4::IDENTITY;
192        let mut at = Some(cell);
193        while let Some(index) = at {
194            let cell = &self.cells[index];
195            transform = transform.then(&cell.to_parent);
196            at = cell.parent;
197        }
198        transform
199    }
200
201    /// Opens the space the scene `layer` shows: `place` holds the space its
202    /// doorway lies in, the frame of `layer` there, and the surface whose
203    /// content holds the doorway with the map from `layer` into that
204    /// content.
205    fn open(
206        &mut self,
207        layer: &'a LayerNode,
208        scene: &'a Scene3d,
209        place: Option<(usize, Mat4, Option<Host>)>,
210    ) {
211        let index = self.cells.len();
212        let cell = match place {
213            None => Cell {
214                layer,
215                scene,
216                parent: None,
217                hole: None,
218                doorway: Mat4::IDENTITY,
219                to_parent: Mat4::IDENTITY,
220            },
221            Some((parent, frame, host)) => {
222                let rect = layer.local_bounds;
223                Cell {
224                    layer,
225                    scene,
226                    parent: Some(parent),
227                    hole: host
228                        .map(|(surface, to_content)| (surface, to_content.bounds_for_rect(rect))),
229                    doorway: Mat4::scaling(Vec3::new(rect.width, rect.height, 1.0))
230                        .then(&Mat4::translation(Vec3::new(rect.x, rect.y, 0.0)))
231                        .then(&frame),
232                    to_parent: scene.camera.doorway(rect).then(&frame),
233                }
234            }
235        };
236        self.cells.push(cell);
237        self.children(layer, &Mat4::IDENTITY, (index, None));
238    }
239
240    /// Visits the children of `layer`, whose frame `frame` maps it into
241    /// space `cell`; `host` holds the surface whose content `layer` lies flat
242    /// in, with the map from `layer` into that content.
243    fn children(
244        &mut self,
245        layer: &'a LayerNode,
246        frame: &Mat4,
247        (cell, host): (usize, Option<Host>),
248    ) {
249        for child in &layer.children {
250            let RenderNode::Layer(child) = child else {
251                continue;
252            };
253            if !child.has_space() {
254                continue;
255            }
256            let child_frame = embed(child.transform_to_parent).then(frame);
257            let child_host = host
258                .map(|(surface, to_content)| (surface, child.transform_to_parent.then(to_content)));
259            match child.graphics_layer.space.as_deref() {
260                Some(LayerSpace::Scene(scene)) => {
261                    self.open(child, scene, Some((cell, child_frame, child_host)));
262                }
263                Some(LayerSpace::Surface(surface)) => {
264                    self.surface(child, surface, &child_frame, cell);
265                }
266                Some(LayerSpace::Transform(transform)) => {
267                    self.children(child, &transform.then(&child_frame), (cell, None));
268                }
269                None => self.children(child, &child_frame, (cell, child_host)),
270            }
271        }
272    }
273
274    /// Places the mesh surface `layer` draws its content on in `frame`, in
275    /// space `cell`, and the frames of its children in its content.
276    fn surface(&mut self, layer: &'a LayerNode, surface: &MeshSurface, frame: &Mat4, cell: usize) {
277        let content = layer.local_bounds;
278        let placed = surface.placed_by(frame);
279        let to_scene = placed.content_to_scene(content.size());
280        let index = self.surfaces.len();
281        self.surfaces.push(Placed {
282            layer,
283            surface: placed,
284            cell,
285            content,
286        });
287        if let Some(to_scene) = to_scene {
288            // The children lie in the layer, whose content starts at the
289            // corner of its bounds.
290            let to_content = ProjectiveTransform::translation(-content.x, -content.y);
291            self.children(
292                layer,
293                &embed(to_content).then(&to_scene),
294                (cell, Some((index, to_content))),
295            );
296        }
297    }
298}
299
300/// What the doorway between a space and the next one an eye looks into
301/// opens onto.
302#[derive(Clone, Copy, PartialEq, Eq)]
303enum Through {
304    /// A space behind a doorway in the space the eye looks from.
305    Inward,
306    /// The space the doorway of the space the eye looks from lies in.
307    Outward,
308}
309
310impl<'a> EyeView<'a> {
311    /// What the window's eye sees of `tree`, the scene of a node of `size`:
312    /// it stands where the window scene's [`Scene3d::eye`] says, and sees
313    /// through each doorway whose opening shows on screen.
314    pub fn of(tree: &SpaceTree<'a>, size: Size) -> Self {
315        let root = tree.cells[0].scene;
316        let cell = root
317            .eye
318            .as_ref()
319            .map_or(0, |eye| tree.cell_along(&eye.doorways));
320        let mut view = Self {
321            camera: root.camera,
322            size,
323            cell,
324            seen: vec![SeenSpace {
325                cell,
326                through: None,
327                depth: 0,
328                to_eye: Mat4::IDENTITY,
329                light: tree.cells[cell].scene.light,
330                door: None,
331            }],
332            surfaces: Vec::new(),
333        };
334        view.look_through(tree, (0, None), Rect::from_size(size));
335        let mut seen_of = vec![None; tree.cells.len()];
336        for (index, seen) in view.seen.iter().enumerate() {
337            seen_of[seen.cell] = Some(index);
338        }
339        for placed in &tree.surfaces {
340            let Some(seen) = seen_of[placed.cell] else {
341                continue;
342            };
343            view.surfaces.push(SeenSurface {
344                layer: placed.layer,
345                surface: placed.surface.placed_by(&view.seen[seen].to_eye),
346                content: placed.content,
347                seen,
348            });
349        }
350        view
351    }
352
353    /// Looks through the doorways of seen space `seen`, other than the one
354    /// into the space `came_from`, within `opening`.
355    fn look_through(
356        &mut self,
357        tree: &SpaceTree<'a>,
358        (seen, came_from): (usize, Option<usize>),
359        opening: Rect,
360    ) {
361        let (cell, to_eye) = (self.seen[seen].cell, self.seen[seen].to_eye);
362        for (inner, space) in tree.cells.iter().enumerate() {
363            if space.parent != Some(cell) || Some(inner) == came_from {
364                continue;
365            }
366            let inner_to_eye = space.to_parent.then(&to_eye);
367            let quad = space.doorway.then(&to_eye);
368            self.through(
369                tree,
370                (seen, inner, inner_to_eye),
371                (quad, Through::Inward),
372                opening,
373            );
374        }
375        let space = &tree.cells[cell];
376        if let Some(outer) = space.parent.filter(|outer| Some(*outer) != came_from)
377            && let Some(from_parent) = space.to_parent.inverse()
378        {
379            let outer_to_eye = from_parent.then(&to_eye);
380            let quad = space.doorway.then(&outer_to_eye);
381            self.through(
382                tree,
383                (seen, outer, outer_to_eye),
384                (quad, Through::Outward),
385                opening,
386            );
387        }
388    }
389
390    /// Sees space `cell`, which `to_eye` maps into the eye's, through the
391    /// doorway `quad` spans, from seen space `seen`, within `opening`; then
392    /// looks on through its doorways.
393    fn through(
394        &mut self,
395        tree: &SpaceTree<'a>,
396        (seen, cell, to_eye): (usize, usize, Mat4),
397        (quad, through): (Mat4, Through),
398        opening: Rect,
399    ) {
400        let Some(depth) = self.seen[seen].depth.checked_add(1) else {
401            return;
402        };
403        let corner = quad.transform_point(Vec3::ZERO);
404        let Some(normal) = quad.transform_vector(Vec3::new(0.0, 0.0, 1.0)).normalized() else {
405            return;
406        };
407        let side = normal.dot(self.camera.eye() - corner);
408        // A doorway opens one way: into its space from the side its z leaves,
409        // and back out of it from the side its z points to.
410        let (facing, away, clip) = match through {
411            Through::Inward => (side < 0.0, 1.0, plane(normal, corner, 0.0)),
412            Through::Outward => {
413                let margin = BACK_THROUGH_DOORWAY * normal.dot(corner).abs().max(1.0);
414                (side > 0.0, -1.0, plane(-normal, corner, margin))
415            }
416        };
417        let shown = facing
418            .then(|| self.opening(&quad))
419            .flatten()
420            .and_then(|shown| shown.intersect(opening))
421            .filter(|shown| shown.width * shown.height >= SMALLEST_OPENING);
422        let Some(shown) = shown else {
423            return;
424        };
425        let light = tree.cells[cell].scene.light.map(|light| Light {
426            direction: to_eye.transform_vector(light.direction),
427            ..light
428        });
429        self.seen.push(SeenSpace {
430            cell,
431            through: Some(seen),
432            depth,
433            to_eye,
434            light,
435            door: Some(SeenDoor {
436                quad,
437                away,
438                clip,
439                opening: shown,
440            }),
441        });
442        let index = self.seen.len() - 1;
443        self.look_through(tree, (index, Some(self.seen[seen].cell)), shown);
444    }
445
446    /// The bounds on screen of the unit square `quad` places in the eye's
447    /// space, of its part deeper than [`DOORWAY_NEAR`] of the camera's near
448    /// plane; `None` when it lies wholly nearer.
449    fn opening(&self, quad: &Mat4) -> Option<Rect> {
450        let to_camera = quad.then(&self.camera.view);
451        let corners = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
452            .map(|(u, v)| to_camera.transform_point(Vec3::new(u, v, 0.0)));
453        let near = self.camera.near() * DOORWAY_NEAR;
454        let (mut low, mut high) = (
455            (f32::INFINITY, f32::INFINITY),
456            (f32::NEG_INFINITY, f32::NEG_INFINITY),
457        );
458        let mut any = false;
459        let mut show = |point: Vec3| {
460            let scale = self.camera.focal_length / point.z;
461            let (x, y) = (
462                self.size.width * 0.5 + point.x * scale,
463                self.size.height * 0.5 + point.y * scale,
464            );
465            low = (low.0.min(x), low.1.min(y));
466            high = (high.0.max(x), high.1.max(y));
467            any = true;
468        };
469        for (index, &from) in corners.iter().enumerate() {
470            let to = corners[(index + 1) % corners.len()];
471            if from.z >= near {
472                show(from);
473            }
474            if (from.z >= near) != (to.z >= near) {
475                show(from + (to - from) * ((near - from.z) / (to.z - from.z)));
476            }
477        }
478        any.then_some(Rect {
479            x: low.0,
480            y: low.1,
481            width: high.0 - low.0,
482            height: high.1 - low.1,
483        })
484    }
485
486    /// Fills `world` with what the eye can bump into and walk through: the
487    /// space it stands in, the spaces one doorway from it, their solid
488    /// surfaces and the doorways between them, in the eye's space.
489    pub fn build_world(&self, tree: &SpaceTree<'_>, world: &mut EyeWorld) {
490        if let Some(window_scene) = tree.cells[0].layer.node_id {
491            world.set_window_scene(window_scene.0);
492        }
493        let walked = tree.walked_to(self.cell);
494        let base = world.add_space(Arc::clone(&walked), Mat4::IDENTITY);
495        let mut spaces = vec![(self.cell, base, Mat4::IDENTITY)];
496        let own = &tree.cells[self.cell];
497        if let Some(outer) = own.parent
498            && let Some(from_parent) = own.to_parent.inverse()
499        {
500            let path: Arc<[usize]> = Arc::from(&walked[..walked.len().saturating_sub(1)]);
501            let index = world.add_space(path, from_parent);
502            world.add_doorway((index, base), own.doorway.then(&from_parent));
503            spaces.push((outer, index, from_parent));
504        }
505        for (inner, space) in tree.cells.iter().enumerate() {
506            let (Some(cell), Some(node)) = (space.parent, space.layer.node_id) else {
507                continue;
508            };
509            if cell != self.cell {
510                continue;
511            }
512            let path: Arc<[usize]> = walked.iter().copied().chain([node.0]).collect();
513            let index = world.add_space(path, space.to_parent);
514            world.add_doorway((base, index), space.doorway);
515            spaces.push((inner, index, space.to_parent));
516        }
517        for (surface_index, placed) in tree.surfaces.iter().enumerate() {
518            let Some(&(_, space, to_base)) =
519                spaces.iter().find(|(cell, _, _)| *cell == placed.cell)
520            else {
521                continue;
522            };
523            if !placed.surface.solid {
524                continue;
525            }
526            let holes = tree.cells.iter().filter_map(|cell| {
527                cell.hole
528                    .filter(|(host, _)| *host == surface_index)
529                    .map(|(_, rect)| rect)
530            });
531            world.add_solid(
532                (space, Arc::clone(&placed.surface.mesh)),
533                placed.surface.transform.then(&to_base),
534                (placed.content.size(), holes),
535            );
536        }
537    }
538}
539
540impl SpaceTree<'_> {
541    /// The scene nodes of the doorways from the window's scene to space
542    /// `cell`, outermost first.
543    fn walked_to(&self, cell: usize) -> Arc<[usize]> {
544        let mut path = Vec::new();
545        let mut at = cell;
546        while let Some(parent) = self.cells[at].parent {
547            if let Some(node) = self.cells[at].layer.node_id {
548                path.push(node.0);
549            }
550            at = parent;
551        }
552        path.reverse();
553        Arc::from(path)
554    }
555}
556
557/// The plane through `point` square to the unit `normal`, moved `margin`
558/// back along it: positive where `normal` points.
559fn plane(normal: Vec3, point: Vec3, margin: f32) -> [f32; 4] {
560    [normal.x, normal.y, normal.z, -normal.dot(point) - margin]
561}
562
563/// A plane map of the frame's plane z = 0 as a 3D map: z stays and
564/// distances along it scale as the plane's do not.
565pub fn embed(transform: ProjectiveTransform) -> Mat4 {
566    let [[a, b, c], [d, e, f], [g, h, i]] = transform.matrix();
567    Mat4::from_rows([
568        [a, b, 0.0, c],
569        [d, e, 0.0, f],
570        [0.0, 0.0, 1.0, 0.0],
571        [g, h, 0.0, i],
572    ])
573}
574
575/// The scene a layer shows, or `None`.
576pub fn scene_of(layer: &LayerNode) -> Option<&Scene3d> {
577    match layer.graphics_layer.space.as_deref()? {
578        LayerSpace::Scene(scene) => Some(scene),
579        LayerSpace::Surface(_) | LayerSpace::Transform(_) => None,
580    }
581}
582
583/// Visits the space tree of each scene the window shows from `layer` down,
584/// what its eye sees of it, and the map from the scene's own coordinates,
585/// the top left of its layer's bounds at the origin, to the window, given
586/// `to_window` for `layer`.
587pub fn for_each_window_eye(
588    layer: &LayerNode,
589    to_window: ProjectiveTransform,
590    visit: &mut impl FnMut(&SpaceTree<'_>, &EyeView<'_>, ProjectiveTransform),
591) {
592    for_each_window_scene(layer, to_window, &mut |scene_layer, to_window| {
593        let Some(tree) = SpaceTree::of(scene_layer) else {
594            return;
595        };
596        let rect = scene_layer.local_bounds;
597        let view = EyeView::of(&tree, rect.size());
598        visit(
599            &tree,
600            &view,
601            ProjectiveTransform::translation(rect.x, rect.y).then(to_window),
602        );
603    });
604}
605
606impl EyeView<'_> {
607    /// The map from the layer of seen surface `seen` to the window, as the
608    /// eye shows it, given `to_window` for the scene's own coordinates;
609    /// `None` for a surface whose mesh carries no texture plane.
610    pub fn content_to_window(
611        &self,
612        seen: &SeenSurface<'_>,
613        to_window: ProjectiveTransform,
614    ) -> Option<ProjectiveTransform> {
615        let content = seen.content;
616        seen.surface
617            .content_transform(&self.camera, self.size, content.size())
618            .map(|shown| {
619                ProjectiveTransform::translation(-content.x, -content.y)
620                    .then(shown)
621                    .then(to_window)
622            })
623    }
624}
625
626/// Visits each scene the window shows from `layer` down: a layer that
627/// shows a scene outside every other scene's space, with the map from its
628/// local space into the window, given `to_window` for `layer`.
629pub fn for_each_window_scene<'a>(
630    layer: &'a LayerNode,
631    to_window: ProjectiveTransform,
632    visit: &mut impl FnMut(&'a LayerNode, ProjectiveTransform),
633) {
634    if scene_of(layer).is_some() {
635        visit(layer, to_window);
636        return;
637    }
638    for child in &layer.children {
639        if let RenderNode::Layer(child) = child
640            && child.has_space()
641        {
642            for_each_window_scene(child, child.transform_to_parent.then(to_window), visit);
643        }
644    }
645}