Keep fine meshes whole at collapsed taper boundaries (#906)
The exact surface search could return an arbitrary parameter at a collapsed NURBS boundary, which corrupted the trimmed triangulation domain and lost volume on fine meshes of a fillet tapering to zero. Search results in the boundary projection are now validated, preferring a bounded parameter at a pole and snapping to a finite endpoint only when its transverse tangent has collapsed and the residual improves. Adds four regressions for decreasing chord tolerances, pole parameters, unbounded planar support and provisional preview. Fixes #796.
This commit is contained in:
1 parent
7da7bbb335
commit
3f1c023563
5 files changed
+235
-15
No files matched your search
+46
-14
@@ -46,17 +46,47 @@ fn clamped<S: ParametricSurface>(surface: &S, (u, v): (f64, f64)) -> (f64, f64)
|
||||
(c(u, ur, surface.u_period()), c(v, vr, surface.v_period()))
|
||||
}
|
||||
|
||||
/// SolidCraft: a nearest-parameter search can run off a B-spline's domain and extrapolate to
|
||||
/// thousands of parameter units (meshing that grid never finishes). Outside the nominal domain
|
||||
/// keep the result only if it is really closer to the point than the clamped one (planes report
|
||||
/// a nominal unit range but extend for ever).
|
||||
fn sane<S: RobustMeshableSurface>(surface: &S, point: Point3, uv: (f64, f64)) -> (f64, f64) {
|
||||
if in_domain(surface, uv) {
|
||||
return uv;
|
||||
}
|
||||
/// SolidCraft: exact and nearest searches can run off a B-spline's domain, especially at a
|
||||
/// collapsed edge. Prefer a bounded equivalent within the existing spatial search tolerance;
|
||||
/// otherwise retain extrapolation only when it is closer (planes have a nominal unit range
|
||||
/// but extend for ever). At most four boundary candidates are evaluated, with no new search.
|
||||
fn sane<S: ParametricSurface3D>(surface: &S, point: Point3, uv: (f64, f64)) -> (f64, f64) {
|
||||
let c = clamped(surface, uv);
|
||||
let d = |(u, v): (f64, f64)| surface.subs(u, v).distance2(point);
|
||||
if d(uv) <= d(c) { uv } else { c }
|
||||
let (mut best, mut error) = (uv, d(uv));
|
||||
let dc = d(c);
|
||||
let collapsed = |(u, v): (f64, f64), axis| {
|
||||
let tangent = if axis == 0 { surface.vder(u, v) } else { surface.uder(u, v) };
|
||||
tangent.magnitude2() <= TOLERANCE2
|
||||
};
|
||||
if !in_domain(surface, uv)
|
||||
&& (dc < error || (dc <= TOLERANCE2 && (collapsed(c, 0) || collapsed(c, 1)))) {
|
||||
(best, error) = (c, dc);
|
||||
}
|
||||
// SolidCraft: at a collapsed boundary an exact search can return arbitrary,
|
||||
// enormous parameters, or stop just inside the pole. Prefer an equivalent
|
||||
// in-domain point and the exact boundary when it improves the spatial fit.
|
||||
// A plane's nominal unit box must not clip genuinely unbounded geometry.
|
||||
let (ur, vr) = surface.try_range_tuple();
|
||||
for (axis, range, period) in [(0, ur, surface.u_period()), (1, vr, surface.v_period())] {
|
||||
if period.is_some() { continue; }
|
||||
if let Some((a, b)) = range {
|
||||
for end in [a, b] {
|
||||
if !end.is_finite() { continue; }
|
||||
let coordinate = if axis == 0 { best.0 } else { best.1 };
|
||||
let other = if end == a { b } else { a };
|
||||
if (coordinate - end).abs() > (coordinate - other).abs() { continue; }
|
||||
let candidate = if axis == 0 { (end, best.1) } else { (best.0, end) };
|
||||
let distance = d(candidate);
|
||||
// A closed spline seam has equivalent endpoints too, but moving
|
||||
// between them changes the trim loop. Only canonicalize poles.
|
||||
if distance <= TOLERANCE2 && distance < error && collapsed(candidate, axis) {
|
||||
(best, error) = (candidate, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
pub(super) fn by_search_parameter<S>(
|
||||
@@ -83,8 +113,8 @@ where
|
||||
surface
|
||||
.search_parameter(point, hint, 100)
|
||||
.or_else(|| surface.search_parameter(point, None, 100))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 100).map(|uv| sane(surface, point, uv)))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 100).map(|uv| sane(surface, point, uv)))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 100))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 100))
|
||||
}
|
||||
|
||||
/// Tessellates faces
|
||||
@@ -393,7 +423,9 @@ impl PolyBoundaryPiece {
|
||||
.into_iter()
|
||||
.flat_map(|pt| {
|
||||
let (mut u, mut v) = match sp(surface, pt, previous) {
|
||||
Some(hint) => hint,
|
||||
// SolidCraft: validate exact and nearest results alike, once per
|
||||
// boundary point, including the bounded provisional preview.
|
||||
Some(hint) => sane(surface, pt, hint),
|
||||
None => return vec![None],
|
||||
};
|
||||
if let (Some(up), Some((u0, _))) = (up, previous) {
|
||||
@@ -704,8 +736,8 @@ pub(super) fn preview_face<S: RobustMeshableSurface>(surface: &S, loops: &[Vec<P
|
||||
let search = |surface: &S, point, hint| {
|
||||
surface.search_parameter(point, hint, 16)
|
||||
.or_else(|| surface.search_parameter(point, None, 16))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 16).map(|uv| sane(surface, point, uv)))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 16).map(|uv| sane(surface, point, uv)))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 16))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 16))
|
||||
};
|
||||
let pieces = loops.iter().map(|w| {
|
||||
PolyBoundaryPiece::try_new(surface, std::iter::once(PolylineCurve(w.clone())), search)
|
||||
|
||||
Reference in new issue
Block a user