Skip to main content

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            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    /// Generate indices for volume bounding box (12 triangles forming a cube)
216    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            // Cube faces (2 triangles per face)
221            let indices = vec![
222                // Front face
223                0, 1, 2, 0, 2, 3, // Back face
224                4, 6, 5, 4, 7, 6, // Left face
225                0, 3, 7, 0, 7, 4, // Right face
226                1, 5, 6, 1, 6, 2, // Bottom face
227                0, 4, 5, 0, 5, 1, // Top face
228                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    /// Generate complete render data for the graphics pipeline
238    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, // For volume bounding box
272            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    /// Get volume statistics for debugging
283    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    /// Estimate memory usage in bytes
307    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}