Skip to content

Allow for geo-3d integration – Part 1.1: Code quality #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions src/csg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use stl_io;

/// The main CSG solid structure. Contains a list of 3D polygons, 2D polylines, and some metadata.
#[derive(Debug, Clone)]
pub struct CSG<S: Clone> {
pub struct CSG<S: Clone = ()> {
/// 3D polygons for volumetric shapes
pub polygons: Vec<Polygon<S>>,

Expand All @@ -48,8 +48,13 @@ pub struct CSG<S: Clone> {
pub metadata: Option<S>,
}

impl<S: Clone + Debug> CSG<S>
where S: Clone + Send + Sync {
impl<S: Clone + Debug + Send + Sync> Default for CSG<S> {
fn default() -> Self {
Self::new()
}
}

impl<S: Clone + Debug + Send + Sync> CSG<S> {
/// Create an empty CSG
pub fn new() -> Self {
CSG {
Expand Down Expand Up @@ -860,13 +865,13 @@ where S: Clone + Send + Sync {
// 1) Gather from the 3D polygons
for poly in &self.polygons {
for v in &poly.vertices {
min_x = *partial_min(&min_x, &v.pos.x).unwrap();
min_y = *partial_min(&min_y, &v.pos.y).unwrap();
min_z = *partial_min(&min_z, &v.pos.z).unwrap();
min_x = min_x.min(v.pos.x);
min_y = min_y.min(v.pos.y);
min_z = min_z.min(v.pos.z);

max_x = *partial_max(&max_x, &v.pos.x).unwrap();
max_y = *partial_max(&max_y, &v.pos.y).unwrap();
max_z = *partial_max(&max_z, &v.pos.z).unwrap();
max_x = max_x.max(v.pos.x);
max_y = max_y.max(v.pos.y);
max_z = max_z.max(v.pos.z);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/extrudes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,7 @@ where S: Clone + Send + Sync {
}

// Build the polygon
let poly = Polygon::new(verts, metadata.clone());
Some(poly)
Some(Polygon::new(verts, metadata.clone()))
}

//----------------------------------------------------------------------
Expand Down
3 changes: 1 addition & 2 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use image::GrayImage;
use nalgebra::{Point3, Vector3};
use std::fmt::Debug;

impl<S: Clone + Debug> CSG<S>
where S: Clone + Send + Sync {
impl<S: Clone + Debug + Send + Sync> CSG<S> {
/// Builds a new CSG from the “on” pixels of a grayscale image,
/// tracing connected outlines (and holes) via the `contour_tracing` code.
///
Expand Down
31 changes: 30 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
//#![allow(dead_code)]
//! A fast, optionally multithreaded **Constructive Solid Geometry (CSG)** library,
//! built around Boolean operations (*union*, *difference*, *intersection*, *xor*) on sets of polygons stored in [BSP](bsp) trees.
//!
//! ![Example CSG output][Example CSG output]
#![cfg_attr(doc, doc = doc_image_embed::embed_image!("Example CSG output", "docs/csg.png"))]
//!
//! # Features
//! #### Default
//! - **f64**: use f64 as Real
//! - [**stl-io**](https://en.wikipedia.org/wiki/STL_(file_format)): `.stl` import/export
//! - [**dxf-io**](https://en.wikipedia.org/wiki/AutoCAD_DXF): `.dxf` import/export
//! - **chull-io**: convex hull and minkowski sum
//! - **metaballs**: enables a `CSG` implementation of [metaballs](https://en.wikipedia.org/wiki/Metaballs)
//! - **hashmap**: enables use of hashbrown for slice, related helper functions, and `is_manifold`
//! - **sdf**: signed distance fields ([sdf](https://en.wikipedia.org/wiki/Signed_distance_function)) using [fast-surface-nets](https://crates.io/crates/fast-surface-nets)
//! - **offset**: use `geo-buf` for offset operations
//! - **delaunay**: use `geo`s `spade` feature for triangulation
//!
//! #### Optional
//! - **f32**: use f32 as Real, this conflicts with f64
//! - **parallel**: use rayon for multithreading
//! - **svg-io**: create `CSG`s from and convert `CSG`s to SVG's
//! - **truetype-text**: create `CSG`s using TrueType fonts `.ttf`
//! - **hershey-text**: create `CSG`s using Hershey fonts (`.jhf`)
//! - **image-io**: make 2d `CSG`s from images
//! - **earcut**: use `geo`s `earcutr` feature for triangulation

#![forbid(unsafe_code)]

pub mod errors;
Expand All @@ -13,6 +39,9 @@ pub mod shapes3d;
pub mod extrudes;
pub mod io;

#[cfg(any(all(feature = "f64", feature = "f32"), not(any(feature = "f64", feature = "f32"))))]
compile_error!("Either 'f64' or 'f32' feature must be specified, but not both");

#[cfg(any(all(feature = "delaunay", feature = "earcut"), not(any(feature = "delaunay", feature = "earcut"))))]
compile_error!("Either 'delaunay' or 'earcut' feature must be specified, but not both");

Expand Down
15 changes: 7 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use csgrs::float_types::Real;
use std::fs;
use nalgebra::{Vector3, Point3};
use nalgebra::{Vector3, Point3, Point2};
use csgrs::plane::Plane;

#[cfg(feature = "image")]
Expand Down Expand Up @@ -128,11 +128,11 @@ fn main() {
[0.5, 1.0, 0.0],
[0.5, 0.5, 1.0],
];
let faces = vec![
vec![0, 2, 1], // base triangle
vec![0, 1, 3], // side
vec![1, 2, 3],
vec![2, 0, 3],
let faces: Vec<&[usize]> = vec![
&[0, 2, 1], // base triangle
&[0, 1, 3], // side
&[1, 2, 3],
&[2, 0, 3],
];
let poly = CSG::polyhedron(&points, &faces, None);
#[cfg(feature = "stl-io")]
Expand All @@ -157,7 +157,7 @@ fn main() {

// 14) Mass properties (just printing them)
let (mass, com, principal_frame) = cube.mass_properties(1.0);
println!("Cube mass = {}", mass);
println!("Cube mass = {mass}");
println!("Cube center of mass = {:?}", com);
println!("Cube principal inertia local frame = {:?}", principal_frame);

Expand Down Expand Up @@ -244,7 +244,6 @@ fn main() {
let _ = fs::write("stl/pie_slice.stl", wedge.to_stl_ascii("pie_slice"));

// Create a 2D "metaball" shape from 3 circles
use nalgebra::Point2;
let balls_2d = vec![
(Point2::new(0.0, 0.0), 1.0),
(Point2::new(1.5, 0.0), 1.0),
Expand Down
2 changes: 1 addition & 1 deletion src/metaballs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn stitch(contours: &[LineString<Real>]) -> Vec<LineString<Real>> {
}

// close if ends coincide
if chain.len() >= 3 && (chain[0] == *chain.last().unwrap()) == false {
if chain.len() >= 3 && chain[0] != *chain.last().unwrap() {
chain.push(chain[0]);
}
chains.push(LineString::new(chain));
Expand Down
8 changes: 4 additions & 4 deletions src/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use geo::{Geometry, GeometryCollection};
use geo_buf::{buffer_multi_polygon, buffer_polygon};
use std::fmt::Debug;

impl<S: Clone + Debug> CSG<S>
where S: Clone + Send + Sync {
/// Grows/shrinks/offsets all polygons in the XY plane by `distance` using cavalier_contours parallel_offset.
/// for each Polygon we convert to a cavalier_contours Polyline<Real> and call parallel_offset
impl<S: Clone + Debug + Send + Sync> CSG<S> {
/// Grows/shrinks/offsets all 2D geometry by `distance`
///
/// Note: this does not affect the 3d polygons of the `CSG`
pub fn offset(&self, distance: Real) -> CSG<S> {
// For each Geometry in the collection:
// - If it's a Polygon, buffer it and store the result as a MultiPolygon
Expand Down
4 changes: 2 additions & 2 deletions src/plane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct Plane {

impl Plane {
/// Create a plane from three points
pub fn from_points(a: &Point3<Real>, b: &Point3<Real>, c: &Point3<Real>) -> Plane {
pub const fn from_points(a: &Point3<Real>, b: &Point3<Real>, c: &Point3<Real>) -> Plane {
Plane {
point_a: *a,
point_b: *b,
Expand Down Expand Up @@ -103,7 +103,7 @@ impl Plane {
self.normal().dot(&self.point_a.coords)
}

pub fn flip(&mut self) {
pub const fn flip(&mut self) {
std::mem::swap(&mut self.point_a, &mut self.point_b);
}

Expand Down
30 changes: 21 additions & 9 deletions src/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,31 @@ pub struct Polygon<S: Clone> {
impl<S: Clone> Polygon<S>
where S: Clone + Send + Sync {
/// Create a polygon from vertices
pub fn new(vertices: Vec<Vertex>, metadata: Option<S>) -> Self {
pub const fn new(vertices: Vec<Vertex>, metadata: Option<S>) -> Self {
Polygon {
vertices,
metadata,
}
}

/// Create a polygon from three vertices
pub fn from_tri(vertices: &[Vertex; 3], metadata: Option<S>) -> Self {
Polygon {
vertices: vertices.to_vec(),
metadata,
}
}

// I think a worst-case can be constructed for any heuristic here
// the only optimal solution may be to calculate which vertices are far apart
// which we would need a fast solution for. Finding the best solution is likely
// to be too intensive, but finding a "good enough" solution may still be quick.
// It may be useful to retain this version and implement a slower higher quality
// solution as a second function.
/// Returns a [`Plane`] defined by the first three vertices of the [`Polygon`]
///
/// I think a worst-case can be constructed for any heuristic here the
/// only optimal solution may be to calculate which vertices are far apart
/// which we would need a fast solution for.
///
/// Finding the best solution is likely to be too intensive,
/// but finding a "good enough" solution may still be quick.
/// It may be useful to retain this version and implement a slower higher
/// quality solution as a second function.
#[inline]
pub fn plane(&self) -> Plane {
Plane::from_points(&self.vertices[0].pos, &self.vertices[1].pos, &self.vertices[2].pos)
Expand Down Expand Up @@ -197,12 +209,12 @@ where S: Clone + Send + Sync {
}

/// Returns a reference to the metadata, if any.
pub fn metadata(&self) -> Option<&S> {
pub const fn metadata(&self) -> Option<&S> {
self.metadata.as_ref()
}

/// Returns a mutable reference to the metadata, if any.
pub fn metadata_mut(&mut self) -> Option<&mut S> {
pub const fn metadata_mut(&mut self) -> Option<&mut S> {
self.metadata.as_mut()
}

Expand Down
1 change: 1 addition & 0 deletions src/shapes2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ where S: Clone + Send + Sync {
/// Teardrop shape. A simple approach:
/// - a circle arc for the "round" top
/// - it tapers down to a cusp at bottom.
///
/// This is just one of many possible "teardrop" definitions.
// todo: center on focus of the arc
pub fn teardrop_outline(
Expand Down
10 changes: 5 additions & 5 deletions src/shapes3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ where S: Clone + Send + Sync {
CSG::frustum_ptp(
Point3::origin(),
Point3::new(0.0, 0.0, height),
radius.clone(),
radius,
radius,
segments,
metadata,
Expand Down Expand Up @@ -354,18 +354,18 @@ where S: Clone + Send + Sync {
///
/// let csg_poly = CSG::polyhedron(pts, &fcs);
/// ```
pub fn polyhedron(points: &[[Real; 3]], faces: &[Vec<usize>], metadata: Option<S>) -> CSG<S> {
pub fn polyhedron(points: &[[Real; 3]], faces: &[&[usize]], metadata: Option<S>) -> CSG<S> {
let mut polygons = Vec::new();

for face in faces {
for face in faces.iter() {
// Skip degenerate faces
if face.len() < 3 {
continue;
}

// Gather the vertices for this face
let mut face_vertices = Vec::with_capacity(face.len());
for &idx in face {
for &idx in face.iter() {
// Ensure the index is valid
if idx >= points.len() {
panic!( // todo return error
Expand Down Expand Up @@ -515,7 +515,7 @@ where S: Clone + Send + Sync {
/// - `direction`: the vector defining arrow length and intended pointing direction
/// - `segments`: number of segments for approximating the cylinder and frustum
/// - `orientation`: when false (default) the arrow points away from start (its base is at start);
/// when true the arrow points toward start (its tip is at start).
/// when true the arrow points toward start (its tip is at start).
/// - `metadata`: optional metadata for the generated polygons.
pub fn arrow(
start: Point3<Real>,
Expand Down
Loading