use std::{
ops::Range,
sync::{Arc, Mutex, PoisonError},
};
use super::{
math::{Mat4, Vec3},
mesh::Mesh,
scene::Camera,
};
use crate::{Point, Rect, Size};
const DOWN: Vec3 = Vec3::new(0.0, 1.0, 0.0);
const MAX_CROSSINGS: usize = 4;
const PUSHES: usize = 3;
const MAX_PIECES: f32 = 256.0;
pub const PITCH_LIMIT: f32 = 1.4;
#[derive(Clone, Debug, PartialEq)]
pub struct EyePose {
pub doorways: Arc<[usize]>,
pub position: Vec3,
pub forward: Vec3,
pub down: Vec3,
}
impl EyePose {
pub fn standing(position: Vec3, yaw: f32, pitch: f32) -> Self {
let mut pose = Self {
doorways: Arc::from([]),
position,
forward: Vec3::new(0.0, 0.0, 1.0),
down: DOWN,
};
pose.turn(yaw, pitch);
pose
}
pub fn camera(&self, focal_length: f32) -> Camera {
Camera::look_at(
self.position,
self.position + self.forward,
self.down,
focal_length,
)
}
pub fn pitch(&self) -> f32 {
(-self.forward.dot(DOWN)).clamp(-1.0, 1.0).asin()
}
pub fn turn(&mut self, yaw: f32, pitch: f32) {
self.forward = rotated(self.forward, DOWN, yaw);
self.down = rotated(self.down, DOWN, yaw);
let level = self.pitch();
let pitch = (level + pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT) - level;
let right = self.down.cross(self.forward);
if let Some(right) = right.normalized() {
self.forward = rotated(self.forward, right, pitch);
self.down = rotated(self.down, right, pitch);
}
self.square_up();
}
pub fn walking_axes(&self) -> (Vec3, Vec3) {
let ahead = (self.forward - DOWN * self.forward.dot(DOWN))
.normalized()
.unwrap_or(self.forward);
(ahead, DOWN.cross(ahead))
}
fn square_up(&mut self) {
let forward = self
.forward
.normalized()
.unwrap_or(Vec3::new(0.0, 0.0, 1.0));
let down = (self.down - forward * self.down.dot(forward))
.normalized()
.unwrap_or(DOWN);
self.forward = forward;
self.down = down;
}
}
fn rotated(vector: Vec3, axis: Vec3, angle: f32) -> Vec3 {
let (sin, cos) = angle.sin_cos();
vector * cos + axis.cross(vector) * sin + axis * (axis.dot(vector) * (1.0 - cos))
}
#[derive(Clone, Debug, PartialEq)]
pub struct EyePlace {
pub doorways: Arc<[usize]>,
pub world: WorldSink,
}
#[derive(Clone, Debug, Default)]
pub struct WorldSink(Arc<Mutex<Arc<EyeWorld>>>);
impl WorldSink {
pub fn rebuild(&self, build: impl FnOnce(&mut EyeWorld)) {
let mut held = self.0.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(world) = Arc::get_mut(&mut held) {
world.clear();
build(world);
return;
}
let mut world = EyeWorld::default();
build(&mut world);
*held = Arc::new(world);
}
pub fn current(&self) -> Arc<EyeWorld> {
Arc::clone(&self.0.lock().unwrap_or_else(PoisonError::into_inner))
}
}
impl PartialEq for WorldSink {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
#[derive(Debug, Default)]
pub struct EyeWorld {
window_scene: Option<usize>,
spaces: Vec<WorldSpace>,
solids: Vec<WorldSolid>,
holes: Vec<Rect>,
doorways: Vec<WorldDoorway>,
}
#[derive(Debug)]
struct WorldSpace {
doorways: Arc<[usize]>,
to_base: Mat4,
from_base: Mat4,
scale: f32,
}
#[derive(Debug)]
struct WorldSolid {
space: usize,
mesh: Arc<Mesh>,
transform: Mat4,
size: Size,
holes: Range<usize>,
low: Vec3,
high: Vec3,
}
#[derive(Debug)]
struct WorldDoorway {
outer: usize,
inner: usize,
plane: [f32; 4],
to_door: Mat4,
}
impl EyeWorld {
fn clear(&mut self) {
self.window_scene = None;
self.spaces.clear();
self.solids.clear();
self.holes.clear();
self.doorways.clear();
}
pub fn set_window_scene(&mut self, scene: usize) {
self.window_scene = Some(scene);
}
pub fn doorways_to(&self, scene: usize) -> Option<Arc<[usize]>> {
self.space_of(scene)
.map(|space| Arc::clone(&space.doorways))
}
pub fn pose_in(&self, pose: &EyePose, scene: usize) -> Option<EyePose> {
let from = self
.spaces
.iter()
.find(|space| space.doorways == pose.doorways)?;
let to = self.space_of(scene)?;
let map = from.to_base.then(&to.from_base);
let mut seen = EyePose {
doorways: Arc::clone(&to.doorways),
position: map.transform_point(pose.position),
forward: map.transform_vector(pose.forward),
down: map.transform_vector(pose.down),
};
seen.square_up();
Some(seen)
}
fn space_of(&self, scene: usize) -> Option<&WorldSpace> {
self.spaces
.iter()
.find(|space| match space.doorways.last() {
Some(&last) => last == scene,
None => self.window_scene == Some(scene),
})
}
pub fn add_space(&mut self, doorways: Arc<[usize]>, to_base: Mat4) -> usize {
self.spaces.push(WorldSpace {
doorways,
from_base: to_base.inverse().unwrap_or(Mat4::IDENTITY),
scale: to_base.length_scale(),
to_base,
});
self.spaces.len() - 1
}
pub fn add_solid(
&mut self,
(space, mesh): (usize, Arc<Mesh>),
transform: Mat4,
(size, holes): (Size, impl IntoIterator<Item = Rect>),
) {
let start = self.holes.len();
self.holes.extend(holes);
let (low, high) = mesh.bounds_in(&transform);
self.solids.push(WorldSolid {
space,
mesh,
transform,
size,
holes: start..self.holes.len(),
low,
high,
});
}
pub fn add_doorway(&mut self, (outer, inner): (usize, usize), door: Mat4) {
let corner = door.transform_point(Vec3::ZERO);
let normal = door
.transform_vector(Vec3::new(0.0, 0.0, 1.0))
.normalized()
.unwrap_or(Vec3::new(0.0, 0.0, 1.0));
self.doorways.push(WorldDoorway {
outer,
inner,
plane: [normal.x, normal.y, normal.z, -normal.dot(corner)],
to_door: door.inverse().unwrap_or(Mat4::IDENTITY),
});
}
pub fn walk(&self, from: &EyePose, step: Vec3, radius: f32) -> EyePose {
let Some(start) = self
.spaces
.iter()
.position(|space| space.doorways == from.doorways)
else {
return EyePose {
position: from.position + step,
..from.clone()
};
};
let base = &self.spaces[start];
let step = base.to_base.transform_vector(step);
let smallest = self
.spaces
.iter()
.map(|space| space.scale)
.fold(f32::INFINITY, f32::min);
let pieces = (step.length() / (radius * smallest * 0.5).max(f32::EPSILON))
.ceil()
.clamp(1.0, MAX_PIECES);
let piece = step * (1.0 / pieces);
let mut at = base.to_base.transform_point(from.position);
let mut space = start;
for _ in 0..pieces as usize {
let end = at + piece;
let mut along = at;
for _ in 0..MAX_CROSSINGS {
let crossed = self
.doorways
.iter()
.find_map(|door| door.crossing(space, (along, end)));
let Some((next, crossing)) = crossed else {
break;
};
space = next;
along = crossing;
}
let reach = radius * self.spaces[space].scale;
at = self.kept_behind_doorways(space, self.pushed_out(space, end, reach), reach);
}
let target = &self.spaces[space];
let turn = base.to_base.then(&target.from_base);
let mut pose = EyePose {
doorways: Arc::clone(&target.doorways),
position: target.from_base.transform_point(at),
forward: turn.transform_vector(from.forward),
down: turn.transform_vector(from.down),
};
pose.square_up();
pose
}
fn kept_behind_doorways(&self, space: usize, mut at: Vec3, radius: f32) -> Vec3 {
for door in self.doorways.iter().filter(|door| door.inner == space) {
let [x, y, z, offset] = door.plane;
let normal = Vec3::new(x, y, z);
let behind = normal.dot(at) + offset;
let on_door = door.to_door.transform_point(at);
let within = (0.0..=1.0).contains(&on_door.x) && (0.0..=1.0).contains(&on_door.y);
if behind < radius && !within {
at = at + normal * (radius - behind);
}
}
at
}
fn pushed_out(&self, space: usize, mut at: Vec3, radius: f32) -> Vec3 {
for _ in 0..PUSHES {
let mut pushed = false;
for solid in self.solids.iter().filter(|solid| solid.space == space) {
if let Some(push) = solid.push(at, radius, &self.holes[solid.holes.clone()]) {
at = at + push;
pushed = true;
}
}
if !pushed {
break;
}
}
at
}
}
impl WorldSolid {
fn push(&self, at: Vec3, radius: f32, holes: &[Rect]) -> Option<Vec3> {
let outside =
|value: f32, low: f32, high: f32| value < low - radius || value > high + radius;
if outside(at.x, self.low.x, self.high.x)
|| outside(at.y, self.low.y, self.high.y)
|| outside(at.z, self.low.z, self.high.z)
{
return None;
}
let contact = self.mesh.nearest(at, &self.transform)?;
if contact.distance >= radius {
return None;
}
let content = Point::new(
contact.uv[0] * self.size.width,
contact.uv[1] * self.size.height,
);
if holes.iter().any(|hole| hole.contains(content.x, content.y)) {
return None;
}
let away = (at - contact.point).normalized()?;
Some(away * (radius - contact.distance))
}
}
impl WorldDoorway {
fn crossing(&self, space: usize, (from, to): (Vec3, Vec3)) -> Option<(usize, Vec3)> {
let (next, inward) = if space == self.outer {
(self.inner, true)
} else if space == self.inner {
(self.outer, false)
} else {
return None;
};
let [x, y, z, offset] = self.plane;
let side = |point: Vec3| x * point.x + y * point.y + z * point.z + offset;
let (before, after) = (side(from), side(to));
let passes = if inward {
before < 0.0 && after >= 0.0
} else {
before >= 0.0 && after < 0.0
};
if !passes {
return None;
}
let at = from + (to - from) * (before / (before - after));
let on_door = self.to_door.transform_point(at);
((0.0..=1.0).contains(&on_door.x) && (0.0..=1.0).contains(&on_door.y)).then_some((next, at))
}
}