use super::*;
pub fn tessellate_brep_watertight_face_stride(
solid: &BrepSolid,
chord_tolerance: f64,
stride: usize,
offset: usize,
) -> Result<Mesh, String> {
tessellate_brep_watertight_face_stride_keyed(solid, chord_tolerance, stride, offset).map(|(mesh, _)| mesh)
}
pub(crate) fn tessellate_brep_watertight_face_stride_keyed(
solid: &BrepSolid,
chord_tolerance: f64,
stride: usize,
offset: usize,
) -> Result<(Mesh, Vec<[u64; 2]>), String> {
if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
}
let armed_here = arm_for_face_lines();
let started = profile_started();
let samples = sample_all_edges(solid, chord_tolerance)?;
let sampling_ms = elapsed_ms(started);
tess_profile(|p| p.edge_sampling_ms += sampling_ms);
let mut keys = Vec::new();
let mesh = tessellate_faces_stride_keyed(solid, &samples, chord_tolerance, stride, offset, Some(&mut keys))?;
profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
Ok((mesh, keys))
}
pub fn tessellate_brep_watertight_face_stride_with_samples(
solid: &BrepSolid,
chord_tolerance: f64,
stride: usize,
offset: usize,
encoded_samples: &[f64],
) -> Result<Mesh, String> {
if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
}
let armed_here = arm_for_face_lines();
let samples = decode_edge_samples(encoded_samples)?;
let mesh = tessellate_faces_stride(solid, &samples, chord_tolerance, stride, offset)?;
profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
Ok(mesh)
}
#[cfg(feature = "parallel")]
pub(super) struct SyncFace<'a>(u32, &'a FaceRecord);
#[cfg(feature = "parallel")]
unsafe impl Sync for SyncFace<'_> {}
pub(super) fn tessellate_faces_stride(
solid: &BrepSolid,
samples: &HashMap<u64, EdgeSamples>,
chord_tolerance: f64,
stride: usize,
offset: usize,
) -> Result<Mesh, String> {
tessellate_faces_stride_keyed(solid, samples, chord_tolerance, stride, offset, None)
}
pub(super) fn tessellate_faces_stride_keyed(
solid: &BrepSolid,
samples: &HashMap<u64, EdgeSamples>,
chord_tolerance: f64,
stride: usize,
offset: usize,
keys: Option<&mut Vec<[u64; 2]>>,
) -> Result<Mesh, String> {
let stride = stride.max(1);
let offset = offset % stride;
let mut work: Vec<(u32, &FaceRecord)> = Vec::new();
let mut sequential_face_id = 0u32;
for shell in &solid.shells {
for face in &shell.faces {
if (sequential_face_id as usize) % stride == offset {
work.push((sequential_face_id, face));
}
sequential_face_id += 1;
}
}
let armed_here = arm_for_face_lines();
let face_lines = face_lines_enabled();
tess_profile(|p| p.faces += work.len() as u64);
let mesh_fresh = |face_id: u32, face: &FaceRecord| -> Result<Mesh, String> {
let mut fragment = Mesh::default();
if face_lines {
let before = snapshot();
let started = web_time::Instant::now();
tess_profile(|p| p.route = 0);
tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
report_face(
face_id,
face,
fragment.indices.len() / 3,
started.elapsed().as_secs_f64() * 1_000.0,
before,
);
return Ok(fragment);
}
tessellate_face_watertight(face, samples, chord_tolerance, face_id, &mut fragment)?;
Ok(fragment)
};
let memo = super::face_memo::enabled();
let face_keys: Option<Vec<[u64; 2]>> = (memo || keys.is_some())
.then(|| work.iter().map(|&(_, face)| super::face_memo::key(face, samples, chord_tolerance)).collect());
if let Some(keys) = keys {
*keys = face_keys.clone().unwrap_or_default();
}
let scan_mesh = super::scan_mesh::active();
let mesh_one = |index: usize, face_id: u32, face: &FaceRecord| -> Result<Mesh, String> {
let _scan_mesh = super::scan_mesh::ScanMeshScope::set(scan_mesh);
let key = if memo { face_keys.as_ref().map(|keys| keys[index]) } else { None };
if let Some(key) = key {
if face_lines { super::face_memo::trace_key(face_id, face, samples, chord_tolerance, key); }
if let Some(fragment) = super::face_memo::lookup(key, face_id) {
if face_lines {
eprintln!(
"tess.face face={face_id} id={} memo=hit tris={}",
face.id,
fragment.indices.len() / 3
);
}
return Ok(fragment);
}
}
let fragment = mesh_fresh(face_id, face)?;
if let Some(key) = key {
super::face_memo::store(key, &fragment);
}
Ok(fragment)
};
#[cfg(feature = "parallel")]
let fragments = {
use rayon::prelude::*;
let sync_work: Vec<SyncFace> = work.iter().map(|&(id, face)| SyncFace(id, face)).collect();
sync_work
.par_iter()
.enumerate()
.map(|(index, face)| mesh_one(index, face.0, face.1))
.collect::<Result<Vec<Mesh>, String>>()?
};
#[cfg(not(feature = "parallel"))]
let fragments = work
.iter()
.enumerate()
.map(|(index, &(id, face))| mesh_one(index, id, face))
.collect::<Result<Vec<Mesh>, String>>()?;
let mesh = concatenate_meshes(fragments);
if tess_profile_enabled() {
let (hits, misses, entries, triangles) = super::face_memo::take_counts();
profile_line!("tess.memo enabled={memo} hits={hits} misses={misses} entries={entries} stored_tris={triangles}");
}
if armed_here.is_some() {
profile::end(armed_here, chord_tolerance, mesh.indices.len() / 3);
}
Ok(mesh)
}
pub(super) fn concatenate_meshes(fragments: Vec<Mesh>) -> Mesh {
let mut mesh = Mesh::default();
for fragment in fragments {
let base = (mesh.positions.len() / 3) as u32;
mesh.positions.extend_from_slice(&fragment.positions);
mesh.normals.extend_from_slice(&fragment.normals);
mesh.indices
.extend(fragment.indices.iter().map(|index| index + base));
mesh.face_ids.extend_from_slice(&fragment.face_ids);
}
mesh
}
pub fn sample_edge_polylines(
solid: &BrepSolid,
chord_tolerance: f64,
) -> Result<Vec<(u64, Vec<Vec3>)>, String> {
if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
}
let samples = sample_all_edges(solid, chord_tolerance)?;
let degenerate: std::collections::HashSet<u64> = solid
.edges
.iter()
.filter(|edge| edge.degenerate)
.map(|edge| edge.id)
.collect();
let mut out: Vec<(u64, Vec<Vec3>)> = samples
.into_iter()
.filter(|(id, samples)| !degenerate.contains(id) && samples.positions.len() >= 2)
.map(|(id, samples)| (id, samples.positions))
.collect();
out.sort_by_key(|(id, _)| *id);
Ok(out)
}
pub fn sample_edges_encoded(solid: &BrepSolid, chord_tolerance: f64) -> Result<Vec<f64>, String> {
if !(chord_tolerance > 0.0) || !chord_tolerance.is_finite() {
return Err("tessellate_brep_watertight: chord tolerance must be positive".into());
}
let armed_here = arm_for_face_lines();
let started = profile_started();
let samples = sample_all_edges(solid, chord_tolerance)?;
tess_profile(|p| p.edge_sampling_ms += elapsed_ms(started));
let encoded = encode_edge_samples(&samples);
profile::end(armed_here, chord_tolerance, 0);
Ok(encoded)
}
pub(super) fn encode_edge_samples(samples: &HashMap<u64, EdgeSamples>) -> Vec<f64> {
let _timer = stage_timer(Stage::Encode);
let mut out = Vec::new();
out.push(samples.len() as f64);
for (edge_id, edge) in samples {
out.push(*edge_id as f64);
out.push(edge.fractions.len() as f64);
out.push(edge.worst_sag);
out.extend_from_slice(&edge.fractions);
for position in &edge.positions {
out.push(position.x);
out.push(position.y);
out.push(position.z);
}
}
out
}
pub(super) fn decode_edge_samples(data: &[f64]) -> Result<HashMap<u64, EdgeSamples>, String> {
let _timer = stage_timer(Stage::Decode);
let mut cursor = 0usize;
let mut take = |count: usize| -> Result<&[f64], String> {
let end = cursor
.checked_add(count)
.filter(|end| *end <= data.len())
.ok_or("watertight tessellation: truncated edge-sample buffer")?;
let slice = &data[cursor..end];
cursor = end;
Ok(slice)
};
let edge_count = take(1)?[0] as usize;
let mut samples = HashMap::with_capacity(edge_count);
for _ in 0..edge_count {
let header = take(3)?;
let edge_id = header[0] as u64;
let sample_count = header[1] as usize;
let worst_sag = header[2];
let fractions = take(sample_count)?.to_vec();
let position_values = take(sample_count * 3)?;
let positions = position_values
.chunks_exact(3)
.map(|chunk| Vec3::new(chunk[0], chunk[1], chunk[2]))
.collect();
samples.insert(
edge_id,
EdgeSamples {
fractions,
positions,
worst_sag,
},
);
}
Ok(samples)
}