runmat_plot/plots/
volume.rs

1//! Volume rendering implementation
2//!
3//! GPU-accelerated volume visualization with raycasting and texture-based rendering.
4
5use crate::core::{BoundingBox, DrawCall, Material, PipelineType, RenderData, Vertex};
6use glam::{Vec3, Vec4};
7
8/// High-performance GPU-accelerated volume plot
9#[derive(Debug, Clone)]
10pub struct VolumePlot {
11    /// 3D volume data (values at 3D grid points)
12    pub volume_data: Vec<Vec<Vec<f64>>>, // volume_data[x][y][z]
13
14    /// Grid dimensions and spacing
15    pub dimensions: (usize, usize, usize), // (nx, ny, nz)
16    pub spacing: Vec3, // Grid spacing in world coordinates
17    pub origin: Vec3,  // Origin of the volume in world coordinates
18
19    /// Volume rendering properties
20    pub opacity: f32,
21    pub color_map: VolumeColorMap,
22    pub iso_value: Option<f64>, // For isosurface extraction
23
24    /// Transfer function for opacity mapping
25    pub opacity_transfer: Vec<(f64, f32)>, // (value, opacity) pairs
26    pub color_transfer: Vec<(f64, Vec4)>, // (value, color) pairs
27
28    /// Rendering settings
29    pub ray_step_size: f32,
30    pub max_steps: u32,
31    pub lighting_enabled: bool,
32
33    /// Metadata
34    pub label: Option<String>,
35    pub visible: bool,
36
37    /// Generated rendering data (cached)
38    vertices: Option<Vec<Vertex>>,
39    indices: Option<Vec<u32>>,
40    bounds: Option<BoundingBox>,
41    dirty: bool,
42}
43
44/// Volume color mapping schemes
45#[derive(Debug, Clone, Copy, PartialEq)]
46pub enum VolumeColorMap {
47    /// Grayscale
48    Grayscale,
49    /// Hot (black -> red -> orange -> yellow -> white)
50    Hot,
51    /// Jet (blue -> cyan -> green -> yellow -> red)
52    Jet,
53    /// Viridis (purple -> blue -> green -> yellow)
54    Viridis,
55    /// Alpha-blended RGB
56    RGB,
57    /// Custom transfer function
58    Custom,
59}
60
61/// Volume rendering statistics
62#[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    /// Create a new volume plot from 3D data
78    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        // Validate consistent dimensions
90        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    /// Set volume rendering properties
128    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    /// Set color mapping scheme
135    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    /// Set isosurface value for surface extraction
142    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    /// Set the plot label for legends
149    pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
150        self.label = Some(label.into());
151        self
152    }
153
154    /// Get the bounding box of the volume
155    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    /// Generate vertices for volume rendering (bounding box for raycasting)
169    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            // Generate bounding box vertices for ray casting entry/exit points
180            let min = bounds.min;
181            let max = bounds.max;
182
183            // 8 vertices of the bounding box
184            let positions = [
185                Vec3::new(min.x, min.y, min.z), // 0
186                Vec3::new(max.x, min.y, min.z), // 1
187                Vec3::new(max.x, max.y, min.z), // 2
188                Vec3::new(min.x, max.y, min.z), // 3
189                Vec3::new(min.x, min.y, max.z), // 4
190                Vec3::new(max.x, min.y, max.z), // 5
191                Vec3::new(max.x, max.y, max.z), // 6
192                Vec3::new(min.x, max.y, max.z), // 7
193            ];
194
195            for pos in positions.iter() {
196                vertices.push(Vertex {
197                    position: pos.to_array(),
198                    normal: [0.0, 0.0, 1.0], // Will be computed in shader
199                    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    /// Generate indices for volume bounding box (12 triangles forming a cube)
215    fn generate_indices(&mut self) -> &Vec<u32> {
216        if self.dirty || self.indices.is_none() {
217            println!("DEBUG: Generating volume indices");
218
219            // Cube faces (2 triangles per face)
220            let indices = vec![
221                // Front face
222                0, 1, 2, 0, 2, 3, // Back face
223                4, 6, 5, 4, 7, 6, // Left face
224                0, 3, 7, 0, 7, 4, // Right face
225                1, 5, 6, 1, 6, 2, // Bottom face
226                0, 4, 5, 0, 5, 1, // Top face
227                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    /// Generate complete render data for the graphics pipeline
237    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, // For volume bounding box
269            vertices,
270            indices: Some(indices),
271            material,
272            draw_calls: vec![draw_call],
273        }
274    }
275
276    /// Get volume statistics for debugging
277    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    /// Estimate memory usage in bytes
301    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}