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 {
199 instance
200 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(window).unwrap())
201 .map_err(|e| RendererError::SurfaceError(e.to_string()))?
202 };
203
204 let adapter = instance
206 .request_adapter(&wgpu::RequestAdapterOptions {
207 power_preference: wgpu::PowerPreference::HighPerformance,
208 compatible_surface: Some(&surface),
209 force_fallback_adapter: false,
210 })
211 .await
212 .map_err(|e| RendererError::AdapterError(e.to_string()))?;
213
214 let (device, queue) = adapter
216 .request_device(&wgpu::DeviceDescriptor {
217 required_features: wgpu::Features::empty(),
218 required_limits: wgpu::Limits::default(),
219 label: Some("wavesim3d_device"),
220 ..Default::default()
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: Some("vs_main"),
299 compilation_options: Default::default(),
300 buffers: &[Vertex3D::desc()],
301 },
302 fragment: Some(wgpu::FragmentState {
303 module: &shader,
304 entry_point: Some("fs_main"),
305 compilation_options: Default::default(),
306 targets: &[Some(wgpu::ColorTargetState {
307 format: surface_config.format,
308 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
309 write_mask: wgpu::ColorWrites::ALL,
310 })],
311 }),
312 primitive: wgpu::PrimitiveState {
313 topology: wgpu::PrimitiveTopology::TriangleList,
314 strip_index_format: None,
315 front_face: wgpu::FrontFace::Ccw,
316 cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
318 unclipped_depth: false,
319 conservative: false,
320 },
321 depth_stencil: Some(wgpu::DepthStencilState {
322 format: wgpu::TextureFormat::Depth32Float,
323 depth_write_enabled: true,
324 depth_compare: wgpu::CompareFunction::Less,
325 stencil: wgpu::StencilState::default(),
326 bias: wgpu::DepthBiasState::default(),
327 }),
328 multisample: wgpu::MultisampleState {
329 count: 1,
330 mask: !0,
331 alpha_to_coverage_enabled: false,
332 },
333 multiview: None,
334 cache: None,
335 });
336
337 let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
339 label: Some("line_pipeline"),
340 layout: Some(&pipeline_layout),
341 vertex: wgpu::VertexState {
342 module: &shader,
343 entry_point: Some("vs_main"),
344 compilation_options: Default::default(),
345 buffers: &[Vertex3D::desc()],
346 },
347 fragment: Some(wgpu::FragmentState {
348 module: &shader,
349 entry_point: Some("fs_main_line"),
350 compilation_options: Default::default(),
351 targets: &[Some(wgpu::ColorTargetState {
352 format: surface_config.format,
353 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
354 write_mask: wgpu::ColorWrites::ALL,
355 })],
356 }),
357 primitive: wgpu::PrimitiveState {
358 topology: wgpu::PrimitiveTopology::LineList,
359 ..Default::default()
360 },
361 depth_stencil: Some(wgpu::DepthStencilState {
362 format: wgpu::TextureFormat::Depth32Float,
363 depth_write_enabled: true,
364 depth_compare: wgpu::CompareFunction::Less,
365 stencil: wgpu::StencilState::default(),
366 bias: wgpu::DepthBiasState::default(),
367 }),
368 multisample: wgpu::MultisampleState::default(),
369 multiview: None,
370 cache: None,
371 });
372
373 let mut camera = Camera3D::for_grid(grid_size);
375 camera.set_aspect(size.width as f32 / size.height as f32);
376 let camera_controller = CameraController::from_camera(&camera);
377
378 let depth_texture_view = Self::create_depth_texture_static(&device, &surface_config);
380
381 let volume_renderer = VolumeRenderer::new(
383 &device,
384 surface_config.format,
385 &bind_group_layout,
386 grid_dimensions,
387 );
388
389 Ok(Self {
390 device,
391 queue,
392 surface_config,
393 surface,
394 triangle_pipeline,
395 line_pipeline,
396 camera_buffer,
397 camera_bind_group,
398 depth_texture_view,
399 slice_buffer: None,
400 slice_vertex_count: 0,
401 line_buffer: None,
402 line_vertex_count: 0,
403 marker_buffer: None,
404 marker_vertex_count: 0,
405 volume_renderer: Some(volume_renderer),
406 grid_dimensions,
407 camera_bind_group_layout: bind_group_layout,
408 camera,
409 camera_controller,
410 slice_renderer: SliceRenderer::new(),
411 config: RenderConfig::default(),
412 grid_size,
413 sources: Vec::new(),
414 listener: None,
415 })
416 }
417
418 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
420 if new_size.width > 0 && new_size.height > 0 {
421 self.surface_config.width = new_size.width;
422 self.surface_config.height = new_size.height;
423 self.surface.configure(&self.device, &self.surface_config);
424 self.camera
425 .set_aspect(new_size.width as f32 / new_size.height as f32);
426 self.depth_texture_view =
428 Self::create_depth_texture_static(&self.device, &self.surface_config);
429 }
430 }
431
432 pub fn add_source(&mut self, position: Position3D, color: [f32; 4]) {
434 self.sources.push(MarkerSphere::new(position, 0.1, color));
435 }
436
437 pub fn clear_sources(&mut self) {
439 self.sources.clear();
440 }
441
442 pub fn set_listener(&mut self, position: Position3D, scale: f32) {
444 self.listener = Some(HeadWireframe::new(position, scale));
445 }
446
447 pub fn clear_listener(&mut self) {
449 self.listener = None;
450 }
451
452 fn update_camera_uniform(&self) {
454 let mut uniform = CameraUniform::new();
455 uniform.update(&self.camera, self.grid_size);
456 self.queue
457 .write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[uniform]));
458 }
459
460 fn create_depth_texture_static(
462 device: &wgpu::Device,
463 surface_config: &wgpu::SurfaceConfiguration,
464 ) -> wgpu::TextureView {
465 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
466 label: Some("depth_texture"),
467 size: wgpu::Extent3d {
468 width: surface_config.width,
469 height: surface_config.height,
470 depth_or_array_layers: 1,
471 },
472 mip_level_count: 1,
473 sample_count: 1,
474 dimension: wgpu::TextureDimension::D2,
475 format: wgpu::TextureFormat::Depth32Float,
476 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
477 view_formats: &[],
478 });
479
480 depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
481 }
482
483 fn update_vertex_buffer_static(
485 device: &wgpu::Device,
486 queue: &wgpu::Queue,
487 vertices: &[Vertex3D],
488 buffer: &mut Option<wgpu::Buffer>,
489 vertex_count: &mut u32,
490 label: &str,
491 ) {
492 let new_count = vertices.len() as u32;
493 let data = bytemuck::cast_slice(vertices);
494 let required_size = data.len() as u64;
495
496 let needs_recreate = match buffer {
498 Some(ref existing) => existing.size() < required_size,
499 None => true,
500 };
501
502 if needs_recreate && !vertices.is_empty() {
503 let alloc_size = (required_size as f64 * 1.5) as u64;
505 *buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
506 label: Some(label),
507 size: alloc_size,
508 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
509 mapped_at_creation: false,
510 }));
511 }
512
513 if let Some(ref buf) = buffer {
515 if !vertices.is_empty() {
516 queue.write_buffer(buf, 0, data);
517 }
518 }
519
520 *vertex_count = new_count;
521 }
522
523 pub fn render(
525 &mut self,
526 grid: &crate::simulation::SimulationGrid3D,
527 ) -> Result<(), RendererError> {
528 let output = self
530 .surface
531 .get_current_texture()
532 .map_err(|e| RendererError::SurfaceError(e.to_string()))?;
533
534 let view = output
535 .texture
536 .create_view(&wgpu::TextureViewDescriptor::default());
537
538 self.update_camera_uniform();
540
541 if self.config.auto_scale {
543 self.slice_renderer.set_max_pressure(grid.max_pressure());
544 } else {
545 self.slice_renderer
546 .set_max_pressure(self.config.max_pressure);
547 }
548
549 let slice_vertices = self.slice_renderer.generate_vertices(grid, self.grid_size);
551
552 let mut line_vertices = Vec::new();
553
554 if self.config.show_bounding_box {
556 let grid_lines = GridLines::new(self.grid_size);
557 line_vertices.extend(grid_lines.generate_box());
558 }
559
560 if self.config.show_floor_grid {
562 let grid_lines = GridLines::new(self.grid_size);
563 line_vertices.extend(grid_lines.generate_floor_grid());
564 }
565
566 let mut marker_vertices = Vec::new();
568 if self.config.show_sources {
569 for source in &self.sources {
570 marker_vertices.extend(source.generate_vertices(16));
571 }
572 }
573
574 if self.config.show_listener {
576 if let Some(ref head) = self.listener {
577 line_vertices.extend(head.generate_vertices());
578 }
579 }
580
581 Self::update_vertex_buffer_static(
583 &self.device,
584 &self.queue,
585 &slice_vertices,
586 &mut self.slice_buffer,
587 &mut self.slice_vertex_count,
588 "slice_vertex_buffer",
589 );
590 Self::update_vertex_buffer_static(
591 &self.device,
592 &self.queue,
593 &line_vertices,
594 &mut self.line_buffer,
595 &mut self.line_vertex_count,
596 "line_vertex_buffer",
597 );
598 Self::update_vertex_buffer_static(
599 &self.device,
600 &self.queue,
601 &marker_vertices,
602 &mut self.marker_buffer,
603 &mut self.marker_vertex_count,
604 "marker_vertex_buffer",
605 );
606
607 let mut encoder = self
609 .device
610 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
611 label: Some("render_encoder"),
612 });
613
614 if self.config.mode == VisualizationMode::VolumeRender {
616 if let Some(ref mut volume_renderer) = self.volume_renderer {
617 volume_renderer.update_volume(&self.queue, grid);
618 volume_renderer.update_params(&self.queue);
619 }
620 }
621
622 {
623 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
624 label: Some("render_pass"),
625 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
626 view: &view,
627 resolve_target: None,
628 ops: wgpu::Operations {
629 load: wgpu::LoadOp::Clear(wgpu::Color {
630 r: self.config.background_color[0] as f64,
631 g: self.config.background_color[1] as f64,
632 b: self.config.background_color[2] as f64,
633 a: self.config.background_color[3] as f64,
634 }),
635 store: wgpu::StoreOp::Store,
636 },
637 depth_slice: None,
638 })],
639 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
640 view: &self.depth_texture_view,
641 depth_ops: Some(wgpu::Operations {
642 load: wgpu::LoadOp::Clear(1.0),
643 store: wgpu::StoreOp::Store,
644 }),
645 stencil_ops: None,
646 }),
647 timestamp_writes: None,
648 occlusion_query_set: None,
649 });
650
651 match self.config.mode {
653 VisualizationMode::VolumeRender => {
654 if let Some(ref volume_renderer) = self.volume_renderer {
656 volume_renderer.render(&mut render_pass, &self.camera_bind_group);
657 }
658 }
659 _ => {
660 if self.slice_vertex_count > 0 {
662 if let Some(ref buffer) = self.slice_buffer {
663 render_pass.set_pipeline(&self.triangle_pipeline);
664 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
665 render_pass.set_vertex_buffer(0, buffer.slice(..));
666 render_pass.draw(0..self.slice_vertex_count, 0..1);
667 }
668 }
669 }
670 }
671
672 if self.marker_vertex_count > 0 {
674 if let Some(ref buffer) = self.marker_buffer {
675 render_pass.set_pipeline(&self.triangle_pipeline);
676 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
677 render_pass.set_vertex_buffer(0, buffer.slice(..));
678 render_pass.draw(0..self.marker_vertex_count, 0..1);
679 }
680 }
681
682 if self.line_vertex_count > 0 {
684 if let Some(ref buffer) = self.line_buffer {
685 render_pass.set_pipeline(&self.line_pipeline);
686 render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
687 render_pass.set_vertex_buffer(0, buffer.slice(..));
688 render_pass.draw(0..self.line_vertex_count, 0..1);
689 }
690 }
691 }
692
693 self.queue.submit(std::iter::once(encoder.finish()));
694 output.present();
695
696 Ok(())
697 }
698
699 pub fn device(&self) -> &wgpu::Device {
701 &self.device
702 }
703
704 pub fn queue(&self) -> &wgpu::Queue {
706 &self.queue
707 }
708
709 pub fn surface_format(&self) -> wgpu::TextureFormat {
711 self.surface_config.format
712 }
713}
714
715#[derive(Debug)]
717pub enum RendererError {
718 SurfaceError(String),
719 AdapterError(String),
720 DeviceError(String),
721 ShaderError(String),
722}
723
724impl std::fmt::Display for RendererError {
725 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726 match self {
727 RendererError::SurfaceError(msg) => write!(f, "Surface error: {}", msg),
728 RendererError::AdapterError(msg) => write!(f, "Adapter error: {}", msg),
729 RendererError::DeviceError(msg) => write!(f, "Device error: {}", msg),
730 RendererError::ShaderError(msg) => write!(f, "Shader error: {}", msg),
731 }
732 }
733}
734
735impl std::error::Error for RendererError {}