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

commit734d6ba1c89bf13c86577b6fdb600b63e2e76303
parenta8640e5a7b
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-25 10:10
feat: Show Occluded — a translucent fill shows what it hides, sorted back to front

A toggle (viewport menu, palette, state.kdl and the project's display
block) that, below full opacity, draws the fill through cce-ui's new
see-through pipeline: no culling, no depth writes, depth test kept. The
fill's far side, the wires and the scene behind it show through. The stage
pass re-sorts the triangles farthest first from the eye in mesh space and
re-uploads them whenever the geometry, shading or eye moves. At 100%
opacity the toggle says it has nothing to show. cce-ui pinned to f50f6ef.

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

 CLAUDE.md       | 20 ++++++++++++++
 Cargo.toml      |  2 +-
 src/app.rs      | 85 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 src/command.rs  |  1 +
 src/dialog.rs   |  1 +
 src/geometry.rs | 27 ++++++++++++++++++
 src/main.rs     | 55 +++++++++++++++++++++++++++++++++++++
 src/shortcut.rs |  2 ++
 src/vk_smoke.rs |  6 ++--
 9 files changed, 184 insertions(+), 15 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 61c4d4e..8513593 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1404,6 +1404,26 @@ its materials. Smoothing follows topology, so a welded mesh rounds off and
 a soup of unshared triangles stays faceted. Persisted as
 `render.smooth_shading` in state.kdl.
 
+**Show Occluded draws a translucent fill see-through** (`toggle_show_occluded`,
+a viewport-menu row under the opacity presets; `render.show_occluded`). In
+effect only below full opacity (`State::see_through_active`) — at 100% the
+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, cube, points, markers) still occludes it.
+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
+order, exact for non-intersecting triangles and close for the rest) and
+re-uploads them whenever the geometry version, the shading or the eye moves
+(`State::sorted_fill_key`), which during an orbit is every frame. The eye
+is taken in MESH space, the inverse of view × model. Leaving see-through
+clears the key and re-uploads nothing: sorted order is still a valid
+opaque mesh.
+
 ### Dragging the scene orbits the camera
 
 `State::orbit_camera_by` turns the camera by a drag delta, armed by a left
diff --git a/Cargo.toml b/Cargo.toml
index b918fb1..6b3e93a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies]
-cce-ui = { git = "https://github.com/lsgalante/cce-ui.git", rev = "a530574f7fa1f0938049db92053a17df27756acb" }
+cce-ui = { git = "https://github.com/lsgalante/cce-ui.git", rev = "f50f6ef20a0b50dc31f1d9f55ec3ae2545c49207" }
 smithay-client-toolkit = "0.19.2"
 calloop = "0.13.0"
 calloop-wayland-source = "0.3.0"
diff --git a/src/app.rs b/src/app.rs
index bf35786..3ca0609 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1199,6 +1199,12 @@ pub struct RenderSettings {
     /// flat.
     #[serde(default)]
     pub smooth_shading: bool,
+    /// See-through fill: below full opacity the fill draws with no culling
+    /// and no depth writes, triangles sorted back to front for the eye, so
+    /// what it occludes — its own far side, the wires, the scene behind —
+    /// shows through. Absent in older files: off.
+    #[serde(default)]
+    pub show_occluded: bool,
 }
 
 fn default_group_marker_scale() -> f32 {
@@ -1238,6 +1244,7 @@ impl Default for RenderSettings {
             point_color: default_point_color(),
             group_marker_scale: default_group_marker_scale(),
             smooth_shading: false,
+            show_occluded: false,
         }
     }
 }
