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