use super::{orientation, GeometryError, GeometryResult, Point2D, GEOMETRY_EPSILON};
pub fn polygon_signed_area(vertices: &[Point2D]) -> f64 {
if vertices.len() < 3 {
return 0.0;
}
let n = vertices.len();
let mut area = 0.0;
for i in 0..n {
let j = (i + 1) % n;
area += vertices[i].x * vertices[j].y;
area -= vertices[j].x * vertices[i].y;
}
area / 2.0
}
pub fn polygon_area(vertices: &[Point2D]) -> f64 {
polygon_signed_area(vertices).abs()
}
pub fn polygon_centroid(vertices: &[Point2D]) -> GeometryResult<Point2D> {
if vertices.len() < 3 {
return Err(GeometryError::InsufficientPoints {
needed: 3,
got: vertices.len(),
});
}
let signed_area = polygon_signed_area(vertices);
if signed_area.abs() < GEOMETRY_EPSILON {
return Err(GeometryError::DegenerateGeometry(
"Polygon has zero area, centroid is undefined".to_string(),
));
}
let n = vertices.len();
let mut cx = 0.0;
let mut cy = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let cross = vertices[i].x * vertices[j].y - vertices[j].x * vertices[i].y;
cx += (vertices[i].x + vertices[j].x) * cross;
cy += (vertices[i].y + vertices[j].y) * cross;
}
let factor = 1.0 / (6.0 * signed_area);
Ok(Point2D::new(cx * factor, cy * factor))
}
pub fn polygon_perimeter(vertices: &[Point2D]) -> f64 {
if vertices.len() < 2 {
return 0.0;
}
let n = vertices.len();
let mut perimeter = 0.0;
for i in 0..n {
let j = (i + 1) % n;
perimeter += vertices[i].distance(&vertices[j]);
}
perimeter
}
pub fn point_in_polygon(point: &Point2D, vertices: &[Point2D]) -> bool {
if vertices.len() < 3 {
return false;
}
let n = vertices.len();
let mut inside = false;
let mut j = n - 1;
for i in 0..n {
let vi = &vertices[i];
let vj = &vertices[j];
if ((vi.y > point.y) != (vj.y > point.y))
&& (point.x < (vj.x - vi.x) * (point.y - vi.y) / (vj.y - vi.y) + vi.x)
{
inside = !inside;
}
j = i;
}
inside
}
pub fn polygon_is_convex(vertices: &[Point2D]) -> bool {
if vertices.len() < 3 {
return false;
}
let n = vertices.len();
let mut positive_cross = false;
let mut negative_cross = false;
for i in 0..n {
let a = &vertices[i];
let b = &vertices[(i + 1) % n];
let c = &vertices[(i + 2) % n];
let cross = orientation(a, b, c);
if cross > GEOMETRY_EPSILON {
positive_cross = true;
} else if cross < -GEOMETRY_EPSILON {
negative_cross = true;
}
if positive_cross && negative_cross {
return false;
}
}
true
}
#[derive(Debug, Clone)]
pub enum IntersectionResult {
None,
Point(Point2D),
Collinear,
}
pub fn line_segment_intersection(
p1: &Point2D,
p2: &Point2D,
p3: &Point2D,
p4: &Point2D,
) -> IntersectionResult {
let d1 = p2.sub(p1);
let d2 = p4.sub(p3);
let denom = d1.x * d2.y - d1.y * d2.x;
if denom.abs() < GEOMETRY_EPSILON {
let d3 = p3.sub(p1);
let cross = d3.x * d1.y - d3.y * d1.x;
if cross.abs() < GEOMETRY_EPSILON {
let t0 = if d1.x.abs() > GEOMETRY_EPSILON {
(p3.x - p1.x) / d1.x
} else if d1.y.abs() > GEOMETRY_EPSILON {
(p3.y - p1.y) / d1.y
} else {
return IntersectionResult::None;
};
let t1 = if d1.x.abs() > GEOMETRY_EPSILON {
(p4.x - p1.x) / d1.x
} else if d1.y.abs() > GEOMETRY_EPSILON {
(p4.y - p1.y) / d1.y
} else {
return IntersectionResult::None;
};
let (t_min, t_max) = if t0 < t1 { (t0, t1) } else { (t1, t0) };
if t_max < -GEOMETRY_EPSILON || t_min > 1.0 + GEOMETRY_EPSILON {
return IntersectionResult::None;
}
return IntersectionResult::Collinear;
}
return IntersectionResult::None;
}
let d3 = p3.sub(p1);
let t = (d3.x * d2.y - d3.y * d2.x) / denom;
let u = (d3.x * d1.y - d3.y * d1.x) / denom;
if (-GEOMETRY_EPSILON..=1.0 + GEOMETRY_EPSILON).contains(&t)
&& (-GEOMETRY_EPSILON..=1.0 + GEOMETRY_EPSILON).contains(&u)
{
let intersection = Point2D::new(p1.x + t * d1.x, p1.y + t * d1.y);
IntersectionResult::Point(intersection)
} else {
IntersectionResult::None
}
}
pub fn polygon_simplify_rdp(vertices: &[Point2D], epsilon: f64) -> GeometryResult<Vec<Point2D>> {
if epsilon < 0.0 {
return Err(GeometryError::InvalidPolygon(
"Epsilon must be non-negative".to_string(),
));
}
if vertices.len() <= 2 {
return Ok(vertices.to_vec());
}
let n = vertices.len();
let is_closed = vertices[0].distance(&vertices[n - 1]) < GEOMETRY_EPSILON;
if is_closed && n > 3 {
let open = &vertices[..n - 1];
let mut simplified = rdp_recursive(open, epsilon);
if simplified.len() >= 2 {
simplified.push(simplified[0]);
}
return Ok(simplified);
}
Ok(rdp_recursive(vertices, epsilon))
}
fn rdp_recursive(points: &[Point2D], epsilon: f64) -> Vec<Point2D> {
if points.len() <= 2 {
return points.to_vec();
}
let first = &points[0];
let last = &points[points.len() - 1];
let mut max_dist = 0.0;
let mut max_idx = 0;
for (i, point) in points.iter().enumerate().skip(1).take(points.len() - 2) {
let dist = perpendicular_distance(point, first, last);
if dist > max_dist {
max_dist = dist;
max_idx = i;
}
}
if max_dist > epsilon {
let mut left = rdp_recursive(&points[..=max_idx], epsilon);
let right = rdp_recursive(&points[max_idx..], epsilon);
left.pop();
left.extend(right);
left
} else {
vec![*first, *last]
}
}
fn perpendicular_distance(point: &Point2D, line_start: &Point2D, line_end: &Point2D) -> f64 {
let dx = line_end.x - line_start.x;
let dy = line_end.y - line_start.y;
let line_len_sq = dx * dx + dy * dy;
if line_len_sq < GEOMETRY_EPSILON * GEOMETRY_EPSILON {
return point.distance(line_start);
}
let area = ((point.x - line_start.x) * dy - (point.y - line_start.y) * dx).abs();
area / line_len_sq.sqrt()
}
pub fn polygon_clipping_sutherland_hodgman(
subject: &[Point2D],
clip: &[Point2D],
) -> GeometryResult<Vec<Point2D>> {
if subject.len() < 3 {
return Err(GeometryError::InsufficientPoints {
needed: 3,
got: subject.len(),
});
}
if clip.len() < 3 {
return Err(GeometryError::InsufficientPoints {
needed: 3,
got: clip.len(),
});
}
let mut output = subject.to_vec();
let clip_n = clip.len();
for i in 0..clip_n {
if output.is_empty() {
break;
}
let edge_start = &clip[i];
let edge_end = &clip[(i + 1) % clip_n];
let input = output;
output = Vec::new();
let n = input.len();
for j in 0..n {
let current = &input[j];
let previous = &input[(j + n - 1) % n];
let current_inside = is_inside_edge(current, edge_start, edge_end);
let previous_inside = is_inside_edge(previous, edge_start, edge_end);
if current_inside {
if !previous_inside {
if let Some(intersection) =
line_line_intersection(previous, current, edge_start, edge_end)
{
output.push(intersection);
}
}
output.push(*current);
} else if previous_inside {
if let Some(intersection) =
line_line_intersection(previous, current, edge_start, edge_end)
{
output.push(intersection);
}
}
}
}
Ok(output)
}
fn is_inside_edge(point: &Point2D, edge_start: &Point2D, edge_end: &Point2D) -> bool {
orientation(edge_start, edge_end, point) >= -GEOMETRY_EPSILON
}
fn line_line_intersection(
p1: &Point2D,
p2: &Point2D,
p3: &Point2D,
p4: &Point2D,
) -> Option<Point2D> {
let d1x = p2.x - p1.x;
let d1y = p2.y - p1.y;
let d2x = p4.x - p3.x;
let d2y = p4.y - p3.y;
let denom = d1x * d2y - d1y * d2x;
if denom.abs() < GEOMETRY_EPSILON {
return None; }
let t = ((p3.x - p1.x) * d2y - (p3.y - p1.y) * d2x) / denom;
Some(Point2D::new(p1.x + t * d1x, p1.y + t * d1y))
}
pub fn minimum_bounding_rectangle(points: &[Point2D]) -> GeometryResult<(Point2D, Point2D)> {
if points.is_empty() {
return Err(GeometryError::InsufficientPoints { needed: 1, got: 0 });
}
let mut min_x = points[0].x;
let mut min_y = points[0].y;
let mut max_x = points[0].x;
let mut max_y = points[0].y;
for p in points.iter().skip(1) {
if p.x < min_x {
min_x = p.x;
}
if p.y < min_y {
min_y = p.y;
}
if p.x > max_x {
max_x = p.x;
}
if p.y > max_y {
max_y = p.y;
}
}
Ok((Point2D::new(min_x, min_y), Point2D::new(max_x, max_y)))
}
pub fn convex_polygons_intersect(poly_a: &[Point2D], poly_b: &[Point2D]) -> bool {
if poly_a.len() < 3 || poly_b.len() < 3 {
return false;
}
if has_separating_axis(poly_a, poly_b) {
return false;
}
if has_separating_axis(poly_b, poly_a) {
return false;
}
true
}
fn has_separating_axis(source: &[Point2D], target: &[Point2D]) -> bool {
let n = source.len();
for i in 0..n {
let j = (i + 1) % n;
let edge = source[j].sub(&source[i]);
let axis = Point2D::new(-edge.y, edge.x);
let (src_min, src_max) = project_polygon(&axis, source);
let (tgt_min, tgt_max) = project_polygon(&axis, target);
if src_max < tgt_min - GEOMETRY_EPSILON || tgt_max < src_min - GEOMETRY_EPSILON {
return true;
}
}
false
}
fn project_polygon(axis: &Point2D, vertices: &[Point2D]) -> (f64, f64) {
let first_proj = axis.dot(&vertices[0]);
let mut min_proj = first_proj;
let mut max_proj = first_proj;
for v in vertices.iter().skip(1) {
let proj = axis.dot(v);
if proj < min_proj {
min_proj = proj;
}
if proj > max_proj {
max_proj = proj;
}
}
(min_proj, max_proj)
}
pub fn convex_polygon_union(
poly_a: &[Point2D],
poly_b: &[Point2D],
) -> GeometryResult<Vec<Point2D>> {
let mut all_points = Vec::with_capacity(poly_a.len() + poly_b.len());
all_points.extend_from_slice(poly_a);
all_points.extend_from_slice(poly_b);
if all_points.len() < 3 {
return Err(GeometryError::InsufficientPoints {
needed: 3,
got: all_points.len(),
});
}
super::convex_hull::graham_scan(&all_points)
}
pub fn polygon_is_simple(vertices: &[Point2D]) -> bool {
let n = vertices.len();
if n < 3 {
return false;
}
for i in 0..n {
let i_next = (i + 1) % n;
for j in (i + 2)..n {
let j_next = (j + 1) % n;
if j_next == i {
continue;
}
let result = line_segment_intersection(
&vertices[i],
&vertices[i_next],
&vertices[j],
&vertices[j_next],
);
match result {
IntersectionResult::Point(_) | IntersectionResult::Collinear => return false,
IntersectionResult::None => {}
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polygon_area_rectangle() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(4.0, 3.0),
Point2D::new(0.0, 3.0),
];
assert!((polygon_area(&vertices) - 12.0).abs() < 1e-10);
}
#[test]
fn test_polygon_area_triangle() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(2.0, 3.0),
];
assert!((polygon_area(&vertices) - 6.0).abs() < 1e-10);
}
#[test]
fn test_polygon_signed_area_ccw() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(1.0, 1.0),
Point2D::new(0.0, 1.0),
];
assert!(polygon_signed_area(&vertices) > 0.0);
}
#[test]
fn test_polygon_signed_area_cw() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(0.0, 1.0),
Point2D::new(1.0, 1.0),
Point2D::new(1.0, 0.0),
];
assert!(polygon_signed_area(&vertices) < 0.0);
}
#[test]
fn test_polygon_centroid_square() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(4.0, 4.0),
Point2D::new(0.0, 4.0),
];
let centroid = polygon_centroid(&vertices).expect("centroid should succeed");
assert!((centroid.x - 2.0).abs() < 1e-10);
assert!((centroid.y - 2.0).abs() < 1e-10);
}
#[test]
fn test_polygon_centroid_triangle() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(6.0, 0.0),
Point2D::new(0.0, 6.0),
];
let centroid = polygon_centroid(&vertices).expect("centroid should succeed");
assert!((centroid.x - 2.0).abs() < 1e-10);
assert!((centroid.y - 2.0).abs() < 1e-10);
}
#[test]
fn test_polygon_perimeter() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(3.0, 0.0),
Point2D::new(3.0, 4.0),
Point2D::new(0.0, 4.0),
];
assert!((polygon_perimeter(&vertices) - 14.0).abs() < 1e-10);
}
#[test]
fn test_point_in_polygon_square() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(4.0, 4.0),
Point2D::new(0.0, 4.0),
];
assert!(point_in_polygon(&Point2D::new(2.0, 2.0), &vertices));
assert!(point_in_polygon(&Point2D::new(0.1, 0.1), &vertices));
assert!(point_in_polygon(&Point2D::new(3.9, 3.9), &vertices));
assert!(!point_in_polygon(&Point2D::new(5.0, 2.0), &vertices));
assert!(!point_in_polygon(&Point2D::new(-1.0, 2.0), &vertices));
assert!(!point_in_polygon(&Point2D::new(2.0, -1.0), &vertices));
assert!(!point_in_polygon(&Point2D::new(2.0, 5.0), &vertices));
}
#[test]
fn test_point_in_polygon_triangle() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(4.0, 0.0),
Point2D::new(2.0, 4.0),
];
assert!(point_in_polygon(&Point2D::new(2.0, 1.0), &vertices));
assert!(!point_in_polygon(&Point2D::new(0.0, 4.0), &vertices));
}
#[test]
fn test_polygon_is_convex() {
let square = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(1.0, 1.0),
Point2D::new(0.0, 1.0),
];
assert!(polygon_is_convex(&square));
let l_shape = vec![
Point2D::new(0.0, 0.0),
Point2D::new(2.0, 0.0),
Point2D::new(2.0, 1.0),
Point2D::new(1.0, 1.0),
Point2D::new(1.0, 2.0),
Point2D::new(0.0, 2.0),
];
assert!(!polygon_is_convex(&l_shape));
}
#[test]
fn test_line_segment_intersection_crossing() {
let p1 = Point2D::new(0.0, 0.0);
let p2 = Point2D::new(2.0, 2.0);
let p3 = Point2D::new(0.0, 2.0);
let p4 = Point2D::new(2.0, 0.0);
match line_segment_intersection(&p1, &p2, &p3, &p4) {
IntersectionResult::Point(p) => {
assert!((p.x - 1.0).abs() < 1e-10);
assert!((p.y - 1.0).abs() < 1e-10);
}
_ => panic!("Expected point intersection"),
}
}
#[test]
fn test_line_segment_intersection_no_crossing() {
let p1 = Point2D::new(0.0, 0.0);
let p2 = Point2D::new(1.0, 0.0);
let p3 = Point2D::new(2.0, 1.0);
let p4 = Point2D::new(3.0, 1.0);
match line_segment_intersection(&p1, &p2, &p3, &p4) {
IntersectionResult::None => {}
_ => panic!("Expected no intersection"),
}
}
#[test]
fn test_line_segment_intersection_parallel() {
let p1 = Point2D::new(0.0, 0.0);
let p2 = Point2D::new(1.0, 0.0);
let p3 = Point2D::new(0.0, 1.0);
let p4 = Point2D::new(1.0, 1.0);
match line_segment_intersection(&p1, &p2, &p3, &p4) {
IntersectionResult::None => {}
_ => panic!("Expected no intersection for parallel segments"),
}
}
#[test]
fn test_line_segment_intersection_collinear_overlapping() {
let p1 = Point2D::new(0.0, 0.0);
let p2 = Point2D::new(2.0, 0.0);
let p3 = Point2D::new(1.0, 0.0);
let p4 = Point2D::new(3.0, 0.0);
match line_segment_intersection(&p1, &p2, &p3, &p4) {
IntersectionResult::Collinear => {}
_ => panic!("Expected collinear intersection"),
}
}
#[test]
fn test_polygon_simplify_rdp_simple() {
let triangle = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(0.5, 1.0),
];
let simplified =
polygon_simplify_rdp(&triangle, 0.1).expect("simplification should succeed");
assert!(simplified.len() <= 3);
}
#[test]
fn test_polygon_simplify_rdp_collinear() {
let line = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(2.0, 0.0),
Point2D::new(3.0, 0.0),
Point2D::new(4.0, 0.0),
];
let simplified = polygon_simplify_rdp(&line, 0.1).expect("simplification should succeed");
assert_eq!(simplified.len(), 2);
assert!((simplified[0].x - 0.0).abs() < 1e-10);
assert!((simplified[1].x - 4.0).abs() < 1e-10);
}
#[test]
fn test_polygon_simplify_rdp_nearly_straight() {
let points = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.01),
Point2D::new(2.0, -0.01),
Point2D::new(3.0, 0.02),
Point2D::new(4.0, 0.0),
];
let simplified = polygon_simplify_rdp(&points, 0.1).expect("simplification should succeed");
assert_eq!(simplified.len(), 2);
let detailed = polygon_simplify_rdp(&points, 0.005).expect("simplification should succeed");
assert!(detailed.len() > 2);
}
#[test]
fn test_polygon_simplify_rdp_negative_epsilon() {
let points = vec![Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)];
assert!(polygon_simplify_rdp(&points, -0.1).is_err());
}
#[test]
fn test_polygon_clipping_sutherland_hodgman() {
let subject = vec![
Point2D::new(1.0, 0.0),
Point2D::new(2.0, 1.0),
Point2D::new(1.0, 2.0),
Point2D::new(0.0, 1.0),
];
let clip = vec![
Point2D::new(0.5, 0.0),
Point2D::new(1.5, 0.0),
Point2D::new(1.5, 2.0),
Point2D::new(0.5, 2.0),
];
let result =
polygon_clipping_sutherland_hodgman(&subject, &clip).expect("clipping should succeed");
assert!(result.len() >= 3);
let clipped_area = polygon_area(&result);
let subject_area = polygon_area(&subject);
let clip_area = polygon_area(&clip);
assert!(clipped_area <= subject_area + GEOMETRY_EPSILON);
assert!(clipped_area <= clip_area + GEOMETRY_EPSILON);
}
#[test]
fn test_polygon_clipping_fully_inside() {
let subject = vec![
Point2D::new(1.0, 1.0),
Point2D::new(2.0, 1.0),
Point2D::new(2.0, 2.0),
Point2D::new(1.0, 2.0),
];
let clip = vec![
Point2D::new(0.0, 0.0),
Point2D::new(3.0, 0.0),
Point2D::new(3.0, 3.0),
Point2D::new(0.0, 3.0),
];
let result =
polygon_clipping_sutherland_hodgman(&subject, &clip).expect("clipping should succeed");
assert_eq!(result.len(), 4);
let clipped_area = polygon_area(&result);
assert!((clipped_area - 1.0).abs() < 1e-8);
}
#[test]
fn test_minimum_bounding_rectangle() {
let points = vec![
Point2D::new(1.0, 2.0),
Point2D::new(3.0, 5.0),
Point2D::new(-1.0, 0.0),
Point2D::new(4.0, 3.0),
];
let (min_pt, max_pt) =
minimum_bounding_rectangle(&points).expect("bounding rect should succeed");
assert!((min_pt.x - (-1.0)).abs() < 1e-10);
assert!((min_pt.y - 0.0).abs() < 1e-10);
assert!((max_pt.x - 4.0).abs() < 1e-10);
assert!((max_pt.y - 5.0).abs() < 1e-10);
}
#[test]
fn test_minimum_bounding_rectangle_single_point() {
let points = vec![Point2D::new(3.0, 4.0)];
let (min_pt, max_pt) =
minimum_bounding_rectangle(&points).expect("bounding rect should succeed");
assert!((min_pt.x - 3.0).abs() < 1e-10);
assert!((min_pt.y - 4.0).abs() < 1e-10);
assert!((max_pt.x - 3.0).abs() < 1e-10);
assert!((max_pt.y - 4.0).abs() < 1e-10);
}
#[test]
fn test_minimum_bounding_rectangle_empty() {
let points: Vec<Point2D> = vec![];
assert!(minimum_bounding_rectangle(&points).is_err());
}
#[test]
fn test_polygon_area_irregular() {
let n = 5;
let mut vertices = Vec::with_capacity(n);
for i in 0..n {
let angle = 2.0 * std::f64::consts::PI * (i as f64) / (n as f64);
vertices.push(Point2D::new(angle.cos(), angle.sin()));
}
let area = polygon_area(&vertices);
let expected = 2.5 * (2.0 * std::f64::consts::PI / 5.0).sin();
assert!((area - expected).abs() < 1e-8);
}
#[test]
fn test_perpendicular_distance() {
let point = Point2D::new(1.0, 1.0);
let start = Point2D::new(0.0, 0.0);
let end = Point2D::new(2.0, 0.0);
let dist = perpendicular_distance(&point, &start, &end);
assert!((dist - 1.0).abs() < 1e-10);
}
#[test]
fn test_centroid_degenerate() {
let vertices = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(2.0, 0.0),
];
assert!(polygon_centroid(&vertices).is_err());
}
#[test]
fn test_convex_polygons_intersect_overlapping() {
let sq1 = vec![
Point2D::new(0.0, 0.0),
Point2D::new(2.0, 0.0),
Point2D::new(2.0, 2.0),
Point2D::new(0.0, 2.0),
];
let sq2 = vec![
Point2D::new(1.0, 1.0),
Point2D::new(3.0, 1.0),
Point2D::new(3.0, 3.0),
Point2D::new(1.0, 3.0),
];
assert!(convex_polygons_intersect(&sq1, &sq2));
}
#[test]
fn test_convex_polygons_intersect_separated() {
let sq1 = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(1.0, 1.0),
Point2D::new(0.0, 1.0),
];
let sq2 = vec![
Point2D::new(5.0, 5.0),
Point2D::new(6.0, 5.0),
Point2D::new(6.0, 6.0),
Point2D::new(5.0, 6.0),
];
assert!(!convex_polygons_intersect(&sq1, &sq2));
}
#[test]
fn test_convex_polygon_union() {
let sq1 = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(1.0, 1.0),
Point2D::new(0.0, 1.0),
];
let sq2 = vec![
Point2D::new(0.5, 0.0),
Point2D::new(1.5, 0.0),
Point2D::new(1.5, 1.0),
Point2D::new(0.5, 1.0),
];
let union_hull = convex_polygon_union(&sq1, &sq2).expect("union should succeed");
let area = polygon_area(&union_hull);
assert!((area - 1.5).abs() < 1e-8);
}
#[test]
fn test_polygon_is_simple_square() {
let sq = vec![
Point2D::new(0.0, 0.0),
Point2D::new(1.0, 0.0),
Point2D::new(1.0, 1.0),
Point2D::new(0.0, 1.0),
];
assert!(polygon_is_simple(&sq));
}
#[test]
fn test_polygon_is_simple_bowtie() {
let bowtie = vec![
Point2D::new(0.0, 0.0),
Point2D::new(2.0, 2.0),
Point2D::new(2.0, 0.0),
Point2D::new(0.0, 2.0),
];
assert!(!polygon_is_simple(&bowtie));
}
}