1use 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
17const DOWN: Vec3 = Vec3::new(0.0, 1.0, 0.0);
19
20const MAX_CROSSINGS: usize = 4;
22
23const PUSHES: usize = 3;
26
27const MAX_PIECES: f32 = 256.0;
30
31pub const PITCH_LIMIT: f32 = 1.4;
33
34#[derive(Clone, Debug, PartialEq)]
36pub struct EyePose {
37 pub doorways: Arc<[usize]>,
41 pub position: Vec3,
43 pub forward: Vec3,
45 pub down: Vec3,
48}
49
50impl EyePose {
51 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 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 pub fn pitch(&self) -> f32 {
77 (-self.forward.dot(DOWN)).clamp(-1.0, 1.0).asin()
78 }
79
80 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 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 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
118fn 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#[derive(Clone, Debug, PartialEq)]
128pub struct EyePlace {
129 pub doorways: Arc<[usize]>,
132 pub world: WorldSink,
135}
136
137#[derive(Clone, Debug, Default)]
140pub struct WorldSink(Arc<Mutex<Arc<EyeWorld>>>);
141
142impl WorldSink {
143 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 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#[derive(Debug, Default)]
174pub struct EyeWorld {
175 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 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 plane: [f32; 4],
210 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 pub fn set_window_scene(&mut self, scene: usize) {
226 self.window_scene = Some(scene);
227 }
228
229 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 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 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 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 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 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 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 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 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 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}