graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: Wire Color is rgba — its alpha is the wireframe's opacity; geometry Opacity is polygons-only
The wire pass no longer inherits the Opacity slider: wires fade via the
Wire Color alpha channel instead, in both color modes. Older saves'
plain-rgb Wire Color migrates to rgba on load.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/app.rs | 16 +++++++++++-----
src/project.rs | 26 +++++++++++++++++++++++---
2 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 2e3deb0..c6a77a1 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -997,11 +997,13 @@ pub struct State {
/// they carry the geometry's vertex colors unlit — brighter than the lit
/// fill beneath, which is what separates them.
pub wire_single_color: bool,
- pub wire_color: [f32; 3],
+ /// RGBA: the alpha channel is the wireframe's OWN opacity in both color
+ /// modes — the geometry Opacity slider affects only the polygons.
+ pub wire_color: [f32; 4],
/// Wire line width in framebuffer pixels ("Wire Thickness" slider).
pub wire_width: f32,
pub last_viewport_wire_single_color: bool,
- pub last_viewport_wire_color: [f32; 3],
+ pub last_viewport_wire_color: [f32; 4],
pub last_viewport_wire_width: f32,
/// Opacity of the rendered node geometry (the Render node's "Opacity"
/// slider): 1.0 opaque, straight-alpha blended toward the viewport bg.
@@ -2835,10 +2837,10 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
wireframe: false,
last_viewport_wireframe: false,
wire_single_color: false,
- wire_color: [1.0, 1.0, 1.0],
+ wire_color: [1.0, 1.0, 1.0, 1.0],
wire_width: 1.0,
last_viewport_wire_single_color: false,
- last_viewport_wire_color: [1.0, 1.0, 1.0],
+ last_viewport_wire_color: [1.0, 1.0, 1.0, 1.0],
last_viewport_wire_width: 1.0,
geo_opacity: 1.0,
last_viewport_geo_opacity: 1.0,
@@ -5441,12 +5443,16 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
// fill beneath. Far-side wires that clear the
// depth test near the limb show as their own
// (complementary) colors — a soft x-ray read.
+ // The wires' opacity is the Wire Color ALPHA in
+ // both modes; the geometry Opacity slider is
+ // polygons-only.
let tint = if self.wire_single_color {
[self.wire_color[0], self.wire_color[1], self.wire_color[2], 1.0]
} else {
[0.0, 0.0, 0.0, 0.0]
};
- draws.push(SceneDraw { mesh: meshes.sphere_edges, mvp, wireframe: true, wire_tint: tint, opacity: geo_opacity, line_width: self.wire_width, wire_base_width: 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 });
}
}
renderer.stage_scene((sx, sy, cw, ch), draws);
diff --git a/src/project.rs b/src/project.rs
index aed6924..7bc4b57 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -15,6 +15,20 @@ fn hex_to_color(hex: &str) -> Option<[f32; 3]> {
cce_ui::color::parse_hex_rgb(hex)
}
+fn color_to_hex8(rgba: [f32; 4]) -> String {
+ format!("#{:02x}{:02x}{:02x}{:02x}",
+ (rgba[0] * 255.0).round().clamp(0.0, 255.0) as u8,
+ (rgba[1] * 255.0).round().clamp(0.0, 255.0) as u8,
+ (rgba[2] * 255.0).round().clamp(0.0, 255.0) as u8,
+ (rgba[3] * 255.0).round().clamp(0.0, 255.0) as u8
+ )
+}
+
+/// 6- or 8-digit hex → RGBA (alpha 1.0 when absent).
+fn hex_to_rgba(hex: &str) -> Option<[f32; 4]> {
+ cce_ui::color::parse_hex_rgba(hex)
+}
+
impl State {
@@ -530,7 +544,9 @@ 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, "Wire Single Color", "toggle", bool_str(wire_single_color), &[], None, None, None);
- ensure_param(render_node, "Wire Color", "color", &color_to_hex(wire_color), &[], None, None, None);
+ // rgba: the alpha channel is the wireframe's own opacity (the
+ // geometry Opacity slider deliberately leaves wires alone).
+ ensure_param(render_node, "Wire Color", "rgba", &color_to_hex8(wire_color), &[], None, None, None);
ensure_param(render_node, "Wire Thickness", "slider:1.0:8.0:1", &format!("{:.1}", wire_width), &[], Some(1.0), Some(8.0), None);
ensure_param(render_node, "Opacity", "slider:0.00:1.00", &format!("{:.2}", geo_opacity), &[], Some(0.0), Some(1.0), None);
ensure_param(render_node, "Render Points", "toggle", bool_str(render_points), &[], None, None, None);
@@ -541,7 +557,11 @@ impl State {
match p.name.as_str() {
"Show Wireframe" => set_toggle(p, wireframe),
"Wire Single Color" => set_toggle(p, wire_single_color),
- "Wire Color" => p.default = color_to_hex(wire_color),
+ "Wire Color" => {
+ // Type migration: early saves carried a plain rgb color.
+ p.param_type = "rgba".to_string();
+ p.default = color_to_hex8(wire_color);
+ }
"Wire Thickness" => p.default = format!("{:.1}", wire_width),
"Opacity" => p.default = format!("{:.2}", geo_opacity),
"Render Points" => set_toggle(p, render_points),
@@ -690,7 +710,7 @@ impl State {
match p.name.as_str() {
"Show Wireframe" => if let Ok(val) = p.default.parse::<bool>() { self.wireframe = val; }
"Wire Single Color" => if let Ok(val) = p.default.parse::<bool>() { self.wire_single_color = val; }
- "Wire Color" => if let Some(col) = hex_to_color(&p.default) { self.wire_color = col; }
+ "Wire Color" => if let Some(col) = hex_to_rgba(&p.default) { self.wire_color = col; }
"Wire Thickness" => if let Ok(val) = p.default.parse::<f32>() { self.wire_width = val.clamp(1.0, 8.0); }
"Opacity" => if let Ok(val) = p.default.parse::<f32>() { self.geo_opacity = val.clamp(0.0, 1.0); }
"Render Points" => if let Ok(val) = p.default.parse::<bool>() { self.render_points = val; }