1use crate::core::{
4 vertex_utils, AlphaMode, BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType,
5 RenderData, Vertex,
6};
7use crate::plots::line::LineMarkerAppearance;
8use glam::{Vec3, Vec4};
9
10#[derive(Debug, Clone)]
11pub struct StairsPlot {
12 pub x: Vec<f64>,
13 pub y: Vec<f64>,
14 pub color: Vec4,
15 pub line_width: f32,
16 pub label: Option<String>,
17 pub visible: bool,
18 vertices: Option<Vec<Vertex>>,
19 bounds: Option<BoundingBox>,
20 dirty: bool,
21 gpu_vertices: Option<GpuVertexBuffer>,
22 gpu_vertex_count: Option<usize>,
23 gpu_bounds: Option<BoundingBox>,
24 marker: Option<LineMarkerAppearance>,
25 marker_vertices: Option<Vec<Vertex>>,
26 marker_gpu_vertices: Option<GpuVertexBuffer>,
27 marker_dirty: bool,
28}
29
30impl StairsPlot {
31 pub fn new(x: Vec<f64>, y: Vec<f64>) -> Result<Self, String> {
32 if x.len() != y.len() || x.is_empty() {
33 return Err("stairs: X and Y must be same non-zero length".to_string());
34 }
35 Ok(Self {
36 x,
37 y,
38 color: Vec4::new(0.0, 0.5, 1.0, 1.0),
39 line_width: 1.0,
40 label: None,
41 visible: true,
42 vertices: None,
43 bounds: None,
44 dirty: true,
45 gpu_vertices: None,
46 gpu_vertex_count: None,
47 gpu_bounds: None,
48 marker: None,
49 marker_vertices: None,
50 marker_gpu_vertices: None,
51 marker_dirty: true,
52 })
53 }
54
55 pub fn from_gpu_buffer(
57 color: Vec4,
58 buffer: GpuVertexBuffer,
59 vertex_count: usize,
60 bounds: BoundingBox,
61 ) -> Self {
62 Self {
63 x: Vec::new(),
64 y: Vec::new(),
65 color,
66 line_width: 1.0,
67 label: None,
68 visible: true,
69 vertices: None,
70 bounds: None,
71 dirty: false,
72 gpu_vertices: Some(buffer),
73 gpu_vertex_count: Some(vertex_count),
74 gpu_bounds: Some(bounds),
75 marker: None,
76 marker_vertices: None,
77 marker_gpu_vertices: None,
78 marker_dirty: true,
79 }
80 }
81
82 pub fn with_style(mut self, color: Vec4, line_width: f32) -> Self {
83 self.color = color;
84 self.line_width = line_width.max(0.5);
85 self.dirty = true;
86 self.marker_dirty = true;
87 self.drop_gpu();
88 self
89 }
90 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
91 self.label = Some(label.into());
92 self
93 }
94 pub fn set_visible(&mut self, v: bool) {
95 self.visible = v;
96 }
97
98 pub fn set_marker(&mut self, marker: Option<LineMarkerAppearance>) {
99 self.marker = marker;
100 self.marker_dirty = true;
101 if self.marker.is_none() {
102 self.marker_vertices = None;
103 self.marker_gpu_vertices = None;
104 }
105 }
106
107 pub fn set_marker_gpu_vertices(&mut self, buffer: Option<GpuVertexBuffer>) {
108 let has_gpu = buffer.is_some();
109 self.marker_gpu_vertices = buffer;
110 if has_gpu {
111 self.marker_vertices = None;
112 }
113 }
114
115 fn drop_gpu(&mut self) {
116 self.gpu_vertices = None;
117 self.gpu_vertex_count = None;
118 self.gpu_bounds = None;
119 self.marker_gpu_vertices = None;
120 }
121 pub fn generate_vertices(&mut self) -> &Vec<Vertex> {
122 if self.gpu_vertices.is_some() {
123 if self.vertices.is_none() {
124 self.vertices = Some(Vec::new());
125 }
126 return self.vertices.as_ref().unwrap();
127 }
128 if self.dirty || self.vertices.is_none() {
129 let mut verts = Vec::new();
130 for i in 0..self.x.len().saturating_sub(1) {
131 let x0 = self.x[i] as f32;
132 let y0 = self.y[i] as f32;
133 let x1 = self.x[i + 1] as f32;
134 let y1 = self.y[i + 1] as f32;
135 if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
136 continue;
137 }
138 verts.push(Vertex::new(Vec3::new(x0, y0, 0.0), self.color));
140 verts.push(Vertex::new(Vec3::new(x1, y0, 0.0), self.color));
141 verts.push(Vertex::new(Vec3::new(x1, y0, 0.0), self.color));
143 verts.push(Vertex::new(Vec3::new(x1, y1, 0.0), self.color));
144 }
145 self.vertices = Some(verts);
146 self.dirty = false;
147 }
148 self.vertices.as_ref().unwrap()
149 }
150 pub fn bounds(&mut self) -> BoundingBox {
151 if let Some(bounds) = self.gpu_bounds {
152 return bounds;
153 }
154 if self.dirty || self.bounds.is_none() {
155 let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, 0.0);
156 let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0);
157 for (&x, &y) in self.x.iter().zip(self.y.iter()) {
158 let (x, y) = (x as f32, y as f32);
159 if !x.is_finite() || !y.is_finite() {
160 continue;
161 }
162 min.x = min.x.min(x);
163 max.x = max.x.max(x);
164 min.y = min.y.min(y);
165 max.y = max.y.max(y);
166 }
167 if !min.x.is_finite() {
168 min = Vec3::ZERO;
169 max = Vec3::ZERO;
170 }
171 self.bounds = Some(BoundingBox::new(min, max));
172 }
173 self.bounds.unwrap()
174 }
175 pub fn render_data(&mut self) -> RenderData {
176 let using_gpu = self.gpu_vertices.is_some();
177 let bounds = self.bounds();
178 let (vertices, vertex_count, gpu_vertices) = if using_gpu {
179 (
180 Vec::new(),
181 self.gpu_vertex_count.unwrap_or(0),
182 self.gpu_vertices.clone(),
183 )
184 } else {
185 let verts = self.generate_vertices().clone();
186 let count = verts.len();
187 (verts, count, None)
188 };
189 let material = Material {
190 albedo: self.color,
191 ..Default::default()
192 };
193 let draw_call = DrawCall {
194 vertex_offset: 0,
195 vertex_count,
196 index_offset: None,
197 index_count: None,
198 instance_count: 1,
199 };
200 RenderData {
201 pipeline_type: PipelineType::Lines,
202 vertices,
203 indices: None,
204 gpu_vertices,
205 bounds: Some(bounds),
206 material,
207 draw_calls: vec![draw_call],
208 image: None,
209 }
210 }
211
212 pub fn render_data_with_viewport(&mut self, viewport_px: Option<(u32, u32)>) -> RenderData {
213 if self.gpu_vertices.is_some() {
214 return self.render_data();
215 }
216
217 let bounds = self.bounds();
218 let (vertices, vertex_count, pipeline_type) = if self.line_width > 1.0 {
219 let viewport_px = viewport_px.unwrap_or((600, 400));
220 let data_per_px = crate::core::data_units_per_px(&bounds, viewport_px);
221 let width_data = self.line_width.max(0.1) * data_per_px;
222 let verts = self.generate_vertices().clone();
223 let mut thick = Vec::new();
224 for segment in verts.chunks_exact(2) {
225 let x = [segment[0].position[0] as f64, segment[1].position[0] as f64];
226 let y = [segment[0].position[1] as f64, segment[1].position[1] as f64];
227 let color = Vec4::from_array(segment[0].color);
228 thick.extend(vertex_utils::create_thick_polyline(
229 &x, &y, color, width_data,
230 ));
231 }
232 let count = thick.len();
233 (thick, count, PipelineType::Triangles)
234 } else {
235 let verts = self.generate_vertices().clone();
236 let count = verts.len();
237 (verts, count, PipelineType::Lines)
238 };
239 let material = Material {
240 albedo: self.color,
241 roughness: self.line_width.max(0.0),
242 ..Default::default()
243 };
244 let draw_call = DrawCall {
245 vertex_offset: 0,
246 vertex_count,
247 index_offset: None,
248 index_count: None,
249 instance_count: 1,
250 };
251 RenderData {
252 pipeline_type,
253 vertices,
254 indices: None,
255 gpu_vertices: None,
256 bounds: Some(bounds),
257 material,
258 draw_calls: vec![draw_call],
259 image: None,
260 }
261 }
262
263 pub fn marker_render_data(&mut self) -> Option<RenderData> {
264 let marker = self.marker.clone()?;
265 if let Some(gpu_vertices) = self.marker_gpu_vertices.clone() {
266 let vertex_count = gpu_vertices.vertex_count;
267 if vertex_count == 0 {
268 return None;
269 }
270 let draw_call = DrawCall {
271 vertex_offset: 0,
272 vertex_count,
273 index_offset: None,
274 index_count: None,
275 instance_count: 1,
276 };
277 let material = Self::marker_material(&marker);
278 return Some(RenderData {
279 pipeline_type: PipelineType::Points,
280 vertices: Vec::new(),
281 indices: None,
282 gpu_vertices: Some(gpu_vertices),
283 bounds: None,
284 material,
285 draw_calls: vec![draw_call],
286 image: None,
287 });
288 }
289
290 let vertices = self.marker_vertices_slice(&marker)?;
291 if vertices.is_empty() {
292 return None;
293 }
294 let draw_call = DrawCall {
295 vertex_offset: 0,
296 vertex_count: vertices.len(),
297 index_offset: None,
298 index_count: None,
299 instance_count: 1,
300 };
301 let material = Self::marker_material(&marker);
302 Some(RenderData {
303 pipeline_type: PipelineType::Points,
304 vertices: vertices.to_vec(),
305 indices: None,
306 gpu_vertices: None,
307 bounds: None,
308 material,
309 draw_calls: vec![draw_call],
310 image: None,
311 })
312 }
313
314 fn marker_material(marker: &LineMarkerAppearance) -> Material {
315 let mut material = Material {
316 albedo: marker.face_color,
317 ..Default::default()
318 };
319 if !marker.filled {
320 material.albedo.w = 0.0;
321 }
322 material.emissive = marker.edge_color;
323 material.roughness = 1.0;
324 material.metallic = 0.0;
325 material.alpha_mode = AlphaMode::Blend;
326 material
327 }
328
329 fn marker_vertices_slice(&mut self, marker: &LineMarkerAppearance) -> Option<&[Vertex]> {
330 if self.x.len() != self.y.len() || self.x.is_empty() {
331 return None;
332 }
333 if self.marker_vertices.is_none() || self.marker_dirty {
334 let mut verts = Vec::with_capacity(self.x.len());
335 for (&x, &y) in self.x.iter().zip(self.y.iter()) {
336 let mut vertex = Vertex::new(Vec3::new(x as f32, y as f32, 0.0), marker.face_color);
337 vertex.normal[2] = marker.size.max(1.0);
338 verts.push(vertex);
339 }
340 self.marker_vertices = Some(verts);
341 self.marker_dirty = false;
342 }
343 self.marker_vertices.as_deref()
344 }
345 pub fn estimated_memory_usage(&self) -> usize {
346 self.vertices
347 .as_ref()
348 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>())
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn thick_stairs_use_viewport_aware_triangles() {
358 let mut plot = StairsPlot::new(vec![0.0, 1.0, 2.0], vec![1.0, 2.0, 1.5])
359 .unwrap()
360 .with_style(Vec4::ONE, 2.0);
361 let render = plot.render_data_with_viewport(Some((600, 400)));
362 assert_eq!(render.pipeline_type, PipelineType::Triangles);
363 assert!(!render.vertices.is_empty());
364 }
365}