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 println!(
205 "DEBUG: Generated {} vertices for volume bounding box",
206 vertices.len()
207 );
208 self.vertices = Some(vertices);
209 self.dirty = false;
210 }
211 self.vertices.as_ref().unwrap()
212 }
213
214 fn generate_indices(&mut self) -> &Vec<u32> {
216 if self.dirty || self.indices.is_none() {
217 println!("DEBUG: Generating volume indices");
218
219 let indices = vec![
221 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,
228 ];
229
230 println!("DEBUG: Generated {} indices for volume", indices.len());
231 self.indices = Some(indices);
232 }
233 self.indices.as_ref().unwrap()
234 }
235
236 pub fn render_data(&mut self) -> RenderData {
238 println!(
239 "DEBUG: VolumePlot::render_data() called for {} x {} x {} volume",
240 self.dimensions.0, self.dimensions.1, self.dimensions.2
241 );
242
243 let vertices = self.generate_vertices().clone();
244 let indices = self.generate_indices().clone();
245
246 println!(
247 "DEBUG: Volume render data: {} vertices, {} indices",
248 vertices.len(),
249 indices.len()
250 );
251
252 let material = Material {
253 albedo: Vec4::new(1.0, 1.0, 1.0, self.opacity),
254 ..Default::default()
255 };
256
257 let draw_call = DrawCall {
258 vertex_offset: 0,
259 vertex_count: vertices.len(),
260 index_offset: Some(0),
261 index_count: Some(indices.len()),
262 instance_count: 1,
263 };
264
265 println!("DEBUG: VolumePlot render_data completed successfully");
266
267 RenderData {
268 pipeline_type: PipelineType::Triangles, vertices,
270 indices: Some(indices),
271 material,
272 draw_calls: vec![draw_call],
273 }
274 }
275
276 pub fn statistics(&self) -> VolumeStatistics {
278 let voxel_count = self.dimensions.0 * self.dimensions.1 * self.dimensions.2;
279
280 let mut min_val = f64::INFINITY;
281 let mut max_val = f64::NEG_INFINITY;
282
283 for plane in &self.volume_data {
284 for row in plane {
285 for &val in row {
286 min_val = min_val.min(val);
287 max_val = max_val.max(val);
288 }
289 }
290 }
291
292 VolumeStatistics {
293 voxel_count,
294 memory_usage: self.estimated_memory_usage(),
295 data_range: (min_val, max_val),
296 dimensions: self.dimensions,
297 }
298 }
299
300 pub fn estimated_memory_usage(&self) -> usize {
302 let data_size =
303 self.dimensions.0 * self.dimensions.1 * self.dimensions.2 * std::mem::size_of::<f64>();
304 let vertices_size = self
305 .vertices
306 .as_ref()
307 .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>());
308 let indices_size = self
309 .indices
310 .as_ref()
311 .map_or(0, |i| i.len() * std::mem::size_of::<u32>());
312
313 data_size + vertices_size + indices_size
314 }
315}