435 lines
9.8 KiB
Rust
435 lines
9.8 KiB
Rust
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<Object>,
|
||
}
|
||
|
||
#[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<Item = Ray> + '_ {
|
||
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<fn(&Self, ray: Ray) -> 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<Object> {
|
||
self.objs
|
||
.iter()
|
||
.map(|&Object { id, loc, r }| Object {
|
||
id,
|
||
loc: tfm(loc),
|
||
r,
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn hit_objects(
|
||
objs: &[Object],
|
||
ray: Ray,
|
||
limit: Option<f32>,
|
||
globalize: impl Fn(Vec3) -> Vec3,
|
||
) -> Vec<Hit> {
|
||
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<Ray> {
|
||
match self.which_subspace(a) {
|
||
Outer => vec![Ray {
|
||
pos: b,
|
||
dir: (b - a).normalize(),
|
||
}],
|
||
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);
|
||
let dir = (b - a).normalize();
|
||
(1..=n)
|
||
.map(|k| {
|
||
cs.flat_to_global(Ray {
|
||
pos: a.lerp(b, k as f32 / n as f32),
|
||
dir,
|
||
})
|
||
})
|
||
.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<T>(max_iters: usize, init: T, mut succ: impl FnMut(T) -> Option<T>) {
|
||
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<Hit> {
|
||
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<Hit>, 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>, ray| {
|
||
for ray in self.trace_iter(ray).skip(1) {
|
||
points.push(ray);
|
||
if let Some(hitter) = self.obj_hitter(ray.pos) {
|
||
return (ray, hitter(self, ray));
|
||
}
|
||
}
|
||
unreachable!("Space::trace_iter terminated!")
|
||
};
|
||
|
||
points.push(ray);
|
||
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 });
|
||
};
|
||
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<f32> {
|
||
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<f32> {
|
||
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)
|
||
);
|
||
}
|