forked from 1b-agent/fieldcraft-upstream
mesh: the tet objective sees needles; no tet below scaled Jacobian 0.3 on the skill's tet parts
The improvement objective was the smallest dihedral sine, which misses needles: every dihedral angle fine while a corner is nearly flat, so the corpus meshes had scaled Jacobians down to 0.01-0.07. The objective is now the smaller of the dihedral sine and the scaled Jacobian at the worst corner (the measure analysts check, Knupp 2001), weighted so a Jacobian of 0.3 (the skill's limit) counts as a 30 degree dihedral angle (the schedule's target): the Jacobian decides only near that limit. The existing edge removal, multi-face removal, smoothing and insertion then remove the needles. Skill tet10 min SJ: sphere-on-plate 0.092 -> 0.335, filleted-bracket 0.021 -> 0.318 (both pass, nothing marked). Default-size tet10: thin-ribs 0.240, shallow-groove 0.321, rounded-box 0.311. The strong-relaxation test relaxes below 0.45: nothing is left below 0.3. Literature note in docs/meshing/literature.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
d7444edac7
commit
417c0c8897
8 files changed
+64
-17
No files matched your search
@@ -74,11 +74,14 @@ pub fn dihedral_cosines(x: &[P; 4]) -> [f64; 6] {
|
||||
})
|
||||
}
|
||||
|
||||
/// The quality objective: the smallest signed dihedral sine (1 is impossible; the regular
|
||||
/// tetrahedron scores √(8/9) ≈ 0.943). Positive for a valid tetrahedron, 0 for a flat one,
|
||||
/// negative for an inverted one (then the sine of largest magnitude, so worse inversions score
|
||||
/// lower). With `sin θ_ij = 6V ℓ_ij / (2A_k · 2A_l)`, the extreme sine is found on squared
|
||||
/// lengths and areas, with one square root.
|
||||
/// The quality objective of a valid tetrahedron: the smaller of its smallest dihedral sine and
|
||||
/// its weighted scaled Jacobian ([`JACOBIAN_WEIGHT`]; the regular tetrahedron scores
|
||||
/// √(8/9) ≈ 0.943). The dihedral sine catches slivers and caps; it does not see needles, whose
|
||||
/// dihedral angles can all be fine while a corner is nearly flat (Shewchuk 2002), and the scaled
|
||||
/// Jacobian `√2 · 6V / (ℓ₁ℓ₂ℓ₃)` at the worst corner, the measure analysts check (the skill's
|
||||
/// limit, [`crate::quality`]), does. 0 for a flat tetrahedron; for an inverted one the negative
|
||||
/// sine of largest magnitude, so worse inversions score lower. Extremes are found on squared
|
||||
/// lengths and areas, with one square root each.
|
||||
pub fn quality(x: &[P; 4]) -> f64 {
|
||||
let v6 = volume6(x);
|
||||
if v6 == 0.0 || !v6.is_finite() {
|
||||
@@ -95,9 +98,36 @@ pub fn quality(x: &[P; 4]) -> f64 {
|
||||
pick = if v6 > 0.0 { pick.min(r) } else { pick.max(r) };
|
||||
}
|
||||
let q = v6 * pick.sqrt();
|
||||
let q = if v6 > 0.0 { q.min(JACOBIAN_WEIGHT * scaled_jacobian(x, v6)) } else { q };
|
||||
if q.is_finite() { q } else { 0.0 }
|
||||
}
|
||||
|
||||
/// The weight of the scaled Jacobian in [`quality`]: the meshing skill's limit, a scaled
|
||||
/// Jacobian of 0.3, counts as much as a 30° dihedral angle (sin 30°, the improvement's target,
|
||||
/// [`super::improve::ImproveOptions::target`]), so the Jacobian decides only for tetrahedra near
|
||||
/// that limit (needles) and the dihedral sine everywhere else.
|
||||
pub const JACOBIAN_WEIGHT: f64 = 0.5 / 0.3;
|
||||
|
||||
/// The corners of a tetrahedron and the other three corners of each.
|
||||
const CORNERS: [(usize, [usize; 3]); 4] = [(0, [1, 2, 3]), (1, [0, 2, 3]), (2, [0, 1, 3]), (3, [0, 1, 2])];
|
||||
|
||||
/// The squared product of the three edge lengths at each corner.
|
||||
fn corner_products(x: &[P; 4]) -> [f64; 4] {
|
||||
CORNERS.map(|(c, o)| {
|
||||
o.iter().fold(1.0, |m, &j| {
|
||||
let e = sub(x[j], x[c]);
|
||||
m * dot(e, e)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The scaled Jacobian at the worst corner of a tetrahedron with six times the volume `v6`
|
||||
/// (as [`crate::quality`] reports it).
|
||||
fn scaled_jacobian(x: &[P; 4], v6: f64) -> f64 {
|
||||
let worst = corner_products(x).iter().fold(0.0_f64, |m, &p| m.max(p));
|
||||
std::f64::consts::SQRT_2 * v6 / worst.sqrt()
|
||||
}
|
||||
|
||||
/// The smallest and largest dihedral angle in degrees (reports only).
|
||||
pub fn dihedral_range(x: &[P; 4]) -> (f64, f64) {
|
||||
let (s, c) = (dihedral_sines(x), dihedral_cosines(x));
|
||||
@@ -221,6 +251,16 @@ mod tests {
|
||||
assert!(circumcenter(&flat).is_none());
|
||||
}
|
||||
|
||||
/// A needle: every dihedral angle above 20°, yet one corner nearly flat; the objective sees
|
||||
/// it through the scaled Jacobian.
|
||||
#[test]
|
||||
fn a_needle_scores_low() {
|
||||
let x = positive([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 0.866, 0.0], [0.5, 0.29, 12.0]]);
|
||||
let (lo, hi) = dihedral_range(&x);
|
||||
assert!(lo > 20.0 && hi < 120.0, "{lo} {hi}");
|
||||
assert!(quality(&x) < 0.15 * JACOBIAN_WEIGHT, "{}", quality(&x));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sliver_scores_low() {
|
||||
let x = positive([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.01]]);
|
||||
|
||||
@@ -433,7 +433,17 @@ mod determinism {
|
||||
let (p, t, f) = washer();
|
||||
let m = mesh(&p, &t, &f);
|
||||
assert!(m.elements.len() > 100, "{:?}", m.report);
|
||||
assert_eq!(fingerprint(&m), 6_285_915_972_108_509_003, "{} tets, {} nodes", m.elements.len(), m.points.len());
|
||||
// No needles either: the scaled Jacobian analysts check is above the skill's limit.
|
||||
let sj = m
|
||||
.elements
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let xyz: Vec<[f64; 3]> = e.iter().map(|&v| m.points[v as usize]).collect();
|
||||
crate::quality::element_quality(m.kind, &xyz).unwrap().get(crate::quality::Metric::ScaledJacobian).unwrap()
|
||||
})
|
||||
.fold(f64::INFINITY, f64::min);
|
||||
assert!(sj >= 0.3, "min scaled Jacobian {sj}");
|
||||
assert_eq!(fingerprint(&m), 11_252_377_805_886_396_373, "{} tets, {} nodes", m.elements.len(), m.points.len());
|
||||
}
|
||||
|
||||
/// Listing the triangles, their corners and the vertices in another order, and numbering
|
||||
|
||||
Reference in new issue
Block a user