Fix tetrahedron orientation in FlexiCubes output#63
Conversation
FlexiCubes' _tetrahedralize builds tets from two sub-procedures (surface pyramids and interior edges) whose vertex orderings do not share a consistent winding. As a result a large fraction of elements came out inverted (negative signed volume) - nearly all surface tets and ~40% of interior tets - which breaks FEA solvers that require a positive signed volume / Jacobian on every element. Add _orient_tets to normalize every tet to positive orientation by swapping two vertices where the signed volume is negative. This only reorders integer indices, so element geometry, |volume|, and gradients to the vertices are all preserved (the extractor stays differentiable). Add regression tests asserting no inverted tets are produced.
The interior tetrahedralization sub-procedure can emit elements whose four vertices are exactly coplanar (the two dual-mesh vertices land symmetric about the grid edge), yielding zero-volume tets that fail the positive-Jacobian requirement of FEA solvers just like inverted ones. Drop these elements in _orient_tets and log 'removed x elements with 0 volume'. Coplanarity is detected with a tolerance relative to the Hadamard bound of the determinant rather than an exact zero compare, since the rounding of an exactly-degenerate triple product depends on association order. The measured relative volumes are cleanly bimodal (degenerates at <=1e-7, real elements at >=1e-5), so the 1e-5 cutoff removes only degenerate elements.
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| Python | Jun 11, 2026 12:25p.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
There was a problem hiding this comment.
Pull request overview
This PR corrects tetrahedron winding produced by FlexiCubes volume mesh extraction so that all tetrahedra have strictly positive signed volume (as required by typical FEA solvers), and drops degenerate (near-zero-volume) elements.
Changes:
- Added
_orient_tets()to normalize tetrahedron orientation to positive signed volume and filter degenerate elements. - Integrated orientation normalization into the
_tetrahedralize()output pipeline. - Added regression tests to catch inverted/degenerate tetrahedra and validate end-to-end volume-mesh extraction.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
DeepSDFStruct/flexicubes/flexicubes.py |
Adds tet orientation normalization and degenerate element filtering to ensure positive signed volumes. |
tests/test_tet_orientation.py |
Adds regression + end-to-end tests ensuring FlexiCubes outputs only positively oriented tetrahedra. |
| x_nx3, cube_fx8 = fc.construct_voxel_grid(res) | ||
| # Spread the unit grid over [-1, 1] so the sphere sits inside the domain. | ||
| x_nx3 = x_nx3 * 2.0 | ||
| c = torch.tensor(center, dtype=x_nx3.dtype) | ||
| s_n = torch.linalg.norm(x_nx3 - c, dim=1) - radius |
| if tets.shape[0] == 0: | ||
| return tets | ||
| v0 = vertices[tets[:, 0]] | ||
| v1 = vertices[tets[:, 1]] | ||
| v2 = vertices[tets[:, 2]] | ||
| v3 = vertices[tets[:, 3]] | ||
| e1, e2, e3 = v1 - v0, v2 - v0, v3 - v0 | ||
| signed_vol = torch.einsum("ij,ij->i", e1, torch.linalg.cross(e2, e3, dim=1)) | ||
| inverted = signed_vol < 0 | ||
| tets = tets.clone() | ||
| tets[inverted] = tets[inverted][:, [0, 1, 3, 2]] | ||
| # Coplanar tets only evaluate to exactly zero up to floating-point | ||
| # rounding (which depends on the association order of the triple | ||
| # product), so compare against a tolerance relative to the Hadamard | ||
| # bound |e1||e2||e3| of the determinant instead of zero itself. | ||
| scale = e1.norm(dim=1) * e2.norm(dim=1) * e3.norm(dim=1) | ||
| degenerate = signed_vol.abs() <= 1e-5 * scale |
| scale = e1.norm(dim=1) * e2.norm(dim=1) * e3.norm(dim=1) | ||
| degenerate = signed_vol.abs() <= 1e-5 * scale | ||
| if degenerate.any(): | ||
| logger.info(f"removed {int(degenerate.sum())} elements with 0 volume") |
There was a problem hiding this comment.
Keeping this message as-is — the wording was explicitly requested by the maintainer. The tolerance only exists to catch elements that are coplanar up to float32 vertex precision: the measured relative-volume distribution is cleanly bimodal (degenerates at ≤1e-7 from rounding noise, real elements at ≥1e-5), so everything removed here is zero-volume at working precision, not merely "near-zero".
Generated by Claude Code
construct_voxel_grid defaults to bounds [-0.05, 1.05] in this repo, so scaling by 2 did not produce the [-1, 1] domain the test comment claimed; pass explicit bounds instead. Also wrap the orientation classification in torch.no_grad() since it only derives integer index masks.
for more information, see https://pre-commit.ci
Summary
FlexiCubes'
_tetrahedralizemethod was producing tetrahedra with inconsistent vertex winding, resulting in a large fraction of elements having negative signed volumes. This breaks FEA solvers which require strictly positive signed volumes. This PR adds orientation correction and degenerate element removal to ensure all output tetrahedra are properly oriented.Key Changes
Added
_orient_tets()static method to FlexiCubes that:[0, 1, 3, 2]instead of[0, 1, 2, 3]Integrated
_orient_tets()into the_tetrahedralizepipeline to normalize all output tetrahedra before returningAdded comprehensive regression tests covering:
Implementation Details
det([v1-v0, v2-v0, v3-v0])using cross product and dot product1e-5 * |e1||e2||e3|(Hadamard bound) rather than exact zero comparisonhttps://claude.ai/code/session_01AC2cJ4wuVk7EB3X9V1A6HQ