use crate::simul_eq::simul_eq_solve;
pub struct TransBilinear {
mtx: [[f64; 2]; 4],
valid: bool,
}
impl TransBilinear {
pub fn new() -> Self {
Self {
mtx: [[0.0; 2]; 4],
valid: false,
}
}
pub fn new_quad_to_quad(src: &[f64; 8], dst: &[f64; 8]) -> Self {
let mut t = Self::new();
t.quad_to_quad(src, dst);
t
}
pub fn new_rect_to_quad(x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) -> Self {
let mut t = Self::new();
t.rect_to_quad(x1, y1, x2, y2, quad);
t
}
pub fn new_quad_to_rect(quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
let mut t = Self::new();
t.quad_to_rect(quad, x1, y1, x2, y2);
t
}
pub fn quad_to_quad(&mut self, src: &[f64; 8], dst: &[f64; 8]) {
let mut left = [[0.0_f64; 4]; 4];
let mut right = [[0.0_f64; 2]; 4];
for i in 0..4 {
let ix = i * 2;
let iy = ix + 1;
left[i][0] = 1.0;
left[i][1] = src[ix] * src[iy];
left[i][2] = src[ix];
left[i][3] = src[iy];
right[i][0] = dst[ix];
right[i][1] = dst[iy];
}
self.valid = simul_eq_solve(&left, &right, &mut self.mtx);
}
pub fn rect_to_quad(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, quad: &[f64; 8]) {
let src = [x1, y1, x2, y1, x2, y2, x1, y2];
self.quad_to_quad(&src, quad);
}
pub fn quad_to_rect(&mut self, quad: &[f64; 8], x1: f64, y1: f64, x2: f64, y2: f64) {
let dst = [x1, y1, x2, y1, x2, y2, x1, y2];
self.quad_to_quad(quad, &dst);
}
pub fn is_valid(&self) -> bool {
self.valid
}
pub fn transform(&self, x: &mut f64, y: &mut f64) {
let tx = *x;
let ty = *y;
let xy = tx * ty;
*x = self.mtx[0][0] + self.mtx[1][0] * xy + self.mtx[2][0] * tx + self.mtx[3][0] * ty;
*y = self.mtx[0][1] + self.mtx[1][1] * xy + self.mtx[2][1] * tx + self.mtx[3][1] * ty;
}
pub fn begin(&self, x: f64, y: f64, step: f64) -> IteratorX {
IteratorX::new(x, y, step, &self.mtx)
}
}
impl Default for TransBilinear {
fn default() -> Self {
Self::new()
}
}
pub struct IteratorX {
inc_x: f64,
inc_y: f64,
pub x: f64,
pub y: f64,
}
impl IteratorX {
fn new(tx: f64, ty: f64, step: f64, m: &[[f64; 2]; 4]) -> Self {
Self {
inc_x: m[1][0] * step * ty + m[2][0] * step,
inc_y: m[1][1] * step * ty + m[2][1] * step,
x: m[0][0] + m[1][0] * tx * ty + m[2][0] * tx + m[3][0] * ty,
y: m[0][1] + m[1][1] * tx * ty + m[2][1] * tx + m[3][1] * ty,
}
}
pub fn next(&mut self) {
self.x += self.inc_x;
self.y += self.inc_y;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_rect() {
let src = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
let dst = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
let t = TransBilinear::new_quad_to_quad(&src, &dst);
assert!(t.is_valid());
let mut x = 0.5;
let mut y = 0.5;
t.transform(&mut x, &mut y);
assert!((x - 0.5).abs() < 1e-10);
assert!((y - 0.5).abs() < 1e-10);
}
#[test]
fn test_rect_to_quad_corners() {
let quad = [10.0, 10.0, 20.0, 10.0, 20.0, 20.0, 10.0, 20.0];
let t = TransBilinear::new_rect_to_quad(0.0, 0.0, 1.0, 1.0, &quad);
assert!(t.is_valid());
let mut x = 0.0;
let mut y = 0.0;
t.transform(&mut x, &mut y);
assert!((x - 10.0).abs() < 1e-10);
assert!((y - 10.0).abs() < 1e-10);
x = 1.0;
y = 1.0;
t.transform(&mut x, &mut y);
assert!((x - 20.0).abs() < 1e-10);
assert!((y - 20.0).abs() < 1e-10);
}
#[test]
fn test_quad_to_rect() {
let quad = [10.0, 10.0, 20.0, 10.0, 20.0, 20.0, 10.0, 20.0];
let t = TransBilinear::new_quad_to_rect(&quad, 0.0, 0.0, 1.0, 1.0);
assert!(t.is_valid());
let mut x = 10.0;
let mut y = 10.0;
t.transform(&mut x, &mut y);
assert!((x - 0.0).abs() < 1e-10);
assert!((y - 0.0).abs() < 1e-10);
}
#[test]
fn test_round_trip_parallelogram() {
let quad = [5.0, 0.0, 15.0, 2.0, 17.0, 12.0, 7.0, 10.0];
let forward = TransBilinear::new_rect_to_quad(0.0, 0.0, 10.0, 10.0, &quad);
let reverse = TransBilinear::new_quad_to_rect(&quad, 0.0, 0.0, 10.0, 10.0);
assert!(forward.is_valid());
assert!(reverse.is_valid());
let mut x = 5.0;
let mut y = 5.0;
forward.transform(&mut x, &mut y);
reverse.transform(&mut x, &mut y);
assert!((x - 5.0).abs() < 1e-8);
assert!((y - 5.0).abs() < 1e-8);
}
#[test]
fn test_iterator() {
let src = [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
let dst = [0.0, 0.0, 10.0, 0.0, 10.0, 10.0, 0.0, 10.0];
let t = TransBilinear::new_quad_to_quad(&src, &dst);
let mut it = t.begin(0.0, 0.0, 0.5);
assert!((it.x - 0.0).abs() < 1e-10);
assert!((it.y - 0.0).abs() < 1e-10);
it.next();
assert!((it.x - 5.0).abs() < 1e-10);
assert!((it.y - 0.0).abs() < 1e-10);
}
#[test]
fn test_default_is_invalid() {
let t = TransBilinear::new();
assert!(!t.is_valid());
}
#[test]
fn test_scaling_transform() {
let quad = [0.0, 0.0, 2.0, 0.0, 2.0, 2.0, 0.0, 2.0];
let t = TransBilinear::new_rect_to_quad(0.0, 0.0, 1.0, 1.0, &quad);
assert!(t.is_valid());
let mut x = 0.5;
let mut y = 0.5;
t.transform(&mut x, &mut y);
assert!((x - 1.0).abs() < 1e-10);
assert!((y - 1.0).abs() < 1e-10);
}
#[test]
fn test_translation_transform() {
let quad = [10.0, 20.0, 11.0, 20.0, 11.0, 21.0, 10.0, 21.0];
let t = TransBilinear::new_rect_to_quad(0.0, 0.0, 1.0, 1.0, &quad);
assert!(t.is_valid());
let mut x = 0.0;
let mut y = 0.0;
t.transform(&mut x, &mut y);
assert!((x - 10.0).abs() < 1e-10);
assert!((y - 20.0).abs() < 1e-10);
}
}