use anyhow::{Result, ensure};
use nalgebra::{DVector, DVectorView, Unit, Vector3};
use serde::{Deserialize, Serialize};
use vision_calibration_core::Pt3;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LaserPlane {
pub normal: Unit<Vector3<f64>>,
pub distance: f64,
}
impl LaserPlane {
pub fn new(normal: Vector3<f64>, distance: f64) -> Self {
Self {
normal: Unit::new_normalize(normal),
distance,
}
}
pub fn to_dvec(&self) -> DVector<f64> {
DVector::from_vec(vec![
self.normal.x,
self.normal.y,
self.normal.z,
self.distance,
])
}
pub fn normal_to_dvec(&self) -> DVector<f64> {
DVector::from_vec(vec![self.normal.x, self.normal.y, self.normal.z])
}
pub fn distance_to_dvec(&self) -> DVector<f64> {
DVector::from_vec(vec![self.distance])
}
pub fn from_dvec(v: DVectorView<f64>) -> Result<Self> {
ensure!(
v.len() == 4,
"LaserPlane requires 4D vector, got {}",
v.len()
);
let normal = Unit::new_normalize(Vector3::new(v[0], v[1], v[2]));
Ok(Self {
normal,
distance: v[3],
})
}
pub fn from_split_dvec(normal: DVectorView<f64>, distance: DVectorView<f64>) -> Result<Self> {
ensure!(
normal.len() == 3,
"LaserPlane normal requires 3D vector, got {}",
normal.len()
);
ensure!(
distance.len() == 1,
"LaserPlane distance requires 1D vector, got {}",
distance.len()
);
let normal = Unit::new_normalize(Vector3::new(normal[0], normal[1], normal[2]));
Ok(Self {
normal,
distance: distance[0],
})
}
pub fn point_distance(&self, point: &Pt3) -> f64 {
self.normal.dot(&point.coords) + self.distance
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plane_from_normal_distance() {
let normal = Vector3::new(0.0, 0.0, 2.0); let plane = LaserPlane::new(normal, -0.5);
assert!((plane.normal.z - 1.0).abs() < 1e-10);
assert!((plane.normal.x).abs() < 1e-10);
assert!((plane.normal.y).abs() < 1e-10);
assert!((plane.distance + 0.5).abs() < 1e-10);
}
#[test]
fn plane_roundtrip_conversion() {
let plane = LaserPlane::new(Vector3::new(1.0, 0.0, 1.0), -0.3);
let v = plane.to_dvec();
let plane2 = LaserPlane::from_dvec(v.as_view()).unwrap();
assert!((plane.normal.x - plane2.normal.x).abs() < 1e-10);
assert!((plane.normal.y - plane2.normal.y).abs() < 1e-10);
assert!((plane.normal.z - plane2.normal.z).abs() < 1e-10);
assert!((plane.distance - plane2.distance).abs() < 1e-10);
}
#[test]
fn plane_point_distance() {
let plane = LaserPlane::new(Vector3::new(0.0, 0.0, 1.0), -0.5);
let p1 = Pt3::new(1.0, 2.0, 0.5);
assert!(plane.point_distance(&p1).abs() < 1e-10);
let p2 = Pt3::new(1.0, 2.0, 0.7);
assert!((plane.point_distance(&p2) - 0.2).abs() < 1e-10);
let p3 = Pt3::new(1.0, 2.0, 0.3);
assert!((plane.point_distance(&p3) + 0.2).abs() < 1e-10);
}
#[test]
fn plane_from_dvec_wrong_size() {
let v = DVector::from_vec(vec![1.0, 2.0, 3.0]); assert!(LaserPlane::from_dvec(v.as_view()).is_err());
}
#[test]
fn plane_split_roundtrip() {
let plane = LaserPlane::new(Vector3::new(1.0, 2.0, 3.0), -0.4);
let normal = plane.normal_to_dvec();
let distance = plane.distance_to_dvec();
let plane2 = LaserPlane::from_split_dvec(normal.as_view(), distance.as_view()).unwrap();
assert!((plane.normal.x - plane2.normal.x).abs() < 1e-10);
assert!((plane.normal.y - plane2.normal.y).abs() < 1e-10);
assert!((plane.normal.z - plane2.normal.z).abs() < 1e-10);
assert!((plane.distance - plane2.distance).abs() < 1e-10);
}
}