brep_kernel/meshing/watertight_tessellation/
stride_encode.rs1use super::*;
2
3pub fn tessellate_brep_watertight_face_stride(
23 solid: &BrepSolid,
24 chord_tolerance: f64,
25 stride: usize,
26 offset: usize,
27) -> Result<Mesh, String> {
28 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
29 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
30 }
31 let started = profile_started();
32 let samples = sample_all_edges(solid, chord_tolerance)?;
33 let sampling_ms = elapsed_ms(started);
34 tess_profile(|p| p.edge_sampling_ms += sampling_ms);
35 tessellate_faces_stride(solid, &samples, chord_tolerance, stride, offset)
36}
37
38pub fn tessellate_brep_watertight_face_stride_with_samples(
45 solid: &BrepSolid,
46 chord_tolerance: f64,
47 stride: usize,
48 offset: usize,
49 encoded_samples: &[f64],
50) -> Result<Mesh, String> {
51 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
52 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
53 }
54 let samples = decode_edge_samples(encoded_samples)?;
55 tessellate_faces_stride(solid, &samples, chord_tolerance, stride, offset)
56}
57
58#[cfg(feature = "parallel")]
70pub(super) struct SyncFace<'a>(u32, &'a FaceRecord);
71#[cfg(feature = "parallel")]
72unsafe impl Sync for SyncFace<'_> {}
73
74pub(super) fn tessellate_faces_stride(
84 solid: &BrepSolid,
85 samples: &HashMap<u64, EdgeSamples>,
86 chord_tolerance: f64,
87 stride: usize,
88 offset: usize,
89) -> Result<Mesh, String> {
90 let stride = stride.max(1);
91 let offset = offset % stride;
92 let mut work: Vec<(u32, &FaceRecord)> = Vec::new();
93 let mut sequential_face_id = 0u32;
94 for shell in &solid.shells {
95 for face in &shell.faces {
96 if (sequential_face_id as usize) % stride == offset {
97 work.push((sequential_face_id, face));
98 }
99 sequential_face_id += 1;
100 }
101 }
102 let armed_here = arm_for_face_lines();
107 let face_lines = face_lines_enabled();
108 tess_profile(|p| p.faces += work.len() as u64);
109 let mesh_one = |face_id: u32, face: &FaceRecord| -> Result<Mesh, String> {
110 let mut fragment = Mesh::default();
111 if face_lines {
112 let before = snapshot();
113 let started = web_time::Instant::now();
114 tess_profile(|p| p.route = 0);
115 tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
116 report_face(
117 face_id,
118 face,
119 fragment.indices.len() / 3,
120 started.elapsed().as_secs_f64() * 1_000.0,
121 before,
122 );
123 return Ok(fragment);
124 }
125 tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
126 Ok(fragment)
127 };
128 #[cfg(feature = "parallel")]
129 let fragments = {
130 use rayon::prelude::*;
131 let sync_work: Vec<SyncFace> = work.iter().map(|&(id, face)| SyncFace(id, face)).collect();
132 sync_work
133 .par_iter()
134 .map(|face| mesh_one(face.0, face.1))
135 .collect::<Result<Vec<Mesh>, String>>()?
136 };
137 #[cfg(not(feature = "parallel"))]
138 let fragments = work
139 .iter()
140 .map(|&(id, face)| mesh_one(id, face))
141 .collect::<Result<Vec<Mesh>, String>>()?;
142 let mesh = concatenate_meshes(fragments);
143 if armed_here.is_some() {
144 profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
145 }
146 Ok(mesh)
147}
148
149pub(super) fn concatenate_meshes(fragments: Vec<Mesh>) -> Mesh {
152 let mut mesh = Mesh::default();
153 for fragment in fragments {
154 let base = (mesh.positions.len() / 3) as u32;
155 mesh.positions.extend_from_slice(&fragment.positions);
156 mesh.normals.extend_from_slice(&fragment.normals);
157 mesh.indices
158 .extend(fragment.indices.iter().map(|index| index + base));
159 mesh.face_ids.extend_from_slice(&fragment.face_ids);
160 }
161 mesh
162}
163
164pub fn sample_edge_polylines(
170 solid: &BrepSolid,
171 chord_tolerance: f64,
172) -> Result<Vec<(u64, Vec<Vec3>)>, String> {
173 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
174 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
175 }
176 let samples = sample_all_edges(solid, chord_tolerance)?;
177 let degenerate: std::collections::HashSet<u64> = solid
178 .edges
179 .iter()
180 .filter(|edge| edge.degenerate)
181 .map(|edge| edge.id)
182 .collect();
183 let mut out: Vec<(u64, Vec<Vec3>)> = samples
184 .into_iter()
185 .filter(|(id, samples)| !degenerate.contains(id) && samples.positions.len() >= 2)
186 .map(|(id, samples)| (id, samples.positions))
187 .collect();
188 out.sort_by_key(|(id, _)| *id);
189 Ok(out)
190}
191
192pub fn sample_edges_encoded(solid: &BrepSolid, chord_tolerance: f64) -> Result<Vec<f64>, String> {
198 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
199 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
200 }
201 let samples = sample_all_edges(solid, chord_tolerance)?;
202 Ok(encode_edge_samples(&samples))
203}
204
205pub(super) fn encode_edge_samples(samples: &HashMap<u64, EdgeSamples>) -> Vec<f64> {
206 let mut out = Vec::new();
207 out.push(samples.len() as f64);
208 for (edge_id, edge) in samples {
209 out.push(*edge_id as f64);
210 out.push(edge.fractions.len() as f64);
211 out.push(edge.worst_sag);
212 out.extend_from_slice(&edge.fractions);
213 for position in &edge.positions {
214 out.push(position.x);
215 out.push(position.y);
216 out.push(position.z);
217 }
218 }
219 out
220}
221
222pub(super) fn decode_edge_samples(data: &[f64]) -> Result<HashMap<u64, EdgeSamples>, String> {
223 let mut cursor = 0usize;
224 let mut take = |count: usize| -> Result<&[f64], String> {
225 let end = cursor
226 .checked_add(count)
227 .filter(|end| *end <= data.len())
228 .ok_or("watertight tessellation: truncated edge-sample buffer")?;
229 let slice = &data[cursor..end];
230 cursor = end;
231 Ok(slice)
232 };
233 let edge_count = take(1)?[0] as usize;
234 let mut samples = HashMap::with_capacity(edge_count);
235 for _ in 0..edge_count {
236 let header = take(3)?;
237 let edge_id = header[0] as u64;
238 let sample_count = header[1] as usize;
239 let worst_sag = header[2];
240 let fractions = take(sample_count)?.to_vec();
241 let position_values = take(sample_count * 3)?;
242 let positions = position_values
243 .chunks_exact(3)
244 .map(|chunk| Vec3::new(chunk[0], chunk[1], chunk[2]))
245 .collect();
246 samples.insert(
247 edge_id,
248 EdgeSamples {
249 fractions,
250 positions,
251 worst_sag,
252 },
253 );
254 }
255 Ok(samples)
256}