#![allow(
clippy::many_single_char_names,
clippy::similar_names,
clippy::suboptimal_flops,
clippy::needless_range_loop,
clippy::cast_precision_loss,
clippy::doc_markdown,
clippy::cast_possible_truncation,
clippy::manual_let_else,
clippy::tuple_array_conversions,
clippy::imprecise_flops,
clippy::too_many_lines,
clippy::option_if_let_else,
clippy::bool_to_int_with_if,
clippy::if_same_then_else,
clippy::used_underscore_binding,
clippy::map_unwrap_or
)]
mod edge_sampling;
mod face;
mod mesh_ops;
mod nonplanar;
mod nurbs;
mod planar;
mod solid;
#[cfg(test)]
mod tests;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::face::FaceId;
pub use face::{tessellate_with_uvs, tessellate_with_uvs_a};
pub(crate) use mesh_ops::COINCIDENT_DEDUPE_GRID;
pub use mesh_ops::{
EdgeLines, boundary_edge_count, is_watertight, non_manifold_edge_count, sample_solid_edges,
sample_solid_edges_filtered,
};
pub use solid::{
tessellate_solid, tessellate_solid_for_boolean, tessellate_solid_grouped_with_tolerance,
tessellate_solid_with_tolerance,
};
const MERGE_GRID: f64 = 1e-7;
pub(super) fn point_merge_key(pt: Point3, grid: f64) -> (i64, i64, i64) {
#[allow(clippy::cast_possible_truncation)]
(
(pt.x() / grid).round() as i64,
(pt.y() / grid).round() as i64,
(pt.z() / grid).round() as i64,
)
}
pub(super) fn shorter_arc_range(
circle: &brepkit_math::curves::Circle3D,
topo: &Topology,
edge: &brepkit_topology::edge::Edge,
) -> Result<(f64, f64), crate::OperationsError> {
let sp = topo.vertex(edge.start())?.point();
let ep = topo.vertex(edge.end())?.point();
let ts = circle.project(sp);
let te_raw = circle.project(ep);
let fwd_span = (te_raw - ts).rem_euclid(std::f64::consts::TAU);
if fwd_span <= std::f64::consts::PI {
Ok((ts, ts + fwd_span))
} else {
let rev_span = std::f64::consts::TAU - fwd_span;
Ok((ts, ts - rev_span))
}
}
#[derive(Debug, Clone, Default)]
pub struct TriangleMesh {
pub positions: Vec<Point3>,
pub normals: Vec<Vec3>,
pub indices: Vec<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct TriangleMeshUV {
pub mesh: TriangleMesh,
pub uvs: Vec<[f64; 2]>,
}
pub(super) enum AnalyticKind {
General,
SpherePole,
ConeApex,
VMaxPole,
}
pub fn tessellate(
topo: &Topology,
face: FaceId,
deflection: f64,
) -> Result<TriangleMesh, crate::OperationsError> {
tessellate_with_uvs(topo, face, deflection).map(|uv| uv.mesh)
}
pub fn tessellate_with_tolerance(
topo: &Topology,
face: FaceId,
deflection: f64,
angular_tol: f64,
) -> Result<TriangleMesh, crate::OperationsError> {
face::tessellate_with_uvs_a(topo, face, deflection, angular_tol).map(|uv| uv.mesh)
}
#[cfg(test)]
#[must_use]
pub(super) fn position_based_boundary_count(mesh: &TriangleMesh) -> usize {
const DIAGNOSTIC_GRID: f64 = 1e-6;
use brepkit_math::det_hash::{DetHashMap, DetHashSet};
let mut pos_to_canonical: DetHashMap<(i64, i64, i64), u32> = DetHashMap::default();
let mut canonical_ids: Vec<u32> = Vec::with_capacity(mesh.positions.len());
let mut next_id: u32 = 0;
for pos in &mesh.positions {
let key = point_merge_key(*pos, DIAGNOSTIC_GRID);
let id = *pos_to_canonical.entry(key).or_insert_with(|| {
let id = next_id;
next_id += 1;
id
});
canonical_ids.push(id);
}
let mut half_edges: DetHashSet<(u32, u32)> = DetHashSet::default();
for tri in mesh.indices.chunks_exact(3) {
let i0 = canonical_ids[tri[0] as usize];
let i1 = canonical_ids[tri[1] as usize];
let i2 = canonical_ids[tri[2] as usize];
half_edges.insert((i0, i1));
half_edges.insert((i1, i2));
half_edges.insert((i2, i0));
}
half_edges
.iter()
.filter(|&&(a, b)| !half_edges.contains(&(b, a)))
.count()
}