Skip to main content

runmat_plot/plots/
quiver.rs

1//! Quiver plot (vector field) implementation
2
3use crate::core::{
4    BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType, RenderData, Vertex,
5};
6use glam::{Vec3, Vec4};
7
8#[derive(Debug, Clone)]
9pub struct QuiverPlot {
10    pub x: Vec<f64>,
11    pub y: Vec<f64>,
12    pub u: Vec<f64>,
13    pub v: Vec<f64>,
14
15    pub color: Vec4,
16    pub line_width: f32,
17    pub scale: f32,
18    pub head_size: f32,
19
20    pub label: Option<String>,
21    pub visible: bool,
22
23    vertices: Option<Vec<Vertex>>,
24    bounds: Option<BoundingBox>,
25    dirty: bool,
26    gpu_vertices: Option<GpuVertexBuffer>,
27    gpu_vertex_count: Option<usize>,
28    gpu_bounds: Option<BoundingBox>,
29}
30
31impl QuiverPlot {
32    pub fn new(x: Vec<f64>, y: Vec<f64>, u: Vec<f64>, v: Vec<f64>) -> Result<Self, String> {
33        let n = x.len();
34        if n == 0 || y.len() != n || u.len() != n || v.len() != n {
35            return Err("quiver: X,Y,U,V must have same non-zero length".to_string());
36        }
37        Ok(Self {
38            x,
39            y,
40            u,
41            v,
42            color: Vec4::new(0.0, 0.0, 0.0, 1.0),
43            line_width: 1.0,
44            scale: 1.0,
45            head_size: 0.1,
46            label: None,
47            visible: true,
48            vertices: None,
49            bounds: None,
50            dirty: true,
51            gpu_vertices: None,
52            gpu_vertex_count: None,
53            gpu_bounds: None,
54        })
55    }
56    pub fn from_gpu_buffer(
57        color: Vec4,
58        line_width: f32,
59        scale: f32,
60        head_size: f32,
61        buffer: GpuVertexBuffer,
62        vertex_count: usize,
63        bounds: BoundingBox,
64    ) -> Self {
65        Self {
66            x: Vec::new(),
67            y: Vec::new(),
68            u: Vec::new(),
69            v: Vec::new(),
70            color,
71            line_width,
72            scale,
73            head_size,
74            label: None,
75            visible: true,
76            vertices: None,
77            bounds: Some(bounds),
78            dirty: false,
79            gpu_vertices: Some(buffer),
80            gpu_vertex_count: Some(vertex_count),
81            gpu_bounds: Some(bounds),
82        }
83    }
84    pub fn with_style(mut self, color: Vec4, line_width: f32, scale: f32, head_size: f32) -> Self {
85        self.color = color;
86        self.line_width = line_width.max(0.5);
87        self.scale = scale.max(0.0);
88        self.head_size = head_size.max(0.0);
89        self.dirty = true;
90        self
91    }
92    pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
93        self.label = Some(label.into());
94        self
95    }
96    pub fn set_visible(&mut self, v: bool) {
97        self.visible = v;
98    }
99
100    pub fn generate_vertices(&mut self) -> &Vec<Vertex> {
101        if self.dirty || self.vertices.is_none() {
102            let mut verts = Vec::new();
103            for i in 0..self.x.len() {
104                let (x, y, u, v) = (
105                    self.x[i] as f32,
106                    self.y[i] as f32,
107                    self.u[i] as f32,
108                    self.v[i] as f32,
109                );
110                if !x.is_finite() || !y.is_finite() || !u.is_finite() || !v.is_finite() {
111                    continue;
112                }
113                let dx = u * self.scale;
114                let dy = v * self.scale;
115                // Main shaft
116                verts.push(Vertex::new(Vec3::new(x, y, 0.0), self.color));
117                verts.push(Vertex::new(Vec3::new(x + dx, y + dy, 0.0), self.color));
118                // Arrowhead as two short lines forming a V
119                let len = (dx * dx + dy * dy).sqrt();
120                if len > 0.0 && self.head_size > 0.0 {
121                    let hx = dx / len;
122                    let hy = dy / len;
123                    // Perpendicular
124                    let px = -hy;
125                    let py = hx;
126                    let h = self.head_size.min(len * 0.5);
127                    let tipx = x + dx;
128                    let tipy = y + dy;
129                    let leftx = tipx - h * hx + 0.5 * h * px;
130                    let lefty = tipy - h * hy + 0.5 * h * py;
131                    let rightx = tipx - h * hx - 0.5 * h * px;
132                    let righty = tipy - h * hy - 0.5 * h * py;
133                    verts.push(Vertex::new(Vec3::new(tipx, tipy, 0.0), self.color));
134                    verts.push(Vertex::new(Vec3::new(leftx, lefty, 0.0), self.color));
135                    verts.push(Vertex::new(Vec3::new(tipx, tipy, 0.0), self.color));
136                    verts.push(Vertex::new(Vec3::new(rightx, righty, 0.0), self.color));
137                }
138            }
139            self.vertices = Some(verts);
140            self.dirty = false;
141        }
142        self.vertices.as_ref().unwrap()
143    }
144
145    pub fn bounds(&mut self) -> BoundingBox {
146        if let Some(bounds) = self.gpu_bounds {
147            return bounds;
148        }
149        if self.dirty || self.bounds.is_none() {
150            let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, 0.0);
151            let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0);
152            for i in 0..self.x.len() {
153                let x = self.x[i] as f32;
154                let y = self.y[i] as f32;
155                let dx = (self.u[i] as f32) * self.scale;
156                let dy = (self.v[i] as f32) * self.scale;
157                if !x.is_finite() || !y.is_finite() || !dx.is_finite() || !dy.is_finite() {
158                    continue;
159                }
160                min.x = min.x.min(x.min(x + dx));
161                max.x = max.x.max(x.max(x + dx));
162                min.y = min.y.min(y.min(y + dy));
163                max.y = max.y.max(y.max(y + dy));
164            }
165            if !min.x.is_finite() {
166                min = Vec3::ZERO;
167                max = Vec3::ZERO;
168            }
169            self.bounds = Some(BoundingBox::new(min, max));
170        }
171        self.bounds.unwrap()
172    }
173
174    pub fn render_data(&mut self) -> RenderData {
175        let using_gpu = self.gpu_vertices.is_some();
176        let bounds = self.bounds();
177        let vertices = if using_gpu {
178            Vec::new()
179        } else {
180            self.generate_vertices().clone()
181        };
182        let material = Material {
183            albedo: self.color,
184            ..Default::default()
185        };
186        let draw_call = DrawCall {
187            vertex_offset: 0,
188            vertex_count: self.gpu_vertex_count.unwrap_or(vertices.len()),
189            index_offset: None,
190            index_count: None,
191            instance_count: 1,
192        };
193        RenderData {
194            pipeline_type: PipelineType::Lines,
195            vertices,
196            indices: None,
197            gpu_vertices: self.gpu_vertices.clone(),
198            bounds: Some(bounds),
199            material,
200            draw_calls: vec![draw_call],
201            image: None,
202        }
203    }
204
205    pub fn estimated_memory_usage(&self) -> usize {
206        self.vertices
207            .as_ref()
208            .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>())
209    }
210}