graphic design tool
git clone https://git.lucas.co/cce-designer.git
Update system configuration and interface modules
Cargo.lock | 1 +
src/geometry.rs | 787 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/main.rs | 668 +++++------------------------------------------
3 files changed, 858 insertions(+), 598 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7399529..1171472 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -210,6 +210,7 @@ dependencies = [
"pollster",
"raw-window-handle",
"resvg",
+ "serde",
"smithay-client-toolkit",
"tokio",
"wayland-client",
diff --git a/src/geometry.rs b/src/geometry.rs
new file mode 100644
index 0000000..62a6cb6
--- /dev/null
+++ b/src/geometry.rs
@@ -0,0 +1,787 @@
+use super::FsNode;
+use std::collections::HashMap;
+use opencl3::platform::get_platforms;
+use opencl3::device::{Device, CL_DEVICE_TYPE_GPU, CL_DEVICE_TYPE_CPU};
+use opencl3::context::Context;
+use opencl3::command_queue::CommandQueue;
+use opencl3::program::Program;
+use opencl3::kernel::{Kernel, ExecuteKernel};
+use opencl3::memory::{Buffer as ClBuffer, CL_MEM_READ_WRITE};
+use opencl3::types::{cl_float, cl_int, CL_TRUE};
+use glam::Vec3;
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum GAttribute {
+ Float(f32),
+ Float2([f32; 2]),
+ Float3([f32; 3]),
+ Float4([f32; 4]),
+}
+
+#[derive(Clone, Debug)]
+pub struct GVertex {
+ pub pos: [f32; 3],
+ pub col: [f32; 3],
+ pub attributes: HashMap<String, GAttribute>,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Geometry {
+ pub vertices: Vec<GVertex>,
+}
+
+impl Geometry {
+ pub fn new() -> Self {
+ Geometry { vertices: Vec::new() }
+ }
+
+ pub fn merge(&mut self, other: Geometry) {
+ self.vertices.extend(other.vertices);
+ }
+
+ pub fn to_vertex3d_vec(&self) -> Vec<Vertex3D> {
+ self.vertices.iter().map(|v| Vertex3D {
+ position: v.pos,
+ color: v.col,
+ }).collect()
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct Vertex3D {
+ pub position: [f32; 3],
+ pub color: [f32; 3],
+}
+
+impl Vertex3D {
+ const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
+
+ pub fn desc() -> wgpu::VertexBufferLayout<'static> {
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &Self::ATTRIBS,
+ }
+ }
+}
+
+pub fn cube_vertices() -> Vec<Vertex3D> {
+ let s = 0.5;
+ let data: &[([f32; 3], [f32; 3])] = &[
+ ([-s, -s, s], [0.8, 0.2, 0.2]), ([s, -s, s], [0.8, 0.2, 0.2]), ([s, s, s], [0.8, 0.2, 0.2]),
+ ([-s, -s, s], [0.8, 0.2, 0.2]), ([s, s, s], [0.8, 0.2, 0.2]), ([-s, s, s], [0.8, 0.2, 0.2]),
+ ([s, -s, -s], [0.2, 0.8, 0.2]), ([-s, -s, -s], [0.2, 0.8, 0.2]), ([-s, s, -s], [0.2, 0.8, 0.2]),
+ ([s, -s, -s], [0.2, 0.8, 0.2]), ([-s, s, -s], [0.2, 0.8, 0.2]), ([s, s, -s], [0.2, 0.8, 0.2]),
+ ([-s, s, s], [0.2, 0.2, 0.8]), ([s, s, s], [0.2, 0.2, 0.8]), ([s, s, -s], [0.2, 0.2, 0.8]),
+ ([-s, s, s], [0.2, 0.2, 0.8]), ([s, s, -s], [0.2, 0.2, 0.8]), ([-s, s, -s], [0.2, 0.2, 0.8]),
+ ([-s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, s], [0.8, 0.8, 0.2]),
+ ([-s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, s], [0.8, 0.8, 0.2]), ([-s, -s, s], [0.8, 0.8, 0.2]),
+ ([s, -s, s], [0.8, 0.2, 0.8]), ([s, -s, -s], [0.8, 0.2, 0.8]), ([s, s, -s], [0.8, 0.2, 0.8]),
+ ([s, -s, s], [0.8, 0.2, 0.8]), ([s, s, -s], [0.8, 0.2, 0.8]), ([s, s, s], [0.8, 0.2, 0.8]),
+ ([-s, -s, -s], [0.2, 0.8, 0.8]), ([-s, -s, s], [0.2, 0.8, 0.8]), ([-s, s, s], [0.2, 0.8, 0.8]),
+ ([-s, -s, -s], [0.2, 0.8, 0.8]), ([-s, s, s], [0.2, 0.8, 0.8]), ([-s, s, -s], [0.2, 0.8, 0.8]),
+ ];
+ data.iter().map(|&(p, c)| Vertex3D { position: p, color: c }).collect()
+}
+
+pub fn sphere_vertices(center: Vec3, radius: f32) -> Geometry {
+ let lat_steps = 16;
+ let lon_steps = 24;
+ let mut vertices = Vec::new();
+
+ for lat in 0..lat_steps {
+ let theta0 = std::f32::consts::PI * lat as f32 / lat_steps as f32;
+ let theta1 = std::f32::consts::PI * (lat + 1) as f32 / lat_steps as f32;
+ for lon in 0..lon_steps {
+ let phi0 = std::f32::consts::TAU * lon as f32 / lon_steps as f32;
+ let phi1 = std::f32::consts::TAU * (lon + 1) as f32 / lon_steps as f32;
+ let p00 = sphere_point(center, radius, theta0, phi0);
+ let p10 = sphere_point(center, radius, theta1, phi0);
+ let p11 = sphere_point(center, radius, theta1, phi1);
+ let p01 = sphere_point(center, radius, theta0, phi1);
+ vertices.push(sphere_vertex(center, p00));
+ vertices.push(sphere_vertex(center, p10));
+ vertices.push(sphere_vertex(center, p11));
+ vertices.push(sphere_vertex(center, p00));
+ vertices.push(sphere_vertex(center, p11));
+ vertices.push(sphere_vertex(center, p01));
+ }
+ }
+
+ Geometry { vertices }
+}
+
+fn sphere_point(center: Vec3, radius: f32, theta: f32, phi: f32) -> Vec3 {
+ center + Vec3::new(
+ radius * theta.sin() * phi.cos(),
+ radius * theta.cos(),
+ radius * theta.sin() * phi.sin(),
+ )
+}
+
+fn sphere_vertex(center: Vec3, point: Vec3) -> GVertex {
+ let n = (point - center).normalize_or_zero();
+ let u = 0.5 + n.z.atan2(n.x) / std::f32::consts::TAU;
+ let v = 0.5 - n.y.asin() / std::f32::consts::PI;
+
+ let pos = point.to_array();
+ let col = [0.35 + n.x.abs() * 0.35, 0.45 + n.y.abs() * 0.35, 0.85];
+
+ let mut attributes = HashMap::new();
+ attributes.insert("Norm".to_string(), GAttribute::Float3(n.to_array()));
+ attributes.insert("UV".to_string(), GAttribute::Float2([u, v]));
+
+ GVertex {
+ pos,
+ col,
+ attributes,
+ }
+}
+
+pub fn line_vertices(start: Vec3, end: Vec3, thickness: f32) -> Geometry {
+ let mut vertices = Vec::new();
+ let dir = (end - start).normalize_or_zero();
+ if dir.length_squared() < 0.0001 {
+ return Geometry { vertices };
+ }
+
+ // Find two orthogonal vectors to dir
+ let up = if dir.x.abs() > 0.9 { Vec3::Y } else { Vec3::X };
+ let u = dir.cross(up).normalize();
+ let v = dir.cross(u).normalize();
+
+ let t = thickness * 0.5;
+
+ // 8 corners of the box
+ let c0 = start - t * u - t * v;
+ let c1 = start + t * u - t * v;
+ let c2 = start + t * u + t * v;
+ let c3 = start - t * u + t * v;
+
+ let c4 = end - t * u - t * v;
+ let c5 = end + t * u - t * v;
+ let c6 = end + t * u + t * v;
+ let c7 = end - t * u + t * v;
+
+ // Helper to add a triangle face
+ let mut add_quad = |p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, normal: Vec3, color: [f32; 3]| {
+ let make_vertex = |p: Vec3| {
+ let mut attributes = HashMap::new();
+ attributes.insert("Norm".to_string(), GAttribute::Float3(normal.to_array()));
+ attributes.insert("UV".to_string(), GAttribute::Float2([0.0, 0.0]));
+ GVertex {
+ pos: p.to_array(),
+ col: color,
+ attributes,
+ }
+ };
+ // Triangle 1: p0, p1, p2
+ vertices.push(make_vertex(p0));
+ vertices.push(make_vertex(p1));
+ vertices.push(make_vertex(p2));
+ // Triangle 2: p0, p2, p3
+ vertices.push(make_vertex(p0));
+ vertices.push(make_vertex(p2));
+ vertices.push(make_vertex(p3));
+ };
+
+ let col = [0.85, 0.45, 0.35]; // distinct color for lines
+
+ // Front face (start cap)
+ add_quad(c0, c1, c2, c3, -dir, col);
+ // Back face (end cap)
+ add_quad(c5, c4, c7, c6, dir, col);
+ // Left face
+ add_quad(c4, c0, c3, c7, -u, col);
+ // Right face
+ add_quad(c1, c5, c6, c2, u, col);
+ // Top face
+ add_quad(c3, c2, c6, c7, v, col);
+ // Bottom face
+ add_quad(c0, c4, c5, c1, -v, col);
+
+ Geometry { vertices }
+}
+
+pub fn node_param_f32(node: &FsNode, name: &str, fallback: f32) -> f32 {
+ node.params.iter()
+ .find(|p| p.name.eq_ignore_ascii_case(name))
+ .and_then(|p| p.default.parse::<f32>().ok())
+ .unwrap_or(fallback)
+}
+
+pub fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String> {
+ let is_generator = code.contains("out_count");
+ if geom.vertices.is_empty() && !is_generator {
+ return Ok(());
+ }
+
+ let platforms = get_platforms().map_err(|e| format!("Failed to get platforms: {:?}", e))?;
+ if platforms.is_empty() {
+ return Err("No OpenCL platforms found".to_string());
+ }
+
+ // Try to find a GPU device first, then fallback to CPU
+ let mut device_id = None;
+ for platform in &platforms {
+ if let Ok(devices) = platform.get_devices(CL_DEVICE_TYPE_GPU) {
+ if !devices.is_empty() {
+ device_id = Some(devices[0]);
+ break;
+ }
+ }
+ }
+ if device_id.is_none() {
+ for platform in &platforms {
+ if let Ok(devices) = platform.get_devices(CL_DEVICE_TYPE_CPU) {
+ if !devices.is_empty() {
+ device_id = Some(devices[0]);
+ break;
+ }
+ }
+ }
+ }
+ let device_id = device_id.ok_or_else(|| "No OpenCL devices found".to_string())?;
+ let device = Device::new(device_id);
+
+ let context = Context::from_device(&device).map_err(|e| format!("Failed to create Context: {:?}", e))?;
+ let queue = unsafe { CommandQueue::create(&context, device_id, 0) }
+ .map_err(|e| format!("Failed to create CommandQueue: {:?}", e))?;
+
+ let mut program = Program::create_from_source(&context, code).map_err(|e| format!("Failed to create Program: {:?}", e))?;
+ if let Err(e) = program.build(&[device_id], "") {
+ let log = program.get_build_log(device_id).unwrap_or_else(|_| "Failed to retrieve build log".to_string());
+ return Err(format!("OpenCL JIT compilation error: {}\nLog:\n{}", e, log));
+ }
+
+ let kernel = Kernel::create(&program, "process").map_err(|e| format!("Failed to create kernel 'process': {:?}", e))?;
+
+ if is_generator {
+ let in_count = geom.vertices.len();
+ let max_vertices = 200_000;
+
+ // Prepare flat input position and color data
+ let mut in_pos_data: Vec<cl_float> = Vec::with_capacity(in_count * 3);
+ let mut in_col_data: Vec<cl_float> = Vec::with_capacity(in_count * 3);
+ for v in &geom.vertices {
+ in_pos_data.extend_from_slice(&v.pos);
+ in_col_data.extend_from_slice(&v.col);
+ }
+
+ // Create GPU buffers for inputs
+ let mut in_pos_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, in_count * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create input positions buffer: {:?}", e))?
+ };
+ let mut in_col_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, in_count * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create input colors buffer: {:?}", e))?
+ };
+
+ // Write input data to GPU
+ let _write_pos_event = unsafe {
+ queue.enqueue_write_buffer(&mut in_pos_buf, CL_TRUE, 0, &in_pos_data, &[])
+ .map_err(|e| format!("Failed to write input positions buffer: {:?}", e))?
+ };
+ let _write_col_event = unsafe {
+ queue.enqueue_write_buffer(&mut in_col_buf, CL_TRUE, 0, &in_col_data, &[])
+ .map_err(|e| format!("Failed to write input colors buffer: {:?}", e))?
+ };
+
+ // Create GPU buffers for outputs
+ let out_pos_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, max_vertices * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create output positions buffer: {:?}", e))?
+ };
+ let out_col_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, max_vertices * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create output colors buffer: {:?}", e))?
+ };
+
+ // Create output count buffer initialized to 0
+ let mut out_count_buf = unsafe {
+ ClBuffer::<cl_int>::create(&context, CL_MEM_READ_WRITE, 1, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create output count buffer: {:?}", e))?
+ };
+ let initial_count_data: [cl_int; 1] = [0];
+ let _write_count_event = unsafe {
+ queue.enqueue_write_buffer(&mut out_count_buf, CL_TRUE, 0, &initial_count_data, &[])
+ .map_err(|e| format!("Failed to write output count buffer: {:?}", e))?
+ };
+
+ // Execute kernel
+ let global_work_size = if in_count == 0 { 1 } else { in_count };
+ let kernel_event = unsafe {
+ ExecuteKernel::new(&kernel)
+ .set_arg(&in_pos_buf)
+ .set_arg(&in_col_buf)
+ .set_arg(&(in_count as cl_int))
+ .set_arg(&out_pos_buf)
+ .set_arg(&out_col_buf)
+ .set_arg(&out_count_buf)
+ .set_arg(&(max_vertices as cl_int))
+ .set_global_work_size(global_work_size)
+ .enqueue_nd_range(&queue)
+ .map_err(|e| format!("Failed to enqueue kernel: {:?}", e))?
+ };
+
+ kernel_event.wait().map_err(|e| format!("Failed to wait for kernel: {:?}", e))?;
+
+ // Read count back
+ let mut final_count_data: [cl_int; 1] = [0];
+ let _read_count_event = unsafe {
+ queue.enqueue_read_buffer(&out_count_buf, CL_TRUE, 0, &mut final_count_data, &[])
+ .map_err(|e| format!("Failed to read output count: {:?}", e))?
+ };
+ let final_count = (final_count_data[0] as usize).min(max_vertices);
+
+ // Read output positions and colors back
+ let mut out_pos_data: Vec<cl_float> = vec![0.0; final_count * 3];
+ let mut out_col_data: Vec<cl_float> = vec![0.0; final_count * 3];
+ if final_count > 0 {
+ let _read_pos_event = unsafe {
+ queue.enqueue_read_buffer(&out_pos_buf, CL_TRUE, 0, &mut out_pos_data, &[])
+ .map_err(|e| format!("Failed to read output positions buffer: {:?}", e))?
+ };
+ let _read_col_event = unsafe {
+ queue.enqueue_read_buffer(&out_col_buf, CL_TRUE, 0, &mut out_col_data, &[])
+ .map_err(|e| format!("Failed to read output colors buffer: {:?}", e))?
+ };
+ }
+
+ // Rebuild geometry vertices
+ geom.vertices.clear();
+ for i in 0..final_count {
+ let mut attributes = HashMap::new();
+ attributes.insert("Norm".to_string(), GAttribute::Float3([0.0, 1.0, 0.0]));
+ attributes.insert("UV".to_string(), GAttribute::Float2([0.0, 0.0]));
+ geom.vertices.push(GVertex {
+ pos: [out_pos_data[i * 3], out_pos_data[i * 3 + 1], out_pos_data[i * 3 + 2]],
+ col: [out_col_data[i * 3], out_col_data[i * 3 + 1], out_col_data[i * 3 + 2]],
+ attributes,
+ });
+ }
+ } else {
+ let count = geom.vertices.len();
+
+ // Prepare flat position and color buffers
+ let mut pos_data: Vec<cl_float> = Vec::with_capacity(count * 3);
+ let mut col_data: Vec<cl_float> = Vec::with_capacity(count * 3);
+ for v in &geom.vertices {
+ pos_data.extend_from_slice(&v.pos);
+ col_data.extend_from_slice(&v.col);
+ }
+
+ // Create device buffers
+ let mut pos_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, count * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create positions buffer: {:?}", e))?
+ };
+ let mut col_buf = unsafe {
+ ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, count * 3, std::ptr::null_mut())
+ .map_err(|e| format!("Failed to create colors buffer: {:?}", e))?
+ };
+
+ // Write data to device
+ let _write_pos_event = unsafe {
+ queue.enqueue_write_buffer(&mut pos_buf, CL_TRUE, 0, &pos_data, &[])
+ .map_err(|e| format!("Failed to write positions buffer: {:?}", e))?
+ };
+ let _write_col_event = unsafe {
+ queue.enqueue_write_buffer(&mut col_buf, CL_TRUE, 0, &col_data, &[])
+ .map_err(|e| format!("Failed to write colors buffer: {:?}", e))?
+ };
+
+ // Execute kernel
+ let kernel_event = unsafe {
+ ExecuteKernel::new(&kernel)
+ .set_arg(&pos_buf)
+ .set_arg(&col_buf)
+ .set_arg(&(count as cl_int))
+ .set_global_work_size(count)
+ .enqueue_nd_range(&queue)
+ .map_err(|e| format!("Failed to enqueue kernel: {:?}", e))?
+ };
+
+ kernel_event.wait().map_err(|e| format!("Failed to wait for kernel: {:?}", e))?;
+
+ // Read data back from device
+ let _read_pos_event = unsafe {
+ queue.enqueue_read_buffer(&pos_buf, CL_TRUE, 0, &mut pos_data, &[])
+ .map_err(|e| format!("Failed to read positions buffer: {:?}", e))?
+ };
+ let _read_col_event = unsafe {
+ queue.enqueue_read_buffer(&col_buf, CL_TRUE, 0, &mut col_data, &[])
+ .map_err(|e| format!("Failed to read colors buffer: {:?}", e))?
+ };
+
+ // Write back to Geometry
+ for i in 0..count {
+ geom.vertices[i].pos = [pos_data[i * 3], pos_data[i * 3 + 1], pos_data[i * 3 + 2]];
+ geom.vertices[i].col = [col_data[i * 3], col_data[i * 3 + 1], col_data[i * 3 + 2]];
+ }
+ }
+
+ Ok(())
+}
+
+pub fn network_sphere_vertices(root: &FsNode) -> Geometry {
+ fn visit(node: &FsNode, count: &mut usize, out: &mut Geometry) {
+ if node.node_type.eq_ignore_ascii_case("sphere") {
+ let idx = *count;
+ *count += 1;
+ if node.geometry_visible {
+ let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
+ out.merge(sphere_vertices(center, node_param_f32(node, "Radius", 0.5).max(0.05)));
+ }
+ } else if node.node_type.eq_ignore_ascii_case("line") {
+ let idx = *count;
+ *count += 1;
+ if node.geometry_visible {
+ let start = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
+ let length = node_param_f32(node, "Length", 1.0);
+ let thickness = node_param_f32(node, "Thickness", 0.02);
+ let end = start + Vec3::new(0.0, length, 0.0);
+ out.merge(line_vertices(start, end, thickness));
+ }
+ }
+ for child in &node.children {
+ visit(child, count, out);
+ }
+ }
+
+ let mut out = Geometry::new();
+ let mut count = 0;
+ for child in &root.children {
+ visit(child, &mut count, &mut out);
+ }
+ out
+}
+
+pub fn find_sphere_index(root: &FsNode, target: &FsNode) -> Option<usize> {
+ fn visit(node: &FsNode, target: &FsNode, count: &mut usize) -> Option<usize> {
+ let is_target = std::ptr::eq(node, target);
+ if node.node_type.eq_ignore_ascii_case("sphere") || node.node_type.eq_ignore_ascii_case("line") {
+ let idx = *count;
+ *count += 1;
+ if is_target {
+ return Some(idx);
+ }
+ }
+ for child in &node.children {
+ if let Some(res) = visit(child, target, count) {
+ return Some(res);
+ }
+ }
+ None
+ }
+ let mut count = 0;
+ for child in &root.children {
+ if let Some(res) = visit(child, target, &mut count) {
+ return Some(res);
+ }
+ }
+ None
+}
+
+fn add_box(center: Vec3, size: Vec3, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
+ let dx = size.x * 0.5;
+ let dy = size.y * 0.5;
+ let dz = size.z * 0.5;
+
+ let faces = [
+ // front (z = +dz)
+ [-dx, -dy, dz, dx, -dy, dz, dx, dy, dz, -dx, -dy, dz, dx, dy, dz, -dx, dy, dz],
+ // back (z = -dz)
+ [-dx, -dy, -dz, -dx, dy, -dz, dx, dy, -dz, -dx, -dy, -dz, dx, dy, -dz, dx, -dy, -dz],
+ // left (x = -dx)
+ [-dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz, -dx, -dy, -dz, -dx, dy, dz, -dx, dy, -dz],
+ // right (x = +dx)
+ [dx, -dy, -dz, dx, dy, -dz, dx, dy, dz, dx, -dy, -dz, dx, dy, dz, dx, -dy, dz],
+ // top (y = +dy)
+ [-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, -dx, dy, -dz, dx, dy, dz, dx, dy, -dz],
+ // bottom (y = -dy)
+ [-dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz, -dx, -dy, -dz, dx, -dy, dz, -dx, -dy, dz],
+ ];
+
+ for face in &faces {
+ for chunk in face.chunks(3) {
+ verts.push(Vertex3D {
+ position: [center.x + chunk[0], center.y + chunk[1], center.z + chunk[2]],
+ color,
+ });
+ }
+ }
+}
+
+fn add_pyramid_x(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
+ let s = base_size * 0.5;
+ let x = base_center.x;
+ let y = base_center.y;
+ let z = base_center.z;
+
+ let p0 = Vec3::new(x, y - s, z - s);
+ let p1 = Vec3::new(x, y + s, z - s);
+ let p2 = Vec3::new(x, y + s, z + s);
+ let p3 = Vec3::new(x, y - s, z + s);
+ let tip = Vec3::new(x + height, y, z);
+
+ // Base (two triangles)
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+ verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
+
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+
+ // Sides
+ let sides = [
+ (p0, p3), (p3, p2), (p2, p1), (p1, p0)
+ ];
+ for (a, b) in &sides {
+ verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
+ verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
+ verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
+ }
+}
+
+fn add_pyramid_y(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
+ let s = base_size * 0.5;
+ let x = base_center.x;
+ let y = base_center.y;
+ let z = base_center.z;
+
+ let p0 = Vec3::new(x - s, y, z - s);
+ let p1 = Vec3::new(x + s, y, z - s);
+ let p2 = Vec3::new(x + s, y, z + s);
+ let p3 = Vec3::new(x - s, y, z + s);
+ let tip = Vec3::new(x, y + height, z);
+
+ // Base (two triangles)
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+ verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
+
+ // Sides
+ let sides = [
+ (p0, p1), (p1, p2), (p2, p3), (p3, p0)
+ ];
+ for (a, b) in &sides {
+ verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
+ verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
+ verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
+ }
+}
+
+fn add_pyramid_z(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
+ let s = base_size * 0.5;
+ let x = base_center.x;
+ let y = base_center.y;
+ let z = base_center.z;
+
+ let p0 = Vec3::new(x - s, y - s, z);
+ let p1 = Vec3::new(x + s, y - s, z);
+ let p2 = Vec3::new(x + s, y + s, z);
+ let p3 = Vec3::new(x - s, y + s, z);
+ let tip = Vec3::new(x, y, z + height);
+
+ // Base (two triangles)
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+ verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
+
+ verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
+ verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
+ verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
+
+ // Sides
+ let sides = [
+ (p0, p3), (p3, p2), (p2, p1), (p1, p0)
+ ];
+ for (a, b) in &sides {
+ verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
+ verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
+ verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
+ }
+}
+
+pub fn origin_vectors_vertices(scale: f32) -> Vec<Vertex3D> {
+ let mut verts = Vec::new();
+
+ let t = 0.008 * scale;
+ let a_size = 0.024 * scale;
+ let a_height = 0.15 * scale;
+ let axis_len = 0.85 * scale;
+ let half_axis_len = 0.425 * scale;
+
+ // Red for X-axis (points to +scale)
+ let red = [0.9, 0.1, 0.1];
+ add_box(Vec3::new(half_axis_len, 0.0, 0.0), Vec3::new(axis_len, t, t), red, &mut verts);
+ add_pyramid_x(Vec3::new(axis_len, 0.0, 0.0), a_size, a_height, red, &mut verts);
+
+ // Green for Y-axis (points to +scale)
+ let green = [0.1, 0.8, 0.1];
+ add_box(Vec3::new(0.0, half_axis_len, 0.0), Vec3::new(t, axis_len, t), green, &mut verts);
+ add_pyramid_y(Vec3::new(0.0, axis_len, 0.0), a_size, a_height, green, &mut verts);
+
+ // Blue for Z-axis (points to +scale)
+ let blue = [0.1, 0.1, 0.9];
+ add_box(Vec3::new(0.0, 0.0, half_axis_len), Vec3::new(t, t, axis_len), blue, &mut verts);
+ add_pyramid_z(Vec3::new(0.0, 0.0, axis_len), a_size, a_height, blue, &mut verts);
+
+ verts
+}
+
+pub fn camera_pivot_vertices(scale: f32) -> Vec<Vertex3D> {
+ let mut verts = Vec::new();
+ let t = 0.002 * scale;
+ let len = 0.4 * scale;
+
+ // Red for X-axis
+ let red = [0.9, 0.1, 0.1];
+ add_box(Vec3::new(len * 0.5, 0.0, 0.0), Vec3::new(len, t, t), red, &mut verts);
+
+ // Green for Y-axis
+ let green = [0.1, 0.8, 0.1];
+ add_box(Vec3::new(0.0, len * 0.5, 0.0), Vec3::new(t, len, t), green, &mut verts);
+
+ // Blue for Z-axis
+ let blue = [0.1, 0.1, 0.9];
+ add_box(Vec3::new(0.0, 0.0, len * 0.5), Vec3::new(t, t, len), blue, &mut verts);
+
+ verts
+}
+
+pub fn grid_vertices(thickness: f32) -> Vec<Vertex3D> {
+ let color = [0.35, 0.35, 0.40];
+ let range = 4.0;
+ let step = 1.0;
+ let mut geom = Geometry::new();
+
+ let mut z = -range;
+ while z <= range {
+ let start = Vec3::new(-range, 0.0, z);
+ let end = Vec3::new(range, 0.0, z);
+ let mut line_geom = line_vertices(start, end, thickness);
+ for v in &mut line_geom.vertices {
+ v.col = color;
+ }
+ geom.merge(line_geom);
+ z += step;
+ }
+
+ let mut x = -range;
+ while x <= range {
+ let start = Vec3::new(x, 0.0, -range);
+ let end = Vec3::new(x, 0.0, range);
+ let mut line_geom = line_vertices(start, end, thickness);
+ for v in &mut line_geom.vertices {
+ v.col = color;
+ }
+ geom.merge(line_geom);
+ x += step;
+ }
+
+ geom.to_vertex3d_vec()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_opencl_deformer_mode() {
+ if opencl3::platform::get_platforms().unwrap_or_default().is_empty() {
+ println!("Skipping OpenCL deformer test: No OpenCL platforms found");
+ return;
+ }
+
+ let code = r#"
+ __kernel void process(__global float* pos, __global float* col, int count) {
+ int id = get_global_id(0);
+ if (id < count) {
+ pos[id * 3 + 1] += 1.0f;
+ }
+ }
+ "#;
+
+ let mut geom = Geometry {
+ vertices: vec![GVertex {
+ pos: [1.0, 2.0, 3.0],
+ col: [1.0, 0.0, 0.0],
+ attributes: std::collections::HashMap::new(),
+ }],
+ };
+
+ run_opencl_kernel(code, &mut geom).unwrap();
+
+ assert_eq!(geom.vertices.len(), 1);
+ assert_eq!(geom.vertices[0].pos, [1.0, 3.0, 3.0]);
+ }
+
+ #[test]
+ fn test_opencl_generator_mode() {
+ if opencl3::platform::get_platforms().unwrap_or_default().is_empty() {
+ println!("Skipping OpenCL generator test: No OpenCL platforms found");
+ return;
+ }
+
+ let code = r#"
+ __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_out_count
+ ) {
+ int id = get_global_id(0);
+ if (id < in_count) {
+ // Copy original
+ int idx1 = atomic_inc(out_count);
+ if (idx1 < max_out_count) {
+ out_pos[idx1 * 3] = in_pos[id * 3];
+ out_pos[idx1 * 3 + 1] = in_pos[id * 3 + 1];
+ out_pos[idx1 * 3 + 2] = in_pos[id * 3 + 2];
+ out_col[idx1 * 3] = in_col[id * 3];
+ out_col[idx1 * 3 + 1] = in_col[id * 3 + 1];
+ out_col[idx1 * 3 + 2] = in_col[id * 3 + 2];
+ }
+ // Generate new offset
+ int idx2 = atomic_inc(out_count);
+ if (idx2 < max_out_count) {
+ out_pos[idx2 * 3] = in_pos[id * 3] + 1.0f;
+ out_pos[idx2 * 3 + 1] = in_pos[id * 3 + 1] + 2.0f;
+ out_pos[idx2 * 3 + 2] = in_pos[id * 3 + 2] + 3.0f;
+ out_col[idx2 * 3] = 0.5f;
+ out_col[idx2 * 3 + 1] = 0.5f;
+ out_col[idx2 * 3 + 2] = 0.5f;
+ }
+ }
+ }
+ "#;
+
+ let mut geom = Geometry {
+ vertices: vec![GVertex {
+ pos: [1.0, 2.0, 3.0],
+ col: [1.0, 0.0, 0.0],
+ attributes: std::collections::HashMap::new(),
+ }],
+ };
+
+ run_opencl_kernel(code, &mut geom).unwrap();
+
+ assert_eq!(geom.vertices.len(), 2);
+ assert_eq!(geom.vertices[0].pos, [1.0, 2.0, 3.0]);
+ assert_eq!(geom.vertices[1].pos, [2.0, 4.0, 6.0]);
+ assert_eq!(geom.vertices[1].col, [0.5, 0.5, 0.5]);
+ }
+}
+
diff --git a/src/main.rs b/src/main.rs
index 57707cc..3f30616 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,3 @@
-use std::sync::Arc;
use std::time::Instant;
use std::fs;
use std::path::Path;
@@ -7,16 +6,7 @@ use std::io::{BufRead, BufReader, Read, Write};
use serde::{Deserialize, Serialize};
-use opencl3::platform::get_platforms;
-use opencl3::device::{Device, CL_DEVICE_TYPE_GPU, CL_DEVICE_TYPE_CPU};
-use opencl3::context::Context;
-use opencl3::command_queue::CommandQueue;
-use opencl3::program::Program;
-use opencl3::kernel::{Kernel, ExecuteKernel};
-use opencl3::memory::{Buffer as ClBuffer, CL_MEM_READ_WRITE};
-use opencl3::types::{cl_float, cl_int, CL_TRUE};
-
-use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey, Position};
+use clear_ui::widget::{ElementState, MouseButton, MouseScrollDelta, KeyEvent, Key, NamedKey};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
@@ -26,7 +16,7 @@ use smithay_client_toolkit::{
output::{OutputHandler, OutputState},
seat::{
keyboard::KeyboardHandler,
- pointer::PointerHandler,
+ pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
Capability, SeatHandler, SeatState,
},
shell::{
@@ -45,6 +35,9 @@ use wayland_client::{
};
use calloop_wayland_source::WaylandSource;
+mod geometry;
+use geometry::*;
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TouchPhase {
Started,
@@ -1390,6 +1383,10 @@ impl Widget for ConfigDialog {
if !self.visible { return false; }
true
}
+
+ fn z_index(&self) -> i32 {
+ 200
+ }
}
struct NodePalette {
@@ -1475,6 +1472,10 @@ impl Widget for NodePalette {
}
labels
}
+
+ fn z_index(&self) -> i32 {
+ 200
+ }
}
#[derive(Clone, Copy, PartialEq)]
@@ -1536,578 +1537,6 @@ impl Vertex {
}
}
-#[derive(Clone, Debug, PartialEq)]
-enum GAttribute {
- Float(f32),
- Float2([f32; 2]),
- Float3([f32; 3]),
- Float4([f32; 4]),
-}
-
-#[derive(Clone, Debug)]
-struct GVertex {
- pos: [f32; 3],
- col: [f32; 3],
- attributes: std::collections::HashMap<String, GAttribute>,
-}
-
-#[derive(Clone, Debug, Default)]
-struct Geometry {
- vertices: Vec<GVertex>,
-}
-
-impl Geometry {
- fn new() -> Self {
- Geometry { vertices: Vec::new() }
- }
-
- fn merge(&mut self, other: Geometry) {
- self.vertices.extend(other.vertices);
- }
-
- fn to_vertex3d_vec(&self) -> Vec<Vertex3D> {
- self.vertices.iter().map(|v| Vertex3D {
- position: v.pos,
- color: v.col,
- }).collect()
- }
-}
-
-#[repr(C)]
-#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
-struct Vertex3D {
- position: [f32; 3],
- color: [f32; 3],
-}
-
-impl Vertex3D {
- const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
-
- fn desc() -> wgpu::VertexBufferLayout<'static> {
- wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &Self::ATTRIBS,
- }
- }
-}
-
-fn cube_vertices() -> Vec<Vertex3D> {
- let s = 0.5;
- let data: &[([f32; 3], [f32; 3])] = &[
- ([-s, -s, s], [0.8, 0.2, 0.2]), ([s, -s, s], [0.8, 0.2, 0.2]), ([s, s, s], [0.8, 0.2, 0.2]),
- ([-s, -s, s], [0.8, 0.2, 0.2]), ([s, s, s], [0.8, 0.2, 0.2]), ([-s, s, s], [0.8, 0.2, 0.2]),
- ([s, -s, -s], [0.2, 0.8, 0.2]), ([-s, -s, -s], [0.2, 0.8, 0.2]), ([-s, s, -s], [0.2, 0.8, 0.2]),
- ([s, -s, -s], [0.2, 0.8, 0.2]), ([-s, s, -s], [0.2, 0.8, 0.2]), ([s, s, -s], [0.2, 0.8, 0.2]),
- ([-s, s, s], [0.2, 0.2, 0.8]), ([s, s, s], [0.2, 0.2, 0.8]), ([s, s, -s], [0.2, 0.2, 0.8]),
- ([-s, s, s], [0.2, 0.2, 0.8]), ([s, s, -s], [0.2, 0.2, 0.8]), ([-s, s, -s], [0.2, 0.2, 0.8]),
- ([-s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, s], [0.8, 0.8, 0.2]),
- ([-s, -s, -s], [0.8, 0.8, 0.2]), ([s, -s, s], [0.8, 0.8, 0.2]), ([-s, -s, s], [0.8, 0.8, 0.2]),
- ([s, -s, s], [0.8, 0.2, 0.8]), ([s, -s, -s], [0.8, 0.2, 0.8]), ([s, s, -s], [0.8, 0.2, 0.8]),
- ([s, -s, s], [0.8, 0.2, 0.8]), ([s, s, -s], [0.8, 0.2, 0.8]), ([s, s, s], [0.8, 0.2, 0.8]),
- ([-s, -s, -s], [0.2, 0.8, 0.8]), ([-s, -s, s], [0.2, 0.8, 0.8]), ([-s, s, s], [0.2, 0.8, 0.8]),
- ([-s, -s, -s], [0.2, 0.8, 0.8]), ([-s, s, s], [0.2, 0.8, 0.8]), ([-s, s, -s], [0.2, 0.8, 0.8]),
- ];
- data.iter().map(|&(p, c)| Vertex3D { position: p, color: c }).collect()
-}
-
-fn sphere_vertices(center: Vec3, radius: f32) -> Geometry {
- let lat_steps = 16;
- let lon_steps = 24;
- let mut vertices = Vec::new();
-
- for lat in 0..lat_steps {
- let theta0 = std::f32::consts::PI * lat as f32 / lat_steps as f32;
- let theta1 = std::f32::consts::PI * (lat + 1) as f32 / lat_steps as f32;
- for lon in 0..lon_steps {
- let phi0 = std::f32::consts::TAU * lon as f32 / lon_steps as f32;
- let phi1 = std::f32::consts::TAU * (lon + 1) as f32 / lon_steps as f32;
- let p00 = sphere_point(center, radius, theta0, phi0);
- let p10 = sphere_point(center, radius, theta1, phi0);
- let p11 = sphere_point(center, radius, theta1, phi1);
- let p01 = sphere_point(center, radius, theta0, phi1);
- vertices.push(sphere_vertex(center, p00));
- vertices.push(sphere_vertex(center, p10));
- vertices.push(sphere_vertex(center, p11));
- vertices.push(sphere_vertex(center, p00));
- vertices.push(sphere_vertex(center, p11));
- vertices.push(sphere_vertex(center, p01));
- }
- }
-
- Geometry { vertices }
-}
-
-fn sphere_point(center: Vec3, radius: f32, theta: f32, phi: f32) -> Vec3 {
- center + Vec3::new(
- radius * theta.sin() * phi.cos(),
- radius * theta.cos(),
- radius * theta.sin() * phi.sin(),
- )
-}
-
-fn sphere_vertex(center: Vec3, point: Vec3) -> GVertex {
- let n = (point - center).normalize_or_zero();
- let u = 0.5 + n.z.atan2(n.x) / std::f32::consts::TAU;
- let v = 0.5 - n.y.asin() / std::f32::consts::PI;
-
- let pos = point.to_array();
- let col = [0.35 + n.x.abs() * 0.35, 0.45 + n.y.abs() * 0.35, 0.85];
-
- let mut attributes = std::collections::HashMap::new();
- attributes.insert("Norm".to_string(), GAttribute::Float3(n.to_array()));
- attributes.insert("UV".to_string(), GAttribute::Float2([u, v]));
-
- GVertex {
- pos,
- col,
- attributes,
- }
-}
-
-fn line_vertices(start: Vec3, end: Vec3, thickness: f32) -> Geometry {
- let mut vertices = Vec::new();
- let dir = (end - start).normalize_or_zero();
- if dir.length_squared() < 0.0001 {
- return Geometry { vertices };
- }
-
- // Find two orthogonal vectors to dir
- let up = if dir.x.abs() > 0.9 { Vec3::Y } else { Vec3::X };
- let u = dir.cross(up).normalize();
- let v = dir.cross(u).normalize();
-
- let t = thickness * 0.5;
-
- // 8 corners of the box
- let c0 = start - t * u - t * v;
- let c1 = start + t * u - t * v;
- let c2 = start + t * u + t * v;
- let c3 = start - t * u + t * v;
-
- let c4 = end - t * u - t * v;
- let c5 = end + t * u - t * v;
- let c6 = end + t * u + t * v;
- let c7 = end - t * u + t * v;
-
- // Helper to add a triangle face
- let mut add_quad = |p0: Vec3, p1: Vec3, p2: Vec3, p3: Vec3, normal: Vec3, color: [f32; 3]| {
- let make_vertex = |p: Vec3| {
- let mut attributes = std::collections::HashMap::new();
- attributes.insert("Norm".to_string(), GAttribute::Float3(normal.to_array()));
- attributes.insert("UV".to_string(), GAttribute::Float2([0.0, 0.0]));
- GVertex {
- pos: p.to_array(),
- col: color,
- attributes,
- }
- };
- // Triangle 1: p0, p1, p2
- vertices.push(make_vertex(p0));
- vertices.push(make_vertex(p1));
- vertices.push(make_vertex(p2));
- // Triangle 2: p0, p2, p3
- vertices.push(make_vertex(p0));
- vertices.push(make_vertex(p2));
- vertices.push(make_vertex(p3));
- };
-
- let col = [0.85, 0.45, 0.35]; // distinct color for lines
-
- // Front face (start cap)
- add_quad(c0, c1, c2, c3, -dir, col);
- // Back face (end cap)
- add_quad(c5, c4, c7, c6, dir, col);
- // Left face
- add_quad(c4, c0, c3, c7, -u, col);
- // Right face
- add_quad(c1, c5, c6, c2, u, col);
- // Top face
- add_quad(c3, c2, c6, c7, v, col);
- // Bottom face
- add_quad(c0, c4, c5, c1, -v, col);
-
- Geometry { vertices }
-}
-
-fn node_param_f32(node: &FsNode, name: &str, fallback: f32) -> f32 {
- node.params.iter()
- .find(|p| p.name.eq_ignore_ascii_case(name))
- .and_then(|p| p.default.parse::<f32>().ok())
- .unwrap_or(fallback)
-}
-
-fn run_opencl_kernel(code: &str, geom: &mut Geometry) -> Result<(), String> {
- if geom.vertices.is_empty() {
- return Ok(());
- }
-
- let platforms = get_platforms().map_err(|e| format!("Failed to get platforms: {:?}", e))?;
- if platforms.is_empty() {
- return Err("No OpenCL platforms found".to_string());
- }
-
- // Try to find a GPU device first, then fallback to CPU
- let mut device_id = None;
- for platform in &platforms {
- if let Ok(devices) = platform.get_devices(CL_DEVICE_TYPE_GPU) {
- if !devices.is_empty() {
- device_id = Some(devices[0]);
- break;
- }
- }
- }
- if device_id.is_none() {
- for platform in &platforms {
- if let Ok(devices) = platform.get_devices(CL_DEVICE_TYPE_CPU) {
- if !devices.is_empty() {
- device_id = Some(devices[0]);
- break;
- }
- }
- }
- }
- let device_id = device_id.ok_or_else(|| "No OpenCL devices found".to_string())?;
- let device = Device::new(device_id);
-
- let context = Context::from_device(&device).map_err(|e| format!("Failed to create Context: {:?}", e))?;
- let queue = unsafe { CommandQueue::create(&context, device_id, 0) }
- .map_err(|e| format!("Failed to create CommandQueue: {:?}", e))?;
-
- let mut program = Program::create_from_source(&context, code).map_err(|e| format!("Failed to create Program: {:?}", e))?;
- if let Err(e) = program.build(&[device_id], "") {
- let log = program.get_build_log(device_id).unwrap_or_else(|_| "Failed to retrieve build log".to_string());
- return Err(format!("OpenCL JIT compilation error: {}\nLog:\n{}", e, log));
- }
-
- let kernel = Kernel::create(&program, "process").map_err(|e| format!("Failed to create kernel 'process': {:?}", e))?;
-
- let count = geom.vertices.len();
-
- // Prepare flat position and color buffers
- let mut pos_data: Vec<cl_float> = Vec::with_capacity(count * 3);
- let mut col_data: Vec<cl_float> = Vec::with_capacity(count * 3);
- for v in &geom.vertices {
- pos_data.extend_from_slice(&v.pos);
- col_data.extend_from_slice(&v.col);
- }
-
- // Create device buffers
- let mut pos_buf = unsafe {
- ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, count * 3, std::ptr::null_mut())
- .map_err(|e| format!("Failed to create positions buffer: {:?}", e))?
- };
- let mut col_buf = unsafe {
- ClBuffer::<cl_float>::create(&context, CL_MEM_READ_WRITE, count * 3, std::ptr::null_mut())
- .map_err(|e| format!("Failed to create colors buffer: {:?}", e))?
- };
-
- // Write data to device
- let _write_pos_event = unsafe {
- queue.enqueue_write_buffer(&mut pos_buf, CL_TRUE, 0, &pos_data, &[])
- .map_err(|e| format!("Failed to write positions buffer: {:?}", e))?
- };
- let _write_col_event = unsafe {
- queue.enqueue_write_buffer(&mut col_buf, CL_TRUE, 0, &col_data, &[])
- .map_err(|e| format!("Failed to write colors buffer: {:?}", e))?
- };
-
- // Execute kernel
- let kernel_event = unsafe {
- ExecuteKernel::new(&kernel)
- .set_arg(&pos_buf)
- .set_arg(&col_buf)
- .set_arg(&(count as cl_int))
- .set_global_work_size(count)
- .enqueue_nd_range(&queue)
- .map_err(|e| format!("Failed to enqueue kernel: {:?}", e))?
- };
-
- kernel_event.wait().map_err(|e| format!("Failed to wait for kernel: {:?}", e))?;
-
- // Read data back from device
- let _read_pos_event = unsafe {
- queue.enqueue_read_buffer(&pos_buf, CL_TRUE, 0, &mut pos_data, &[])
- .map_err(|e| format!("Failed to read positions buffer: {:?}", e))?
- };
- let _read_col_event = unsafe {
- queue.enqueue_read_buffer(&col_buf, CL_TRUE, 0, &mut col_data, &[])
- .map_err(|e| format!("Failed to read colors buffer: {:?}", e))?
- };
-
- // Write back to Geometry
- for i in 0..count {
- geom.vertices[i].pos = [pos_data[i * 3], pos_data[i * 3 + 1], pos_data[i * 3 + 2]];
- geom.vertices[i].col = [col_data[i * 3], col_data[i * 3 + 1], col_data[i * 3 + 2]];
- }
-
- Ok(())
-}
-
-fn network_sphere_vertices(root: &FsNode) -> Geometry {
- fn visit(node: &FsNode, count: &mut usize, out: &mut Geometry) {
- if node.node_type.eq_ignore_ascii_case("sphere") {
- let idx = *count;
- *count += 1;
- if node.geometry_visible {
- let center = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
- out.merge(sphere_vertices(center, node_param_f32(node, "Radius", 0.5).max(0.05)));
- }
- } else if node.node_type.eq_ignore_ascii_case("line") {
- let idx = *count;
- *count += 1;
- if node.geometry_visible {
- let start = Vec3::new((idx % 4) as f32 * 1.25 - 1.875, 0.55, -((idx / 4) as f32) * 1.25);
- let length = node_param_f32(node, "Length", 1.0);
- let thickness = node_param_f32(node, "Thickness", 0.02);
- let end = start + Vec3::new(0.0, length, 0.0);
- out.merge(line_vertices(start, end, thickness));
- }
- }
- for child in &node.children {
- visit(child, count, out);
- }
- }
-
- let mut out = Geometry::new();
- let mut count = 0;
- for child in &root.children {
- visit(child, &mut count, &mut out);
- }
- out
-}
-
-fn find_sphere_index(root: &FsNode, target: &FsNode) -> Option<usize> {
- fn visit(node: &FsNode, target: &FsNode, count: &mut usize) -> Option<usize> {
- let is_target = std::ptr::eq(node, target);
- if node.node_type.eq_ignore_ascii_case("sphere") || node.node_type.eq_ignore_ascii_case("line") {
- let idx = *count;
- *count += 1;
- if is_target {
- return Some(idx);
- }
- }
- for child in &node.children {
- if let Some(res) = visit(child, target, count) {
- return Some(res);
- }
- }
- None
- }
- let mut count = 0;
- for child in &root.children {
- if let Some(res) = visit(child, target, &mut count) {
- return Some(res);
- }
- }
- None
-}
-
-fn add_box(center: Vec3, size: Vec3, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
- let dx = size.x * 0.5;
- let dy = size.y * 0.5;
- let dz = size.z * 0.5;
-
- let faces = [
- // front (z = +dz)
- [-dx, -dy, dz, dx, -dy, dz, dx, dy, dz, -dx, -dy, dz, dx, dy, dz, -dx, dy, dz],
- // back (z = -dz)
- [-dx, -dy, -dz, -dx, dy, -dz, dx, dy, -dz, -dx, -dy, -dz, dx, dy, -dz, dx, -dy, -dz],
- // left (x = -dx)
- [-dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz, -dx, -dy, -dz, -dx, dy, dz, -dx, dy, -dz],
- // right (x = +dx)
- [dx, -dy, -dz, dx, dy, -dz, dx, dy, dz, dx, -dy, -dz, dx, dy, dz, dx, -dy, dz],
- // top (y = +dy)
- [-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, -dx, dy, -dz, dx, dy, dz, dx, dy, -dz],
- // bottom (y = -dy)
- [-dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz, -dx, -dy, -dz, dx, -dy, dz, -dx, -dy, dz],
- ];
-
- for face in &faces {
- for chunk in face.chunks(3) {
- verts.push(Vertex3D {
- position: [center.x + chunk[0], center.y + chunk[1], center.z + chunk[2]],
- color,
- });
- }
- }
-}
-
-fn add_pyramid_x(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
- let s = base_size * 0.5;
- let x = base_center.x;
- let y = base_center.y;
- let z = base_center.z;
-
- let p0 = Vec3::new(x, y - s, z - s);
- let p1 = Vec3::new(x, y + s, z - s);
- let p2 = Vec3::new(x, y + s, z + s);
- let p3 = Vec3::new(x, y - s, z + s);
- let tip = Vec3::new(x + height, y, z);
-
- // Base (two triangles)
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
- verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
-
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
-
- // Sides
- let sides = [
- (p0, p3), (p3, p2), (p2, p1), (p1, p0)
- ];
- for (a, b) in &sides {
- verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
- verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
- verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
- }
-}
-
-fn add_pyramid_y(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
- let s = base_size * 0.5;
- let x = base_center.x;
- let y = base_center.y;
- let z = base_center.z;
-
- let p0 = Vec3::new(x - s, y, z - s);
- let p1 = Vec3::new(x + s, y, z - s);
- let p2 = Vec3::new(x + s, y, z + s);
- let p3 = Vec3::new(x - s, y, z + s);
- let tip = Vec3::new(x, y + height, z);
-
- // Base (two triangles)
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
-
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
- verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
-
- // Sides
- let sides = [
- (p0, p1), (p1, p2), (p2, p3), (p3, p0)
- ];
- for (a, b) in &sides {
- verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
- verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
- verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
- }
-}
-
-fn add_pyramid_z(base_center: Vec3, base_size: f32, height: f32, color: [f32; 3], verts: &mut Vec<Vertex3D>) {
- let s = base_size * 0.5;
- let x = base_center.x;
- let y = base_center.y;
- let z = base_center.z;
-
- let p0 = Vec3::new(x - s, y - s, z);
- let p1 = Vec3::new(x + s, y - s, z);
- let p2 = Vec3::new(x + s, y + s, z);
- let p3 = Vec3::new(x - s, y + s, z);
- let tip = Vec3::new(x, y, z + height);
-
- // Base (two triangles)
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
- verts.push(Vertex3D { position: [p1.x, p1.y, p1.z], color });
-
- verts.push(Vertex3D { position: [p0.x, p0.y, p0.z], color });
- verts.push(Vertex3D { position: [p3.x, p3.y, p3.z], color });
- verts.push(Vertex3D { position: [p2.x, p2.y, p2.z], color });
-
- // Sides
- let sides = [
- (p0, p3), (p3, p2), (p2, p1), (p1, p0)
- ];
- for (a, b) in &sides {
- verts.push(Vertex3D { position: [a.x, a.y, a.z], color });
- verts.push(Vertex3D { position: [tip.x, tip.y, tip.z], color });
- verts.push(Vertex3D { position: [b.x, b.y, b.z], color });
- }
-}
-
-fn origin_vectors_vertices(scale: f32) -> Vec<Vertex3D> {
- let mut verts = Vec::new();
-
- let t = 0.008 * scale;
- let a_size = 0.024 * scale;
- let a_height = 0.15 * scale;
- let axis_len = 0.85 * scale;
- let half_axis_len = 0.425 * scale;
-
- // Red for X-axis (points to +scale)
- let red = [0.9, 0.1, 0.1];
- add_box(Vec3::new(half_axis_len, 0.0, 0.0), Vec3::new(axis_len, t, t), red, &mut verts);
- add_pyramid_x(Vec3::new(axis_len, 0.0, 0.0), a_size, a_height, red, &mut verts);
-
- // Green for Y-axis (points to +scale)
- let green = [0.1, 0.8, 0.1];
- add_box(Vec3::new(0.0, half_axis_len, 0.0), Vec3::new(t, axis_len, t), green, &mut verts);
- add_pyramid_y(Vec3::new(0.0, axis_len, 0.0), a_size, a_height, green, &mut verts);
-
- // Blue for Z-axis (points to +scale)
- let blue = [0.1, 0.1, 0.9];
- add_box(Vec3::new(0.0, 0.0, half_axis_len), Vec3::new(t, t, axis_len), blue, &mut verts);
- add_pyramid_z(Vec3::new(0.0, 0.0, axis_len), a_size, a_height, blue, &mut verts);
-
- verts
-}
-
-fn camera_pivot_vertices(scale: f32) -> Vec<Vertex3D> {
- let mut verts = Vec::new();
- let t = 0.002 * scale;
- let len = 0.4 * scale;
-
- // Red for X-axis
- let red = [0.9, 0.1, 0.1];
- add_box(Vec3::new(len * 0.5, 0.0, 0.0), Vec3::new(len, t, t), red, &mut verts);
-
- // Green for Y-axis
- let green = [0.1, 0.8, 0.1];
- add_box(Vec3::new(0.0, len * 0.5, 0.0), Vec3::new(t, len, t), green, &mut verts);
-
- // Blue for Z-axis
- let blue = [0.1, 0.1, 0.9];
- add_box(Vec3::new(0.0, 0.0, len * 0.5), Vec3::new(t, t, len), blue, &mut verts);
-
- verts
-}
-
-fn grid_vertices(thickness: f32) -> Vec<Vertex3D> {
- let color = [0.35, 0.35, 0.40];
- let range = 4.0;
- let step = 1.0;
- let mut geom = Geometry::new();
-
- let mut z = -range;
- while z <= range {
- let start = Vec3::new(-range, 0.0, z);
- let end = Vec3::new(range, 0.0, z);
- let mut line_geom = line_vertices(start, end, thickness);
- for v in &mut line_geom.vertices {
- v.col = color;
- }
- geom.merge(line_geom);
- z += step;
- }
-
- let mut x = -range;
- while x <= range {
- let start = Vec3::new(x, 0.0, -range);
- let end = Vec3::new(x, 0.0, range);
- let mut line_geom = line_vertices(start, end, thickness);
- for v in &mut line_geom.vertices {
- v.col = color;
- }
- geom.merge(line_geom);
- x += step;
- }
-
- geom.to_vertex3d_vec()
-}
fn quad_vertices(
x: f32, y: f32, w: f32, h: f32,
@@ -3129,8 +2558,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
let status_buffer = make_text_buffer(&mut font_system, "Ready", 12.0);
- let splitter1_x = (sw - SPLITTER_W) * 0.25;
- let splitter2_x = (sw - SPLITTER_W) * 0.75;
+ let splitter1_x = (sw - 2.0 * SPLITTER_W) / 3.0;
+ let splitter2_x = splitter1_x + SPLITTER_W + (sw - 2.0 * SPLITTER_W) / 3.0;
let (depth_texture, depth_texture_view) = {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Depth Texture"),
@@ -3195,7 +2624,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
.collect();
let mut widgets: Vec<Box<dyn Widget>> = vec![
- Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_item("File", &["New Project", "Open", "Save", "Configure", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom"]).with_item("Help", &["About"])),
+ Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_item("File", &["New Project", "Open", "Save", "Configure", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom"]).with_item("Help", &["About"]).with_z_index(110)),
Box::new(ContentBg::new()),
Box::new(Splitter::new(SPLITTER_W)),
Box::new(ViewportBg::new()),
@@ -3523,11 +2952,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
fn sync_pane_focus(&mut self) {
for &menubar_idx in &[LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX] {
- if menubar_idx == self.focused_pane {
- self.widgets[menubar_idx].focus();
- } else {
- self.widgets[menubar_idx].unfocus();
- }
+ self.widgets[menubar_idx].set_selected(menubar_idx == self.focused_pane);
}
}
@@ -3680,7 +3105,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
let clip = (0.0, node_area_y, self.content_left_w(), self.height - STATUS_H);
- for (i, w) in self.widgets.iter().enumerate() {
+ let mut draw_order: Vec<usize> = (0..self.widgets.len()).collect();
+ draw_order.sort_by_key(|&i| self.widgets[i].z_index());
+
+ for &i in &draw_order {
+ let w = &self.widgets[i];
if i == CONTENT_IDX {
verts.extend(widget_vertices(w.as_ref(), sw, sh));
for (qx, qy, qw, qh, qc) in w.extra_quads() {
@@ -3870,6 +3299,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
+ let old_width = self.width;
self.physical_width = width;
self.physical_height = height;
self.width = width as f32 / self.scale as f32;
@@ -3882,6 +3312,15 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
self.depth_texture = tex;
self.depth_texture_view = view;
+ if old_width > 0.0 {
+ let r = self.width / old_width;
+ self.splitter1_x *= r;
+ self.splitter2_x *= r;
+ let body_h = self.body_h();
+ self.widgets[SPLITTER1_IDX].set_rect(self.splitter1_x, HEADER_H, SPLITTER_W, body_h);
+ self.widgets[SPLITTER2_IDX].set_rect(self.splitter2_x, HEADER_H, SPLITTER_W, body_h);
+ }
+
self.sync_layout();
self.read_panel_offsets();
self.keep_cursor_in_view();
@@ -4256,8 +3695,23 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
match btn_state {
ElementState::Pressed => {
- let click_target = (0..self.widgets.len()).rev()
- .find(|&i| self.widgets[i].hit_test(self.cursor_x, self.cursor_y));
+ eprintln!("[DEBUG] Left click at cursor logical coords: ({:.2}, {:.2})", self.cursor_x, self.cursor_y);
+ let mut click_target = None;
+ for i in 0..self.widgets.len() {
+ if self.widgets[i].is_menu_open() && self.widgets[i].hit_test(self.cursor_x, self.cursor_y) {
+ click_target = Some(i);
+ break;
+ }
+ }
+ if click_target.is_none() {
+ click_target = (0..self.widgets.len()).rev()
+ .find(|&i| self.widgets[i].hit_test(self.cursor_x, self.cursor_y));
+ }
+ eprintln!("[DEBUG] Selected click target: {:?}", click_target);
+ if let Some(i) = click_target {
+ let (rx, ry, rw, rh) = self.widgets[i].rect();
+ eprintln!("[DEBUG] Target rect: ({:.2}, {:.2}, {:.2}, {:.2}), is_menu_bar: {}, is_menu_open: {}", rx, ry, rw, rh, self.widgets[i].is_menu_bar(), self.widgets[i].is_menu_open());
+ }
// Determine new focused pane
let mut new_pane = None;
@@ -5059,7 +4513,7 @@ struct AppState {
output_state: OutputState,
seats: Vec<wl_seat::WlSeat>,
- pointer: Option<wl_pointer::WlPointer>,
+ pointer: Option<ThemedPointer>,
keyboard: Option<wl_keyboard::WlKeyboard>,
window: Option<XdgWindow>,
@@ -5166,8 +4620,15 @@ impl SeatHandler for AppState {
capability: Capability,
) {
if capability == Capability::Pointer && self.pointer.is_none() {
- let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
- self.pointer = Some(pointer);
+ let surface = self.compositor_state.create_surface(qh);
+ let themed_pointer = self.seat_state.get_pointer_with_theme(
+ qh,
+ &seat,
+ self.shm_state.wl_shm(),
+ surface,
+ ThemeSpec::System,
+ ).unwrap();
+ self.pointer = Some(themed_pointer);
}
if capability == Capability::Keyboard && self.keyboard.is_none() {
let keyboard = self
@@ -5261,6 +4722,11 @@ impl PointerHandler for AppState {
};
self.process_event(ev);
}
+ PointerEventKind::Enter { .. } => {
+ if let Some(ref themed_pointer) = self.pointer {
+ let _ = themed_pointer.set_cursor(_conn, CursorIcon::Default);
+ }
+ }
_ => {}
}
}
@@ -5396,6 +4862,12 @@ impl AppState {
xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
xkeysym::Keysym::space => Key::Named(NamedKey::Space),
+ xkeysym::Keysym::comma => Key::Character(",".into()),
+ xkeysym::Keysym::g | xkeysym::Keysym::G => Key::Character("g".into()),
+ xkeysym::Keysym::e | xkeysym::Keysym::E => Key::Character("e".into()),
+ xkeysym::Keysym::a | xkeysym::Keysym::A => Key::Character("a".into()),
+ xkeysym::Keysym::f | xkeysym::Keysym::F => Key::Character("f".into()),
+ xkeysym::Keysym::grave => Key::Character("`".into()),
_ => {
if let Some(ref text) = event.utf8 {
Key::Character(text.clone())