git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commite658e964892f3dc9da754038c6850423c0dfeac2
parentbd3f476102
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-25 12:22
fix: far-side wires no longer paint over a see-through fill

With Show Occluded on below full opacity, the fill writes no depth and
the wires drew after it, so every far-side wire passed the depth test
and painted over the near faces at full strength. The wires now go
first and write depth (cce-ui's see-through wire pipeline), so each
fill layer covers exactly the wires behind it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 CLAUDE.md  | 19 ++++++++++++++++---
 src/app.rs | 16 ++++++++++++++--
 2 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index d0194d6..851fee4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1450,9 +1450,22 @@ ordinary fill is exact and cheaper, and the toggle says so on the status
 line rather than doing nothing silently. The fill then draws through cce-ui's
 `SceneDraw::see_through` pipeline (2026-09-25): no face culling, so a closed
 mesh shows its far wall, and no depth WRITES, so its near layers hide
-neither its far ones nor the wires — the wire pass needs no change, since
-nothing it tests against was written. The depth TEST stays on, so what is
-drawn before the fill (grid, points, markers) still occludes it.
+neither its far ones. The depth TEST stays on, so what is drawn before the
+fill (grid, points, markers) still occludes it.
+
+**The wires go BEFORE a see-through fill, and write depth.** Until
+2026-09-25 they drew after it as usual, reasoning that with nothing written
+nothing could hide them — which is exactly the bug: every far-side wire
+passed and painted OVER the near faces at full strength, so a translucent
+sphere read as if its back lattice sat in front of the camera-facing prims.
+The wire draw now carries `see_through` too, which selects cce-ui's
+depth-writing wire pipeline, and is submitted first; each fill layer then
+lands only where it is nearer than the wire under it. A far wire is dimmed
+by exactly the layers in front of it, a near wire by none (the fill's
+`wire_base_width` offset puts it ahead of its own face), for any mesh
+shape, convex or not. The one cost is with a translucent WIRE colour: the
+fill layers behind a wire are not drawn under it, so the wire blends over
+what is behind the whole mesh rather than over the far fill.
 Blending without depth writes is in submission order, so the stage pass
 re-sorts the fill's triangles FARTHEST FIRST from the eye
 (`geometry::sort_triangles_back_to_front`, centroid distance — painter's
diff --git a/src/app.rs b/src/app.rs
index 32f5b01..f68c413 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -9752,7 +9752,18 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                         // With wires coming, the fill is pushed back by its
                         // slope-scaled offset so the lattice reads solid.
                         let base = if self.wireframe { self.wire_width } else { 0.0 };
-                        draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: base, prelit: self.smooth_shading, see_through });
+                        let mut fill = Some(SceneDraw { mesh: meshes.spheres, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: base, prelit: self.smooth_shading, see_through });
+                        // A see-through fill writes no depth, so wires drawn
+                        // AFTER it pass everywhere and the far side's paint
+                        // over the near faces. Seen through, the wires go
+                        // FIRST and write depth instead: each fill layer then
+                        // lands only where it is nearer than the wire under
+                        // it, so a far wire is dimmed by the layers in front
+                        // of it and a near wire by none (the offset above
+                        // puts it ahead of its own face).
+                        if !see_through {
+                            draws.extend(fill.take());
+                        }
                         if self.wireframe {
                             // The wire pass rides ON TOP of the fill (never
                             // replaces it). Single-color mode replaces the
@@ -9771,8 +9782,9 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                                 [0.0, 0.0, 0.0, 0.0]
                             };
                             let wire_alpha = self.wire_color[3].clamp(0.0, 1.0);
-                            draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: wire_alpha, line_width: self.wire_width, wire_base_width: 0.0, prelit: false, see_through: false });
+                            draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: wire_alpha, line_width: self.wire_width, wire_base_width: 0.0, prelit: false, see_through });
                         }
+                        draws.extend(fill);
                         // Show Point Normals: thin cyan whiskers,
                         // width deliberately fixed (a chunky Wire Width is a
                         // wireframe styling choice, not a normals one).