From a9685c81fdc8911e1a2f8f2dd604ac0a990aa572 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 30 Jun 2024 14:19:36 +0300 Subject: [PATCH] Extract CS management into a file --- src/bin/flat/tube/coords.rs | 227 +++++++++++++++++++++++++++++++++++ src/bin/flat/tube/mod.rs | 231 +----------------------------------- 2 files changed, 228 insertions(+), 230 deletions(-) create mode 100644 src/bin/flat/tube/coords.rs diff --git a/src/bin/flat/tube/coords.rs b/src/bin/flat/tube/coords.rs new file mode 100644 index 0000000..de2e614 --- /dev/null +++ b/src/bin/flat/tube/coords.rs @@ -0,0 +1,227 @@ +use glam::{Mat2, Vec2, vec2}; + +use crate::riemann::Metric; +use crate::types::{Location, Ray}; + +use super::{Rect, Tube}; + +pub trait FlatCoordinateSystem { + fn flat_to_global(&self, v: T) -> T; + fn global_to_flat(&self, v: T) -> T; +} + +pub trait FlatRegion: FlatCoordinateSystem + FlatCoordinateSystem + FlatCoordinateSystem { + // Измеряет расстояние до выхода за пределы области вдоль луча ray. Луч задаётся в плоской СК. + fn distance_to_boundary(&self, _ray: Ray) -> Option { None } +} + +trait MetricCS: FlatCoordinateSystem { + fn global_metric(&self) -> &impl Metric; + fn flat_to_global_tfm_at(&self, pos: Vec2) -> Mat2 { + self.global_metric().sqrt_at(self.flat_to_global(pos)).inverse().into() + } + fn global_to_flat_tfm_at(&self, pos: Vec2) -> Mat2 { + self.global_metric().sqrt_at(pos).into() + } +} + +impl + MetricCS> FlatCoordinateSystem for T { + fn flat_to_global(&self, ray: Ray) -> Ray { + Ray { + pos: self.flat_to_global(ray.pos), + dir: self.flat_to_global_tfm_at(ray.pos) * ray.dir, + } + } + + fn global_to_flat(&self, ray: Ray) -> Ray { + Ray { + pos: self.global_to_flat(ray.pos), + dir: self.global_to_flat_tfm_at(ray.pos) * ray.dir, + } + } +} + +impl + MetricCS> FlatCoordinateSystem for T { + fn flat_to_global(&self, loc: Location) -> Location { + Location { + pos: self.flat_to_global(loc.pos), + rot: self.flat_to_global_tfm_at(loc.pos) * loc.rot, + } + } + + fn global_to_flat(&self, loc: Location) -> Location { + Location { + pos: self.global_to_flat(loc.pos), // в плоской СК для Inner или её продолжении на Outer + rot: self.global_to_flat_tfm_at(loc.pos) * loc.rot, + } + } +} + +pub struct InnerCS(pub Tube); + +impl MetricCS for InnerCS { fn global_metric(&self) -> &impl Metric { &self.0 } } + +impl FlatCoordinateSystem for InnerCS { + fn flat_to_global(&self, pos: Vec2) -> Vec2 { + vec2(pos.x, self.0.y(pos.y)) + } + + // Работает только при |pos.x| ≤ inner_radius или |pos.y| ≥ external_halflength. + fn global_to_flat(&self, pos: Vec2) -> Vec2 { + vec2(pos.x, self.0.v(pos.y)) + } +} + +impl FlatRegion for InnerCS { + fn distance_to_boundary(&self, ray: Ray) -> Option { + Rect { size: vec2(self.0.inner_radius, self.0.internal_halflength) }.trace_out_of(ray) + } +} + +pub struct OuterCS(pub Tube); + +impl MetricCS for OuterCS { fn global_metric(&self) -> &impl Metric { &self.0 } } + +impl FlatCoordinateSystem for OuterCS { + fn flat_to_global(&self, pos: Vec2) -> Vec2 { + let inner = Rect { size: vec2(self.0.inner_radius + 1.0, self.0.external_halflength) }; + if inner.is_inside(pos) { + let Vec2 { x, y: v } = pos; + let y = self.0.y(v - v.signum() * (self.0.external_halflength - self.0.internal_halflength)); + vec2(x, y) + } else { + pos + } + } + + fn global_to_flat(&self, pos: Vec2) -> Vec2 { + let inner = Rect { size: vec2(self.0.inner_radius + 1.0, self.0.external_halflength) }; + if inner.is_inside(pos) { + let Vec2 { x: u, y } = pos; // в основной СК + let v = self.0.v(y) + y.signum() * (self.0.external_halflength - self.0.internal_halflength); + vec2(u, v) // в плоском продолжении СК Outer на область Inner + } else { + pos + } + } +} + +impl FlatRegion for OuterCS { + fn distance_to_boundary(&self, ray: Ray) -> Option { + Rect { size: vec2(self.0.outer_radius, self.0.external_halflength) }.trace_into(ray) + } +} + +#[cfg(test)] +mod test { + use approx::{AbsDiffEq, assert_abs_diff_eq}; + use glam::{Mat2, vec2, Vec2}; + use itertools_num::linspace; + + use super::*; + + fn test_flat_region(region: &impl FlatRegion, range_global: (Vec2, Vec2), range_flat: (Vec2, Vec2)) { + const ε: f32 = 1e-3; + macro_rules! assert_eq_at { + ($at: expr, $left: expr, $right: expr) => { + let at = $at; + let left = $left; + let right = $right; + assert!(left.abs_diff_eq(right, ε), "Assertion failed at {at}:\n left: {left} = {}\n right: {right} = {}", stringify!($left), stringify!($right)); + }; + } + fn check_range(name_a: &str, a: Vec2, range_a: (Vec2, Vec2), name_b: &str, b: Vec2, range_b: (Vec2, Vec2)) { + assert!(b.cmpge(range_b.0 - ε).all() && b.cmple(range_b.1 + ε).all(), "Assertion failed:\nAt {name_a}: {a}, from range: {range_a:?}\nGot {name_b}: {b}, which is out of range {range_b:?}"); + // TODO sort out when to check these conditions: + if a.x.abs_diff_eq(&range_a.0.x, ε) { assert_abs_diff_eq!(b.x, range_b.0.x, epsilon=ε); } + if a.y.abs_diff_eq(&range_a.0.y, ε) { assert_abs_diff_eq!(b.y, range_b.0.y, epsilon=ε); } + if a.x.abs_diff_eq(&range_a.1.x, ε) { assert_abs_diff_eq!(b.x, range_b.1.x, epsilon=ε); } + if a.y.abs_diff_eq(&range_a.1.y, ε) { assert_abs_diff_eq!(b.y, range_b.1.y, epsilon=ε); } + } + for x in linspace(range_global.0.x, range_global.1.x, 20) { + for y in linspace(range_global.0.y, range_global.1.y, 20) { + let pos_global = vec2(x, y); + let pos_flat = region.global_to_flat(pos_global); + check_range("global", pos_global, range_global, "flat", pos_flat, range_flat); + assert_eq_at!(pos_global, region.global_to_flat(Location { pos: pos_global, rot: Mat2::IDENTITY }).pos, pos_flat); + assert_eq_at!(pos_global, region.flat_to_global(pos_flat), pos_global); + assert_eq_at!(pos_global, region.flat_to_global(region.global_to_flat(Location { pos: pos_global, rot: Mat2::IDENTITY })).rot, Mat2::IDENTITY); + } + } + for x in linspace(range_flat.0.x, range_flat.1.x, 20) { + for y in linspace(range_flat.0.y, range_flat.1.y, 20) { + let pos_flat = vec2(x, y); + let pos_global = region.flat_to_global(pos_flat); + check_range("flat", pos_flat, range_flat, "global", pos_global, range_global); + assert_eq_at!(pos_flat, region.flat_to_global(Location { pos: pos_flat, rot: Mat2::IDENTITY }).pos, pos_global); + assert_eq_at!(pos_flat, region.global_to_flat(pos_global), pos_flat); + assert_eq_at!(pos_flat, region.global_to_flat(region.flat_to_global(Location { pos: pos_global, rot: Mat2::IDENTITY })).rot, Mat2::IDENTITY); + } + } + } + + #[test] + fn test_mapper_inner() { + let mapper = InnerCS(Tube { + inner_radius: 30.0, + outer_radius: 50.0, + internal_halflength: 100.0, + external_halflength: 300.0, + }); + test_flat_region(&mapper, (vec2(-30.0, -300.0), vec2(30.0, 300.0)), (vec2(-30.0, -100.0), vec2(30.0, 100.0))); + test_flat_region(&mapper, (vec2(-60.0, -400.0), vec2(60.0, -300.0)), (vec2(-60.0, -200.0), vec2(60.0, -100.0))); + test_flat_region(&mapper, (vec2(-60.0, 300.0), vec2(60.0, 400.0)), (vec2(-60.0, 100.0), vec2(60.0, 200.0))); + } + + #[test] + fn test_mapper_outer() { + let mapper = OuterCS(Tube { + inner_radius: 30.0, + outer_radius: 50.0, + internal_halflength: 100.0, + external_halflength: 300.0, + }); + // TODO replace 200.20016 with something sane + test_flat_region(&mapper, (vec2(-30.0, -300.0), vec2(30.0, -1.0)), (vec2(-30.0, -300.0), vec2(30.0, -200.20016))); + test_flat_region(&mapper, (vec2(-30.0, 1.0), vec2(30.0, 300.0)), (vec2(-30.0, 200.20016), vec2(30.0, 300.0))); + test_flat_region(&mapper, (vec2(-60.0, -400.0), vec2(60.0, -300.0)), (vec2(-60.0, -400.0), vec2(60.0, -300.0))); + test_flat_region(&mapper, (vec2(-60.0, 300.0), vec2(60.0, 400.0)), (vec2(-60.0, 300.0), vec2(60.0, 400.0))); + // straight + for x in linspace(-60., 60., 20) { + for y in linspace(-320., 320., 20) { + assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.x, x); + } + } + // symmetrical + for x in linspace(0., 60., 20) { + for y in linspace(0., 320., 20) { + let pp = mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos; + let np = mapper.global_to_flat(Location { pos: vec2(-x, y), rot: Mat2::IDENTITY }).pos; + let pn = mapper.global_to_flat(Location { pos: vec2(x, -y), rot: Mat2::IDENTITY }).pos; + let nn = mapper.global_to_flat(Location { pos: vec2(-x, -y), rot: Mat2::IDENTITY }).pos; + assert_eq!(np, vec2(-pp.x, pp.y)); + assert_eq!(pn, vec2(pp.x, -pp.y)); + assert_eq!(nn, vec2(-pp.x, -pp.y)); + } + } + // clean boundary + for x in linspace(50., 60., 20) { + for y in linspace(0., 320., 20) { + assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y, y); + } + } + for x in linspace(0., 60., 20) { + for y in linspace(300., 320., 20) { + assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y, y); + } + } + // accelerating + for x in linspace(-29., 29., 20) { + for y in linspace(1., 299., 20) { + let v = mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y; + assert!(v > 200.0); + assert!(v > y); + } + } + } +} diff --git a/src/bin/flat/tube/mod.rs b/src/bin/flat/tube/mod.rs index 713e107..722f98b 100644 --- a/src/bin/flat/tube/mod.rs +++ b/src/bin/flat/tube/mod.rs @@ -9,6 +9,7 @@ use crate::tube::coords::FlatRegion; use crate::types::{FlatTraceResult, Hit, Location, Object, Ray}; pub mod metric; +mod coords; pub struct Space { pub tube: Tube, @@ -184,233 +185,3 @@ fn test_rect() { 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 coords { - use glam::{Mat2, Vec2, vec2}; - - use crate::riemann::Metric; - use crate::types::{Location, Ray}; - - use super::{Rect, Tube}; - - pub trait FlatCoordinateSystem { - fn flat_to_global(&self, v: T) -> T; - fn global_to_flat(&self, v: T) -> T; - } - - pub trait FlatRegion: FlatCoordinateSystem + FlatCoordinateSystem + FlatCoordinateSystem { - // Измеряет расстояние до выхода за пределы области вдоль луча ray. Луч задаётся в плоской СК. - fn distance_to_boundary(&self, _ray: Ray) -> Option { None } - } - - trait MetricCS: FlatCoordinateSystem { - fn global_metric(&self) -> &impl Metric; - fn flat_to_global_tfm_at(&self, pos: Vec2) -> Mat2 { - self.global_metric().sqrt_at(self.flat_to_global(pos)).inverse().into() - } - fn global_to_flat_tfm_at(&self, pos: Vec2) -> Mat2 { - self.global_metric().sqrt_at(pos).into() - } - } - - impl + MetricCS> FlatCoordinateSystem for T { - fn flat_to_global(&self, ray: Ray) -> Ray { - Ray { - pos: self.flat_to_global(ray.pos), - dir: self.flat_to_global_tfm_at(ray.pos) * ray.dir, - } - } - - fn global_to_flat(&self, ray: Ray) -> Ray { - Ray { - pos: self.global_to_flat(ray.pos), - dir: self.global_to_flat_tfm_at(ray.pos) * ray.dir, - } - } - } - - impl + MetricCS> FlatCoordinateSystem for T { - fn flat_to_global(&self, loc: Location) -> Location { - Location { - pos: self.flat_to_global(loc.pos), - rot: self.flat_to_global_tfm_at(loc.pos) * loc.rot, - } - } - - fn global_to_flat(&self, loc: Location) -> Location { - Location { - pos: self.global_to_flat(loc.pos), // в плоской СК для Inner или её продолжении на Outer - rot: self.global_to_flat_tfm_at(loc.pos) * loc.rot, - } - } - } - - pub struct InnerCS(pub Tube); - - impl MetricCS for InnerCS { fn global_metric(&self) -> &impl Metric { &self.0 } } - - impl FlatCoordinateSystem for InnerCS { - fn flat_to_global(&self, pos: Vec2) -> Vec2 { - vec2(pos.x, self.0.y(pos.y)) - } - - // Работает только при |pos.x| ≤ inner_radius или |pos.y| ≥ external_halflength. - fn global_to_flat(&self, pos: Vec2) -> Vec2 { - vec2(pos.x, self.0.v(pos.y)) - } - } - - impl FlatRegion for InnerCS { - fn distance_to_boundary(&self, ray: Ray) -> Option { - Rect { size: vec2(self.0.inner_radius, self.0.internal_halflength) }.trace_out_of(ray) - } - } - - pub struct OuterCS(pub Tube); - - impl MetricCS for OuterCS { fn global_metric(&self) -> &impl Metric { &self.0 } } - - impl FlatCoordinateSystem for OuterCS { - fn flat_to_global(&self, pos: Vec2) -> Vec2 { - let inner = Rect { size: vec2(self.0.inner_radius + 1.0, self.0.external_halflength) }; - if inner.is_inside(pos) { - let Vec2 { x, y: v } = pos; - let y = self.0.y(v - v.signum() * (self.0.external_halflength - self.0.internal_halflength)); - vec2(x, y) - } else { - pos - } - } - - fn global_to_flat(&self, pos: Vec2) -> Vec2 { - let inner = Rect { size: vec2(self.0.inner_radius + 1.0, self.0.external_halflength) }; - if inner.is_inside(pos) { - let Vec2 { x: u, y } = pos; // в основной СК - let v = self.0.v(y) + y.signum() * (self.0.external_halflength - self.0.internal_halflength); - vec2(u, v) // в плоском продолжении СК Outer на область Inner - } else { - pos - } - } - } - - impl FlatRegion for OuterCS { - fn distance_to_boundary(&self, ray: Ray) -> Option { - Rect { size: vec2(self.0.outer_radius, self.0.external_halflength) }.trace_into(ray) - } - } - - #[cfg(test)] - mod test { - use approx::{AbsDiffEq, assert_abs_diff_eq}; - use glam::{Mat2, vec2, Vec2}; - use itertools_num::linspace; - - use super::*; - - fn test_flat_region(region: &impl FlatRegion, range_global: (Vec2, Vec2), range_flat: (Vec2, Vec2)) { - const ε: f32 = 1e-3; - macro_rules! assert_eq_at { - ($at: expr, $left: expr, $right: expr) => { - let at = $at; - let left = $left; - let right = $right; - assert!(left.abs_diff_eq(right, ε), "Assertion failed at {at}:\n left: {left} = {}\n right: {right} = {}", stringify!($left), stringify!($right)); - }; - } - fn check_range(name_a: &str, a: Vec2, range_a: (Vec2, Vec2), name_b: &str, b: Vec2, range_b: (Vec2, Vec2)) { - assert!(b.cmpge(range_b.0 - ε).all() && b.cmple(range_b.1 + ε).all(), "Assertion failed:\nAt {name_a}: {a}, from range: {range_a:?}\nGot {name_b}: {b}, which is out of range {range_b:?}"); - // TODO sort out when to check these conditions: - if a.x.abs_diff_eq(&range_a.0.x, ε) { assert_abs_diff_eq!(b.x, range_b.0.x, epsilon=ε); } - if a.y.abs_diff_eq(&range_a.0.y, ε) { assert_abs_diff_eq!(b.y, range_b.0.y, epsilon=ε); } - if a.x.abs_diff_eq(&range_a.1.x, ε) { assert_abs_diff_eq!(b.x, range_b.1.x, epsilon=ε); } - if a.y.abs_diff_eq(&range_a.1.y, ε) { assert_abs_diff_eq!(b.y, range_b.1.y, epsilon=ε); } - } - for x in linspace(range_global.0.x, range_global.1.x, 20) { - for y in linspace(range_global.0.y, range_global.1.y, 20) { - let pos_global = vec2(x, y); - let pos_flat = region.global_to_flat(pos_global); - check_range("global", pos_global, range_global, "flat", pos_flat, range_flat); - assert_eq_at!(pos_global, region.global_to_flat(Location { pos: pos_global, rot: Mat2::IDENTITY }).pos, pos_flat); - assert_eq_at!(pos_global, region.flat_to_global(pos_flat), pos_global); - assert_eq_at!(pos_global, region.flat_to_global(region.global_to_flat(Location { pos: pos_global, rot: Mat2::IDENTITY })).rot, Mat2::IDENTITY); - } - } - for x in linspace(range_flat.0.x, range_flat.1.x, 20) { - for y in linspace(range_flat.0.y, range_flat.1.y, 20) { - let pos_flat = vec2(x, y); - let pos_global = region.flat_to_global(pos_flat); - check_range("flat", pos_flat, range_flat, "global", pos_global, range_global); - assert_eq_at!(pos_flat, region.flat_to_global(Location { pos: pos_flat, rot: Mat2::IDENTITY }).pos, pos_global); - assert_eq_at!(pos_flat, region.global_to_flat(pos_global), pos_flat); - assert_eq_at!(pos_flat, region.global_to_flat(region.flat_to_global(Location { pos: pos_global, rot: Mat2::IDENTITY })).rot, Mat2::IDENTITY); - } - } - } - - #[test] - fn test_mapper_inner() { - let mapper = InnerCS(Tube { - inner_radius: 30.0, - outer_radius: 50.0, - internal_halflength: 100.0, - external_halflength: 300.0, - }); - test_flat_region(&mapper, (vec2(-30.0, -300.0), vec2(30.0, 300.0)), (vec2(-30.0, -100.0), vec2(30.0, 100.0))); - test_flat_region(&mapper, (vec2(-60.0, -400.0), vec2(60.0, -300.0)), (vec2(-60.0, -200.0), vec2(60.0, -100.0))); - test_flat_region(&mapper, (vec2(-60.0, 300.0), vec2(60.0, 400.0)), (vec2(-60.0, 100.0), vec2(60.0, 200.0))); - } - - #[test] - fn test_mapper_outer() { - let mapper = OuterCS(Tube { - inner_radius: 30.0, - outer_radius: 50.0, - internal_halflength: 100.0, - external_halflength: 300.0, - }); - // TODO replace 200.20016 with something sane - test_flat_region(&mapper, (vec2(-30.0, -300.0), vec2(30.0, -1.0)), (vec2(-30.0, -300.0), vec2(30.0, -200.20016))); - test_flat_region(&mapper, (vec2(-30.0, 1.0), vec2(30.0, 300.0)), (vec2(-30.0, 200.20016), vec2(30.0, 300.0))); - test_flat_region(&mapper, (vec2(-60.0, -400.0), vec2(60.0, -300.0)), (vec2(-60.0, -400.0), vec2(60.0, -300.0))); - test_flat_region(&mapper, (vec2(-60.0, 300.0), vec2(60.0, 400.0)), (vec2(-60.0, 300.0), vec2(60.0, 400.0))); - // straight - for x in linspace(-60., 60., 20) { - for y in linspace(-320., 320., 20) { - assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.x, x); - } - } - // symmetrical - for x in linspace(0., 60., 20) { - for y in linspace(0., 320., 20) { - let pp = mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos; - let np = mapper.global_to_flat(Location { pos: vec2(-x, y), rot: Mat2::IDENTITY }).pos; - let pn = mapper.global_to_flat(Location { pos: vec2(x, -y), rot: Mat2::IDENTITY }).pos; - let nn = mapper.global_to_flat(Location { pos: vec2(-x, -y), rot: Mat2::IDENTITY }).pos; - assert_eq!(np, vec2(-pp.x, pp.y)); - assert_eq!(pn, vec2(pp.x, -pp.y)); - assert_eq!(nn, vec2(-pp.x, -pp.y)); - } - } - // clean boundary - for x in linspace(50., 60., 20) { - for y in linspace(0., 320., 20) { - assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y, y); - } - } - for x in linspace(0., 60., 20) { - for y in linspace(300., 320., 20) { - assert_eq!(mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y, y); - } - } - // accelerating - for x in linspace(-29., 29., 20) { - for y in linspace(1., 299., 20) { - let v = mapper.global_to_flat(Location { pos: vec2(x, y), rot: Mat2::IDENTITY }).pos.y; - assert!(v > 200.0); - assert!(v > y); - } - } - } - } -}