use super::*;
use super::earclip::{lawson_flips, lawson_flips_guarded};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::watertight_tessellation) enum BoundaryForm {
SingleRing,
Constrained,
}
pub(in crate::watertight_tessellation) fn seed_interior_grid(
face: &FaceRecord,
vertices: &mut Vec<FaceVertex>,
triangles: &mut Vec<[usize; 3]>,
boundary_pairs: &HashSet<(usize, usize)>,
chord_tolerance: f64,
domains: [f64; 4],
scale: [f64; 2],
periodic: bool,
boundary: BoundaryForm,
) -> Result<(), String> {
let _timer = stage_timer(Stage::Seed);
let [u0, u1, v0, v1] = domains;
let [su, sv] = scale;
let boundary_count = vertices.len();
let mut suu: f64 = 0.0;
let mut svv: f64 = 0.0;
let mut suv: f64 = 0.0;
for i in 0..=4 {
for j in 0..=4 {
let u = u0 + (u1 - u0) * i as f64 / 4.0;
let v = v0 + (v1 - v0) * j as f64 / 4.0;
let derivatives = if periodic {
face.surface.derivatives_extended(u, v, 2)?
} else {
face.surface.derivatives(u, v, 2)?
};
suu = suu.max(derivatives[2][0].length());
svv = svv.max(derivatives[0][2].length());
suv = suv.max(derivatives[1][1].length());
}
}
let step = |second_derivative: f64| -> f64 {
if second_derivative > 1e-12 {
(8.0 * chord_tolerance / second_derivative).sqrt()
} else {
f64::INFINITY
}
};
let mut du = step(suu);
let mut dv = step(svv);
{
let cell_u = du.min(u1 - u0);
let cell_v = dv.min(v1 - v0);
let diagonal =
suu * cell_u * cell_u + 2.0 * suv * cell_u * cell_v + svv * cell_v * cell_v;
if diagonal > 8.0 * chord_tolerance {
let shrink = (8.0 * chord_tolerance / diagonal).sqrt();
du = cell_u * shrink;
dv = cell_v * shrink;
}
}
let clamp = |d: f64, extent: f64| -> f64 {
if d.is_finite() {
d.min(extent).max(extent / 256.0)
} else {
f64::INFINITY
}
};
let du = clamp(du, u1 - u0);
let dv = clamp(dv, v1 - v0);
let nu = if du.is_finite() {
((u1 - u0) / du).ceil() as usize
} else {
1
};
let nv = if dv.is_finite() {
((v1 - v0) / dv).ceil() as usize
} else {
1
};
if nu <= 1 || nv <= 1 {
return Ok(());
}
const STATION_BUDGET: usize = 8_192;
let box_area = ((u1 - u0) * (v1 - v0)).abs();
let region_area: f64 = triangles
.iter()
.map(|&[a, b, c]| triangle_area(vertices[a].uv, vertices[b].uv, vertices[c].uv).abs())
.sum();
let share = if box_area > 0.0 {
(region_area / box_area).clamp(1.0 / 256.0, 1.0)
} else {
1.0
};
let budget = (STATION_BUDGET as f64 / share) as usize;
let stations = nu.saturating_mul(nv);
let (nu, nv) = if stations > budget {
let shrink = (stations as f64 / budget as f64).sqrt();
(
((nu as f64 / shrink).floor() as usize).max(2),
((nv as f64 / shrink).floor() as usize).max(2),
)
} else {
(nu, nv)
};
let du = du.max((u1 - u0) / nu as f64);
let dv = dv.max((v1 - v0) / nv as f64);
let single_ring = matches!(boundary, BoundaryForm::SingleRing);
let segments: Vec<([f64; 2], [f64; 2])> = if single_ring {
(0..boundary_count)
.map(|index| {
(
vertices[index].uv,
vertices[(index + 1) % boundary_count].uv,
)
})
.collect()
} else {
let mut keys: Vec<(usize, usize)> = boundary_pairs.iter().copied().collect();
keys.sort_unstable();
keys.into_iter()
.map(|(first, second)| (vertices[first].uv, vertices[second].uv))
.collect()
};
let polygon: Vec<[f64; 2]> = (0..boundary_count)
.map(|index| vertices[index].uv)
.collect();
let margin = 0.35 * (du * su).min(dv * sv).max(1e-12);
let index = BoundaryIndex::new(
&segments,
if single_ring { &polygon } else { &[] },
scale,
margin,
);
let inside_with_margin = |uv: [f64; 2]| -> bool {
if index.near_segment(uv) {
return false;
}
if !single_ring {
return true;
}
index.ring_encloses(uv)
};
tess_profile(|p| p.stations += (nu.saturating_sub(1) * nv.saturating_sub(1)) as u64);
for iu in 1..nu {
for iv in 1..nv {
let uv = [
u0 + (u1 - u0) * iu as f64 / nu as f64,
v0 + (v1 - v0) * iv as f64 / nv as f64,
];
if !inside_with_margin(uv) {
continue;
}
let mut host = None;
tess_profile(|p| p.stations_placed += 1);
let host_timer = stage_timer(Stage::Host);
let mut visits = 0u64;
for (triangle_index, triangle) in triangles.iter().enumerate().rev() {
visits += 1;
let a = vertices[triangle[0]].uv;
let b = vertices[triangle[1]].uv;
let c = vertices[triangle[2]].uv;
let total = triangle_area(a, b, c);
if total <= 1e-30 {
continue;
}
let w0 = triangle_area(uv, b, c) / total;
let w1 = triangle_area(uv, c, a) / total;
let w2 = triangle_area(uv, a, b) / total;
if w0 > 1e-6 && w1 > 1e-6 && w2 > 1e-6 {
host = Some(triangle_index);
break;
}
}
tess_profile(|p| p.host_visits += visits);
drop(host_timer);
let Some(host) = host else {
continue;
};
let position = if periodic {
face.surface.evaluate_extended(uv[0], uv[1])?
} else {
face.surface.evaluate(uv[0], uv[1])?
};
let index = vertices.len();
vertices.push(FaceVertex { uv, position });
let [a, b, c] = triangles[host];
triangles[host] = [a, b, index];
triangles.push([b, c, index]);
triangles.push([c, a, index]);
}
}
lawson_flips(vertices, triangles, boundary_pairs, scale);
Ok(())
}
pub(in crate::watertight_tessellation) fn refine_interior(
face: &FaceRecord,
vertices: &mut Vec<FaceVertex>,
triangles: &mut Vec<[usize; 3]>,
boundary_pairs: &HashSet<(usize, usize)>,
chord_tolerance: f64,
scale: [f64; 2],
periodic: bool,
) -> Result<(), String> {
let _timer = stage_timer(Stage::Refine);
lawson_flips(vertices, triangles, boundary_pairs, scale);
for _ in 0..MAX_REFINE_PASSES {
tess_profile(|p| p.refine_passes += 1);
let mut corners: Vec<(usize, usize, usize)> = Vec::with_capacity(triangles.len() * 3);
for (triangle_index, triangle) in triangles.iter().enumerate() {
for corner in 0..3 {
let a = triangle[corner];
let b = triangle[(corner + 1) % 3];
corners.push((a.min(b), a.max(b), triangle_index));
}
}
corners.sort_unstable();
let mut splits: HashMap<(usize, usize), usize> = HashMap::new();
let mut group = 0usize;
while group < corners.len() {
let (a, b, _) = corners[group];
let mut end = group + 1;
while end < corners.len() && corners[end].0 == a && corners[end].1 == b {
end += 1;
}
let users = end - group;
group = end;
if boundary_pairs.contains(&(a, b)) {
continue;
}
if users != 2 {
continue;
}
let uv_mid = [
(vertices[a].uv[0] + vertices[b].uv[0]) * 0.5,
(vertices[a].uv[1] + vertices[b].uv[1]) * 0.5,
];
let surface_mid = if periodic {
face.surface.evaluate_extended(uv_mid[0], uv_mid[1])?
} else {
face.surface.evaluate(uv_mid[0], uv_mid[1])?
};
let chord_sag =
deflection_to_chord(surface_mid, vertices[a].position, vertices[b].position);
if chord_sag <= chord_tolerance {
let angular_gated = chord_sag < ANGULAR_SAG_FLOOR * chord_tolerance
|| interior_edge_within_angle(face, vertices[a].uv, vertices[b].uv, periodic);
if angular_gated {
continue;
}
}
let index = vertices.len();
vertices.push(FaceVertex {
uv: uv_mid,
position: surface_mid,
});
splits.insert((a, b), index);
tess_profile(|p| p.splits += 1);
}
if std::env::var("BREP_DEBUG_TESS").is_ok() {
let mut bands = [0usize; 5];
let mut worst_sag = 0.0f64;
for (&(a, b), &mid) in &splits {
let v = vertices[mid].uv[1];
let band = ((v * 5.0) as usize).min(4);
bands[band] += 1;
let surface_mid = vertices[mid].position;
let sag = deflection_to_chord(
surface_mid,
vertices[a].position,
vertices[b].position,
);
worst_sag = worst_sag.max(sag);
}
eprintln!(
" refine pass: {} triangles, {} splits v-bands={:?} worst_sag={:.3e}",
triangles.len(),
splits.len(),
bands,
worst_sag
);
}
if splits.is_empty() {
lawson_flips_guarded(
vertices,
triangles,
boundary_pairs,
scale,
Some((face, chord_tolerance, periodic)),
);
return Ok(());
}
let mut next_triangles = Vec::with_capacity(triangles.len() + splits.len() * 2);
for triangle in triangles.iter() {
let mut polygon: Vec<usize> = Vec::with_capacity(6);
let mut first_midpoint: Option<usize> = None;
for corner in 0..3 {
let a = triangle[corner];
let b = triangle[(corner + 1) % 3];
polygon.push(a);
if let Some(&middle) = splits.get(&(a.min(b), a.max(b))) {
if first_midpoint.is_none() {
first_midpoint = Some(polygon.len());
}
polygon.push(middle);
}
}
let start = first_midpoint.unwrap_or(0);
for offset in 1..polygon.len() - 1 {
next_triangles.push([
polygon[start],
polygon[(start + offset) % polygon.len()],
polygon[(start + offset + 1) % polygon.len()],
]);
}
}
*triangles = next_triangles;
lawson_flips_guarded(
vertices,
triangles,
boundary_pairs,
scale,
Some((face, chord_tolerance, periodic)),
);
}
lawson_flips_guarded(
vertices,
triangles,
boundary_pairs,
scale,
Some((face, chord_tolerance, periodic)),
);
Ok(())
}
fn interior_edge_within_angle(
face: &FaceRecord,
uv_a: [f64; 2],
uv_b: [f64; 2],
periodic: bool,
) -> bool {
let normal = |uv: [f64; 2]| -> Option<Vec3> {
let (_, su, sv) = if periodic {
face.surface.deriv1_extended(uv[0], uv[1]).ok()?
} else {
face.surface.deriv1(uv[0], uv[1]).ok()?
};
su.cross(sv).normalized().ok()
};
let (Some(na), Some(nb)) = (normal(uv_a), normal(uv_b)) else {
return true;
};
na.dot(nb) >= ANGULAR_TOL.cos()
}
pub(in crate::watertight_tessellation) fn face_normal_at(face: &FaceRecord, uv: [f64; 2], domains: [f64; 4]) -> Result<Vec3, String> {
let (_, su, sv) = face.surface.deriv1(uv[0], uv[1])?;
let mut normal = su.cross(sv);
if normal.length() <= 1e-12 {
let [u0, u1, v0, v1] = domains;
let epsilon = 1e-4;
let nudged_u = uv[0].clamp(u0 + epsilon * (u1 - u0), u1 - epsilon * (u1 - u0));
let nudged_v = uv[1].clamp(v0 + epsilon * (v1 - v0), v1 - epsilon * (v1 - v0));
let (_, nsu, nsv) = face.surface.deriv1(nudged_u, nudged_v)?;
normal = nsu.cross(nsv);
if normal.length() <= 1e-12 {
normal = Vec3::new(0.0, 0.0, 1.0);
}
}
let mut normal = normal.normalized()?;
if !face.same_sense {
normal = normal.scale(-1.0);
}
Ok(normal)
}