refraction/src/tube/mod.rs

435 lines
9.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 doesnt 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)
);
}