1use super::camera::{Camera3D, CameraController};
6use super::slice::SliceRenderer;
7use super::volume::VolumeRenderer;
8use super::{CameraUniform, ColorMap, GridLines, HeadWireframe, MarkerSphere, Vertex3D};
9use crate::simulation::physics::Position3D;
10use wgpu::util::DeviceExt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum VisualizationMode {
15 SingleSlice,
17 MultiSlice,
19 #[default]
21 VolumeRender,
22 Isosurface,
24}
25
26#[derive(Debug, Clone)]
28pub struct RenderConfig {
29 pub mode: VisualizationMode,
31 pub color_map: ColorMap,
33 pub background_color: [f32; 4],
35 pub show_bounding_box: bool,
37 pub show_floor_grid: bool,
39 pub show_sources: bool,
41 pub show_listener: bool,
43 pub slice_opacity: f32,
45 pub auto_scale: bool,
47 pub max_pressure: f32,
49}
50
51impl Default for RenderConfig {
52 fn default() -> Self {
53 Self {
54 mode: VisualizationMode::VolumeRender,
55 color_map: ColorMap::BlueWhiteRed,
56 background_color: [0.1, 0.1, 0.15, 1.0],
57 show_bounding_box: true,
58 show_floor_grid: true,
59 show_sources: true,
60 show_listener: true,
61 slice_opacity: 0.85,
62 auto_scale: true,
63 max_pressure: 1.0,
64 }
65 }
66}
67
68const SHADER_SOURCE: &str = r#"
70struct CameraUniform {
71 view_proj: mat4x4<f32>,
72 view: mat4x4<f32>,
73 camera_pos: vec4<f32>,
74 grid_size: vec4<f32>,
75}
76
77@group(0) @binding(0)
78var<uniform> camera: CameraUniform;
79
80struct VertexInput {
81 @location(0) position: vec3<f32>,
82 @location(1) color: vec4<f32>,
83 @location(2) normal: vec3<f32>,
84 @location(3) tex_coord: vec2<f32>,
85}
86
87struct VertexOutput {
88 @builtin(position) clip_position: vec4<f32>,
89 @location(0) color: vec4<f32>,
90 @location(1) world_pos: vec3<f32>,
91 @location(2) normal: vec3<f32>,
92}
93
94@vertex
95fn vs_main(in: VertexInput) -> VertexOutput {
96 var out: VertexOutput;
97 out.clip_position = camera.view_proj * vec4<f32>(in.position, 1.0);
98 out.color = in.color;
99 out.world_pos = in.position;
100 out.normal = in.normal;
101 return out;
102}
103
104@fragment
105fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
106 // Simple lighting
107 let light_dir = normalize(vec3<f32>(0.5, 1.0, 0.3));
108 let normal = normalize(in.normal);
109 let diffuse = max(dot(normal, light_dir), 0.3);
110
111 var color = in.color;
112 color = vec4<f32>(color.rgb * diffuse, color.a);
113
114 return color;
115}
116
117@fragment
118fn fs_main_line(in: VertexOutput) -> @location(0) vec4<f32> {
119 // No lighting for lines
120 return in.color;
121}
122"#;
123
124pub struct Renderer3D {
126 device: wgpu::Device,
128 queue: wgpu::Queue,
130 surface_config: wgpu::SurfaceConfiguration,
132 surface: wgpu::Surface<'static>,
134 triangle_pipeline: wgpu::RenderPipeline,
136 line_pipeline: wgpu::RenderPipeline,
138 camera_buffer: wgpu::Buffer,
140 camera_bind_group: wgpu::BindGroup,
142 depth_texture_view: wgpu::TextureView,
144 slice_buffer: Option<wgpu::Buffer>,
146 slice_vertex_count: u32,
148 line_buffer: Option<wgpu::Buffer>,
150 line_vertex_count: u32,
152 marker_buffer: Option<wgpu::Buffer>,
154 marker_vertex_count: u32,
156 volume_renderer: Option<VolumeRenderer>,
158 #[allow(dead_code)]
160 grid_dimensions: (usize, usize, usize),
161 #[allow(dead_code)]
163 camera_bind_group_layout: wgpu::BindGroupLayout,
164 pub camera: Camera3D,
166 pub camera_controller: CameraController,
168 pub slice_renderer: SliceRenderer,
170 pub config: RenderConfig,
172 grid_size: (f32, f32, f32),
174 sources: Vec<MarkerSphere>,
176 listener: Option<HeadWireframe>,
178}
179
180impl Renderer3D {
181 pub async fn new(
183 window: &winit::window::Window,
184 grid_size: (f32, f32, f32),
185 grid_dimensions: (usize, usize, usize),
186 ) -> Result<Self, RendererError> {
187 let size = window.inner_size();
188
189 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
191 backends: wgpu::Backends::all(),
192 ..Default::default()
193 });
194
195 let surface = unsafe {
197 instance
198 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(window).unwrap())
199 .map_err(|e| RendererError::SurfaceError(e.to_string()))?
200 };
201
202 let adapter = instance
204 .request_adapter(&wgpu::RequestAdapterOptions {
205 power_preference: wgpu::PowerPreference::HighPerformance,
206 compatible_surface: Some(&surface),
207 force_fallback_adapter: false,
208 })
209 .await
210 .ok_or_else(|| RendererError::AdapterError("No adapter found".into()))?;
211
212 let (device, queue) = adapter
214 .request_device(
215 &wgpu::DeviceDescriptor {
216 required_features: wgpu::Features::empty(),
217 required_limits: wgpu::Limits::default(),
218 label: Some("wavesim3d_device"),
219 },
220 None,
221 )
222 .await
223 .map_err(|e| RendererError::DeviceError(e.to_string()))?;
224
225 let surface_caps = surface.get_capabilities(&adapter);
227 let surface_format = surface_caps
228 .formats
229 .iter()
230 .find(|f| f.is_srgb())
231 .copied()
232 .unwrap_or(surface_caps.formats[0]);
233
234 let surface_config = wgpu::SurfaceConfiguration {
235 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
236 format: surface_format,
237 width: size.width.max(1),
238 height: size.height.max(1),
239 present_mode: wgpu::PresentMode::AutoVsync,
240 alpha_mode: surface_caps.alpha_modes[0],
241 view_formats: vec![],
242 desired_maximum_frame_latency: 2,
243 };
244 surface.configure(&device, &surface_config);
245
246 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
248 label: Some("wavesim3d_shader"),
249 source: wgpu::ShaderSource::Wgsl(SHADER_SOURCE.into()),
250 });
251
252 let camera_uniform = CameraUniform::new();
254 let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
255 label: Some("camera_buffer"),
256 contents: bytemuck::cast_slice(&[camera_uniform]),
257 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
258 });
259
260 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
262 label: Some("camera_bind_group_layout"),
263 entries: &[wgpu::BindGroupLayoutEntry {
264 binding: 0,
265 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
266 ty: wgpu::BindingType::Buffer {
267 ty: wgpu::BufferBindingType::Uniform,
268 has_dynamic_offset: false,
269 min_binding_size: None,
270 },
271 count: None,
272 }],
273 });
274
275 let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
277 label: Some("camera_bind_group"),
278 layout: &bind_group_layout,
279 entries: &[wgpu::BindGroupEntry {
280 binding: 0,
281 resource: camera_buffer.as_entire_binding(),
282 }],
283 });
284
285 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
287 label: Some("render_pipeline_layout"),
288 bind_group_layouts: &[&bind_group_layout],
289 push_constant_ranges: &[],
290 });
291
292 let triangle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
294 label: Some("triangle_pipeline"),
295 layout: Some(&pipeline_layout),
296 vertex: wgpu::VertexState {
297 module: &shader,
298 entry_point: "vs_main",
299 buffers: &[Vertex3D::desc()],
300 },
301 fragment: Some(wgpu::FragmentState {
302 module: &shader,
303 entry_point: "fs_main",
304 targets: &[Some(wgpu::ColorTargetState {
305 format: surface_config.format,
306 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
307 write_mask: wgpu::ColorWrites::ALL,
308 })],
309 }),
310 primitive: wgpu::PrimitiveState {
311 topology: wgpu::PrimitiveTopology::TriangleList,
312 strip_index_format: None,
313 front_face: wgpu::FrontFace::Ccw,
314 cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
316 unclipped_depth: false,
317 conservative: false,
318 },
319 depth_stencil: Some(wgpu::DepthStencilState {
320 format: wgpu::TextureFormat::Depth32Float,
321 depth_write_enabled: true,
322 depth_compare: wgpu::CompareFunction::Less,
323 stencil: wgpu::StencilState::default(),
324 bias: wgpu::DepthBiasState::default(),
325 }),
326 multisample: wgpu::MultisampleState {
327 count: 1,
328 mask: !0,
329 alpha_to_coverage_enabled: false,
330 },
331 multiview: None,
332 });
333
334 let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
336 label: Some("line_pipeline"),
337 layout: Some(&pipeline_layout),
338 vertex: wgpu::VertexState {
339 module: &shader,
340 entry_point: "vs_main",
341 buffers: &[Vertex3D::desc()],
342 },
343 fragment: Some(wgpu::FragmentState {
344 module: &shader,
345 entry_point: "fs_main_line",
346 targets: &[Some(wgpu::ColorTargetState {
347 format: surface_config.format,
348 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
349 write_mask: wgpu::ColorWrites::ALL,
350 })],
351 }),
352 primitive: wgpu::PrimitiveState {
353 topology: wgpu::PrimitiveTopology::LineList,
354 ..Default::default()
355 },
356 depth_stencil: Some(wgpu::DepthStencilState {
357 format: wgpu::TextureFormat::Depth32Float,
358 depth_write_enabled: true,
359 depth_compare: wgpu::CompareFunction::Less,
360 stencil: wgpu::StencilState::default(),
361 bias: wgpu::DepthBiasState::default(),
362 }),
363 multisample: wgpu::MultisampleState::default(),
364 multiview: None,
365 });
366
367 let mut camera = Camera3D::for_grid(grid_size);
369 camera.set_aspect(size.width as f32 / size.height as f32);
370 let camera_controller = CameraController::from_camera(&camera);
371
372 let depth_texture_view = Self::create_depth_texture_static(&device, &surface_config);
374
375 let volume_renderer = VolumeRenderer::new(
377 &device,
378 surface_config.format,
379 &bind_group_layout,
380 grid_dimensions,
381 );
382
383 Ok(Self {
384 device,
385 queue,
386 surface_config,
387 surface,
388 triangle_pipeline,
389 line_pipeline,
390 camera_buffer,
391 camera_bind_group,
392 depth_texture_view,
393 slice_buffer: None,
394 slice_vertex_count: 0,
395 line_buffer: None,
396 line_vertex_count: 0,
397 marker_buffer: None,
398 marker_vertex_count: 0,
399 volume_renderer: Some(volume_renderer),
400 grid_dimensions,
401 camera_bind_group_layout: bind_group_layout,
402 camera,
403 camera_controller,
404 slice_renderer: SliceRenderer::new(),
405 config: RenderConfig::default(),
406 grid_size,
407 sources: Vec::new(),
408 listener: None,
409 })
410 }
411
412 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
414 if new_size.width > 0 && new_size.height > 0 {
415 self.surface_config.width = new_size.width;
416 self.surface_config.height = new_size.height;
417 self.surface.configure(&self.device, &self.surface_config);
418 self.camera
419 .set_aspect(new_size.width as f32 / new_size.height as f32);
420 self.depth_texture_view =
422 Self::create_depth_texture_static(&self.device, &self.surface_config);
423 }
424 }
425
426 pub fn add_source(&mut self, position: Position3D, color: [f32; 4]) {
428 self.sources.push(MarkerSphere::new(position, 0.1, color));
429 }
430
431 pub fn clear_sources(&mut self) {
433 self.sources.clear();
434 }
435
436 pub fn set_listener(&mut self, position: Position3D, scale: f32) {
438 self.listener = Some(HeadWireframe::new(position, scale));
439 }
440
441 pub fn clear_listener(&mut self) {
443 self.listener = None;
444 }
445
446 fn update_camera_uniform(&self) {
448 let mut uniform = CameraUniform::new();
449 uniform.update(&self.camera, self.grid_size);
450 self.queue
451 .write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[uniform]));
452 }
453
454 fn create_depth_texture_static(
456 device: &wgpu::Device,
457 surface_config: &wgpu::SurfaceConfiguration,
458 ) -> wgpu::TextureView {
459 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
460 label: Some("depth_texture"),
461 size: wgpu::Extent3d {
462 width: surface_config.width,
463 height: surface_config.height,
464 depth_or_array_layers: 1,
465 },
466 mip_level_count: 1,
467 sample_count: 1,
468 dimension: wgpu::TextureDimension::D2,
469 format: wgpu::TextureFormat::Depth32Float,
470 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
471 view_formats: &[],
472 });
473
474 depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
475 }
476
477 fn update_vertex_buffer_static(
479 device: &wgpu::Device,
480 queue: &wgpu::Queue,
481 vertices: &[Vertex3D],
482 buffer: &mut Option<wgpu::Buffer>,
483 vertex_count: &mut u32,
484 label: &str,
485 ) {
486 let new_count = vertices.len() as u32;
487 let data = bytemuck::cast_slice(vertices);
488 let required_size = data.len() as u64;
489
490 let needs_recreate = match buffer {
492 Some(ref existing) => existing.size() < required_size,
493 None => true,
494 };
495
496 if needs_recreate && !vertices.is_empty() {
497 let alloc_size = (required_size as f64 * 1.5) as u64;
499 *buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
500 label: Some(label),
501 size: alloc_size,
502 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
503 mapped_at_creation: false,
504 }));
505 }
506
507 if let Some(ref buf) = buffer {
509 if !vertices.is_empty() {
510 queue.write_buffer(buf, 0, data);
511 }
512 }
513
514 *vertex_count = new_count;
515 }
516
517 pub fn render(
519 &mut self,
520 grid: &crate::simulation::SimulationGrid3D,
521 ) -> Result<(), RendererError> {
522 let output = self
524 .surface
525 .get_current_texture()
526 .map_err(|e| RendererError::SurfaceError(e.to_string()))?;
527
528 let view = output
529 .texture
530 .create_view(&wgpu::TextureViewDescriptor::default());
531
532 self.update_camera_uniform();
534
535 if self.config.auto_scale {
537 self.slice_renderer.set_max_pressure(grid.max_pressure());
538 } else {
539 self.slice_renderer
540 .set_max_pressure(self.config.max_pressure);
541 }
542
543 let slice_vertices = self.slice_renderer.generate_vertices(grid, self.grid_size);
545
546 let mut line_vertices = Vec::new();
547
548 if self.config.show_bounding_box {
550 let grid_lines = GridLines::new(self.grid_size);
551 line_vertices.extend(grid_lines.generate_box());
552 }
553
554 if self.config.show_floor_grid {
556 let grid_lines = GridLines::new(self.grid_size);
557 line_vertices.extend(grid_lines.generate_floor_grid());
558 }
559
560 let mut marker_vertices = Vec::new();
562 if self.config.show_sources {
563 for source in &self.sources {
564 marker_vertices.extend(source.generate_vertices(16));
565 }
566 }
567
568 if self.config.show_listener {
570 if let Some(ref head) = self.listener {
571 line_vertices.extend(head.generate_vertices());
572 }
573 }
574
575 Self::update_vertex_buffer_static(
577 &self.device,
578 &self.queue,
579 &slice_vertices,
580 &mut self.slice_buffer,
581 &mut self.slice_vertex_count,
582 "slice_vertex_buffer",
583 );
584 Self::update_vertex_buffer_static(
585 &self.device,
586 &self.queue,
587 &line_vertices,
588 &mut self.line_buffer,
589 &mut self.line_vertex_count,
590 "line_vertex_buffer",
591 );
592 Self::update_vertex_buffer_static(
593 &self.device,
594 &self.queue,
595 &marker_vertices,
596 &mut self.marker_buffer,
597 &mut self.marker_vertex_count,
598 "marker_vertex_buffer",
599 );
600
601 let mut encoder = self
603 .device
604 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
605 label: Some("render_encoder"),
606 });
607
608 if self.config.mode == VisualizationMode::VolumeRender {
610 if let Some(ref mut volume_renderer) = self.volume_renderer {
611 volume_renderer.update_volume(&self.queue, grid);
612 volume_renderer.update_params(&self.queue);
613 }
614 }
615
616 {
617 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
618 label: Some("render_pass"),
619 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
620 view: &view,
621 resolve_target: None,
622 ops: wgpu::Operations {
623 load: wgpu::LoadOp::Clear(wgpu::Color {
624 r: self.config.background_color[0] as f64,
625 g: self.config.background_color[1] as f64,
626 b: self.config.background_color[2] as f64,
627 a: self.config.background_color[3] as f64,
628 }),
629 store: wgpu::StoreOp::Store,
630 },
631 })],
632 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
633 view: &self.depth_texture_view,
634 depth_ops: Some(wgpu::Operations {
635 load: wgpu::LoadOp::Clear(1.0),
636 store: wgpu::StoreOp::Store,
637 }),
638 stencil_ops: None,
639 }),
640 timestamp_writes: None,
641 occlusion_query_set: None,
642 });
643
644 match self.config.mode {
646 VisualizationMode::VolumeRender => {
647 if let Some(ref volume_renderer) = self.volume_renderer {
649 volume_renderer.render(&mut render_pass, &self.camera_bind_group);
650 }
651 }
652 _ => {
653 if self.slice_vertex_count > 0 {
655 if let Some(ref buffer) = self.slice_buffer {
656 render_pass.set_pipeline(&self.triangle_pipeline);
657 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
658 render_pass.set_vertex_buffer(0, buffer.slice(..));
659 render_pass.draw(0..self.slice_vertex_count, 0..1);
660 }
661 }
662 }
663 }
664
665 if self.marker_vertex_count > 0 {
667 if let Some(ref buffer) = self.marker_buffer {
668 render_pass.set_pipeline(&self.triangle_pipeline);
669 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
670 render_pass.set_vertex_buffer(0, buffer.slice(..));
671 render_pass.draw(0..self.marker_vertex_count, 0..1);
672 }
673 }
674
675 if self.line_vertex_count > 0 {
677 if let Some(ref buffer) = self.line_buffer {
678 render_pass.set_pipeline(&self.line_pipeline);
679 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
680 render_pass.set_vertex_buffer(0, buffer.slice(..));
681 render_pass.draw(0..self.line_vertex_count, 0..1);
682 }
683 }
684 }
685
686 self.queue.submit(std::iter::once(encoder.finish()));
687 output.present();
688
689 Ok(())
690 }
691
692 pub fn device(&self) -> &wgpu::Device {
694 &self.device
695 }
696
697 pub fn queue(&self) -> &wgpu::Queue {
699 &self.queue
700 }
701
702 pub fn surface_format(&self) -> wgpu::TextureFormat {
704 self.surface_config.format
705 }
706}
707
708#[derive(Debug)]
710pub enum RendererError {
711 SurfaceError(String),
712 AdapterError(String),
713 DeviceError(String),
714 ShaderError(String),
715}
716
717impl std::fmt::Display for RendererError {
718 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
719 match self {
720 RendererError::SurfaceError(msg) => write!(f, "Surface error: {}", msg),
721 RendererError::AdapterError(msg) => write!(f, "Adapter error: {}", msg),
722 RendererError::DeviceError(msg) => write!(f, "Device error: {}", msg),
723 RendererError::ShaderError(msg) => write!(f, "Shader error: {}", msg),
724 }
725 }
726}
727
728impl std::error::Error for RendererError {}