1use crate::device::GpuContext;
2use bytemuck::{Pod, Zeroable};
3use nalgebra::{Matrix4, Vector3};
4use threecrate_core::{ColoredPoint3f, Error, Point3f, PointCloud, Result};
5use wgpu::util::DeviceExt;
6use winit::window::Window;
7
8#[repr(C)]
10#[derive(Copy, Clone, Pod, Zeroable)]
11pub struct PointVertex {
12 pub position: [f32; 3],
13 pub color: [f32; 3],
14 pub size: f32,
15 pub normal: [f32; 3],
16}
17
18impl PointVertex {
19 pub fn from_point(point: &Point3f, color: [f32; 3], size: f32, normal: [f32; 3]) -> Self {
21 Self {
22 position: [point.x, point.y, point.z],
23 color,
24 size,
25 normal,
26 }
27 }
28
29 pub fn from_colored_point(point: &ColoredPoint3f, size: f32, normal: [f32; 3]) -> Self {
31 Self {
32 position: [point.position.x, point.position.y, point.position.z],
33 color: [
34 point.color[0] as f32 / 255.0,
35 point.color[1] as f32 / 255.0,
36 point.color[2] as f32 / 255.0,
37 ],
38 size,
39 normal,
40 }
41 }
42
43 pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
45 wgpu::VertexBufferLayout {
46 array_stride: std::mem::size_of::<PointVertex>() as wgpu::BufferAddress,
47 step_mode: wgpu::VertexStepMode::Vertex,
48 attributes: &[
49 wgpu::VertexAttribute {
51 offset: 0,
52 shader_location: 0,
53 format: wgpu::VertexFormat::Float32x3,
54 },
55 wgpu::VertexAttribute {
57 offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
58 shader_location: 1,
59 format: wgpu::VertexFormat::Float32x3,
60 },
61 wgpu::VertexAttribute {
63 offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
64 shader_location: 2,
65 format: wgpu::VertexFormat::Float32,
66 },
67 wgpu::VertexAttribute {
69 offset: std::mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
70 shader_location: 3,
71 format: wgpu::VertexFormat::Float32x3,
72 },
73 ],
74 }
75 }
76}
77
78#[repr(C)]
80#[derive(Copy, Clone, Pod, Zeroable)]
81pub struct CameraUniform {
82 pub view_proj: [[f32; 4]; 4],
83 pub view_pos: [f32; 3],
84 pub _padding: f32,
85}
86
87#[repr(C)]
89#[derive(Copy, Clone, Debug, Pod, Zeroable)]
90pub struct RenderParams {
91 pub point_size: f32,
92 pub alpha_threshold: f32,
93 pub enable_splatting: f32,
94 pub enable_lighting: f32,
95 pub ambient_strength: f32,
96 pub diffuse_strength: f32,
97 pub specular_strength: f32,
98 pub shininess: f32,
99}
100
101impl Default for RenderParams {
102 fn default() -> Self {
103 Self {
104 point_size: 4.0,
105 alpha_threshold: 0.1,
106 enable_splatting: 1.0,
107 enable_lighting: 1.0,
108 ambient_strength: 0.3,
109 diffuse_strength: 0.7,
110 specular_strength: 0.5,
111 shininess: 32.0,
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct RenderConfig {
119 pub render_params: RenderParams,
120 pub background_color: [f64; 4],
121 pub enable_depth_test: bool,
122 pub enable_alpha_blending: bool,
123 pub enable_multisampling: bool,
124}
125
126impl Default for RenderConfig {
127 fn default() -> Self {
128 Self {
129 render_params: RenderParams::default(),
130 background_color: [0.1, 0.1, 0.1, 1.0],
131 enable_depth_test: true,
132 enable_alpha_blending: true,
133 enable_multisampling: true,
134 }
135 }
136}
137
138pub struct PointCloudRenderer<'window> {
140 pub gpu_context: GpuContext,
141 pub surface: wgpu::Surface<'window>,
142 pub surface_config: wgpu::SurfaceConfiguration,
143 pub render_pipeline: wgpu::RenderPipeline,
144 pub camera_uniform: CameraUniform,
145 pub camera_buffer: wgpu::Buffer,
146 pub render_params: RenderParams,
147 pub render_params_buffer: wgpu::Buffer,
148 pub bind_group: wgpu::BindGroup,
149 pub bind_group_layout: wgpu::BindGroupLayout,
150 pub config: RenderConfig,
151 pub msaa_texture: Option<wgpu::Texture>,
152 pub msaa_view: Option<wgpu::TextureView>,
153}
154
155impl<'window> PointCloudRenderer<'window> {
156 pub async fn new(window: &'window Window, config: RenderConfig) -> Result<Self> {
158 let gpu_context = GpuContext::new().await?;
159
160 let surface = gpu_context
161 .instance
162 .create_surface(window)
163 .map_err(|e| Error::Gpu(format!("Failed to create surface: {:?}", e)))?;
164
165 let surface_caps = surface.get_capabilities(&gpu_context.adapter);
166 let surface_format = surface_caps
167 .formats
168 .iter()
169 .copied()
170 .find(|f| f.is_srgb())
171 .unwrap_or(surface_caps.formats[0]);
172
173 let size = window.inner_size();
174 let sample_count = if config.enable_multisampling { 4 } else { 1 };
175
176 let surface_config = wgpu::SurfaceConfiguration {
177 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
178 format: surface_format,
179 width: size.width,
180 height: size.height,
181 present_mode: surface_caps.present_modes[0],
182 alpha_mode: surface_caps.alpha_modes[0],
183 view_formats: vec![],
184 desired_maximum_frame_latency: 2,
185 };
186 surface.configure(&gpu_context.device, &surface_config);
187
188 let (msaa_texture, msaa_view) = if config.enable_multisampling {
190 let msaa_texture = gpu_context.device.create_texture(&wgpu::TextureDescriptor {
191 label: Some("MSAA Texture"),
192 size: wgpu::Extent3d {
193 width: size.width,
194 height: size.height,
195 depth_or_array_layers: 1,
196 },
197 mip_level_count: 1,
198 sample_count,
199 dimension: wgpu::TextureDimension::D2,
200 format: surface_format,
201 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
202 view_formats: &[],
203 });
204 let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
205 (Some(msaa_texture), Some(msaa_view))
206 } else {
207 (None, None)
208 };
209
210 let camera_uniform = CameraUniform {
212 view_proj: Matrix4::identity().into(),
213 view_pos: [0.0, 0.0, 0.0],
214 _padding: 0.0,
215 };
216
217 let camera_buffer =
218 gpu_context
219 .device
220 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
221 label: Some("Camera Buffer"),
222 contents: bytemuck::bytes_of(&camera_uniform),
223 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
224 });
225
226 let render_params = config.render_params;
228 let render_params_buffer =
229 gpu_context
230 .device
231 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
232 label: Some("Render Params Buffer"),
233 contents: bytemuck::bytes_of(&render_params),
234 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
235 });
236
237 let bind_group_layout =
239 gpu_context
240 .device
241 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
242 entries: &[
243 wgpu::BindGroupLayoutEntry {
244 binding: 0,
245 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
246 ty: wgpu::BindingType::Buffer {
247 ty: wgpu::BufferBindingType::Uniform,
248 has_dynamic_offset: false,
249 min_binding_size: None,
250 },
251 count: None,
252 },
253 wgpu::BindGroupLayoutEntry {
254 binding: 1,
255 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
256 ty: wgpu::BindingType::Buffer {
257 ty: wgpu::BufferBindingType::Uniform,
258 has_dynamic_offset: false,
259 min_binding_size: None,
260 },
261 count: None,
262 },
263 ],
264 label: Some("point_cloud_bind_group_layout"),
265 });
266
267 let bind_group = gpu_context
268 .device
269 .create_bind_group(&wgpu::BindGroupDescriptor {
270 layout: &bind_group_layout,
271 entries: &[
272 wgpu::BindGroupEntry {
273 binding: 0,
274 resource: camera_buffer.as_entire_binding(),
275 },
276 wgpu::BindGroupEntry {
277 binding: 1,
278 resource: render_params_buffer.as_entire_binding(),
279 },
280 ],
281 label: Some("point_cloud_bind_group"),
282 });
283
284 let shader = gpu_context
286 .device
287 .create_shader_module(wgpu::ShaderModuleDescriptor {
288 label: Some("Point Cloud Shader"),
289 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/point_cloud.wgsl").into()),
290 });
291
292 let render_pipeline_layout =
293 gpu_context
294 .device
295 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
296 label: Some("Point Cloud Render Pipeline Layout"),
297 bind_group_layouts: &[Some(&bind_group_layout)],
298 immediate_size: 0,
299 });
300
301 let render_pipeline =
302 gpu_context
303 .device
304 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
305 label: Some("Point Cloud Render Pipeline"),
306 layout: Some(&render_pipeline_layout),
307 vertex: wgpu::VertexState {
308 module: &shader,
309 entry_point: Some("vs_main"),
310 buffers: &[PointVertex::desc()],
311 compilation_options: wgpu::PipelineCompilationOptions::default(),
312 },
313 fragment: Some(wgpu::FragmentState {
314 module: &shader,
315 entry_point: Some("fs_main"),
316 targets: &[Some(wgpu::ColorTargetState {
317 format: surface_config.format,
318 blend: if config.enable_alpha_blending {
319 Some(wgpu::BlendState::ALPHA_BLENDING)
320 } else {
321 Some(wgpu::BlendState::REPLACE)
322 },
323 write_mask: wgpu::ColorWrites::ALL,
324 })],
325 compilation_options: wgpu::PipelineCompilationOptions::default(),
326 }),
327 primitive: wgpu::PrimitiveState {
328 topology: wgpu::PrimitiveTopology::TriangleList,
329 strip_index_format: None,
330 front_face: wgpu::FrontFace::Ccw,
331 cull_mode: None,
332 unclipped_depth: false,
333 polygon_mode: wgpu::PolygonMode::Fill,
334 conservative: false,
335 },
336 depth_stencil: if config.enable_depth_test {
337 Some(wgpu::DepthStencilState {
338 format: wgpu::TextureFormat::Depth32Float,
339 depth_write_enabled: Some(true),
340 depth_compare: Some(wgpu::CompareFunction::Less),
341 stencil: wgpu::StencilState::default(),
342 bias: wgpu::DepthBiasState::default(),
343 })
344 } else {
345 None
346 },
347 multisample: wgpu::MultisampleState {
348 count: sample_count,
349 mask: !0,
350 alpha_to_coverage_enabled: false,
351 },
352 multiview_mask: None,
353 cache: None,
354 });
355
356 Ok(Self {
357 gpu_context,
358 surface,
359 surface_config,
360 render_pipeline,
361 camera_uniform,
362 camera_buffer,
363 render_params,
364 render_params_buffer,
365 bind_group,
366 bind_group_layout,
367 config,
368 msaa_texture,
369 msaa_view,
370 })
371 }
372
373 pub fn update_camera(
375 &mut self,
376 view_matrix: Matrix4<f32>,
377 proj_matrix: Matrix4<f32>,
378 camera_pos: Vector3<f32>,
379 ) {
380 self.camera_uniform.view_proj = (proj_matrix * view_matrix).into();
381 self.camera_uniform.view_pos = camera_pos.into();
382
383 self.gpu_context.queue.write_buffer(
384 &self.camera_buffer,
385 0,
386 bytemuck::bytes_of(&self.camera_uniform),
387 );
388 }
389
390 pub fn update_render_params(&mut self, params: RenderParams) {
392 self.render_params = params;
393 self.gpu_context.queue.write_buffer(
394 &self.render_params_buffer,
395 0,
396 bytemuck::bytes_of(&self.render_params),
397 );
398 }
399
400 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
402 self.surface_config.width = new_size.width;
403 self.surface_config.height = new_size.height;
404 self.surface
405 .configure(&self.gpu_context.device, &self.surface_config);
406
407 if self.config.enable_multisampling {
409 let msaa_texture = self
410 .gpu_context
411 .device
412 .create_texture(&wgpu::TextureDescriptor {
413 label: Some("MSAA Texture"),
414 size: wgpu::Extent3d {
415 width: new_size.width,
416 height: new_size.height,
417 depth_or_array_layers: 1,
418 },
419 mip_level_count: 1,
420 sample_count: 4,
421 dimension: wgpu::TextureDimension::D2,
422 format: self.surface_config.format,
423 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
424 view_formats: &[],
425 });
426 let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
427 self.msaa_texture = Some(msaa_texture);
428 self.msaa_view = Some(msaa_view);
429 }
430 }
431
432 pub fn create_vertex_buffer(&self, vertices: &[PointVertex]) -> wgpu::Buffer {
434 self.gpu_context
435 .device
436 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
437 label: Some("Point Cloud Vertex Buffer"),
438 contents: bytemuck::cast_slice(vertices),
439 usage: wgpu::BufferUsages::VERTEX,
440 })
441 }
442
443 pub fn create_depth_texture(&self) -> wgpu::Texture {
445 let sample_count = if self.config.enable_multisampling {
446 4
447 } else {
448 1
449 };
450
451 self.gpu_context
452 .device
453 .create_texture(&wgpu::TextureDescriptor {
454 label: Some("Depth Texture"),
455 size: wgpu::Extent3d {
456 width: self.surface_config.width,
457 height: self.surface_config.height,
458 depth_or_array_layers: 1,
459 },
460 mip_level_count: 1,
461 sample_count,
462 dimension: wgpu::TextureDimension::D2,
463 format: wgpu::TextureFormat::Depth32Float,
464 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
465 view_formats: &[],
466 })
467 }
468
469 pub fn render(&self, vertices: &[PointVertex]) -> Result<()> {
471 let vertex_buffer = self.create_vertex_buffer(vertices);
472 let depth_texture = self.create_depth_texture();
473 let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
474
475 let output = match self.surface.get_current_texture() {
476 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
477 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
478 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
479 return Ok(())
480 }
481 _ => return Err(Error::Gpu("Failed to get surface texture".to_string())),
482 };
483
484 let view = output
485 .texture
486 .create_view(&wgpu::TextureViewDescriptor::default());
487
488 let mut encoder =
489 self.gpu_context
490 .device
491 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
492 label: Some("Point Cloud Render Encoder"),
493 });
494
495 let (color_attachment, resolve_target) = if let Some(ref msaa_view) = self.msaa_view {
497 (msaa_view, Some(&view))
498 } else {
499 (&view, None)
500 };
501
502 {
503 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
504 label: Some("Point Cloud Render Pass"),
505 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
506 view: color_attachment,
507 resolve_target,
508 ops: wgpu::Operations {
509 load: wgpu::LoadOp::Clear(wgpu::Color {
510 r: self.config.background_color[0],
511 g: self.config.background_color[1],
512 b: self.config.background_color[2],
513 a: self.config.background_color[3],
514 }),
515 store: wgpu::StoreOp::Store,
516 },
517 depth_slice: None,
518 })],
519 depth_stencil_attachment: if self.config.enable_depth_test {
520 Some(wgpu::RenderPassDepthStencilAttachment {
521 view: &depth_view,
522 depth_ops: Some(wgpu::Operations {
523 load: wgpu::LoadOp::Clear(1.0),
524 store: wgpu::StoreOp::Store,
525 }),
526 stencil_ops: None,
527 })
528 } else {
529 None
530 },
531 timestamp_writes: None,
532 occlusion_query_set: None,
533 multiview_mask: None,
534 });
535
536 render_pass.set_pipeline(&self.render_pipeline);
537 render_pass.set_bind_group(0, &self.bind_group, &[]);
538 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
539 render_pass.draw(0..vertices.len() as u32, 0..1);
540 }
541
542 self.gpu_context
543 .queue
544 .submit(std::iter::once(encoder.finish()));
545 output.present();
546
547 Ok(())
548 }
549}
550
551pub fn point_cloud_to_vertices(
553 point_cloud: &PointCloud<Point3f>,
554 color: [f32; 3],
555 size: f32,
556) -> Vec<PointVertex> {
557 let normals = estimate_point_normals(&point_cloud.points);
558 point_cloud
559 .points
560 .iter()
561 .zip(normals.iter())
562 .map(|(point, normal)| PointVertex::from_point(point, color, size, *normal))
563 .collect()
564}
565
566pub fn point_cloud_to_vertices_colored(
568 point_cloud: &PointCloud<Point3f>,
569 size: f32,
570) -> Vec<PointVertex> {
571 let normals = estimate_point_normals(&point_cloud.points);
572 point_cloud
573 .points
574 .iter()
575 .zip(normals.iter())
576 .map(|(point, normal)| {
577 let color = [
579 (point.x * 0.5 + 0.5).clamp(0.0, 1.0),
580 (point.y * 0.5 + 0.5).clamp(0.0, 1.0),
581 (point.z * 0.5 + 0.5).clamp(0.0, 1.0),
582 ];
583
584 PointVertex::from_point(point, color, size, *normal)
585 })
586 .collect()
587}
588
589pub fn colored_point_cloud_to_vertices(
591 point_cloud: &PointCloud<ColoredPoint3f>,
592 size: f32,
593) -> Vec<PointVertex> {
594 let positions: Vec<Point3f> = point_cloud
595 .points
596 .iter()
597 .map(|p| Point3f::new(p.position.x, p.position.y, p.position.z))
598 .collect();
599 let normals = estimate_point_normals(&positions);
600
601 point_cloud
602 .points
603 .iter()
604 .zip(normals.iter())
605 .map(|(point, normal)| PointVertex::from_colored_point(point, size, *normal))
606 .collect()
607}
608
609fn estimate_point_normals(points: &[Point3f]) -> Vec<[f32; 3]> {
611 let mut normals = vec![[0.0, 0.0, 1.0]; points.len()];
612
613 for (i, point) in points.iter().enumerate() {
614 let mut neighbors = Vec::new();
616 let search_radius = 0.1;
617
618 for (j, other_point) in points.iter().enumerate() {
619 if i != j {
620 let distance = ((point.x - other_point.x).powi(2)
621 + (point.y - other_point.y).powi(2)
622 + (point.z - other_point.z).powi(2))
623 .sqrt();
624
625 if distance < search_radius && neighbors.len() < 10 {
626 neighbors.push(*other_point);
627 }
628 }
629 }
630
631 if neighbors.len() >= 3 {
632 let mut normal = estimate_normal_from_neighbors(point, &neighbors);
634
635 let length = (normal[0].powi(2) + normal[1].powi(2) + normal[2].powi(2)).sqrt();
637 if length > 0.0 {
638 normal[0] /= length;
639 normal[1] /= length;
640 normal[2] /= length;
641 }
642
643 normals[i] = normal;
644 }
645 }
646
647 normals
648}
649
650fn estimate_normal_from_neighbors(center: &Point3f, neighbors: &[Point3f]) -> [f32; 3] {
652 if neighbors.len() < 2 {
653 return [0.0, 0.0, 1.0];
654 }
655
656 let v1 = [
658 neighbors[0].x - center.x,
659 neighbors[0].y - center.y,
660 neighbors[0].z - center.z,
661 ];
662
663 let v2 = [
664 neighbors[1].x - center.x,
665 neighbors[1].y - center.y,
666 neighbors[1].z - center.z,
667 ];
668
669 let normal = [
671 v1[1] * v2[2] - v1[2] * v2[1],
672 v1[2] * v2[0] - v1[0] * v2[2],
673 v1[0] * v2[1] - v1[1] * v2[0],
674 ];
675
676 normal
677}
678
679