use glam::Vec2;
pub struct ProjectivePlane {
pub size: f32,
}
impl ProjectivePlane {
pub fn new(size: f32) -> Self {
Self { size }
}
pub fn projective_wrap(&self, pos: Vec2) -> Vec2 {
projective_wrap(pos, self.size)
}
}
pub fn projective_wrap(pos: Vec2, size: f32) -> Vec2 {
let mut x = pos.x;
let mut y = pos.y;
let double = 2.0 * size;
x = ((x % double) + double) % double;
y = ((y % double) + double) % double;
if x >= size && y >= size {
x -= size;
y -= size;
} else if x >= size {
x -= size;
y += size;
if y >= double {
y -= double;
}
if x >= size || y >= size {
x = ((x % size) + size) % size;
y = ((y % size) + size) % size;
}
} else if y >= size {
y -= size;
x += size;
if x >= double {
x -= double;
}
if x >= size || y >= size {
x = ((x % size) + size) % size;
y = ((y % size) + size) % size;
}
}
x = ((x % size) + size) % size;
y = ((y % size) + size) % size;
Vec2::new(x, y)
}
pub fn homogeneous_coords(x: f32, y: f32) -> [f32; 3] {
[x, y, 1.0]
}
pub fn projective_line(p1: [f32; 3], p2: [f32; 3]) -> [f32; 3] {
cross3(p1, p2)
}
pub fn line_intersection(l1: [f32; 3], l2: [f32; 3]) -> Option<Vec2> {
let p = cross3(l1, l2);
if p[2].abs() < 1e-10 {
if p[0].abs() < 1e-10 && p[1].abs() < 1e-10 {
return None; }
let scale = 1e6;
let len = (p[0] * p[0] + p[1] * p[1]).sqrt();
return Some(Vec2::new(p[0] / len * scale, p[1] / len * scale));
}
Some(Vec2::new(p[0] / p[2], p[1] / p[2]))
}
fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
pub fn cross_ratio(a: f32, b: f32, c: f32, d: f32) -> f32 {
let ac = c - a;
let bd = d - b;
let bc = c - b;
let ad = d - a;
if bc.abs() < 1e-10 || ad.abs() < 1e-10 {
return f32::INFINITY;
}
(ac * bd) / (bc * ad)
}
pub fn cross_ratio_homogeneous(a: [f32; 2], b: [f32; 2], c: [f32; 2], d: [f32; 2]) -> f32 {
let ac = a[0] * c[1] - a[1] * c[0];
let bd = b[0] * d[1] - b[1] * d[0];
let bc = b[0] * c[1] - b[1] * c[0];
let ad = a[0] * d[1] - a[1] * d[0];
if bc.abs() < 1e-10 || ad.abs() < 1e-10 {
return f32::INFINITY;
}
(ac * bd) / (bc * ad)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_projective_wrap_inside() {
let p = projective_wrap(Vec2::new(3.0, 4.0), 10.0);
assert!((p.x - 3.0).abs() < 1e-4);
assert!((p.y - 4.0).abs() < 1e-4);
}
#[test]
fn test_projective_wrap_result_in_domain() {
for ix in -20..20 {
for iy in -20..20 {
let pos = Vec2::new(ix as f32 * 3.7, iy as f32 * 2.3);
let w = projective_wrap(pos, 10.0);
assert!(w.x >= -1e-4 && w.x < 10.0 + 1e-4, "x out of range: {}", w.x);
assert!(w.y >= -1e-4 && w.y < 10.0 + 1e-4, "y out of range: {}", w.y);
}
}
}
#[test]
fn test_homogeneous_coords() {
let h = homogeneous_coords(3.0, 4.0);
assert_eq!(h, [3.0, 4.0, 1.0]);
}
#[test]
fn test_projective_line_through_points() {
let p1 = homogeneous_coords(0.0, 0.0);
let p2 = homogeneous_coords(1.0, 1.0);
let line = projective_line(p1, p2);
let dot1 = line[0] * p1[0] + line[1] * p1[1] + line[2] * p1[2];
let dot2 = line[0] * p2[0] + line[1] * p2[1] + line[2] * p2[2];
assert!(dot1.abs() < 1e-6);
assert!(dot2.abs() < 1e-6);
}
#[test]
fn test_line_intersection() {
let l1 = projective_line(homogeneous_coords(0.0, 0.0), homogeneous_coords(1.0, 0.0));
let l2 = projective_line(homogeneous_coords(0.5, -1.0), homogeneous_coords(0.5, 1.0));
let inter = line_intersection(l1, l2);
assert!(inter.is_some());
let p = inter.unwrap();
assert!((p.x - 0.5).abs() < 1e-3, "x = {}", p.x);
assert!(p.y.abs() < 1e-3, "y = {}", p.y);
}
#[test]
fn test_line_intersection_parallel() {
let l1 = [0.0_f32, 1.0, -1.0]; let l2 = [0.0_f32, 1.0, -2.0]; let inter = line_intersection(l1, l2);
assert!(inter.is_some());
}
#[test]
fn test_cross_ratio_basic() {
let cr = cross_ratio(0.0, 1.0, 2.0, 3.0);
assert!((cr - 4.0 / 3.0).abs() < 1e-4, "Cross ratio = {}", cr);
}
#[test]
fn test_cross_ratio_projective_invariant() {
let transform = |x: f32| (2.0 * x + 1.0) / (x + 1.0);
let a = 0.0_f32;
let b = 1.0;
let c = 2.0;
let d = 3.0;
let cr1 = cross_ratio(a, b, c, d);
let cr2 = cross_ratio(transform(a), transform(b), transform(c), transform(d));
assert!((cr1 - cr2).abs() < 1e-3, "Cross-ratio not invariant: {} vs {}", cr1, cr2);
}
#[test]
fn test_cross_ratio_homogeneous() {
let cr1 = cross_ratio(0.0, 1.0, 2.0, 3.0);
let cr2 = cross_ratio_homogeneous([0.0, 1.0], [1.0, 1.0], [2.0, 1.0], [3.0, 1.0]);
assert!((cr1 - cr2).abs() < 1e-4);
}
}