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

commit844cf6e32a29c36c0044264a58607dff31ade089
parent724c32cab5
authorLucas Galante <[email protected]>
date2026-07-21 13:04
feat: Plane node width/length + per-axis cell counts

Size splits into Width (X) and Length (Z) — slider:0:4 so the readout
range covers the values instead of display-clamping at the default 0-2
— plus Columns/Rows spinboxes (chi, clamped 1-128 in the kernel: 128^2
cells stays under the 200k vertex budget) controlling the grid
resolution per axis.

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

 nodes/plane.json | 27 ++++++++++++++++++++++++---
 src/main.rs      | 28 +++++++++++++++++-----------
 2 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/nodes/plane.json b/nodes/plane.json
index 9abd095..f5d068c 100644
--- a/nodes/plane.json
+++ b/nodes/plane.json
@@ -5,9 +5,30 @@
   "outputs": 1,
   "params": [
     {
-      "name": "Size",
+      "name": "Width",
       "default": "1.0",
-      "type": "slider"
+      "type": "slider:0:4"
+    },
+    {
+      "name": "Length",
+      "default": "1.0",
+      "type": "slider:0:4"
+    },
+    {
+      "name": "Columns",
+      "default": "16",
+      "type": "spinbox",
+      "min": 1,
+      "max": 128,
+      "step": 1
+    },
+    {
+      "name": "Rows",
+      "default": "16",
+      "type": "spinbox",
+      "min": 1,
+      "max": 128,
+      "step": 1
     }
   ],
   "children": [
@@ -17,7 +38,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 = chf(\"Size\", 1.0f);\n        float center_x = 0.0f;\n        float center_y = 0.0f;\n        float center_z = 0.0f;\n        int divs = 16;\n        float half_size = size * 0.5f;\n        float step = size / (float)divs;\n        float inv = size > 0.0f ? 1.0f / size : 0.0f;\n        int count = 0;\n        for (int i = 0; i < divs; i++) {\n            float x0 = -half_size + (float)i * step;\n            float x1 = x0 + step;\n            for (int j = 0; j < divs; j++) {\n                float z0 = -half_size + (float)j * step;\n                float z1 = z0 + step;\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_size) * inv;\n                        float fz = (pz[v] + half_size) * inv;\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        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 = 0.0f;\n        float center_y = 0.0f;\n        float 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}"
         }
       ],
       "position": [
diff --git a/src/main.rs b/src/main.rs
index 4f7449b..fe24536 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -244,14 +244,14 @@ mod tests {
         let output1 = plane_template.children.iter().find(|c| c.name == "output1").unwrap();
         assert_eq!(output1.node_type, "output");
 
-        let generate = |size: Option<&str>, id: &str| {
+        let generate = |overrides: &[(&str, &str)], id: &str| {
             let mut inst = plane_template.clone();
             inst.id = id.to_string();
             for child in &mut inst.children {
                 child.id = format!("{}_{}", inst.id, child.name);
             }
-            if let Some(size) = size {
-                inst.params.iter_mut().find(|p| p.name == "Size").unwrap().default = size.to_string();
+            for (name, value) in overrides {
+                inst.params.iter_mut().find(|p| p.name == *name).unwrap().default = value.to_string();
             }
             let root = FsNode {
                 id: "root".to_string(),
@@ -276,9 +276,9 @@ mod tests {
             geom
         };
 
-        // Default Size 1.0: a 16x16 grid of two-triangle cells, flat at y = 0,
-        // spanning [-0.5, 0.5] on X and Z.
-        let geom = generate(None, "plane_inst");
+        // Defaults (Width/Length 1.0, Columns/Rows 16): a 16x16 grid of
+        // two-triangle cells, flat at y = 0, spanning [-0.5, 0.5] on X and Z.
+        let geom = generate(&[], "plane_inst");
         assert_eq!(geom.vertices.len(), 16 * 16 * 6);
         let mut max_x: f32 = 0.0;
         let mut max_z: f32 = 0.0;
@@ -287,13 +287,19 @@ mod tests {
             max_x = max_x.max(v.pos[0].abs());
             max_z = max_z.max(v.pos[2].abs());
         }
-        assert!((max_x - 0.5).abs() < 0.01, "Expected half-size 0.5 on X, got {}", max_x);
-        assert!((max_z - 0.5).abs() < 0.01, "Expected half-size 0.5 on Z, got {}", max_z);
+        assert!((max_x - 0.5).abs() < 0.01, "Expected half-width 0.5 on X, got {}", max_x);
+        assert!((max_z - 0.5).abs() < 0.01, "Expected half-length 0.5 on Z, got {}", max_z);
 
-        // Size override 2.0 spans [-1, 1].
-        let geom_2 = generate(Some("2.0"), "plane_inst_2");
+        // Width and Length size their axes independently.
+        let geom_2 = generate(&[("Width", "2.0"), ("Length", "3.0")], "plane_inst_2");
         let max_x_2 = geom_2.vertices.iter().map(|v| v.pos[0].abs()).fold(0.0f32, f32::max);
-        assert!((max_x_2 - 1.0).abs() < 0.01, "Expected half-size 1.0 on X, got {}", max_x_2);
+        let max_z_2 = geom_2.vertices.iter().map(|v| v.pos[2].abs()).fold(0.0f32, f32::max);
+        assert!((max_x_2 - 1.0).abs() < 0.01, "Expected half-width 1.0 on X, got {}", max_x_2);
+        assert!((max_z_2 - 1.5).abs() < 0.01, "Expected half-length 1.5 on Z, got {}", max_z_2);
+
+        // Columns/Rows control the cell counts per axis.
+        let geom_3 = generate(&[("Columns", "4"), ("Rows", "8")], "plane_inst_3");
+        assert_eq!(geom_3.vertices.len(), 4 * 8 * 6);
     }
 
     #[test]