use super::*;
pub(in crate::watertight_tessellation) fn unwrap_seam_crossing_trim(
face: &FaceRecord,
chains: &[Vec<FaceVertex>],
domains: [f64; 4],
closed_u: bool,
closed_v: bool,
chord_tolerance: f64,
) -> Result<Option<(Vec<FaceVertex>, bool)>, String> {
if closed_u == closed_v || chains.len() != 1 {
return Ok(None);
}
let [u0, u1, v0, v1] = domains;
let p_is_u = closed_u;
let (p0, p1) = if p_is_u { (u0, u1) } else { (v0, v1) };
let (q0, q1) = if p_is_u { (v0, v1) } else { (u0, u1) };
let period = p1 - p0;
if !(period > 0.0) {
return Ok(None);
}
let cross_extent = (q1 - q0).abs();
let mut cross_second: f64 = 0.0;
let mut periodic_second: 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 = face.surface.derivatives(u, v, 2)?;
let second = if p_is_u {
derivatives[0][2]
} else {
derivatives[2][0]
};
cross_second = cross_second.max(second.length());
let p_second = if p_is_u {
derivatives[2][0]
} else {
derivatives[0][2]
};
periodic_second = periodic_second.max(p_second.length());
}
}
if cross_second * cross_extent * cross_extent > 8.0 * chord_tolerance {
return Ok(None);
}
let p_of = |vertex: &FaceVertex| if p_is_u { vertex.uv[0] } else { vertex.uv[1] };
let chain = &chains[0];
let count = chain.len();
if count < 3 {
return Ok(None);
}
let mut unwrapped: Vec<FaceVertex> = Vec::with_capacity(count);
let mut offset = 0.0f64;
let mut jumps = 0usize;
let mut previous = p_of(&chain[0]);
for (index, vertex) in chain.iter().enumerate() {
let raw = p_of(vertex);
if index > 0 {
let delta = raw - previous;
if delta > 0.5 * period {
offset -= period;
jumps += 1;
} else if delta < -0.5 * period {
offset += period;
jumps += 1;
}
}
previous = raw;
let mut uv = vertex.uv;
if p_is_u {
uv[0] = raw + offset;
} else {
uv[1] = raw + offset;
}
unwrapped.push(FaceVertex {
uv,
position: vertex.position,
});
}
if jumps == 0 {
return Ok(None); }
let closing_delta = p_of(&chain[0]) - previous;
let closing_offset = if closing_delta > 0.5 * period {
offset - period
} else if closing_delta < -0.5 * period {
offset + period
} else {
offset
};
if closing_offset.abs() > 0.5 * period {
return Ok(None);
}
let (mut pmin, mut pmax) = (f64::INFINITY, f64::NEG_INFINITY);
for vertex in &unwrapped {
let p = if p_is_u { vertex.uv[0] } else { vertex.uv[1] };
pmin = pmin.min(p);
pmax = pmax.max(p);
}
if unwrapped.len() < 3 {
return Ok(None);
}
let span = pmax - pmin;
let periodic_sag_exceeds = periodic_second * span * span > 8.0 * chord_tolerance;
let needs_periodic_interior = span > 1.5 * period || periodic_sag_exceeds;
if needs_periodic_interior && !polygon_is_simple(&unwrapped) {
return Ok(None);
}
if signed_area(&unwrapped) < 0.0 {
unwrapped.reverse();
}
Ok(Some((unwrapped, needs_periodic_interior)))
}
pub(in crate::watertight_tessellation) fn polygon_is_simple(polygon: &[FaceVertex]) -> bool {
let _timer = stage_timer(Stage::Simple);
tess_profile(|p| p.simple_tests += 1);
let n = polygon.len();
if n < 4 {
return true;
}
let mut total = 0.0f64;
for i in 0..n {
let a = polygon[i].uv;
let b = polygon[(i + 1) % n].uv;
total += ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
}
let epsilon = (total / n as f64).powi(2) * 1e-12;
let mut edges: Vec<(f64, f64, f64, f64, usize)> = (0..n)
.map(|index| {
let a = polygon[index].uv;
let b = polygon[(index + 1) % n].uv;
(
a[0].min(b[0]),
a[0].max(b[0]),
a[1].min(b[1]),
a[1].max(b[1]),
index,
)
})
.collect();
edges.sort_by(|a, b| a.0.total_cmp(&b.0).then_with(|| a.4.cmp(&b.4)));
for left in 0..edges.len() {
let (_, max_u, min_v, max_v, i) = edges[left];
let a = polygon[i].uv;
let b = polygon[(i + 1) % n].uv;
for &(other_min_u, _, other_min_v, other_max_v, j) in &edges[left + 1..] {
if other_min_u > max_u {
break;
}
if other_min_v > max_v || other_max_v < min_v {
continue;
}
if j == i || (j + 1) % n == i || (i + 1) % n == j {
continue;
}
let c = polygon[j].uv;
let d = polygon[(j + 1) % n].uv;
if segments_properly_cross(a, b, c, d, epsilon) {
return false;
}
}
}
true
}
pub(in crate::watertight_tessellation) fn seam_straddle_wall_polygons(
face: &FaceRecord,
chains: &[Vec<FaceVertex>],
domains: [f64; 4],
closed_u: bool,
closed_v: bool,
chord_tolerance: f64,
) -> Result<Option<(Vec<FaceVertex>, Vec<Vec<FaceVertex>>)>, String> {
if closed_u == closed_v || chains.is_empty() {
return Ok(None);
}
let [u0, u1, v0, v1] = domains;
let p_is_u = closed_u;
let (p0, p1) = if p_is_u { (u0, u1) } else { (v0, v1) };
let (q0, q1) = if p_is_u { (v0, v1) } else { (u0, u1) };
let period = p1 - p0;
let q_extent = q1 - q0;
if !(period > 0.0) || !(q_extent.abs() > 0.0) {
return Ok(None);
}
let p_of = |vertex: &FaceVertex| if p_is_u { vertex.uv[0] } else { vertex.uv[1] };
let q_of = |vertex: &FaceVertex| if p_is_u { vertex.uv[1] } else { vertex.uv[0] };
let mk = |p: f64, q: f64| -> [f64; 2] {
if p_is_u {
[p, q]
} else {
[q, p]
}
};
let outer_index = (0..chains.len()).max_by(|&a, &b| {
signed_area(&chains[a])
.abs()
.total_cmp(&signed_area(&chains[b]).abs())
});
let Some(outer_index) = outer_index else {
return Ok(None);
};
let outer = &chains[outer_index];
let dbg = std::env::var("BREP_DEBUG_STRADDLE").is_ok();
if outer.len() < 4 || !polygon_is_simple(outer) {
if dbg {
eprintln!("[straddle] bail: outer len={} simple={}", outer.len(), polygon_is_simple(outer));
}
return Ok(None);
}
let (mut qrim_lo, mut qrim_hi) = (f64::INFINITY, f64::NEG_INFINITY);
for vertex in outer {
let q = q_of(vertex);
qrim_lo = qrim_lo.min(q);
qrim_hi = qrim_hi.max(q);
}
let rim_extent = qrim_hi - qrim_lo;
if !(rim_extent > 0.0) {
return Ok(None);
}
let p_tol = 1e-3 * period;
let q_tol = 1e-3 * rim_extent;
let mut bottom: Vec<FaceVertex> = Vec::new();
let mut top: Vec<FaceVertex> = Vec::new();
for vertex in outer {
let (p, q) = (p_of(vertex), q_of(vertex));
let on_q0 = (q - qrim_lo).abs() <= q_tol;
let on_q1 = (q - qrim_hi).abs() <= q_tol;
let on_seam = (p - p0).abs() <= p_tol || (p - p1).abs() <= p_tol;
if on_q0 {
bottom.push(*vertex);
} else if on_q1 {
top.push(*vertex);
} else if !on_seam {
if dbg {
eprintln!("[straddle] bail: interior outer vertex p={p:.5} q={q:.5}");
}
return Ok(None);
}
}
if bottom.len() < 2 || top.len() < 2 {
if dbg {
eprintln!("[straddle] bail: bottom={} top={}", bottom.len(), top.len());
}
return Ok(None);
}
bottom.sort_by(|a, b| p_of(a).total_cmp(&p_of(b)));
top.sort_by(|a, b| p_of(b).total_cmp(&p_of(a)));
let mut interior: Vec<Vec<FaceVertex>> = Vec::new();
let mut right_arcs: Vec<Vec<FaceVertex>> = Vec::new();
let mut left_arcs: Vec<Vec<FaceVertex>> = Vec::new();
for (index, chain) in chains.iter().enumerate() {
if index == outer_index {
continue;
}
let (mut pmin, mut pmax) = (f64::INFINITY, f64::NEG_INFINITY);
for vertex in chain {
let p = p_of(vertex);
pmin = pmin.min(p);
pmax = pmax.max(p);
}
if pmax - pmin > 0.5 * period {
let n = chain.len();
let mut net = 0.0;
for i in 0..n {
let mut delta = p_of(&chain[(i + 1) % n]) - p_of(&chain[i]);
if delta > 0.5 * period {
delta -= period;
} else if delta < -0.5 * period {
delta += period;
}
net += delta;
}
if net.abs() > 0.25 * period {
if dbg {
eprintln!("[straddle] bail: full-wrap loop {index} (net={net:.4})");
}
return Ok(None);
}
let Some((right, left)) = super::rim::split_seam_arcs(chain, [p0, p1], p_is_u) else {
if dbg {
eprintln!("[straddle] bail: split_arcs failed on loop {index} (n={})", chain.len());
}
return Ok(None);
};
right_arcs.push(right);
left_arcs.push(left);
} else {
interior.push(chain.clone());
}
}
if right_arcs.is_empty() {
if dbg {
eprintln!("[straddle] bail: no straddle holes (interior={})", interior.len());
}
return Ok(None);
}
let build_seam = |p_seam: f64,
arcs: &[Vec<FaceVertex>]|
-> Result<Vec<FaceVertex>, String> {
let base = seam_meridian_samples(
face,
p_seam,
p_is_u,
qrim_lo,
qrim_hi,
chord_tolerance,
false,
)?;
let mut ranges: Vec<(f64, f64, usize)> = arcs
.iter()
.enumerate()
.map(|(index, arc)| {
let qa = q_of(&arc[0]);
let qb = q_of(arc.last().unwrap());
(qa.min(qb), qa.max(qb), index)
})
.collect();
ranges.sort_by(|a, b| a.0.total_cmp(&b.0));
let q_lo = qrim_lo;
let q_hi = qrim_hi;
let border_eps = 1e-9 * rim_extent.max(1.0);
let mut column: Vec<FaceVertex> = Vec::new();
let mut cursor = q_lo;
for &(range_lo, range_hi, arc_index) in &ranges {
for &(q, position) in &base {
if q > cursor + border_eps
&& q > q_lo + border_eps
&& q < range_lo - border_eps
{
column.push(FaceVertex {
uv: mk(p_seam, q),
position,
});
}
}
for vertex in &arcs[arc_index] {
column.push(FaceVertex {
uv: mk(p_seam, q_of(vertex)),
position: vertex.position,
});
}
cursor = range_hi;
}
for &(q, position) in &base {
if q > cursor + border_eps && q < q_hi - border_eps {
column.push(FaceVertex {
uv: mk(p_seam, q),
position,
});
}
}
Ok(column)
};
let right_seam = build_seam(p1, &right_arcs)?;
let mut left_seam = build_seam(p0, &left_arcs)?;
left_seam.reverse();
let mut polygon: Vec<FaceVertex> = Vec::new();
polygon.extend(bottom.iter().copied());
polygon.extend(right_seam);
polygon.extend(top.iter().copied());
polygon.extend(left_seam);
if polygon.len() < 3 || !polygon_is_simple(&polygon) {
if dbg {
eprintln!("[straddle] bail: assembled polygon len={} simple={}", polygon.len(), polygon_is_simple(&polygon));
}
return Ok(None);
}
if signed_area(&polygon) < 0.0 {
polygon.reverse();
}
let holes = interior
.into_iter()
.map(|mut hole| {
if signed_area(&hole) > 0.0 {
hole.reverse();
}
hole
})
.collect::<Vec<_>>();
Ok(Some((polygon, holes)))
}
#[allow(clippy::too_many_arguments)]
pub(in crate::watertight_tessellation) fn close_periodic_recut_wall(
face: &FaceRecord,
chains: &[Vec<FaceVertex>],
domains: [f64; 4],
closed_u: bool,
closed_v: bool,
chord_tolerance: f64,
) -> Result<Option<(Vec<FaceVertex>, Vec<Vec<FaceVertex>>)>, String> {
if closed_u == closed_v || chains.is_empty() {
return Ok(None);
}
let [u0, u1, v0, v1] = domains;
let p_is_u = closed_u;
let (p0, p1) = if p_is_u { (u0, u1) } else { (v0, v1) };
let (q0, q1) = if p_is_u { (v0, v1) } else { (u0, u1) };
let period = p1 - p0;
let q_extent = (q1 - q0).abs();
if !(period > 0.0) || !(q_extent > 0.0) {
return Ok(None);
}
let dbg = std::env::var("BREP_DEBUG_RECUT").is_ok();
let p_of = |vertex: &FaceVertex| if p_is_u { vertex.uv[0] } else { vertex.uv[1] };
let q_of = |vertex: &FaceVertex| if p_is_u { vertex.uv[1] } else { vertex.uv[0] };
let mk = |p: f64, q: f64| -> [f64; 2] { if p_is_u { [p, q] } else { [q, p] } };
let Some(outer_index) = (0..chains.len()).max_by(|&a, &b| {
signed_area(&chains[a])
.abs()
.total_cmp(&signed_area(&chains[b]).abs())
}) else {
return Ok(None);
};
let outer = &chains[outer_index];
if outer.len() < 4 || !polygon_is_simple(outer) {
return Ok(None);
}
let (mut qrim_lo, mut qrim_hi) = (f64::INFINITY, f64::NEG_INFINITY);
for vertex in outer {
let q = q_of(vertex);
qrim_lo = qrim_lo.min(q);
qrim_hi = qrim_hi.max(q);
}
let rim_extent = qrim_hi - qrim_lo;
if !(rim_extent > 0.0) {
return Ok(None);
}
let p_tol = 1e-3 * period;
let q_tol = 1e-3 * rim_extent;
let mut bottom: Vec<FaceVertex> = Vec::new();
let mut top: Vec<FaceVertex> = Vec::new();
for vertex in outer {
let (p, q) = (p_of(vertex), q_of(vertex));
let on_lo = (q - qrim_lo).abs() <= q_tol;
let on_hi = (q - qrim_hi).abs() <= q_tol;
let on_seam = (p - p0).abs() <= p_tol || (p - p1).abs() <= p_tol;
if on_lo {
bottom.push(*vertex);
} else if on_hi {
top.push(*vertex);
} else if !on_seam {
if dbg {
eprintln!("[recut] bail: interior outer vertex p={p:.5} q={q:.5}");
}
return Ok(None);
}
}
if bottom.len() < 2 || top.len() < 2 {
return Ok(None);
}
let domain_area = (period * rim_extent).abs();
let hole_eps = 1e-9 * domain_area.max(1e-30);
let mut hole_indices: Vec<usize> = Vec::new();
let mut angles: Vec<f64> = Vec::new(); let mut any_straddle = false;
for (index, chain) in chains.iter().enumerate() {
if index == outer_index {
continue;
}
if signed_area(chain).abs() <= hole_eps {
continue;
}
let n = chain.len();
let (mut pmin, mut pmax) = (f64::INFINITY, f64::NEG_INFINITY);
let mut net = 0.0;
for vertex in chain {
let p = p_of(vertex);
pmin = pmin.min(p);
pmax = pmax.max(p);
}
for i in 0..n {
let mut delta = p_of(&chain[(i + 1) % n]) - p_of(&chain[i]);
if delta > 0.5 * period {
delta -= period;
} else if delta < -0.5 * period {
delta += period;
}
net += delta;
}
if net.abs() > 0.25 * period {
if dbg {
eprintln!("[recut] bail: full-wrap loop {index} (net={net:.4})");
}
return Ok(None);
}
let straddles =
pmax - pmin > 0.5 * period || pmin <= p0 + p_tol || pmax >= p1 - p_tol;
any_straddle = any_straddle || straddles;
hole_indices.push(index);
for vertex in chain {
let a = (p_of(vertex) - p0).rem_euclid(period);
angles.push(a);
}
}
if !any_straddle || hole_indices.is_empty() {
return Ok(None);
}
angles.sort_by(|a, b| a.total_cmp(b));
let (mut gap, mut gap_center) = (0.0f64, 0.0f64);
for w in angles.windows(2) {
let g = w[1] - w[0];
if g > gap {
gap = g;
gap_center = 0.5 * (w[0] + w[1]);
}
}
let wrap = (angles[0] + period) - angles[angles.len() - 1];
if wrap > gap {
gap = wrap;
gap_center = (0.5 * (angles[angles.len() - 1] + angles[0] + period)).rem_euclid(period);
}
if gap < 0.02 * period {
if dbg {
eprintln!("[recut] bail: no hole-free meridian (largest gap={:.4} of period)", gap / period);
}
return Ok(None);
}
let seam_p = p0 + gap_center;
let shift = |p: f64| -> f64 { if p < seam_p { p + period } else { p } };
let mut bottom_s: Vec<FaceVertex> = bottom
.iter()
.map(|vertex| FaceVertex {
uv: mk(shift(p_of(vertex)), q_of(vertex)),
position: vertex.position,
})
.collect();
let mut top_s: Vec<FaceVertex> = top
.iter()
.map(|vertex| FaceVertex {
uv: mk(shift(p_of(vertex)), q_of(vertex)),
position: vertex.position,
})
.collect();
bottom_s.sort_by(|a, b| p_of(a).total_cmp(&p_of(b)));
top_s.sort_by(|a, b| p_of(a).total_cmp(&p_of(b)));
let dedup = |v: &mut FaceVertex, w: &mut FaceVertex| -> bool {
(v.uv[0] - w.uv[0]).abs() <= p_tol && (v.uv[1] - w.uv[1]).abs() <= q_tol
};
bottom_s.dedup_by(dedup);
top_s.dedup_by(dedup);
if bottom_s.len() < 2 || top_s.len() < 2 {
return Ok(None);
}
let bl = bottom_s[0];
let tl = top_s[0];
let (p_bl, q_bl) = (p_of(&bl), q_of(&bl));
let (p_tl, q_tl) = (p_of(&tl), q_of(&tl));
if (p_bl - seam_p).abs() > 0.25 * gap || (p_tl - seam_p).abs() > 0.25 * gap {
if dbg {
eprintln!("[recut] bail: rim too sparse near cut (p_bl={p_bl:.4} p_tl={p_tl:.4} seam_p={seam_p:.4} gap={:.4})", gap);
}
return Ok(None);
}
let eval_diag = |q: f64| -> Result<(f64, Vec3), String> {
let t = if (q_tl - q_bl).abs() > 1e-30 {
(q - q_bl) / (q_tl - q_bl)
} else {
0.0
};
let p = p_bl + (p_tl - p_bl) * t;
let position = if p_is_u {
face.surface.evaluate_extended(p, q)?
} else {
face.surface.evaluate_extended(q, p)?
};
Ok((p, position))
};
let mut qs = vec![q_bl, q_tl];
loop {
let mut refined = Vec::with_capacity(qs.len() * 2);
let mut split = false;
for pair in qs.windows(2) {
refined.push(pair[0]);
let mid = 0.5 * (pair[0] + pair[1]);
if deflection_to_chord(eval_diag(mid)?.1, eval_diag(pair[0])?.1, eval_diag(pair[1])?.1)
> chord_tolerance
{
refined.push(mid);
split = true;
}
}
refined.push(*qs.last().unwrap());
qs = refined;
if !split || qs.len() > MAX_EDGE_SPANS {
break;
}
}
let diagonal: Vec<(f64, f64, Vec3)> = qs
.into_iter()
.map(|q| {
let (p, position) = eval_diag(q)?;
Ok::<_, String>((p, q, position))
})
.collect::<Result<Vec<_>, _>>()?;
if diagonal.len() < 2 {
return Ok(None);
}
let mut polygon: Vec<FaceVertex> = Vec::new();
polygon.extend(bottom_s.iter().copied());
polygon.push(FaceVertex { uv: mk(p_bl + period, q_bl), position: bl.position });
for &(p, q, position) in &diagonal[1..diagonal.len() - 1] {
polygon.push(FaceVertex { uv: mk(p + period, q), position });
}
polygon.push(FaceVertex { uv: mk(p_tl + period, q_tl), position: tl.position });
for vertex in top_s.iter().rev() {
polygon.push(*vertex);
}
for &(p, q, position) in diagonal[1..diagonal.len() - 1].iter().rev() {
polygon.push(FaceVertex { uv: mk(p, q), position });
}
polygon.dedup_by(|a, b| {
(a.uv[0] - b.uv[0]).abs() <= p_tol && (a.uv[1] - b.uv[1]).abs() <= q_tol
});
if polygon.len() < 3 || !polygon_is_simple(&polygon) {
if dbg {
eprintln!("[recut] bail: assembled polygon len={} simple={}", polygon.len(), polygon_is_simple(&polygon));
}
return Ok(None);
}
if signed_area(&polygon) < 0.0 {
polygon.reverse();
}
let mut holes: Vec<Vec<FaceVertex>> = Vec::new();
for &index in &hole_indices {
let mut hole: Vec<FaceVertex> = chains[index]
.iter()
.map(|vertex| FaceVertex {
uv: mk(shift(p_of(vertex)), q_of(vertex)),
position: vertex.position,
})
.collect();
if signed_area(&hole) > 0.0 {
hole.reverse();
}
holes.push(hole);
}
if dbg {
eprintln!(
"[recut] seam_p={seam_p:.4} gap={:.4} p_bl={p_bl:.4} p_tl={p_tl:.4} diag_n={} outer={} holes={} straddle={any_straddle}",
gap / period,
diagonal.len(),
polygon.len(),
holes.len()
);
}
Ok(Some((polygon, holes)))
}