csx 0.0.2

Pure Rust continuous-structure geometry and material modeling, compatible with CSXCAD and openEMS
Documentation
//! One- and zero-dimensional primitives: curves, wires and points.

use crate::coords::CoordSystem;
use crate::error::{Error, Result};
use crate::math::{BoundingBox, Vec3};
use crate::param::{ParameterSet, Scalar, ScalarPoint};

use super::geom::{dist_point_segment, tol};

fn polyline_dist(points: &[ScalarPoint], p: Vec3, cs: CoordSystem) -> f64 {
    let pts: Vec<Vec3> = points.iter().map(|q| cs.to_cartesian(q.value())).collect();
    match pts.len() {
        0 => f64::INFINITY,
        1 => (p - pts[0]).norm(),
        _ => pts
            .windows(2)
            .map(|w| dist_point_segment(p, w[0], w[1]))
            .fold(f64::INFINITY, f64::min),
    }
}

fn polyline_bbox(points: &[ScalarPoint], cs: CoordSystem) -> BoundingBox {
    BoundingBox::from_points(points.iter().map(|q| cs.to_cartesian(q.value())))
}

fn polyline_scale(points: &[ScalarPoint]) -> f64 {
    points
        .iter()
        .flat_map(|q| q.value().0)
        .fold(0.0, |m, c| m.max(c.abs()))
}

/// A polyline through a list of points (1-D).
///
/// As in CSXCAD, no point is ever "inside" a curve: curves have no volume
/// and are handled by their edges (see [`crate::discretize`]). In a
/// cylindrical coordinate system the vertices are `(ρ, α, z)`.
///
/// Curves are used for thin wires in the mesh (their vertices become mesh
/// lines, their segments PEC edges) and for line probes.
///
/// ```
/// use csx::primitives::{Curve, Primitive};
/// use csx::{CoordSystem, Vec3};
///
/// let c = Primitive::new(Curve::new([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 2.0, 0.0]]));
/// assert_eq!(c.dimension(), 1);
/// assert!(!c.contains(Vec3::new(0.5, 0.0, 0.0), CoordSystem::Cartesian));
/// assert_eq!(c.bounding_box(CoordSystem::Cartesian).max, Vec3::new(1.0, 2.0, 0.0));
/// ```
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Curve {
    /// Polyline vertices.
    pub points: Vec<ScalarPoint>,
}

impl Curve {
    /// Curve through `points`.
    pub fn new<P: Into<ScalarPoint>>(points: impl IntoIterator<Item = P>) -> Self {
        Curve {
            points: points.into_iter().map(Into::into).collect(),
        }
    }

    pub(crate) fn inside(&self, _p: Vec3) -> bool {
        false
    }

    pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
        polyline_bbox(&self.points, cs)
    }

    pub(crate) fn dimension(&self) -> usize {
        if self.points.len() >= 2 { 1 } else { 0 }
    }

    pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
        self.points.iter_mut().try_for_each(|q| q.update(p))
    }
}

/// A curve with a circular cross section of radius `radius`: the set of
/// points within `radius` of the polyline (round joints and end caps). With
/// a zero radius it behaves like a [`Curve`]. In a cylindrical coordinate
/// system the vertices are `(ρ, α, z)`; the segments stay straight.
///
/// ```
/// use csx::primitives::{Primitive, Wire};
/// use csx::{CoordSystem, Vec3};
///
/// let w = Primitive::new(Wire::new([[0.0, 0.0, 0.0], [0.0, 0.0, 5.0]], 0.2));
/// assert_eq!(w.dimension(), 3);
/// assert!(w.contains(Vec3::new(0.1, 0.0, 2.5), CoordSystem::Cartesian));
/// assert!(!w.contains(Vec3::new(0.3, 0.0, 2.5), CoordSystem::Cartesian));
/// assert_eq!(w.bounding_box(CoordSystem::Cartesian).max, Vec3::new(0.2, 0.2, 5.2));
/// ```
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Wire {
    /// Polyline vertices.
    pub points: Vec<ScalarPoint>,
    /// Wire radius.
    pub radius: Scalar,
}

impl Wire {
    /// Wire through `points` with radius `radius`.
    pub fn new<P: Into<ScalarPoint>>(
        points: impl IntoIterator<Item = P>,
        radius: impl Into<Scalar>,
    ) -> Self {
        Wire {
            points: points.into_iter().map(Into::into).collect(),
            radius: radius.into(),
        }
    }

    pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
        let r = self.radius.value();
        polyline_dist(&self.points, p, cs) <= r + tol(polyline_scale(&self.points), r)
    }

    pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
        let b = polyline_bbox(&self.points, cs);
        if b.is_empty() {
            b
        } else {
            b.expanded(self.radius.value())
        }
    }

    pub(crate) fn dimension(&self) -> usize {
        if self.radius.value() > 0.0 {
            3
        } else if self.points.len() >= 2 {
            1
        } else {
            0
        }
    }

    pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
        self.points.iter_mut().try_for_each(|q| q.update(p))?;
        self.radius.update(p)?;
        if self.radius.value() < 0.0 {
            return Err(Error::Invalid(format!(
                "negative wire radius {}",
                self.radius.value()
            )));
        }
        Ok(())
    }
}

/// A single point (0-D, `<Point>` in CSXCAD files). Like curves, a point
/// never contains anything; it marks a location (e.g. a field probe) and a
/// mesh line candidate. In a cylindrical coordinate system it is
/// `(ρ, α, z)`.
///
/// ```
/// use csx::primitives::{PointShape, Primitive};
/// use csx::{CoordSystem, Vec3};
///
/// let p = Primitive::new(PointShape::new([1.0, 2.0, 3.0]));
/// assert_eq!(p.dimension(), 0);
/// assert!(!p.contains(Vec3::new(1.0, 2.0, 3.0), CoordSystem::Cartesian));
/// assert_eq!(p.bounding_box(CoordSystem::Cartesian).min, Vec3::new(1.0, 2.0, 3.0));
/// ```
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PointShape {
    /// Location.
    pub point: ScalarPoint,
}

impl PointShape {
    /// Point primitive at `p`.
    pub fn new(p: impl Into<ScalarPoint>) -> Self {
        PointShape { point: p.into() }
    }

    pub(crate) fn inside(&self, _p: Vec3) -> bool {
        false
    }

    pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
        let q = cs.to_cartesian(self.point.value());
        BoundingBox::from_corners(q, q)
    }

    pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
        self.point.update(p)
    }
}