@@ -2046,6 +2053,16 @@ pub struct State {
     /// uploads this in place of `rt_sphere_verts`, which the path tracer
     /// keeps reading unlit.
     pub scene_smooth_verts: Vec<Vertex3D>,
+    /// See-through fill (`toggle_show_occluded`), in effect only below full
+    /// opacity — at 100% there is nothing to see through, and the ordinary
+    /// depth-writing fill is exact.
+    pub show_occluded: bool,
+    /// What the fill mesh was last SORTED for: (geometry version, smooth,
+    /// eye in mesh space). The stage pass re-sorts and re-uploads when any
+    /// of the three moves while see-through is in effect — which during an
+    /// orbit is every frame — and clears it when see-through ends, so the
+    /// next entry sorts afresh.
+    pub sorted_fill_key: Option<(u64, bool, [f32; 3])>,
     /// (geometry version, quantized size, color) the points mesh was last
     /// built from; `point_vertex_count` gates the draw.
     pub last_points_key: Option<(u64, i32, [u8; 3])>,
@@ -2236,6 +2253,13 @@ impl State {
 
 
 
+    /// Whether the fill draws see-through this frame: Show Occluded is on
+    /// and the fill is translucent. At full opacity the ordinary fill is
+    /// exact and cheaper — no sort, no re-upload.
+    pub fn see_through_active(&self) -> bool {
+        self.show_occluded && self.geo_opacity < 0.999
+    }
+
     /// The live display settings — what state.kdl and a project's
     /// `display` block both carry.
     pub fn display_settings(&self) -> DisplaySettings {
@@ -2272,6 +2296,7 @@ impl State {
                 point_color: self.point_color,
                 group_marker_scale: self.group_marker_scale,
                 smooth_shading: self.smooth_shading,
+                show_occluded: self.show_occluded,
             },
         }
     }
@@ -2341,6 +2366,7 @@ impl State {
         self.point_color = r.point_color;
         self.group_marker_scale = r.group_marker_scale;
         self.smooth_shading = r.smooth_shading;
+        self.show_occluded = r.show_occluded;
 
         // The checkmarks the guide and pane toggles keep in step by hand.
         let (sg, sc, so, cp) = {
@@ -4319,6 +4345,9 @@ impl State {
             options.push(format!("{} Opacity {}%", mark(on), (o * 100.0).round() as i32));
             actions.push(ViewportMenuAction::Opacity(o));
         }
+        let occluded_label = crate::command::by_id("toggle_show_occluded").map(|c| c.label).unwrap_or("Show Occluded");
+        options.push(format!("{} {occluded_label}", mark(self.show_occluded)));
+        actions.push(ViewportMenuAction::Command("toggle_show_occluded"));
 
         // The viewport's editor binding, as a radio group: follow the active
         // editor, or pin to one. Pin rows appear only while a second editor
@@ -5372,6 +5401,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
             point_color: settings.render.point_color,
             group_marker_scale: settings.render.group_marker_scale,
             smooth_shading: settings.render.smooth_shading,
+            show_occluded: settings.render.show_occluded,
+            sorted_fill_key: None,
             scene_smooth_verts: Vec::new(),
             last_points_key: None,
             point_vertex_count: 0,
@@ -7005,6 +7036,17 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                 self.rebuild_scene_geometry();
                 settings_changed = true;
             }
+            // A draw-time switch: the stage pass sorts and picks the
+            // see-through pipeline; nothing to rebuild. Said out loud at full
+            // opacity, where it has nothing to show and would look broken.
+            Action::ToggleShowOccluded => {
+                self.show_occluded = !self.show_occluded;
+                self.viewport_dirty = true;
+                settings_changed = true;
+                if self.show_occluded && self.geo_opacity >= 0.999 {
+                    self.update_status_text("Show Occluded takes effect below 100% opacity.");
+                }
+            }
             // The three point overlays. Each is collected in
             // `rebuild_scene_geometry` off the scene's own Detail, so the
             // flip has to re-run it: the meshes are built from the flags,
@@ -9441,36 +9483,57 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
                     // furniture stays opaque.
                     const NO_TINT: [f32; 4] = [0.0; 4];
                     let geo_opacity = self.geo_opacity.clamp(0.0, 1.0);
-                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false }];
+
+                    // See-through fill: re-sort the triangles back to front
+                    // for THIS eye and upload them over the flush's order,
+                    // whenever the geometry, the shading or the eye moved.
+                    // The eye is taken in mesh space (the inverse of view *
+                    // model), the space the triangles are in.
+                    let see_through = self.see_through_active();
+                    if see_through && self.vertex_count_spheres > 0 {
+                        let eye = (view_mat * model).inverse().transform_point3(Vec3::ZERO);
+                        let key = (self.rt_geometry_version, self.smooth_shading, eye.to_array());
+                        if self.sorted_fill_key != Some(key) {
+                            let src = if self.smooth_shading { &self.scene_smooth_verts } else { &self.rt_sphere_verts };
+                            let sorted = crate::geometry::sort_triangles_back_to_front(src, eye);
+                            renderer.update_mesh(meshes.spheres, bytemuck::cast_slice(&sorted));
+                            self.sorted_fill_key = Some(key);
+                        }
+                    } else {
+                        // The sorted order is still a valid opaque mesh, so
+                        // nothing re-uploads; the next entry just sorts anew.
+                        self.sorted_fill_key = None;
+                    }
+                    let mut draws = vec![SceneDraw { mesh: meshes.viewport_bg, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false }];
                     if self.viewport().show_grid {
-                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.grid, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     if self.viewport().show_origin {
-                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.origin, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     if self.viewport().show_camera_pivot {
-                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.pivot, mvp: mvp_pivot, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     if self.viewport().show_cube {
-                        draws.push(SceneDraw { mesh: meshes.cube, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.cube, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     if self.render_points && self.point_vertex_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: geo_opacity, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     // Selected-Group markers: full-opacity selection feedback,
                     // deliberately outside the Render node's Opacity.
                     if self.group_point_vertex_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.group_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.group_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     // Per-node meta "Point Markers", same full-opacity tier.
                     if self.overlay_point_count > 0 {
-                        draws.push(SceneDraw { mesh: meshes.overlay_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                        draws.push(SceneDraw { mesh: meshes.overlay_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                     }
                     if self.vertex_count_spheres > 0 {
                         // 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 });
+                        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 });
                         if self.wireframe {
                             // The wire pass rides ON TOP of the fill (never
                             // replaces it). Single-color mode replaces the
@@ -9489,13 +9552,13 @@ 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 });
+                            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 });
                         }
                         // Show Point Normals: thin cyan whiskers,
                         // width deliberately fixed (a chunky Wire Width is a
                         // wireframe styling choice, not a normals one).
                         if self.overlay_normal_count > 0 {
-                            draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false });
+                            draws.push(SceneDraw { mesh: meshes.overlay_normals, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false });
                         }
                     }
                     renderer.stage_scene((sx, sy, cw, ch), draws);
diff --git a/src/command.rs b/src/command.rs
index 40ca7d8..0710ff0 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -190,6 +190,7 @@ pub const COMMANDS: &[Command] = &[
     Command { id: "toggle_camera_pivot", label: "Show Camera Pivot", context: Context::Viewport, run: Run::Key(Action::ToggleCameraPivot), default_chord: None },
     Command { id: "toggle_wireframe", label: "Show Wireframe", context: Context::Viewport, run: Run::Key(Action::ToggleWireframe), default_chord: None },
     Command { id: "toggle_smooth_shading", label: "Smooth Shading", context: Context::Viewport, run: Run::Key(Action::ToggleSmoothShading), default_chord: None },
+    Command { id: "toggle_show_occluded", label: "Show Occluded", context: Context::Viewport, run: Run::Key(Action::ToggleShowOccluded), default_chord: None },
     // The point overlays on the visible scene. Per-node `meta` child
     // preferences until 2026-09-23; global display settings now, reached
     // here like every other viewport toggle.
diff --git a/src/dialog.rs b/src/dialog.rs
index 0c5589a..2a6f562 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -1610,6 +1610,7 @@ impl State {
             "toggle_camera_pivot" => self.viewport().show_camera_pivot,
             "toggle_wireframe" => self.wireframe,
             "toggle_smooth_shading" => self.smooth_shading,
+            "toggle_show_occluded" => self.show_occluded,
             "toggle_point_markers" => self.show_point_markers,
             "toggle_point_numbers" => self.show_point_numbers,
             "toggle_point_normals" => self.show_point_normals,
diff --git a/src/geometry.rs b/src/geometry.rs
index adaef4c..3af1e61 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -124,6 +124,33 @@ pub fn detail_vertices(d: &Detail) -> Vec<Vertex3D> {
     d.triangulate(|position, color| Vertex3D { position, color })
 }
 
+/// The fill's triangles reordered FARTHEST FIRST from `eye` (mesh space),
+/// by the squared distance to each centroid — what a see-through fill
+/// needs, since with no depth writes the pass blends in submission order and
+/// a near layer drawn before a far one would sit under it. Painter's order
+/// by centroid is exact for non-intersecting triangles of similar size and
+/// close enough for the rest, which is the trade every sorted-transparency
+/// viewport makes. `verts` is a triangle list; a trailing partial triangle
+/// is dropped.
+pub fn sort_triangles_back_to_front(verts: &[Vertex3D], eye: Vec3) -> Vec<Vertex3D> {
+    let tris = verts.len() / 3;
+    let mut keyed: Vec<(f32, usize)> = (0..tris)
+        .map(|t| {
+            let c = (Vec3::from_array(verts[3 * t].position)
+                + Vec3::from_array(verts[3 * t + 1].position)
+                + Vec3::from_array(verts[3 * t + 2].position))
+                / 3.0;
+            ((c - eye).length_squared(), t)
+        })
+        .collect();
+    keyed.sort_unstable_by(|a, b| b.0.total_cmp(&a.0));
+    let mut out = Vec::with_capacity(tris * 3);
+    for (_, t) in keyed {
+        out.extend_from_slice(&verts[3 * t..3 * t + 3]);
+    }
+    out
+}
+
 /// The raster pass's light, in WORLD space — `scene3d.wgsl`'s `l`, which the
 /// smooth bake below has to match or switching shading modes would move
 /// the lit side of the model.
diff --git a/src/main.rs b/src/main.rs
index a1989b6..4a47874 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1493,6 +1493,59 @@ mod tests {
         assert!(varied > lit.len() / 6, "smooth shading varies across faces: {varied}");
     }
 
+    /// The see-through fill's order: farthest triangle first from the eye,
+    /// every triangle kept whole and exactly once — a sort that split or
+    /// dropped one would draw a torn mesh while looking like a blend bug.
+    #[test]
+    fn a_see_through_fill_sorts_its_triangles_back_to_front() {
+        use crate::geometry::{detail_vertices, sort_triangles_back_to_front, sphere_detail, Vertex3D};
+        use glam::Vec3;
+        let verts = detail_vertices(&sphere_detail(Vec3::ZERO, 1.0, 10, 14));
+        let eye = Vec3::new(3.0, 1.5, -2.0);
+        let sorted = sort_triangles_back_to_front(&verts, eye);
+        assert_eq!(sorted.len(), verts.len());
+        let dist = |t: &[Vertex3D]| {
+            let c = t.iter().fold(Vec3::ZERO, |a, v| a + Vec3::from_array(v.position)) / 3.0;
+            (c - eye).length_squared()
+        };
+        let d: Vec<f32> = sorted.chunks(3).map(dist).collect();
+        assert!(d.windows(2).all(|w| w[0] >= w[1]), "not farthest-first");
+        let key = |t: &[Vertex3D]| format!("{:?}", t.iter().map(|v| v.position).collect::<Vec<_>>());
+        let mut a: Vec<String> = verts.chunks(3).map(key).collect();
+        let mut b: Vec<String> = sorted.chunks(3).map(key).collect();
+        a.sort();
+        b.sort();
+        assert_eq!(a, b, "the same triangles, each whole, each once");
+        // From the opposite side the order reverses its ends.
+        let back = sort_triangles_back_to_front(&verts, -eye);
+        assert_eq!(key(&back[..3]), key(&sorted[sorted.len() - 3..]));
+    }
+
+    /// Show Occluded is a toggle with a viewport-menu row, and it takes
+    /// effect only below full opacity — at 100% it says so rather than
+    /// quietly doing nothing.
+    #[test]
+    fn show_occluded_sees_through_a_translucent_fill_only() {
+        use crate::app::ViewportMenuAction as A;
+        let mut state = State::new(false);
+        state.show_occluded = false;
+        state.geo_opacity = 1.0;
+        let (options, actions) = state.viewport_menu_rows();
+        let i = actions.iter().position(|a| *a == A::Command("toggle_show_occluded")).expect("a Show Occluded row");
+        assert_eq!(options[i], "○ Show Occluded");
+
+        state.run_viewport_menu_action(A::Command("toggle_show_occluded"));
+        assert!(state.show_occluded);
+        assert_eq!(state.command_toggle_state("toggle_show_occluded"), Some(true));
+        assert!(!state.see_through_active(), "nothing to see through at 100%");
+        assert!(state.last_status_text.contains("below 100%"), "{}", state.last_status_text);
+
+        state.run_viewport_menu_action(A::Opacity(0.5));
+        assert!(state.see_through_active());
+        state.run_command("toggle_show_occluded");
+        assert!(!state.see_through_active());
+    }
+
     /// The viewport's right-click menu sets the display mode: the wireframe
     /// switch, flat or smooth shading as a radio pair, and the polygon
     /// opacity as presets — each mark reading the live state, each row
@@ -4857,6 +4910,7 @@ mod tests {
         a.point_color = [0.0, 1.0, 0.0];
         a.group_marker_scale = 2.5;
         a.smooth_shading = true;
+        a.show_occluded = true;
         a.save_settings();
 
         let kdl = std::fs::read_to_string(DesignSettings::file_path()).expect("state.kdl was written");
@@ -4895,6 +4949,7 @@ mod tests {
         assert!(close(back.render.point_size, 0.05));
         assert!(close(back.render.group_marker_scale, 2.5));
         assert!(back.render.smooth_shading);
+        assert!(back.render.show_occluded);
     }
 
     /// Changing the wire colour turns single-colour mode on, so the colour
diff --git a/src/shortcut.rs b/src/shortcut.rs
index 6988a2e..220b15e 100644
--- a/src/shortcut.rs
+++ b/src/shortcut.rs
@@ -13,6 +13,8 @@ pub enum Action {
     ToggleWireframe,
     /// Flat (faceted) or smooth shading of the scene fill.
     ToggleSmoothShading,
+    /// See through a translucent fill to what it occludes.
+    ToggleShowOccluded,
     /// The three point overlays on the visible scene — markers, index
     /// numbers, normal whiskers. Global display settings reached from the
     /// command palette; they were per-node `meta` child preferences until
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index 496e664..7bbe0d3 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, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false },
-                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false },
-                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false },
+                        SceneDraw { mesh: bg_mesh, mvp: Mat4::IDENTITY.to_cols_array_2d(), wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false },
+                        SceneDraw { mesh: grid_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false },
+                        SceneDraw { mesh: cube_mesh, mvp, wireframe: false, wire_tint: [0.0; 4], opacity: 1.0, line_width: 1.0, wire_base_width: 0.0, prelit: false, see_through: false },
                     ],
                 );
             }