1use crate::core::{BoundingBox, DrawCall, Material, PipelineType, RenderData, Vertex};
6use glam::{Vec3, Vec4};
7
8#[derive(Debug, Clone)]
10pub struct VolumePlot {
11 pub volume_data: Vec<Vec<Vec<f64>>>, pub dimensions: (usize, usize, usize), pub spacing: Vec3, pub origin: Vec3, pub opacity: f32,
21 pub color_map: VolumeColorMap,
22 pub iso_value: Option<f64>, pub opacity_transfer: Vec<(f64, f32)>, pub color_transfer: Vec<(f64, Vec4)>, pub ray_step_size: f32,
30 pub max_steps: u32,
31 pub lighting_enabled: bool,
32
33 pub label: Option<String>,
35 pub visible: bool,
36
37 vertices: Option<Vec<Vertex>>,
39 indices: Option<Vec<u32>>,
40 bounds: Option<BoundingBox>,
41 dirty: bool,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
46pub enum VolumeColorMap {
47 Grayscale,
49 Hot,
51 Jet,
53 Viridis,
55 RGB,
57 Custom,
59}
60
61#[derive(Debug, Clone)]
63pub struct VolumeStatistics {
64 pub voxel_count: usize,
65 pub memory_usage: usize,
66 pub data_range: (f64, f64),
67 pub dimensions: (usize, usize, usize),
68}
69
70impl Default for VolumeColorMap {
71 fn default() -> Self {
72 Self::Viridis
73 }
74}
75
76impl VolumePlot {
77 pub fn new(volume_data: Vec<Vec<Vec<f64>>>) -> Result<Self, String> {
79 if volume_data.is_empty() || volume_data[0].is_empty() || volume_data[0][0].is_empty() {
80 return Err("Volume data cannot be empty".to_string());
81 }
82
83 let dimensions = (
84 volume_data.len(),
85 volume_data[0].len(),
86 volume_data[0][0].len(),
87 );
88
89 for (x, plane) in volume_data.iter().enumerate() {
91 if plane.len() != dimensions.1 {
92 return Err(format!("Inconsistent Y dimension at X={x}"));
93 }
94 for (y, row) in plane.iter().enumerate() {
95 if row.len() != dimensions.2 {
96 return Err(format!("Inconsistent Z dimension at X={x}, Y={y}"));
97 }
98 }
99 }
100
101 Ok(Self {
102 volume_data,
103 dimensions,
104 spacing: Vec3::new(1.0, 1.0, 1.0),
105 origin: Vec3::ZERO,
106 opacity: 0.5,
107 color_map: VolumeColorMap::default(),
108 iso_value: None,
109 opacity_transfer: vec![(0.0, 0.0), (1.0, 1.0)],
110 color_transfer: vec![
111 (0.0, Vec4::new(0.0, 0.0, 0.5, 1.0)),
112 (0.5, Vec4::new(0.0, 1.0, 0.0, 1.0)),
113 (1.0, Vec4::new(1.0, 0.0, 0.0, 1.0)),
114 ],
115 ray_step_size: 0.01,
116 max_steps: 1000,
117 lighting_enabled: true,
118 label: None,
119 visible: true,
120 vertices: None,
121 indices: None,
122 bounds: None,
123 dirty: true,
124 })
125 }
126
127 pub fn with_opacity(mut self, opacity: f32) -> Self {
129 self.opacity = opacity.clamp(0.0, 1.0);
130 self.dirty = true;
131 self
132 }
133
134 pub fn with_colormap(mut self, color_map: VolumeColorMap) -> Self {
136 self.color_map = color_map;
137 self.dirty = true;
138 self
139 }
140
141 pub fn with_isosurface(mut self, iso_value: f64) -> Self {
143 self.iso_value = Some(iso_value);
144 self.dirty = true;
145 self
146 }
147
148 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
150 self.label = Some(label.into());
151 self
152 }
153
154 pub fn bounds(&mut self) -> BoundingBox {
156 if self.dirty || self.bounds.is_none() {
157 let max_coord = Vec3::new(
158 (self.dimensions.0 - 1) as f32 * self.spacing.x,
159 (self.dimensions.1 - 1) as f32 * self.spacing.y,
160 (self.dimensions.2 - 1) as f32 * self.spacing.z,
161 ) + self.origin;
162
163 self.bounds = Some(BoundingBox::new(self.origin, max_coord));
164 }
165 self.bounds.unwrap()
166 }
167
168 fn generate_vertices(&mut self) -> &Vec<Vertex> {
170 if self.dirty || self.vertices.is_none() {
171 println!(
172 "DEBUG: Generating volume vertices for {} x {} x {} grid",
173 self.dimensions.0, self.dimensions.1, self.dimensions.2
174 );
175
176 let mut vertices = Vec::new();
177 let bounds = self.bounds();
178
179 let min = bounds.min;
181 let max = bounds.max;
182
183 let positions = [
185 Vec3::new(min.x, min.y, min.z), Vec3::new(max.x, min.y, min.z), Vec3::new(max.x, max.y, min.z), Vec3::new(min.x, max.y, min.z), Vec3::new(min.x, min.y, max.z), Vec3::new(max.x, min.y, max.z), Vec3::new(max.x, max.y, max.z), Vec3::new(min.x, max.y, max.z), ];
194
195 for pos in positions.iter() {
196 vertices.push(Vertex {
197 position: pos.to_array(),
198 normal: [0.0, 0.0, 1.0], color: [1.0, 1.0, 1.0, self.opacity],
200 tex_coords: [pos.x / (max.x - min.x), pos.y / (max.y - min.y)],
201 });
202 }
203
204 log::trace!(
205 target: "runmat_plot",
206 "volume bbox vertices={}",
207 vertices.len()
208 );
209 self.vertices = Some(vertices);
210 self.dirty = false;
211 }
212 self.vertices.as_ref().unwrap()
213 }
214
215 fn generate_indices(&mut self) -> &Vec<u32> {
217 if self.dirty || self.indices.is_none() {
218 log::trace!(target: "runmat_plot", "volume generating indices");
219
220 let indices = vec![
222 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 3, 7, 0, 7, 4, 1, 5, 6, 1, 6, 2, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7,
229 ];
230
231 log::trace!(target: "runmat_plot", "volume indices={}", indices.len());
232 self.indices = Some(indices);
233 }
234 self.indices.as_ref().unwrap()
235 }
236
237 pub fn render_data(&mut self) -> RenderData {
239 log::debug!(
240 target: "runmat_plot",
241 "volume render_data: dims=({},{},{})",
242 self.dimensions.0, self.dimensions.1, self.dimensions.2
243 );
244
245 let vertices = self.generate_vertices().clone();
246 let indices = self.generate_indices().clone();
247
248 log::debug!(
249 target: "runmat_plot",
250 "volume render_data generated: vertices={}, indices={}",
251 vertices.len(),
252 indices.len()
253 );
254
255 let material = Material {
256 albedo: Vec4::new(1.0, 1.0, 1.0, self.opacity),
257 ..Default::default()
258 };
259
260 let draw_call = DrawCall {
261 vertex_offset: 0,
262 vertex_count: vertices.len(),
263 index_offset: Some(0),
264 index_count: Some(indices.len()),
265 instance_count: 1,
266 };
267
268 log::trace!(target: "runmat_plot", "volume render_data done");
269
270 RenderData {
271 pipeline_type: PipelineType::Triangles, vertices,
273 indices: Some(indices),
274 material,
275 draw_calls: vec![draw_call],
276 gpu_vertices: None,
277 bounds: None,
278 image: None,
279 }
280 }
281
282 pub fn statistics(&self) -> VolumeStatistics {
284 let voxel_count = self.dimensions.0 * self.dimensions.1 * self.dimensions.2;
285
286 let mut min_val = f64::INFINITY;
287 let mut max_val = f64::NEG_INFINITY;
288
289 for plane in &self.volume_data {
290 for row in plane {
291 for &val in row {
292 min_val = min_val.min(val);
293 max_val = max_val.max(val);
294 }
295 }
296 }
297
298 VolumeStatistics {
299 voxel_count,
300 memory_usage: self.estimated_memory_usage(),
301 data_range: (min_val, max_val),
302 dimensions: self.dimensions,
303 }
304 }
305
306 pub fn estimated_memory_usage(&self) -> usize {
308 let data_size =
309 self.dimensions.0 * self.dimensions.1 * self.dimensions.2 * std::mem::size_of::<f64>();
310 let vertices_size = self
311 .vertices
312 .as_ref()
313 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>());
314 let indices_size = self
315 .indices
316 .as_ref()
317 .map_or(0, |i| i.len() * std::mem::size_of::<u32>());
318
319 data_size + vertices_size + indices_size
320 }
321}