refraction/src/bin/flat/tube/mod.rs

188 lines
6.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, Mat2, Vec2, vec2};
use coords::{FlatCoordinateSystem, InnerCS, OuterCS};
use metric::Tube;
use Subspace::{Boundary, Inner, Outer};
use crate::riemann;
use crate::tube::coords::FlatRegion;
use crate::types::{FlatTraceResult, Hit, Location, Object, Ray};
pub mod metric;
mod coords;
pub struct Space {
pub tube: Tube,
pub objs: Vec<Object>,
}
#[derive(PartialEq, Eq, Debug)]
pub enum Subspace {
Outer,
Boundary,
Inner,
}
impl Space {
pub fn which_subspace(&self, pt: Vec2) -> 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: Vec2 = -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: Vec2) -> Location {
let corr = Mat2::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)))
}
pub fn trace_inner(&self, ray: Ray) -> FlatTraceResult {
assert_eq!(self.which_subspace(ray.pos), Inner);
self.trace_flat(InnerCS(self.tube), ray)
}
pub fn trace_outer(&self, ray: Ray) -> FlatTraceResult {
assert_eq!(self.which_subspace(ray.pos), Outer);
self.trace_flat(OuterCS(self.tube), ray)
}
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(Vec2) -> Vec2) -> 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: Vec2, b: Vec2, step: f32) -> Vec<Vec2> {
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!"),
}
}
}
struct Rect {
pub size: Vec2,
}
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: Vec2) -> 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: vec2(2.0, 3.0), dir: vec2(4.0, 5.0) }), Ray { pos: vec2(2.0, 3.0), dir: vec2(4.0, 5.0) });
assert_eq!(Rect::flip_ray(Ray { pos: vec2(2.0, 3.0), dir: vec2(-4.0, 5.0) }), Ray { pos: vec2(-2.0, 3.0), dir: vec2(4.0, 5.0) });
assert_eq!(Rect::flip_ray(Ray { pos: vec2(2.0, 3.0), dir: vec2(4.0, -5.0) }), Ray { pos: vec2(2.0, -3.0), dir: vec2(4.0, 5.0) });
assert_eq!(Rect::flip_ray(Ray { pos: vec2(2.0, 3.0), dir: vec2(4.0, 0.0) }), Ray { pos: vec2(2.0, 3.0), dir: vec2(4.0, 0.0) });
let r = Rect { size: vec2(2.0, 3.0) };
assert_eq!(r.trace_into(Ray { pos: vec2(3.0, 3.0), dir: vec2(1.0, 1.0) }), None);
assert_eq!(r.trace_into(Ray { pos: vec2(-3.0, 2.0), dir: vec2(1.0, 0.0) }), Some(1.0));
assert_eq!(r.trace_into(Ray { pos: vec2(-3.0, 2.0), dir: vec2(-1.0, 0.0) }), None);
assert_eq!(r.trace_into(Ray { pos: vec2(-3.0, 1.0), dir: vec2(2.0, 2.0) }), Some(0.5));
assert_eq!(r.trace_into(Ray { pos: vec2(-3.0, 2.1), dir: vec2(2.0, 2.0) }), None);
assert_eq!(r.trace_into(Ray { pos: vec2(2.0, 3.0), dir: vec2(1.0, 1.0) }), None);
assert_eq!(r.trace_into(Ray { pos: vec2(-2.0, 3.0), dir: vec2(-1.0, 1.0) }), None);
assert_eq!(r.trace_into(Ray { pos: vec2(2.0, 3.0), dir: vec2(-1.0, -1.0) }), Some(0.0));
assert_eq!(r.trace_into(Ray { pos: vec2(2.0, -3.0), dir: vec2(-1.0, 1.0) }), Some(0.0));
assert_eq!(r.trace_out_of(Ray { pos: vec2(0.0, 0.0), dir: vec2(1.0, 1.0) }), Some(2.0));
assert_eq!(r.trace_out_of(Ray { pos: vec2(0.0, 0.0), dir: vec2(0.0, 1.0) }), Some(3.0));
assert_eq!(r.trace_out_of(Ray { pos: vec2(0.0, 1.0), dir: vec2(0.0, -1.0) }), Some(4.0));
assert_eq!(r.trace_out_of(Ray { pos: vec2(1.0, 1.0), dir: vec2(0.0, -1.0) }), Some(4.0));
assert_eq!(r.trace_out_of(Ray { pos: vec2(2.0, 3.0), dir: vec2(1.0, 1.0) }), Some(0.0));
}