1use crate::core::{Camera, Scene, WgpuRenderer};
8use crate::plots::Figure;
9use glam::{Mat4, Vec3, Vec4};
10use std::sync::Arc;
11
12pub struct PlotRenderer {
14 pub wgpu_renderer: WgpuRenderer,
16
17 pub scene: Scene,
19
20 pub camera: Camera,
22
23 pub theme: crate::styling::PlotThemeConfig,
25
26 data_bounds: Option<(f64, f64, f64, f64)>,
28 needs_update: bool,
29}
30
31#[derive(Debug, Clone)]
33pub struct PlotRenderConfig {
34 pub width: u32,
36 pub height: u32,
37
38 pub background_color: Vec4,
40
41 pub show_grid: bool,
43
44 pub show_axes: bool,
46
47 pub show_title: bool,
49
50 pub msaa_samples: u32,
52
53 pub theme: crate::styling::PlotThemeConfig,
55}
56
57impl Default for PlotRenderConfig {
58 fn default() -> Self {
59 Self {
60 width: 800,
61 height: 600,
62 background_color: Vec4::new(0.08, 0.09, 0.11, 1.0), show_grid: true,
64 show_axes: true,
65 show_title: true,
66 msaa_samples: 4,
67 theme: crate::styling::PlotThemeConfig::default(),
68 }
69 }
70}
71
72#[derive(Debug)]
74pub struct RenderResult {
75 pub success: bool,
77
78 pub data_bounds: Option<(f64, f64, f64, f64)>,
80
81 pub vertex_count: usize,
83 pub triangle_count: usize,
84 pub render_time_ms: f64,
85}
86
87impl PlotRenderer {
88 pub async fn new(
90 device: Arc<wgpu::Device>,
91 queue: Arc<wgpu::Queue>,
92 surface_config: wgpu::SurfaceConfiguration,
93 ) -> Result<Self, Box<dyn std::error::Error>> {
94 let wgpu_renderer = WgpuRenderer::new(device, queue, surface_config).await;
95 let scene = Scene::new();
96 let camera = Self::create_default_camera();
97 let theme = crate::styling::PlotThemeConfig::default();
98
99 Ok(Self {
100 wgpu_renderer,
101 scene,
102 camera,
103 theme,
104 data_bounds: None,
105 needs_update: true,
106 })
107 }
108
109 pub fn set_figure(&mut self, figure: Figure) {
111 self.scene.clear();
113
114 self.add_figure_to_scene(figure);
116
117 self.needs_update = true;
119 }
120
121 fn add_figure_to_scene(&mut self, mut figure: Figure) {
123 use crate::core::SceneNode;
124
125 let render_data_list = figure.render_data();
127
128 for (node_id_counter, render_data) in render_data_list.into_iter().enumerate() {
129 let node = SceneNode {
131 id: node_id_counter as u64,
132 name: format!("Plot {node_id_counter}"),
133 transform: Mat4::IDENTITY,
134 visible: true,
135 cast_shadows: false,
136 receive_shadows: false,
137 parent: None,
138 children: Vec::new(),
139 render_data: Some(render_data),
140 bounds: crate::core::BoundingBox::default(),
141 lod_levels: Vec::new(),
142 current_lod: 0,
143 };
144
145 self.scene.add_node(node);
146 }
147
148 self.fit_camera_to_data();
151 }
152
153 pub fn calculate_data_bounds(&mut self) -> Option<(f64, f64, f64, f64)> {
155 let mut min_x = f64::INFINITY;
156 let mut max_x = f64::NEG_INFINITY;
157 let mut min_y = f64::INFINITY;
158 let mut max_y = f64::NEG_INFINITY;
159
160 for node in self.scene.get_visible_nodes() {
161 if let Some(render_data) = &node.render_data {
162 for vertex in &render_data.vertices {
163 let x = vertex.position[0] as f64;
164 let y = vertex.position[1] as f64;
165 min_x = min_x.min(x);
166 max_x = max_x.max(x);
167 min_y = min_y.min(y);
168 max_y = max_y.max(y);
169 }
170 }
171 }
172
173 if min_x != f64::INFINITY && max_x != f64::NEG_INFINITY {
174 let x_range = (max_x - min_x).max(0.1);
176 let y_range = (max_y - min_y).max(0.1);
177 let x_margin = x_range * 0.1;
178 let y_margin = y_range * 0.1;
179
180 let bounds = (
181 min_x - x_margin,
182 max_x + x_margin,
183 min_y - y_margin,
184 max_y + y_margin,
185 );
186
187 self.data_bounds = Some(bounds);
189 Some(bounds)
190 } else {
191 self.data_bounds = None;
192 None
193 }
194 }
195
196 pub fn fit_camera_to_data(&mut self) {
198 if let Some((x_min, x_max, y_min, y_max)) = self.calculate_data_bounds() {
199 if let crate::core::camera::ProjectionType::Orthographic {
201 ref mut left,
202 ref mut right,
203 ref mut bottom,
204 ref mut top,
205 ..
206 } = self.camera.projection
207 {
208 *left = -2.0;
210 *right = 4.0;
211 *bottom = -2.0;
212 *top = 4.0;
213
214 println!(
215 "CAMERA: Set orthographic bounds: left={}, right={}, bottom={}, top={}",
216 *left, *right, *bottom, *top
217 );
218 }
219
220 let center_x = (x_min + x_max) / 2.0;
222 let center_y = (y_min + y_max) / 2.0;
223 self.camera.position = Vec3::new(center_x as f32, center_y as f32, 5.0);
224 self.camera.target = Vec3::new(center_x as f32, center_y as f32, 0.0);
225 }
226 }
227
228 pub fn render_to_viewport(
230 &mut self,
231 encoder: &mut wgpu::CommandEncoder,
232 target_view: &wgpu::TextureView,
233 _viewport: (f32, f32, f32, f32), clear_background: bool,
235 background_color: Option<glam::Vec4>,
236 ) -> Result<RenderResult, Box<dyn std::error::Error>> {
237 let start_time = std::time::Instant::now();
238
239 let mut render_items = Vec::new();
241 let mut total_vertices = 0;
242 let mut total_triangles = 0;
243
244 for node in self.scene.get_visible_nodes() {
245 if let Some(render_data) = &node.render_data {
246 if !render_data.vertices.is_empty() {
247 self.wgpu_renderer
249 .ensure_pipeline(render_data.pipeline_type);
250
251 let vertex_buffer = self
253 .wgpu_renderer
254 .create_vertex_buffer(&render_data.vertices);
255
256 if render_data.vertices.len() == 12 {
258 println!(
259 "CRITICAL: {} vertices -> GPU, draw calls: {}",
260 render_data.vertices.len(),
261 render_data.draw_calls.len()
262 );
263 for (i, call) in render_data.draw_calls.iter().enumerate() {
264 println!(
265 " Call {}: offset={}, count={}",
266 i, call.vertex_offset, call.vertex_count
267 );
268 }
269 }
270
271 render_items.push((render_data, vertex_buffer));
272 total_vertices += render_data.vertices.len();
273
274 match render_data.pipeline_type {
276 crate::core::PipelineType::Triangles => {
277 total_triangles += render_data.vertices.len() / 3;
278 }
279 _ => {
280 }
282 }
283 }
284 }
285 }
286
287 let view_proj_matrix = self.camera.view_proj_matrix();
289
290 self.wgpu_renderer
291 .update_uniforms(view_proj_matrix, Mat4::IDENTITY);
292
293 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
295 label: Some("Viewport Plot Render Pass"),
296 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
297 view: target_view,
298 resolve_target: None,
299 ops: wgpu::Operations {
300 load: if clear_background {
301 wgpu::LoadOp::Clear(wgpu::Color {
302 r: background_color.map_or(0.08, |c| c.x as f64),
303 g: background_color.map_or(0.09, |c| c.y as f64),
304 b: background_color.map_or(0.11, |c| c.z as f64),
305 a: background_color.map_or(1.0, |c| c.w as f64),
306 })
307 } else {
308 wgpu::LoadOp::Load
309 },
310 store: wgpu::StoreOp::Store,
311 },
312 })],
313 depth_stencil_attachment: None,
314 occlusion_query_set: None,
315 timestamp_writes: None,
316 });
317
318 for (render_data, vertex_buffer) in &render_items {
324 let pipeline = self.wgpu_renderer.get_pipeline(render_data.pipeline_type);
325 println!(
326 "RENDER: Using {:?} pipeline for {} vertices",
327 render_data.pipeline_type,
328 render_data.vertices.len()
329 );
330 render_pass.set_pipeline(pipeline);
331 render_pass.set_bind_group(0, self.wgpu_renderer.get_uniform_bind_group(), &[]);
332 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
333
334 for draw_call in &render_data.draw_calls {
336 render_pass.draw(
337 draw_call.vertex_offset as u32
338 ..(draw_call.vertex_offset + draw_call.vertex_count) as u32,
339 0..draw_call.instance_count as u32,
340 );
341 }
342 }
343
344 drop(render_pass);
345
346 let render_time = start_time.elapsed().as_secs_f64() * 1000.0;
347
348 Ok(RenderResult {
349 success: true,
350 data_bounds: self.data_bounds,
351 vertex_count: total_vertices,
352 triangle_count: total_triangles,
353 render_time_ms: render_time,
354 })
355 }
356
357 pub fn render_direct_to_viewport(
360 &mut self,
361 encoder: &mut wgpu::CommandEncoder,
362 target_view: &wgpu::TextureView,
363 viewport: (f32, f32, f32, f32), data_bounds: (f64, f64, f64, f64), clear_background: bool,
366 background_color: Option<glam::Vec4>,
367 ) -> Result<RenderResult, Box<dyn std::error::Error>> {
368 let start_time = std::time::Instant::now();
369
370 self.wgpu_renderer.ensure_direct_line_pipeline();
372
373 let window_width = self.wgpu_renderer.surface_config.width as f32;
375 let window_height = self.wgpu_renderer.surface_config.height as f32;
376
377 let (viewport_x, viewport_y, viewport_width, viewport_height) = viewport;
378
379 let ndc_left = (viewport_x / window_width) * 2.0 - 1.0;
381 let ndc_right = ((viewport_x + viewport_width) / window_width) * 2.0 - 1.0;
382 let ndc_top = 1.0 - (viewport_y / window_height) * 2.0;
383 let ndc_bottom = 1.0 - ((viewport_y + viewport_height) / window_height) * 2.0;
384
385 self.wgpu_renderer.update_direct_uniforms(
387 [data_bounds.0 as f32, data_bounds.2 as f32], [data_bounds.1 as f32, data_bounds.3 as f32], [ndc_left, ndc_bottom], [ndc_right, ndc_top], );
392
393 let mut render_items = Vec::new();
395 let mut total_vertices = 0;
396
397 for node in self.scene.get_visible_nodes() {
398 if let Some(render_data) = &node.render_data {
399 if !render_data.vertices.is_empty() {
400 let vertex_buffer = self
401 .wgpu_renderer
402 .create_vertex_buffer(&render_data.vertices);
403 render_items.push((render_data, vertex_buffer));
404 total_vertices += render_data.vertices.len();
405 }
406 }
407 }
408
409 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
411 label: Some("Direct Viewport Plot Render Pass"),
412 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
413 view: target_view,
414 resolve_target: None,
415 ops: wgpu::Operations {
416 load: if clear_background {
417 wgpu::LoadOp::Clear(wgpu::Color {
418 r: background_color.map_or(0.08, |c| c.x as f64),
419 g: background_color.map_or(0.09, |c| c.y as f64),
420 b: background_color.map_or(0.11, |c| c.z as f64),
421 a: background_color.map_or(1.0, |c| c.w as f64),
422 })
423 } else {
424 wgpu::LoadOp::Load
425 },
426 store: wgpu::StoreOp::Store,
427 },
428 })],
429 depth_stencil_attachment: None,
430 timestamp_writes: None,
431 occlusion_query_set: None,
432 });
433
434 for (render_data, vertex_buffer) in &render_items {
436 if let Some(pipeline) = &self.wgpu_renderer.direct_line_pipeline {
438 render_pass.set_pipeline(pipeline);
439 render_pass.set_bind_group(0, &self.wgpu_renderer.direct_uniform_bind_group, &[]);
440 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
441
442 for draw_call in &render_data.draw_calls {
444 render_pass.draw(
445 draw_call.vertex_offset as u32
446 ..(draw_call.vertex_offset + draw_call.vertex_count) as u32,
447 0..1,
448 );
449 }
450 }
451 }
452
453 drop(render_pass);
454
455 let render_time = start_time.elapsed().as_millis() as f64;
456
457 Ok(RenderResult {
458 success: true,
459 data_bounds: Some(data_bounds),
460 vertex_count: total_vertices,
461 triangle_count: 0,
462 render_time_ms: render_time,
463 })
464 }
465
466 pub fn render(
468 &mut self,
469 encoder: &mut wgpu::CommandEncoder,
470 target_view: &wgpu::TextureView,
471 config: &PlotRenderConfig,
472 ) -> Result<RenderResult, Box<dyn std::error::Error>> {
473 let start_time = std::time::Instant::now();
474
475 let aspect_ratio = config.width as f32 / config.height as f32;
477 self.camera.update_aspect_ratio(aspect_ratio);
478
479 let view_proj_matrix = self.camera.view_proj_matrix();
481 let model_matrix = Mat4::IDENTITY;
482 self.wgpu_renderer
483 .update_uniforms(view_proj_matrix, model_matrix);
484
485 let mut render_items = Vec::new();
487 let mut total_vertices = 0;
488 let mut total_triangles = 0;
489
490 for node in self.scene.get_visible_nodes() {
491 if let Some(render_data) = &node.render_data {
492 if !render_data.vertices.is_empty() {
493 self.wgpu_renderer
495 .ensure_pipeline(render_data.pipeline_type);
496
497 let vertex_buffer = self
499 .wgpu_renderer
500 .create_vertex_buffer(&render_data.vertices);
501
502 let index_buffer = if let Some(indices) = &render_data.indices {
504 Some(self.wgpu_renderer.create_index_buffer(indices))
505 } else {
506 None
507 };
508
509 render_items.push((render_data, vertex_buffer, index_buffer));
510
511 total_vertices += render_data.vertices.len();
512 if let Some(indices) = &render_data.indices {
513 total_triangles += indices.len() / 3;
514 }
515 }
516 }
517 }
518
519 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
521 label: Some("Plot Render Pass"),
522 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
523 view: target_view,
524 resolve_target: None,
525 ops: wgpu::Operations {
526 load: wgpu::LoadOp::Clear(wgpu::Color {
527 r: config.background_color.x as f64,
528 g: config.background_color.y as f64,
529 b: config.background_color.z as f64,
530 a: config.background_color.w as f64,
531 }),
532 store: wgpu::StoreOp::Store,
533 },
534 })],
535 depth_stencil_attachment: None,
536 occlusion_query_set: None,
537 timestamp_writes: None,
538 });
539
540 for (render_data, vertex_buffer, index_buffer) in &render_items {
542 let pipeline = self.wgpu_renderer.get_pipeline(render_data.pipeline_type);
544 render_pass.set_pipeline(pipeline);
545
546 render_pass.set_bind_group(0, self.wgpu_renderer.get_uniform_bind_group(), &[]);
548
549 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
550
551 if let Some(index_buffer) = index_buffer {
552 render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
553 if let Some(indices) = &render_data.indices {
554 println!(
555 "RENDER: Drawing {} indices with triangle pipeline",
556 indices.len()
557 );
558 render_pass.draw_indexed(0..indices.len() as u32, 0, 0..1);
559 }
560 } else {
561 println!("RENDER: Drawing direct vertices - no index buffer");
562 for draw_call in &render_data.draw_calls {
564 println!("RENDER: Direct draw - vertex_offset={}, vertex_count={}, instance_count={}",
565 draw_call.vertex_offset, draw_call.vertex_count, draw_call.instance_count);
566 render_pass.draw(
567 draw_call.vertex_offset as u32
568 ..(draw_call.vertex_offset + draw_call.vertex_count) as u32,
569 0..draw_call.instance_count as u32,
570 );
571 }
572 }
573 }
574
575 drop(render_pass);
576
577 let render_time = start_time.elapsed().as_secs_f64() * 1000.0;
578
579 Ok(RenderResult {
580 success: true,
581 data_bounds: self.data_bounds,
582 vertex_count: total_vertices,
583 triangle_count: total_triangles,
584 render_time_ms: render_time,
585 })
586 }
587
588 fn create_default_camera() -> Camera {
590 let mut camera = Camera::new();
591 camera.projection = crate::core::camera::ProjectionType::Orthographic {
592 left: -5.0,
593 right: 5.0,
594 bottom: -5.0,
595 top: 5.0,
596 near: 0.1,
597 far: 100.0,
598 };
599 camera.position = Vec3::new(0.0, 0.0, 5.0);
600 camera.target = Vec3::new(0.0, 0.0, 0.0);
601 camera.up = Vec3::new(0.0, 1.0, 0.0);
602 camera
603 }
604
605 pub fn data_bounds(&self) -> Option<(f64, f64, f64, f64)> {
607 self.data_bounds
608 }
609
610 pub fn camera(&self) -> &Camera {
612 &self.camera
613 }
614
615 pub fn camera_mut(&mut self) -> &mut Camera {
617 &mut self.camera
618 }
619
620 pub fn scene(&self) -> &Scene {
622 &self.scene
623 }
624
625 pub fn scene_statistics(&self) -> crate::core::SceneStatistics {
627 self.scene.statistics()
628 }
629}
630
631pub mod plot_utils {
633
634 pub fn calculate_tick_interval(range: f64) -> f64 {
636 let magnitude = 10.0_f64.powf(range.log10().floor());
637 let normalized = range / magnitude;
638
639 let nice_interval = if normalized <= 1.0 {
640 0.2
641 } else if normalized <= 2.0 {
642 0.5
643 } else if normalized <= 5.0 {
644 1.0
645 } else {
646 2.0
647 };
648
649 nice_interval * magnitude
650 }
651
652 pub fn format_tick_label(value: f64) -> String {
654 if value.abs() < 0.001 {
655 "0".to_string()
656 } else if value.abs() >= 1000.0 || value.fract().abs() < 0.001 {
657 format!("{value:.0}")
658 } else {
659 format!("{value:.1}")
660 }
661 }
662
663 pub fn generate_grid_lines(
665 bounds: (f64, f64, f64, f64),
666 plot_rect: (f32, f32, f32, f32), ) -> Vec<(f32, f32, f32, f32)> {
668 let (x_min, x_max, y_min, y_max) = bounds;
670 let (left, right, bottom, top) = plot_rect;
671
672 let mut lines = Vec::new();
673
674 let x_range = x_max - x_min;
676 let x_interval = calculate_tick_interval(x_range);
677 let mut x_val = (x_min / x_interval).ceil() * x_interval;
678
679 while x_val <= x_max {
680 let x_screen = left + ((x_val - x_min) / x_range) as f32 * (right - left);
681 lines.push((x_screen, bottom, x_screen, top));
682 x_val += x_interval;
683 }
684
685 let y_range = y_max - y_min;
687 let y_interval = calculate_tick_interval(y_range);
688 let mut y_val = (y_min / y_interval).ceil() * y_interval;
689
690 while y_val <= y_max {
691 let y_screen = bottom + ((y_val - y_min) / y_range) as f32 * (top - bottom);
692 lines.push((left, y_screen, right, y_screen));
693 y_val += y_interval;
694 }
695
696 lines
697 }
698}