1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! The eye a window's 3D scene is seen from, as a state an app holds: it
//! turns as the app asks, and walks through the spaces behind the scene's
//! doorways, stopped by solid surfaces where the renderer last placed them.
use cranpose_core::{
CompositionLocal, MutableState, NodeId, compositionLocalOf, mutableStateOf, remember,
};
use cranpose_macros::composable;
use cranpose_ui_graphics::space::{EyePlace, EyePose, Light, Scene3d, Vec3, WorldSink};
/// The eye of a window's 3D scene.
///
/// Read in a [`scene3d`](crate::Modifier::scene3d) closure through
/// [`Eye::scene`], it moves the scene's camera without recomposition. The
/// scene tells the renderer where the eye stands among the spaces behind its
/// doorways, and the renderer leaves the eye what it can bump into there
/// each time it places the scene, which [`Eye::walk`] reads.
#[derive(Clone)]
pub struct Eye {
pose: MutableState<EyePose>,
world: WorldSink,
radius: f32,
}
impl Eye {
/// An eye at `pose`: a ball of `radius`, in the units of the space it
/// stands in, that stops short of solid surfaces.
pub fn new(pose: EyePose, radius: f32) -> Self {
Self {
pose: mutableStateOf(pose),
world: WorldSink::default(),
radius,
}
}
/// Where the eye stands and which way it looks.
pub fn pose(&self) -> EyePose {
self.pose.get()
}
/// The scene seen through the eye: its camera, with `focal_length`, in
/// the space it stands in, and `light`.
pub fn scene(&self, focal_length: f32, light: Option<Light>) -> Scene3d {
let pose = self.pose.get();
Scene3d {
camera: pose.camera(focal_length),
light,
eye: Some(EyePlace {
doorways: pose.doorways,
world: self.world.clone(),
}),
}
}
/// Walks the eye `step`, in the coordinates of the space it stands in:
/// through the doorways the step passes, and pushed back from the solid
/// surfaces of the space it walks into, along which it slides.
pub fn walk(&self, step: Vec3) {
let world = self.world.current();
let radius = self.radius;
self.pose
.update(|pose| *pose = world.walk(pose, step, radius));
}
/// Turns the eye `yaw` radians to the right and `pitch` up (see
/// [`EyePose::turn`]).
pub fn turn(&self, yaw: f32, pitch: f32) {
self.pose.update(|pose| pose.turn(yaw, pitch));
}
/// Puts the eye at `pose`, through no doorway and stopped by nothing.
pub fn place(&self, pose: EyePose) {
self.pose.set(pose);
}
/// Where the eye stands and which way it looks, in the coordinates of
/// the space of the scene node `scene`: the one it stands in or one a
/// doorway from it. `None` when no such space lies there.
pub fn pose_in(&self, scene: NodeId) -> Option<EyePose> {
self.world.current().pose_in(&self.pose.get(), scene)
}
/// Puts the eye at `pose`, in the coordinates of the space of the scene
/// node `scene`, as if it walked there: that space is the one the eye
/// stands in or one a doorway from it. False, and the eye stays where
/// it is, when no such space lies there.
pub fn enter(&self, scene: NodeId, pose: EyePose) -> bool {
let Some(doorways) = self.world.current().doorways_to(scene) else {
return false;
};
self.pose.set(EyePose { doorways, ..pose });
true
}
}
/// Two handles are equal when they hold the same eye.
impl PartialEq for Eye {
fn eq(&self, other: &Self) -> bool {
self.world == other.world
}
}
/// An [`Eye`] kept across recompositions, made at `pose` the first time.
#[allow(non_snake_case)]
#[composable]
pub fn rememberEye(pose: impl FnOnce() -> EyePose, radius: f32) -> Eye {
remember(move || Eye::new(pose(), radius)).with(Clone::clone)
}
/// CompositionLocal carrying the eye of the window scene the content lies
/// in, or `None` outside one.
///
/// An app that shows a scene through an [`Eye`] provides the eye around the
/// scene's content, so a scene composed behind one of its doorways knows the
/// window's eye walks into it and leaves its own camera standing in the
/// doorway.
pub fn local_eye() -> CompositionLocal<Option<Eye>> {
crate::environment_locals::cached_local(|locals| &locals.eye, || compositionLocalOf(|| None))
}