use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector3};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Polyline3DFlags {
pub closed: bool,
pub spline_fit: bool,
pub is_3d: bool,
pub is_3d_mesh: bool,
pub mesh_closed_n: bool,
pub is_polyface_mesh: bool,
pub linetype_continuous: bool,
}
impl Polyline3DFlags {
pub fn polyline_3d() -> Self {
Self {
is_3d: true,
..Default::default()
}
}
pub fn from_bits(bits: i32) -> Self {
Self {
closed: (bits & 1) != 0,
spline_fit: (bits & 4) != 0,
is_3d: (bits & 8) != 0,
is_3d_mesh: (bits & 16) != 0,
mesh_closed_n: (bits & 32) != 0,
is_polyface_mesh: (bits & 64) != 0,
linetype_continuous: (bits & 128) != 0,
}
}
pub fn to_bits(&self) -> i32 {
let mut bits = 0;
if self.closed { bits |= 1; }
if self.spline_fit { bits |= 4; }
if self.is_3d { bits |= 8; }
if self.is_3d_mesh { bits |= 16; }
if self.mesh_closed_n { bits |= 32; }
if self.is_polyface_mesh { bits |= 64; }
if self.linetype_continuous { bits |= 128; }
bits
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SmoothSurfaceType {
#[default]
None = 0,
QuadraticBSpline = 5,
CubicBSpline = 6,
Bezier = 8,
}
impl SmoothSurfaceType {
pub fn from_value(value: i16) -> Self {
match value {
5 => SmoothSurfaceType::QuadraticBSpline,
6 => SmoothSurfaceType::CubicBSpline,
8 => SmoothSurfaceType::Bezier,
_ => SmoothSurfaceType::None,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vertex3DPolyline {
pub handle: Handle,
pub layer: String,
pub position: Vector3,
pub flags: i32,
}
impl Vertex3DPolyline {
pub fn new(position: Vector3) -> Self {
Self {
handle: Handle::NULL,
layer: "0".to_string(),
position,
flags: 32, }
}
pub fn from_xyz(x: f64, y: f64, z: f64) -> Self {
Self::new(Vector3::new(x, y, z))
}
}
impl Default for Vertex3DPolyline {
fn default() -> Self {
Self::new(Vector3::ZERO)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Polyline3D {
pub common: EntityCommon,
pub flags: Polyline3DFlags,
pub smooth_type: SmoothSurfaceType,
pub default_start_width: f64,
pub default_end_width: f64,
pub mesh_m_count: u16,
pub mesh_n_count: u16,
pub smooth_m_density: u16,
pub smooth_n_density: u16,
pub elevation: f64,
pub normal: Vector3,
pub vertices: Vec<Vertex3DPolyline>,
}
impl Polyline3D {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
flags: Polyline3DFlags::polyline_3d(),
smooth_type: SmoothSurfaceType::None,
default_start_width: 0.0,
default_end_width: 0.0,
mesh_m_count: 0,
mesh_n_count: 0,
smooth_m_density: 0,
smooth_n_density: 0,
elevation: 0.0,
normal: Vector3::UNIT_Z,
vertices: Vec::new(),
}
}
pub fn from_points(points: Vec<Vector3>) -> Self {
let mut polyline = Self::new();
for point in points {
polyline.add_vertex(point);
}
polyline
}
pub fn add_vertex(&mut self, position: Vector3) {
self.vertices.push(Vertex3DPolyline::new(position));
}
pub fn add_vertex_full(&mut self, vertex: Vertex3DPolyline) {
self.vertices.push(vertex);
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn is_closed(&self) -> bool {
self.flags.closed
}
pub fn close(&mut self) {
self.flags.closed = true;
}
pub fn open(&mut self) {
self.flags.closed = false;
}
pub fn get_vertex(&self, index: usize) -> Option<&Vertex3DPolyline> {
self.vertices.get(index)
}
pub fn get_vertex_mut(&mut self, index: usize) -> Option<&mut Vertex3DPolyline> {
self.vertices.get_mut(index)
}
pub fn length(&self) -> f64 {
if self.vertices.len() < 2 {
return 0.0;
}
let mut total = 0.0;
for i in 0..self.vertices.len() - 1 {
total += self.vertices[i].position.distance(&self.vertices[i + 1].position);
}
if self.is_closed() && self.vertices.len() >= 2 {
total += self.vertices.last().unwrap().position
.distance(&self.vertices.first().unwrap().position);
}
total
}
pub fn centroid(&self) -> Vector3 {
if self.vertices.is_empty() {
return Vector3::ZERO;
}
let sum: Vector3 = self.vertices.iter()
.fold(Vector3::ZERO, |acc, v| acc + v.position);
sum * (1.0 / self.vertices.len() as f64)
}
pub fn positions(&self) -> Vec<Vector3> {
self.vertices.iter().map(|v| v.position).collect()
}
pub fn start_point(&self) -> Option<Vector3> {
self.vertices.first().map(|v| v.position)
}
pub fn end_point(&self) -> Option<Vector3> {
self.vertices.last().map(|v| v.position)
}
pub fn reverse(&mut self) {
self.vertices.reverse();
}
pub fn remove_vertex(&mut self, index: usize) -> Option<Vertex3DPolyline> {
if index < self.vertices.len() {
Some(self.vertices.remove(index))
} else {
None
}
}
pub fn insert_vertex(&mut self, index: usize, position: Vector3) {
if index <= self.vertices.len() {
self.vertices.insert(index, Vertex3DPolyline::new(position));
}
}
pub fn clear(&mut self) {
self.vertices.clear();
}
pub fn with_layer(mut self, layer: impl Into<String>) -> Self {
self.common.layer = layer.into();
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.common.color = color;
self
}
pub fn with_closed(mut self) -> Self {
self.flags.closed = true;
self
}
}
impl Default for Polyline3D {
fn default() -> Self {
Self::new()
}
}
impl Entity for Polyline3D {
fn handle(&self) -> Handle {
self.common.handle
}
fn set_handle(&mut self, handle: Handle) {
self.common.handle = handle;
}
fn layer(&self) -> &str {
&self.common.layer
}
fn set_layer(&mut self, layer: String) {
self.common.layer = layer;
}
fn color(&self) -> Color {
self.common.color
}
fn set_color(&mut self, color: Color) {
self.common.color = color;
}
fn line_weight(&self) -> LineWeight {
self.common.line_weight
}
fn set_line_weight(&mut self, weight: LineWeight) {
self.common.line_weight = weight;
}
fn transparency(&self) -> Transparency {
self.common.transparency
}
fn set_transparency(&mut self, transparency: Transparency) {
self.common.transparency = transparency;
}
fn is_invisible(&self) -> bool {
self.common.invisible
}
fn set_invisible(&mut self, invisible: bool) {
self.common.invisible = invisible;
}
fn bounding_box(&self) -> BoundingBox3D {
let positions: Vec<Vector3> = self.vertices.iter().map(|v| v.position).collect();
BoundingBox3D::from_points(&positions).unwrap_or_default()
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_polyline3d(self, offset);
}
fn entity_type(&self) -> &'static str {
"POLYLINE"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_polyline3d(self, transform);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polyline3d_creation() {
let polyline = Polyline3D::new();
assert_eq!(polyline.vertex_count(), 0);
assert!(!polyline.is_closed());
assert!(polyline.flags.is_3d);
}
#[test]
fn test_polyline3d_from_points() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 5.0),
Vector3::new(10.0, 10.0, 10.0),
];
let polyline = Polyline3D::from_points(points);
assert_eq!(polyline.vertex_count(), 3);
}
#[test]
fn test_polyline3d_add_vertex() {
let mut polyline = Polyline3D::new();
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 5.0));
assert_eq!(polyline.vertex_count(), 2);
}
#[test]
fn test_polyline3d_close() {
let mut polyline = Polyline3D::new();
assert!(!polyline.is_closed());
polyline.close();
assert!(polyline.is_closed());
polyline.open();
assert!(!polyline.is_closed());
}
#[test]
fn test_polyline3d_length() {
let mut polyline = Polyline3D::new();
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 10.0, 0.0));
assert!((polyline.length() - 20.0).abs() < 1e-10);
polyline.close();
let expected = 20.0 + (200.0_f64).sqrt();
assert!((polyline.length() - expected).abs() < 1e-10);
}
#[test]
fn test_polyline3d_centroid() {
let mut polyline = Polyline3D::new();
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 10.0, 0.0));
polyline.add_vertex(Vector3::new(0.0, 10.0, 0.0));
let centroid = polyline.centroid();
assert!((centroid.x - 5.0).abs() < 1e-10);
assert!((centroid.y - 5.0).abs() < 1e-10);
}
#[test]
fn test_polyline3d_reverse() {
let mut polyline = Polyline3D::new();
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(20.0, 0.0, 0.0));
polyline.reverse();
assert_eq!(polyline.vertices[0].position.x, 20.0);
assert_eq!(polyline.vertices[2].position.x, 0.0);
}
#[test]
fn test_polyline3d_translate() {
let mut polyline = Polyline3D::new();
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 0.0));
polyline.translate(Vector3::new(5.0, 5.0, 5.0));
assert_eq!(polyline.vertices[0].position, Vector3::new(5.0, 5.0, 5.0));
assert_eq!(polyline.vertices[1].position, Vector3::new(15.0, 5.0, 5.0));
}
#[test]
fn test_polyline3d_flags() {
let flags = Polyline3DFlags::from_bits(9); assert!(flags.closed);
assert!(flags.is_3d);
assert!(!flags.spline_fit);
assert_eq!(flags.to_bits(), 9);
}
#[test]
fn test_polyline3d_start_end_points() {
let mut polyline = Polyline3D::new();
assert!(polyline.start_point().is_none());
assert!(polyline.end_point().is_none());
polyline.add_vertex(Vector3::new(0.0, 0.0, 0.0));
polyline.add_vertex(Vector3::new(10.0, 0.0, 0.0));
assert_eq!(polyline.start_point(), Some(Vector3::new(0.0, 0.0, 0.0)));
assert_eq!(polyline.end_point(), Some(Vector3::new(10.0, 0.0, 0.0)));
}
}