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

commit0632a91c7feccb3447995fbf5bff4f9ba6a9b2ef
parent84a1105791
authorLucas Galante <[email protected]>
date2026-09-21 14:50
feat: Plane gains the same Color toggle as the Sphere

On by default, the gradient the plane has always carried; off, DEFAULT_COLOR,
the grey geometry without Cd renders at. The colour-toggle test is now one
helper run over both templates.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 nodes/plane.json |  7 ++++++-
 src/main.rs      | 37 +++++++++++++++++++++++--------------
 2 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/nodes/plane.json b/nodes/plane.json
index 6c5d35f..bea95b0 100644
--- a/nodes/plane.json
+++ b/nodes/plane.json
@@ -44,6 +44,11 @@
       "name": "Center Z",
       "default": "0.0",
       "type": "slider:-2:2"
+    },
+    {
+      "name": "Color",
+      "default": "true",
+      "type": "toggle"
     }
   ],
   "children": [
@@ -53,7 +58,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 size_x = chf(\"Width\", 1.0f);\n        float size_z = chf(\"Length\", 1.0f);\n        int cols = chi(\"Columns\", 16);\n        int rows = chi(\"Rows\", 16);\n        if (cols < 1) cols = 1;\n        if (cols > 128) cols = 128;\n        if (rows < 1) rows = 1;\n        if (rows > 128) rows = 128;\n        float center_x = chf(\"Center X\", 0.0f);\n        float center_y = chf(\"Center Y\", 0.0f);\n        float center_z = chf(\"Center Z\", 0.0f);\n        float half_x = size_x * 0.5f;\n        float half_z = size_z * 0.5f;\n        float step_x = size_x / (float)cols;\n        float step_z = size_z / (float)rows;\n        float inv_x = size_x > 0.0f ? 1.0f / size_x : 0.0f;\n        float inv_z = size_z > 0.0f ? 1.0f / size_z : 0.0f;\n        int count = 0;\n        for (int i = 0; i < cols; i++) {\n            float x0 = -half_x + (float)i * step_x;\n            float x1 = x0 + step_x;\n            for (int j = 0; j < rows; j++) {\n                float z0 = -half_z + (float)j * step_z;\n                float z1 = z0 + step_z;\n                float px[6] = {x0, x1, x1, x0, x0, x1};\n                float pz[6] = {z0, z1, z0, z0, z1, z1};\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;\n                        out_pos[idx * 3 + 2] = center_z + pz[v];\n                        float fx = (px[v] + half_x) * inv_x;\n                        float fz = (pz[v] + half_z) * inv_z;\n                        out_col[idx * 3 + 0] = 0.35f + fx * 0.35f;\n                        out_col[idx * 3 + 1] = 0.45f + fz * 0.35f;\n                        out_col[idx * 3 + 2] = 0.85f;\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 size_x = chf(\"Width\", 1.0f);\n        float size_z = chf(\"Length\", 1.0f);\n        int cols = chi(\"Columns\", 16);\n        int rows = chi(\"Rows\", 16);\n        int colored = chb(\"Color\", true) > 0.5f ? 1 : 0;\n        if (cols < 1) cols = 1;\n        if (cols > 128) cols = 128;\n        if (rows < 1) rows = 1;\n        if (rows > 128) rows = 128;\n        float center_x = chf(\"Center X\", 0.0f);\n        float center_y = chf(\"Center Y\", 0.0f);\n        float center_z = chf(\"Center Z\", 0.0f);\n        float half_x = size_x * 0.5f;\n        float half_z = size_z * 0.5f;\n        float step_x = size_x / (float)cols;\n        float step_z = size_z / (float)rows;\n        float inv_x = size_x > 0.0f ? 1.0f / size_x : 0.0f;\n        float inv_z = size_z > 0.0f ? 1.0f / size_z : 0.0f;\n        int count = 0;\n        for (int i = 0; i < cols; i++) {\n            float x0 = -half_x + (float)i * step_x;\n            float x1 = x0 + step_x;\n            for (int j = 0; j < rows; j++) {\n                float z0 = -half_z + (float)j * step_z;\n                float z1 = z0 + step_z;\n                float px[6] = {x0, x1, x1, x0, x0, x1};\n                float pz[6] = {z0, z1, z0, z0, z1, z1};\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;\n                        out_pos[idx * 3 + 2] = center_z + pz[v];\n                        float fx = (px[v] + half_x) * inv_x;\n                        float fz = (pz[v] + half_z) * inv_z;\n                        if (colored) {\n                            out_col[idx * 3 + 0] = 0.35f + fx * 0.35f;\n                            out_col[idx * 3 + 1] = 0.45f + fz * 0.35f;\n                            out_col[idx * 3 + 2] = 0.85f;\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": [
diff --git a/src/main.rs b/src/main.rs
index eae34b9..2bb8f6b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1445,25 +1445,24 @@ 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
+    /// A template's Color toggle: on, the gradient the shape has always
+    /// carried; off, `DEFAULT_COLOR` — the same grey geometry with no `Cd`
+    /// at all renders at, so an uncoloured sphere or plane looks like an
     /// uncoloured anything else rather than like a second colour scheme.
-    #[test]
-    fn sphere_color_toggle_switches_between_gradient_and_default() {
+    fn assert_color_toggle_switches_between_gradient_and_default(template_name: &str) {
         let templates_root = crate::app::load_fs_tree();
-        let sphere_template = templates_root
+        let 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");
+            .find(|t| t.name == template_name)
+            .unwrap_or_else(|| panic!("{template_name} template should be loaded"));
+        let color = 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}");
+            let mut inst = template.clone();
+            inst.id = format!("{template_name}_{on}");
             for child in &mut inst.children {
                 child.id = format!("{}_{}", inst.id, child.name);
             }
@@ -1489,14 +1488,14 @@ mod tests {
                 &mut crate::geometry::EvalSim::new(0, 0, &mut crate::geometry::SimCache::default()),
             )
             .expect("geometry");
-            assert!(err.is_none(), "kernel error: {err:?}");
+            assert!(err.is_none(), "{template_name} 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");
+            assert_eq!(off.color(p), crate::detail::DEFAULT_COLOR, "{template_name} point {p} off");
         }
 
         let on = generate("true");
@@ -1504,10 +1503,20 @@ mod tests {
         let first = on.color(0);
         assert!(
             (0..on.num_points()).any(|p| on.color(p) != first),
-            "on, the sphere carries its gradient"
+            "on, the {template_name} carries its gradient"
         );
     }
 
+    #[test]
+    fn sphere_color_toggle_switches_between_gradient_and_default() {
+        assert_color_toggle_switches_between_gradient_and_default("Sphere");
+    }
+
+    #[test]
+    fn plane_color_toggle_switches_between_gradient_and_default() {
+        assert_color_toggle_switches_between_gradient_and_default("Plane");
+    }
+
     #[test]
     fn test_sphere_subnet_geometry_generation() {
         let templates_root = crate::app::load_fs_tree();