// Mesh GPU buffers — TriangleList rendering for solid objects. // // Vertex layout (40 bytes): // position [f32; 3] offset 0 12 B // normal [f32; 3] offset 12 12 B // color [f32; 4] offset 24 16 B // ------ // 40 B / vertex use crate::scene::mesh_model::{MeshLodSet, MeshModel}; use iced::wgpu; use iced::wgpu::util::DeviceExt; // ── Vertex layout ───────────────────────────────────────────────────────── #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct MeshVertex { pub position: [f32; 3], pub normal: [f32; 3], pub color: [f32; 4], } impl MeshVertex { pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { array_stride: std::mem::size_of::() as u64, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: 12, shader_location: 1, format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { offset: 24, shader_location: 2, format: wgpu::VertexFormat::Float32x4, }, ], } } } // ── GPU handle ──────────────────────────────────────────────────────────── pub struct MeshGpu { pub vertex_buffer: wgpu::Buffer, pub index_buffer: wgpu::Buffer, pub index_count: u32, /// Line-list index buffer: every triangle `(a, b, c)` from the /// solid index buffer is expanded into three segments /// `(a,b)(b,c)(c,a)`. Used by the wireframe-mode render path so 3D /// solids draw as their triangle edges without needing the /// `POLYGON_MODE_LINE` device feature. pub wire_index_buffer: wgpu::Buffer, pub wire_index_count: u32, } /// GPU-side bundle of MeshLodSet — one MeshGpu per available LOD plus /// the world-XY AABB needed to pick a level per frame. pub struct MeshLodGpu { pub lods: Vec, pub world_aabb: [f32; 4], } impl MeshLodGpu { pub fn new(device: &wgpu::Device, set: &MeshLodSet) -> Self { Self { lods: set .lods .iter() .filter(|m| !m.indices.is_empty()) .map(|m| MeshGpu::new(device, m)) .collect(), world_aabb: set.world_aabb, } } } impl MeshGpu { pub fn new(device: &wgpu::Device, mesh: &MeshModel) -> Self { let has_normals = mesh.normals.len() == mesh.verts.len(); let vertices: Vec = mesh .verts .iter() .enumerate() .map(|(i, &pos)| { let normal = if has_normals { mesh.normals[i] } else { [0.0, 1.0, 0.0] }; MeshVertex { position: pos, normal, color: mesh.color, } }) .collect(); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("mesh.vbuf.{}", mesh.name)), contents: bytemuck::cast_slice(&vertices), usage: wgpu::BufferUsages::VERTEX, }); let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("mesh.ibuf.{}", mesh.name)), contents: bytemuck::cast_slice(&mesh.indices), usage: wgpu::BufferUsages::INDEX, }); // Wireframe-mode index buffer: expand each triangle into its // three edge segments. Allocates ~2× the solid index count but // is cheap compared to mesh tessellation and only happens when // a new mesh is uploaded. let mut wire_indices: Vec = Vec::with_capacity(mesh.indices.len() * 2); for tri in mesh.indices.chunks_exact(3) { let (a, b, c) = (tri[0], tri[1], tri[2]); wire_indices.extend_from_slice(&[a, b, b, c, c, a]); } let wire_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("mesh.wire_ibuf.{}", mesh.name)), contents: bytemuck::cast_slice(&wire_indices), usage: wgpu::BufferUsages::INDEX, }); Self { vertex_buffer, index_buffer, index_count: mesh.indices.len() as u32, wire_index_buffer, wire_index_count: wire_indices.len() as u32, } } }