Extract CS management into a file

This commit is contained in:
numzero 2024-06-30 14:19:36 +03:00
parent 54aa1369ab
commit a9685c81fd
2 changed files with 228 additions and 230 deletions

227
src/bin/flat/tube/coords.rs Normal file
View File

@ -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<T> {
fn flat_to_global(&self, v: T) -> T;
fn global_to_flat(&self, v: T) -> T;
}
pub trait FlatRegion: FlatCoordinateSystem<Vec2> + FlatCoordinateSystem<Ray> + FlatCoordinateSystem<Location> {
// Измеряет расстояние до выхода за пределы области вдоль луча ray. Луч задаётся в плоской СК.
fn distance_to_boundary(&self, _ray: Ray) -> Option<f32> { None }
}
trait MetricCS: FlatCoordinateSystem<Vec2> {
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<T: FlatCoordinateSystem<Vec2> + MetricCS> FlatCoordinateSystem<Ray> 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<T: FlatCoordinateSystem<Vec2> + MetricCS> FlatCoordinateSystem<Location> 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<Vec2> 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<f32> {
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<Vec2> 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<f32> {
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);
}
}
}
}

View File

@ -9,6 +9,7 @@ use crate::tube::coords::FlatRegion;
use crate::types::{FlatTraceResult, Hit, Location, Object, Ray}; use crate::types::{FlatTraceResult, Hit, Location, Object, Ray};
pub mod metric; pub mod metric;
mod coords;
pub struct Space { pub struct Space {
pub tube: Tube, 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(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)); 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<T> {
fn flat_to_global(&self, v: T) -> T;
fn global_to_flat(&self, v: T) -> T;
}
pub trait FlatRegion: FlatCoordinateSystem<Vec2> + FlatCoordinateSystem<Ray> + FlatCoordinateSystem<Location> {
// Измеряет расстояние до выхода за пределы области вдоль луча ray. Луч задаётся в плоской СК.
fn distance_to_boundary(&self, _ray: Ray) -> Option<f32> { None }
}
trait MetricCS: FlatCoordinateSystem<Vec2> {
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<T: FlatCoordinateSystem<Vec2> + MetricCS> FlatCoordinateSystem<Ray> 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<T: FlatCoordinateSystem<Vec2> + MetricCS> FlatCoordinateSystem<Location> 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<Vec2> 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<f32> {
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<Vec2> 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<f32> {
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);
}
}
}
}
}