pub use projective_grid::expert::lattice::GridTransform;
use projective_grid::Coord;
pub type GridAlignment = GridTransform;
#[inline]
pub fn cell_rect_corners_at(gc: Coord, px_per_cell: f32) -> [nalgebra::Point2<f32>; 4] {
let x0 = gc.u as f32 * px_per_cell;
let y0 = gc.v as f32 * px_per_cell;
let s = px_per_cell;
[
nalgebra::Point2::new(x0, y0),
nalgebra::Point2::new(x0 + s, y0),
nalgebra::Point2::new(x0 + s, y0 + s),
nalgebra::Point2::new(x0, y0 + s),
]
}
pub const GRID_TRANSFORMS_D4: [GridTransform; 8] = [
projective_grid::expert::lattice::D4_TRANSFORMS[0],
projective_grid::expert::lattice::D4_TRANSFORMS[3],
projective_grid::expert::lattice::D4_TRANSFORMS[2],
projective_grid::expert::lattice::D4_TRANSFORMS[1],
projective_grid::expert::lattice::D4_TRANSFORMS[4],
projective_grid::expert::lattice::D4_TRANSFORMS[5],
projective_grid::expert::lattice::D4_TRANSFORMS[6],
projective_grid::expert::lattice::D4_TRANSFORMS[7],
];
pub const GRID_TRANSFORMS_C4: [GridTransform; 4] = [
projective_grid::expert::lattice::D4_TRANSFORMS[0],
projective_grid::expert::lattice::D4_TRANSFORMS[3],
projective_grid::expert::lattice::D4_TRANSFORMS[2],
projective_grid::expert::lattice::D4_TRANSFORMS[1],
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn d4_index_1_is_v_negative_u() {
let t = GRID_TRANSFORMS_D4[1];
assert_eq!(t.apply(Coord::new(1, 0)), Coord::new(0, -1));
assert_eq!(t.apply(Coord::new(0, 1)), Coord::new(1, 0));
}
#[test]
fn transform_identity_mapping_and_inverse() {
let identity = GridTransform::IDENTITY;
assert_eq!(identity.apply(Coord::new(7, -3)), Coord::new(7, -3));
assert_eq!(identity.inverse(), Some(identity));
for t in GRID_TRANSFORMS_D4 {
let inv = t.inverse().expect("D4 transform is unimodular");
let p = Coord::new(4, -9);
let q = t.apply(p);
assert_eq!(inv.apply(q), p);
}
}
#[test]
fn c4_is_the_first_four_of_d4() {
assert_eq!(GRID_TRANSFORMS_C4.len(), 4);
for (idx, (c4, d4)) in GRID_TRANSFORMS_C4
.iter()
.zip(GRID_TRANSFORMS_D4.iter())
.enumerate()
{
assert_eq!(c4, d4, "C4[{idx}] must be D4[{idx}]");
}
}
#[test]
fn c4_entries_are_rotations_and_d4_tail_are_reflections() {
for (idx, t) in GRID_TRANSFORMS_C4.iter().enumerate() {
assert_eq!(t.determinant(), 1, "C4[{idx}] must preserve orientation");
}
for (offset, t) in GRID_TRANSFORMS_D4[4..].iter().enumerate() {
let idx = offset + 4;
assert_eq!(t.determinant(), -1, "D4[{idx}] must reverse orientation");
}
}
#[test]
fn c4_is_closed_under_composition() {
let basis = [Coord::new(1, 0), Coord::new(0, 1)];
for (i, a) in GRID_TRANSFORMS_C4.iter().enumerate() {
for (j, b) in GRID_TRANSFORMS_C4.iter().enumerate() {
let composed = basis.map(|p| a.apply(b.apply(p)));
assert!(
GRID_TRANSFORMS_C4
.iter()
.any(|c| basis.map(|p| c.apply(p)) == composed),
"C4[{i}] ∘ C4[{j}] left the subgroup"
);
}
}
}
#[test]
fn alignment_mapping_and_inverse() {
let align = GRID_TRANSFORMS_D4[1].with_translation([3, -4]);
let p = Coord::new(2, 5);
let q = align.apply(p);
assert_eq!(q, Coord::new(8, -6));
let inv = align.inverse().expect("D4 alignment is invertible");
assert_eq!(inv.apply(q), p);
}
}