use crate::{Float, Point2};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Jacobian2x2<T: Float> {
pub m11: T,
pub m12: T,
pub m21: T,
pub m22: T,
}
impl<T: Float> Jacobian2x2<T> {
pub fn for_quad(
xi: T,
eta: T,
a: Point2<T>,
b: Point2<T>,
c: Point2<T>,
d: Point2<T>,
) -> Self {
let one = T::one();
let d_n1_dxi = -one + eta; let d_n1_deta = -one + xi; let d_n2_dxi = one - eta; let d_n2_deta = -one - xi; let d_n3_dxi = one + eta; let d_n3_deta = one + xi; let d_n4_dxi = -one - eta; let d_n4_deta = one - xi; let m11 = d_n1_dxi * a.x + d_n2_dxi * b.x + d_n3_dxi * c.x + d_n4_dxi * d.x;
let m12 = d_n1_deta * a.x + d_n2_deta * b.x + d_n3_deta * c.x + d_n4_deta * d.x;
let m21 = d_n1_dxi * a.y + d_n2_dxi * b.y + d_n3_dxi * c.y + d_n4_dxi * d.y;
let m22 = d_n1_deta * a.y + d_n2_deta * b.y + d_n3_deta * c.y + d_n4_deta * d.y;
Jacobian2x2 { m11, m12, m21, m22 }
}
pub fn det(self) -> T {
self.m11 * self.m22 - self.m12 * self.m21
}
pub fn inverse(self) -> Option<Jacobian2x2<T>> {
let d = self.det();
if d.abs() < T::epsilon() { return None; }
let inv = T::one() / d;
Some(Jacobian2x2 {
m11: self.m22 * inv,
m12: -self.m12 * inv,
m21: -self.m21 * inv,
m22: self.m11 * inv,
})
}
}
pub fn invert_quad_mapping<T: Float>(
mut xi: T,
mut eta: T,
p: Point2<T>,
a: Point2<T>,
b: Point2<T>,
c: Point2<T>,
d: Point2<T>,
tol: T,
max_iters: usize,
) -> Option<(T, T)> {
for _ in 0..max_iters {
let n1 = (T::one()-xi)*(T::one()-eta)/T::from(4.0).unwrap();
let n2 = (T::one()+xi)*(T::one()-eta)/T::from(4.0).unwrap();
let n3 = (T::one()+xi)*(T::one()+eta)/T::from(4.0).unwrap();
let n4 = (T::one()-xi)*(T::one()+eta)/T::from(4.0).unwrap();
let x = a.x*n1 + b.x*n2 + c.x*n3 + d.x*n4;
let y = a.y*n1 + b.y*n2 + c.y*n3 + d.y*n4;
let rx = x - p.x;
let ry = y - p.y;
if rx.abs() < tol && ry.abs() < tol {
return Some((xi, eta));
}
let j = Jacobian2x2::for_quad(xi, eta, a, b, c, d);
let inv_j = match j.inverse() {
Some(inv) => inv,
None => return None,
};
let dxi = inv_j.m11 * rx + inv_j.m12 * ry;
let deta = inv_j.m21 * rx + inv_j.m22 * ry;
xi = xi - dxi;
eta = eta - deta;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Point2;
use num_traits::Zero;
#[test]
fn jacobian_quad_det_nonzero() {
let a = Point2::new(0.0,0.0);
let b = Point2::new(2.0,0.0);
let c = Point2::new(2.0,1.0);
let d = Point2::new(0.0,1.0);
let j = Jacobian2x2::for_quad(0.0_f64, 0.0, a,b,c,d );
assert!(!j.det().is_zero());
}
#[test]
fn invert_quad_identity() {
let a=Point2::new(0.0,0.0);
let b=Point2::new(1.0,0.0);
let c=Point2::new(1.0,1.0);
let d=Point2::new(0.0,1.0);
let p = Point2::new(0.3, 0.7);
let got = invert_quad_mapping(0.0_f64, 0.0, p, a,b,c,d, 1e-6, 50 );
assert!(got.is_some());
let (xi,eta)=got.unwrap();
assert!((xi - (-0.4)).abs() < 1e-3);
assert!((eta - 0.4).abs() < 1e-3);
}
}