use nalgebra::Vector3;
use super::HomogeneousPoint;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HomogeneousLine {
coeffs: Vector3<f64>,
}
impl HomogeneousLine {
pub fn new(a: f64, b: f64, c: f64) -> Self {
Self {
coeffs: Vector3::new(a, b, c),
}
}
pub fn through_points(p1: &HomogeneousPoint, p2: &HomogeneousPoint) -> Self {
let cross = p1.coords().cross(p2.coords());
Self { coeffs: cross }
}
pub fn intersect(&self, other: &HomogeneousLine) -> HomogeneousPoint {
let cross = self.coeffs.cross(&other.coeffs);
HomogeneousPoint::new(cross[0], cross[1], cross[2])
}
pub fn contains(&self, point: &HomogeneousPoint) -> bool {
let dot = self.coeffs.dot(point.coords());
dot.abs() < 1e-10
}
pub fn is_parallel(&self, other: &HomogeneousLine) -> bool {
self.intersect(other).is_at_infinity()
}
pub fn normalize(&self) -> Self {
let norm = (self.coeffs[0].powi(2) + self.coeffs[1].powi(2)).sqrt();
if norm < 1e-10 {
*self
} else {
Self::new(
self.coeffs[0] / norm,
self.coeffs[1] / norm,
self.coeffs[2] / norm,
)
}
}
pub fn a(&self) -> f64 {
self.coeffs[0]
}
pub fn b(&self) -> f64 {
self.coeffs[1]
}
pub fn c(&self) -> f64 {
self.coeffs[2]
}
pub fn coeffs(&self) -> &Vector3<f64> {
&self.coeffs
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-10
}
#[test]
fn test_new() {
let line = HomogeneousLine::new(1.0, 2.0, 3.0);
assert_eq!(line.a(), 1.0);
assert_eq!(line.b(), 2.0);
assert_eq!(line.c(), 3.0);
}
#[test]
fn test_through_points() {
let p1 = HomogeneousPoint::new(1.0, 0.0, 1.0); let p2 = HomogeneousPoint::new(0.0, 1.0, 1.0);
let line = HomogeneousLine::through_points(&p1, &p2);
assert!(line.contains(&p1));
assert!(line.contains(&p2));
}
#[test]
fn test_contains() {
let line = HomogeneousLine::new(1.0, 1.0, -2.0);
let p1 = HomogeneousPoint::new(1.0, 1.0, 1.0); let p2 = HomogeneousPoint::new(0.0, 2.0, 1.0); let p3 = HomogeneousPoint::new(2.0, 0.0, 1.0); let p4 = HomogeneousPoint::new(0.0, 0.0, 1.0);
assert!(line.contains(&p1));
assert!(line.contains(&p2));
assert!(line.contains(&p3));
assert!(!line.contains(&p4));
}
#[test]
fn test_intersect() {
let l1 = HomogeneousLine::new(1.0, 1.0, -1.0);
let l2 = HomogeneousLine::new(1.0, -1.0, 0.0);
let intersection = l1.intersect(&l2);
let euclidean = intersection.to_euclidean().unwrap();
assert!(approx_eq(euclidean.x(), 0.5));
assert!(approx_eq(euclidean.y(), 0.5));
}
#[test]
fn test_intersect_parallel() {
let l1 = HomogeneousLine::new(1.0, -1.0, 0.0);
let l2 = HomogeneousLine::new(1.0, -1.0, 1.0);
let intersection = l1.intersect(&l2);
assert!(intersection.is_at_infinity());
}
#[test]
fn test_is_parallel() {
let l1 = HomogeneousLine::new(1.0, -1.0, 0.0); let l2 = HomogeneousLine::new(1.0, -1.0, 1.0); let l3 = HomogeneousLine::new(1.0, 1.0, 0.0);
assert!(l1.is_parallel(&l2));
assert!(!l1.is_parallel(&l3));
}
#[test]
fn test_normalize() {
let line = HomogeneousLine::new(3.0, 4.0, 5.0);
let normalized = line.normalize();
let sum_squares = normalized.a().powi(2) + normalized.b().powi(2);
assert!(approx_eq(sum_squares, 1.0));
let scale = (3.0_f64.powi(2) + 4.0_f64.powi(2)).sqrt();
assert!(approx_eq(normalized.c(), 5.0 / scale));
}
#[test]
fn test_through_points_contains_both() {
let p1 = HomogeneousPoint::new(2.0, 3.0, 1.0);
let p2 = HomogeneousPoint::new(-1.0, 5.0, 1.0);
let line = HomogeneousLine::through_points(&p1, &p2);
assert!(line.contains(&p1));
assert!(line.contains(&p2));
}
#[test]
fn test_line_point_duality() {
let l1 = HomogeneousLine::new(1.0, 0.0, -1.0); let l2 = HomogeneousLine::new(0.0, 1.0, -1.0);
let p = l1.intersect(&l2);
let p2 = HomogeneousPoint::new(2.0, 2.0, 1.0);
let l3 = HomogeneousLine::through_points(&p, &p2);
assert!(l3.contains(&p));
assert!(l3.contains(&p2));
}
#[test]
fn test_equivalence_under_scaling() {
let l1 = HomogeneousLine::new(1.0, 2.0, 3.0);
let l2 = HomogeneousLine::new(2.0, 4.0, 6.0);
let l3 = HomogeneousLine::new(0.5, 1.0, 1.5);
let p = HomogeneousPoint::new(1.0, -2.0, 1.0);
assert_eq!(l1.contains(&p), l2.contains(&p));
assert_eq!(l1.contains(&p), l3.contains(&p));
}
}