From 455b69d4472a101dcad6752b6543a96e2957b062 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Jun 2024 13:04:36 +0300 Subject: [PATCH] Extract the implementation to a module --- src/bin/flat/main.rs | 320 +------------------------------------- src/bin/flat/tube/mod.rs | 322 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 317 deletions(-) diff --git a/src/bin/flat/main.rs b/src/bin/flat/main.rs index b2c6acb..e3f5cb4 100644 --- a/src/bin/flat/main.rs +++ b/src/bin/flat/main.rs @@ -9,9 +9,10 @@ mod float_fun; mod tube; use riemann::{Metric, trace_iter}; -use shape::Shape; -use Subspace::{Boundary, Inner, Outer}; +use tube::shape::Shape; +use tube::Subspace::{Boundary, Inner, Outer}; use tube::metric::Tube; +use tube::Space; const DT: f32 = 0.1; @@ -118,18 +119,6 @@ struct Object { r: f32, } -struct Space { - tube: Tube, - objs: Vec, -} - -#[derive(PartialEq, Eq, Debug)] -enum Subspace { - Outer, - Boundary, - Inner, -} - struct Hit { distance: f32, id: i32, @@ -142,149 +131,6 @@ struct FlatTraceResult { objects: Vec, } -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 + '_ { - 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 { - self.objs.iter().map(|&Object { id, loc, r }| Object { id, loc: tfm(loc), r }).collect() - } - - fn list_objects_outer(&self) -> Vec { - 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 { - 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 { - 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 { - 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, space: &Space, base: Vec2, dir: Vec2) { let mut hits = Vec::::new(); let dir = space.tube.globalize(base, dir); @@ -449,163 +295,3 @@ impl std::ops::Mul for Mat2 { 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 { - 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) - } - - fn visualise(&self) -> Vec { - 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; - /// Ищет ближайшее пересечение луча с границей в направлении вовне контура. Возвращает расстояние (в ray.dir). - fn trace_out_of(&self, ray: Ray) -> Option; - - /// Возвращает визуальное представление контура, для отладки. - fn visualise(&self) -> Vec; - } -} - -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 { - 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)) - } -} diff --git a/src/bin/flat/tube/mod.rs b/src/bin/flat/tube/mod.rs index e0026c2..c7b1e13 100644 --- a/src/bin/flat/tube/mod.rs +++ b/src/bin/flat/tube/mod.rs @@ -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 struct Space { + pub tube: Tube, + pub objs: Vec, +} + +#[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 + '_ { + 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 { + self.objs.iter().map(|&Object { id, loc, r }| Object { id, loc: tfm(loc), r }).collect() + } + + fn list_objects_outer(&self) -> Vec { + 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 { + 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 { + 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 { + 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 { + 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) + } + + fn visualise(&self) -> Vec { + 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; + /// Ищет ближайшее пересечение луча с границей в направлении вовне контура. Возвращает расстояние (в ray.dir). + fn trace_out_of(&self, ray: Ray) -> Option; + + /// Возвращает визуальное представление контура, для отладки. + fn visualise(&self) -> Vec; + } +} + +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 { + 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)) + } +}