brepkit_operations/tessellate/
mod.rs1#![allow(
4 clippy::many_single_char_names,
5 clippy::similar_names,
6 clippy::suboptimal_flops,
7 clippy::needless_range_loop,
8 clippy::cast_precision_loss,
9 clippy::doc_markdown,
10 clippy::cast_possible_truncation,
11 clippy::manual_let_else,
12 clippy::tuple_array_conversions,
13 clippy::imprecise_flops,
14 clippy::too_many_lines,
15 clippy::option_if_let_else,
16 clippy::bool_to_int_with_if,
17 clippy::if_same_then_else,
18 clippy::used_underscore_binding,
19 clippy::map_unwrap_or
20)]
21
22mod edge_sampling;
23mod face;
24mod mesh_ops;
25mod nonplanar;
26mod nurbs;
27mod planar;
28mod solid;
29#[cfg(test)]
30mod tests;
31
32use brepkit_math::vec::{Point3, Vec3};
33use brepkit_topology::Topology;
34use brepkit_topology::face::FaceId;
35
36pub use face::{tessellate_with_uvs, tessellate_with_uvs_a};
38pub(crate) use mesh_ops::COINCIDENT_DEDUPE_GRID;
39pub use mesh_ops::{
40 EdgeLines, boundary_edge_count, is_watertight, non_manifold_edge_count, sample_solid_edges,
41 sample_solid_edges_filtered,
42};
43pub use solid::{
44 tessellate_solid, tessellate_solid_for_boolean, tessellate_solid_grouped_with_tolerance,
45 tessellate_solid_with_tolerance,
46};
47
48const MERGE_GRID: f64 = 1e-7;
54
55pub(super) fn point_merge_key(pt: Point3, grid: f64) -> (i64, i64, i64) {
61 #[allow(clippy::cast_possible_truncation)]
62 (
63 (pt.x() / grid).round() as i64,
64 (pt.y() / grid).round() as i64,
65 (pt.z() / grid).round() as i64,
66 )
67}
68
69pub(super) fn shorter_arc_range(
75 circle: &brepkit_math::curves::Circle3D,
76 topo: &Topology,
77 edge: &brepkit_topology::edge::Edge,
78) -> Result<(f64, f64), crate::OperationsError> {
79 let sp = topo.vertex(edge.start())?.point();
80 let ep = topo.vertex(edge.end())?.point();
81 let ts = circle.project(sp);
82 let te_raw = circle.project(ep);
83 let fwd_span = (te_raw - ts).rem_euclid(std::f64::consts::TAU);
84 if fwd_span <= std::f64::consts::PI {
85 Ok((ts, ts + fwd_span))
87 } else {
88 let rev_span = std::f64::consts::TAU - fwd_span;
90 Ok((ts, ts - rev_span))
91 }
92}
93
94#[derive(Debug, Clone, Default)]
96pub struct TriangleMesh {
97 pub positions: Vec<Point3>,
99 pub normals: Vec<Vec3>,
101 pub indices: Vec<u32>,
103}
104
105#[derive(Debug, Clone, Default)]
107pub struct TriangleMeshUV {
108 pub mesh: TriangleMesh,
110 pub uvs: Vec<[f64; 2]>,
112}
113
114pub(super) enum AnalyticKind {
116 General,
118 SpherePole,
120 ConeApex,
122 VMaxPole,
124}
125
126pub fn tessellate(
138 topo: &Topology,
139 face: FaceId,
140 deflection: f64,
141) -> Result<TriangleMesh, crate::OperationsError> {
142 tessellate_with_uvs(topo, face, deflection).map(|uv| uv.mesh)
143}
144
145pub fn tessellate_with_tolerance(
154 topo: &Topology,
155 face: FaceId,
156 deflection: f64,
157 angular_tol: f64,
158) -> Result<TriangleMesh, crate::OperationsError> {
159 face::tessellate_with_uvs_a(topo, face, deflection, angular_tol).map(|uv| uv.mesh)
160}
161
162#[cfg(test)]
170#[must_use]
171pub(super) fn position_based_boundary_count(mesh: &TriangleMesh) -> usize {
172 const DIAGNOSTIC_GRID: f64 = 1e-6;
175
176 use brepkit_math::det_hash::{DetHashMap, DetHashSet};
177
178 let mut pos_to_canonical: DetHashMap<(i64, i64, i64), u32> = DetHashMap::default();
180 let mut canonical_ids: Vec<u32> = Vec::with_capacity(mesh.positions.len());
181 let mut next_id: u32 = 0;
182
183 for pos in &mesh.positions {
184 let key = point_merge_key(*pos, DIAGNOSTIC_GRID);
185 let id = *pos_to_canonical.entry(key).or_insert_with(|| {
186 let id = next_id;
187 next_id += 1;
188 id
189 });
190 canonical_ids.push(id);
191 }
192
193 let mut half_edges: DetHashSet<(u32, u32)> = DetHashSet::default();
195 for tri in mesh.indices.chunks_exact(3) {
196 let i0 = canonical_ids[tri[0] as usize];
197 let i1 = canonical_ids[tri[1] as usize];
198 let i2 = canonical_ids[tri[2] as usize];
199 half_edges.insert((i0, i1));
200 half_edges.insert((i1, i2));
201 half_edges.insert((i2, i0));
202 }
203
204 half_edges
205 .iter()
206 .filter(|&&(a, b)| !half_edges.contains(&(b, a)))
207 .count()
208}