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