1use 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
27const BACK_THROUGH_DOORWAY: f32 = 1.0e-4;
32
33const SMALLEST_OPENING: f32 = 0.25;
36
37pub const DOORWAY_NEAR: f32 = 1.0 / 1024.0;
42
43pub struct Cell<'a> {
45 pub layer: &'a LayerNode,
47 pub scene: &'a Scene3d,
49 pub parent: Option<usize>,
51 pub hole: Option<(usize, Rect)>,
55 pub doorway: Mat4,
58 pub to_parent: Mat4,
60}
61
62pub struct Placed<'a> {
64 pub layer: &'a LayerNode,
66 pub surface: MeshSurface,
68 pub cell: usize,
70 pub content: Rect,
73}
74
75type Host = (usize, ProjectiveTransform);
78
79pub struct SpaceTree<'a> {
82 pub cells: Vec<Cell<'a>>,
84 pub surfaces: Vec<Placed<'a>>,
86}
87
88pub struct EyeView<'a> {
92 pub camera: Camera,
94 pub size: Size,
96 pub cell: usize,
98 pub seen: Vec<SeenSpace>,
101 pub surfaces: Vec<SeenSurface<'a>>,
103}
104
105pub struct SeenSpace {
107 pub cell: usize,
109 pub through: Option<usize>,
112 pub depth: u8,
116 pub to_eye: Mat4,
118 pub light: Option<Light>,
120 pub door: Option<SeenDoor>,
122}
123
124#[derive(Clone, Copy, Debug, PartialEq)]
126pub struct SeenDoor {
127 pub quad: Mat4,
129 pub away: f32,
131 pub clip: [f32; 4],
134 pub opening: Rect,
137}
138
139pub struct SeenSurface<'a> {
141 pub layer: &'a LayerNode,
143 pub surface: MeshSurface,
145 pub content: Rect,
147 pub seen: usize,
149}
150
151impl<'a> SpaceTree<'a> {
152 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 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 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 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 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 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 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 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#[derive(Clone, Copy, PartialEq, Eq)]
303enum Through {
304 Inward,
306 Outward,
308}
309
310impl<'a> EyeView<'a> {
311 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 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 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 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 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 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 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
557fn plane(normal: Vec3, point: Vec3, margin: f32) -> [f32; 4] {
560 [normal.x, normal.y, normal.z, -normal.dot(point) - margin]
561}
562
563pub 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
575pub 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
583pub 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 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
626pub 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}