forked from 1b-agent/fieldcraft-upstream
The end-to-end demo met a raw-CDT sliver that the validity gate accepted (exactly positive) and the solver refused. Now: - One validity criterion, `fieldcraft_model::admissible_jacobian` (normalized Jacobian above 1e-12), used by the implicit solver (fem `positive_inverse`), the explicit tet4/hex8 kinematics and the mesh gate, which proves it on the whole element (tet4: the solvers' own Jacobian; curved kinds: a Bernstein floor), so whatever passes the gate passes the solvers. - CDT rounding repaired by flips on the rounded mesh (Diazzi et al. 2023 §5.1) instead of the ±2-ulp nudge. - `fieldcraft-mesh::tet`: a mutable tetrahedral complex whose every change is a checked replacement (positive and admissible tetrahedra, faces matched with opposite orientation) with an exact undo log; edge removal (Klincsek), multi-face removal, Delaunay insertion with boundary faces, edge collapse; a curvature- and thickness-graded size field; constrained Delaunay refinement with encroachment splits; ODT smoothing; the Klingner–Shewchuk schedule; boundary vertices moved onto the exact B-rep surfaces at the end; tet10 with mid-edge nodes on the geometry. - `mesh.tet.generate` gains size, order, quality, quality_target, curvature_angle, grading, max_passes, max_points and reports the CDT's and the mesh's quality; `solve.probe` takes a set and reports its largest von Mises stress. - Oracle cases filleted-bracket-fillet-stress(-coarse): the fillet's peak von Mises stress on tet10 meshes of 2k and 13.5k elements, 213.3 MPa (a four-level study stays within 1.8%); the CDT cases keep the raw tetrahedralization. - Corpus quality columns: all 20 valid parts mesh and pass the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
41 lines
1.2 KiB
Rust
41 lines
1.2 KiB
Rust
//! Small 3-vector helpers on `[f64; 3]` (floating point: measures and display, never meshing
|
|
//! decisions, which use [`crate::predicates`]).
|
|
|
|
pub(crate) type P = [f64; 3];
|
|
|
|
pub(crate) fn sub(a: P, b: P) -> P {
|
|
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
|
}
|
|
pub(crate) fn dot(a: P, b: P) -> f64 {
|
|
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
|
}
|
|
pub(crate) fn cross(a: P, b: P) -> P {
|
|
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
|
|
}
|
|
pub(crate) fn norm(a: P) -> f64 {
|
|
dot(a, a).sqrt()
|
|
}
|
|
pub(crate) fn det(a: P, b: P, c: P) -> f64 {
|
|
dot(a, cross(b, c))
|
|
}
|
|
|
|
/// The angle between two vectors in degrees (0 when either is zero).
|
|
pub(crate) fn angle_deg(a: P, b: P) -> f64 {
|
|
norm(cross(a, b)).atan2(dot(a, b)).to_degrees()
|
|
}
|
|
pub(crate) fn add(a: P, b: P) -> P {
|
|
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
|
}
|
|
pub(crate) fn scale(a: P, s: f64) -> P {
|
|
[a[0] * s, a[1] * s, a[2] * s]
|
|
}
|
|
pub(crate) fn dist(a: P, b: P) -> f64 {
|
|
norm(sub(a, b))
|
|
}
|
|
pub(crate) fn lerp(a: P, b: P, t: f64) -> P {
|
|
[a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t]
|
|
}
|
|
pub(crate) fn mid(a: P, b: P) -> P {
|
|
[0.5 * (a[0] + b[0]), 0.5 * (a[1] + b[1]), 0.5 * (a[2] + b[2])]
|
|
}
|