Skip to main content

runmat_plot/plots/
contour.rs

1//! Contour plot implementation (iso-lines on a surface or base plane).
2
3use crate::core::{
4    vertex_utils, BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType, RenderData,
5    Vertex,
6};
7use glam::{Vec3, Vec4};
8
9#[derive(Debug, Clone)]
10pub struct ContourPlot {
11    pub base_z: f32,
12    pub label: Option<String>,
13    pub visible: bool,
14    pub line_width: f32,
15    vertices: Option<Vec<Vertex>>,
16    gpu_vertices: Option<GpuVertexBuffer>,
17    vertex_count: usize,
18    bounds: Option<BoundingBox>,
19}
20
21impl ContourPlot {
22    /// Create a contour plot from CPU vertices.
23    pub fn from_vertices(vertices: Vec<Vertex>, base_z: f32, bounds: BoundingBox) -> Self {
24        Self {
25            base_z,
26            label: None,
27            visible: true,
28            line_width: 1.0,
29            vertex_count: vertices.len(),
30            vertices: Some(vertices),
31            gpu_vertices: None,
32            bounds: Some(bounds),
33        }
34    }
35
36    /// Create a contour plot backed by a GPU vertex buffer.
37    pub fn from_gpu_buffer(
38        buffer: GpuVertexBuffer,
39        vertex_count: usize,
40        base_z: f32,
41        bounds: BoundingBox,
42    ) -> Self {
43        Self {
44            base_z,
45            label: None,
46            visible: true,
47            line_width: 1.0,
48            vertex_count,
49            vertices: None,
50            gpu_vertices: Some(buffer),
51            bounds: Some(bounds),
52        }
53    }
54
55    pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
56        self.label = Some(label.into());
57        self
58    }
59
60    pub fn set_visible(&mut self, visible: bool) {
61        self.visible = visible;
62    }
63
64    pub fn with_line_width(mut self, line_width: f32) -> Self {
65        self.line_width = line_width.max(0.5);
66        self
67    }
68
69    pub fn vertices(&mut self) -> &Vec<Vertex> {
70        if self.vertices.is_none() {
71            self.vertices = Some(Vec::new());
72        }
73        self.vertices.as_ref().unwrap()
74    }
75
76    pub fn bounds(&self) -> BoundingBox {
77        self.bounds.unwrap_or_default()
78    }
79
80    pub fn cpu_vertices(&self) -> Option<&[Vertex]> {
81        self.vertices.as_deref()
82    }
83
84    pub fn render_data_with_viewport(&mut self, viewport_px: Option<(u32, u32)>) -> RenderData {
85        if self.gpu_vertices.is_some() {
86            return self.render_data();
87        }
88
89        let bounds = self.bounds();
90        let (vertices, vertex_count, pipeline_type) = if self.line_width > 1.0 {
91            let viewport_px = viewport_px.unwrap_or((600, 400));
92            let data_per_px = crate::core::data_units_per_px(&bounds, viewport_px);
93            let width_data = self.line_width.max(0.1) * data_per_px;
94            let verts = self.vertices().clone();
95            let mut thick = Vec::new();
96            for segment in verts.chunks_exact(2) {
97                let x = [segment[0].position[0] as f64, segment[1].position[0] as f64];
98                let y = [segment[0].position[1] as f64, segment[1].position[1] as f64];
99                let color = Vec4::from_array(segment[0].color);
100                thick.extend(vertex_utils::create_thick_polyline(
101                    &x, &y, color, width_data,
102                ));
103            }
104            let count = thick.len();
105            (thick, count, PipelineType::Triangles)
106        } else {
107            let verts = self.vertices().clone();
108            let count = verts.len();
109            (verts, count, PipelineType::Lines)
110        };
111
112        let material = Material {
113            albedo: Vec4::ONE,
114            roughness: self.line_width.max(0.0),
115            ..Default::default()
116        };
117
118        let draw_call = DrawCall {
119            vertex_offset: 0,
120            vertex_count,
121            index_offset: None,
122            index_count: None,
123            instance_count: 1,
124        };
125
126        RenderData {
127            pipeline_type,
128            vertices,
129            indices: None,
130            gpu_vertices: None,
131            bounds: Some(bounds),
132            material,
133            draw_calls: vec![draw_call],
134            image: None,
135        }
136    }
137
138    pub fn render_data(&mut self) -> RenderData {
139        let using_gpu = self.gpu_vertices.is_some();
140        let bounds = self.bounds();
141        let (vertices, vertex_count, gpu_vertices, pipeline_type) = if using_gpu {
142            (
143                Vec::new(),
144                self.vertex_count,
145                self.gpu_vertices.clone(),
146                PipelineType::Lines,
147            )
148        } else {
149            let verts = self.vertices().clone();
150            if self.line_width > 1.0 {
151                let mut thick = Vec::new();
152                for segment in verts.chunks_exact(2) {
153                    let x = [segment[0].position[0] as f64, segment[1].position[0] as f64];
154                    let y = [segment[0].position[1] as f64, segment[1].position[1] as f64];
155                    let color = Vec4::from_array(segment[0].color);
156                    thick.extend(vertex_utils::create_thick_polyline(
157                        &x,
158                        &y,
159                        color,
160                        self.line_width,
161                    ));
162                }
163                let count = thick.len();
164                (thick, count, None, PipelineType::Triangles)
165            } else {
166                let count = verts.len();
167                (verts, count, None, PipelineType::Lines)
168            }
169        };
170
171        let material = Material {
172            albedo: Vec4::ONE,
173            roughness: self.line_width.max(0.0),
174            ..Default::default()
175        };
176
177        let draw_call = DrawCall {
178            vertex_offset: 0,
179            vertex_count,
180            index_offset: None,
181            index_count: None,
182            instance_count: 1,
183        };
184
185        RenderData {
186            pipeline_type,
187            vertices,
188            indices: None,
189            gpu_vertices,
190            bounds: Some(bounds),
191            material,
192            draw_calls: vec![draw_call],
193            image: None,
194        }
195    }
196
197    pub fn estimated_memory_usage(&self) -> usize {
198        self.vertices
199            .as_ref()
200            .map(|v| v.len() * std::mem::size_of::<Vertex>())
201            .unwrap_or(0)
202    }
203}
204
205pub fn contour_bounds(x_min: f32, x_max: f32, y_min: f32, y_max: f32, base_z: f32) -> BoundingBox {
206    BoundingBox::new(
207        Vec3::new(x_min, y_min, base_z),
208        Vec3::new(x_max, y_max, base_z),
209    )
210}