use glam::{bool, f32, vec3, Mat3, Vec3}; use crate::ifaces::{DebugTraceable, RayPath, Traceable}; use coords::{FlatCoordinateSystem, InnerCS, OuterCS}; use metric::Tube; use Subspace::{Boundary, Inner, Outer}; use crate::riemann::Metric; use crate::tube::coords::FlatRegion; use crate::types::{FlatTraceResult, Hit, Location, Object, Ray}; use crate::{riemann, DT}; mod coords; pub mod metric; pub struct Space { pub tube: Tube, pub objs: Vec, } #[derive(PartialEq, Eq, Debug)] pub enum Subspace { Outer, Boundary, Inner, } impl Space { fn which_subspace(&self, pt: Vec3) -> Subspace { if pt.y.abs() > self.tube.external_halflength { Outer } else if pt.x.abs() > self.tube.outer_radius { Outer } else if pt.x.abs() > self.tube.inner_radius { Boundary } else { Inner } } /// Выполняет один шаг трассировки. Работает в любой части пространства, но вне Boundary доступны более эффективные методы. /// ray задаётся в основной СК. pub fn trace_step(&self, ray: Ray) -> Ray { let a = -riemann::contract2(riemann::krist(&self.tube, ray.pos), ray.dir); let v = ray.dir + a; let p = ray.pos + v; Ray { pos: p, dir: v } } /// Выполняет один шаг перемещения. Работает в любой части пространства. /// off задаётся в локальной СК. Рекомендуется считать небольшими шагами. pub fn move_step(&self, loc: Location, off: Vec3) -> Location { let corr = Mat3::IDENTITY - riemann::contract(riemann::krist(&self.tube, loc.pos), loc.rot * off); let p = loc.pos + corr * loc.rot * off; Location { pos: p, rot: corr * loc.rot, } } pub fn trace_iter(&self, ray: Ray) -> impl Iterator + '_ { std::iter::successors(Some(ray), |&ray| Some(self.trace_step(ray))) } fn trace_inner(&self, ray: Ray) -> FlatTraceResult { assert_eq!(self.which_subspace(ray.pos), Inner); self.trace_flat(InnerCS(self.tube), ray) } fn trace_outer(&self, ray: Ray) -> FlatTraceResult { assert_eq!(self.which_subspace(ray.pos), Outer); self.trace_flat(OuterCS(self.tube), ray) } fn obj_hitter(&self, pos: Vec3) -> Option FlatTraceResult> { match self.which_subspace(pos) { Inner => Some(Self::trace_inner), Outer => Some(Self::trace_outer), Boundary => None, } } fn trace_flat(&self, cs: impl FlatRegion, ray: Ray) -> FlatTraceResult { let ray = cs.global_to_flat(ray); let dist = cs.distance_to_boundary(ray); let objs = self.list_objects(|loc| cs.global_to_flat(loc)); FlatTraceResult { end: dist.map(|dist| cs.flat_to_global(ray.forward(dist))), objects: Self::hit_objects(objs.as_slice(), ray, dist, |pos| cs.flat_to_global(pos)), } } fn trace_boundary(&self, ray: Ray) -> Ray { assert_eq!(self.which_subspace(ray.pos), Boundary); self.trace_iter(ray) .find(|&ray| self.which_subspace(ray.pos) != Boundary) .expect("Can't get outta the wall!") } fn list_objects(&self, tfm: impl Fn(Location) -> Location) -> Vec { self.objs .iter() .map(|&Object { id, loc, r }| Object { id, loc: tfm(loc), r, }) .collect() } fn hit_objects( objs: &[Object], ray: Ray, limit: Option, globalize: impl Fn(Vec3) -> Vec3, ) -> Vec { let limit = limit.unwrap_or(f32::INFINITY); objs.iter() .filter_map(|obj| { let rel = ray.pos - obj.loc.pos; let diff = rel.dot(ray.dir).powi(2) - ray.dir.length_squared() * (rel.length_squared() - obj.r.powi(2)); if diff > 0.0 { let t = (-rel.dot(ray.dir) - diff.sqrt()) / ray.dir.length_squared(); Some((obj, t)) } else { None } }) .filter(|&(_, t)| t >= 0.0 && t < limit) .map(|(obj, t)| { let pos = ray.forward(t).pos; let rel = obj.loc.rot.inverse() * Ray { pos: pos - obj.loc.pos, dir: ray.dir, }; Hit { id: obj.id, distance: t, pos: globalize(pos), rel, } }) .collect() } pub fn line(&self, a: Vec3, b: Vec3, step: f32) -> Vec { match self.which_subspace(a) { Outer => vec![b], Inner => { let cs = InnerCS(self.tube); let n = ((b - a).length() / step) as usize + 1; let a = cs.global_to_flat(a); let b = cs.global_to_flat(b); (1..=n) .map(|k| cs.flat_to_global(a.lerp(b, k as f32 / n as f32))) .collect() } Boundary => panic!("Can't draw a line here!"), } } fn camera_ray_to_abs(&self, camera: Location, ray: Ray) -> Ray { let pos = camera.pos; let dir = camera.rot * ray.dir; // TODO account for ray.pos let dir = DT * self.tube.normalize_vec_at(pos, dir); Ray { pos, dir } } } /// Like [`std::iter::successors`] but with an upper limit on iteration count. /// /// # Panics /// /// Panics if the sequence doesn’t terminate in `max_iters` calls of `succ`. fn iterate_with_limit(max_iters: usize, init: T, mut succ: impl FnMut(T) -> Option) { let mut state = init; for _ in 0..max_iters { match succ(state) { Some(next) => state = next, None => return, } } panic!("iteration limit exceeded"); } impl Traceable for Space { fn trace(&self, camera: Location, ray: Ray) -> Vec { let ray = self.camera_ray_to_abs(camera, ray); let mut hits = vec![]; iterate_with_limit(100, ray, |ray| { let ret = self .trace_iter(ray) .skip(1) .find_map(|ray| self.obj_hitter(ray.pos).map(|hitter| hitter(self, ray))) .expect("Space::trace_iter does not terminate"); hits.extend(ret.objects); // TODO fix distance ret.end }); hits } } impl DebugTraceable for Space { fn trace_dbg(&self, camera: Location, ray: Ray) -> (Vec, RayPath) { let mut points = vec![]; let mut hits = vec![]; let mut ray = self.camera_ray_to_abs(camera, ray); let trace_to_flat = |points: &mut Vec, ray| { for ray in self.trace_iter(ray).skip(1) { points.push(ray.pos); if let Some(hitter) = self.obj_hitter(ray.pos) { return (ray, hitter(self, ray)); } } unreachable!("Space::trace_iter terminated!") }; points.push(ray.pos); for _ in 0..100 { let (ray_into_flat, ret) = trace_to_flat(&mut points, ray); hits.extend(ret.objects); // TODO fix distance let Some(ray_outta_flat) = ret.end else { return ( hits, RayPath { points, end_dir: ray_into_flat.dir.normalize(), }, ); }; points.extend(self.line(ray_into_flat.pos, ray_outta_flat.pos, 10.0)); ray = ray_outta_flat; } panic!("tracing didn't terminate"); } } struct Rect { pub size: Vec3, } impl Rect { /// Отражает луч, чтобы все координаты направления были положительны (допустимо благодаря симметрии Rect). fn flip_ray(ray: Ray) -> Ray { Ray { pos: ray.pos * ray.dir.signum(), dir: ray.dir.abs(), } } fn is_inside(&self, pt: Vec3) -> bool { pt.abs().cmplt(self.size).all() } fn trace_into(&self, ray: Ray) -> Option { let ray = Self::flip_ray(ray); // ray.pos.x + t * ray.dir.x = −size.x let ts = (-self.size - ray.pos) / ray.dir; let t = ts.max_element(); let pt = ray.pos + t * ray.dir; if t < 0.0 { return None; } if pt.cmpgt(self.size).any() { return None; } Some(t) } fn trace_out_of(&self, ray: Ray) -> Option { let ray = Self::flip_ray(ray); // ray.pos.x + t * ray.dir.x = +size.x let ts = (self.size - ray.pos) / ray.dir; let t = ts.min_element(); Some(t) } } #[test] fn test_rect() { assert_eq!( Rect::flip_ray(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(4.0, 5.0, 4.0) }), Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(4.0, 5.0, 4.0) } ); assert_eq!( Rect::flip_ray(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(-4.0, 5.0, -4.0) }), Ray { pos: vec3(-2.0, 3.0, -2.0), dir: vec3(4.0, 5.0, 4.0) } ); assert_eq!( Rect::flip_ray(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(4.0, -5.0, 4.0) }), Ray { pos: vec3(2.0, -3.0, 2.0), dir: vec3(4.0, 5.0, 4.0) } ); assert_eq!( Rect::flip_ray(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(4.0, 0.0, 4.0) }), Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(4.0, 0.0, 4.0) } ); let r = Rect { size: vec3(2.0, 3.0, 2.0), }; assert_eq!( r.trace_into(Ray { pos: vec3(3.0, 3.0, 3.0), dir: vec3(1.0, 1.0, 1.0) }), None ); assert_eq!( r.trace_into(Ray { pos: vec3(-3.0, 2.0, -3.0), dir: vec3(1.0, 0.0, 1.0) }), Some(1.0) ); assert_eq!( r.trace_into(Ray { pos: vec3(-3.0, 2.0, -3.0), dir: vec3(-1.0, 0.0, -1.0) }), None ); assert_eq!( r.trace_into(Ray { pos: vec3(-3.0, 1.0, -3.0), dir: vec3(2.0, 2.0, 2.0) }), Some(0.5) ); assert_eq!( r.trace_into(Ray { pos: vec3(-3.0, 2.1, -3.0), dir: vec3(2.0, 2.0, 2.0) }), None ); assert_eq!( r.trace_into(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(1.0, 1.0, 1.0) }), None ); assert_eq!( r.trace_into(Ray { pos: vec3(-2.0, 3.0, -2.0), dir: vec3(-1.0, 1.0, -1.0) }), None ); assert_eq!( r.trace_into(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(-1.0, -1.0, -1.0) }), Some(0.0) ); assert_eq!( r.trace_into(Ray { pos: vec3(2.0, -3.0, 2.0), dir: vec3(-1.0, 1.0, -1.0) }), Some(0.0) ); assert_eq!( r.trace_out_of(Ray { pos: vec3(0.0, 0.0, 0.0), dir: vec3(1.0, 1.0, 1.0) }), Some(2.0) ); assert_eq!( r.trace_out_of(Ray { pos: vec3(0.0, 0.0, 0.0), dir: vec3(0.0, 1.0, 0.0) }), Some(3.0) ); assert_eq!( r.trace_out_of(Ray { pos: vec3(0.0, 1.0, 0.0), dir: vec3(0.0, -1.0, 0.0) }), Some(4.0) ); assert_eq!( r.trace_out_of(Ray { pos: vec3(1.0, 1.0, 1.0), dir: vec3(0.0, -1.0, 0.0) }), Some(4.0) ); assert_eq!( r.trace_out_of(Ray { pos: vec3(2.0, 3.0, 2.0), dir: vec3(1.0, 1.0, 1.0) }), Some(0.0) ); }