Extract the implementation to a module
This commit is contained in:
parent
84068a5a13
commit
455b69d447
|
|
@ -9,9 +9,10 @@ mod float_fun;
|
||||||
mod tube;
|
mod tube;
|
||||||
|
|
||||||
use riemann::{Metric, trace_iter};
|
use riemann::{Metric, trace_iter};
|
||||||
use shape::Shape;
|
use tube::shape::Shape;
|
||||||
use Subspace::{Boundary, Inner, Outer};
|
use tube::Subspace::{Boundary, Inner, Outer};
|
||||||
use tube::metric::Tube;
|
use tube::metric::Tube;
|
||||||
|
use tube::Space;
|
||||||
|
|
||||||
const DT: f32 = 0.1;
|
const DT: f32 = 0.1;
|
||||||
|
|
||||||
|
|
@ -118,18 +119,6 @@ struct Object {
|
||||||
r: f32,
|
r: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Space {
|
|
||||||
tube: Tube,
|
|
||||||
objs: Vec<Object>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
|
||||||
enum Subspace {
|
|
||||||
Outer,
|
|
||||||
Boundary,
|
|
||||||
Inner,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Hit {
|
struct Hit {
|
||||||
distance: f32,
|
distance: f32,
|
||||||
id: i32,
|
id: i32,
|
||||||
|
|
@ -142,149 +131,6 @@ struct FlatTraceResult {
|
||||||
objects: Vec<Hit>,
|
objects: Vec<Hit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Space {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flat_to_global(&self, at: Vec2) -> Mat2 {
|
|
||||||
Mat2::from(self.tube.sqrt_at(at).inverse())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn global_to_flat(&self, at: Vec2) -> Mat2 {
|
|
||||||
Mat2::from(self.tube.sqrt_at(at))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Выполняет один шаг трассировки. Работает в любой части пространства, но вне Boundary доступны более эффективные методы.
|
|
||||||
/// ray задаётся в основной СК.
|
|
||||||
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 задаётся в локальной СК. Рекомендуется считать небольшими шагами.
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
let cell = TubeInside { tube: self.tube };
|
|
||||||
let ray = cell.ray_to_local(ray);
|
|
||||||
let objs = self.list_objects_inner();
|
|
||||||
let dist = cell.to_boundary(ray).expect("Can't get outta here!");
|
|
||||||
FlatTraceResult {
|
|
||||||
end: Some(cell.ray_to_global(ray.forward(dist))),
|
|
||||||
objects: Self::hit_objects(objs.as_slice(), ray, dist, |pos| cell.pos_to_global(pos)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trace_outer(&self, ray: Ray) -> FlatTraceResult {
|
|
||||||
assert_eq!(self.which_subspace(ray.pos), Outer);
|
|
||||||
let cell = basic_shapes::Rect { size: vec2(self.tube.outer_radius, self.tube.external_halflength) };
|
|
||||||
let objs = self.list_objects_outer();
|
|
||||||
let lim = cell.trace_into(ray);
|
|
||||||
let dist = lim.unwrap_or(f32::INFINITY);
|
|
||||||
FlatTraceResult {
|
|
||||||
end: lim.map(|dist| ray.forward(dist)),
|
|
||||||
objects: Self::hit_objects(objs.as_slice(), ray, dist, |pos| 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 list_objects_outer(&self) -> Vec<Object> {
|
|
||||||
self.list_objects(|loc|
|
|
||||||
match self.which_subspace(loc.pos) {
|
|
||||||
Outer => loc,
|
|
||||||
Inner => {
|
|
||||||
let Vec2 { x: u, y } = loc.pos; // в основной СК
|
|
||||||
let v = self.tube.v(y) + y.signum() * (self.tube.external_halflength - self.tube.internal_halflength);
|
|
||||||
Location {
|
|
||||||
pos: vec2(u, v), // в плоском продолжении СК Outer на область Inner
|
|
||||||
rot: self.global_to_flat(loc.pos) * loc.rot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Boundary => panic!("Object at {} was destroyed by the space curvature", loc.pos),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_objects_inner(&self) -> Vec<Object> {
|
|
||||||
self.list_objects(|Location { pos, rot }|
|
|
||||||
match self.which_subspace(pos) {
|
|
||||||
Inner | Outer => {
|
|
||||||
// NB: не работает для частей Outer с |y| < external_halflength. Но они и не нужны.
|
|
||||||
Location {
|
|
||||||
pos: vec2(pos.x, self.tube.v(pos.y)), // в плоской СК для Inner или её продолжении на Outer
|
|
||||||
rot: self.global_to_flat(pos) * rot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Boundary => panic!("Object at {pos} was destroyed by the space curvature"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hit_objects(objs: &[Object], ray: Ray, limit: f32, globalize: impl Fn(Vec2) -> Vec2) -> Vec<Hit> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line(&self, a: Vec2, b: Vec2, step: f32) -> Vec<Vec2> {
|
|
||||||
match self.which_subspace(a) {
|
|
||||||
Outer => vec![b],
|
|
||||||
Inner => {
|
|
||||||
let cell = TubeInside { tube: self.tube };
|
|
||||||
let n = ((b - a).length() / step) as usize + 1;
|
|
||||||
let a = cell.pos_to_local(a);
|
|
||||||
let b = cell.pos_to_local(b);
|
|
||||||
(1..=n).map(|k| cell.pos_to_global(a.lerp(b, k as f32 / n as f32))).collect()
|
|
||||||
}
|
|
||||||
Boundary => panic!("Can't draw a line here!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_ray_2(gc: &mut Vec<Draw>, space: &Space, base: Vec2, dir: Vec2) {
|
fn draw_ray_2(gc: &mut Vec<Draw>, space: &Space, base: Vec2, dir: Vec2) {
|
||||||
let mut hits = Vec::<Draw>::new();
|
let mut hits = Vec::<Draw>::new();
|
||||||
let dir = space.tube.globalize(base, dir);
|
let dir = space.tube.globalize(base, dir);
|
||||||
|
|
@ -449,163 +295,3 @@ impl std::ops::Mul<Ray> for Mat2 {
|
||||||
Ray { pos: self * rhs.pos, dir: self * rhs.dir }
|
Ray { pos: self * rhs.pos, dir: self * rhs.dir }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod basic_shapes {
|
|
||||||
use glam::{Vec2, vec2};
|
|
||||||
use crate::Ray;
|
|
||||||
use crate::shape::Shape;
|
|
||||||
|
|
||||||
pub 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() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Shape for Rect {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visualise(&self) -> Vec<Vec2> {
|
|
||||||
vec![vec2(-self.size.x, -self.size.y), vec2(self.size.x, -self.size.y), vec2(self.size.x, self.size.y), vec2(-self.size.x, self.size.y)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod shape {
|
|
||||||
use glam::Vec2;
|
|
||||||
use crate::Ray;
|
|
||||||
|
|
||||||
pub trait Shape {
|
|
||||||
fn is_inside(&self, pt: Vec2) -> bool;
|
|
||||||
|
|
||||||
/// Ищет ближайшее пересечение луча с границей в направлении внутрь контура. Возвращает расстояние (в ray.dir).
|
|
||||||
fn trace_into(&self, ray: Ray) -> Option<f32>;
|
|
||||||
/// Ищет ближайшее пересечение луча с границей в направлении вовне контура. Возвращает расстояние (в ray.dir).
|
|
||||||
fn trace_out_of(&self, ray: Ray) -> Option<f32>;
|
|
||||||
|
|
||||||
/// Возвращает визуальное представление контура, для отладки.
|
|
||||||
fn visualise(&self) -> Vec<Vec2>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait FlatCell: std::fmt::Debug {
|
|
||||||
fn pos_to_global(&self, pos: Vec2) -> Vec2;
|
|
||||||
fn pos_to_local(&self, pos: Vec2) -> Vec2;
|
|
||||||
fn ray_to_global(&self, ray: Ray) -> Ray;
|
|
||||||
fn ray_to_local(&self, ray: Ray) -> Ray;
|
|
||||||
|
|
||||||
fn is_inside(&self, pos: Vec2) -> bool {
|
|
||||||
let bnd = self.local_bounds();
|
|
||||||
pos.cmpge(bnd.0).all() && pos.cmple(bnd.1).all()
|
|
||||||
}
|
|
||||||
fn local_bounds(&self) -> (Vec2, Vec2);
|
|
||||||
|
|
||||||
fn to_boundary(&self, ray: Ray) -> Option<f32> {
|
|
||||||
assert!(self.is_inside(ray.pos));
|
|
||||||
let sgn = ray.dir.signum();
|
|
||||||
let p = ray.pos * sgn;
|
|
||||||
let v = ray.dir * sgn;
|
|
||||||
let mut bnd = self.local_bounds();
|
|
||||||
if sgn.x < 0.0 {
|
|
||||||
(bnd.0.x, bnd.1.x) = (-bnd.1.x, -bnd.0.x);
|
|
||||||
}
|
|
||||||
if sgn.y < 0.0 {
|
|
||||||
(bnd.0.y, bnd.1.y) = (-bnd.1.y, -bnd.0.y);
|
|
||||||
}
|
|
||||||
let t = if (bnd.1.x - p.x) * v.y <= (bnd.1.y - p.y) * v.x {
|
|
||||||
(bnd.1.x - p.x) / v.x
|
|
||||||
} else {
|
|
||||||
(bnd.1.y - p.y) / v.y
|
|
||||||
};
|
|
||||||
if t <= 100000.0 {
|
|
||||||
Some(t)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TubeInside {
|
|
||||||
tube: Tube,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlatCell for TubeInside {
|
|
||||||
fn pos_to_global(&self, pos: Vec2) -> Vec2 {
|
|
||||||
vec2(pos.x, self.tube.y(pos.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pos_to_local(&self, pos: Vec2) -> Vec2 {
|
|
||||||
vec2(pos.x, self.tube.v(pos.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ray_to_global(&self, ray: Ray) -> Ray {
|
|
||||||
Ray {
|
|
||||||
pos: self.pos_to_global(ray.pos),
|
|
||||||
dir: vec2(ray.dir.x, self.tube.dy(ray.pos.y) * ray.dir.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ray_to_local(&self, ray: Ray) -> Ray {
|
|
||||||
Ray {
|
|
||||||
pos: self.pos_to_local(ray.pos),
|
|
||||||
dir: vec2(ray.dir.x, self.tube.dv(ray.pos.y) * ray.dir.y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn local_bounds(&self) -> (Vec2, Vec2) {
|
|
||||||
(vec2(-self.tube.inner_radius, -self.tube.internal_halflength), vec2(self.tube.inner_radius, self.tube.internal_halflength))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,323 @@
|
||||||
|
use glam::{bool, f32, Mat2, Vec2, vec2};
|
||||||
|
use crate::{FlatTraceResult, Hit, Location, Object, Ray, riemann};
|
||||||
|
use crate::riemann::Metric;
|
||||||
|
use Subspace::{Boundary, Inner, Outer};
|
||||||
|
use metric::Tube;
|
||||||
|
use shape::Shape;
|
||||||
|
|
||||||
pub mod metric;
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_to_global(&self, at: Vec2) -> Mat2 {
|
||||||
|
Mat2::from(self.tube.sqrt_at(at).inverse())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn global_to_flat(&self, at: Vec2) -> Mat2 {
|
||||||
|
Mat2::from(self.tube.sqrt_at(at))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Выполняет один шаг трассировки. Работает в любой части пространства, но вне 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);
|
||||||
|
let cell = TubeInside { tube: self.tube };
|
||||||
|
let ray = cell.ray_to_local(ray);
|
||||||
|
let objs = self.list_objects_inner();
|
||||||
|
let dist = cell.to_boundary(ray).expect("Can't get outta here!");
|
||||||
|
FlatTraceResult {
|
||||||
|
end: Some(cell.ray_to_global(ray.forward(dist))),
|
||||||
|
objects: Self::hit_objects(objs.as_slice(), ray, dist, |pos| cell.pos_to_global(pos)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace_outer(&self, ray: Ray) -> FlatTraceResult {
|
||||||
|
assert_eq!(self.which_subspace(ray.pos), Outer);
|
||||||
|
let cell = basic_shapes::Rect { size: vec2(self.tube.outer_radius, self.tube.external_halflength) };
|
||||||
|
let objs = self.list_objects_outer();
|
||||||
|
let lim = cell.trace_into(ray);
|
||||||
|
let dist = lim.unwrap_or(f32::INFINITY);
|
||||||
|
FlatTraceResult {
|
||||||
|
end: lim.map(|dist| ray.forward(dist)),
|
||||||
|
objects: Self::hit_objects(objs.as_slice(), ray, dist, |pos| 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 list_objects_outer(&self) -> Vec<Object> {
|
||||||
|
self.list_objects(|loc|
|
||||||
|
match self.which_subspace(loc.pos) {
|
||||||
|
Outer => loc,
|
||||||
|
Inner => {
|
||||||
|
let Vec2 { x: u, y } = loc.pos; // в основной СК
|
||||||
|
let v = self.tube.v(y) + y.signum() * (self.tube.external_halflength - self.tube.internal_halflength);
|
||||||
|
Location {
|
||||||
|
pos: vec2(u, v), // в плоском продолжении СК Outer на область Inner
|
||||||
|
rot: self.global_to_flat(loc.pos) * loc.rot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Boundary => panic!("Object at {} was destroyed by the space curvature", loc.pos),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_objects_inner(&self) -> Vec<Object> {
|
||||||
|
self.list_objects(|Location { pos, rot }|
|
||||||
|
match self.which_subspace(pos) {
|
||||||
|
Inner | Outer => {
|
||||||
|
// NB: не работает для частей Outer с |y| < external_halflength. Но они и не нужны.
|
||||||
|
Location {
|
||||||
|
pos: vec2(pos.x, self.tube.v(pos.y)), // в плоской СК для Inner или её продолжении на Outer
|
||||||
|
rot: self.global_to_flat(pos) * rot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Boundary => panic!("Object at {pos} was destroyed by the space curvature"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_objects(objs: &[Object], ray: Ray, limit: f32, globalize: impl Fn(Vec2) -> Vec2) -> Vec<Hit> {
|
||||||
|
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 cell = TubeInside { tube: self.tube };
|
||||||
|
let n = ((b - a).length() / step) as usize + 1;
|
||||||
|
let a = cell.pos_to_local(a);
|
||||||
|
let b = cell.pos_to_local(b);
|
||||||
|
(1..=n).map(|k| cell.pos_to_global(a.lerp(b, k as f32 / n as f32))).collect()
|
||||||
|
}
|
||||||
|
Boundary => panic!("Can't draw a line here!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod basic_shapes {
|
||||||
|
use glam::{Vec2, vec2};
|
||||||
|
use crate::Ray;
|
||||||
|
use super::shape::Shape;
|
||||||
|
|
||||||
|
pub 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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape for Rect {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visualise(&self) -> Vec<Vec2> {
|
||||||
|
vec![vec2(-self.size.x, -self.size.y), vec2(self.size.x, -self.size.y), vec2(self.size.x, self.size.y), vec2(-self.size.x, self.size.y)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod shape {
|
||||||
|
use glam::Vec2;
|
||||||
|
use crate::Ray;
|
||||||
|
|
||||||
|
pub trait Shape {
|
||||||
|
fn is_inside(&self, pt: Vec2) -> bool;
|
||||||
|
|
||||||
|
/// Ищет ближайшее пересечение луча с границей в направлении внутрь контура. Возвращает расстояние (в ray.dir).
|
||||||
|
fn trace_into(&self, ray: Ray) -> Option<f32>;
|
||||||
|
/// Ищет ближайшее пересечение луча с границей в направлении вовне контура. Возвращает расстояние (в ray.dir).
|
||||||
|
fn trace_out_of(&self, ray: Ray) -> Option<f32>;
|
||||||
|
|
||||||
|
/// Возвращает визуальное представление контура, для отладки.
|
||||||
|
fn visualise(&self) -> Vec<Vec2>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait FlatCell: std::fmt::Debug {
|
||||||
|
fn pos_to_global(&self, pos: Vec2) -> Vec2;
|
||||||
|
fn pos_to_local(&self, pos: Vec2) -> Vec2;
|
||||||
|
fn ray_to_global(&self, ray: Ray) -> Ray;
|
||||||
|
fn ray_to_local(&self, ray: Ray) -> Ray;
|
||||||
|
|
||||||
|
fn is_inside(&self, pos: Vec2) -> bool {
|
||||||
|
let bnd = self.local_bounds();
|
||||||
|
pos.cmpge(bnd.0).all() && pos.cmple(bnd.1).all()
|
||||||
|
}
|
||||||
|
fn local_bounds(&self) -> (Vec2, Vec2);
|
||||||
|
|
||||||
|
fn to_boundary(&self, ray: Ray) -> Option<f32> {
|
||||||
|
assert!(self.is_inside(ray.pos));
|
||||||
|
let sgn = ray.dir.signum();
|
||||||
|
let p = ray.pos * sgn;
|
||||||
|
let v = ray.dir * sgn;
|
||||||
|
let mut bnd = self.local_bounds();
|
||||||
|
if sgn.x < 0.0 {
|
||||||
|
(bnd.0.x, bnd.1.x) = (-bnd.1.x, -bnd.0.x);
|
||||||
|
}
|
||||||
|
if sgn.y < 0.0 {
|
||||||
|
(bnd.0.y, bnd.1.y) = (-bnd.1.y, -bnd.0.y);
|
||||||
|
}
|
||||||
|
let t = if (bnd.1.x - p.x) * v.y <= (bnd.1.y - p.y) * v.x {
|
||||||
|
(bnd.1.x - p.x) / v.x
|
||||||
|
} else {
|
||||||
|
(bnd.1.y - p.y) / v.y
|
||||||
|
};
|
||||||
|
if t <= 100000.0 {
|
||||||
|
Some(t)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TubeInside {
|
||||||
|
tube: Tube,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlatCell for TubeInside {
|
||||||
|
fn pos_to_global(&self, pos: Vec2) -> Vec2 {
|
||||||
|
vec2(pos.x, self.tube.y(pos.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pos_to_local(&self, pos: Vec2) -> Vec2 {
|
||||||
|
vec2(pos.x, self.tube.v(pos.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ray_to_global(&self, ray: Ray) -> Ray {
|
||||||
|
Ray {
|
||||||
|
pos: self.pos_to_global(ray.pos),
|
||||||
|
dir: vec2(ray.dir.x, self.tube.dy(ray.pos.y) * ray.dir.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ray_to_local(&self, ray: Ray) -> Ray {
|
||||||
|
Ray {
|
||||||
|
pos: self.pos_to_local(ray.pos),
|
||||||
|
dir: vec2(ray.dir.x, self.tube.dv(ray.pos.y) * ray.dir.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_bounds(&self) -> (Vec2, Vec2) {
|
||||||
|
(vec2(-self.tube.inner_radius, -self.tube.internal_halflength), vec2(self.tube.inner_radius, self.tube.internal_halflength))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user