refraction/src/riemann.rs

241 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::mathx::{make_mat3, Decomp3};
use glam::*;
pub type Tens3 = [Mat3; 3];
pub trait Metric {
fn sqrt_at(&self, pos: Vec3) -> Decomp3;
fn at(&self, pos: Vec3) -> Mat3 {
self.sqrt_at(pos).square().into()
}
fn inverse_at(&self, pos: Vec3) -> Mat3 {
self.sqrt_at(pos).square().inverse().into()
}
fn part_derivs_at(&self, pos: Vec3) -> Tens3 {
part_deriv(|p| self.at(p), pos, 1.0 / 1024.0) // division by such eps is exact which is good for overall precision
}
fn vec_length_at(&self, at: Vec3, v: Vec3) -> f32 {
v.dot(self.at(at) * v).sqrt()
}
fn normalize_vec_at(&self, at: Vec3, v: Vec3) -> Vec3 {
v / self.vec_length_at(at, v)
}
}
pub struct TraceIter<'a, M: Metric> {
space: &'a M,
p: Vec3,
v: Vec3,
}
impl<'a, M: Metric> Iterator for TraceIter<'a, M> {
type Item = Vec3;
fn next(&mut self) -> Option<Self::Item> {
let a: Vec3 = -contract2(krist(self.space, self.p), self.v);
self.v += a;
self.p += self.v;
Some(self.p)
}
}
pub fn trace_iter<M: Metric>(space: &M, base: Vec3, dir: Vec3, dt: f32) -> TraceIter<M> {
TraceIter {
space,
p: base,
v: dt * space.normalize_vec_at(base, dir),
}
}
pub fn krist(space: &impl Metric, pos: Vec3) -> Tens3 {
// Γ^i_k_l = .5 * g^i^m * (g_m_k,l + g_m_l,k - g_k_l,m)
let g = &space.inverse_at(pos); // с верхними индексами
let d = space.part_derivs_at(pos);
// ret[i][l][k] = sum((m) => .5f * g[m][i] * (d[k][l][m] + d[l][k][m] - d[m][k][l]))
make_tens3(|i, l, k| {
0.5 * (0..2)
.map(|m| g.col(m)[i] * (d[l].col(k)[m] + d[k].col(m)[l] - d[m].col(k)[l]))
.sum::<f32>()
})
}
fn dir_deriv(f: impl Fn(Vec3) -> Mat3, pos: Vec3, delta: Vec3) -> Mat3 {
(f(pos + delta) - f(pos - delta)) / (2.0 * delta.length())
}
fn part_deriv(f: impl Fn(Vec3) -> Mat3, pos: Vec3, eps: f32) -> Tens3 {
[
dir_deriv(&f, pos, vec3(eps, 0.0, 0.0)),
dir_deriv(&f, pos, vec3(0.0, eps, 0.0)),
dir_deriv(&f, pos, vec3(0.0, 0.0, eps)),
]
}
/// Сворачивает тензор t с вектором u
pub fn contract(t: Tens3, u: Vec3) -> Mat3 {
mat3(t[0] * u, t[1] * u, t[2] * u).transpose()
}
/// Сворачивает тензор t с вектором v дважды, по второму и третьему индексам.
pub fn contract2(t: Tens3, v: Vec3) -> Vec3 {
contract(t, v) * v
}
fn make_tens3(f: impl Fn(usize, usize, usize) -> f32) -> Tens3 {
std::array::from_fn(|i| make_mat3(|j, k| f(i, j, k)))
}
#[test]
fn m3() {
let m = make_mat3(|i, j| (i + 2 * j) as f32);
assert_eq!(m.col(0)[0], 0.0);
assert_eq!(m.col(1)[0], 1.0);
assert_eq!(m.col(0)[1], 2.0);
assert_eq!(m.col(1)[1], 3.0);
}
#[test]
fn t3() {
let t = make_tens3(|i, j, k| (i + 2 * j + 4 * k) as f32);
assert_eq!(t[0].col(0)[0], 0.0);
assert_eq!(t[1].col(0)[0], 1.0);
assert_eq!(t[0].col(1)[0], 2.0);
assert_eq!(t[1].col(1)[0], 3.0);
assert_eq!(t[0].col(0)[1], 4.0);
assert_eq!(t[1].col(0)[1], 5.0);
assert_eq!(t[0].col(1)[1], 6.0);
assert_eq!(t[1].col(1)[1], 7.0);
}
pub mod samples {
use glam::{Mat3, Vec3};
use super::{Decomp3, Metric};
pub struct ScaledMetric {
/// Specifies unit size in each cardinal direction. E.g. with scale=(2, 3), vector (1, 0) has length 2 while a unit vector with the same direction is (1/2, 0).
pub scale: Vec3,
}
impl Metric for ScaledMetric {
fn sqrt_at(&self, _pos: Vec3) -> Decomp3 {
Decomp3 {
diag: self.scale,
ortho: Mat3::IDENTITY,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use glam::{vec3, Mat3};
use rand::{Rng, SeedableRng};
#[test]
fn uniform_scaled_metric() {
let mut rng = rand_pcg::Pcg64Mcg::seed_from_u64(17);
let metric = samples::ScaledMetric {
scale: vec3(3., 4., 5.),
};
assert_eq!(
metric.sqrt_at(rng.gen()),
Decomp3 {
ortho: Mat3::IDENTITY,
diag: vec3(3., 4., 5.)
}
);
assert_eq!(
metric.at(rng.gen()),
Mat3::from_cols_array(&[9., 0., 0., 0., 16., 0., 0., 0., 25.])
);
assert_eq!(
metric.inverse_at(rng.gen()),
Mat3::from_cols_array(&[1. / 9., 0., 0., 0., 1. / 16., 0., 0., 0., 1. / 25.])
);
assert_eq!(
metric.part_derivs_at(rng.gen()),
[Mat3::ZERO, Mat3::ZERO, Mat3::ZERO]
);
assert_eq!(metric.vec_length_at(rng.gen(), vec3(1., 0., 0.)), 3.);
assert_eq!(metric.vec_length_at(rng.gen(), vec3(0., 1., 0.)), 4.);
assert_eq!(metric.vec_length_at(rng.gen(), vec3(0., 0., 1.)), 5.);
assert_eq!(metric.vec_length_at(rng.gen(), vec3(1., 1., 0.)), 5.);
assert_eq!(
metric.normalize_vec_at(rng.gen(), vec3(1., 0., 0.)),
vec3(1. / 3., 0., 0.)
);
assert_eq!(
metric.normalize_vec_at(rng.gen(), vec3(0., 1., 0.)),
vec3(0., 1. / 4., 0.)
);
assert_eq!(
metric.normalize_vec_at(rng.gen(), vec3(1., 1., 0.)),
vec3(1. / 5., 1. / 5., 0.)
);
}
#[test]
fn test_trace_iter() {
let metric = samples::ScaledMetric {
scale: vec3(2., 4., 3.),
};
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(1., 0., 0.), 1.)
.nth(7)
.unwrap(),
vec3(7., 5., 0.)
);
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(2., 0., 0.), 1.)
.nth(7)
.unwrap(),
vec3(7., 5., 0.)
);
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(1., 0., 0.), 0.5)
.nth(7)
.unwrap(),
vec3(5., 5., 0.)
);
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(0., 1., 0.), 1.)
.nth(9)
.unwrap(),
vec3(3., 7.5, 0.)
);
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(0., 4., 0.), 1.)
.nth(9)
.unwrap(),
vec3(3., 7.5, 0.)
);
assert_eq!(
trace_iter(&metric, vec3(3., 5., 0.), vec3(0., 1., 0.), 0.5)
.nth(9)
.unwrap(),
vec3(3., 6.25, 0.)
);
assert_abs_diff_eq!(
trace_iter(
&metric,
vec3(3., 5., 0.),
vec3(0.5, 0.25, 0.),
std::f32::consts::SQRT_2
)
.nth(7)
.unwrap(),
vec3(7., 7., 0.),
epsilon = 1e-5
);
}
}