use super::*;
pub fn loop_seam_offsets(
coedges: &[CoedgeRecord],
closed_u: bool,
closed_v: bool,
u_period: f64,
v_period: f64,
) -> Result<Vec<[f64; 2]>, String> {
let mut offsets = Vec::with_capacity(coedges.len());
let fold = |from: f64, to: f64, period: f64| -> f64 {
if period > 0.0 {
((from - to) / period).round() * period
} else {
0.0
}
};
let mut offset = [0.0f64, 0.0f64];
let mut previous_end: Option<[f64; 2]> = None;
let mut first_start: Option<[f64; 2]> = None;
for coedge in coedges {
let [d0, d1] = coedge.pcurve.domain()?;
let start = coedge.pcurve.evaluate(d0)?;
first_start.get_or_insert([start.x, start.y]);
if let Some(end) = previous_end {
if closed_u {
offset[0] += fold(end[0], start.x, u_period);
}
if closed_v {
offset[1] += fold(end[1], start.y, v_period);
}
}
offsets.push(offset);
let finish = coedge.pcurve.evaluate(d1)?;
previous_end = Some([finish.x, finish.y]);
}
if let (Some(end), Some(start)) = (previous_end, first_start) {
let mut closing = offset;
if closed_u {
closing[0] += fold(end[0], start[0], u_period);
}
if closed_v {
closing[1] += fold(end[1], start[1], v_period);
}
let period = u_period.abs().max(v_period.abs()).max(1.0);
if closing[0].abs() > 1e-6 * period || closing[1].abs() > 1e-6 * period {
return Ok(vec![[0.0, 0.0]; coedges.len()]);
}
}
Ok(offsets)
}
#[derive(Clone, Copy, Debug)]
pub struct SeamBand {
pub p_is_u: bool,
pub rim_index: usize,
pub cut_index: usize,
pub rim_level: f64,
pub material_above_cut: bool,
pub flat_level: f64,
}
pub(crate) fn doubly_periodic_has_only_collapsed_loops(face: &FaceRecord) -> Result<bool, String> {
if face.loops.is_empty() || face.surface.closed_directions()? != (true, true) {
return Ok(false);
}
let [u0, u1] = face.surface.domain_u()?;
let [v0, v1] = face.surface.domain_v()?;
let u_tolerance = 1e-9 * (u1 - u0).abs().max(1.0);
let v_tolerance = 1e-9 * (v1 - v0).abs().max(1.0);
for loop_record in &face.loops {
if loop_record.coedges.is_empty() {
return Ok(false);
}
let mut bounds = [
f64::INFINITY,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
];
for coedge in &loop_record.coedges {
let [t0, t1] = coedge.pcurve.domain()?;
for index in 0..=4 {
let point = coedge
.pcurve
.evaluate(t0 + (t1 - t0) * index as f64 / 4.0)?;
bounds[0] = bounds[0].min(point.x);
bounds[1] = bounds[1].max(point.x);
bounds[2] = bounds[2].min(point.y);
bounds[3] = bounds[3].max(point.y);
}
}
if bounds[1] - bounds[0] > u_tolerance || bounds[3] - bounds[2] > v_tolerance {
return Ok(false);
}
}
Ok(true)
}
pub fn analyze_doubly_periodic_seam_band(
loops_uv: &[Vec<[f64; 2]>],
domain: [f64; 4],
same_sense: bool,
) -> Option<SeamBand> {
if loops_uv.len() != 2 {
return None;
}
let [u0, u1, v0, v1] = domain;
for p_is_u in [true, false] {
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) {
continue;
}
let coord = |pt: &[f64; 2]| -> (f64, f64) {
if p_is_u {
(pt[0], pt[1])
} else {
(pt[1], pt[0])
}
};
struct Shape {
full_wrap: bool,
is_rim: bool,
single_valued: bool,
level: f64,
dir: i32,
qmin: f64,
qmax: f64,
}
let mut shapes: Vec<Shape> = Vec::with_capacity(2);
for points in loops_uv {
if points.len() < 2 {
return None;
}
let (mut qmin, mut qmax) = (f64::INFINITY, f64::NEG_INFINITY);
for pt in points {
let (_p, q) = coord(pt);
qmin = qmin.min(q);
qmax = qmax.max(q);
}
let mut net = 0.0;
let mut total_variation = 0.0;
for pair in points.windows(2) {
let mut delta = coord(&pair[1]).0 - coord(&pair[0]).0;
if delta > 0.5 * period {
delta -= period;
} else if delta < -0.5 * period {
delta += period;
}
net += delta;
total_variation += delta.abs();
}
let backward_fraction = 0.5 * (total_variation - net.abs()) / period;
let single_valued = backward_fraction <= 0.1;
let dir = if net > 0.25 * period {
1
} else if net < -0.25 * period {
-1
} else {
0
};
shapes.push(Shape {
full_wrap: (net.abs() - period).abs() <= 0.25 * period,
is_rim: (qmax - qmin) <= 0.05 * q_extent,
single_valued,
level: 0.5 * (qmin + qmax),
dir,
qmin,
qmax,
});
}
if !shapes.iter().all(|s| s.full_wrap) {
continue;
}
let (rim_index, cut_index) = match (shapes[0].is_rim, shapes[1].is_rim) {
(true, false) => (0, 1),
(false, true) => (1, 0),
_ => continue,
};
if !shapes[cut_index].single_valued {
continue;
}
let tol = 0.02 * q_extent;
let rim_at_lo = (shapes[rim_index].level - q0).abs() <= tol;
let rim_at_hi = (shapes[rim_index].level - q1).abs() <= tol;
if !(rim_at_lo || rim_at_hi) {
if std::env::var("BREP_BAND_INDOMAIN_RIM").as_deref() == Ok("0") {
continue;
}
let rim_level = shapes[rim_index].level;
if (shapes[rim_index].qmax - shapes[rim_index].qmin) > 1e-3 * q_extent {
continue;
}
let margin = 1e-6 * q_extent;
let rim_below_cut = rim_level + margin < shapes[cut_index].qmin;
let rim_above_cut = rim_level - margin > shapes[cut_index].qmax;
if !(rim_below_cut || rim_above_cut) {
continue;
}
let in_domain = |shape: &Shape| {
shape.qmin >= q0 - margin && shape.qmax <= q1 + margin
};
if !(in_domain(&shapes[rim_index]) && in_domain(&shapes[cut_index])) {
continue;
}
let (lower_index, upper_index) = if shapes[0].level <= shapes[1].level {
(0, 1)
} else {
(1, 0)
};
let (lower_dir, upper_dir) = (shapes[lower_index].dir, shapes[upper_index].dir);
if lower_dir == 0 || upper_dir == 0 || lower_dir == upper_dir {
continue;
}
let between_is_ccw_uv = if p_is_u { lower_dir > 0 } else { lower_dir < 0 };
if between_is_ccw_uv != same_sense {
continue; }
return Some(SeamBand {
p_is_u,
rim_index,
cut_index,
rim_level,
material_above_cut: rim_above_cut,
flat_level: rim_level,
});
}
let margin = 1e-6 * q_extent;
let separable = if rim_at_lo {
shapes[rim_index].level + margin < shapes[cut_index].qmin
} else {
shapes[rim_index].level - margin > shapes[cut_index].qmax
};
if !separable {
continue;
}
let (lower_index, upper_index) = if shapes[0].level <= shapes[1].level {
(0, 1)
} else {
(1, 0)
};
let (lower_dir, upper_dir) = (shapes[lower_index].dir, shapes[upper_index].dir);
if lower_dir == 0 || upper_dir == 0 || lower_dir == upper_dir {
continue;
}
let between_rims_is_ccw_uv = if p_is_u { lower_dir > 0 } else { lower_dir < 0 };
let complement = between_rims_is_ccw_uv != same_sense;
let material_above_cut = if rim_at_lo { complement } else { !complement };
return Some(SeamBand {
p_is_u,
rim_index,
cut_index,
rim_level: if rim_at_lo { q0 } else { q1 },
material_above_cut,
flat_level: if material_above_cut { q1 } else { q0 },
});
}
None
}
pub fn seam_band_uv_polygon(
loops_uv: &[Vec<[f64; 2]>],
domain: [f64; 4],
band: &SeamBand,
) -> Vec<[f64; 2]> {
seam_band_uv_polygon_with(loops_uv, |point| *point, |point| point, domain, band)
}
pub fn seam_band_uv_polygon_with<P: Copy>(
loops: &[Vec<P>],
uv: impl Fn(&P) -> [f64; 2],
synthetic: impl Fn([f64; 2]) -> P,
domain: [f64; 4],
band: &SeamBand,
) -> Vec<P> {
let [u0, u1, v0, v1] = domain;
let (p0, p1) = if band.p_is_u { (u0, u1) } else { (v0, v1) };
let (q0, _q1) = if band.p_is_u { (v0, v1) } else { (u0, u1) };
let coord = |pt: &[f64; 2]| -> (f64, f64) {
if band.p_is_u {
(pt[0], pt[1])
} else {
(pt[1], pt[0])
}
};
let mk = |p: f64, q: f64| -> [f64; 2] {
if band.p_is_u {
[p, q]
} else {
[q, p]
}
};
let period = p1 - p0;
let mut cut: Vec<P> = ascending_wrap_boundary(&loops[band.cut_index], |p| coord(&uv(p)));
let eps = 1e-6 * period;
if cut.first().map_or(true, |c| coord(&uv(c)).0 > p0 + eps) {
let q = cut.first().map(|c| coord(&uv(c)).1).unwrap_or(q0);
cut.insert(0, synthetic(mk(p0, q)));
}
if cut.last().map_or(true, |c| coord(&uv(c)).0 < p1 - eps) {
let q = cut.last().map(|c| coord(&uv(c)).1).unwrap_or(q0);
cut.push(synthetic(mk(p1, q)));
}
let q_flat = band.flat_level;
let mut polygon: Vec<P> = Vec::with_capacity(cut.len() + 2);
polygon.extend(cut);
polygon.push(synthetic(mk(p1, q_flat)));
polygon.push(synthetic(mk(p0, q_flat)));
polygon
}
fn ascending_wrap_boundary<P: Copy>(points: &[P], coord: impl Fn(&P) -> (f64, f64)) -> Vec<P> {
let n = points.len();
if n == 0 {
return Vec::new();
}
let (mut seam, mut worst) = (0usize, -1.0);
for i in 0..n {
let a = coord(&points[i]).0;
let b = coord(&points[(i + 1) % n]).0;
let d = (b - a).abs();
if d > worst {
worst = d;
seam = (i + 1) % n;
}
}
let mut rotated: Vec<P> = (0..n).map(|k| points[(seam + k) % n]).collect();
if rotated.first().map(|c| coord(c).0) > rotated.last().map(|c| coord(c).0) {
rotated.reverse();
}
rotated
}