Skip to main content

cranpose_ui_graphics/space/
eye.rs

1//! The eye a window's scene is seen from: where it stands among the scene's
2//! space and the spaces behind its doorways, which way it looks, and how it
3//! walks among them, stopped by solid surfaces and let through doorways.
4
5use std::{
6    ops::Range,
7    sync::{Arc, Mutex, PoisonError},
8};
9
10use super::{
11    math::{Mat4, Vec3},
12    mesh::Mesh,
13    scene::Camera,
14};
15use crate::{Point, Rect, Size};
16
17/// The direction a space calls down: its y.
18const DOWN: Vec3 = Vec3::new(0.0, 1.0, 0.0);
19
20/// How many doorways one step can take an eye through.
21const MAX_CROSSINGS: usize = 4;
22
23/// How many times a step pushes the eye out of the solids it overlaps: more
24/// than once lets it settle in a corner between two.
25const PUSHES: usize = 3;
26
27/// How many pieces a step is walked in at most, each no longer than half
28/// the eye's radius below this many, so it cannot pass through a thin wall.
29const MAX_PIECES: f32 = 256.0;
30
31/// How far an eye looks up or down at most, in radians from level.
32pub const PITCH_LIMIT: f32 = 1.4;
33
34/// Where an eye stands and which way it looks.
35#[derive(Clone, Debug, PartialEq)]
36pub struct EyePose {
37    /// The scene nodes of the doorways the eye walked through from the
38    /// window's scene, outermost first: it stands in the space of the last,
39    /// or in the window scene's own when there is none.
40    pub doorways: Arc<[usize]>,
41    /// Where it stands, in the space it stands in.
42    pub position: Vec3,
43    /// The direction it looks, of length one.
44    pub forward: Vec3,
45    /// The direction that shows as down, square to `forward`, of length
46    /// one.
47    pub down: Vec3,
48}
49
50impl EyePose {
51    /// An eye in the window scene's own space at `position`, turned `yaw`
52    /// radians to the right of the space's z and `pitch` up from level.
53    pub fn standing(position: Vec3, yaw: f32, pitch: f32) -> Self {
54        let mut pose = Self {
55            doorways: Arc::from([]),
56            position,
57            forward: Vec3::new(0.0, 0.0, 1.0),
58            down: DOWN,
59        };
60        pose.turn(yaw, pitch);
61        pose
62    }
63
64    /// The camera the eye sees through, with `focal_length`, in the space it
65    /// stands in.
66    pub fn camera(&self, focal_length: f32) -> Camera {
67        Camera::look_at(
68            self.position,
69            self.position + self.forward,
70            self.down,
71            focal_length,
72        )
73    }
74
75    /// How far the eye looks up from level, in radians.
76    pub fn pitch(&self) -> f32 {
77        (-self.forward.dot(DOWN)).clamp(-1.0, 1.0).asin()
78    }
79
80    /// Turns the eye `yaw` radians to the right about its space's down, then
81    /// `pitch` up, no further than [`PITCH_LIMIT`] from level.
82    pub fn turn(&mut self, yaw: f32, pitch: f32) {
83        self.forward = rotated(self.forward, DOWN, yaw);
84        self.down = rotated(self.down, DOWN, yaw);
85        let level = self.pitch();
86        let pitch = (level + pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT) - level;
87        let right = self.down.cross(self.forward);
88        if let Some(right) = right.normalized() {
89            self.forward = rotated(self.forward, right, pitch);
90            self.down = rotated(self.down, right, pitch);
91        }
92        self.square_up();
93    }
94
95    /// The level directions the eye walks along: ahead, as it looks but
96    /// level with its space's floor, and to its right.
97    pub fn walking_axes(&self) -> (Vec3, Vec3) {
98        let ahead = (self.forward - DOWN * self.forward.dot(DOWN))
99            .normalized()
100            .unwrap_or(self.forward);
101        (ahead, DOWN.cross(ahead))
102    }
103
104    /// Keeps `forward` and `down` square to each other and of length one.
105    fn square_up(&mut self) {
106        let forward = self
107            .forward
108            .normalized()
109            .unwrap_or(Vec3::new(0.0, 0.0, 1.0));
110        let down = (self.down - forward * self.down.dot(forward))
111            .normalized()
112            .unwrap_or(DOWN);
113        self.forward = forward;
114        self.down = down;
115    }
116}
117
118/// `vector` turned `angle` radians about the unit `axis`, by the right-hand
119/// rule.
120fn rotated(vector: Vec3, axis: Vec3, angle: f32) -> Vec3 {
121    let (sin, cos) = angle.sin_cos();
122    vector * cos + axis.cross(vector) * sin + axis * (axis.dot(vector) * (1.0 - cos))
123}
124
125/// Where a window's eye stands among the spaces of its scene, as the scene
126/// carries it to the renderer.
127#[derive(Clone, Debug, PartialEq)]
128pub struct EyePlace {
129    /// The scene nodes of the doorways the eye walked through, as
130    /// [`EyePose::doorways`] holds them.
131    pub doorways: Arc<[usize]>,
132    /// Where the renderer leaves the eye's world each time it places the
133    /// scene.
134    pub world: WorldSink,
135}
136
137/// Where the renderer leaves an eye's [`EyeWorld`] each time it places the
138/// scene, and where the eye reads it when it walks.
139#[derive(Clone, Debug, Default)]
140pub struct WorldSink(Arc<Mutex<Arc<EyeWorld>>>);
141
142impl WorldSink {
143    /// Builds the world again with `build`, in the storage of the last one
144    /// while no walk holds it.
145    pub fn rebuild(&self, build: impl FnOnce(&mut EyeWorld)) {
146        let mut held = self.0.lock().unwrap_or_else(PoisonError::into_inner);
147        if let Some(world) = Arc::get_mut(&mut held) {
148            world.clear();
149            build(world);
150            return;
151        }
152        let mut world = EyeWorld::default();
153        build(&mut world);
154        *held = Arc::new(world);
155    }
156
157    /// The world the renderer left last.
158    pub fn current(&self) -> Arc<EyeWorld> {
159        Arc::clone(&self.0.lock().unwrap_or_else(PoisonError::into_inner))
160    }
161}
162
163impl PartialEq for WorldSink {
164    fn eq(&self, other: &Self) -> bool {
165        Arc::ptr_eq(&self.0, &other.0)
166    }
167}
168
169/// What an eye can bump into and walk through around where it stood when
170/// the renderer last placed the scene: the space it stood in, the spaces
171/// next to it through doorways, their solid surfaces and the doorways
172/// between them, all in the coordinates of the space it stood in.
173#[derive(Debug, Default)]
174pub struct EyeWorld {
175    /// The scene node of the window's scene, whose own space an eye stands
176    /// in through no doorway.
177    window_scene: Option<usize>,
178    spaces: Vec<WorldSpace>,
179    solids: Vec<WorldSolid>,
180    holes: Vec<Rect>,
181    doorways: Vec<WorldDoorway>,
182}
183
184#[derive(Debug)]
185struct WorldSpace {
186    doorways: Arc<[usize]>,
187    to_base: Mat4,
188    from_base: Mat4,
189    /// How long a unit of the space is in the world's coordinates.
190    scale: f32,
191}
192
193#[derive(Debug)]
194struct WorldSolid {
195    space: usize,
196    mesh: Arc<Mesh>,
197    transform: Mat4,
198    size: Size,
199    holes: Range<usize>,
200    low: Vec3,
201    high: Vec3,
202}
203
204#[derive(Debug)]
205struct WorldDoorway {
206    outer: usize,
207    inner: usize,
208    /// The doorway's plane, positive in the inner space.
209    plane: [f32; 4],
210    /// Maps the world onto the unit square the doorway spans.
211    to_door: Mat4,
212}
213
214impl EyeWorld {
215    fn clear(&mut self) {
216        self.window_scene = None;
217        self.spaces.clear();
218        self.solids.clear();
219        self.holes.clear();
220        self.doorways.clear();
221    }
222
223    /// Names `scene` as the scene node of the window's scene, whose own
224    /// space is the one an eye stands in through no doorway.
225    pub fn set_window_scene(&mut self, scene: usize) {
226        self.window_scene = Some(scene);
227    }
228
229    /// The doorways an eye walks through from the window's scene into the
230    /// space of the scene node `scene`, when the world holds that space: the
231    /// space the eye stood in, or one a doorway from it.
232    pub fn doorways_to(&self, scene: usize) -> Option<Arc<[usize]>> {
233        self.space_of(scene)
234            .map(|space| Arc::clone(&space.doorways))
235    }
236
237    /// `pose`, of an eye in a space of the world, in the coordinates of the
238    /// space of the scene node `scene`; `None` when the world holds either
239    /// space not.
240    pub fn pose_in(&self, pose: &EyePose, scene: usize) -> Option<EyePose> {
241        let from = self
242            .spaces
243            .iter()
244            .find(|space| space.doorways == pose.doorways)?;
245        let to = self.space_of(scene)?;
246        let map = from.to_base.then(&to.from_base);
247        let mut seen = EyePose {
248            doorways: Arc::clone(&to.doorways),
249            position: map.transform_point(pose.position),
250            forward: map.transform_vector(pose.forward),
251            down: map.transform_vector(pose.down),
252        };
253        seen.square_up();
254        Some(seen)
255    }
256
257    fn space_of(&self, scene: usize) -> Option<&WorldSpace> {
258        self.spaces
259            .iter()
260            .find(|space| match space.doorways.last() {
261                Some(&last) => last == scene,
262                None => self.window_scene == Some(scene),
263            })
264    }
265
266    /// Adds the space an eye that walked through `doorways` stands in, whose
267    /// coordinates `to_base` maps into the world's, and returns its number.
268    pub fn add_space(&mut self, doorways: Arc<[usize]>, to_base: Mat4) -> usize {
269        self.spaces.push(WorldSpace {
270            doorways,
271            from_base: to_base.inverse().unwrap_or(Mat4::IDENTITY),
272            scale: to_base.length_scale(),
273            to_base,
274        });
275        self.spaces.len() - 1
276    }
277
278    /// Adds a surface of space `space` that an eye cannot walk through:
279    /// `mesh` placed by `transform` in the world's coordinates, carrying
280    /// content of `size` in which `holes` are doorways the eye passes.
281    pub fn add_solid(
282        &mut self,
283        (space, mesh): (usize, Arc<Mesh>),
284        transform: Mat4,
285        (size, holes): (Size, impl IntoIterator<Item = Rect>),
286    ) {
287        let start = self.holes.len();
288        self.holes.extend(holes);
289        let (low, high) = mesh.bounds_in(&transform);
290        self.solids.push(WorldSolid {
291            space,
292            mesh,
293            transform,
294            size,
295            holes: start..self.holes.len(),
296            low,
297            high,
298        });
299    }
300
301    /// Adds the doorway from space `outer` into space `inner` that `door`
302    /// spans: the unit square placed in the world's coordinates, with
303    /// `inner` lying beyond it along its z.
304    pub fn add_doorway(&mut self, (outer, inner): (usize, usize), door: Mat4) {
305        let corner = door.transform_point(Vec3::ZERO);
306        let normal = door
307            .transform_vector(Vec3::new(0.0, 0.0, 1.0))
308            .normalized()
309            .unwrap_or(Vec3::new(0.0, 0.0, 1.0));
310        self.doorways.push(WorldDoorway {
311            outer,
312            inner,
313            plane: [normal.x, normal.y, normal.z, -normal.dot(corner)],
314            to_door: door.inverse().unwrap_or(Mat4::IDENTITY),
315        });
316    }
317
318    /// Where an eye at `from`, a ball of `radius`, ends up when it walks
319    /// `step`, both in the units of the space it stands in: through the
320    /// doorways the step passes into the spaces behind them, and pushed out
321    /// of the solid surfaces of the space it walks in, along which it
322    /// slides. Its ball keeps `radius` in the units of each space it walks
323    /// into. The step goes in pieces of at most half the radius in the
324    /// smallest space, so it cannot pass through a wall. An eye whose space
325    /// the world does not hold walks freely.
326    pub fn walk(&self, from: &EyePose, step: Vec3, radius: f32) -> EyePose {
327        let Some(start) = self
328            .spaces
329            .iter()
330            .position(|space| space.doorways == from.doorways)
331        else {
332            return EyePose {
333                position: from.position + step,
334                ..from.clone()
335            };
336        };
337        let base = &self.spaces[start];
338        let step = base.to_base.transform_vector(step);
339        let smallest = self
340            .spaces
341            .iter()
342            .map(|space| space.scale)
343            .fold(f32::INFINITY, f32::min);
344        let pieces = (step.length() / (radius * smallest * 0.5).max(f32::EPSILON))
345            .ceil()
346            .clamp(1.0, MAX_PIECES);
347        let piece = step * (1.0 / pieces);
348        let mut at = base.to_base.transform_point(from.position);
349        let mut space = start;
350        for _ in 0..pieces as usize {
351            let end = at + piece;
352            let mut along = at;
353            for _ in 0..MAX_CROSSINGS {
354                let crossed = self
355                    .doorways
356                    .iter()
357                    .find_map(|door| door.crossing(space, (along, end)));
358                let Some((next, crossing)) = crossed else {
359                    break;
360                };
361                space = next;
362                along = crossing;
363            }
364            let reach = radius * self.spaces[space].scale;
365            at = self.kept_behind_doorways(space, self.pushed_out(space, end, reach), reach);
366        }
367        let target = &self.spaces[space];
368        let turn = base.to_base.then(&target.from_base);
369        let mut pose = EyePose {
370            doorways: Arc::clone(&target.doorways),
371            position: target.from_base.transform_point(at),
372            forward: turn.transform_vector(from.forward),
373            down: turn.transform_vector(from.down),
374        };
375        pose.square_up();
376        pose
377    }
378
379    /// `at` kept a ball of `radius` behind the planes of the doorways into
380    /// `space`, but where it stands within a doorway's rect: a space lies
381    /// behind the doorways it is entered by, and only through them does the
382    /// eye leave it.
383    fn kept_behind_doorways(&self, space: usize, mut at: Vec3, radius: f32) -> Vec3 {
384        for door in self.doorways.iter().filter(|door| door.inner == space) {
385            let [x, y, z, offset] = door.plane;
386            let normal = Vec3::new(x, y, z);
387            let behind = normal.dot(at) + offset;
388            let on_door = door.to_door.transform_point(at);
389            let within = (0.0..=1.0).contains(&on_door.x) && (0.0..=1.0).contains(&on_door.y);
390            if behind < radius && !within {
391                at = at + normal * (radius - behind);
392            }
393        }
394        at
395    }
396
397    /// `at` pushed out of the solids of `space` it lies within `radius` of.
398    fn pushed_out(&self, space: usize, mut at: Vec3, radius: f32) -> Vec3 {
399        for _ in 0..PUSHES {
400            let mut pushed = false;
401            for solid in self.solids.iter().filter(|solid| solid.space == space) {
402                if let Some(push) = solid.push(at, radius, &self.holes[solid.holes.clone()]) {
403                    at = at + push;
404                    pushed = true;
405                }
406            }
407            if !pushed {
408                break;
409            }
410        }
411        at
412    }
413}
414
415impl WorldSolid {
416    /// How far to move a ball of `radius` at `at` so it no longer reaches
417    /// into the surface; `None` when it does not, or reaches it where a
418    /// doorway in `holes` opens.
419    fn push(&self, at: Vec3, radius: f32, holes: &[Rect]) -> Option<Vec3> {
420        let outside =
421            |value: f32, low: f32, high: f32| value < low - radius || value > high + radius;
422        if outside(at.x, self.low.x, self.high.x)
423            || outside(at.y, self.low.y, self.high.y)
424            || outside(at.z, self.low.z, self.high.z)
425        {
426            return None;
427        }
428        let contact = self.mesh.nearest(at, &self.transform)?;
429        if contact.distance >= radius {
430            return None;
431        }
432        let content = Point::new(
433            contact.uv[0] * self.size.width,
434            contact.uv[1] * self.size.height,
435        );
436        if holes.iter().any(|hole| hole.contains(content.x, content.y)) {
437            return None;
438        }
439        let away = (at - contact.point).normalized()?;
440        Some(away * (radius - contact.distance))
441    }
442}
443
444impl WorldDoorway {
445    /// The space a step from `from` to `to` in `space` passes into through
446    /// this doorway, and where it passes; `None` when it does not.
447    fn crossing(&self, space: usize, (from, to): (Vec3, Vec3)) -> Option<(usize, Vec3)> {
448        let (next, inward) = if space == self.outer {
449            (self.inner, true)
450        } else if space == self.inner {
451            (self.outer, false)
452        } else {
453            return None;
454        };
455        let [x, y, z, offset] = self.plane;
456        let side = |point: Vec3| x * point.x + y * point.y + z * point.z + offset;
457        let (before, after) = (side(from), side(to));
458        let passes = if inward {
459            before < 0.0 && after >= 0.0
460        } else {
461            before >= 0.0 && after < 0.0
462        };
463        if !passes {
464            return None;
465        }
466        let at = from + (to - from) * (before / (before - after));
467        let on_door = self.to_door.transform_point(at);
468        ((0.0..=1.0).contains(&on_door.x) && (0.0..=1.0).contains(&on_door.y)).then_some((next, at))
469    }
470}