graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: meta Wireframe — per-node wire overlay
Third meta pref: Wireframe draws the flagged node's triangle edges as a
LINE_LIST wire pass in the geometry's own colors, over the scene fill —
the sphere_edges expansion scoped to one node, riding the same overlay
rebuild as the markers. The fill takes its slope-scaled offset whenever
ANY wires are coming (the global toggle's or a node's), so the lattice
reads solid either way. Existing metas gain the param through the
ensure migration; no scene-format change.
src/app.rs | 33 ++++++++++++++++++++++++++++-----
src/main.rs | 19 +++++++++++--------
src/render.rs | 31 +++++++++++++++++++++++++------
3 files changed, 64 insertions(+), 19 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index c5cdbd5..c37cc1b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -327,7 +327,9 @@ pub fn ensure_meta_on(node: &mut FsNode) {
});
}
let meta = node.children.iter_mut().find(|c| c.node_type == "meta").unwrap();
- for (name, default) in [("Point Markers", "false"), ("Point Numbers", "false")] {
+ for (name, default) in
+ [("Point Markers", "false"), ("Point Numbers", "false"), ("Wireframe", "false")]
+ {
if !meta.params.iter().any(|p| p.name == name) {
meta.params.push(ParamDef {
name: name.to_string(),
@@ -805,6 +807,8 @@ pub struct SceneMeshes {
pub group_points: cce_ui::vk::MeshId,
/// Per-node meta "Point Markers" overlay.
pub meta_points: cce_ui::vk::MeshId,
+ /// Per-node meta "Wireframe" overlay (LINE_LIST edge pairs).
+ pub meta_wires: cce_ui::vk::MeshId,
}
/// A left-press on the detached circular window's chrome that becomes an
@@ -1075,6 +1079,10 @@ pub struct State {
pub meta_points_dirty: bool,
pub meta_point_count: u32,
pub meta_number_labels: Vec<([f32; 3], u32)>,
+ /// Per-node meta "Wireframe": LINE_LIST edge pairs of the flagged nodes'
+ /// triangles, drawn as a wire pass over the scene fill.
+ pub meta_wire_verts: Vec<Vertex3D>,
+ pub meta_wire_count: u32,
/// The raster scene's model-view-projection and the viewport pane rect in
/// LOGICAL px, cached at staging so the 2D pass can project 3D overlays.
pub last_scene_mvp: Option<Mat4>,
@@ -3252,6 +3260,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
meta_points_dirty: false,
meta_point_count: 0,
meta_number_labels: Vec::new(),
+ meta_wire_verts: Vec::new(),
+ meta_wire_count: 0,
last_scene_mvp: None,
last_scene_view_rect: (0.0, 0.0, 0.0, 0.0),
last_viewport_rt_mode: false,
@@ -5802,11 +5812,13 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.viewport_dirty = true;
}
- // Per-node meta markers, staged by rebuild_scene_geometry.
+ // Per-node meta markers and wires, staged by rebuild_scene_geometry.
if self.meta_points_dirty {
self.meta_points_dirty = false;
renderer.update_mesh(meshes.meta_points, bytemuck::cast_slice(&self.meta_marker_verts));
self.meta_point_count = self.meta_marker_verts.len() as u32;
+ renderer.update_mesh(meshes.meta_wires, bytemuck::cast_slice(&self.meta_wire_verts));
+ self.meta_wire_count = self.meta_wire_verts.len() as u32;
self.viewport_dirty = true;
}
}
@@ -5833,6 +5845,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
points: renderer.create_mesh(&[]),
group_points: renderer.create_mesh(&[]),
meta_points: renderer.create_mesh(&[]),
+ meta_wires: renderer.create_mesh(&[]),
});
// Scene geometry built during `State::new` (before the renderer
// existed) uploads on the first frame's flush.
@@ -6006,9 +6019,14 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
draws.push(SceneDraw { mesh: meshes.meta_points, mvp, wireframe: false, wire_tint: NO_TINT, opacity: 1.0, line_width: 1.0, wire_base_width: 0.0 });
}
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 };
+ // With wires coming — the global toggle's or any
+ // node's meta Wireframe — the fill is pushed back by
+ // its slope-scaled offset so the lattice reads solid.
+ let base = if self.wireframe || self.meta_wire_count > 0 {
+ 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 });
if self.wireframe {
// The wire pass rides ON TOP of the fill (never
@@ -6030,6 +6048,11 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
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 });
}
+ // Per-node meta Wireframe: the same wire pass, scoped
+ // to the flagged nodes' edges, in geometry colors.
+ if self.meta_wire_count > 0 {
+ draws.push(SceneDraw { mesh: meshes.meta_wires, mvp, wireframe: true, wire_tint: NO_TINT, opacity: 1.0, line_width: self.wire_width, wire_base_width: 0.0 });
+ }
}
renderer.stage_scene((sx, sy, cw, ch), draws);
}
diff --git a/src/main.rs b/src/main.rs
index d330ebc..db73fbc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1171,7 +1171,7 @@ mod tests {
let meta = s.children.iter().find(|c| c.node_type == "meta").expect("sphere meta");
assert_eq!(
meta.params.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
- ["Point Markers", "Point Numbers"]
+ ["Point Markers", "Point Numbers", "Wireframe"]
);
assert!(meta.params.iter().all(|p| p.default == "false"));
let opencl = s.children.iter().find(|c| c.name == "opencl1").unwrap();
@@ -1206,31 +1206,34 @@ mod tests {
// Overlays: nothing while the prefs are off…
let mut cache = crate::geometry::SimCache::default();
- let (markers, labels) = crate::render::collect_meta_overlays(
+ let (markers, labels, wires) = crate::render::collect_meta_overlays(
&root, 0.02, &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
- assert!(markers.is_empty() && labels.is_empty());
+ assert!(markers.is_empty() && labels.is_empty() && wires.is_empty());
- // …both overlays for the flagged sphere (240 marker verts per
- // deduped point, labels matching the same dedupe)…
+ // …all three overlays for the flagged sphere: 240 marker verts per
+ // deduped point, labels matching the same dedupe, and one LINE_LIST
+ // pair per triangle edge (2304 verts = 768 triangles = 4608 pairs).
{
let meta = root.children[0].children.iter_mut()
.find(|c| c.node_type == "meta").unwrap();
for p in meta.params.iter_mut() { p.default = "true".to_string(); }
}
assert!(crate::app::meta_pref(&root.children[0], "Point Markers"));
+ assert!(crate::app::meta_pref(&root.children[0], "Wireframe"));
let mut cache = crate::geometry::SimCache::default();
- let (markers, labels) = crate::render::collect_meta_overlays(
+ let (markers, labels, wires) = crate::render::collect_meta_overlays(
&root, 0.02, &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
assert!(!labels.is_empty() && labels.len() < 16 * 24 * 6);
assert_eq!(markers.len(), labels.len() * 240);
assert!(labels.iter().any(|(_, i)| *i > 0));
+ assert_eq!(wires.len(), (16 * 24 * 6 / 3) * 6);
// …and none once the node's geometry is hidden.
root.children[0].geometry_visible = false;
let mut cache = crate::geometry::SimCache::default();
- let (markers, labels) = crate::render::collect_meta_overlays(
+ let (markers, labels, wires) = crate::render::collect_meta_overlays(
&root, 0.02, &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
- assert!(markers.is_empty() && labels.is_empty());
+ assert!(markers.is_empty() && labels.is_empty() && wires.is_empty());
}
/// The Plane template's construction controls: Rows/Columns set the grid
diff --git a/src/render.rs b/src/render.rs
index 05fbf95..c0d5669 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -856,13 +856,14 @@ impl State {
// Per-node meta overlays ride the same rebuild: markers and point
// numbers for nodes whose meta child asks for them.
let mut sim_cache = std::mem::take(&mut self.sim_cache);
- let (markers, labels) = {
+ let (markers, labels, wires) = {
let mut sim = crate::geometry::EvalSim::new(frame, start, &mut sim_cache);
collect_meta_overlays(&self.fs_root, self.point_size, &mut sim)
};
self.sim_cache = sim_cache;
self.meta_marker_verts = markers;
self.meta_number_labels = labels;
+ self.meta_wire_verts = wires;
self.meta_points_dirty = true;
}
@@ -898,9 +899,10 @@ pub(crate) fn collect_meta_overlays(
root: &FsNode,
point_size: f32,
sim: &mut crate::geometry::EvalSim,
-) -> (Vec<crate::geometry::Vertex3D>, Vec<([f32; 3], u32)>) {
+) -> (Vec<crate::geometry::Vertex3D>, Vec<([f32; 3], u32)>, Vec<crate::geometry::Vertex3D>) {
let mut markers = Vec::new();
let mut labels = Vec::new();
+ let mut wires = Vec::new();
fn visit(
root: &FsNode,
node: &FsNode,
@@ -908,17 +910,34 @@ pub(crate) fn collect_meta_overlays(
point_size: f32,
markers: &mut Vec<crate::geometry::Vertex3D>,
labels: &mut Vec<([f32; 3], u32)>,
+ wires: &mut Vec<crate::geometry::Vertex3D>,
sim: &mut crate::geometry::EvalSim,
) {
let is_visible = parent_visible && node.geometry_visible;
let want_markers = is_visible && crate::app::meta_pref(node, "Point Markers");
let want_numbers = is_visible && crate::app::meta_pref(node, "Point Numbers");
- if want_markers || want_numbers {
+ let want_wires = is_visible && crate::app::meta_pref(node, "Wireframe");
+ if want_markers || want_numbers || want_wires {
let mut visited = Vec::new();
let mut err = None;
if let Some(geom) = crate::geometry::generate_single_node_geometry_with_errors(
root, node, &mut visited, &mut err, sim,
) {
+ if want_wires {
+ // Each triangle's three edges as LINE_LIST pairs carrying
+ // the geometry's own colors — the sphere_edges expansion,
+ // scoped to this node.
+ for tri in geom.vertices.chunks_exact(3) {
+ for (a, b) in [(0usize, 1usize), (1, 2), (2, 0)] {
+ for v in [&tri[a], &tri[b]] {
+ wires.push(crate::geometry::Vertex3D {
+ position: v.pos,
+ color: v.col,
+ });
+ }
+ }
+ }
+ }
if want_markers {
let src: Vec<crate::geometry::Vertex3D> = geom
.vertices
@@ -947,11 +966,11 @@ pub(crate) fn collect_meta_overlays(
}
}
for c in &node.children {
- visit(root, c, is_visible, point_size, markers, labels, sim);
+ visit(root, c, is_visible, point_size, markers, labels, wires, sim);
}
}
for c in &root.children {
- visit(root, c, true, point_size, &mut markers, &mut labels, sim);
+ visit(root, c, true, point_size, &mut markers, &mut labels, &mut wires, sim);
}
// A dense mesh can label tens of thousands of points; the text pass is
// per-frame, so cap it rather than melt the frame rate.
@@ -959,5 +978,5 @@ pub(crate) fn collect_meta_overlays(
if labels.len() > MAX_LABELS {
labels.truncate(MAX_LABELS);
}
- (markers, labels)
+ (markers, labels, wires)
}