plotpy/
surface.rs

1use super::{matrix_to_array, AsMatrix, GraphMaker, StrError};
2use crate::quote_marker;
3use num_traits::Num;
4use std::fmt::Write;
5
6/// Generates a 3D a surface (or wireframe, or both)
7///
8/// [See Matplotlib's documentation](https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.plot_surface.html)
9///
10/// # Example
11///
12/// ```
13/// use plotpy::{generate3d, Plot, StrError, Surface};
14///
15/// fn main() -> Result<(), StrError> {
16///     // generate (x,y,z) matrices
17///     let n = 21;
18///     let (x, y, z) = generate3d(-2.0, 2.0, -2.0, 2.0, n, n, |x, y| x * x - y * y);
19///
20///     // configure and draw surface + wireframe
21///     let mut surface = Surface::new();
22///     surface.set_colormap_name("seismic")
23///         .set_with_colorbar(true)
24///         .set_with_wireframe(true)
25///         .set_wire_line_width(0.3);
26///
27///     // draw surface + wireframe
28///     surface.draw(&x, &y, &z);
29///
30///     // add surface to plot
31///     let mut plot = Plot::new();
32///     plot.add(&surface)
33///         .set_title("horse saddle equation") // must be after add surface
34///         .set_camera(20.0, 35.0); // must be after add surface
35///
36///     // save figure
37///     plot.save("/tmp/plotpy/doc_tests/doc_surface.svg")?;
38///     Ok(())
39/// }
40/// ```
41///
42/// ![doc_surface.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_surface.svg)
43///
44/// See also integration tests in the [tests directory](https://github.com/cpmech/plotpy/tree/main/tests)
45///
46/// Output from some integration tests:
47///
48/// ![integ_surface_wireframe.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_surface_wireframe.svg)
49pub struct Surface {
50    row_stride: usize,        // Row stride
51    col_stride: usize,        // Column stride
52    with_surface: bool,       // Generates a surface
53    with_wireframe: bool,     // Generates a wireframe
54    with_points: bool,        // Generates (a scatter of) points on the surface
55    colormap_name: String,    // Colormap name
56    with_colorbar: bool,      // Draw a colorbar
57    colorbar_label: String,   // Colorbar label
58    number_format_cb: String, // Number format for labels in colorbar
59    surf_color: String,       // Const color of surface (when not using colormap)
60    surf_line_color: String,  // Color of surface lines
61    surf_line_style: String,  // Style of surface lines
62    surf_line_width: f64,     // Width of surface lines
63    wire_line_color: String,  // Color of wireframe lines
64    wire_line_style: String,  // Style of wireframe line
65    wire_line_width: f64,     // Width of wireframe line
66    point_color: String,      // Color of markers (scatter)
67    point_void: bool,         // Draws a void marker (edge only)
68    point_line_color: String, // Edge color of markers
69    point_line_width: f64,    // Edge width of markers
70    point_size: f64,          // Size of markers
71    point_style: String,      // Style of markers, e.g., "`o`", "`+`"
72    buffer: String,           // buffer
73}
74
75impl Surface {
76    /// Creates a new Surface object
77    pub fn new() -> Self {
78        Surface {
79            row_stride: 0,
80            col_stride: 0,
81            with_surface: true,
82            with_wireframe: false,
83            with_points: false,
84            colormap_name: "bwr".to_string(),
85            with_colorbar: false,
86            colorbar_label: String::new(),
87            number_format_cb: String::new(),
88            surf_color: String::new(),
89            surf_line_color: String::new(),
90            surf_line_style: String::new(),
91            surf_line_width: 0.0,
92            wire_line_color: "black".to_string(),
93            wire_line_style: String::new(),
94            wire_line_width: 0.0,
95            point_color: String::new(),
96            point_void: false,
97            point_line_color: String::new(),
98            point_line_width: 0.0,
99            point_size: 0.0,
100            point_style: String::new(),
101            buffer: String::new(),
102        }
103    }
104
105    /// Draws a surface, or wireframe, or both
106    ///
107    /// # Input
108    ///
109    /// * `x` -- matrix with x values
110    /// * `y` -- matrix with y values
111    /// * `z` -- matrix with z values
112    ///
113    /// # Flags
114    ///
115    /// The following flags control what features are not to be drawn:
116    ///
117    /// * `surface` -- draws surface
118    /// * `wireframe` -- draws wireframe
119    pub fn draw<'a, T, U>(&mut self, x: &'a T, y: &'a T, z: &'a T)
120    where
121        T: AsMatrix<'a, U>,
122        U: 'a + std::fmt::Display + Num,
123    {
124        matrix_to_array(&mut self.buffer, "x", x);
125        matrix_to_array(&mut self.buffer, "y", y);
126        matrix_to_array(&mut self.buffer, "z", z);
127        if self.with_surface {
128            let opt_surface = self.options_surface();
129            write!(&mut self.buffer, "sf=ax3d().plot_surface(x,y,z{})\n", &opt_surface).unwrap();
130        }
131        if self.with_wireframe {
132            let opt_wireframe = self.options_wireframe();
133            write!(&mut self.buffer, "ax3d().plot_wireframe(x,y,z{})\n", &opt_wireframe).unwrap();
134        }
135        if self.with_points {
136            let opt_points = self.options_points();
137            write!(&mut self.buffer, "ax3d().scatter(x,y,z{})\n", &opt_points).unwrap();
138        }
139        if self.with_colorbar {
140            let opt_colorbar = self.options_colorbar();
141            write!(&mut self.buffer, "cb=plt.colorbar(sf{})\n", &opt_colorbar).unwrap();
142            if self.colorbar_label != "" {
143                write!(&mut self.buffer, "cb.ax.set_ylabel(r'{}')\n", self.colorbar_label).unwrap();
144            }
145        }
146    }
147
148    /// Sets the row stride
149    pub fn set_row_stride(&mut self, value: usize) -> &mut Self {
150        self.row_stride = value;
151        self
152    }
153
154    /// Sets the column stride
155    pub fn set_col_stride(&mut self, value: usize) -> &mut Self {
156        self.col_stride = value;
157        self
158    }
159
160    /// Sets option to generate surface
161    pub fn set_with_surface(&mut self, flag: bool) -> &mut Self {
162        self.with_surface = flag;
163        self
164    }
165
166    /// Enables the drawing of a wireframe representing the surface
167    pub fn set_with_wireframe(&mut self, flag: bool) -> &mut Self {
168        self.with_wireframe = flag;
169        self
170    }
171
172    /// Enables the drawing of (a scatter of) points representing the surface
173    pub fn set_with_points(&mut self, flag: bool) -> &mut Self {
174        self.with_points = flag;
175        self
176    }
177
178    // -- surface --------------------------------------------------------------------------------
179
180    /// Sets the colormap index
181    ///
182    /// Options:
183    ///
184    /// * 0 -- bwr
185    /// * 1 -- RdBu
186    /// * 2 -- hsv
187    /// * 3 -- jet
188    /// * 4 -- terrain
189    /// * 5 -- pink
190    /// * 6 -- Greys
191    /// * `>`6 -- starts over from 0
192    pub fn set_colormap_index(&mut self, index: usize) -> &mut Self {
193        const CMAP: [&str; 7] = ["bwr", "RdBu", "hsv", "jet", "terrain", "pink", "Greys"];
194        self.colormap_name = CMAP[index % 7].to_string();
195        self
196    }
197
198    /// Sets the colormap name
199    ///
200    /// Options:
201    ///
202    /// * `bwr`
203    /// * `RdBu`
204    /// * `hsv`
205    /// * `jet`
206    /// * `terrain`
207    /// * `pink`
208    /// * `Greys`
209    /// * see more here <https://matplotlib.org/stable/tutorials/colors/colormaps.html>
210    pub fn set_colormap_name(&mut self, name: &str) -> &mut Self {
211        self.colormap_name = String::from(name);
212        self
213    }
214
215    /// Sets option to draw a colorbar
216    pub fn set_with_colorbar(&mut self, flag: bool) -> &mut Self {
217        self.with_colorbar = flag;
218        self
219    }
220
221    /// Sets the colorbar label
222    pub fn set_colorbar_label(&mut self, label: &str) -> &mut Self {
223        self.colorbar_label = String::from(label);
224        self
225    }
226
227    /// Sets the number format for the labels in the colorbar (cb)
228    pub fn set_number_format_cb(&mut self, format: &str) -> &mut Self {
229        self.number_format_cb = String::from(format);
230        self
231    }
232
233    /// Sets a constant color for the surface (disables colormap)
234    pub fn set_surf_color(&mut self, color: &str) -> &mut Self {
235        self.surf_color = String::from(color);
236        self
237    }
238
239    /// Sets the color of surface lines
240    pub fn set_surf_line_color(&mut self, color: &str) -> &mut Self {
241        self.surf_line_color = String::from(color);
242        self
243    }
244
245    /// Sets the style of surface lines
246    ///
247    /// Options:
248    ///
249    /// * "`-`", "`:`", "`--`", "`-.`"
250    pub fn set_surf_line_style(&mut self, style: &str) -> &mut Self {
251        self.surf_line_style = String::from(style);
252        self
253    }
254
255    /// Sets the width of surface lines
256    pub fn set_surf_line_width(&mut self, width: f64) -> &mut Self {
257        self.surf_line_width = width;
258        self
259    }
260
261    // -- wireframe ------------------------------------------------------------------------------
262
263    /// Sets the color of wireframe lines
264    pub fn set_wire_line_color(&mut self, color: &str) -> &mut Self {
265        self.wire_line_color = String::from(color);
266        self
267    }
268
269    /// Sets the style of wireframe lines
270    ///
271    /// Options:
272    ///
273    /// * "`-`", "`:`", "`--`", "`-.`"
274    pub fn set_wire_line_style(&mut self, style: &str) -> &mut Self {
275        self.wire_line_style = String::from(style);
276        self
277    }
278
279    /// Sets the width of wireframe lines
280    pub fn set_wire_line_width(&mut self, width: f64) -> &mut Self {
281        self.wire_line_width = width;
282        self
283    }
284
285    // -- scatter --------------------------------------------------------------------------------
286
287    /// Sets the color of point markers
288    pub fn set_point_color(&mut self, color: &str) -> &mut Self {
289        self.point_color = String::from(color);
290        self.point_void = false;
291        self
292    }
293
294    /// Sets the option to draw a void point marker (edge only)
295    pub fn set_point_void(&mut self, flag: bool) -> &mut Self {
296        self.point_void = flag;
297        self
298    }
299
300    /// Sets the edge color of point markers
301    pub fn set_point_line_color(&mut self, color: &str) -> &mut Self {
302        self.point_line_color = String::from(color);
303        self
304    }
305
306    /// Sets the edge width of point markers
307    pub fn set_point_line_width(&mut self, width: f64) -> &mut Self {
308        self.point_line_width = width;
309        self
310    }
311
312    /// Sets the size of point markers
313    pub fn set_point_size(&mut self, size: f64) -> &mut Self {
314        self.point_size = size;
315        self
316    }
317
318    /// Sets the style of point markers
319    ///
320    /// Examples:
321    ///
322    /// * "`o`", "`+`"
323    /// * As defined in <https://matplotlib.org/stable/api/markers_api.html>
324    pub fn set_point_style(&mut self, style: &str) -> &mut Self {
325        self.point_style = String::from(style);
326        self
327    }
328
329    // -- options --------------------------------------------------------------------------------
330
331    /// Returns options for surface
332    fn options_surface(&self) -> String {
333        let mut opt = String::new();
334        if self.row_stride > 0 {
335            write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
336        }
337        if self.col_stride > 0 {
338            write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
339        }
340        if self.surf_color != "" {
341            write!(&mut opt, ",color='{}'", self.surf_color).unwrap();
342        } else {
343            if self.colormap_name != "" {
344                write!(&mut opt, ",cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
345            }
346        }
347        if self.surf_line_color != "" {
348            write!(&mut opt, ",edgecolors='{}'", self.surf_line_color).unwrap();
349        }
350        if self.surf_line_style != "" {
351            write!(&mut opt, ",linestyle='{}'", self.surf_line_style).unwrap();
352        }
353        if self.surf_line_width > 0.0 {
354            write!(&mut opt, ",linewidth={}", self.surf_line_width).unwrap();
355        }
356        opt
357    }
358
359    /// Returns options for wireframe
360    fn options_wireframe(&self) -> String {
361        let mut opt = String::new();
362        if self.row_stride > 0 {
363            write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
364        }
365        if self.col_stride > 0 {
366            write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
367        }
368        if self.wire_line_color != "" {
369            write!(&mut opt, ",color='{}'", self.wire_line_color).unwrap();
370        }
371        if self.wire_line_style != "" {
372            write!(&mut opt, ",linestyle='{}'", self.wire_line_style).unwrap();
373        }
374        if self.wire_line_width > 0.0 {
375            write!(&mut opt, ",linewidth={}", self.wire_line_width).unwrap();
376        }
377        opt
378    }
379
380    /// Returns options for points
381    fn options_points(&self) -> String {
382        let mut opt = String::new();
383        if self.row_stride > 0 {
384            write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
385        }
386        if self.col_stride > 0 {
387            write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
388        }
389        if self.point_line_width > 0.0 {
390            write!(&mut opt, ",linewidths={}", self.point_line_width).unwrap();
391        }
392        if self.point_size > 0.0 {
393            write!(&mut opt, ",s={}", self.point_size).unwrap();
394        }
395        if self.point_style != "" {
396            write!(&mut opt, ",marker={}", quote_marker(&self.point_style)).unwrap();
397        }
398        // note: unlike contour and surface, scatter requires setting 'c=z'
399        if self.point_void {
400            let lc = if self.point_line_color == "" {
401                "black"
402            } else {
403                self.point_line_color.as_str()
404            };
405            write!(&mut opt, ",color='none',edgecolor='{}'", lc).unwrap();
406        } else if self.point_color != "" {
407            write!(&mut opt, ",color='{}'", self.point_color).unwrap();
408            if self.point_line_color != "" {
409                write!(&mut opt, ",edgecolor='{}'", self.point_line_color).unwrap();
410            }
411        } else if self.colormap_name != "" {
412            write!(&mut opt, ",c=z,cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
413        }
414        opt
415    }
416
417    /// Returns options for colorbar
418    fn options_colorbar(&self) -> String {
419        let mut opt = String::new();
420        if self.number_format_cb != "" {
421            write!(&mut opt, ",format='{}'", self.number_format_cb).unwrap();
422        }
423        opt
424    }
425
426    /// Creates a triad aligned to an axis passing through a and b
427    pub(super) fn aligned_system(a: &[f64], b: &[f64]) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), StrError> {
428        // vector aligned with the axis
429        let n = vec![b[0] - a[0], b[1] - a[1], b[2] - a[2]];
430        let n_dot_n = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
431        if n_dot_n <= f64::EPSILON {
432            return Err("a-to-b segment is too short");
433        }
434
435        // arbitrary vector not parallel to n
436        let x = if f64::abs(n[1]) <= f64::EPSILON && f64::abs(n[2]) <= f64::EPSILON {
437            vec![n[0], n[1] + 1.0, n[2]] // parallel to x => distort along y
438        } else {
439            vec![n[0] + 1.0, n[1], n[2]] // distort along x
440        };
441
442        // orthogonal projection of x onto the axis
443        // q = x - p = x - n * (x⋅n)/(n⋅n)
444        let x_dot_n = x[0] * n[0] + x[1] * n[1] + x[2] * n[2];
445        let q = vec![
446            x[0] - n[0] * x_dot_n / n_dot_n,
447            x[1] - n[1] * x_dot_n / n_dot_n,
448            x[2] - n[2] * x_dot_n / n_dot_n,
449        ];
450
451        // local system aligned with the axis (parallel to n)
452        let norm_n = f64::sqrt(n_dot_n);
453        let norm_q = f64::sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
454        let e0 = vec![n[0] / norm_n, n[1] / norm_n, n[2] / norm_n];
455        let e1 = vec![q[0] / norm_q, q[1] / norm_q, q[2] / norm_q];
456        let e2 = vec![
457            e0[1] * e1[2] - e0[2] * e1[1],
458            e0[2] * e1[0] - e0[0] * e1[2],
459            e0[0] * e1[1] - e0[1] * e1[0],
460        ];
461        Ok((e0, e1, e2))
462    }
463}
464
465impl GraphMaker for Surface {
466    fn get_buffer<'a>(&'a self) -> &'a String {
467        &self.buffer
468    }
469    fn clear_buffer(&mut self) {
470        self.buffer.clear();
471    }
472}
473
474////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
475
476#[cfg(test)]
477mod tests {
478    use super::Surface;
479    use crate::GraphMaker;
480
481    #[test]
482    fn new_works() {
483        let surface = Surface::new();
484        assert_eq!(surface.row_stride, 0);
485        assert_eq!(surface.col_stride, 0);
486        assert_eq!(surface.with_surface, true);
487        assert_eq!(surface.with_wireframe, false);
488        assert_eq!(surface.colormap_name, "bwr".to_string());
489        assert_eq!(surface.with_colorbar, false);
490        assert_eq!(surface.colorbar_label.len(), 0);
491        assert_eq!(surface.number_format_cb.len(), 0);
492        assert_eq!(surface.wire_line_color, "black".to_string());
493        assert_eq!(surface.wire_line_style.len(), 0);
494        assert_eq!(surface.wire_line_width, 0.0);
495        assert_eq!(surface.buffer.len(), 0);
496    }
497
498    #[test]
499    fn options_surface_works() {
500        let mut surface = Surface::new();
501        surface.set_row_stride(3).set_col_stride(4);
502        let opt = surface.options_surface();
503        assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('bwr')");
504
505        surface.set_colormap_name("Pastel1");
506        let opt = surface.options_surface();
507        assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('Pastel1')");
508
509        surface.set_colormap_index(3);
510        let opt = surface.options_surface();
511        assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('jet')");
512
513        surface.set_colormap_name("turbo");
514        let opt = surface.options_surface();
515        assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('turbo')");
516
517        surface.set_surf_color("blue");
518        let opt = surface.options_surface();
519        assert_eq!(opt, ",rstride=3,cstride=4,color='blue'");
520
521        let mut surface = Surface::new();
522        surface
523            .set_surf_line_color("red")
524            .set_surf_line_style("--")
525            .set_surf_line_width(2.5);
526        let opt = surface.options_surface();
527        assert_eq!(
528            opt,
529            ",cmap=plt.get_cmap('bwr'),edgecolors='red',linestyle='--',linewidth=2.5"
530        );
531    }
532
533    #[test]
534    fn options_wireframe_works() {
535        let mut surface = Surface::new();
536        surface
537            .set_row_stride(3)
538            .set_col_stride(4)
539            .set_wire_line_color("red")
540            .set_wire_line_style("--")
541            .set_wire_line_width(2.5);
542        let opt = surface.options_wireframe();
543        assert_eq!(opt, ",rstride=3,cstride=4,color='red',linestyle='--',linewidth=2.5");
544    }
545
546    #[test]
547    fn options_points_works() {
548        let mut surface = Surface::new();
549        surface.set_row_stride(3).set_col_stride(4);
550        let opt = surface.options_points();
551        assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('bwr')");
552
553        surface.set_colormap_name("Pastel1");
554        let opt = surface.options_points();
555        assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('Pastel1')");
556
557        surface.set_colormap_index(3);
558        let opt = surface.options_points();
559        assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('jet')");
560
561        surface.set_colormap_name("turbo");
562        let opt = surface.options_points();
563        assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('turbo')");
564
565        let mut surface = Surface::new();
566        surface
567            .set_point_color("blue")
568            .set_point_line_color("red")
569            .set_point_size(100.0)
570            .set_point_style("*")
571            .set_point_line_width(3.0);
572        let opt = surface.options_points();
573        assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='blue',edgecolor='red'");
574
575        surface.set_point_void(true);
576        let opt = surface.options_points();
577        assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='red'");
578
579        surface.set_point_void(true).set_point_line_color("");
580        let opt = surface.options_points();
581        assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='black'");
582    }
583
584    #[test]
585    fn options_colorbar_works() {
586        let mut surface = Surface::new();
587        surface.set_number_format_cb("%.3f");
588        let opt = surface.options_colorbar();
589        assert_eq!(opt, ",format='%.3f'");
590    }
591
592    #[test]
593    fn draw_works() {
594        let mut surface = Surface::new();
595        surface
596            .set_with_wireframe(true)
597            .set_with_colorbar(true)
598            .set_colorbar_label("temperature");
599        let x = vec![vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5]];
600        let y = vec![vec![-0.5, -0.5, -0.5], vec![0.0, 0.0, 0.0], vec![0.5, 0.5, 0.5]];
601        let z = vec![vec![0.50, 0.25, 0.50], vec![0.25, 0.00, 0.25], vec![0.50, 0.25, 0.50]];
602        surface.draw(&x, &y, &z);
603        let b: &str = "x=np.array([[-0.5,0,0.5,],[-0.5,0,0.5,],[-0.5,0,0.5,],],dtype=float)\n\
604                       y=np.array([[-0.5,-0.5,-0.5,],[0,0,0,],[0.5,0.5,0.5,],],dtype=float)\n\
605                       z=np.array([[0.5,0.25,0.5,],[0.25,0,0.25,],[0.5,0.25,0.5,],],dtype=float)\n\
606                       sf=ax3d().plot_surface(x,y,z,cmap=plt.get_cmap('bwr'))\n\
607                       ax3d().plot_wireframe(x,y,z,color='black')\n\
608                       cb=plt.colorbar(sf)\n\
609                       cb.ax.set_ylabel(r'temperature')\n";
610        assert_eq!(surface.buffer, b);
611        surface.clear_buffer();
612        assert_eq!(surface.buffer, "");
613    }
614
615    #[test]
616    fn aligned_system_fails_on_wrong_input() {
617        let res = Surface::aligned_system(&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0]);
618        assert_eq!(res.err(), Some("a-to-b segment is too short"));
619    }
620
621    fn approx_eq(a: f64, b: f64, tol: f64) {
622        let diff = f64::abs(a - b);
623        if diff > tol {
624            panic!("numbers are not approximately equal. diff = {:?}", diff);
625        }
626    }
627
628    #[test]
629    #[should_panic(expected = "numbers are not approximately equal. diff = 1.0")]
630    fn approx_eq_captures_errors() {
631        approx_eq(1.0, 2.0, 1e-15);
632    }
633
634    #[test]
635    fn aligned_system_works() {
636        let (e0, e1, e2) = Surface::aligned_system(&[-1.0, 0.0, 0.0], &[8.0, 0.0, 0.0]).unwrap();
637        assert_eq!(e0, &[1.0, 0.0, 0.0]);
638        assert_eq!(e1, &[0.0, 1.0, 0.0]);
639        assert_eq!(e2, &[0.0, 0.0, 1.0]);
640
641        let (e0, e1, e2) = Surface::aligned_system(&[0.0, -3.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
642        assert_eq!(e0, &[0.0, 1.0, 0.0]);
643        assert_eq!(e1, &[1.0, 0.0, 0.0]);
644        assert_eq!(e2, &[0.0, 0.0, -1.0]);
645
646        let (e0, e1, e2) = Surface::aligned_system(&[0.0, 10.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
647        assert_eq!(e0, &[0.0, -1.0, 0.0]);
648        assert_eq!(e1, &[1.0, 0.0, 0.0]);
649        assert_eq!(e2, &[0.0, 0.0, 1.0]);
650
651        let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 80.0], &[0.0, 0.0, 7770.0]).unwrap();
652        assert_eq!(e0, &[0.0, 0.0, 1.0]);
653        assert_eq!(e1, &[1.0, 0.0, 0.0]);
654        assert_eq!(e2, &[0.0, 1.0, 0.0]);
655
656        let (m, n, l) = (3.0, 4.0, 5.0);
657        let (e0, e1, e2) = Surface::aligned_system(&[2.0, -7.0, 5.0], &[2.0 + m, -7.0 + n, 5.0]).unwrap();
658        let correct0 = &[m / l, n / l, 0.0];
659        let correct1 = &[n / l, -m / l, 0.0];
660        let correct2 = &[0.0, 0.0, -1.0];
661        for i in 0..3 {
662            approx_eq(e0[i], correct0[i], 1e-15);
663            approx_eq(e1[i], correct1[i], 1e-15);
664            approx_eq(e2[i], correct2[i], 1e-15);
665        }
666
667        let s = f64::sqrt(2.0) / 2.0;
668        let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 1.0], &[1.0, 0.0, 2.0]).unwrap();
669        let correct0 = &[s, 0.0, s];
670        let correct1 = &[s, 0.0, -s];
671        let correct2 = &[0.0, 1.0, 0.0];
672        for i in 0..3 {
673            approx_eq(e0[i], correct0[i], 1e-15);
674            approx_eq(e1[i], correct1[i], 1e-15);
675            approx_eq(e2[i], correct2[i], 1e-15);
676        }
677
678        let (c, d, e) = (1.0 / f64::sqrt(3.0), 1.0 / f64::sqrt(6.0), 1.0 / f64::sqrt(2.0));
679        let (e0, e1, e2) = Surface::aligned_system(&[3.0, 4.0, 5.0], &[13.0, 14.0, 15.0]).unwrap();
680        let correct0 = &[c, c, c];
681        let correct1 = &[2.0 * d, -d, -d];
682        let correct2 = &[0.0, e, -e];
683        for i in 0..3 {
684            approx_eq(e0[i], correct0[i], 1e-15);
685            approx_eq(e1[i], correct1[i], 1e-15);
686            approx_eq(e2[i], correct2[i], 1e-15);
687        }
688    }
689}