graphic design tool
git clone https://git.lucas.co/cce-designer.git
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).