1use crate::context::shared_wgpu_context;
4use crate::core::{
5 BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType, RenderData, Vertex,
6};
7use crate::gpu::axis::OwnedAxisData;
8use crate::gpu::{util::readback_scalar_buffer_f64, ScalarType};
9use glam::{Vec3, Vec4};
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
13pub struct AreaPlot {
14 pub x: Vec<f64>,
15 pub y: Vec<f64>,
16 pub baseline: f64,
17 pub lower_y: Option<Vec<f64>>,
18 pub color: Vec4,
19 pub label: Option<String>,
20 pub visible: bool,
21 vertices: Option<Vec<Vertex>>,
22 indices: Option<Vec<u32>>,
23 bounds: Option<BoundingBox>,
24 dirty: bool,
25 gpu_vertices: Option<GpuVertexBuffer>,
26 gpu_vertex_count: Option<usize>,
27 gpu_bounds: Option<BoundingBox>,
28 gpu_source: Option<AreaGpuSource>,
29}
30
31#[derive(Clone, Debug)]
32pub struct AreaGpuSource {
33 pub x_axis: OwnedAxisData,
34 pub y_buffer: Arc<wgpu::Buffer>,
35 pub rows: usize,
36 pub cols: usize,
37 pub target_col: usize,
38 pub scalar: ScalarType,
39}
40
41impl AreaPlot {
42 pub async fn export_scene_xy_data(&self) -> Result<(Vec<f64>, Vec<f64>), String> {
43 if !self.x.is_empty() && self.x.len() == self.y.len() {
44 return Ok((self.x.clone(), self.y.clone()));
45 }
46 if !self.x.is_empty() || !self.y.is_empty() {
47 return Err(format!(
48 "area plot has partial CPU source data: x has {} values, y has {} values",
49 self.x.len(),
50 self.y.len()
51 ));
52 }
53
54 if let Some(source) = &self.gpu_source {
55 let context = shared_wgpu_context().ok_or_else(|| {
56 "area plot has GPU source data but no shared WGPU context is installed".to_string()
57 })?;
58 let x = source
59 .x_axis
60 .export_f64(&context.device, &context.queue, source.rows, source.scalar)
61 .await?;
62 let all_y = readback_scalar_buffer_f64(
63 &context.device,
64 &context.queue,
65 &source.y_buffer,
66 source.rows * source.cols,
67 source.scalar,
68 )
69 .await?;
70 let offset = source.target_col * source.rows;
71 let y = all_y
72 .get(offset..offset + source.rows)
73 .ok_or_else(|| "area plot GPU source column is out of range".to_string())?
74 .to_vec();
75 return Ok((x, y));
76 }
77
78 if self.gpu_vertices.is_some() {
79 return Err(
80 "area plot has GPU render vertices but no exportable source data".to_string(),
81 );
82 }
83
84 Ok((Vec::new(), Vec::new()))
85 }
86
87 pub fn new(x: Vec<f64>, y: Vec<f64>) -> Result<Self, String> {
88 if x.len() != y.len() || x.is_empty() {
89 return Err("area: X and Y must be same non-zero length".to_string());
90 }
91 Ok(Self {
92 x,
93 y,
94 baseline: 0.0,
95 lower_y: None,
96 color: Vec4::new(0.0, 0.5, 1.0, 0.4),
97 label: None,
98 visible: true,
99 vertices: None,
100 indices: None,
101 bounds: None,
102 dirty: true,
103 gpu_vertices: None,
104 gpu_vertex_count: None,
105 gpu_bounds: None,
106 gpu_source: None,
107 })
108 }
109 pub fn from_gpu_buffer(
110 color: Vec4,
111 baseline: f64,
112 lower_y: Option<Vec<f64>>,
113 buffer: GpuVertexBuffer,
114 vertex_count: usize,
115 bounds: BoundingBox,
116 ) -> Self {
117 Self {
118 x: Vec::new(),
119 y: Vec::new(),
120 baseline,
121 lower_y,
122 color,
123 label: None,
124 visible: true,
125 vertices: None,
126 indices: None,
127 bounds: Some(bounds),
128 dirty: false,
129 gpu_vertices: Some(buffer),
130 gpu_vertex_count: Some(vertex_count),
131 gpu_bounds: Some(bounds),
132 gpu_source: None,
133 }
134 }
135 pub fn with_gpu_source(mut self, source: AreaGpuSource) -> Self {
136 self.gpu_source = Some(source);
137 self
138 }
139 pub fn with_style(mut self, color: Vec4, baseline: f64) -> Self {
140 self.color = color;
141 self.baseline = baseline;
142 self.dirty = true;
143 self
144 }
145 pub fn with_lower_curve(mut self, lower_y: Vec<f64>) -> Self {
146 self.lower_y = Some(lower_y);
147 self.gpu_source = None;
148 self.dirty = true;
149 self
150 }
151 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
152 self.label = Some(label.into());
153 self
154 }
155 pub fn set_visible(&mut self, v: bool) {
156 self.visible = v;
157 }
158 pub fn generate_vertices(&mut self) -> (&Vec<Vertex>, &Vec<u32>) {
159 if self.dirty || self.vertices.is_none() {
160 let mut verts = Vec::new();
161 let mut inds = Vec::new();
162 for i in 0..self.x.len() {
164 let xi = self.x[i] as f32;
165 let yi = self.y[i] as f32;
166 let b = self
167 .lower_y
168 .as_ref()
169 .and_then(|vals| vals.get(i).copied())
170 .unwrap_or(self.baseline) as f32;
171 if !xi.is_finite() || !yi.is_finite() {
172 continue;
173 }
174 verts.push(Vertex::new(Vec3::new(xi, b, 0.0), self.color));
175 verts.push(Vertex::new(Vec3::new(xi, yi, 0.0), self.color));
176 }
177 for i in 0..(verts.len() / 2 - 1) {
179 let base = (i * 2) as u32;
180 inds.extend_from_slice(&[base, base + 1, base + 3, base, base + 3, base + 2]);
181 }
182 self.vertices = Some(verts);
183 self.indices = Some(inds);
184 self.dirty = false;
185 }
186 (
187 self.vertices.as_ref().unwrap(),
188 self.indices.as_ref().unwrap(),
189 )
190 }
191 pub fn bounds(&mut self) -> BoundingBox {
192 if let Some(bounds) = self.gpu_bounds {
193 return bounds;
194 }
195 if self.dirty || self.bounds.is_none() {
196 let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, 0.0);
197 let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0);
198 for (idx, (&x, &y)) in self.x.iter().zip(self.y.iter()).enumerate() {
199 let (x, y) = (x as f32, y as f32);
200 let lower = self
201 .lower_y
202 .as_ref()
203 .and_then(|vals| vals.get(idx).copied())
204 .unwrap_or(self.baseline) as f32;
205 if !x.is_finite() || !y.is_finite() {
206 continue;
207 }
208 min.x = min.x.min(x);
209 max.x = max.x.max(x);
210 min.y = min.y.min(y.min(lower));
211 max.y = max.y.max(y.max(lower));
212 }
213 if !min.x.is_finite() {
214 min = Vec3::ZERO;
215 max = Vec3::ZERO;
216 }
217 self.bounds = Some(BoundingBox::new(min, max));
218 }
219 self.bounds.unwrap()
220 }
221 pub fn render_data(&mut self) -> RenderData {
222 let using_gpu = self.gpu_vertices.is_some();
223 let bounds = self.bounds();
224 let (vertices, indices) = if using_gpu {
225 (Vec::new(), Vec::new())
226 } else {
227 let (v, i) = self.generate_vertices();
228 (v.clone(), i.clone())
229 };
230 let material = Material {
231 albedo: self.color,
232 ..Default::default()
233 };
234 let draw_call = DrawCall {
235 vertex_offset: 0,
236 vertex_count: self.gpu_vertex_count.unwrap_or(vertices.len()),
237 index_offset: if using_gpu { None } else { Some(0) },
238 index_count: if using_gpu { None } else { Some(indices.len()) },
239 instance_count: 1,
240 };
241 RenderData {
242 pipeline_type: PipelineType::Triangles,
243 vertices,
244 indices: if using_gpu { None } else { Some(indices) },
245 material,
246 draw_calls: vec![draw_call],
247 gpu_vertices: self.gpu_vertices.clone(),
248 bounds: Some(bounds),
249 image: None,
250 }
251 }
252 pub fn estimated_memory_usage(&self) -> usize {
253 self.vertices
254 .as_ref()
255 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>())
256 + self
257 .indices
258 .as_ref()
259 .map_or(0, |i| i.len() * std::mem::size_of::<u32>())
260 }
261}