Skip to main content

threecrate_gpu/
mesh.rs

1//! GPU-accelerated mesh rendering with PBR and flat shading
2
3use threecrate_core::{Result, Error};
4use threecrate_simplification::ProgressiveMesh;
5use crate::GpuContext;
6use nalgebra::{Matrix4, Vector3, Point3};
7use bytemuck::{Pod, Zeroable};
8use wgpu::util::DeviceExt;
9use winit::window::Window;
10
11/// Vertex data for mesh rendering with full PBR attributes
12#[repr(C)]
13#[derive(Copy, Clone, Debug, Pod, Zeroable)]
14pub struct MeshVertex {
15    pub position: [f32; 3],
16    pub normal: [f32; 3],
17    pub tangent: [f32; 3],
18    pub bitangent: [f32; 3],
19    pub uv: [f32; 2],
20    pub color: [f32; 3],
21    pub _padding: f32,
22}
23
24impl MeshVertex {
25    /// Create a new mesh vertex
26    pub fn new(
27        position: [f32; 3],
28        normal: [f32; 3],
29        uv: [f32; 2],
30        color: [f32; 3],
31    ) -> Self {
32        // Calculate tangent and bitangent vectors (simplified)
33        let tangent = if normal[0].abs() > 0.9 {
34            [0.0, 1.0, 0.0]
35        } else {
36            [1.0, 0.0, 0.0]
37        };
38        
39        let bitangent = [
40            normal[1] * tangent[2] - normal[2] * tangent[1],
41            normal[2] * tangent[0] - normal[0] * tangent[2],
42            normal[0] * tangent[1] - normal[1] * tangent[0],
43        ];
44        
45        Self {
46            position,
47            normal,
48            tangent,
49            bitangent,
50            uv,
51            color,
52            _padding: 0.0,
53        }
54    }
55    
56    /// Create vertex from position and normal with default color
57    pub fn from_pos_normal(position: [f32; 3], normal: [f32; 3]) -> Self {
58        Self::new(position, normal, [0.0, 0.0], [0.8, 0.8, 0.8])
59    }
60    
61    /// Vertex buffer layout descriptor
62    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
63        wgpu::VertexBufferLayout {
64            array_stride: std::mem::size_of::<MeshVertex>() as wgpu::BufferAddress,
65            step_mode: wgpu::VertexStepMode::Vertex,
66            attributes: &[
67                // Position
68                wgpu::VertexAttribute {
69                    offset: 0,
70                    shader_location: 0,
71                    format: wgpu::VertexFormat::Float32x3,
72                },
73                // Normal
74                wgpu::VertexAttribute {
75                    offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
76                    shader_location: 1,
77                    format: wgpu::VertexFormat::Float32x3,
78                },
79                // Tangent
80                wgpu::VertexAttribute {
81                    offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
82                    shader_location: 2,
83                    format: wgpu::VertexFormat::Float32x3,
84                },
85                // Bitangent
86                wgpu::VertexAttribute {
87                    offset: std::mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
88                    shader_location: 3,
89                    format: wgpu::VertexFormat::Float32x3,
90                },
91                // UV
92                wgpu::VertexAttribute {
93                    offset: std::mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
94                    shader_location: 4,
95                    format: wgpu::VertexFormat::Float32x2,
96                },
97                // Color
98                wgpu::VertexAttribute {
99                    offset: std::mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
100                    shader_location: 5,
101                    format: wgpu::VertexFormat::Float32x3,
102                },
103            ],
104        }
105    }
106}
107
108/// Camera uniform data for mesh rendering
109#[repr(C)]
110#[derive(Copy, Clone, Pod, Zeroable)]
111pub struct MeshCameraUniform {
112    pub view_proj: [[f32; 4]; 4],
113    pub view_pos: [f32; 3],
114    pub _padding: f32,
115}
116
117/// PBR material properties
118#[repr(C)]
119#[derive(Copy, Clone, Debug, Pod, Zeroable)]
120pub struct PbrMaterial {
121    pub albedo: [f32; 3],
122    pub metallic: f32,
123    pub roughness: f32,
124    pub ao: f32,
125    pub emission: [f32; 3],
126    pub _padding: f32,
127}
128
129impl Default for PbrMaterial {
130    fn default() -> Self {
131        Self {
132            albedo: [0.7, 0.7, 0.7],
133            metallic: 0.0,
134            roughness: 0.5,
135            ao: 1.0,
136            emission: [0.0, 0.0, 0.0],
137            _padding: 0.0,
138        }
139    }
140}
141
142/// Flat shading material properties
143#[repr(C)]
144#[derive(Copy, Clone, Debug, Pod, Zeroable)]
145pub struct FlatMaterial {
146    pub color: [f32; 3],
147    pub _padding: f32,
148}
149
150impl Default for FlatMaterial {
151    fn default() -> Self {
152        Self {
153            color: [0.8, 0.8, 0.8],
154            _padding: 0.0,
155        }
156    }
157}
158
159/// Lighting parameters for mesh rendering
160#[repr(C)]
161#[derive(Copy, Clone, Debug, Pod, Zeroable)]
162pub struct MeshLightingParams {
163    pub light_position: [f32; 3],
164    pub light_intensity: f32,
165    pub light_color: [f32; 3],
166    pub ambient_strength: f32,
167    pub gamma: f32,
168    pub exposure: f32,
169    pub _padding: [f32; 2],
170}
171
172impl Default for MeshLightingParams {
173    fn default() -> Self {
174        Self {
175            light_position: [10.0, 10.0, 10.0],
176            light_intensity: 1.0,
177            light_color: [1.0, 1.0, 1.0],
178            ambient_strength: 0.03,
179            gamma: 2.2,
180            exposure: 1.0,
181            _padding: [0.0, 0.0],
182        }
183    }
184}
185
186/// Mesh rendering configuration
187#[derive(Debug, Clone)]
188pub struct MeshRenderConfig {
189    pub lighting_params: MeshLightingParams,
190    pub background_color: [f64; 4],
191    pub enable_depth_test: bool,
192    pub enable_backface_culling: bool,
193    pub enable_multisampling: bool,
194    pub wireframe_mode: bool,
195}
196
197impl Default for MeshRenderConfig {
198    fn default() -> Self {
199        Self {
200            lighting_params: MeshLightingParams::default(),
201            background_color: [0.1, 0.1, 0.1, 1.0],
202            enable_depth_test: true,
203            enable_backface_culling: true,
204            enable_multisampling: true,
205            wireframe_mode: false,
206        }
207    }
208}
209
210/// Mesh data structure for GPU rendering
211#[derive(Debug, Clone)]
212pub struct GpuMesh {
213    pub vertices: Vec<MeshVertex>,
214    pub indices: Vec<u32>,
215    pub material: PbrMaterial,
216}
217
218impl GpuMesh {
219    /// Create a new GPU mesh
220    pub fn new(vertices: Vec<MeshVertex>, indices: Vec<u32>, material: PbrMaterial) -> Self {
221        Self {
222            vertices,
223            indices,
224            material,
225        }
226    }
227    
228    /// Create a simple triangle mesh for testing
229    pub fn triangle() -> Self {
230        let vertices = vec![
231            MeshVertex::new([-0.5, -0.5, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0], [1.0, 0.0, 0.0]),
232            MeshVertex::new([0.5, -0.5, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0], [0.0, 1.0, 0.0]),
233            MeshVertex::new([0.0, 0.5, 0.0], [0.0, 0.0, 1.0], [0.5, 1.0], [0.0, 0.0, 1.0]),
234        ];
235        
236        let indices = vec![0, 1, 2];
237        
238        Self::new(vertices, indices, PbrMaterial::default())
239    }
240    
241    /// Create a cube mesh for testing
242    pub fn cube() -> Self {
243        let vertices = vec![
244            // Front face
245            MeshVertex::new([-1.0, -1.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0], [0.8, 0.2, 0.2]),
246            MeshVertex::new([1.0, -1.0, 1.0], [0.0, 0.0, 1.0], [1.0, 0.0], [0.8, 0.2, 0.2]),
247            MeshVertex::new([1.0, 1.0, 1.0], [0.0, 0.0, 1.0], [1.0, 1.0], [0.8, 0.2, 0.2]),
248            MeshVertex::new([-1.0, 1.0, 1.0], [0.0, 0.0, 1.0], [0.0, 1.0], [0.8, 0.2, 0.2]),
249            
250            // Back face
251            MeshVertex::new([1.0, -1.0, -1.0], [0.0, 0.0, -1.0], [0.0, 0.0], [0.2, 0.8, 0.2]),
252            MeshVertex::new([-1.0, -1.0, -1.0], [0.0, 0.0, -1.0], [1.0, 0.0], [0.2, 0.8, 0.2]),
253            MeshVertex::new([-1.0, 1.0, -1.0], [0.0, 0.0, -1.0], [1.0, 1.0], [0.2, 0.8, 0.2]),
254            MeshVertex::new([1.0, 1.0, -1.0], [0.0, 0.0, -1.0], [0.0, 1.0], [0.2, 0.8, 0.2]),
255            
256            // Top face
257            MeshVertex::new([-1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0], [0.2, 0.2, 0.8]),
258            MeshVertex::new([1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0], [0.2, 0.2, 0.8]),
259            MeshVertex::new([1.0, 1.0, -1.0], [0.0, 1.0, 0.0], [1.0, 1.0], [0.2, 0.2, 0.8]),
260            MeshVertex::new([-1.0, 1.0, -1.0], [0.0, 1.0, 0.0], [0.0, 1.0], [0.2, 0.2, 0.8]),
261            
262            // Bottom face
263            MeshVertex::new([-1.0, -1.0, -1.0], [0.0, -1.0, 0.0], [0.0, 0.0], [0.8, 0.8, 0.2]),
264            MeshVertex::new([1.0, -1.0, -1.0], [0.0, -1.0, 0.0], [1.0, 0.0], [0.8, 0.8, 0.2]),
265            MeshVertex::new([1.0, -1.0, 1.0], [0.0, -1.0, 0.0], [1.0, 1.0], [0.8, 0.8, 0.2]),
266            MeshVertex::new([-1.0, -1.0, 1.0], [0.0, -1.0, 0.0], [0.0, 1.0], [0.8, 0.8, 0.2]),
267            
268            // Right face
269            MeshVertex::new([1.0, -1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 0.0], [0.8, 0.2, 0.8]),
270            MeshVertex::new([1.0, -1.0, -1.0], [1.0, 0.0, 0.0], [1.0, 0.0], [0.8, 0.2, 0.8]),
271            MeshVertex::new([1.0, 1.0, -1.0], [1.0, 0.0, 0.0], [1.0, 1.0], [0.8, 0.2, 0.8]),
272            MeshVertex::new([1.0, 1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0], [0.8, 0.2, 0.8]),
273            
274            // Left face
275            MeshVertex::new([-1.0, -1.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 0.0], [0.2, 0.8, 0.8]),
276            MeshVertex::new([-1.0, -1.0, 1.0], [-1.0, 0.0, 0.0], [1.0, 0.0], [0.2, 0.8, 0.8]),
277            MeshVertex::new([-1.0, 1.0, 1.0], [-1.0, 0.0, 0.0], [1.0, 1.0], [0.2, 0.8, 0.8]),
278            MeshVertex::new([-1.0, 1.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 1.0], [0.2, 0.8, 0.8]),
279        ];
280        
281        let indices = vec![
282            // Front face
283            0, 1, 2, 2, 3, 0,
284            // Back face
285            4, 5, 6, 6, 7, 4,
286            // Top face
287            8, 9, 10, 10, 11, 8,
288            // Bottom face
289            12, 13, 14, 14, 15, 12,
290            // Right face
291            16, 17, 18, 18, 19, 16,
292            // Left face
293            20, 21, 22, 22, 23, 20,
294        ];
295        
296        Self::new(vertices, indices, PbrMaterial::default())
297    }
298    
299    /// Create a mesh from point cloud with estimated normals
300    pub fn from_point_cloud(points: &[Point3<f32>], color: [f32; 3]) -> Self {
301        let vertices: Vec<MeshVertex> = points.iter().map(|p| {
302            // Simple normal estimation (could use the GPU normal computation)
303            let normal = [0.0, 0.0, 1.0]; // Default normal
304            MeshVertex::new([p.x, p.y, p.z], normal, [0.0, 0.0], color)
305        }).collect();
306        
307        // Create indices for point rendering (each point is a degenerate triangle)
308        let indices: Vec<u32> = (0..vertices.len() as u32).collect();
309        
310        Self::new(vertices, indices, PbrMaterial::default())
311    }
312}
313
314/// Shading mode for mesh rendering
315#[derive(Debug, Clone, Copy, PartialEq)]
316pub enum ShadingMode {
317    Flat,
318    Pbr,
319}
320
321/// GPU-accelerated mesh renderer with PBR and flat shading
322pub struct MeshRenderer<'window> {
323    pub gpu_context: GpuContext,
324    pub surface: wgpu::Surface<'window>,
325    pub surface_config: wgpu::SurfaceConfiguration,
326    pub pbr_pipeline: wgpu::RenderPipeline,
327    pub flat_pipeline: wgpu::RenderPipeline,
328    pub camera_uniform: MeshCameraUniform,
329    pub camera_buffer: wgpu::Buffer,
330    pub lighting_params: MeshLightingParams,
331    pub lighting_buffer: wgpu::Buffer,
332    pub bind_group_layout: wgpu::BindGroupLayout,
333    pub config: MeshRenderConfig,
334    pub msaa_texture: Option<wgpu::Texture>,
335    pub msaa_view: Option<wgpu::TextureView>,
336}
337
338impl<'window> MeshRenderer<'window> {
339    /// Create new mesh renderer with PBR and flat shading support
340    pub async fn new(window: &'window Window, config: MeshRenderConfig) -> Result<Self> {
341        let gpu_context = GpuContext::new().await?;
342        
343        let surface = gpu_context.instance.create_surface(window)
344            .map_err(|e| Error::Gpu(format!("Failed to create surface: {:?}", e)))?;
345
346        let surface_caps = surface.get_capabilities(&gpu_context.adapter);
347        let surface_format = surface_caps.formats.iter()
348            .copied()
349            .find(|f| f.is_srgb())
350            .unwrap_or(surface_caps.formats[0]);
351
352        let size = window.inner_size();
353        let sample_count = if config.enable_multisampling { 4 } else { 1 };
354        
355        let surface_config = wgpu::SurfaceConfiguration {
356            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
357            format: surface_format,
358            width: size.width,
359            height: size.height,
360            present_mode: surface_caps.present_modes[0],
361            alpha_mode: surface_caps.alpha_modes[0],
362            view_formats: vec![],
363            desired_maximum_frame_latency: 2,
364        };
365        surface.configure(&gpu_context.device, &surface_config);
366
367        // Create MSAA texture if enabled
368        let (msaa_texture, msaa_view) = if config.enable_multisampling {
369            let msaa_texture = gpu_context.device.create_texture(&wgpu::TextureDescriptor {
370                label: Some("MSAA Texture"),
371                size: wgpu::Extent3d {
372                    width: size.width,
373                    height: size.height,
374                    depth_or_array_layers: 1,
375                },
376                mip_level_count: 1,
377                sample_count,
378                dimension: wgpu::TextureDimension::D2,
379                format: surface_format,
380                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
381                view_formats: &[],
382            });
383            let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
384            (Some(msaa_texture), Some(msaa_view))
385        } else {
386            (None, None)
387        };
388
389        // Create camera uniform
390        let camera_uniform = MeshCameraUniform {
391            view_proj: Matrix4::identity().into(),
392            view_pos: [0.0, 0.0, 0.0],
393            _padding: 0.0,
394        };
395
396        let camera_buffer = gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
397            label: Some("Camera Buffer"),
398            contents: bytemuck::bytes_of(&camera_uniform),
399            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
400        });
401
402        // Create lighting parameters buffer
403        let lighting_params = config.lighting_params;
404        let lighting_buffer = gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
405            label: Some("Lighting Buffer"),
406            contents: bytemuck::bytes_of(&lighting_params),
407            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
408        });
409
410        // Create bind group layout
411        let bind_group_layout = gpu_context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
412            entries: &[
413                wgpu::BindGroupLayoutEntry {
414                    binding: 0,
415                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
416                    ty: wgpu::BindingType::Buffer {
417                        ty: wgpu::BufferBindingType::Uniform,
418                        has_dynamic_offset: false,
419                        min_binding_size: None,
420                    },
421                    count: None,
422                },
423                wgpu::BindGroupLayoutEntry {
424                    binding: 1,
425                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
426                    ty: wgpu::BindingType::Buffer {
427                        ty: wgpu::BufferBindingType::Uniform,
428                        has_dynamic_offset: false,
429                        min_binding_size: None,
430                    },
431                    count: None,
432                },
433                wgpu::BindGroupLayoutEntry {
434                    binding: 2,
435                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
436                    ty: wgpu::BindingType::Buffer {
437                        ty: wgpu::BufferBindingType::Uniform,
438                        has_dynamic_offset: false,
439                        min_binding_size: None,
440                    },
441                    count: None,
442                },
443            ],
444            label: Some("mesh_bind_group_layout"),
445        });
446
447        // Create PBR pipeline
448        let pbr_shader = gpu_context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
449            label: Some("PBR Mesh Shader"),
450            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mesh_pbr.wgsl").into()),
451        });
452
453        let pbr_pipeline = Self::create_render_pipeline(
454            &gpu_context.device,
455            &bind_group_layout,
456            &pbr_shader,
457            surface_format,
458            sample_count,
459            &config,
460            "PBR",
461        );
462
463        // Create flat pipeline
464        let flat_shader = gpu_context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
465            label: Some("Flat Mesh Shader"),
466            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mesh_flat.wgsl").into()),
467        });
468
469        let flat_pipeline = Self::create_render_pipeline(
470            &gpu_context.device,
471            &bind_group_layout,
472            &flat_shader,
473            surface_format,
474            sample_count,
475            &config,
476            "Flat",
477        );
478
479        Ok(Self {
480            gpu_context,
481            surface,
482            surface_config,
483            pbr_pipeline,
484            flat_pipeline,
485            camera_uniform,
486            camera_buffer,
487            lighting_params,
488            lighting_buffer,
489            bind_group_layout,
490            config,
491            msaa_texture,
492            msaa_view,
493        })
494    }
495
496    /// Create a render pipeline for mesh rendering
497    fn create_render_pipeline(
498        device: &wgpu::Device,
499        bind_group_layout: &wgpu::BindGroupLayout,
500        shader: &wgpu::ShaderModule,
501        surface_format: wgpu::TextureFormat,
502        sample_count: u32,
503        config: &MeshRenderConfig,
504        label: &str,
505    ) -> wgpu::RenderPipeline {
506        let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
507            label: Some(&format!("{} Mesh Render Pipeline Layout", label)),
508            bind_group_layouts: &[bind_group_layout],
509            immediate_size: 0,
510        });
511
512        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
513            label: Some(&format!("{} Mesh Render Pipeline", label)),
514            layout: Some(&render_pipeline_layout),
515            vertex: wgpu::VertexState {
516                module: shader,
517                entry_point: Some("vs_main"),
518                buffers: &[MeshVertex::desc()],
519                compilation_options: wgpu::PipelineCompilationOptions::default(),
520            },
521            fragment: Some(wgpu::FragmentState {
522                module: shader,
523                entry_point: Some("fs_main"),
524                targets: &[Some(wgpu::ColorTargetState {
525                    format: surface_format,
526                    blend: Some(wgpu::BlendState::REPLACE),
527                    write_mask: wgpu::ColorWrites::ALL,
528                })],
529                compilation_options: wgpu::PipelineCompilationOptions::default(),
530            }),
531            primitive: wgpu::PrimitiveState {
532                topology: if config.wireframe_mode {
533                    wgpu::PrimitiveTopology::LineList
534                } else {
535                    wgpu::PrimitiveTopology::TriangleList
536                },
537                strip_index_format: None,
538                front_face: wgpu::FrontFace::Ccw,
539                cull_mode: if config.enable_backface_culling {
540                    Some(wgpu::Face::Back)
541                } else {
542                    None
543                },
544                unclipped_depth: false,
545                polygon_mode: wgpu::PolygonMode::Fill,
546                conservative: false,
547            },
548            depth_stencil: if config.enable_depth_test {
549                Some(wgpu::DepthStencilState {
550                    format: wgpu::TextureFormat::Depth32Float,
551                    depth_write_enabled: true,
552                    depth_compare: wgpu::CompareFunction::Less,
553                    stencil: wgpu::StencilState::default(),
554                    bias: wgpu::DepthBiasState::default(),
555                })
556            } else {
557                None
558            },
559            multisample: wgpu::MultisampleState {
560                count: sample_count,
561                mask: !0,
562                alpha_to_coverage_enabled: false,
563            },
564            multiview_mask: None,
565            cache: None,
566        })
567    }
568
569    /// Update camera matrices and position
570    pub fn update_camera(&mut self, view_matrix: Matrix4<f32>, proj_matrix: Matrix4<f32>, camera_pos: Vector3<f32>) {
571        self.camera_uniform.view_proj = (proj_matrix * view_matrix).into();
572        self.camera_uniform.view_pos = camera_pos.into();
573        
574        self.gpu_context.queue.write_buffer(
575            &self.camera_buffer,
576            0,
577            bytemuck::bytes_of(&self.camera_uniform),
578        );
579    }
580
581    /// Update lighting parameters
582    pub fn update_lighting(&mut self, params: MeshLightingParams) {
583        self.lighting_params = params;
584        self.gpu_context.queue.write_buffer(
585            &self.lighting_buffer,
586            0,
587            bytemuck::bytes_of(&self.lighting_params),
588        );
589    }
590
591    /// Resize renderer
592    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
593        if new_size.width > 0 && new_size.height > 0 {
594            self.surface_config.width = new_size.width;
595            self.surface_config.height = new_size.height;
596            self.surface.configure(&self.gpu_context.device, &self.surface_config);
597            
598            // Recreate MSAA texture if needed
599            if self.config.enable_multisampling {
600                let msaa_texture = self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
601                    label: Some("MSAA Texture"),
602                    size: wgpu::Extent3d {
603                        width: new_size.width,
604                        height: new_size.height,
605                        depth_or_array_layers: 1,
606                    },
607                    mip_level_count: 1,
608                    sample_count: 4,
609                    dimension: wgpu::TextureDimension::D2,
610                    format: self.surface_config.format,
611                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
612                    view_formats: &[],
613                });
614                let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
615                self.msaa_texture = Some(msaa_texture);
616                self.msaa_view = Some(msaa_view);
617            }
618        }
619    }
620
621    /// Create vertex buffer
622    pub fn create_vertex_buffer(&self, vertices: &[MeshVertex]) -> wgpu::Buffer {
623        self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
624            label: Some("Mesh Vertex Buffer"),
625            contents: bytemuck::cast_slice(vertices),
626            usage: wgpu::BufferUsages::VERTEX,
627        })
628    }
629
630    /// Create index buffer
631    pub fn create_index_buffer(&self, indices: &[u32]) -> wgpu::Buffer {
632        self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
633            label: Some("Mesh Index Buffer"),
634            contents: bytemuck::cast_slice(indices),
635            usage: wgpu::BufferUsages::INDEX,
636        })
637    }
638
639    /// Create depth texture
640    pub fn create_depth_texture(&self) -> wgpu::Texture {
641        let sample_count = if self.config.enable_multisampling { 4 } else { 1 };
642        
643        self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
644            label: Some("Depth Texture"),
645            size: wgpu::Extent3d {
646                width: self.surface_config.width,
647                height: self.surface_config.height,
648                depth_or_array_layers: 1,
649            },
650            mip_level_count: 1,
651            sample_count,
652            dimension: wgpu::TextureDimension::D2,
653            format: wgpu::TextureFormat::Depth32Float,
654            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
655            view_formats: &[],
656        })
657    }
658
659    /// Render mesh with specified shading mode
660    pub fn render(&self, mesh: &GpuMesh, shading_mode: ShadingMode) -> Result<()> {
661        let vertex_buffer = self.create_vertex_buffer(&mesh.vertices);
662        let index_buffer = self.create_index_buffer(&mesh.indices);
663        let depth_texture = self.create_depth_texture();
664        let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
665
666        let output = self.surface.get_current_texture()
667            .map_err(|e| Error::Gpu(format!("Failed to get surface texture: {:?}", e)))?;
668        
669        let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
670
671        // Create material buffer based on shading mode
672        let material_buffer = match shading_mode {
673            ShadingMode::Pbr => {
674                self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
675                    label: Some("PBR Material Buffer"),
676                    contents: bytemuck::bytes_of(&mesh.material),
677                    usage: wgpu::BufferUsages::UNIFORM,
678                })
679            }
680            ShadingMode::Flat => {
681                let flat_material = FlatMaterial {
682                    color: mesh.material.albedo,
683                    _padding: 0.0,
684                };
685                self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
686                    label: Some("Flat Material Buffer"),
687                    contents: bytemuck::bytes_of(&flat_material),
688                    usage: wgpu::BufferUsages::UNIFORM,
689                })
690            }
691        };
692
693        let bind_group = self.gpu_context.device.create_bind_group(&wgpu::BindGroupDescriptor {
694            layout: &self.bind_group_layout,
695            entries: &[
696                wgpu::BindGroupEntry {
697                    binding: 0,
698                    resource: self.camera_buffer.as_entire_binding(),
699                },
700                wgpu::BindGroupEntry {
701                    binding: 1,
702                    resource: material_buffer.as_entire_binding(),
703                },
704                wgpu::BindGroupEntry {
705                    binding: 2,
706                    resource: self.lighting_buffer.as_entire_binding(),
707                },
708            ],
709            label: Some("mesh_bind_group"),
710        });
711
712        let mut encoder = self.gpu_context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
713            label: Some("Mesh Render Encoder"),
714        });
715
716        // Determine render target
717        let (color_attachment, resolve_target) = if let Some(ref msaa_view) = self.msaa_view {
718            (msaa_view, Some(&view))
719        } else {
720            (&view, None)
721        };
722
723        {
724            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
725                label: Some("Mesh Render Pass"),
726                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
727                    view: color_attachment,
728                    resolve_target,
729                    ops: wgpu::Operations {
730                        load: wgpu::LoadOp::Clear(wgpu::Color {
731                            r: self.config.background_color[0],
732                            g: self.config.background_color[1],
733                            b: self.config.background_color[2],
734                            a: self.config.background_color[3],
735                        }),
736                        store: wgpu::StoreOp::Store,
737                    },
738                    depth_slice: None,
739                })],
740                depth_stencil_attachment: if self.config.enable_depth_test {
741                    Some(wgpu::RenderPassDepthStencilAttachment {
742                        view: &depth_view,
743                        depth_ops: Some(wgpu::Operations {
744                            load: wgpu::LoadOp::Clear(1.0),
745                            store: wgpu::StoreOp::Store,
746                        }),
747                        stencil_ops: None,
748                    })
749                } else {
750                    None
751                },
752                timestamp_writes: None,
753                occlusion_query_set: None,
754                multiview_mask: None,
755            });
756
757            let pipeline = match shading_mode {
758                ShadingMode::Pbr => &self.pbr_pipeline,
759                ShadingMode::Flat => &self.flat_pipeline,
760            };
761
762            render_pass.set_pipeline(pipeline);
763            render_pass.set_bind_group(0, &bind_group, &[]);
764            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
765            render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
766            render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
767        }
768
769        self.gpu_context.queue.submit(std::iter::once(encoder.finish()));
770        output.present();
771
772        Ok(())
773    }
774}
775
776/// Convert threecrate mesh to GPU mesh format
777pub fn mesh_to_gpu_mesh(
778    vertices: &[Point3<f32>],
779    indices: &[u32],
780    normals: Option<&[Vector3<f32>]>,
781    colors: Option<&[[f32; 3]]>,
782    material: Option<PbrMaterial>,
783) -> GpuMesh {
784    let gpu_vertices: Vec<MeshVertex> = vertices
785        .iter()
786        .enumerate()
787        .map(|(i, vertex)| {
788            let normal = normals
789                .and_then(|n| n.get(i))
790                .map(|n| [n.x, n.y, n.z])
791                .unwrap_or([0.0, 0.0, 1.0]);
792            
793            let color = colors
794                .and_then(|c| c.get(i))
795                .copied()
796                .unwrap_or([0.8, 0.8, 0.8]);
797            
798            MeshVertex::new([vertex.x, vertex.y, vertex.z], normal, [0.0, 0.0], color)
799        })
800        .collect();
801
802    GpuMesh::new(
803        gpu_vertices,
804        indices.to_vec(),
805        material.unwrap_or_default(),
806    )
807}
808
809/// Pre-computed LOD levels for a mesh, generated from a progressive mesh.
810///
811/// Levels are ordered from coarsest (index 0) to finest (last index).
812/// Use `select_level` to pick the appropriate LOD for a given camera distance.
813pub struct LodMesh {
814    /// GPU meshes at each LOD level, index 0 = coarsest
815    pub levels: Vec<GpuMesh>,
816    /// Distance thresholds for switching between levels.
817    /// `thresholds[i]` is the maximum distance at which `levels[i+1]` should be used.
818    pub thresholds: Vec<f32>,
819}
820
821impl LodMesh {
822    /// Create LOD levels by sampling the progressive mesh at evenly-spaced detail ratios.
823    ///
824    /// `num_levels` is the total number of LOD levels to generate (minimum 2).
825    pub fn from_progressive_mesh(pm: &ProgressiveMesh, num_levels: usize) -> Self {
826        let num_levels = num_levels.max(2);
827
828        let levels: Vec<GpuMesh> = (0..num_levels)
829            .map(|i| {
830                let ratio = i as f32 / (num_levels - 1) as f32;
831                let mesh = pm.reconstruct_at_ratio(ratio);
832                triangle_mesh_to_gpu_mesh(&mesh)
833            })
834            .collect();
835
836        // Generate default distance thresholds (evenly spaced)
837        let thresholds: Vec<f32> = (0..num_levels.saturating_sub(1))
838            .map(|i| {
839                let t = (num_levels - 1 - i) as f32 / (num_levels - 1) as f32;
840                t * 100.0
841            })
842            .collect();
843
844        LodMesh { levels, thresholds }
845    }
846
847    /// Select the appropriate LOD level for a given camera distance.
848    ///
849    /// Returns the coarsest mesh that still looks acceptable at the given distance.
850    /// Closer distances get finer detail; farther distances get coarser meshes.
851    pub fn select_level(&self, distance: f32) -> &GpuMesh {
852        for (i, &threshold) in self.thresholds.iter().enumerate() {
853            if distance > threshold {
854                return &self.levels[i];
855            }
856        }
857        // Closest distance: return finest level
858        self.levels.last().unwrap_or(&self.levels[0])
859    }
860
861    /// Number of LOD levels.
862    pub fn num_levels(&self) -> usize {
863        self.levels.len()
864    }
865}
866
867/// Convert a `TriangleMesh` to a `GpuMesh` with default material.
868fn triangle_mesh_to_gpu_mesh(mesh: &threecrate_core::TriangleMesh) -> GpuMesh {
869    let normals_slice = mesh.normals.as_deref();
870    let colors_f32: Option<Vec<[f32; 3]>> = mesh.colors.as_ref().map(|colors| {
871        colors
872            .iter()
873            .map(|c| [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0])
874            .collect()
875    });
876
877    let indices: Vec<u32> = mesh
878        .faces
879        .iter()
880        .flat_map(|f| [f[0] as u32, f[1] as u32, f[2] as u32])
881        .collect();
882
883    mesh_to_gpu_mesh(
884        &mesh.vertices,
885        &indices,
886        normals_slice,
887        colors_f32.as_deref(),
888        None,
889    )
890}