graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: Sphere gains a Color toggle
On (the default, and what every saved sphere gets on load through the
template merge), the normal-mapped gradient the sphere has always carried.
Off, the kernel writes DEFAULT_COLOR instead — the same grey that geometry
with no Cd renders at, so an uncoloured sphere looks like an uncoloured
anything else rather than like a second colour scheme. Read in the kernel
through chb(), the toggle prefix the box and extrude templates already use.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
nodes/sphere.json | 9 ++++++--
src/main.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/nodes/sphere.json b/nodes/sphere.json
index c028188..e79e4b4 100644
--- a/nodes/sphere.json
+++ b/nodes/sphere.json
@@ -39,6 +39,11 @@
"name": "Center Z",
"default": "0.0",
"type": "slider:-2:2"
+ },
+ {
+ "name": "Color",
+ "default": "true",
+ "type": "toggle"
}
],
"children": [
@@ -48,7 +53,7 @@
"params": [
{
"name": "Code",
- "default": "__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n int id = get_global_id(0);\n if (id == 0) {\n float radius = chf(\"Radius\", 0.5f);\n float center_x = chf(\"Center X\", 0.0f);\n float center_y = chf(\"Center Y\", 0.55f);\n float center_z = chf(\"Center Z\", 0.0f);\n int lat_steps = chi(\"Rows\", 16);\n if (lat_steps < 2) { lat_steps = 2; }\n if (lat_steps > 128) { lat_steps = 128; }\n int lon_steps = chi(\"Columns\", 24);\n if (lon_steps < 3) { lon_steps = 3; }\n if (lon_steps > 128) { lon_steps = 128; }\n int count = 0;\n for (int lat = 0; lat < lat_steps; lat++) {\n float theta0 = 3.14159265f * (float)lat / (float)lat_steps;\n float theta1 = 3.14159265f * (float)(lat + 1) / (float)lat_steps;\n for (int lon = 0; lon < lon_steps; lon++) {\n float phi0 = 6.2831853f * (float)lon / (float)lon_steps;\n float phi1 = 6.2831853f * (float)(lon + 1) / (float)lon_steps;\n float x00 = radius * sin(theta0) * cos(phi0);\n float y00 = radius * cos(theta0);\n float z00 = radius * sin(theta0) * sin(phi0);\n float x10 = radius * sin(theta1) * cos(phi0);\n float y10 = radius * cos(theta1);\n float z10 = radius * sin(theta1) * sin(phi0);\n float x11 = radius * sin(theta1) * cos(phi1);\n float y11 = radius * cos(theta1);\n float z11 = radius * sin(theta1) * sin(phi1);\n float x01 = radius * sin(theta0) * cos(phi1);\n float y01 = radius * cos(theta0);\n float z01 = radius * sin(theta0) * sin(phi1);\n float px[6] = {x00, x11, x10, x00, x01, x11};\n float py[6] = {y00, y11, y10, y00, y01, y11};\n float pz[6] = {z00, z11, z10, z00, z01, z11};\n for (int v = 0; v < 6; v++) {\n int idx = count++;\n if (idx < max_vertices) {\n out_pos[idx * 3 + 0] = center_x + px[v];\n out_pos[idx * 3 + 1] = center_y + py[v];\n out_pos[idx * 3 + 2] = center_z + pz[v];\n float nx = px[v];\n float ny = py[v];\n float nz = pz[v];\n float len = sqrt(nx*nx + ny*ny + nz*nz);\n if (len > 0.0f) {\n nx /= len;\n ny /= len;\n nz /= len;\n }\n out_col[idx * 3 + 0] = 0.5f + nx * 0.5f;\n out_col[idx * 3 + 1] = 0.5f + ny * 0.5f;\n out_col[idx * 3 + 2] = 0.5f + nz * 0.5f;\n }\n }\n }\n }\n *out_count = count;\n }\n}"
+ "default": "__kernel void process(__global const float* in_pos, __global const float* in_col, int in_count, __global float* out_pos, __global float* out_col, __global int* out_count, int max_vertices) {\n int id = get_global_id(0);\n if (id == 0) {\n float radius = chf(\"Radius\", 0.5f);\n float center_x = chf(\"Center X\", 0.0f);\n float center_y = chf(\"Center Y\", 0.55f);\n float center_z = chf(\"Center Z\", 0.0f);\n int lat_steps = chi(\"Rows\", 16);\n if (lat_steps < 2) { lat_steps = 2; }\n if (lat_steps > 128) { lat_steps = 128; }\n int lon_steps = chi(\"Columns\", 24);\n if (lon_steps < 3) { lon_steps = 3; }\n if (lon_steps > 128) { lon_steps = 128; }\n int colored = chb(\"Color\", true) > 0.5f ? 1 : 0;\n int count = 0;\n for (int lat = 0; lat < lat_steps; lat++) {\n float theta0 = 3.14159265f * (float)lat / (float)lat_steps;\n float theta1 = 3.14159265f * (float)(lat + 1) / (float)lat_steps;\n for (int lon = 0; lon < lon_steps; lon++) {\n float phi0 = 6.2831853f * (float)lon / (float)lon_steps;\n float phi1 = 6.2831853f * (float)(lon + 1) / (float)lon_steps;\n float x00 = radius * sin(theta0) * cos(phi0);\n float y00 = radius * cos(theta0);\n float z00 = radius * sin(theta0) * sin(phi0);\n float x10 = radius * sin(theta1) * cos(phi0);\n float y10 = radius * cos(theta1);\n float z10 = radius * sin(theta1) * sin(phi0);\n float x11 = radius * sin(theta1) * cos(phi1);\n float y11 = radius * cos(theta1);\n float z11 = radius * sin(theta1) * sin(phi1);\n float x01 = radius * sin(theta0) * cos(phi1);\n float y01 = radius * cos(theta0);\n float z01 = radius * sin(theta0) * sin(phi1);\n float px[6] = {x00, x11, x10, x00, x01, x11};\n float py[6] = {y00, y11, y10, y00, y01, y11};\n float pz[6] = {z00, z11, z10, z00, z01, z11};\n for (int v = 0; v < 6; v++) {\n int idx = count++;\n if (idx < max_vertices) {\n out_pos[idx * 3 + 0] = center_x + px[v];\n out_pos[idx * 3 + 1] = center_y + py[v];\n out_pos[idx * 3 + 2] = center_z + pz[v];\n float nx = px[v];\n float ny = py[v];\n float nz = pz[v];\n float len = sqrt(nx*nx + ny*ny + nz*nz);\n if (len > 0.0f) {\n nx /= len;\n ny /= len;\n nz /= len;\n }\n if (colored) {\n out_col[idx * 3 + 0] = 0.5f + nx * 0.5f;\n out_col[idx * 3 + 1] = 0.5f + ny * 0.5f;\n out_col[idx * 3 + 2] = 0.5f + nz * 0.5f;\n } else {\n out_col[idx * 3 + 0] = 0.8f;\n out_col[idx * 3 + 1] = 0.8f;\n out_col[idx * 3 + 2] = 0.8f;\n }\n }\n }\n }\n }\n *out_count = count;\n }\n}"
}
],
"position": [
@@ -71,4 +76,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/src/main.rs b/src/main.rs
index d201c12..eae34b9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1445,6 +1445,69 @@ mod tests {
);
}
+ /// The Sphere's Color toggle: on, the normal-mapped gradient the sphere
+ /// has always had; off, `DEFAULT_COLOR` — the same grey geometry with no
+ /// `Cd` at all renders at, so an uncoloured sphere looks like an
+ /// uncoloured anything else rather than like a second colour scheme.
+ #[test]
+ fn sphere_color_toggle_switches_between_gradient_and_default() {
+ let templates_root = crate::app::load_fs_tree();
+ let sphere_template = templates_root
+ .children
+ .iter()
+ .find(|t| t.name == "Sphere")
+ .expect("Sphere template should be loaded");
+ let color = sphere_template.params.iter().find(|p| p.name == "Color").expect("a Color param");
+ assert_eq!(color.param_type, "toggle");
+ assert_eq!(color.default, "true", "coloured by default, as it always was");
+
+ let generate = |on: &str| {
+ let mut inst = sphere_template.clone();
+ inst.id = format!("sphere_{on}");
+ for child in &mut inst.children {
+ child.id = format!("{}_{}", inst.id, child.name);
+ }
+ inst.params.iter_mut().find(|p| p.name == "Color").unwrap().default = on.to_string();
+ let root = FsNode {
+ id: "root".to_string(),
+ name: "root".to_string(),
+ node_type: "node".to_string(),
+ children: vec![inst],
+ params: vec![],
+ geometry_visible: true,
+ position: (0.0, 0.0),
+ inputs: 0,
+ outputs: 0,
+ };
+ let mut visited = Vec::new();
+ let mut err = None;
+ let geom = crate::geometry::generate_single_node_geometry_with_errors(
+ &root,
+ &root.children[0],
+ &mut visited,
+ &mut err,
+ &mut crate::geometry::EvalSim::new(0, 0, &mut crate::geometry::SimCache::default()),
+ )
+ .expect("geometry");
+ assert!(err.is_none(), "kernel error: {err:?}");
+ geom
+ };
+
+ let off = generate("false");
+ assert!(off.num_points() > 0);
+ for p in 0..off.num_points() {
+ assert_eq!(off.color(p), crate::detail::DEFAULT_COLOR, "point {p} off");
+ }
+
+ let on = generate("true");
+ assert_eq!(on.num_points(), off.num_points(), "the toggle changes colour, not shape");
+ let first = on.color(0);
+ assert!(
+ (0..on.num_points()).any(|p| on.color(p) != first),
+ "on, the sphere carries its gradient"
+ );
+ }
+
#[test]
fn test_sphere_subnet_geometry_generation() {
let templates_root = crate::app::load_fs_tree();
@@ -2599,7 +2662,7 @@ mod tests {
// kernel refreshed.
let s = &root.children[0];
let names: Vec<&str> = s.params.iter().map(|p| p.name.as_str()).collect();
- assert_eq!(names, ["Radius", "Rows", "Columns", "Center X", "Center Y", "Center Z"]);
+ assert_eq!(names, ["Radius", "Rows", "Columns", "Center X", "Center Y", "Center Z", "Color"]);
assert_eq!(s.params[0].default, "0.70", "instance value survives");
let code = &s.children.iter().find(|c| c.name == "opencl1").unwrap()
.params.iter().find(|p| p.name == "Code").unwrap().default;