use crate::entities::{Entity, EntityCommon};
use crate::types::{BoundingBox3D, Color, Handle, LineWeight, Transparency, Vector3};
use bitflags::bitflags;
use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i16)]
pub enum MLineJustification {
Top = 0,
#[default]
Zero = 1,
Bottom = 2,
}
impl From<i16> for MLineJustification {
fn from(value: i16) -> Self {
match value {
0 => Self::Top,
1 => Self::Zero,
2 => Self::Bottom,
_ => Self::Zero,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineFlags: i16 {
const HAS_VERTICES = 1;
const CLOSED = 2;
const NO_START_CAPS = 4;
const NO_END_CAPS = 8;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyleFlags: i16 {
const NONE = 0;
const FILL_ON = 1;
const DISPLAY_JOINTS = 2;
const START_SQUARE_CAP = 16;
const START_INNER_ARCS_CAP = 32;
const START_ROUND_CAP = 64;
const END_SQUARE_CAP = 256;
const END_INNER_ARCS_CAP = 512;
const END_ROUND_CAP = 1024;
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyleElement {
pub offset: f64,
pub color: Color,
pub line_type_handle: Option<Handle>,
pub line_type_name: String,
}
impl MLineStyleElement {
pub fn new(offset: f64) -> Self {
Self {
offset,
color: Color::ByLayer,
line_type_handle: None,
line_type_name: "ByLayer".to_string(),
}
}
pub fn with_color(offset: f64, color: Color) -> Self {
let mut elem = Self::new(offset);
elem.color = color;
elem
}
}
impl Default for MLineStyleElement {
fn default() -> Self {
Self::new(0.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyle {
pub handle: Handle,
pub name: String,
pub description: String,
pub flags: MLineStyleFlags,
pub fill_color: Color,
pub start_angle: f64,
pub end_angle: f64,
pub elements: Vec<MLineStyleElement>,
}
impl MLineStyle {
pub fn new(name: &str) -> Self {
Self {
handle: Handle::NULL,
name: name.to_string(),
description: String::new(),
flags: MLineStyleFlags::NONE,
fill_color: Color::ByLayer,
start_angle: PI / 2.0,
end_angle: PI / 2.0,
elements: Vec::new(),
}
}
pub fn standard() -> Self {
let mut style = Self::new("Standard");
style.add_element(MLineStyleElement::new(0.5));
style.add_element(MLineStyleElement::new(-0.5));
style
}
pub fn add_element(&mut self, element: MLineStyleElement) {
self.elements.push(element);
}
pub fn add_element_with_offset(&mut self, offset: f64) -> &mut MLineStyleElement {
self.elements.push(MLineStyleElement::new(offset));
self.elements.last_mut().unwrap()
}
pub fn element_count(&self) -> usize {
self.elements.len()
}
pub fn total_width(&self) -> f64 {
if self.elements.is_empty() {
return 0.0;
}
let min_offset = self
.elements
.iter()
.map(|e| e.offset)
.fold(f64::INFINITY, f64::min);
let max_offset = self
.elements
.iter()
.map(|e| e.offset)
.fold(f64::NEG_INFINITY, f64::max);
max_offset - min_offset
}
pub fn set_fill_on(&mut self, fill_on: bool) {
if fill_on {
self.flags |= MLineStyleFlags::FILL_ON;
} else {
self.flags &= !MLineStyleFlags::FILL_ON;
}
}
pub fn is_fill_on(&self) -> bool {
self.flags.contains(MLineStyleFlags::FILL_ON)
}
pub fn set_display_joints(&mut self, display: bool) {
if display {
self.flags |= MLineStyleFlags::DISPLAY_JOINTS;
} else {
self.flags &= !MLineStyleFlags::DISPLAY_JOINTS;
}
}
pub fn displays_joints(&self) -> bool {
self.flags.contains(MLineStyleFlags::DISPLAY_JOINTS)
}
pub fn sort_elements(&mut self) {
self.elements
.sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap());
}
}
impl Default for MLineStyle {
fn default() -> Self {
Self::standard()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineSegment {
pub parameters: Vec<f64>,
pub area_fill_parameters: Vec<f64>,
}
impl MLineSegment {
pub fn new() -> Self {
Self {
parameters: Vec::new(),
area_fill_parameters: Vec::new(),
}
}
pub fn add_parameter(&mut self, param: f64) {
self.parameters.push(param);
}
pub fn add_area_fill_parameter(&mut self, param: f64) {
self.area_fill_parameters.push(param);
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineVertex {
pub position: Vector3,
pub direction: Vector3,
pub miter: Vector3,
pub segments: Vec<MLineSegment>,
}
impl MLineVertex {
pub fn new(position: Vector3) -> Self {
Self {
position,
direction: Vector3::new(1.0, 0.0, 0.0),
miter: Vector3::new(0.0, 1.0, 0.0),
segments: Vec::new(),
}
}
pub fn with_direction(position: Vector3, direction: Vector3) -> Self {
let miter = Vector3::new(-direction.y, direction.x, 0.0).normalize();
Self {
position,
direction: direction.normalize(),
miter,
segments: Vec::new(),
}
}
pub fn add_segment(&mut self, segment: MLineSegment) {
self.segments.push(segment);
}
pub fn init_segments(&mut self, element_count: usize) {
self.segments.clear();
for _ in 0..element_count {
self.segments.push(MLineSegment::new());
}
}
}
impl Default for MLineVertex {
fn default() -> Self {
Self::new(Vector3::ZERO)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLine {
pub common: EntityCommon,
pub flags: MLineFlags,
pub justification: MLineJustification,
pub normal: Vector3,
pub scale_factor: f64,
pub start_point: Vector3,
pub style_handle: Option<Handle>,
pub style_name: String,
pub style_element_count: usize,
pub vertices: Vec<MLineVertex>,
}
impl MLine {
pub fn new() -> Self {
Self {
common: EntityCommon::default(),
flags: MLineFlags::HAS_VERTICES,
justification: MLineJustification::Zero,
normal: Vector3::new(0.0, 0.0, 1.0),
scale_factor: 1.0,
start_point: Vector3::ZERO,
style_handle: None,
style_name: "Standard".to_string(),
style_element_count: 2,
vertices: Vec::new(),
}
}
pub fn from_points(points: &[Vector3]) -> Self {
let mut mline = Self::new();
for point in points {
mline.add_vertex(*point);
}
mline
}
pub fn closed_from_points(points: &[Vector3]) -> Self {
let mut mline = Self::from_points(points);
mline.close();
mline
}
pub fn add_vertex(&mut self, position: Vector3) -> &mut MLineVertex {
let direction = if let Some(last) = self.vertices.last() {
(position - last.position).normalize()
} else {
Vector3::new(1.0, 0.0, 0.0)
};
if self.vertices.is_empty() {
self.start_point = position;
}
let mut vertex = MLineVertex::with_direction(position, direction);
vertex.init_segments(self.style_element_count);
self.vertices.push(vertex);
if self.vertices.len() > 1 {
let len = self.vertices.len();
let dir = self.vertices[len - 1].direction;
let prev = &mut self.vertices[len - 2];
let new_dir = (prev.direction + dir).normalize();
prev.direction = new_dir;
prev.miter = Vector3::new(-new_dir.y, new_dir.x, 0.0).normalize();
}
self.vertices.last_mut().unwrap()
}
pub fn close(&mut self) {
self.flags |= MLineFlags::CLOSED;
}
pub fn open(&mut self) {
self.flags &= !MLineFlags::CLOSED;
}
pub fn is_closed(&self) -> bool {
self.flags.contains(MLineFlags::CLOSED)
}
pub fn suppress_start_caps(&mut self) {
self.flags |= MLineFlags::NO_START_CAPS;
}
pub fn suppress_end_caps(&mut self) {
self.flags |= MLineFlags::NO_END_CAPS;
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn positions(&self) -> impl Iterator<Item = Vector3> + '_ {
self.vertices.iter().map(|v| v.position)
}
pub fn length(&self) -> f64 {
if self.vertices.len() < 2 {
return 0.0;
}
let mut total: f64 = self
.vertices
.windows(2)
.map(|w| (w[1].position - w[0].position).length())
.sum();
if self.is_closed() && self.vertices.len() >= 2 {
total += (self.vertices.first().unwrap().position
- self.vertices.last().unwrap().position)
.length();
}
total
}
pub fn translate(&mut self, offset: Vector3) {
self.start_point = self.start_point + offset;
for vertex in &mut self.vertices {
vertex.position = vertex.position + offset;
}
}
pub fn bounding_box(&self) -> Option<(Vector3, Vector3)> {
if self.vertices.is_empty() {
return None;
}
let first = self.vertices[0].position;
let mut min = first;
let mut max = first;
for v in &self.vertices[1..] {
min.x = min.x.min(v.position.x);
min.y = min.y.min(v.position.y);
min.z = min.z.min(v.position.z);
max.x = max.x.max(v.position.x);
max.y = max.y.max(v.position.y);
max.z = max.z.max(v.position.z);
}
Some((min, max))
}
pub fn set_style_element_count(&mut self, count: usize) {
self.style_element_count = count;
}
pub fn reverse(&mut self) {
self.vertices.reverse();
if let Some(first) = self.vertices.first() {
self.start_point = first.position;
}
for i in 0..self.vertices.len() {
let direction = if i + 1 < self.vertices.len() {
(self.vertices[i + 1].position - self.vertices[i].position).normalize()
} else if self.is_closed() && !self.vertices.is_empty() {
(self.vertices[0].position - self.vertices[i].position).normalize()
} else if i > 0 {
self.vertices[i - 1].direction
} else {
Vector3::new(1.0, 0.0, 0.0)
};
self.vertices[i].direction = direction;
self.vertices[i].miter = Vector3::new(-direction.y, direction.x, 0.0).normalize();
}
}
}
impl Default for MLine {
fn default() -> Self {
Self::new()
}
}
impl Entity for MLine {
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, line_weight: LineWeight) {
self.common.line_weight = line_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 {
if self.vertices.is_empty() {
return BoundingBox3D::default();
}
let first = self.vertices[0].position;
let mut min = first;
let mut max = first;
for v in &self.vertices[1..] {
min.x = min.x.min(v.position.x);
min.y = min.y.min(v.position.y);
min.z = min.z.min(v.position.z);
max.x = max.x.max(v.position.x);
max.y = max.y.max(v.position.y);
max.z = max.z.max(v.position.z);
}
BoundingBox3D::new(min, max)
}
fn translate(&mut self, offset: Vector3) {
super::translate::translate_mline(self, offset);
}
fn entity_type(&self) -> &'static str {
"MLINE"
}
fn apply_transform(&mut self, transform: &crate::types::Transform) {
super::transform::transform_mline(self, transform);
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineBuilder {
mline: MLine,
}
impl MLineBuilder {
pub fn new() -> Self {
Self {
mline: MLine::new(),
}
}
pub fn vertex(mut self, position: Vector3) -> Self {
self.mline.add_vertex(position);
self
}
pub fn vertices(mut self, positions: &[Vector3]) -> Self {
for pos in positions {
self.mline.add_vertex(*pos);
}
self
}
pub fn scale(mut self, scale: f64) -> Self {
self.mline.scale_factor = scale;
self
}
pub fn justification(mut self, justification: MLineJustification) -> Self {
self.mline.justification = justification;
self
}
pub fn style_name(mut self, name: &str) -> Self {
self.mline.style_name = name.to_string();
self
}
pub fn closed(mut self) -> Self {
self.mline.close();
self
}
pub fn no_start_caps(mut self) -> Self {
self.mline.suppress_start_caps();
self
}
pub fn no_end_caps(mut self) -> Self {
self.mline.suppress_end_caps();
self
}
pub fn build(self) -> MLine {
self.mline
}
}
impl Default for MLineBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mline_creation() {
let mline = MLine::new();
assert_eq!(mline.justification, MLineJustification::Zero);
assert_eq!(mline.scale_factor, 1.0);
assert_eq!(mline.vertex_count(), 0);
assert!(!mline.is_closed());
}
#[test]
fn test_mline_from_points() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
];
let mline = MLine::from_points(&points);
assert_eq!(mline.vertex_count(), 3);
assert_eq!(mline.start_point, Vector3::new(0.0, 0.0, 0.0));
}
#[test]
fn test_mline_closed() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
Vector3::new(0.0, 10.0, 0.0),
];
let mut mline = MLine::from_points(&points);
assert!(!mline.is_closed());
mline.close();
assert!(mline.is_closed());
mline.open();
assert!(!mline.is_closed());
}
#[test]
fn test_mline_length() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
];
let mline = MLine::from_points(&points);
assert!((mline.length() - 20.0).abs() < 1e-10);
}
#[test]
fn test_mline_length_closed() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
Vector3::new(0.0, 10.0, 0.0),
];
let mline = MLine::closed_from_points(&points);
assert!((mline.length() - 40.0).abs() < 1e-10);
}
#[test]
fn test_mline_translate() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
];
let mut mline = MLine::from_points(&points);
mline.translate(Vector3::new(5.0, 5.0, 0.0));
assert_eq!(mline.start_point, Vector3::new(5.0, 5.0, 0.0));
assert_eq!(mline.vertices[0].position, Vector3::new(5.0, 5.0, 0.0));
assert_eq!(mline.vertices[1].position, Vector3::new(15.0, 5.0, 0.0));
}
#[test]
fn test_mline_bounding_box() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 5.0, 0.0),
Vector3::new(5.0, 10.0, 0.0),
];
let mline = MLine::from_points(&points);
let bbox = mline.bounding_box().unwrap();
assert_eq!(bbox.0, Vector3::new(0.0, 0.0, 0.0));
assert_eq!(bbox.1, Vector3::new(10.0, 10.0, 0.0));
}
#[test]
fn test_mline_justification() {
assert_eq!(MLineJustification::from(0), MLineJustification::Top);
assert_eq!(MLineJustification::from(1), MLineJustification::Zero);
assert_eq!(MLineJustification::from(2), MLineJustification::Bottom);
}
#[test]
fn test_mline_flags() {
let mut mline = MLine::new();
assert!(!mline.flags.contains(MLineFlags::CLOSED));
mline.close();
assert!(mline.flags.contains(MLineFlags::CLOSED));
mline.suppress_start_caps();
assert!(mline.flags.contains(MLineFlags::NO_START_CAPS));
mline.suppress_end_caps();
assert!(mline.flags.contains(MLineFlags::NO_END_CAPS));
}
#[test]
fn test_mlinestyle_creation() {
let style = MLineStyle::new("Custom");
assert_eq!(style.name, "Custom");
assert_eq!(style.element_count(), 0);
assert!((style.start_angle - PI / 2.0).abs() < 1e-10);
assert!((style.end_angle - PI / 2.0).abs() < 1e-10);
}
#[test]
fn test_mlinestyle_standard() {
let style = MLineStyle::standard();
assert_eq!(style.name, "Standard");
assert_eq!(style.element_count(), 2);
assert_eq!(style.elements[0].offset, 0.5);
assert_eq!(style.elements[1].offset, -0.5);
}
#[test]
fn test_mlinestyle_total_width() {
let mut style = MLineStyle::new("Wide");
style.add_element(MLineStyleElement::new(2.0));
style.add_element(MLineStyleElement::new(0.0));
style.add_element(MLineStyleElement::new(-2.0));
assert!((style.total_width() - 4.0).abs() < 1e-10);
}
#[test]
fn test_mlinestyle_fill() {
let mut style = MLineStyle::new("Filled");
assert!(!style.is_fill_on());
style.set_fill_on(true);
assert!(style.is_fill_on());
style.set_fill_on(false);
assert!(!style.is_fill_on());
}
#[test]
fn test_mlinestyle_joints() {
let mut style = MLineStyle::new("Joints");
assert!(!style.displays_joints());
style.set_display_joints(true);
assert!(style.displays_joints());
}
#[test]
fn test_mlinestyle_sort_elements() {
let mut style = MLineStyle::new("Unsorted");
style.add_element(MLineStyleElement::new(1.0));
style.add_element(MLineStyleElement::new(-1.0));
style.add_element(MLineStyleElement::new(0.0));
style.sort_elements();
assert_eq!(style.elements[0].offset, -1.0);
assert_eq!(style.elements[1].offset, 0.0);
assert_eq!(style.elements[2].offset, 1.0);
}
#[test]
fn test_mline_vertex() {
let mut vertex = MLineVertex::new(Vector3::new(5.0, 5.0, 0.0));
assert_eq!(vertex.position, Vector3::new(5.0, 5.0, 0.0));
assert_eq!(vertex.segments.len(), 0);
vertex.init_segments(3);
assert_eq!(vertex.segments.len(), 3);
}
#[test]
fn test_mline_segment() {
let mut segment = MLineSegment::new();
assert!(segment.parameters.is_empty());
assert!(segment.area_fill_parameters.is_empty());
segment.add_parameter(1.0);
segment.add_parameter(2.0);
segment.add_area_fill_parameter(0.5);
assert_eq!(segment.parameters.len(), 2);
assert_eq!(segment.area_fill_parameters.len(), 1);
}
#[test]
fn test_mline_builder() {
let mline = MLineBuilder::new()
.vertex(Vector3::new(0.0, 0.0, 0.0))
.vertex(Vector3::new(10.0, 0.0, 0.0))
.vertex(Vector3::new(10.0, 10.0, 0.0))
.scale(2.0)
.justification(MLineJustification::Top)
.style_name("Custom")
.closed()
.build();
assert_eq!(mline.vertex_count(), 3);
assert_eq!(mline.scale_factor, 2.0);
assert_eq!(mline.justification, MLineJustification::Top);
assert_eq!(mline.style_name, "Custom");
assert!(mline.is_closed());
}
#[test]
fn test_mline_reverse() {
let points = vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
Vector3::new(10.0, 10.0, 0.0),
];
let mut mline = MLine::from_points(&points);
mline.reverse();
assert_eq!(mline.start_point, Vector3::new(10.0, 10.0, 0.0));
assert_eq!(mline.vertices[0].position, Vector3::new(10.0, 10.0, 0.0));
assert_eq!(mline.vertices[1].position, Vector3::new(10.0, 0.0, 0.0));
assert_eq!(mline.vertices[2].position, Vector3::new(0.0, 0.0, 0.0));
}
#[test]
fn test_mlinestyle_element() {
let elem = MLineStyleElement::with_color(0.5, Color::Index(1));
assert_eq!(elem.offset, 0.5);
assert_eq!(elem.color, Color::Index(1));
}
}