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 .map_err(|e| RendererError::AdapterError(e.to_string()))?;
211
212 let (device, queue) = adapter
214 .request_device(&wgpu::DeviceDescriptor {
215 required_features: wgpu::Features::empty(),
216 required_limits: wgpu::Limits::default(),
217 label: Some("wavesim3d_device"),
218 ..Default::default()
219 })
220 .await
221 .map_err(|e| RendererError::DeviceError(e.to_string()))?;
222
223 let surface_caps = surface.get_capabilities(&adapter);
225 let surface_format = surface_caps
226 .formats
227 .iter()
228 .find(|f| f.is_srgb())
229 .copied()
230 .unwrap_or(surface_caps.formats[0]);
231
232 let surface_config = wgpu::SurfaceConfiguration {
233 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
234 format: surface_format,
235 width: size.width.max(1),
236 height: size.height.max(1),
237 present_mode: wgpu::PresentMode::AutoVsync,
238 alpha_mode: surface_caps.alpha_modes[0],
239 view_formats: vec![],
240 desired_maximum_frame_latency: 2,
241 };
242 surface.configure(&device, &surface_config);
243
244 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
246 label: Some("wavesim3d_shader"),
247 source: wgpu::ShaderSource::Wgsl(SHADER_SOURCE.into()),
248 });
249
250 let camera_uniform = CameraUniform::new();
252 let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
253 label: Some("camera_buffer"),
254 contents: bytemuck::cast_slice(&[camera_uniform]),
255 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
256 });
257
258 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
260 label: Some("camera_bind_group_layout"),
261 entries: &[wgpu::BindGroupLayoutEntry {
262 binding: 0,
263 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
264 ty: wgpu::BindingType::Buffer {
265 ty: wgpu::BufferBindingType::Uniform,
266 has_dynamic_offset: false,
267 min_binding_size: None,
268 },
269 count: None,
270 }],
271 });
272
273 let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
275 label: Some("camera_bind_group"),
276 layout: &bind_group_layout,
277 entries: &[wgpu::BindGroupEntry {
278 binding: 0,
279 resource: camera_buffer.as_entire_binding(),
280 }],
281 });
282
283 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
285 label: Some("render_pipeline_layout"),
286 bind_group_layouts: &[&bind_group_layout],
287 push_constant_ranges: &[],
288 });
289
290 let triangle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
292 label: Some("triangle_pipeline"),
293 layout: Some(&pipeline_layout),
294 vertex: wgpu::VertexState {
295 module: &shader,
296 entry_point: Some("vs_main"),
297 compilation_options: Default::default(),
298 buffers: &[Vertex3D::desc()],
299 },
300 fragment: Some(wgpu::FragmentState {
301 module: &shader,
302 entry_point: Some("fs_main"),
303 compilation_options: Default::default(),
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 cache: None,
333 });
334
335 let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
337 label: Some("line_pipeline"),
338 layout: Some(&pipeline_layout),
339 vertex: wgpu::VertexState {
340 module: &shader,
341 entry_point: Some("vs_main"),
342 compilation_options: Default::default(),
343 buffers: &[Vertex3D::desc()],
344 },
345 fragment: Some(wgpu::FragmentState {
346 module: &shader,
347 entry_point: Some("fs_main_line"),
348 compilation_options: Default::default(),
349 targets: &[Some(wgpu::ColorTargetState {
350 format: surface_config.format,
351 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
352 write_mask: wgpu::ColorWrites::ALL,
353 })],
354 }),
355 primitive: wgpu::PrimitiveState {
356 topology: wgpu::PrimitiveTopology::LineList,
357 ..Default::default()
358 },
359 depth_stencil: Some(wgpu::DepthStencilState {
360 format: wgpu::TextureFormat::Depth32Float,
361 depth_write_enabled: true,
362 depth_compare: wgpu::CompareFunction::Less,
363 stencil: wgpu::StencilState::default(),
364 bias: wgpu::DepthBiasState::default(),
365 }),
366 multisample: wgpu::MultisampleState::default(),
367 multiview: None,
368 cache: None,
369 });
370
371 let mut camera = Camera3D::for_grid(grid_size);
373 camera.set_aspect(size.width as f32 / size.height as f32);
374 let camera_controller = CameraController::from_camera(&camera);
375
376 let depth_texture_view = Self::create_depth_texture_static(&device, &surface_config);
378
379 let volume_renderer = VolumeRenderer::new(
381 &device,
382 surface_config.format,
383 &bind_group_layout,
384 grid_dimensions,
385 );
386
387 Ok(Self {
388 device,
389 queue,
390 surface_config,
391 surface,
392 triangle_pipeline,
393 line_pipeline,
394 camera_buffer,
395 camera_bind_group,
396 depth_texture_view,
397 slice_buffer: None,
398 slice_vertex_count: 0,
399 line_buffer: None,
400 line_vertex_count: 0,
401 marker_buffer: None,
402 marker_vertex_count: 0,
403 volume_renderer: Some(volume_renderer),
404 grid_dimensions,
405 camera_bind_group_layout: bind_group_layout,
406 camera,
407 camera_controller,
408 slice_renderer: SliceRenderer::new(),
409 config: RenderConfig::default(),
410 grid_size,
411 sources: Vec::new(),
412 listener: None,
413 })
414 }
415
416 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
418 if new_size.width > 0 && new_size.height > 0 {
419 self.surface_config.width = new_size.width;
420 self.surface_config.height = new_size.height;
421 self.surface.configure(&self.device, &self.surface_config);
422 self.camera
423 .set_aspect(new_size.width as f32 / new_size.height as f32);
424 self.depth_texture_view =
426 Self::create_depth_texture_static(&self.device, &self.surface_config);
427 }
428 }
429
430 pub fn add_source(&mut self, position: Position3D, color: [f32; 4]) {
432 self.sources.push(MarkerSphere::new(position, 0.1, color));
433 }
434
435 pub fn clear_sources(&mut self) {
437 self.sources.clear();
438 }
439
440 pub fn set_listener(&mut self, position: Position3D, scale: f32) {
442 self.listener = Some(HeadWireframe::new(position, scale));
443 }
444
445 pub fn clear_listener(&mut self) {
447 self.listener = None;
448 }
449
450 fn update_camera_uniform(&self) {
452 let mut uniform = CameraUniform::new();
453 uniform.update(&self.camera, self.grid_size);
454 self.queue
455 .write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[uniform]));
456 }
457
458 fn create_depth_texture_static(
460 device: &wgpu::Device,
461 surface_config: &wgpu::SurfaceConfiguration,
462 ) -> wgpu::TextureView {
463 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
464 label: Some("depth_texture"),
465 size: wgpu::Extent3d {
466 width: surface_config.width,
467 height: surface_config.height,
468 depth_or_array_layers: 1,
469 },
470 mip_level_count: 1,
471 sample_count: 1,
472 dimension: wgpu::TextureDimension::D2,
473 format: wgpu::TextureFormat::Depth32Float,
474 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
475 view_formats: &[],
476 });
477
478 depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
479 }
480
481 fn update_vertex_buffer_static(
483 device: &wgpu::Device,
484 queue: &wgpu::Queue,
485 vertices: &[Vertex3D],
486 buffer: &mut Option<wgpu::Buffer>,
487 vertex_count: &mut u32,
488 label: &str,
489 ) {
490 let new_count = vertices.len() as u32;
491 let data = bytemuck::cast_slice(vertices);
492 let required_size = data.len() as u64;
493
494 let needs_recreate = match buffer {
496 Some(ref existing) => existing.size() < required_size,
497 None => true,
498 };
499
500 if needs_recreate && !vertices.is_empty() {
501 let alloc_size = (required_size as f64 * 1.5) as u64;
503 *buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
504 label: Some(label),
505 size: alloc_size,
506 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
507 mapped_at_creation: false,
508 }));
509 }
510
511 if let Some(ref buf) = buffer {
513 if !vertices.is_empty() {
514 queue.write_buffer(buf, 0, data);
515 }
516 }
517
518 *vertex_count = new_count;
519 }
520
521 pub fn render(
523 &mut self,
524 grid: &crate::simulation::SimulationGrid3D,
525 ) -> Result<(), RendererError> {
526 let output = self
528 .surface
529 .get_current_texture()
530 .map_err(|e| RendererError::SurfaceError(e.to_string()))?;
531
532 let view = output
533 .texture
534 .create_view(&wgpu::TextureViewDescriptor::default());
535
536 self.update_camera_uniform();
538
539 if self.config.auto_scale {
541 self.slice_renderer.set_max_pressure(grid.max_pressure());
542 } else {
543 self.slice_renderer
544 .set_max_pressure(self.config.max_pressure);
545 }
546
547 let slice_vertices = self.slice_renderer.generate_vertices(grid, self.grid_size);
549
550 let mut line_vertices = Vec::new();
551
552 if self.config.show_bounding_box {
554 let grid_lines = GridLines::new(self.grid_size);
555 line_vertices.extend(grid_lines.generate_box());
556 }
557
558 if self.config.show_floor_grid {
560 let grid_lines = GridLines::new(self.grid_size);
561 line_vertices.extend(grid_lines.generate_floor_grid());
562 }
563
564 let mut marker_vertices = Vec::new();
566 if self.config.show_sources {
567 for source in &self.sources {
568 marker_vertices.extend(source.generate_vertices(16));
569 }
570 }
571
572 if self.config.show_listener {
574 if let Some(ref head) = self.listener {
575 line_vertices.extend(head.generate_vertices());
576 }
577 }
578
579 Self::update_vertex_buffer_static(
581 &self.device,
582 &self.queue,
583 &slice_vertices,
584 &mut self.slice_buffer,
585 &mut self.slice_vertex_count,
586 "slice_vertex_buffer",
587 );
588 Self::update_vertex_buffer_static(
589 &self.device,
590 &self.queue,
591 &line_vertices,
592 &mut self.line_buffer,
593 &mut self.line_vertex_count,
594 "line_vertex_buffer",
595 );
596 Self::update_vertex_buffer_static(
597 &self.device,
598 &self.queue,
599 &marker_vertices,
600 &mut self.marker_buffer,
601 &mut self.marker_vertex_count,
602 "marker_vertex_buffer",
603 );
604
605 let mut encoder = self
607 .device
608 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
609 label: Some("render_encoder"),
610 });
611
612 if self.config.mode == VisualizationMode::VolumeRender {
614 if let Some(ref mut volume_renderer) = self.volume_renderer {
615 volume_renderer.update_volume(&self.queue, grid);
616 volume_renderer.update_params(&self.queue);
617 }
618 }
619
620 {
621 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
622 label: Some("render_pass"),
623 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
624 view: &view,
625 resolve_target: None,
626 ops: wgpu::Operations {
627 load: wgpu::LoadOp::Clear(wgpu::Color {
628 r: self.config.background_color[0] as f64,
629 g: self.config.background_color[1] as f64,
630 b: self.config.background_color[2] as f64,
631 a: self.config.background_color[3] as f64,
632 }),
633 store: wgpu::StoreOp::Store,
634 },
635 depth_slice: None,
636 })],
637 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
638 view: &self.depth_texture_view,
639 depth_ops: Some(wgpu::Operations {
640 load: wgpu::LoadOp::Clear(1.0),
641 store: wgpu::StoreOp::Store,
642 }),
643 stencil_ops: None,
644 }),
645 timestamp_writes: None,
646 occlusion_query_set: None,
647 });
648
649 match self.config.mode {
651 VisualizationMode::VolumeRender => {
652 if let Some(ref volume_renderer) = self.volume_renderer {
654 volume_renderer.render(&mut render_pass, &self.camera_bind_group);
655 }
656 }
657 _ => {
658 if self.slice_vertex_count > 0 {
660 if let Some(ref buffer) = self.slice_buffer {
661 render_pass.set_pipeline(&self.triangle_pipeline);
662 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
663 render_pass.set_vertex_buffer(0, buffer.slice(..));
664 render_pass.draw(0..self.slice_vertex_count, 0..1);
665 }
666 }
667 }
668 }
669
670 if self.marker_vertex_count > 0 {
672 if let Some(ref buffer) = self.marker_buffer {
673 render_pass.set_pipeline(&self.triangle_pipeline);
674 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
675 render_pass.set_vertex_buffer(0, buffer.slice(..));
676 render_pass.draw(0..self.marker_vertex_count, 0..1);
677 }
678 }
679
680 if self.line_vertex_count > 0 {
682 if let Some(ref buffer) = self.line_buffer {
683 render_pass.set_pipeline(&self.line_pipeline);
684 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
685 render_pass.set_vertex_buffer(0, buffer.slice(..));
686 render_pass.draw(0..self.line_vertex_count, 0..1);
687 }
688 }
689 }
690
691 self.queue.submit(std::iter::once(encoder.finish()));
692 output.present();
693
694 Ok(())
695 }
696
697 pub fn device(&self) -> &wgpu::Device {
699 &self.device
700 }
701
702 pub fn queue(&self) -> &wgpu::Queue {
704 &self.queue
705 }
706
707 pub fn surface_format(&self) -> wgpu::TextureFormat {
709 self.surface_config.format
710 }
711}
712
713#[derive(Debug)]
715pub enum RendererError {
716 SurfaceError(String),
717 AdapterError(String),
718 DeviceError(String),
719 ShaderError(String),
720}
721
722impl std::fmt::Display for RendererError {
723 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
724 match self {
725 RendererError::SurfaceError(msg) => write!(f, "Surface error: {}", msg),
726 RendererError::AdapterError(msg) => write!(f, "Adapter error: {}", msg),
727 RendererError::DeviceError(msg) => write!(f, "Device error: {}", msg),
728 RendererError::ShaderError(msg) => write!(f, "Shader error: {}", msg),
729 }
730 }
731}
732
733impl std::error::Error for RendererError {}