use crate::geometry::{Point, Line};
use crate::math::Vector2;
use std::fmt;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Polyline {
pub vertices: Vec<Point>,
pub is_closed: bool,
}
impl Polyline {
#[inline]
pub fn new() -> Self {
Self {
vertices: Vec::new(),
is_closed: false,
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
vertices: Vec::with_capacity(capacity),
is_closed: false,
}
}
#[inline]
pub fn from_points(points: &[Point]) -> Self {
Self {
vertices: points.to_vec(),
is_closed: false,
}
}
#[inline]
pub fn push(&mut self, point: Point) {
self.vertices.push(point);
}
#[inline]
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.vertices.is_empty()
}
#[inline]
pub fn close(&mut self) {
if !self.is_closed && self.vertices.len() >= 3 {
self.is_closed = true;
}
}
#[inline]
pub fn open(&mut self) {
self.is_closed = false;
}
#[inline]
pub fn segment(&self, index: usize) -> Option<Line> {
if self.vertices.len() < 2 {
return None;
}
let next_index = if self.is_closed {
(index + 1) % self.vertices.len()
} else if index + 1 < self.vertices.len() {
index + 1
} else {
return None;
};
Some(Line::new(self.vertices[index], self.vertices[next_index]))
}
#[inline]
pub fn total_length(&self) -> f64 {
if self.vertices.len() < 2 {
return 0.0;
}
let mut length = 0.0;
for i in 0..self.vertices.len() - 1 {
length += self.vertices[i].distance_to(&self.vertices[i + 1]);
}
if self.is_closed && self.vertices.len() >= 3 {
length += self.vertices.last().unwrap().distance_to(&self.vertices[0]);
}
length
}
#[inline]
pub fn bounding_box(&self) -> Option<(Point, Point)> {
if self.vertices.is_empty() {
return None;
}
let mut min_x = self.vertices[0].x;
let mut max_x = self.vertices[0].x;
let mut min_y = self.vertices[0].y;
let mut max_y = self.vertices[0].y;
let mut min_z = self.vertices[0].z;
let mut max_z = self.vertices[0].z;
for vertex in &self.vertices {
min_x = min_x.min(vertex.x);
max_x = max_x.max(vertex.x);
min_y = min_y.min(vertex.y);
max_y = max_y.max(vertex.y);
min_z = min_z.min(vertex.z);
max_z = max_z.max(vertex.z);
}
Some((
Point::new(min_x, min_y, min_z),
Point::new(max_x, max_y, max_z),
))
}
#[inline]
pub fn centroid(&self) -> Option<Point> {
if self.vertices.is_empty() {
return None;
}
let mut cx = 0.0;
let mut cy = 0.0;
let mut cz = 0.0;
for vertex in &self.vertices {
cx += vertex.x;
cy += vertex.y;
cz += vertex.z;
}
let n = self.vertices.len() as f64;
Some(Point::new(cx / n, cy / n, cz / n))
}
}
impl Default for Polyline {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for Polyline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Polyline(vertices: {}, is_closed: {})",
self.vertex_count(),
self.is_closed
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polyline_creation() {
let polyline = Polyline::new();
assert!(polyline.is_empty());
assert_eq!(polyline.vertex_count(), 0);
}
#[test]
fn test_polyline_push() {
let mut polyline = Polyline::new();
polyline.push(Point::new(0.0, 0.0, 0.0));
polyline.push(Point::new(1.0, 0.0, 0.0));
polyline.push(Point::new(1.0, 1.0, 0.0));
assert_eq!(polyline.vertex_count(), 3);
}
#[test]
fn test_polyline_length() {
let mut polyline = Polyline::new();
polyline.push(Point::new(0.0, 0.0, 0.0));
polyline.push(Point::new(3.0, 0.0, 0.0));
polyline.push(Point::new(3.0, 4.0, 0.0));
assert!((polyline.total_length() - 7.0).abs() < 1e-10);
}
#[test]
fn test_polyline_close() {
let mut polyline = Polyline::new();
polyline.push(Point::new(0.0, 0.0, 0.0));
polyline.push(Point::new(1.0, 0.0, 0.0));
polyline.push(Point::new(1.0, 1.0, 0.0));
polyline.close();
assert!(polyline.is_closed);
}
}