use crate::error::{SpatialError, SpatialResult};
#[derive(Debug, Clone)]
pub struct LineArrangement {
pub lines: Vec<(f64, f64, f64)>,
pub vertices: Vec<(f64, f64)>,
pub edges: Vec<(usize, usize)>,
pub faces: Vec<Vec<usize>>,
edge_line: Vec<usize>,
}
impl LineArrangement {
pub fn build(lines: &[(f64, f64, f64)]) -> SpatialResult<Self> {
if lines.is_empty() {
return Err(SpatialError::InvalidInput("No lines provided".into()));
}
let mut arr = LineArrangement {
lines: Vec::new(),
vertices: Vec::new(),
edges: Vec::new(),
faces: Vec::new(),
edge_line: Vec::new(),
};
for &line in lines {
arr.add_line(line);
}
Ok(arr)
}
pub fn add_line(&mut self, line: (f64, f64, f64)) {
let new_idx = self.lines.len();
self.lines.push(line);
let mut new_vertices: Vec<(f64, f64)> = Vec::new();
for &existing in &self.lines[..new_idx] {
if let Some(pt) = line_line_intersection(existing, line) {
let dup = new_vertices
.iter()
.any(|v| (v.0 - pt.0).abs() < 1e-10 && (v.1 - pt.1).abs() < 1e-10);
if !dup {
new_vertices.push(pt);
}
}
}
let base_verts = self.vertices.len();
self.vertices.extend_from_slice(&new_vertices);
let (a, b, _c) = line;
let dir = [-b, a]; let mut sorted_pts: Vec<usize> = (base_verts..self.vertices.len()).collect();
sorted_pts.sort_by(|&i, &j| {
let pi = self.vertices[i];
let pj = self.vertices[j];
let ti = pi.0 * dir[0] + pi.1 * dir[1];
let tj = pj.0 * dir[0] + pj.1 * dir[1];
ti.partial_cmp(&tj).unwrap_or(std::cmp::Ordering::Equal)
});
if sorted_pts.is_empty() {
let ei = self.edges.len();
self.edges.push((usize::MAX, usize::MAX));
self.edge_line.push(new_idx);
if self.faces.is_empty() {
self.faces.push(vec![ei]);
} else {
self.faces[0].push(ei);
}
} else {
let e0 = self.edges.len();
self.edges.push((usize::MAX, sorted_pts[0]));
self.edge_line.push(new_idx);
for w in sorted_pts.windows(2) {
let ei = self.edges.len();
self.edges.push((w[0], w[1]));
self.edge_line.push(new_idx);
let _ = ei;
}
let last = *sorted_pts.last().expect("non-empty");
self.edges.push((last, usize::MAX));
self.edge_line.push(new_idx);
if self.faces.is_empty() {
self.faces.push(Vec::new());
}
let new_edge_start = e0;
let new_edge_end = self.edges.len();
for ei in new_edge_start..new_edge_end {
self.faces[0].push(ei);
}
}
self.rebuild_faces();
}
fn rebuild_faces(&mut self) {
let n = self.lines.len();
if n <= 1 {
return;
}
let mut faces: Vec<Vec<usize>> = Vec::new();
let outer: Vec<usize> = self
.edges
.iter()
.enumerate()
.filter(|(_, e)| e.0 == usize::MAX || e.1 == usize::MAX)
.map(|(i, _)| i)
.collect();
faces.push(outer);
for i in 0..n {
for j in (i + 1)..n {
if line_line_intersection(self.lines[i], self.lines[j]).is_some() {
let bounded: Vec<usize> = self
.edges
.iter()
.enumerate()
.filter(|(ei, e)| {
(self.edge_line[*ei] == i || self.edge_line[*ei] == j)
&& e.0 != usize::MAX
&& e.1 != usize::MAX
})
.map(|(i, _)| i)
.collect();
if !bounded.is_empty() {
faces.push(bounded);
}
}
}
}
self.faces = faces;
}
pub fn zone_theorem_count(&self, new_line: (f64, f64, f64)) -> usize {
let (a, b, c) = new_line;
let eval = |idx: usize| -> f64 {
if idx == usize::MAX {
return 0.0; }
let (px, py) = self.vertices[idx];
a * px + b * py + c
};
self.edges
.iter()
.filter(|&&(v1, v2)| {
let s1 = eval(v1);
let s2 = eval(v2);
(s1 <= 1e-10 && s2 >= -1e-10) || (s1 >= -1e-10 && s2 <= 1e-10)
})
.count()
}
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
pub fn num_edges(&self) -> usize {
self.edges.len()
}
pub fn num_faces(&self) -> usize {
self.faces.len()
}
}
pub fn line_line_intersection(l1: (f64, f64, f64), l2: (f64, f64, f64)) -> Option<(f64, f64)> {
let (a1, b1, c1) = l1;
let (a2, b2, c2) = l2;
let det = a1 * b2 - a2 * b1;
if det.abs() < 1e-12 {
return None;
}
let x = (b1 * c2 - b2 * c1) / det;
let y = (a2 * c1 - a1 * c2) / det;
Some((x, y))
}
pub fn duality_transform_points_to_lines(points: &[(f64, f64)]) -> Vec<(f64, f64, f64)> {
points
.iter()
.map(|&(a, b)| (a, -1.0, -b))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_line_intersection() {
let l1 = (1.0_f64, -1.0, 0.0);
let l2 = (1.0_f64, 1.0, -2.0);
let pt = line_line_intersection(l1, l2).expect("should intersect");
assert!((pt.0 - 1.0).abs() < 1e-9, "x={}", pt.0);
assert!((pt.1 - 1.0).abs() < 1e-9, "y={}", pt.1);
}
#[test]
fn test_parallel_lines() {
let l1 = (1.0_f64, 0.0, 0.0);
let l2 = (1.0_f64, 0.0, -2.0);
assert!(line_line_intersection(l1, l2).is_none());
}
#[test]
fn test_arrangement_two_lines() {
let lines = vec![(1.0_f64, -1.0, 0.0), (1.0, 1.0, -2.0)];
let arr = LineArrangement::build(&lines).expect("ok");
assert_eq!(arr.num_vertices(), 1);
assert!(arr.num_edges() >= 4); }
#[test]
fn test_arrangement_three_lines() {
let lines = vec![
(1.0_f64, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, -1.0), ];
let arr = LineArrangement::build(&lines).expect("ok");
assert!(arr.num_vertices() >= 3);
}
#[test]
fn test_zone_theorem_count() {
let lines = vec![(1.0_f64, 0.0, 0.0), (0.0, 1.0, 0.0)];
let arr = LineArrangement::build(&lines).expect("ok");
let new_line = (1.0_f64, 1.0, -2.0);
let cnt = arr.zone_theorem_count(new_line);
assert!(cnt > 0);
}
#[test]
fn test_duality() {
let pts = vec![(2.0_f64, 3.0), (0.0, 1.0)];
let lines = duality_transform_points_to_lines(&pts);
assert_eq!(lines.len(), 2);
let (a, b, c) = lines[0];
assert!((a - 2.0).abs() < 1e-9);
assert!((b - (-1.0)).abs() < 1e-9);
assert!((c - (-3.0)).abs() < 1e-9);
}
#[test]
fn test_arrangement_empty_error() {
let result = LineArrangement::build(&[]);
assert!(result.is_err());
}
}