use super::*;
use super::earclip::{crossing_parity_inside, point_in_triangle};
pub(in crate::watertight_tessellation) fn bridge_holes(outer: Vec<FaceVertex>, holes: Vec<Vec<FaceVertex>>) -> Vec<FaceVertex> {
let mut polygon = outer;
let mut remaining: Vec<Vec<FaceVertex>> = holes;
remaining.sort_by(|a, b| {
let max_u = |chain: &Vec<FaceVertex>| {
chain
.iter()
.map(|vertex| vertex.uv[0])
.fold(f64::NEG_INFINITY, f64::max)
};
let max_v = |chain: &Vec<FaceVertex>| {
chain
.iter()
.map(|vertex| vertex.uv[1])
.fold(f64::NEG_INFINITY, f64::max)
};
max_u(b)
.total_cmp(&max_u(a))
.then_with(|| max_v(b).total_cmp(&max_v(a)))
});
while !remaining.is_empty() {
let mut selected: Option<(f64, usize, usize, usize, Vec<FaceVertex>)> = None;
for (hole_index, hole) in remaining.iter().enumerate() {
let hole_anchor = (0..hole.len())
.max_by(|&a, &b| hole[a].uv[0].total_cmp(&hole[b].uv[0]))
.unwrap_or(0);
if let Some((target, merged)) = simple_hole_bridge(&polygon, hole, hole_anchor) {
let a = hole[hole_anchor].uv;
let b = polygon[target].uv;
let distance = (b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2);
if remaining.len() >= 64
&& bridge_avoids_other_holes(
a,
b,
&remaining,
hole_index,
bridge_epsilon(&polygon),
)
{
selected = Some((distance, hole_index, hole_anchor, target, merged));
break;
}
if selected.as_ref().is_none_or(|best| distance < best.0) {
selected = Some((distance, hole_index, hole_anchor, target, merged));
}
}
}
let (_, hole_index, hole_anchor, target, merged) = selected.unwrap_or_else(|| {
let hole = &remaining[0];
let hole_anchor = (0..hole.len())
.max_by(|&a, &b| hole[a].uv[0].total_cmp(&hole[b].uv[0]))
.unwrap_or(0);
let target = bridge_target(&polygon, hole, hole_anchor);
let merged = merge_hole_bridge(&polygon, hole, hole_anchor, target);
(f64::INFINITY, 0, hole_anchor, target, merged)
});
let hole = remaining.remove(hole_index);
let anchor = hole[hole_anchor];
if std::env::var("BREP_DEBUG_TESS_BRIDGE").is_ok() {
eprintln!(
"bridge: anchor=({:.3},{:.3}) -> polygon[{target}]=({:.3},{:.3}) len={}",
anchor.uv[0],
anchor.uv[1],
polygon[target].uv[0],
polygon[target].uv[1],
polygon.len(),
);
}
polygon = merged;
if std::env::var("BREP_DEBUG_TESS_BRIDGE").is_ok() {
eprintln!(" merged simple={}", polygon_is_simple(&polygon));
}
}
polygon
}
fn bridge_epsilon(polygon: &[FaceVertex]) -> f64 {
let total: f64 = (0..polygon.len())
.map(|index| {
let a = polygon[index].uv;
let b = polygon[(index + 1) % polygon.len()].uv;
((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt()
})
.sum();
(total / polygon.len().max(1) as f64).powi(2) * 1e-12
}
fn bridge_avoids_other_holes(
anchor: [f64; 2],
target: [f64; 2],
holes: &[Vec<FaceVertex>],
selected_hole: usize,
epsilon: f64,
) -> bool {
for (hole_index, hole) in holes.iter().enumerate() {
if hole_index == selected_hole {
continue;
}
for edge in 0..hole.len() {
let a = hole[edge].uv;
let b = hole[(edge + 1) % hole.len()].uv;
if segments_properly_cross(anchor, target, a, b, epsilon)
|| point_strictly_on_segment(a, anchor, target, epsilon)
{
return false;
}
}
}
true
}
fn merge_hole_bridge(
polygon: &[FaceVertex],
hole: &[FaceVertex],
hole_anchor: usize,
target: usize,
) -> Vec<FaceVertex> {
let mut merged = Vec::with_capacity(polygon.len() + hole.len() + 2);
merged.extend_from_slice(&polygon[..=target]);
for offset in 0..=hole.len() {
merged.push(hole[(hole_anchor + offset) % hole.len()]);
}
merged.extend_from_slice(&polygon[target..]);
merged
}
fn simple_hole_bridge(
polygon: &[FaceVertex],
hole: &[FaceVertex],
hole_anchor: usize,
) -> Option<(usize, Vec<FaceVertex>)> {
let preferred = bridge_target(polygon, hole, hole_anchor);
let merged = merge_hole_bridge(polygon, hole, hole_anchor, preferred);
if polygon_is_simple(&merged) {
return Some((preferred, merged));
}
let anchor = hole[hole_anchor].uv;
let epsilon = bridge_epsilon(polygon);
let mut candidates: Vec<(f64, usize)> = (0..polygon.len())
.filter_map(|target| {
let point = polygon[target].uv;
let distance = (point[0] - anchor[0]).powi(2) + (point[1] - anchor[1]).powi(2);
(target != preferred && distance > epsilon).then_some((distance, target))
})
.collect();
candidates.sort_by(|a, b| a.0.total_cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
for (_, target) in candidates {
if !bridge_is_clear(polygon, hole, hole_anchor, target, epsilon) {
continue;
}
let merged = merge_hole_bridge(polygon, hole, hole_anchor, target);
if polygon_is_simple(&merged) {
return Some((target, merged));
}
}
None
}
pub(in crate::watertight_tessellation) fn bridge_target(polygon: &[FaceVertex], hole: &[FaceVertex], hole_anchor: usize) -> usize {
let anchor = hole[hole_anchor].uv;
nearest_clear_bridge_target(polygon, hole, hole_anchor)
.unwrap_or_else(|| eberly_bridge_target(polygon, anchor))
}
pub(in crate::watertight_tessellation) fn eberly_bridge_target(polygon: &[FaceVertex], anchor: [f64; 2]) -> usize {
let count = polygon.len();
let mut best_hit: Option<(f64, usize)> = None; for index in 0..count {
let a = polygon[index].uv;
let b = polygon[(index + 1) % count].uv;
let (low, high) = if a[1] <= b[1] { (a, b) } else { (b, a) };
if !(low[1] <= anchor[1] && anchor[1] < high[1]) {
continue;
}
let t = (anchor[1] - a[1]) / (b[1] - a[1]);
let u_hit = a[0] + (b[0] - a[0]) * t;
if u_hit < anchor[0] {
continue;
}
if best_hit.is_none_or(|(best_u, _)| u_hit < best_u) {
best_hit = Some((u_hit, index));
}
}
let Some((u_hit, edge_index)) = best_hit else {
return (0..count)
.min_by(|&a, &b| {
let cost = |vertex: &FaceVertex| {
let du = vertex.uv[0] - anchor[0];
let dv = vertex.uv[1] - anchor[1];
du * du + dv * dv + if du < 0.0 { 1e6 } else { 0.0 }
};
cost(&polygon[a]).total_cmp(&cost(&polygon[b]))
})
.unwrap_or(0);
};
let a = polygon[edge_index].uv;
let b = polygon[(edge_index + 1) % count].uv;
let candidate = if a[0] > b[0] {
edge_index
} else {
(edge_index + 1) % count
};
let hit = [u_hit, anchor[1]];
let candidate_uv = polygon[candidate].uv;
let mut refined: Option<(f64, f64, usize)> = None; for index in 0..count {
if index == candidate {
continue;
}
let point = polygon[index].uv;
let previous = polygon[(index + count - 1) % count].uv;
let next = polygon[(index + 1) % count].uv;
let turn = (point[0] - previous[0]) * (next[1] - point[1])
- (point[1] - previous[1]) * (next[0] - point[0]);
if turn >= 0.0 {
continue;
}
if point[0] <= anchor[0] {
continue;
}
if !point_in_triangle(point, anchor, hit, candidate_uv) {
continue;
}
let tangent = (point[1] - anchor[1]).abs() / (point[0] - anchor[0]);
let distance = (point[0] - anchor[0]).powi(2) + (point[1] - anchor[1]).powi(2);
if refined.is_none_or(|(best_tan, best_dist, _)| {
tangent < best_tan || (tangent == best_tan && distance < best_dist)
}) {
refined = Some((tangent, distance, index));
}
}
refined.map(|(_, _, index)| index).unwrap_or(candidate)
}
fn nearest_clear_bridge_target(
polygon: &[FaceVertex],
hole: &[FaceVertex],
hole_anchor: usize,
) -> Option<usize> {
let anchor = hole[hole_anchor].uv;
let count = polygon.len();
if count < 3 {
return None;
}
let mut total = 0.0f64;
for index in 0..count {
let a = polygon[index].uv;
let b = polygon[(index + 1) % count].uv;
total += ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
}
let epsilon = (total / count as f64).powi(2) * 1e-12;
let uv_key = |uv: [f64; 2]| {
let bits = |value: f64| if value == 0.0 { 0 } else { value.to_bits() };
(bits(uv[0]), bits(uv[1]))
};
let mut uv_counts: FxHashMap<(u64, u64), usize> = FxHashMap::default();
for vertex in polygon {
*uv_counts.entry(uv_key(vertex.uv)).or_default() += 1;
}
let carries_bridge: Vec<bool> = polygon
.iter()
.map(|vertex| uv_counts[&uv_key(vertex.uv)] > 1)
.collect();
let mut candidates: Vec<(f64, usize)> = (0..count)
.filter_map(|index| {
let target = polygon[index].uv;
let distance = (target[0] - anchor[0]).powi(2) + (target[1] - anchor[1]).powi(2);
(distance > epsilon).then_some((distance, index))
})
.collect();
candidates.sort_by(|a, b| a.0.total_cmp(&b.0));
for allow_bridged in [false, true] {
for &(_, index) in &candidates {
if carries_bridge[index] != allow_bridged {
continue;
}
if bridge_is_clear(polygon, hole, hole_anchor, index, epsilon) {
return Some(index);
}
}
}
None
}
pub(in crate::watertight_tessellation) fn bridge_is_clear(
polygon: &[FaceVertex],
hole: &[FaceVertex],
hole_anchor: usize,
target_index: usize,
epsilon: f64,
) -> bool {
let anchor = hole[hole_anchor].uv;
let target = polygon[target_index].uv;
let count = polygon.len();
for offset in 0..count {
let e0 = offset;
let e1 = (offset + 1) % count;
if e0 != target_index && e1 != target_index {
let p = polygon[e0].uv;
let q = polygon[e1].uv;
if segments_properly_cross(anchor, target, p, q, epsilon) {
return false;
}
}
if e0 != target_index && point_strictly_on_segment(polygon[e0].uv, anchor, target, epsilon)
{
return false;
}
}
let hole_count = hole.len();
for offset in 0..hole_count {
let h0 = offset;
let h1 = (offset + 1) % hole_count;
if h0 != hole_anchor
&& h1 != hole_anchor
&& segments_properly_cross(anchor, target, hole[h0].uv, hole[h1].uv, epsilon)
{
return false;
}
if h0 != hole_anchor && point_strictly_on_segment(hole[h0].uv, anchor, target, epsilon) {
return false;
}
}
let midpoint = [(anchor[0] + target[0]) * 0.5, (anchor[1] + target[1]) * 0.5];
point_in_polygon(polygon, midpoint)
}
fn point_strictly_on_segment(p: [f64; 2], a: [f64; 2], b: [f64; 2], epsilon: f64) -> bool {
if triangle_area(a, b, p).abs() > epsilon {
return false;
}
let length2 = (b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2);
if length2 <= epsilon {
return false;
}
let t = ((p[0] - a[0]) * (b[0] - a[0]) + (p[1] - a[1]) * (b[1] - a[1])) / length2;
let margin = (epsilon / length2).sqrt();
t > margin && t < 1.0 - margin
}
pub(super) fn point_in_polygon(polygon: &[FaceVertex], p: [f64; 2]) -> bool {
crossing_parity_inside(polygon.len(), |offset| polygon[offset].uv, p)
}