1use bytemuck::{Pod, Zeroable};
7use glam::{Mat4, Vec3, Vec4};
8use std::sync::Arc;
9use wgpu::util::DeviceExt;
10
11use crate::core::DepthMode;
12use crate::{core::scene::GpuVertexBuffer, gpu::shaders};
13
14#[repr(C)]
16#[derive(Clone, Copy, Debug, Pod, Zeroable)]
17pub struct GridUniforms {
18 pub major_step: f32,
19 pub minor_step: f32,
20 pub fade_start: f32,
21 pub fade_end: f32,
22 pub camera_pos: [f32; 3],
23 pub _pad0: f32,
24 pub target_pos: [f32; 3],
25 pub _pad1: f32,
26 pub major_color: [f32; 4],
27 pub minor_color: [f32; 4],
28}
29
30impl Default for GridUniforms {
31 fn default() -> Self {
32 Self {
33 major_step: 1.0,
34 minor_step: 0.1,
35 fade_start: 10.0,
36 fade_end: 15.0,
37 camera_pos: [0.0, 0.0, 0.0],
38 _pad0: 0.0,
39 target_pos: [0.0, 0.0, 0.0],
40 _pad1: 0.0,
41 major_color: [0.90, 0.92, 0.96, 0.30],
42 minor_color: [0.82, 0.84, 0.88, 0.18],
43 }
44 }
45}
46
47#[repr(C)]
49#[derive(Clone, Copy, Debug, Pod, Zeroable)]
50pub struct Vertex {
51 pub position: [f32; 3],
52 pub color: [f32; 4],
53 pub normal: [f32; 3],
54 pub tex_coords: [f32; 2],
55}
56
57impl Vertex {
58 pub fn new(position: Vec3, color: Vec4) -> Self {
59 Self {
60 position: position.to_array(),
61 color: color.to_array(),
62 normal: [0.0, 0.0, 1.0], tex_coords: [0.0, 0.0], }
65 }
66
67 pub fn desc() -> wgpu::VertexBufferLayout<'static> {
68 let stride = std::mem::size_of::<Vertex>() as wgpu::BufferAddress;
69 log::trace!(
70 target: "runmat_plot",
71 "vertex layout: size={}, stride={}",
72 std::mem::size_of::<Vertex>(),
73 stride
74 );
75 wgpu::VertexBufferLayout {
76 array_stride: stride,
77 step_mode: wgpu::VertexStepMode::Vertex,
78 attributes: &[
79 wgpu::VertexAttribute {
81 offset: 0,
82 shader_location: 0,
83 format: wgpu::VertexFormat::Float32x3,
84 },
85 wgpu::VertexAttribute {
87 offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
88 shader_location: 1,
89 format: wgpu::VertexFormat::Float32x4,
90 },
91 wgpu::VertexAttribute {
93 offset: std::mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
94 shader_location: 2,
95 format: wgpu::VertexFormat::Float32x3,
96 },
97 wgpu::VertexAttribute {
99 offset: std::mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
100 shader_location: 3,
101 format: wgpu::VertexFormat::Float32x2,
102 },
103 ],
104 }
105 }
106}
107
108#[repr(C)]
110#[derive(Clone, Copy, Debug, Pod, Zeroable)]
111pub struct Uniforms {
112 pub view_proj: [[f32; 4]; 4],
113 pub model: [[f32; 4]; 4],
114 pub normal_matrix: [[f32; 4]; 3], }
116
117#[repr(C)]
120#[derive(Clone, Copy, Debug, Pod, Zeroable)]
121pub struct DirectUniforms {
122 pub data_min: [f32; 2], pub data_max: [f32; 2], pub viewport_min: [f32; 2], pub viewport_max: [f32; 2], pub viewport_px: [f32; 2], }
128
129#[repr(C)]
131#[derive(Clone, Copy, Debug, Pod, Zeroable)]
132pub struct PointStyleUniforms {
133 pub face_color: [f32; 4],
134 pub edge_color: [f32; 4],
135 pub edge_thickness_px: f32,
136 pub marker_shape: u32,
137 pub _pad: [f32; 2],
138}
139
140#[repr(C)]
142#[derive(Clone, Copy, Debug, Pod, Zeroable)]
143pub struct MarkerScreenUniforms {
144 pub viewport_px: [f32; 2],
145 pub _pad: [f32; 2],
146}
147
148impl Default for Uniforms {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154impl Uniforms {
155 pub fn new() -> Self {
156 Self {
157 view_proj: Mat4::IDENTITY.to_cols_array_2d(),
158 model: Mat4::IDENTITY.to_cols_array_2d(),
159 normal_matrix: [
160 [1.0, 0.0, 0.0, 0.0],
161 [0.0, 1.0, 0.0, 0.0],
162 [0.0, 0.0, 1.0, 0.0],
163 ],
164 }
165 }
166
167 pub fn update_view_proj(&mut self, view_proj: Mat4) {
168 self.view_proj = view_proj.to_cols_array_2d();
169 }
170
171 pub fn update_model(&mut self, model: Mat4) {
172 self.model = model.to_cols_array_2d();
173 let normal_mat = model.inverse().transpose();
175 self.normal_matrix = [
176 [
177 normal_mat.x_axis.x,
178 normal_mat.x_axis.y,
179 normal_mat.x_axis.z,
180 0.0,
181 ],
182 [
183 normal_mat.y_axis.x,
184 normal_mat.y_axis.y,
185 normal_mat.y_axis.z,
186 0.0,
187 ],
188 [
189 normal_mat.z_axis.x,
190 normal_mat.z_axis.y,
191 normal_mat.z_axis.z,
192 0.0,
193 ],
194 ];
195 }
196}
197
198impl DirectUniforms {
199 pub fn new(
200 data_min: [f32; 2],
201 data_max: [f32; 2],
202 viewport_min: [f32; 2],
203 viewport_max: [f32; 2],
204 viewport_px: [f32; 2],
205 ) -> Self {
206 Self {
207 data_min,
208 data_max,
209 viewport_min,
210 viewport_max,
211 viewport_px,
212 }
213 }
214}
215
216pub fn marker_shape_code(style: crate::plots::scatter::MarkerStyle) -> u32 {
217 match style {
218 crate::plots::scatter::MarkerStyle::Circle => 0,
219 crate::plots::scatter::MarkerStyle::Square => 1,
220 crate::plots::scatter::MarkerStyle::Triangle => 2,
221 crate::plots::scatter::MarkerStyle::Diamond => 3,
222 crate::plots::scatter::MarkerStyle::Plus => 4,
223 crate::plots::scatter::MarkerStyle::Cross => 5,
224 crate::plots::scatter::MarkerStyle::Star => 6,
225 crate::plots::scatter::MarkerStyle::Hexagon => 7,
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub enum PipelineType {
232 Points,
233 Lines,
234 Triangles,
235 Scatter3,
236 Textured,
237}
238
239pub struct WgpuRenderer {
241 pub device: Arc<wgpu::Device>,
242 pub queue: Arc<wgpu::Queue>,
243 pub surface_config: wgpu::SurfaceConfiguration,
244
245 pub msaa_sample_count: u32,
247
248 point_pipeline: Option<wgpu::RenderPipeline>,
250 line_pipeline: Option<wgpu::RenderPipeline>,
251 triangle_pipeline: Option<wgpu::RenderPipeline>,
252
253 pub direct_line_pipeline: Option<wgpu::RenderPipeline>,
255 pub direct_triangle_pipeline: Option<wgpu::RenderPipeline>,
256 pub direct_point_pipeline: Option<wgpu::RenderPipeline>,
257 image_pipeline: Option<wgpu::RenderPipeline>,
258 image_bind_group_layout: wgpu::BindGroupLayout,
259 image_sampler: wgpu::Sampler,
260 point_style_bind_group_layout: wgpu::BindGroupLayout,
261 marker_screen_bind_group_layout: wgpu::BindGroupLayout,
262 marker_screen_uniform_buffer: wgpu::Buffer,
263 marker_screen_bind_group: wgpu::BindGroup,
264 axes_marker_screen_uniform_buffers: Vec<wgpu::Buffer>,
265 axes_marker_screen_bind_groups: Vec<wgpu::BindGroup>,
266
267 grid_uniform_buffer: wgpu::Buffer,
269 pub grid_uniform_bind_group: wgpu::BindGroup,
270 grid_uniform_bind_group_layout: wgpu::BindGroupLayout,
271 axes_grid_uniform_buffers: Vec<wgpu::Buffer>,
272 axes_grid_uniform_bind_groups: Vec<wgpu::BindGroup>,
273 grid_plane_pipeline: Option<wgpu::RenderPipeline>,
274
275 uniform_buffer: wgpu::Buffer,
277 uniform_bind_group: wgpu::BindGroup,
278 uniform_bind_group_layout: wgpu::BindGroupLayout,
279 axes_uniform_buffers: Vec<wgpu::Buffer>,
280 axes_uniform_bind_groups: Vec<wgpu::BindGroup>,
281
282 direct_uniform_buffer: wgpu::Buffer,
284 pub direct_uniform_bind_group: wgpu::BindGroup,
285 direct_uniform_bind_group_layout: wgpu::BindGroupLayout,
286 axes_direct_uniform_buffers: Vec<wgpu::Buffer>,
287 axes_direct_uniform_bind_groups: Vec<wgpu::BindGroup>,
288
289 uniforms: Uniforms,
291 direct_uniforms: DirectUniforms,
292
293 depth_texture: Option<wgpu::Texture>,
295 depth_view: Option<Arc<wgpu::TextureView>>,
296 depth_extent: (u32, u32, u32), msaa_color_texture: Option<wgpu::Texture>,
300 msaa_color_view: Option<Arc<wgpu::TextureView>>,
301 msaa_color_extent: (u32, u32, u32), pub depth_mode: DepthMode,
305}
306
307impl WgpuRenderer {
308 fn create_uniform_bind_group_for_buffer(
309 &self,
310 buffer: &wgpu::Buffer,
311 label: &str,
312 ) -> wgpu::BindGroup {
313 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
314 layout: &self.uniform_bind_group_layout,
315 entries: &[wgpu::BindGroupEntry {
316 binding: 0,
317 resource: buffer.as_entire_binding(),
318 }],
319 label: Some(label),
320 })
321 }
322
323 fn create_direct_uniform_bind_group_for_buffer(
324 &self,
325 buffer: &wgpu::Buffer,
326 label: &str,
327 ) -> wgpu::BindGroup {
328 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
329 layout: &self.direct_uniform_bind_group_layout,
330 entries: &[wgpu::BindGroupEntry {
331 binding: 0,
332 resource: buffer.as_entire_binding(),
333 }],
334 label: Some(label),
335 })
336 }
337
338 fn create_grid_uniform_bind_group_for_buffer(
339 &self,
340 buffer: &wgpu::Buffer,
341 label: &str,
342 ) -> wgpu::BindGroup {
343 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
344 label: Some(label),
345 layout: &self.grid_uniform_bind_group_layout,
346 entries: &[wgpu::BindGroupEntry {
347 binding: 0,
348 resource: buffer.as_entire_binding(),
349 }],
350 })
351 }
352
353 fn create_marker_screen_bind_group_for_buffer(
354 &self,
355 buffer: &wgpu::Buffer,
356 label: &str,
357 ) -> wgpu::BindGroup {
358 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
359 label: Some(label),
360 layout: &self.marker_screen_bind_group_layout,
361 entries: &[wgpu::BindGroupEntry {
362 binding: 0,
363 resource: buffer.as_entire_binding(),
364 }],
365 })
366 }
367
368 pub fn ensure_axes_uniform_capacity(&mut self, axes_count: usize) {
369 while self.axes_uniform_buffers.len() < axes_count {
370 let idx = self.axes_uniform_buffers.len();
371 let buffer = self
372 .device
373 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
374 label: Some(&format!("Axes Uniform Buffer {idx}")),
375 contents: bytemuck::cast_slice(&[Uniforms::new()]),
376 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
377 });
378 let bind_group = self.create_uniform_bind_group_for_buffer(
379 &buffer,
380 &format!("axes_uniform_bind_group_{idx}"),
381 );
382 self.axes_uniform_buffers.push(buffer);
383 self.axes_uniform_bind_groups.push(bind_group);
384 }
385 while self.axes_direct_uniform_buffers.len() < axes_count {
386 let idx = self.axes_direct_uniform_buffers.len();
387 let buffer = self
388 .device
389 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
390 label: Some(&format!("Axes Direct Uniform Buffer {idx}")),
391 contents: bytemuck::cast_slice(&[DirectUniforms::new(
392 [0.0, 0.0],
393 [1.0, 1.0],
394 [-1.0, -1.0],
395 [1.0, 1.0],
396 [1.0, 1.0],
397 )]),
398 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
399 });
400 let bind_group = self.create_direct_uniform_bind_group_for_buffer(
401 &buffer,
402 &format!("axes_direct_uniform_bind_group_{idx}"),
403 );
404 self.axes_direct_uniform_buffers.push(buffer);
405 self.axes_direct_uniform_bind_groups.push(bind_group);
406 }
407 while self.axes_grid_uniform_buffers.len() < axes_count {
408 let idx = self.axes_grid_uniform_buffers.len();
409 let buffer = self
410 .device
411 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
412 label: Some(&format!("Axes Grid Uniform Buffer {idx}")),
413 contents: bytemuck::cast_slice(&[GridUniforms::default()]),
414 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
415 });
416 let bind_group = self.create_grid_uniform_bind_group_for_buffer(
417 &buffer,
418 &format!("axes_grid_uniform_bind_group_{idx}"),
419 );
420 self.axes_grid_uniform_buffers.push(buffer);
421 self.axes_grid_uniform_bind_groups.push(bind_group);
422 }
423 while self.axes_marker_screen_uniform_buffers.len() < axes_count {
424 let idx = self.axes_marker_screen_uniform_buffers.len();
425 let buffer = self
426 .device
427 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
428 label: Some(&format!("Axes Marker Screen Uniform Buffer {idx}")),
429 contents: bytemuck::cast_slice(&[MarkerScreenUniforms {
430 viewport_px: [1.0, 1.0],
431 _pad: [0.0, 0.0],
432 }]),
433 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
434 });
435 let bind_group = self.create_marker_screen_bind_group_for_buffer(
436 &buffer,
437 &format!("axes_marker_screen_bind_group_{idx}"),
438 );
439 self.axes_marker_screen_uniform_buffers.push(buffer);
440 self.axes_marker_screen_bind_groups.push(bind_group);
441 }
442 }
443
444 pub async fn new(
446 device: Arc<wgpu::Device>,
447 queue: Arc<wgpu::Queue>,
448 surface_config: wgpu::SurfaceConfiguration,
449 ) -> Self {
450 let uniforms = Uniforms::new();
452 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
453 label: Some("Uniform Buffer"),
454 contents: bytemuck::cast_slice(&[uniforms]),
455 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
456 });
457
458 let uniform_bind_group_layout =
460 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
461 entries: &[wgpu::BindGroupLayoutEntry {
462 binding: 0,
463 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
464 ty: wgpu::BindingType::Buffer {
465 ty: wgpu::BufferBindingType::Uniform,
466 has_dynamic_offset: false,
467 min_binding_size: None,
468 },
469 count: None,
470 }],
471 label: Some("uniform_bind_group_layout"),
472 });
473
474 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
476 layout: &uniform_bind_group_layout,
477 entries: &[wgpu::BindGroupEntry {
478 binding: 0,
479 resource: uniform_buffer.as_entire_binding(),
480 }],
481 label: Some("uniform_bind_group"),
482 });
483
484 let direct_uniforms = DirectUniforms::new(
486 [0.0, 0.0], [1.0, 1.0], [-1.0, -1.0], [1.0, 1.0], [1.0, 1.0], );
492 let direct_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
493 label: Some("Direct Uniform Buffer"),
494 contents: bytemuck::cast_slice(&[direct_uniforms]),
495 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
496 });
497
498 let direct_uniform_bind_group_layout =
500 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
501 entries: &[wgpu::BindGroupLayoutEntry {
502 binding: 0,
503 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
504 ty: wgpu::BindingType::Buffer {
505 ty: wgpu::BufferBindingType::Uniform,
506 has_dynamic_offset: false,
507 min_binding_size: None,
508 },
509 count: None,
510 }],
511 label: Some("direct_uniform_bind_group_layout"),
512 });
513
514 let direct_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
516 layout: &direct_uniform_bind_group_layout,
517 entries: &[wgpu::BindGroupEntry {
518 binding: 0,
519 resource: direct_uniform_buffer.as_entire_binding(),
520 }],
521 label: Some("direct_uniform_bind_group"),
522 });
523
524 let image_bind_group_layout =
525 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
526 label: Some("Image Bind Group Layout"),
527 entries: &[
528 wgpu::BindGroupLayoutEntry {
530 binding: 0,
531 visibility: wgpu::ShaderStages::FRAGMENT,
532 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
533 count: None,
534 },
535 wgpu::BindGroupLayoutEntry {
537 binding: 1,
538 visibility: wgpu::ShaderStages::FRAGMENT,
539 ty: wgpu::BindingType::Texture {
540 multisampled: false,
541 view_dimension: wgpu::TextureViewDimension::D2,
542 sample_type: wgpu::TextureSampleType::Float { filterable: true },
543 },
544 count: None,
545 },
546 ],
547 });
548
549 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
550 label: Some("Image Sampler"),
551 address_mode_u: wgpu::AddressMode::ClampToEdge,
552 address_mode_v: wgpu::AddressMode::ClampToEdge,
553 address_mode_w: wgpu::AddressMode::ClampToEdge,
554 mag_filter: wgpu::FilterMode::Linear,
555 min_filter: wgpu::FilterMode::Linear,
556 mipmap_filter: wgpu::FilterMode::Nearest,
557 ..Default::default()
558 });
559
560 let point_style_bind_group_layout =
562 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
563 label: Some("Point Style Bind Group Layout"),
564 entries: &[wgpu::BindGroupLayoutEntry {
565 binding: 0,
566 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
567 ty: wgpu::BindingType::Buffer {
568 ty: wgpu::BufferBindingType::Uniform,
569 has_dynamic_offset: false,
570 min_binding_size: None,
571 },
572 count: None,
573 }],
574 });
575 let marker_screen_bind_group_layout =
576 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
577 label: Some("Marker Screen Bind Group Layout"),
578 entries: &[wgpu::BindGroupLayoutEntry {
579 binding: 0,
580 visibility: wgpu::ShaderStages::VERTEX,
581 ty: wgpu::BindingType::Buffer {
582 ty: wgpu::BufferBindingType::Uniform,
583 has_dynamic_offset: false,
584 min_binding_size: None,
585 },
586 count: None,
587 }],
588 });
589 let marker_screen_uniform_buffer =
590 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
591 label: Some("Marker Screen Uniform Buffer"),
592 contents: bytemuck::cast_slice(&[MarkerScreenUniforms {
593 viewport_px: [1.0, 1.0],
594 _pad: [0.0, 0.0],
595 }]),
596 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
597 });
598 let marker_screen_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
599 label: Some("Marker Screen Bind Group"),
600 layout: &marker_screen_bind_group_layout,
601 entries: &[wgpu::BindGroupEntry {
602 binding: 0,
603 resource: marker_screen_uniform_buffer.as_entire_binding(),
604 }],
605 });
606
607 let grid_uniforms = GridUniforms::default();
609 let grid_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
610 label: Some("Grid Uniform Buffer"),
611 contents: bytemuck::cast_slice(&[grid_uniforms]),
612 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
613 });
614 let grid_uniform_bind_group_layout =
615 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
616 entries: &[wgpu::BindGroupLayoutEntry {
617 binding: 0,
618 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
619 ty: wgpu::BindingType::Buffer {
620 ty: wgpu::BufferBindingType::Uniform,
621 has_dynamic_offset: false,
622 min_binding_size: None,
623 },
624 count: None,
625 }],
626 label: Some("grid_uniform_bind_group_layout"),
627 });
628 let grid_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
629 label: Some("grid_uniform_bind_group"),
630 layout: &grid_uniform_bind_group_layout,
631 entries: &[wgpu::BindGroupEntry {
632 binding: 0,
633 resource: grid_uniform_buffer.as_entire_binding(),
634 }],
635 });
636
637 Self {
638 device,
639 queue,
640 surface_config,
641 msaa_sample_count: 1,
642 point_pipeline: None,
643 line_pipeline: None,
644 triangle_pipeline: None,
645 direct_line_pipeline: None,
646 direct_triangle_pipeline: None,
647 direct_point_pipeline: None,
648 image_pipeline: None,
649 image_bind_group_layout,
650 image_sampler,
651 point_style_bind_group_layout,
652 marker_screen_bind_group_layout,
653 marker_screen_uniform_buffer,
654 marker_screen_bind_group,
655 axes_marker_screen_uniform_buffers: Vec::new(),
656 axes_marker_screen_bind_groups: Vec::new(),
657 grid_uniform_buffer,
658 grid_uniform_bind_group,
659 grid_uniform_bind_group_layout,
660 axes_grid_uniform_buffers: Vec::new(),
661 axes_grid_uniform_bind_groups: Vec::new(),
662 grid_plane_pipeline: None,
663 uniform_buffer,
664 uniform_bind_group,
665 uniform_bind_group_layout,
666 axes_uniform_buffers: Vec::new(),
667 axes_uniform_bind_groups: Vec::new(),
668 direct_uniform_buffer,
669 direct_uniform_bind_group,
670 direct_uniform_bind_group_layout,
671 axes_direct_uniform_buffers: Vec::new(),
672 axes_direct_uniform_bind_groups: Vec::new(),
673 uniforms,
674 direct_uniforms,
675 depth_texture: None,
676 depth_view: None,
677 depth_extent: (0, 0, 0),
678 msaa_color_texture: None,
679 msaa_color_view: None,
680 msaa_color_extent: (0, 0, 0),
681 depth_mode: DepthMode::default(),
682 }
683 }
684
685 pub fn update_grid_uniforms(&mut self, uniforms: GridUniforms) {
686 self.queue.write_buffer(
687 &self.grid_uniform_buffer,
688 0,
689 bytemuck::cast_slice(&[uniforms]),
690 );
691 self.ensure_axes_uniform_capacity(1);
692 self.queue.write_buffer(
693 &self.axes_grid_uniform_buffers[0],
694 0,
695 bytemuck::cast_slice(&[uniforms]),
696 );
697 }
698
699 pub fn update_grid_uniforms_for_axes(&mut self, axes_index: usize, uniforms: GridUniforms) {
700 self.ensure_axes_uniform_capacity(axes_index + 1);
701 self.queue.write_buffer(
702 &self.axes_grid_uniform_buffers[axes_index],
703 0,
704 bytemuck::cast_slice(&[uniforms]),
705 );
706 }
707
708 pub fn get_grid_uniform_bind_group_for_axes(&self, axes_index: usize) -> &wgpu::BindGroup {
709 self.axes_grid_uniform_bind_groups
710 .get(axes_index)
711 .unwrap_or(&self.grid_uniform_bind_group)
712 }
713
714 pub fn set_depth_mode(&mut self, mode: DepthMode) {
715 if self.depth_mode != mode {
716 self.depth_mode = mode;
717 self.point_pipeline = None;
719 self.line_pipeline = None;
720 self.triangle_pipeline = None;
721 self.direct_line_pipeline = None;
722 self.direct_triangle_pipeline = None;
723 self.direct_point_pipeline = None;
724 self.image_pipeline = None;
725 self.grid_plane_pipeline = None;
726 }
727 }
728
729 pub fn ensure_msaa(&mut self, requested_count: u32) {
731 let clamped = match requested_count {
732 0 => 1,
733 1 => 1,
734 2 => 2,
735 4 => 4,
736 8 => 8,
737 16 => 8, _ => 4, };
740 if self.msaa_sample_count != clamped {
741 self.msaa_sample_count = clamped;
742 self.point_pipeline = None;
744 self.line_pipeline = None;
745 self.triangle_pipeline = None;
746 self.direct_line_pipeline = None;
747 self.direct_triangle_pipeline = None;
748 self.direct_point_pipeline = None;
749 self.image_pipeline = None;
750 self.grid_plane_pipeline = None;
751 self.depth_texture = None;
753 self.depth_view = None;
754 self.depth_extent = (0, 0, 0);
755 self.msaa_color_texture = None;
756 self.msaa_color_view = None;
757 self.msaa_color_extent = (0, 0, 0);
758 }
759 }
760
761 fn depth_format() -> wgpu::TextureFormat {
762 #[cfg(target_arch = "wasm32")]
764 {
765 wgpu::TextureFormat::Depth24Plus
766 }
767 #[cfg(not(target_arch = "wasm32"))]
768 {
769 wgpu::TextureFormat::Depth32Float
770 }
771 }
772
773 fn depth_compare(&self) -> wgpu::CompareFunction {
774 match self.depth_mode {
775 DepthMode::Standard => wgpu::CompareFunction::LessEqual,
776 DepthMode::ReversedZ => wgpu::CompareFunction::GreaterEqual,
777 }
778 }
779
780 pub fn ensure_depth_view(&mut self) -> Arc<wgpu::TextureView> {
781 let width = self.surface_config.width.max(1);
782 let height = self.surface_config.height.max(1);
783 let samples = self.msaa_sample_count.max(1);
784 if self.depth_view.is_none() || self.depth_extent != (width, height, samples) {
785 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
786 label: Some("runmat_depth_texture"),
787 size: wgpu::Extent3d {
788 width,
789 height,
790 depth_or_array_layers: 1,
791 },
792 mip_level_count: 1,
793 sample_count: samples,
794 dimension: wgpu::TextureDimension::D2,
795 format: Self::depth_format(),
796 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
797 view_formats: &[],
798 });
799 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
800 self.depth_texture = Some(texture);
801 self.depth_view = Some(Arc::new(view));
802 self.depth_extent = (width, height, samples);
803 }
804 self.depth_view
805 .as_ref()
806 .cloned()
807 .expect("depth view missing")
808 }
809
810 pub fn ensure_msaa_color_view(&mut self) -> Arc<wgpu::TextureView> {
811 let width = self.surface_config.width.max(1);
812 let height = self.surface_config.height.max(1);
813 let samples = self.msaa_sample_count.max(1);
814 if self.msaa_color_view.is_none() || self.msaa_color_extent != (width, height, samples) {
815 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
816 label: Some("runmat_msaa_color_plot"),
817 size: wgpu::Extent3d {
818 width,
819 height,
820 depth_or_array_layers: 1,
821 },
822 mip_level_count: 1,
823 sample_count: samples,
824 dimension: wgpu::TextureDimension::D2,
825 format: self.surface_config.format,
826 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
827 view_formats: &[],
828 });
829 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
830 self.msaa_color_texture = Some(texture);
831 self.msaa_color_view = Some(Arc::new(view));
832 self.msaa_color_extent = (width, height, samples);
833 }
834 self.msaa_color_view
835 .as_ref()
836 .cloned()
837 .expect("msaa color view missing")
838 }
839
840 pub fn create_image_texture_and_bind_group(
842 &self,
843 width: u32,
844 height: u32,
845 data: &[u8],
846 ) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup) {
847 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
848 label: Some("Image Texture"),
849 size: wgpu::Extent3d {
850 width,
851 height,
852 depth_or_array_layers: 1,
853 },
854 mip_level_count: 1,
855 sample_count: 1,
856 dimension: wgpu::TextureDimension::D2,
857 format: wgpu::TextureFormat::Rgba8UnormSrgb,
858 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
859 view_formats: &[],
860 });
861 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
862 self.queue.write_texture(
864 wgpu::ImageCopyTexture {
865 texture: &texture,
866 mip_level: 0,
867 origin: wgpu::Origin3d::ZERO,
868 aspect: wgpu::TextureAspect::All,
869 },
870 data,
871 wgpu::ImageDataLayout {
872 offset: 0,
873 bytes_per_row: Some(4 * width),
874 rows_per_image: Some(height),
875 },
876 wgpu::Extent3d {
877 width,
878 height,
879 depth_or_array_layers: 1,
880 },
881 );
882 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
883 label: Some("Image Bind Group"),
884 layout: &self.image_bind_group_layout,
885 entries: &[
886 wgpu::BindGroupEntry {
887 binding: 0,
888 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
889 },
890 wgpu::BindGroupEntry {
891 binding: 1,
892 resource: wgpu::BindingResource::TextureView(&texture_view),
893 },
894 ],
895 });
896 (texture, texture_view, bind_group)
897 }
898
899 pub fn create_vertex_buffer(&self, vertices: &[Vertex]) -> wgpu::Buffer {
901 self.device
902 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
903 label: Some("Vertex Buffer"),
904 contents: bytemuck::cast_slice(vertices),
905 usage: wgpu::BufferUsages::VERTEX,
906 })
907 }
908
909 pub fn vertex_buffer_from_sources(
911 &self,
912 gpu: Option<&GpuVertexBuffer>,
913 cpu_vertices: &[Vertex],
914 ) -> Option<Arc<wgpu::Buffer>> {
915 if let Some(buffer) = gpu {
916 Some(buffer.buffer.clone())
917 } else if !cpu_vertices.is_empty() {
918 Some(Arc::new(self.create_vertex_buffer(cpu_vertices)))
919 } else {
920 None
921 }
922 }
923
924 pub fn create_direct_point_vertices(&self, points: &[Vertex], size_px: f32) -> Vec<Vertex> {
927 let corners: [[f32; 2]; 6] = [
928 [-1.0, -1.0],
929 [1.0, -1.0],
930 [1.0, 1.0],
931 [-1.0, -1.0],
932 [1.0, 1.0],
933 [-1.0, 1.0],
934 ];
935 let mut out = Vec::with_capacity(points.len() * 6);
936 for p in points {
937 for c in corners {
938 let mut v = *p;
939 v.tex_coords = c; let sz = if size_px > 0.0 { size_px } else { p.normal[2] };
941 v.normal = [p.normal[0], p.normal[1], sz];
942 out.push(v);
943 }
944 }
945 out
946 }
947
948 pub fn create_index_buffer(&self, indices: &[u32]) -> wgpu::Buffer {
950 self.device
951 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
952 label: Some("Index Buffer"),
953 contents: bytemuck::cast_slice(indices),
954 usage: wgpu::BufferUsages::INDEX,
955 })
956 }
957
958 pub fn update_uniforms(&mut self, view_proj: Mat4, model: Mat4) {
960 self.uniforms.update_view_proj(view_proj);
961 self.uniforms.update_model(model);
962
963 self.queue.write_buffer(
964 &self.uniform_buffer,
965 0,
966 bytemuck::cast_slice(&[self.uniforms]),
967 );
968 self.ensure_axes_uniform_capacity(1);
969 self.queue.write_buffer(
970 &self.axes_uniform_buffers[0],
971 0,
972 bytemuck::cast_slice(&[self.uniforms]),
973 );
974 }
975
976 pub fn update_uniforms_for_axes(&mut self, axes_index: usize, view_proj: Mat4, model: Mat4) {
977 self.ensure_axes_uniform_capacity(axes_index + 1);
978 let mut uniforms = Uniforms::new();
979 uniforms.update_view_proj(view_proj);
980 uniforms.update_model(model);
981 self.queue.write_buffer(
982 &self.axes_uniform_buffers[axes_index],
983 0,
984 bytemuck::cast_slice(&[uniforms]),
985 );
986 }
987
988 pub fn update_marker_screen_uniforms(&mut self, viewport_px: [f32; 2]) {
989 let uniforms = MarkerScreenUniforms {
990 viewport_px: [viewport_px[0].max(1.0), viewport_px[1].max(1.0)],
991 _pad: [0.0, 0.0],
992 };
993 self.queue.write_buffer(
994 &self.marker_screen_uniform_buffer,
995 0,
996 bytemuck::cast_slice(&[uniforms]),
997 );
998 self.ensure_axes_uniform_capacity(1);
999 self.queue.write_buffer(
1000 &self.axes_marker_screen_uniform_buffers[0],
1001 0,
1002 bytemuck::cast_slice(&[uniforms]),
1003 );
1004 }
1005
1006 pub fn update_marker_screen_uniforms_for_axes(
1007 &mut self,
1008 axes_index: usize,
1009 viewport_px: [f32; 2],
1010 ) {
1011 self.ensure_axes_uniform_capacity(axes_index + 1);
1012 let uniforms = MarkerScreenUniforms {
1013 viewport_px: [viewport_px[0].max(1.0), viewport_px[1].max(1.0)],
1014 _pad: [0.0, 0.0],
1015 };
1016 self.queue.write_buffer(
1017 &self.axes_marker_screen_uniform_buffers[axes_index],
1018 0,
1019 bytemuck::cast_slice(&[uniforms]),
1020 );
1021 }
1022
1023 pub fn get_uniform_bind_group(&self) -> &wgpu::BindGroup {
1025 &self.uniform_bind_group
1026 }
1027
1028 pub fn get_uniform_bind_group_for_axes(&self, axes_index: usize) -> &wgpu::BindGroup {
1029 self.axes_uniform_bind_groups
1030 .get(axes_index)
1031 .unwrap_or(&self.uniform_bind_group)
1032 }
1033
1034 pub fn get_marker_screen_bind_group(&self) -> &wgpu::BindGroup {
1035 &self.marker_screen_bind_group
1036 }
1037
1038 pub fn get_marker_screen_bind_group_for_axes(&self, axes_index: usize) -> &wgpu::BindGroup {
1039 self.axes_marker_screen_bind_groups
1040 .get(axes_index)
1041 .unwrap_or(&self.marker_screen_bind_group)
1042 }
1043
1044 pub fn ensure_pipeline(&mut self, pipeline_type: PipelineType) {
1046 match pipeline_type {
1047 PipelineType::Points => {
1048 if self.point_pipeline.is_none() {
1049 self.point_pipeline = Some(self.create_point_pipeline());
1050 }
1051 }
1052 PipelineType::Lines => {
1053 if self.line_pipeline.is_none() {
1054 self.line_pipeline = Some(self.create_line_pipeline());
1055 }
1056 }
1057 PipelineType::Triangles => {
1058 if self.triangle_pipeline.is_none() {
1059 self.triangle_pipeline = Some(self.create_triangle_pipeline());
1060 }
1061 }
1062 PipelineType::Scatter3 => {
1063 self.ensure_pipeline(PipelineType::Points);
1065 }
1066 PipelineType::Textured => {
1067 if self.image_pipeline.is_none() {
1068 self.image_pipeline = Some(self.create_image_pipeline());
1069 }
1070 }
1071 }
1072 }
1073
1074 pub fn get_pipeline(&self, pipeline_type: PipelineType) -> &wgpu::RenderPipeline {
1076 match pipeline_type {
1077 PipelineType::Points => self.point_pipeline.as_ref().unwrap(),
1078 PipelineType::Lines => self.line_pipeline.as_ref().unwrap(),
1079 PipelineType::Triangles => self.triangle_pipeline.as_ref().unwrap(),
1080 PipelineType::Scatter3 => self.get_pipeline(PipelineType::Points),
1081 PipelineType::Textured => self.image_pipeline.as_ref().unwrap(),
1082 }
1083 }
1084
1085 pub fn ensure_grid_plane_pipeline(&mut self) {
1086 if self.grid_plane_pipeline.is_none() {
1087 self.grid_plane_pipeline = Some(self.create_grid_plane_pipeline());
1088 }
1089 }
1090
1091 pub fn grid_plane_pipeline(&self) -> Option<&wgpu::RenderPipeline> {
1092 self.grid_plane_pipeline.as_ref()
1093 }
1094
1095 fn create_point_pipeline(&self) -> wgpu::RenderPipeline {
1097 let shader = self
1098 .device
1099 .create_shader_module(wgpu::ShaderModuleDescriptor {
1100 label: Some("Point Billboard Shader"),
1101 source: wgpu::ShaderSource::Wgsl(shaders::vertex::POINT_BILLBOARD.into()),
1102 });
1103
1104 let pipeline_layout = self
1105 .device
1106 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1107 label: Some("Point Pipeline Layout"),
1108 bind_group_layouts: &[
1109 &self.uniform_bind_group_layout,
1110 &self.point_style_bind_group_layout,
1111 &self.marker_screen_bind_group_layout,
1112 ],
1113 push_constant_ranges: &[],
1114 });
1115
1116 self.device
1117 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1118 label: Some("Point Billboard Pipeline"),
1119 layout: Some(&pipeline_layout),
1120 vertex: wgpu::VertexState {
1121 module: &shader,
1122 entry_point: "vs_main",
1123 buffers: &[Vertex::desc()],
1124 },
1125 fragment: Some(wgpu::FragmentState {
1126 module: &shader,
1127 entry_point: "fs_main",
1128 targets: &[Some(wgpu::ColorTargetState {
1129 format: self.surface_config.format,
1130 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1131 write_mask: wgpu::ColorWrites::ALL,
1132 })],
1133 }),
1134 primitive: wgpu::PrimitiveState {
1135 topology: wgpu::PrimitiveTopology::TriangleList,
1136 strip_index_format: None,
1137 front_face: wgpu::FrontFace::Ccw,
1138 cull_mode: None,
1139 polygon_mode: wgpu::PolygonMode::Fill,
1140 unclipped_depth: false,
1141 conservative: false,
1142 },
1143 depth_stencil: Some(wgpu::DepthStencilState {
1144 format: Self::depth_format(),
1145 depth_write_enabled: true,
1146 depth_compare: self.depth_compare(),
1147 stencil: wgpu::StencilState::default(),
1148 bias: wgpu::DepthBiasState::default(),
1149 }),
1150 multisample: wgpu::MultisampleState {
1151 count: self.msaa_sample_count,
1152 mask: !0,
1153 alpha_to_coverage_enabled: false,
1154 },
1155 multiview: None,
1156 })
1157 }
1158
1159 fn create_line_pipeline(&self) -> wgpu::RenderPipeline {
1161 let shader = self
1162 .device
1163 .create_shader_module(wgpu::ShaderModuleDescriptor {
1164 label: Some("Line Shader"),
1165 source: wgpu::ShaderSource::Wgsl(shaders::vertex::LINE.into()),
1166 });
1167
1168 let pipeline_layout = self
1169 .device
1170 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1171 label: Some("Line Pipeline Layout"),
1172 bind_group_layouts: &[&self.uniform_bind_group_layout],
1173 push_constant_ranges: &[],
1174 });
1175
1176 self.device
1177 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1178 label: Some("Line Pipeline"),
1179 layout: Some(&pipeline_layout),
1180 vertex: wgpu::VertexState {
1181 module: &shader,
1182 entry_point: "vs_main",
1183 buffers: &[Vertex::desc()],
1184 },
1185 fragment: Some(wgpu::FragmentState {
1186 module: &shader,
1187 entry_point: "fs_main",
1188 targets: &[Some(wgpu::ColorTargetState {
1189 format: self.surface_config.format,
1190 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1191 write_mask: wgpu::ColorWrites::ALL,
1192 })],
1193 }),
1194 primitive: wgpu::PrimitiveState {
1195 topology: wgpu::PrimitiveTopology::LineList,
1196 strip_index_format: None,
1197 front_face: wgpu::FrontFace::Ccw,
1198 cull_mode: None,
1199 polygon_mode: wgpu::PolygonMode::Fill,
1200 unclipped_depth: false,
1201 conservative: false,
1202 },
1203 depth_stencil: Some(wgpu::DepthStencilState {
1204 format: Self::depth_format(),
1205 depth_write_enabled: true,
1206 depth_compare: self.depth_compare(),
1207 stencil: wgpu::StencilState::default(),
1208 bias: wgpu::DepthBiasState::default(),
1209 }),
1210 multisample: wgpu::MultisampleState {
1211 count: self.msaa_sample_count,
1212 mask: !0,
1213 alpha_to_coverage_enabled: false,
1214 },
1215 multiview: None,
1216 })
1217 }
1218
1219 fn create_direct_line_pipeline(&self) -> wgpu::RenderPipeline {
1221 let shader = self
1222 .device
1223 .create_shader_module(wgpu::ShaderModuleDescriptor {
1224 label: Some("Direct Line Shader"),
1225 source: wgpu::ShaderSource::Wgsl(shaders::vertex::LINE_DIRECT.into()),
1226 });
1227
1228 let pipeline_layout = self
1229 .device
1230 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1231 label: Some("Direct Line Pipeline Layout"),
1232 bind_group_layouts: &[&self.direct_uniform_bind_group_layout],
1233 push_constant_ranges: &[],
1234 });
1235
1236 self.device
1237 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1238 label: Some("Direct Line Pipeline"),
1239 layout: Some(&pipeline_layout),
1240 vertex: wgpu::VertexState {
1241 module: &shader,
1242 entry_point: "vs_main",
1243 buffers: &[Vertex::desc()],
1244 },
1245 fragment: Some(wgpu::FragmentState {
1246 module: &shader,
1247 entry_point: "fs_main",
1248 targets: &[Some(wgpu::ColorTargetState {
1249 format: self.surface_config.format,
1250 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1251 write_mask: wgpu::ColorWrites::ALL,
1252 })],
1253 }),
1254 primitive: wgpu::PrimitiveState {
1255 topology: wgpu::PrimitiveTopology::LineList,
1256 strip_index_format: None,
1257 front_face: wgpu::FrontFace::Ccw,
1258 cull_mode: None,
1259 polygon_mode: wgpu::PolygonMode::Fill,
1260 unclipped_depth: false,
1261 conservative: false,
1262 },
1263 depth_stencil: Some(wgpu::DepthStencilState {
1267 format: Self::depth_format(),
1268 depth_write_enabled: false,
1269 depth_compare: wgpu::CompareFunction::Always,
1270 stencil: wgpu::StencilState::default(),
1271 bias: wgpu::DepthBiasState::default(),
1272 }),
1273 multisample: wgpu::MultisampleState {
1274 count: self.msaa_sample_count,
1275 mask: !0,
1276 alpha_to_coverage_enabled: false,
1277 },
1278 multiview: None,
1279 })
1280 }
1281
1282 fn create_direct_triangle_pipeline(&self) -> wgpu::RenderPipeline {
1284 let shader = self
1285 .device
1286 .create_shader_module(wgpu::ShaderModuleDescriptor {
1287 label: Some("Direct Triangle Shader"),
1288 source: wgpu::ShaderSource::Wgsl(shaders::vertex::LINE_DIRECT.into()),
1289 });
1290
1291 let pipeline_layout = self
1292 .device
1293 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1294 label: Some("Direct Triangle Pipeline Layout"),
1295 bind_group_layouts: &[&self.direct_uniform_bind_group_layout],
1296 push_constant_ranges: &[],
1297 });
1298
1299 self.device
1300 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1301 label: Some("Direct Triangle Pipeline"),
1302 layout: Some(&pipeline_layout),
1303 vertex: wgpu::VertexState {
1304 module: &shader,
1305 entry_point: "vs_main",
1306 buffers: &[Vertex::desc()],
1307 },
1308 fragment: Some(wgpu::FragmentState {
1309 module: &shader,
1310 entry_point: "fs_main",
1311 targets: &[Some(wgpu::ColorTargetState {
1312 format: self.surface_config.format,
1313 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1314 write_mask: wgpu::ColorWrites::ALL,
1315 })],
1316 }),
1317 primitive: wgpu::PrimitiveState {
1318 topology: wgpu::PrimitiveTopology::TriangleList,
1319 strip_index_format: None,
1320 front_face: wgpu::FrontFace::Ccw,
1321 cull_mode: None,
1322 polygon_mode: wgpu::PolygonMode::Fill,
1323 unclipped_depth: false,
1324 conservative: false,
1325 },
1326 depth_stencil: Some(wgpu::DepthStencilState {
1327 format: Self::depth_format(),
1328 depth_write_enabled: false,
1329 depth_compare: wgpu::CompareFunction::Always,
1330 stencil: wgpu::StencilState::default(),
1331 bias: wgpu::DepthBiasState::default(),
1332 }),
1333 multisample: wgpu::MultisampleState {
1334 count: self.msaa_sample_count,
1335 mask: !0,
1336 alpha_to_coverage_enabled: false,
1337 },
1338 multiview: None,
1339 })
1340 }
1341
1342 fn create_direct_point_pipeline(&self) -> wgpu::RenderPipeline {
1344 let shader = self
1345 .device
1346 .create_shader_module(wgpu::ShaderModuleDescriptor {
1347 label: Some("Direct Point Shader"),
1348 source: wgpu::ShaderSource::Wgsl(shaders::vertex::POINT_DIRECT.into()),
1349 });
1350
1351 let pipeline_layout = self
1352 .device
1353 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1354 label: Some("Direct Point Pipeline Layout"),
1355 bind_group_layouts: &[
1356 &self.direct_uniform_bind_group_layout,
1357 &self.point_style_bind_group_layout,
1358 ],
1359 push_constant_ranges: &[],
1360 });
1361
1362 self.device
1363 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1364 label: Some("Direct Point Pipeline"),
1365 layout: Some(&pipeline_layout),
1366 vertex: wgpu::VertexState {
1367 module: &shader,
1368 entry_point: "vs_main",
1369 buffers: &[Vertex::desc()],
1370 },
1371 fragment: Some(wgpu::FragmentState {
1372 module: &shader,
1373 entry_point: "fs_main",
1374 targets: &[Some(wgpu::ColorTargetState {
1375 format: self.surface_config.format,
1376 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1377 write_mask: wgpu::ColorWrites::ALL,
1378 })],
1379 }),
1380 primitive: wgpu::PrimitiveState {
1381 topology: wgpu::PrimitiveTopology::TriangleList,
1382 strip_index_format: None,
1383 front_face: wgpu::FrontFace::Ccw,
1384 cull_mode: None,
1385 polygon_mode: wgpu::PolygonMode::Fill,
1386 unclipped_depth: false,
1387 conservative: false,
1388 },
1389 depth_stencil: Some(wgpu::DepthStencilState {
1390 format: Self::depth_format(),
1391 depth_write_enabled: false,
1392 depth_compare: wgpu::CompareFunction::Always,
1393 stencil: wgpu::StencilState::default(),
1394 bias: wgpu::DepthBiasState::default(),
1395 }),
1396 multisample: wgpu::MultisampleState {
1397 count: self.msaa_sample_count,
1398 mask: !0,
1399 alpha_to_coverage_enabled: false,
1400 },
1401 multiview: None,
1402 })
1403 }
1404
1405 pub fn create_point_style_bind_group(
1407 &self,
1408 style: PointStyleUniforms,
1409 ) -> (wgpu::Buffer, wgpu::BindGroup) {
1410 let buffer = self
1411 .device
1412 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1413 label: Some("Point Style Uniform Buffer"),
1414 contents: bytemuck::bytes_of(&style),
1415 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1416 });
1417 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1418 label: Some("Point Style Bind Group"),
1419 layout: &self.point_style_bind_group_layout,
1420 entries: &[wgpu::BindGroupEntry {
1421 binding: 0,
1422 resource: buffer.as_entire_binding(),
1423 }],
1424 });
1425 (buffer, bind_group)
1426 }
1427
1428 fn create_image_pipeline(&self) -> wgpu::RenderPipeline {
1430 let shader = self
1431 .device
1432 .create_shader_module(wgpu::ShaderModuleDescriptor {
1433 label: Some("Image Direct Shader"),
1434 source: wgpu::ShaderSource::Wgsl(shaders::vertex::IMAGE_DIRECT.into()),
1435 });
1436
1437 let pipeline_layout = self
1438 .device
1439 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1440 label: Some("Image Pipeline Layout"),
1441 bind_group_layouts: &[
1442 &self.direct_uniform_bind_group_layout,
1443 &self.image_bind_group_layout,
1444 ],
1445 push_constant_ranges: &[],
1446 });
1447
1448 self.device
1449 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1450 label: Some("Image Pipeline"),
1451 layout: Some(&pipeline_layout),
1452 vertex: wgpu::VertexState {
1453 module: &shader,
1454 entry_point: "vs_main",
1455 buffers: &[Vertex::desc()],
1456 },
1457 fragment: Some(wgpu::FragmentState {
1458 module: &shader,
1459 entry_point: "fs_main",
1460 targets: &[Some(wgpu::ColorTargetState {
1461 format: self.surface_config.format,
1462 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1463 write_mask: wgpu::ColorWrites::ALL,
1464 })],
1465 }),
1466 primitive: wgpu::PrimitiveState {
1467 topology: wgpu::PrimitiveTopology::TriangleList,
1468 strip_index_format: None,
1469 front_face: wgpu::FrontFace::Ccw,
1470 cull_mode: None,
1471 polygon_mode: wgpu::PolygonMode::Fill,
1472 unclipped_depth: false,
1473 conservative: false,
1474 },
1475 depth_stencil: Some(wgpu::DepthStencilState {
1476 format: Self::depth_format(),
1477 depth_write_enabled: false,
1478 depth_compare: wgpu::CompareFunction::Always,
1479 stencil: wgpu::StencilState::default(),
1480 bias: wgpu::DepthBiasState::default(),
1481 }),
1482 multisample: wgpu::MultisampleState {
1483 count: self.msaa_sample_count,
1484 mask: !0,
1485 alpha_to_coverage_enabled: false,
1486 },
1487 multiview: None,
1488 })
1489 }
1490
1491 fn create_triangle_pipeline(&self) -> wgpu::RenderPipeline {
1493 let shader = self
1494 .device
1495 .create_shader_module(wgpu::ShaderModuleDescriptor {
1496 label: Some("Triangle Shader"),
1497 source: wgpu::ShaderSource::Wgsl(shaders::vertex::TRIANGLE.into()),
1498 });
1499
1500 let pipeline_layout = self
1501 .device
1502 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1503 label: Some("Triangle Pipeline Layout"),
1504 bind_group_layouts: &[&self.uniform_bind_group_layout],
1505 push_constant_ranges: &[],
1506 });
1507
1508 self.device
1509 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1510 label: Some("Triangle Pipeline"),
1511 layout: Some(&pipeline_layout),
1512 vertex: wgpu::VertexState {
1513 module: &shader,
1514 entry_point: "vs_main",
1515 buffers: &[Vertex::desc()],
1516 },
1517 fragment: Some(wgpu::FragmentState {
1518 module: &shader,
1519 entry_point: "fs_main",
1520 targets: &[Some(wgpu::ColorTargetState {
1521 format: self.surface_config.format,
1522 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1523 write_mask: wgpu::ColorWrites::ALL,
1524 })],
1525 }),
1526 primitive: wgpu::PrimitiveState {
1527 topology: wgpu::PrimitiveTopology::TriangleList,
1528 strip_index_format: None,
1529 front_face: wgpu::FrontFace::Ccw,
1530 cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
1532 unclipped_depth: false,
1533 conservative: false,
1534 },
1535 depth_stencil: Some(wgpu::DepthStencilState {
1536 format: Self::depth_format(),
1537 depth_write_enabled: true,
1538 depth_compare: self.depth_compare(),
1539 stencil: wgpu::StencilState::default(),
1540 bias: wgpu::DepthBiasState::default(),
1541 }),
1542 multisample: wgpu::MultisampleState {
1543 count: self.msaa_sample_count,
1544 mask: !0,
1545 alpha_to_coverage_enabled: false,
1546 },
1547 multiview: None,
1548 })
1549 }
1550
1551 fn create_grid_plane_pipeline(&self) -> wgpu::RenderPipeline {
1552 let shader = self
1553 .device
1554 .create_shader_module(wgpu::ShaderModuleDescriptor {
1555 label: Some("Grid Plane Shader"),
1556 source: wgpu::ShaderSource::Wgsl(shaders::vertex::GRID_PLANE.into()),
1557 });
1558
1559 let pipeline_layout = self
1560 .device
1561 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1562 label: Some("Grid Plane Pipeline Layout"),
1563 bind_group_layouts: &[
1564 &self.uniform_bind_group_layout,
1565 &self.grid_uniform_bind_group_layout,
1566 ],
1567 push_constant_ranges: &[],
1568 });
1569
1570 self.device
1571 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1572 label: Some("Grid Plane Pipeline"),
1573 layout: Some(&pipeline_layout),
1574 vertex: wgpu::VertexState {
1575 module: &shader,
1576 entry_point: "vs_main",
1577 buffers: &[Vertex::desc()],
1578 },
1579 fragment: Some(wgpu::FragmentState {
1580 module: &shader,
1581 entry_point: "fs_main",
1582 targets: &[Some(wgpu::ColorTargetState {
1583 format: self.surface_config.format,
1584 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1585 write_mask: wgpu::ColorWrites::ALL,
1586 })],
1587 }),
1588 primitive: wgpu::PrimitiveState {
1589 topology: wgpu::PrimitiveTopology::TriangleList,
1590 strip_index_format: None,
1591 front_face: wgpu::FrontFace::Ccw,
1592 cull_mode: None,
1593 polygon_mode: wgpu::PolygonMode::Fill,
1594 unclipped_depth: false,
1595 conservative: false,
1596 },
1597 depth_stencil: Some(wgpu::DepthStencilState {
1598 format: Self::depth_format(),
1599 depth_write_enabled: false,
1600 depth_compare: self.depth_compare(),
1601 stencil: wgpu::StencilState::default(),
1602 bias: wgpu::DepthBiasState::default(),
1603 }),
1604 multisample: wgpu::MultisampleState {
1605 count: self.msaa_sample_count,
1606 mask: !0,
1607 alpha_to_coverage_enabled: false,
1608 },
1609 multiview: None,
1610 })
1611 }
1612
1613 pub fn begin_render_pass<'a>(
1615 &'a self,
1616 encoder: &'a mut wgpu::CommandEncoder,
1617 view: &'a wgpu::TextureView,
1618 _depth_view: &'a wgpu::TextureView,
1619 ) -> wgpu::RenderPass<'a> {
1620 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1621 label: Some("Render Pass"),
1622 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1623 view,
1624 resolve_target: None,
1625 ops: wgpu::Operations {
1626 load: wgpu::LoadOp::Clear(wgpu::Color {
1627 r: 0.1,
1628 g: 0.1,
1629 b: 0.1,
1630 a: 1.0,
1631 }),
1632 store: wgpu::StoreOp::Store,
1633 },
1634 })],
1635 depth_stencil_attachment: None, occlusion_query_set: None,
1637 timestamp_writes: None,
1638 })
1639 }
1640
1641 pub fn render_vertices<'a>(
1643 &'a mut self,
1644 render_pass: &mut wgpu::RenderPass<'a>,
1645 pipeline_type: PipelineType,
1646 vertex_buffer: &'a wgpu::Buffer,
1647 vertex_count: u32,
1648 index_buffer: Option<(&'a wgpu::Buffer, u32)>,
1649 indirect: Option<(&'a wgpu::Buffer, u64)>,
1650 ) {
1651 self.ensure_pipeline(pipeline_type);
1653
1654 let pipeline = self.get_pipeline(pipeline_type);
1656 render_pass.set_pipeline(pipeline);
1657 render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
1658 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1659
1660 if let Some((args, offset)) = indirect {
1661 render_pass.draw_indirect(args, offset);
1662 return;
1663 }
1664
1665 match index_buffer {
1666 Some((indices, index_count)) => {
1667 render_pass.set_index_buffer(indices.slice(..), wgpu::IndexFormat::Uint32);
1668 render_pass.draw_indexed(0..index_count, 0, 0..1);
1669 }
1670 None => {
1671 render_pass.draw(0..vertex_count, 0..1);
1672 }
1673 }
1674 }
1675
1676 pub fn ensure_direct_line_pipeline(&mut self) {
1678 if self.direct_line_pipeline.is_none() {
1679 self.direct_line_pipeline = Some(self.create_direct_line_pipeline());
1680 }
1681 }
1682
1683 pub fn ensure_direct_triangle_pipeline(&mut self) {
1685 if self.direct_triangle_pipeline.is_none() {
1686 self.direct_triangle_pipeline = Some(self.create_direct_triangle_pipeline());
1687 }
1688 }
1689
1690 pub fn ensure_direct_point_pipeline(&mut self) {
1692 if self.direct_point_pipeline.is_none() {
1693 self.direct_point_pipeline = Some(self.create_direct_point_pipeline());
1694 }
1695 }
1696
1697 pub fn ensure_image_pipeline(&mut self) {
1699 if self.image_pipeline.is_none() {
1700 self.image_pipeline = Some(self.create_image_pipeline());
1701 }
1702 }
1703
1704 pub fn update_direct_uniforms(
1706 &mut self,
1707 data_min: [f32; 2],
1708 data_max: [f32; 2],
1709 viewport_min: [f32; 2],
1710 viewport_max: [f32; 2],
1711 viewport_px: [f32; 2],
1712 ) {
1713 self.direct_uniforms =
1714 DirectUniforms::new(data_min, data_max, viewport_min, viewport_max, viewport_px);
1715 self.queue.write_buffer(
1716 &self.direct_uniform_buffer,
1717 0,
1718 bytemuck::cast_slice(&[self.direct_uniforms]),
1719 );
1720 self.ensure_axes_uniform_capacity(1);
1721 self.queue.write_buffer(
1722 &self.axes_direct_uniform_buffers[0],
1723 0,
1724 bytemuck::cast_slice(&[self.direct_uniforms]),
1725 );
1726 }
1727
1728 pub fn update_direct_uniforms_for_axes(
1729 &mut self,
1730 axes_index: usize,
1731 data_min: [f32; 2],
1732 data_max: [f32; 2],
1733 viewport_min: [f32; 2],
1734 viewport_max: [f32; 2],
1735 viewport_px: [f32; 2],
1736 ) {
1737 self.ensure_axes_uniform_capacity(axes_index + 1);
1738 let uniforms =
1739 DirectUniforms::new(data_min, data_max, viewport_min, viewport_max, viewport_px);
1740 self.queue.write_buffer(
1741 &self.axes_direct_uniform_buffers[axes_index],
1742 0,
1743 bytemuck::cast_slice(&[uniforms]),
1744 );
1745 }
1746
1747 pub fn get_direct_uniform_bind_group_for_axes(&self, axes_index: usize) -> &wgpu::BindGroup {
1748 self.axes_direct_uniform_bind_groups
1749 .get(axes_index)
1750 .unwrap_or(&self.direct_uniform_bind_group)
1751 }
1752}
1753
1754pub mod vertex_utils {
1756 use super::*;
1757 use glam::Vec2;
1758
1759 pub fn create_line(start: Vec3, end: Vec3, color: Vec4) -> Vec<Vertex> {
1761 vec![Vertex::new(start, color), Vertex::new(end, color)]
1762 }
1763
1764 pub fn extrude_polyline(points: &[Vec3], color: Vec4, width: f32) -> Vec<Vertex> {
1767 let mut out: Vec<Vertex> = Vec::new();
1768 if points.len() < 2 {
1769 return out;
1770 }
1771 let half_w = width.max(0.0) * 0.5;
1775 for i in 0..points.len() - 1 {
1776 let p0 = points[i];
1777 let p1 = points[i + 1];
1778 let dir = (p1 - p0).truncate();
1779 let len = (dir.x * dir.x + dir.y * dir.y).sqrt().max(1e-6);
1780 let nx = -dir.y / len;
1781 let ny = dir.x / len;
1782 let offset = Vec3::new(nx * half_w, ny * half_w, 0.0);
1783 let a = p0 - offset;
1785 let b = p0 + offset;
1786 let c = p1 + offset;
1787 let d = p1 - offset;
1788 out.push(Vertex::new(a, color));
1790 out.push(Vertex::new(b, color));
1791 out.push(Vertex::new(c, color));
1792 out.push(Vertex::new(a, color));
1793 out.push(Vertex::new(c, color));
1794 out.push(Vertex::new(d, color));
1795 }
1796 out
1797 }
1798
1799 fn line_intersection(p: Vec2, r: Vec2, q: Vec2, s: Vec2) -> Option<Vec2> {
1800 let rxs = r.perp_dot(s);
1801 if rxs.abs() < 1e-6 {
1802 return None;
1803 }
1804 let t = (q - p).perp_dot(s) / rxs;
1805 Some(p + r * t)
1806 }
1807
1808 pub fn extrude_polyline_with_join(
1810 points: &[Vec3],
1811 color: Vec4,
1812 width: f32,
1813 join: crate::plots::line::LineJoin,
1814 ) -> Vec<Vertex> {
1815 let mut out: Vec<Vertex> = Vec::new();
1816 if points.len() < 2 {
1817 return out;
1818 }
1819 let half_w = width.max(0.0) * 0.5;
1821 out.extend(extrude_polyline(points, color, width));
1823
1824 for i in 1..points.len() - 1 {
1826 let p_prev = points[i - 1];
1827 let p = points[i];
1828 let p_next = points[i + 1];
1829 let d0 = (p - p_prev).truncate();
1830 let d1 = (p_next - p).truncate();
1831 let l0 = d0.length().max(1e-6);
1832 let l1 = d1.length().max(1e-6);
1833 let n0 = Vec2::new(-d0.y / l0, d0.x / l0);
1834 let n1 = Vec2::new(-d1.y / l1, d1.x / l1);
1835 let turn = d0.perp_dot(d1); if turn > 1e-6 {
1838 let left0 = p.truncate() + n0 * half_w;
1840 let left1 = p.truncate() + n1 * half_w;
1841 match join {
1842 crate::plots::line::LineJoin::Bevel => {
1843 out.push(Vertex::new(p, color));
1845 out.push(Vertex::new(left0.extend(0.0), color));
1846 out.push(Vertex::new(left1.extend(0.0), color));
1847 }
1848 crate::plots::line::LineJoin::Miter => {
1849 let dir_edge0 = (p.truncate() - p_prev.truncate()).normalize_or_zero();
1850 let dir_edge1 = (p_next.truncate() - p.truncate()).normalize_or_zero();
1851 let l_edge = line_intersection(left0, dir_edge0, left1, dir_edge1);
1852 if let Some(miter) = l_edge {
1853 out.push(Vertex::new(left0.extend(0.0), color));
1855 out.push(Vertex::new(miter.extend(0.0), color));
1856 out.push(Vertex::new(left1.extend(0.0), color));
1857 } else {
1858 out.push(Vertex::new(p, color));
1860 out.push(Vertex::new(left0.extend(0.0), color));
1861 out.push(Vertex::new(left1.extend(0.0), color));
1862 }
1863 }
1864 crate::plots::line::LineJoin::Round => {
1865 let center = p.truncate();
1867 let a0 = (left0 - center).to_array();
1868 let a1 = (left1 - center).to_array();
1869 let ang0 = a0[1].atan2(a0[0]);
1870 let mut ang1 = a1[1].atan2(a1[0]);
1871 if ang1 < ang0 {
1873 ang1 += std::f32::consts::TAU;
1874 }
1875 let steps = 10usize;
1876 let dtheta = (ang1 - ang0) / steps as f32;
1877 let r = half_w;
1878 for k in 0..steps {
1879 let theta0 = ang0 + dtheta * k as f32;
1880 let theta1 = ang0 + dtheta * (k + 1) as f32;
1881 let v0 =
1882 Vec2::new(center.x + theta0.cos() * r, center.y + theta0.sin() * r);
1883 let v1 =
1884 Vec2::new(center.x + theta1.cos() * r, center.y + theta1.sin() * r);
1885 out.push(Vertex::new(p, color));
1886 out.push(Vertex::new(v0.extend(0.0), color));
1887 out.push(Vertex::new(v1.extend(0.0), color));
1888 }
1889 }
1890 }
1891 } else if turn < -1e-6 {
1892 let right0 = p.truncate() - n0 * half_w;
1894 let right1 = p.truncate() - n1 * half_w;
1895 match join {
1896 crate::plots::line::LineJoin::Bevel => {
1897 out.push(Vertex::new(p, color));
1898 out.push(Vertex::new(right1.extend(0.0), color));
1899 out.push(Vertex::new(right0.extend(0.0), color));
1900 }
1901 crate::plots::line::LineJoin::Miter => {
1902 let dir_edge0 = (p.truncate() - p_prev.truncate()).normalize_or_zero();
1903 let dir_edge1 = (p_next.truncate() - p.truncate()).normalize_or_zero();
1904 let l_edge = line_intersection(right0, dir_edge0, right1, dir_edge1);
1905 if let Some(miter) = l_edge {
1906 out.push(Vertex::new(right1.extend(0.0), color));
1907 out.push(Vertex::new(miter.extend(0.0), color));
1908 out.push(Vertex::new(right0.extend(0.0), color));
1909 } else {
1910 out.push(Vertex::new(p, color));
1911 out.push(Vertex::new(right1.extend(0.0), color));
1912 out.push(Vertex::new(right0.extend(0.0), color));
1913 }
1914 }
1915 crate::plots::line::LineJoin::Round => {
1916 let center = p.truncate();
1917 let a0 = (right0 - center).to_array();
1918 let a1 = (right1 - center).to_array();
1919 let mut ang0 = a0[1].atan2(a0[0]);
1920 let mut ang1 = a1[1].atan2(a1[0]);
1921 if ang0 < ang1 {
1923 std::mem::swap(&mut ang0, &mut ang1);
1924 }
1925 let steps = 10usize;
1926 let dtheta = (ang0 - ang1) / steps as f32;
1927 let r = half_w;
1928 for k in 0..steps {
1929 let theta0 = ang0 - dtheta * k as f32;
1930 let theta1 = ang0 - dtheta * (k + 1) as f32;
1931 let v0 =
1932 Vec2::new(center.x + theta0.cos() * r, center.y + theta0.sin() * r);
1933 let v1 =
1934 Vec2::new(center.x + theta1.cos() * r, center.y + theta1.sin() * r);
1935 out.push(Vertex::new(p, color));
1936 out.push(Vertex::new(v0.extend(0.0), color));
1937 out.push(Vertex::new(v1.extend(0.0), color));
1938 }
1939 }
1940 }
1941 }
1942 }
1943
1944 out
1945 }
1946
1947 pub fn create_triangle(p1: Vec3, p2: Vec3, p3: Vec3, color: Vec4) -> Vec<Vertex> {
1949 vec![
1950 Vertex::new(p1, color),
1951 Vertex::new(p2, color),
1952 Vertex::new(p3, color),
1953 ]
1954 }
1955
1956 pub fn create_point_cloud(points: &[Vec3], colors: &[Vec4]) -> Vec<Vertex> {
1958 points
1959 .iter()
1960 .zip(colors.iter())
1961 .map(|(&pos, &color)| Vertex::new(pos, color))
1962 .collect()
1963 }
1964
1965 pub fn create_line_plot(x_data: &[f64], y_data: &[f64], color: Vec4) -> Vec<Vertex> {
1967 let mut vertices = Vec::new();
1968
1969 for i in 1..x_data.len() {
1970 let start = Vec3::new(x_data[i - 1] as f32, y_data[i - 1] as f32, 0.0);
1971 let end = Vec3::new(x_data[i] as f32, y_data[i] as f32, 0.0);
1972 vertices.extend(create_line(start, end, color));
1973 }
1974
1975 vertices
1976 }
1977
1978 pub fn create_line_plot_dashed(
1981 x_data: &[f64],
1982 y_data: &[f64],
1983 color: Vec4,
1984 style: crate::plots::line::LineStyle,
1985 ) -> Vec<Vertex> {
1986 let mut vertices = Vec::new();
1987 for i in 1..x_data.len() {
1988 let include = match style {
1989 crate::plots::line::LineStyle::Solid => true,
1990 crate::plots::line::LineStyle::Dashed => (i % 4) < 2, crate::plots::line::LineStyle::Dotted => false, crate::plots::line::LineStyle::DashDot => {
1993 let m = i % 6;
1994 m < 2 || m == 3 }
1996 };
1997 if include {
1998 let start = Vec3::new(x_data[i - 1] as f32, y_data[i - 1] as f32, 0.0);
1999 let end = Vec3::new(x_data[i] as f32, y_data[i] as f32, 0.0);
2000 vertices.extend(create_line(start, end, color));
2001 }
2002 }
2003 vertices
2004 }
2005
2006 pub fn create_thick_polyline(
2008 x_data: &[f64],
2009 y_data: &[f64],
2010 color: Vec4,
2011 width_px: f32,
2012 ) -> Vec<Vertex> {
2013 let mut pts: Vec<Vec3> = Vec::with_capacity(x_data.len());
2014 for i in 0..x_data.len() {
2015 pts.push(Vec3::new(x_data[i] as f32, y_data[i] as f32, 0.0));
2016 }
2017 extrude_polyline(&pts, color, width_px)
2018 }
2019
2020 pub fn create_thick_polyline_with_join(
2022 x_data: &[f64],
2023 y_data: &[f64],
2024 color: Vec4,
2025 width_px: f32,
2026 join: crate::plots::line::LineJoin,
2027 ) -> Vec<Vertex> {
2028 let mut pts: Vec<Vec3> = Vec::with_capacity(x_data.len());
2029 for i in 0..x_data.len() {
2030 pts.push(Vec3::new(x_data[i] as f32, y_data[i] as f32, 0.0));
2031 }
2032 extrude_polyline_with_join(&pts, color, width_px, join)
2033 }
2034
2035 pub fn create_thick_polyline_dashed(
2037 x_data: &[f64],
2038 y_data: &[f64],
2039 color: Vec4,
2040 width_px: f32,
2041 style: crate::plots::line::LineStyle,
2042 ) -> Vec<Vertex> {
2043 let mut out: Vec<Vertex> = Vec::new();
2044 if x_data.len() < 2 {
2045 return out;
2046 }
2047 let pts: Vec<Vec3> = x_data
2048 .iter()
2049 .zip(y_data.iter())
2050 .map(|(&x, &y)| Vec3::new(x as f32, y as f32, 0.0))
2051 .collect();
2052 for i in 0..pts.len() - 1 {
2053 let include = match style {
2054 crate::plots::line::LineStyle::Solid => true,
2055 crate::plots::line::LineStyle::Dashed => (i % 4) < 2,
2056 crate::plots::line::LineStyle::Dotted => false,
2057 crate::plots::line::LineStyle::DashDot => {
2058 let m = i % 6;
2059 m < 2 || m == 3
2060 }
2061 };
2062 if include {
2063 let seg = [pts[i], pts[i + 1]];
2064 out.extend(extrude_polyline(&seg, color, width_px));
2065 }
2066 }
2067 out
2068 }
2069
2070 pub fn create_thick_polyline_square_caps(
2072 x_data: &[f64],
2073 y_data: &[f64],
2074 color: Vec4,
2075 width_px: f32,
2076 ) -> Vec<Vertex> {
2077 if x_data.len() < 2 {
2078 return Vec::new();
2079 }
2080 let mut pts: Vec<Vec3> = Vec::with_capacity(x_data.len());
2081 for i in 0..x_data.len() {
2082 pts.push(Vec3::new(x_data[i] as f32, y_data[i] as f32, 0.0));
2083 }
2084 let dir0 = (pts[1] - pts[0]).truncate();
2086 let len0 = (dir0.x * dir0.x + dir0.y * dir0.y).sqrt().max(1e-6);
2087 let ext0 = Vec3::new(
2088 -(dir0.x / len0) * (width_px * 0.5),
2089 -(dir0.y / len0) * (width_px * 0.5),
2090 0.0,
2091 );
2092 pts[0] += ext0;
2093 let n = pts.len();
2095 let dir1 = (pts[n - 1] - pts[n - 2]).truncate();
2096 let len1 = (dir1.x * dir1.x + dir1.y * dir1.y).sqrt().max(1e-6);
2097 let ext1 = Vec3::new(
2098 (dir1.x / len1) * (width_px * 0.5),
2099 (dir1.y / len1) * (width_px * 0.5),
2100 0.0,
2101 );
2102 pts[n - 1] += ext1;
2103 extrude_polyline(&pts, color, width_px)
2104 }
2105
2106 pub fn create_thick_polyline_round_caps(
2108 x_data: &[f64],
2109 y_data: &[f64],
2110 color: Vec4,
2111 width_px: f32,
2112 segments: usize,
2113 ) -> Vec<Vertex> {
2114 let mut base = create_thick_polyline_square_caps(x_data, y_data, color, width_px);
2115 if x_data.len() < 2 {
2116 return base;
2117 }
2118 let r = width_px * 0.5;
2119 let p0 = Vec3::new(x_data[0] as f32, y_data[0] as f32, 0.0);
2121 let p1 = Vec3::new(x_data[1] as f32, y_data[1] as f32, 0.0);
2122 let dir0 = (p1 - p0).truncate();
2123 let theta0 = dir0.y.atan2(dir0.x) + std::f32::consts::PI; for i in 0..segments {
2125 let a0 = theta0 - std::f32::consts::PI * (i as f32 / segments as f32);
2126 let a1 = theta0 - std::f32::consts::PI * ((i + 1) as f32 / segments as f32);
2127 let v0 = Vec3::new(p0.x + a0.cos() * r, p0.y + a0.sin() * r, 0.0);
2128 let v1 = Vec3::new(p0.x + a1.cos() * r, p0.y + a1.sin() * r, 0.0);
2129 base.push(Vertex::new(p0, color));
2130 base.push(Vertex::new(v0, color));
2131 base.push(Vertex::new(v1, color));
2132 }
2133 let n = x_data.len();
2135 let q0 = Vec3::new(x_data[n - 2] as f32, y_data[n - 2] as f32, 0.0);
2136 let q1 = Vec3::new(x_data[n - 1] as f32, y_data[n - 1] as f32, 0.0);
2137 let dir1 = (q1 - q0).truncate();
2138 let theta1 = dir1.y.atan2(dir1.x);
2139 let center = q1;
2140 for i in 0..segments {
2141 let a0 = theta1 - std::f32::consts::PI * (i as f32 / segments as f32);
2142 let a1 = theta1 - std::f32::consts::PI * ((i + 1) as f32 / segments as f32);
2143 let v0 = Vec3::new(center.x + a0.cos() * r, center.y + a0.sin() * r, 0.0);
2144 let v1 = Vec3::new(center.x + a1.cos() * r, center.y + a1.sin() * r, 0.0);
2145 base.push(Vertex::new(center, color));
2146 base.push(Vertex::new(v0, color));
2147 base.push(Vertex::new(v1, color));
2148 }
2149 base
2150 }
2151
2152 pub fn create_scatter_plot(x_data: &[f64], y_data: &[f64], color: Vec4) -> Vec<Vertex> {
2154 x_data
2155 .iter()
2156 .zip(y_data.iter())
2157 .map(|(&x, &y)| Vertex::new(Vec3::new(x as f32, y as f32, 0.0), color))
2158 .collect()
2159 }
2160}