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 tessellate_brep_watertight_face_stride_keyed(solid, chord_tolerance, stride, offset).map(|(mesh, _)| mesh)
29}
30
31pub(crate) fn tessellate_brep_watertight_face_stride_keyed(
37 solid: &BrepSolid,
38 chord_tolerance: f64,
39 stride: usize,
40 offset: usize,
41) -> Result<(Mesh, Vec<[u64; 2]>), String> {
42 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
43 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
44 }
45 let armed_here = arm_for_face_lines();
46 let started = profile_started();
47 let samples = sample_all_edges(solid, chord_tolerance)?;
48 let sampling_ms = elapsed_ms(started);
49 tess_profile(|p| p.edge_sampling_ms += sampling_ms);
50 let mut keys = Vec::new();
51 let mesh = tessellate_faces_stride_keyed(solid, &samples, chord_tolerance, stride, offset, Some(&mut keys))?;
52 profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
53 Ok((mesh, keys))
54}
55
56pub fn tessellate_brep_watertight_face_stride_with_samples(
63 solid: &BrepSolid,
64 chord_tolerance: f64,
65 stride: usize,
66 offset: usize,
67 encoded_samples: &[f64],
68) -> Result<Mesh, String> {
69 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
70 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
71 }
72 let armed_here = arm_for_face_lines();
73 let samples = decode_edge_samples(encoded_samples)?;
74 let mesh = tessellate_faces_stride(solid, &samples, chord_tolerance, stride, offset)?;
75 profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
76 Ok(mesh)
77}
78
79#[cfg(feature = "parallel")]
91pub(super) struct SyncFace<'a>(u32, &'a FaceRecord);
92#[cfg(feature = "parallel")]
93unsafe impl Sync for SyncFace<'_> {}
94
95pub(super) fn tessellate_faces_stride(
105 solid: &BrepSolid,
106 samples: &HashMap<u64, EdgeSamples>,
107 chord_tolerance: f64,
108 stride: usize,
109 offset: usize,
110) -> Result<Mesh, String> {
111 tessellate_faces_stride_keyed(solid, samples, chord_tolerance, stride, offset, None)
112}
113
114pub(super) fn tessellate_faces_stride_keyed(
117 solid: &BrepSolid,
118 samples: &HashMap<u64, EdgeSamples>,
119 chord_tolerance: f64,
120 stride: usize,
121 offset: usize,
122 keys: Option<&mut Vec<[u64; 2]>>,
123) -> Result<Mesh, String> {
124 let stride = stride.max(1);
125 let offset = offset % stride;
126 let mut work: Vec<(u32, &FaceRecord)> = Vec::new();
127 let mut sequential_face_id = 0u32;
128 for shell in &solid.shells {
129 for face in &shell.faces {
130 if (sequential_face_id as usize) % stride == offset {
131 work.push((sequential_face_id, face));
132 }
133 sequential_face_id += 1;
134 }
135 }
136 let armed_here = arm_for_face_lines();
141 let face_lines = face_lines_enabled();
142 tess_profile(|p| p.faces += work.len() as u64);
143 let mesh_fresh = |face_id: u32, face: &FaceRecord| -> Result<Mesh, String> {
144 let mut fragment = Mesh::default();
145 if face_lines {
146 let before = snapshot();
147 let started = web_time::Instant::now();
148 tess_profile(|p| p.route = 0);
149 tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
150 report_face(
151 face_id,
152 face,
153 fragment.indices.len() / 3,
154 started.elapsed().as_secs_f64() * 1_000.0,
155 before,
156 );
157 return Ok(fragment);
158 }
159 tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
160 Ok(fragment)
161 };
162 let memo = super::face_memo::enabled();
163 let face_keys: Option<Vec<[u64; 2]>> = (memo || keys.is_some())
167 .then(|| work.iter().map(|&(_, face)| super::face_memo::key(face, samples, chord_tolerance)).collect());
168 if let Some(keys) = keys {
169 *keys = face_keys.clone().unwrap_or_default();
170 }
171 let scan_mesh = super::scan_mesh::active();
172 let mesh_one = |index: usize, face_id: u32, face: &FaceRecord| -> Result<Mesh, String> {
173 let _scan_mesh = super::scan_mesh::ScanMeshScope::set(scan_mesh);
174 let key = if memo { face_keys.as_ref().map(|keys| keys[index]) } else { None };
178 if let Some(key) = key {
179 if face_lines { super::face_memo::trace_key(face_id, face, samples, chord_tolerance, key); }
180 if let Some(fragment) = super::face_memo::lookup(key, face_id) {
181 if face_lines {
182 eprintln!(
183 "tess.face face={face_id} id={} memo=hit tris={}",
184 face.id,
185 fragment.indices.len() / 3
186 );
187 }
188 return Ok(fragment);
189 }
190 }
191 let fragment = mesh_fresh(face_id, face)?;
192 if let Some(key) = key {
193 super::face_memo::store(key, &fragment);
194 }
195 Ok(fragment)
196 };
197 #[cfg(feature = "parallel")]
198 let fragments = {
199 use rayon::prelude::*;
200 let sync_work: Vec<SyncFace> = work.iter().map(|&(id, face)| SyncFace(id, face)).collect();
201 sync_work
202 .par_iter()
203 .enumerate()
204 .map(|(index, face)| mesh_one(index, face.0, face.1))
205 .collect::<Result<Vec<Mesh>, String>>()?
206 };
207 #[cfg(not(feature = "parallel"))]
208 let fragments = work
209 .iter()
210 .enumerate()
211 .map(|(index, &(id, face))| mesh_one(index, id, face))
212 .collect::<Result<Vec<Mesh>, String>>()?;
213 let mesh = concatenate_meshes(fragments);
214 if tess_profile_enabled() {
215 let (hits, misses, entries, triangles) = super::face_memo::take_counts();
216 profile_line!("tess.memo enabled={memo} hits={hits} misses={misses} entries={entries} stored_tris={triangles}");
217 }
218 if armed_here.is_some() {
219 profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
220 }
221 Ok(mesh)
222}
223
224pub(super) fn concatenate_meshes(fragments: Vec<Mesh>) -> Mesh {
227 let mut mesh = Mesh::default();
228 for fragment in fragments {
229 let base = (mesh.positions.len() / 3) as u32;
230 mesh.positions.extend_from_slice(&fragment.positions);
231 mesh.normals.extend_from_slice(&fragment.normals);
232 mesh.indices
233 .extend(fragment.indices.iter().map(|index| index + base));
234 mesh.face_ids.extend_from_slice(&fragment.face_ids);
235 }
236 mesh
237}
238
239pub fn sample_edge_polylines(
245 solid: &BrepSolid,
246 chord_tolerance: f64,
247) -> Result<Vec<(u64, Vec<Vec3>)>, String> {
248 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
249 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
250 }
251 let samples = sample_all_edges(solid, chord_tolerance)?;
252 let degenerate: std::collections::HashSet<u64> = solid
253 .edges
254 .iter()
255 .filter(|edge| edge.degenerate)
256 .map(|edge| edge.id)
257 .collect();
258 let mut out: Vec<(u64, Vec<Vec3>)> = samples
259 .into_iter()
260 .filter(|(id, samples)| !degenerate.contains(id) && samples.positions.len() >= 2)
261 .map(|(id, samples)| (id, samples.positions))
262 .collect();
263 out.sort_by_key(|(id, _)| *id);
264 Ok(out)
265}
266
267pub fn sample_edges_encoded(solid: &BrepSolid, chord_tolerance: f64) -> Result<Vec<f64>, String> {
273 if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
274 return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
275 }
276 let armed_here = arm_for_face_lines();
277 let started = profile_started();
278 let samples = sample_all_edges(solid, chord_tolerance)?;
279 tess_profile(|p| p.edge_sampling_ms += elapsed_ms(started));
280 let encoded = encode_edge_samples(&samples);
281 profile::end(armed_here, chord_tolerance, 0);
282 Ok(encoded)
283}
284
285pub(super) fn encode_edge_samples(samples: &HashMap<u64, EdgeSamples>) -> Vec<f64> {
286 let _timer = stage_timer(Stage::Encode);
287 let mut out = Vec::new();
288 out.push(samples.len() as f64);
289 for (edge_id, edge) in samples {
290 out.push(*edge_id as f64);
291 out.push(edge.fractions.len() as f64);
292 out.push(edge.worst_sag);
293 out.extend_from_slice(&edge.fractions);
294 for position in &edge.positions {
295 out.push(position.x);
296 out.push(position.y);
297 out.push(position.z);
298 }
299 }
300 out
301}
302
303pub(super) fn decode_edge_samples(data: &[f64]) -> Result<HashMap<u64, EdgeSamples>, String> {
304 let _timer = stage_timer(Stage::Decode);
305 let mut cursor = 0usize;
306 let mut take = |count: usize| -> Result<&[f64], String> {
307 let end = cursor
308 .checked_add(count)
309 .filter(|end| *end <= data.len())
310 .ok_or("watertight tessellation: truncated edge-sample buffer")?;
311 let slice = &data[cursor..end];
312 cursor = end;
313 Ok(slice)
314 };
315 let edge_count = take(1)?[0] as usize;
316 let mut samples = HashMap::with_capacity(edge_count);
317 for _ in 0..edge_count {
318 let header = take(3)?;
319 let edge_id = header[0] as u64;
320 let sample_count = header[1] as usize;
321 let worst_sag = header[2];
322 let fractions = take(sample_count)?.to_vec();
323 let position_values = take(sample_count * 3)?;
324 let positions = position_values
325 .chunks_exact(3)
326 .map(|chunk| Vec3::new(chunk[0], chunk[1], chunk[2]))
327 .collect();
328 samples.insert(
329 edge_id,
330 EdgeSamples {
331 fractions,
332 positions,
333 worst_sag,
334 },
335 );
336 }
337 Ok(samples)
338}