use crate::bounds::BoundingBox;
use crate::bounds::box_side;
use crate::cell::Cell;
#[derive(Default, Clone)]
pub struct Cell2DScratch {
vertices: Vec<f64>,
neighbors: Vec<i32>,
dists: Vec<f64>,
}
#[derive(Clone)]
pub struct Cell2D {
pub(crate) id: usize,
pub(crate) vertices: Vec<f64>,
pub(crate) edge_neighbors: Vec<i32>,
}
impl Cell2D {
pub fn new(id: usize, bounds: BoundingBox<2>) -> Cell2D {
let vertices = vec![
bounds.min[0], bounds.min[1], bounds.max[0], bounds.min[1], bounds.max[0], bounds.max[1], bounds.min[0], bounds.max[1], ];
let edge_neighbors = vec![
box_side(1, false), box_side(0, true), box_side(1, true), box_side(0, false), ];
Cell2D {
id,
vertices,
edge_neighbors,
}
}
pub fn id(&self) -> usize {
self.id
}
pub fn vertices(&self) -> Vec<f64> {
self.vertices.clone()
}
pub fn edge_neighbors(&self) -> Vec<i32> {
self.edge_neighbors.clone()
}
pub fn area(&self) -> f64 {
let n = self.vertices.len() / 2;
if n < 3 { return 0.0; }
let mut area = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let xi = self.vertices[i * 2];
let yi = self.vertices[i * 2 + 1];
let xj = self.vertices[j * 2];
let yj = self.vertices[j * 2 + 1];
area += xi * yj - xj * yi;
}
(area * 0.5).abs()
}
pub fn centroid(&self) -> [f64; 2] {
let n = self.vertices.len() / 2;
if n < 3 { return [0.0, 0.0]; }
let mut cx = 0.0;
let mut cy = 0.0;
let mut area = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let xi = self.vertices[i * 2];
let yi = self.vertices[i * 2 + 1];
let xj = self.vertices[j * 2];
let yj = self.vertices[j * 2 + 1];
let cross = xi * yj - xj * yi;
area += cross;
cx += (xi + xj) * cross;
cy += (yi + yj) * cross;
}
if area.abs() < 1e-9 {
return [0.0, 0.0];
}
let factor = 1.0 / (3.0 * area);
[cx * factor, cy * factor]
}
fn clip_with_scratch(&mut self, point: &[f64; 2], normal: &[f64; 2], neighbor_id: i32, scratch: &mut Cell2DScratch, generator: Option<&[f64; 2]>) -> (bool, f64) {
let px = point[0];
let py = point[1];
let nx = normal[0];
let ny = normal[1];
let num_verts = self.vertices.len() / 2;
if num_verts < 3 { return (false, 0.0); }
scratch.dists.clear();
scratch.dists.reserve(num_verts);
let mut all_inside = true;
let mut all_outside = true;
for i in 0..num_verts {
let vx = self.vertices[i * 2];
let vy = self.vertices[i * 2 + 1];
let d = (vx - px) * nx + (vy - py) * ny;
scratch.dists.push(d);
if d > 1e-9 {
all_inside = false;
} else if d < -1e-9 {
all_outside = false;
}
}
if all_inside { return (false, 0.0); }
if all_outside {
self.vertices.clear();
self.edge_neighbors.clear();
return (true, 0.0);
}
scratch.vertices.clear();
scratch.neighbors.clear();
let mut max_d2 = 0.0;
for i in 0..num_verts {
let j = (i + 1) % num_verts;
let d_i = scratch.dists[i];
let d_j = scratch.dists[j];
let neighbor = self.edge_neighbors[i];
if d_i <= 1e-9 {
scratch.vertices.push(self.vertices[i * 2]);
scratch.vertices.push(self.vertices[i * 2 + 1]);
if let Some(g) = generator {
let dx = self.vertices[i * 2] - g[0];
let dy = self.vertices[i * 2 + 1] - g[1];
let d2 = dx * dx + dy * dy;
if d2 > max_d2 { max_d2 = d2; }
}
if d_j <= 1e-9 {
scratch.neighbors.push(neighbor);
} else {
let t = d_i / (d_i - d_j);
let xi = self.vertices[i * 2];
let yi = self.vertices[i * 2 + 1];
let xj = self.vertices[j * 2];
let yj = self.vertices[j * 2 + 1];
let ix = xi + t * (xj - xi);
let iy = yi + t * (yj - yi);
scratch.neighbors.push(neighbor);
scratch.vertices.push(ix);
scratch.vertices.push(iy);
if let Some(g) = generator {
let dx = ix - g[0];
let dy = iy - g[1];
let d2 = dx * dx + dy * dy;
if d2 > max_d2 { max_d2 = d2; }
}
scratch.neighbors.push(neighbor_id);
}
} else {
if d_j <= 1e-9 {
let t = d_i / (d_i - d_j);
let xi = self.vertices[i * 2];
let yi = self.vertices[i * 2 + 1];
let xj = self.vertices[j * 2];
let yj = self.vertices[j * 2 + 1];
let ix = xi + t * (xj - xi);
let iy = yi + t * (yj - yi);
scratch.vertices.push(ix);
scratch.vertices.push(iy);
if let Some(g) = generator {
let dx = ix - g[0];
let dy = iy - g[1];
let d2 = dx * dx + dy * dy;
if d2 > max_d2 { max_d2 = d2; }
}
scratch.neighbors.push(neighbor);
}
}
}
std::mem::swap(&mut self.vertices, &mut scratch.vertices);
std::mem::swap(&mut self.edge_neighbors, &mut scratch.neighbors);
(true, max_d2)
}
}
impl Cell<2> for Cell2D {
type Scratch = Cell2DScratch;
fn new(id: usize, bounds: BoundingBox<2>) -> Self {
Cell2D::new(id, bounds)
}
fn clip(
&mut self,
point: &[f64; 2],
normal: &[f64; 2],
neighbor_id: i32,
scratch: &mut Self::Scratch,
generator: Option<&[f64; 2]>,
) -> (bool, f64) {
self.clip_with_scratch(point, normal, neighbor_id, scratch, generator)
}
fn max_radius_sq(&self, center: &[f64; 2]) -> f64 {
let gx = center[0];
let gy = center[1];
let mut max_d2 = 0.0;
for k in 0..self.vertices.len() / 2 {
let dx = self.vertices[k * 2] - gx;
let dy = self.vertices[k * 2 + 1] - gy;
let d2 = dx * dx + dy * dy;
if d2 > max_d2 {
max_d2 = d2;
}
}
max_d2
}
fn centroid(&self) -> [f64; 2] {
self.centroid()
}
fn is_empty(&self) -> bool {
self.vertices.is_empty()
}
fn neighbors(&self) -> &[i32] {
&self.edge_neighbors
}
fn shared_vertices(&self, neighbor_a: i32, neighbor_b: i32) -> Vec<f64> {
let mut result = Vec::new();
let n = self.vertices.len() / 2;
if n < 3 { return result; }
for i in 0..n {
let prev_edge = if i == 0 { n - 1 } else { i - 1 };
let n1 = self.edge_neighbors[prev_edge];
let n2 = self.edge_neighbors[i];
if (n1 == neighbor_a && n2 == neighbor_b) || (n1 == neighbor_b && n2 == neighbor_a) {
result.push(self.vertices[i * 2]);
result.push(self.vertices[i * 2 + 1]);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cell2d_box() {
let bounds = BoundingBox::new([0.0, 0.0], [1.0, 1.0]);
let cell = Cell2D::new(0, bounds);
assert!((cell.area() - 1.0).abs() < 1e-6);
let c = cell.centroid();
assert!((c[0] - 0.5).abs() < 1e-6);
assert!((c[1] - 0.5).abs() < 1e-6);
}
#[test]
fn test_cell2d_clip() {
let bounds = BoundingBox::new([0.0, 0.0], [1.0, 1.0]);
let mut cell = Cell2D::new(0, bounds);
let mut scratch = Cell2DScratch::default();
cell.clip_with_scratch(&[0.5, 0.5], &[1.0, 0.0], 10, &mut scratch, None);
assert!((cell.area() - 0.5).abs() < 1e-6);
let c = cell.centroid();
assert!((c[0] - 0.25).abs() < 1e-6);
}
}