Skip to content

Fix tetrahedron orientation in FlexiCubes output#63

Open
mkofler96 wants to merge 6 commits into
mainfrom
claude/sweet-goodall-k198uk
Open

Fix tetrahedron orientation in FlexiCubes output#63
mkofler96 wants to merge 6 commits into
mainfrom
claude/sweet-goodall-k198uk

Conversation

@mkofler96

Copy link
Copy Markdown
Owner

Summary

FlexiCubes' _tetrahedralize method 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:

    • Flips inverted tetrahedra (negative signed volume) by reordering vertices [0, 1, 3, 2] instead of [0, 1, 2, 3]
    • Removes degenerate (coplanar, zero-volume) elements that cannot be repaired by reordering
    • Uses a tolerance-based comparison (relative to Hadamard bound) for detecting degenerate elements to handle floating-point rounding
  • Integrated _orient_tets() into the _tetrahedralize pipeline to normalize all output tetrahedra before returning

  • Added comprehensive regression tests covering:

    • Single inverted element correction
    • Empty input handling
    • Degenerate element removal
    • End-to-end validation with sphere volume mesh extraction

Implementation Details

  • The signed volume is computed as det([v1-v0, v2-v0, v3-v0]) using cross product and dot product
  • Vertex reordering preserves gradients to the vertex positions since only indices are permuted
  • Degenerate detection uses tolerance 1e-5 * |e1||e2||e3| (Hadamard bound) rather than exact zero comparison
  • Added logging to track removed degenerate elements

https://claude.ai/code/session_01AC2cJ4wuVk7EB3X9V1A6HQ

claude added 3 commits June 11, 2026 08:42
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.
Copilot AI review requested due to automatic review settings June 11, 2026 10:56
This commit fixes the style issues introduced in efe750d according to the output
from Black.

Details: #63
@deepsource-io

deepsource-io Bot commented Jun 11, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 15981f8...56a5ae0 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tests/test_tet_orientation.py Outdated
Comment on lines +21 to +25
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
Comment thread DeepSDFStruct/flexicubes/flexicubes.py Outdated
Comment on lines +1032 to +1048
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")

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

claude and others added 2 commits June 11, 2026 12:23
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants