diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 90d4905..f1cabc3 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,19 +4,9 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
+
@@ -405,7 +395,23 @@
1783798929531
-
+
+
+ 1783887682052
+
+
+
+ 1783887682052
+
+
+
+ 1783887704766
+
+
+
+ 1783887704766
+
+
@@ -426,8 +432,6 @@
-
-
@@ -451,6 +455,8 @@
-
+
+
+
\ No newline at end of file
diff --git a/src/fluency/rendering/occ_to_mesh.py b/src/fluency/rendering/occ_to_mesh.py
index 8c02cc6..7dcbe14 100644
--- a/src/fluency/rendering/occ_to_mesh.py
+++ b/src/fluency/rendering/occ_to_mesh.py
@@ -87,17 +87,18 @@ def occ_shape_to_ply(
f"Tessellation: {len(vertices)} vertices, {len(faces)} triangles"
)
- # Compute outward-facing vertex normals from triangle geometry.
- # This ensures consistent lighting even when OCC triangulation winding
- # is inconsistent across faces (e.g. after location transforms).
- normals = _compute_outward_normals(vertices, faces, shape)
+ # Compute outward-facing vertex normals AND correct face winding.
+ # OCC triangulation produces inconsistent winding across faces (especially
+ # after location transforms). Mitsuba uses face winding for front/back
+ # determination, so both normals AND winding must be consistent.
+ normals, corrected_faces = _compute_outward_normals(vertices, faces, shape)
- # Write PLY with normals
+ # Write PLY with corrected faces and normals
if output_path is None:
fd, output_path = tempfile.mkstemp(suffix=".ply", prefix="fluency_render_")
os.close(fd)
- _write_ply(output_path, vertices, faces, normals)
+ _write_ply(output_path, vertices, corrected_faces, normals)
logger.info(f"Wrote PLY: {output_path}")
return output_path
@@ -106,14 +107,18 @@ def _compute_outward_normals(
vertices: np.ndarray,
faces: np.ndarray,
shape,
-) -> np.ndarray:
- """Compute outward-facing vertex normals.
+) -> Tuple[np.ndarray, np.ndarray]:
+ """Compute outward-facing vertex normals and correct face winding.
1. Compute per-face normals from cross product of triangle edges.
2. Determine correct orientation by checking face normals against the
shape centroid (outward = away from center).
3. Flip triangles with inward normals before accumulating to vertices.
4. Average and normalize per-vertex normals.
+
+ Returns (normals, corrected_faces) so the PLY writer can use consistent
+ winding order — critical for Mitsuba which uses winding for front/back
+ face determination.
"""
n_verts = len(vertices)
v_normals = np.zeros((n_verts, 3), dtype=np.float64)
@@ -189,7 +194,7 @@ def _compute_outward_normals(
v_lengths[v_lengths < 1e-10] = 1.0
v_normals /= v_lengths
- return v_normals.astype(np.float32)
+ return v_normals.astype(np.float32), corrected_faces.astype(np.uint32)
def occ_shape_to_stl(