Boundary surface preparation (weld, manifold/orientation, exact self-intersection, feature edges) and mesh.surface.check (#15)

* mesh: boundary surface preparation (weld, manifold and orientation checks, exact self-intersection, feature edges)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: mesh.surface.check on an open design's body or an imported surface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: struct-init the test session

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* surface: weld at an absolute distance too; the engine welds CAD tessellations at twice SolidCraft's linear tolerance

Faces of a SolidCraft tessellation carry their own copies of shared boundary points, which agree
only to within the kernel's tolerance (up to ~2e-6 mm on the test corpus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* docs: surface preparation in the literature survey, architecture and roadmap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* surface: simplify after review

- The weld distance travels with the mesh (SurfaceMesh::weld_distance, set by Shape::tessellate),
  so every consumer welds a tessellation correctly without opting in.
- Exact tests are infallible on predicates::orient2d_projected (shared with generic_orient2d), and
  compute each vertex's plane side once per pair with an early reject.
- One Box3 for the weld, the BVH and shells; a Half struct for edges; the weld's two lookups share
  one loop, with chained grid cells; cached normals for feature edges; shared parameter helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* corpus: boundary surface preparation as a corpus stage

20 of 21 parts are valid boundaries with their expected genus; cross-cylinders' tessellation has
cracks and flipped triangles where its two intersection curves meet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 16:01:42 -04:00
1 parent 94dc50bb0c
commit 02cc9dab57
24 files changed
+1987 -60

No files matched your search

+10 -3
View File
@@ -275,16 +275,23 @@ fn orient2d_formula<T: Ring + Signed>([a, b, c]: [Hom<T>; 3], (u, v): (usize, us
/// seen from the positive side of that axis (`Z` → the `xy` plane, `X` → `yz`, `Y` → `zx`).
/// Exact. Attene's `orient2d3d` (§4.5) is this with the axis of the largest normal component.
pub fn generic_orient2d(a: &GenericPoint, b: &GenericPoint, c: &GenericPoint, drop: Axis) -> Sign {
let (u, v) = drop.projected();
if let (Some(a), Some(b), Some(c)) = (a.explicit(), b.explicit(), c.explicit()) {
let proj = |p: &Point3| Point2([coord(&p.0, u), coord(&p.0, v)]);
return orient2d(&proj(a), &proj(b), &proj(c));
return orient2d_projected(a, b, c, drop);
}
let (u, v) = drop.projected();
let (points, flipped) = pivot_last([a, b, c]);
let sign = decide(points, |h| orient2d_formula(h, (u, v)), |h| orient2d_formula(h, (u, v)));
if flipped { -sign } else { sign }
}
/// [`orient2d`] of the projections of three explicit points onto the coordinate plane that drops
/// `drop`, seen from the positive side of that axis (the explicit case of [`generic_orient2d`]).
pub fn orient2d_projected(a: &Point3, b: &Point3, c: &Point3, drop: Axis) -> Sign {
let (u, v) = drop.projected();
let proj = |p: &Point3| Point2([coord(&p.0, u), coord(&p.0, v)]);
orient2d(&proj(a), &proj(b), &proj(c))
}
/// `X_a / W_a - X_b / W_b = (W_b X_a - W_a X_b) / (W_a W_b)` along one axis.
fn compare_formula<T: Ring + Signed>([a, b]: [Hom<T>; 2], k: usize) -> Option<Sign> {
let diff = mulw(coord(&a.x, k), &b.w) - mulw(coord(&b.x, k), &a.w);
+1 -1
View File
@@ -40,7 +40,7 @@ mod ring;
mod sos;
pub use adaptive::{incircle, incircle_filter, insphere, insphere_filter, orient2d, orient2d_filter, orient3d, orient3d_filter};
pub use indirect::{Axis, GenericPoint, Lpi, Tpi, generic_compare, generic_orient2d, generic_orient3d};
pub use indirect::{Axis, GenericPoint, Lpi, Tpi, generic_compare, generic_orient2d, generic_orient3d, orient2d_projected};
pub use sos::{incircle_sos, insphere_sos};
use crate::{Error, Result};