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

commitdebc206aa394ecb72af1b5bcb783378e7ecfe3f3
parentd62b71c8c8
authorLucas Galante <[email protected]>
date2026-07-22 15:24
feat: Wireframe Overlay render setting — wires over shaded prims

A new toggle in the Render node's Render Settings draws the node
geometry's wireframe on top of the filled primitives (dark-tinted
lines via SceneDraw::wire_tint), so prim edges are visible without
giving up the shaded view. Wins over Show Wireframe when both are on;
persists with the project like the other render toggles.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/app.rs      | 33 ++++++++++++++++++++++++++-------
 src/project.rs  |  4 ++++
 src/vk_smoke.rs |  6 +++---
 3 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index ce64fa7..a40bfb7 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -958,6 +958,11 @@ pub struct State {
     /// "Show Wireframe" toggle).
     pub wireframe: bool,
     pub last_viewport_wireframe: bool,
+    /// Draw the wireframe OVER the shaded geometry (the Render node's
+    /// "Wireframe Overlay" toggle) — edges visualized without giving up the
+    /// filled primitives. Wins over `wireframe` when both are set.
+    pub wireframe_overlay: bool,
+    pub last_viewport_wireframe_overlay: bool,
     pub last_viewport_rt_mode: bool,
     /// Sphere-geometry cache for the path tracer (a copy of the last
     /// `rebuild_scene_geometry` output, so entering RT mode never re-runs
@@ -2623,6 +2628,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
             last_viewport_show_viewport: false,
             wireframe: false,
             last_viewport_wireframe: false,
+            wireframe_overlay: false,
+            last_viewport_wireframe_overlay: false,
             last_viewport_rt_mode: false,
             rt_sphere_verts: Vec::new(),
             rt_geometry_version: 0,
@@ -5020,7 +5027,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                     || self.last_viewport_height != ch
                     || self.last_viewport_active_camera != self.active_camera
                     || self.last_viewport_show_viewport != self.show_viewport
-                    || self.last_viewport_wireframe != self.wireframe;
+                    || self.last_viewport_wireframe != self.wireframe
+                    || self.last_viewport_wireframe_overlay != self.wireframe_overlay;
 
                 if viewport_changed {
                     if !rt_mode {
@@ -5041,21 +5049,31 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
 
                     // Same draw order as the wgpu pass: bg quad, grid, origin,
                     // pivot, cube, spheres.
-                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false }];
+                    const NO_TINT: [f32; 4] = [0.0; 4];
+                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false, wire_tint: NO_TINT }];
                     if self.viewport().show_grid {
-                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false });
+                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false, wire_tint: NO_TINT });
                     }
                     if self.viewport().show_origin {
-                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false });
+                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false, wire_tint: NO_TINT });
                     }
                     if self.viewport().show_camera_pivot {
-                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false });
+                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false, wire_tint: NO_TINT });
                     }
                     if self.viewport().show_cube {
-                        draws.push(SceneDraw { mesh: meshes.cube, mvp, wireframe: false });
+                        draws.push(SceneDraw { mesh: meshes.cube, mvp, wireframe: false, wire_tint: NO_TINT });
                     }
                     if self.vertex_count_spheres > 0 {
-                        draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: self.wireframe });
+                        if self.wireframe_overlay {
+                            // Shaded + wireframe: the filled mesh, then its
+                            // wire pass darkened so the edges separate from
+                            // the identical fill (the wire pipeline's depth
+                            // bias keeps the lines above the coplanar fill).
+                            draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: false, wire_tint: NO_TINT });
+                            draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: true, wire_tint: [0.0, 0.0, 0.0, 0.75] });
+                        } else {
+                            draws.push(SceneDraw { mesh: meshes.spheres, mvp, wireframe: self.wireframe, wire_tint: NO_TINT });
+                        }
                     }
                     renderer.stage_scene((sx, sy, cw, ch), draws);
                     }
@@ -5079,6 +5097,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
                     self.last_viewport_active_camera = self.active_camera.clone();
                     self.last_viewport_show_viewport = self.show_viewport;
                     self.last_viewport_wireframe = self.wireframe;
+                    self.last_viewport_wireframe_overlay = self.wireframe_overlay;
                     self.last_viewport_rt_mode = rt_mode;
                     self.viewport_dirty = false;
                 }
diff --git a/src/project.rs b/src/project.rs
index 74baef1..4c30368 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -270,6 +270,7 @@ impl State {
         let show_spreadsheet = self.show_spreadsheet;
         let show_playbar = self.show_playbar;
         let wireframe = self.wireframe;
+        let wireframe_overlay = self.wireframe_overlay;
         let square_viewport = self.square_viewport;
         let bool_str = |b: bool| if b { "true" } else { "false" };
 
@@ -491,10 +492,12 @@ impl State {
 
         ensure_param(render_node, "Render Settings", "section", "", &[], None, None, None);
         ensure_param(render_node, "Show Wireframe", "toggle", bool_str(wireframe), &[], None, None, None);
+        ensure_param(render_node, "Wireframe Overlay", "toggle", bool_str(wireframe_overlay), &[], None, None, None);
 
         for p in render_node.params.iter_mut() {
             match p.name.as_str() {
                 "Show Wireframe" => set_toggle(p, wireframe),
+                "Wireframe Overlay" => set_toggle(p, wireframe_overlay),
                 _ => {}
             }
             if p.param_type == "toggle" {
@@ -599,6 +602,7 @@ impl State {
             for p in &params {
                 match p.name.as_str() {
                     "Show Wireframe" => if let Ok(val) = p.default.parse::<bool>() { self.wireframe = val; }
+                    "Wireframe Overlay" => if let Ok(val) = p.default.parse::<bool>() { self.wireframe_overlay = val; }
                     _ => {}
                 }
             }
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index a10c7cc..8f40f0e 100644
--- a/src/vk_smoke.rs
+++ b/src/vk_smoke.rs
@@ -474,9 +474,9 @@ fn main() {
                 renderer.stage_scene(
                     pane,
                     vec![
-                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false },
-                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false },
-                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false },
+                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false, wire_tint: [0.0; 4] },
+                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false, wire_tint: [0.0; 4] },
+                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false, wire_tint: [0.0; 4] },
                     ],
                 );
             }