diff --git a/src/bin/flat.rs b/src/bin/flat.rs index d4592a8..f4e6152 100644 --- a/src/bin/flat.rs +++ b/src/bin/flat.rs @@ -222,11 +222,164 @@ impl Metric for Coil { } } +#[derive(Debug, PartialEq)] struct Ray { pos: Vec2, dir: Vec2, } +mod basic_shapes { + use glam::{Vec2, vec2}; + use crate::{Ray, 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::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)] + } + } + + #[cfg(test)] + use crate::shape::Shape; + + #[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::{Affine2, 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; + } + + pub struct MovedShape { + pub shape: S, + pub transform: Affine2, // transform(координаты контура) = 0 + } + + impl MovedShape { + fn pt_to_inner(&self, pt: Vec2) -> Vec2 { + self.transform.transform_point2(pt) + } + fn pt_to_outer(&self, pt: Vec2) -> Vec2 { + self.transform.inverse().transform_point2(pt) + } + fn vec_to_inner(&self, vec: Vec2) -> Vec2 { + self.transform.transform_vector2(vec) + } + fn vec_to_outer(&self, vec: Vec2) -> Vec2 { + self.transform.inverse().transform_vector2(vec) + } + fn ray_to_inner(&self, ray: Ray) -> Ray { + Ray { pos: self.pt_to_inner(ray.pos), dir: self.vec_to_inner(ray.dir) } + } + fn ray_to_outer(&self, ray: Ray) -> Ray { + Ray { pos: self.pt_to_outer(ray.pos), dir: self.vec_to_outer(ray.dir) } + } + } + + impl Shape for MovedShape { + fn is_inside(&self, pt: Vec2) -> bool { + self.shape.is_inside(self.pt_to_inner(pt)) + } + fn trace_into(&self, ray: Ray) -> Option { + self.shape.trace_into(self.ray_to_inner(ray)) + } + fn trace_out_of(&self, ray: Ray) -> Option { + self.shape.trace_out_of(self.ray_to_inner(ray)) + } + fn visualise(&self) -> Vec { + self.shape.visualise().iter().map(|pt| self.pt_to_outer(*pt)).collect() + } + } + + pub struct InvertedShape { + pub shape: S, + } + + impl Shape for InvertedShape { + fn is_inside(&self, pt: Vec2) -> bool { + !self.shape.is_inside(pt) + } + fn trace_into(&self, ray: Ray) -> Option { + self.shape.trace_out_of(ray) + } + fn trace_out_of(&self, ray: Ray) -> Option { + self.shape.trace_into(ray) + } + fn visualise(&self) -> Vec { + self.shape.visualise() + } + } +} + struct Grid { hlines: Vec, vlines: Vec,