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