Kernel tests: the box rounded after separate bottom rounds takes its exact volume

issue2422_vertical_after_separate_bottom_rounds compared the face count and
volume (within 0.05) of the vertical round, picked with and without its
bottom mitre, against the same round picked alone, and required every face
meshed. It now holds each of the three picks, on the 40 x 80 x 27 box and
on the same shelled 1 mm open at the top, to the exact volume; the corner
probes stay.

Proof. Horizontal sections, s up from the bottom. Below s = r the four
separate R2 bottom rounds (mitred at the corners) leave the rectangle
moved in by d = r - rho, rho = sqrt(r^2 - (r - s)^2), and where the
vertical round meets them (a sphere octant) its corner is a quarter disc of
radius rho: (L - 2d)(W - 2d) - (1 - pi/4) rho^2. Above, LW - (1 - pi/4) r^2.
With the integrals over the bottom r of rho (pi r^2 / 4), rho^2 (2 r^3 / 3),
d (r^2 (1 - pi/4)) and d^2 (r^3 (5/3 - pi/2)):
V = LW r - 2 (L + W) r^2 (1 - pi/4) + 4 r^3 (5/3 - pi/2)
  - (1 - pi/4) 2 r^3 / 3 + (H - r)(LW - (1 - pi/4) r^2) = 86174.445361;
with four such corners it is issue1557_all_vertical_corners_match_analytic
_volume's formula exactly. The shell's cavity, 38 x 78 x 26, is clear of
both rounds (along the corner's diagonal the R2 arc stays 2 - sqrt(2) in,
the cavity's corner is 1 in), so the cup is V - 77064. Graded at 1e-6.

Today SMLib's check refuses the bottom rounds' result (edge and vertex
tolerance 0.97 against 0.01), as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-30 06:37:59 -04:00
committed by Irakli Kourtis
parent a8f393ca36
commit 5af467aef0
+20 -8
View File
@@ -168,28 +168,40 @@ mod tests {
assert!(fillet_radii(&rounded, &[(seam, 2.0)]).is_err());
}
/// A 40 x 80 x 27 box (and the same shelled 1 mm, open at the top) with its bottom edges
/// rounded R2 as four separate fillets (mitred at the bottom corners), then one vertical
/// edge R2: the same solid whichever of its picks comes first. Horizontal sections: below
/// z = r above the bottom, s up, the rectangle moved in by d = r − ρ, ρ = √(r² − (r − s)²),
/// with the rounded corner a quarter disc of radius ρ less than its square (the sphere octant
/// where the three rounds meet); above, the rectangle less that corner at r. With
/// ∫ ρ = π r² / 4, ∫ ρ² = 2 r³ / 3, ∫ d = r² (1 − π/4) and ∫ d² = r³ (5/3 − π/2) over the
/// bottom r. The shell's cavity, 38 x 78 x 26, lies clear of both rounds (the bottom round's
/// arc passes 2 − √2 from the outer corner along the diagonal, the cavity's corner 1).
#[test]
fn issue2422_vertical_after_separate_bottom_rounds() {
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
let origin = Vec3::new(-5.0, 7.2, -2.0);
let b = box_solid(origin, origin + Vec3::new(40.0, 80.0, 27.0)).unwrap();
let (l, w, h, r) = (40.0, 80.0, 27.0, 2.0);
let b = box_solid(origin, origin + Vec3::new(l, w, h)).unwrap();
let bottom = [Vec3::new(15.0, 7.2, -2.0), Vec3::new(35.0, 47.2, -2.0), Vec3::new(15.0, 87.2, -2.0), Vec3::new(-5.0, 47.2, -2.0)];
let spandrel = 1.0 - FRAC_PI_4;
let solid = l * w * r - 2.0 * (l + w) * r * r * spandrel + 4.0 * r * r * r * (5.0 / 3.0 - FRAC_PI_2) - spandrel * 2.0 * r * r * r / 3.0
+ (h - r) * (l * w - spandrel * r * r);
let cavity = (l - 2.0) * (w - 2.0) * (h - 1.0);
// The issue has no design attached. Reconstruct its outside corner and bottom mitre,
// using the reported picks, with and without the open cavity visible in the screenshot.
let picks = [Vec3::new(-5.0, 87.2, 12.5), Vec3::new(-4.283446394180081, 86.48344639418008, -1.5001085324947512)];
for base in [b.clone(), crate::shell(&b, &[Vec3::new(15.0, 47.2, 25.0)], 1.0).unwrap()] {
for (base, want) in [(b.clone(), solid), (crate::shell(&b, &[Vec3::new(15.0, 47.2, 25.0)], 1.0).unwrap(), solid - cavity)] {
// Each fillet with its tangent chain, as the Fillet feature asks for it by default
// (tangent propagation).
let mut rounded = base;
for edge in bottom {
rounded = fillet(&rounded, &crate::tangent_chain(&rounded, &[edge]).unwrap(), 2.0).unwrap();
}
let reference = fillet(&rounded, &crate::tangent_chain(&rounded, &picks[..1]).unwrap(), 2.0).unwrap();
for selection in [picks, [picks[1], picks[0]]] {
let result = fillet(&rounded, &crate::tangent_chain(&rounded, &selection).unwrap(), 2.0).unwrap();
assert_eq!(result.face_count(), reference.face_count());
assert_eq!(result.unmeshed_faces(), 0);
for selection in [&picks[..1], &picks[..], &[picks[1], picks[0]][..]] {
let result = fillet(&rounded, &crate::tangent_chain(&rounded, selection).unwrap(), 2.0).unwrap();
assert!(result.validity().is_empty(), "{:?}", result.validity());
assert!((vol(&result) - vol(&reference)).abs() < 0.05);
assert!((vol(&result) - want).abs() < 1e-6 * want, "{selection:?}: {} vs {want}", vol(&result));
assert!(!result.contains(Vec3::new(-4.9, 87.1, 10.0)));
assert!(result.contains(Vec3::new(-4.1, 86.3, 10.0)));
}