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