Skip to main content

oxirs_physics/
optics.rs

1//! Optical physics.
2//!
3//! Implements Snell's law, Fresnel equations, thin-lens equation, geometric
4//! ray tracing, diffraction grating analysis, Brewster angle, total internal
5//! reflection, optical path length, prism dispersion, and numerical aperture.
6
7use std::f64::consts::PI;
8
9// ---------------------------------------------------------------------------
10// Error type
11// ---------------------------------------------------------------------------
12
13/// Errors specific to optics calculations.
14#[derive(Debug, Clone, PartialEq)]
15pub enum OpticsError {
16    /// Total internal reflection occurs (no transmitted ray).
17    TotalInternalReflection,
18    /// A physical parameter is invalid (e.g. negative refractive index).
19    InvalidParameter(String),
20    /// No real image is formed.
21    NoImage,
22}
23
24impl std::fmt::Display for OpticsError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            OpticsError::TotalInternalReflection => write!(f, "total internal reflection"),
28            OpticsError::InvalidParameter(msg) => write!(f, "invalid parameter: {msg}"),
29            OpticsError::NoImage => write!(f, "no real image formed"),
30        }
31    }
32}
33
34impl std::error::Error for OpticsError {}
35
36pub type OpticsResult<T> = Result<T, OpticsError>;
37
38// ---------------------------------------------------------------------------
39// Surface description for ray tracing
40// ---------------------------------------------------------------------------
41
42/// A single optical surface (planar interface between two media).
43#[derive(Debug, Clone)]
44pub struct OpticalSurface {
45    /// Refractive index on the incoming side.
46    pub n1: f64,
47    /// Refractive index on the outgoing side.
48    pub n2: f64,
49}
50
51/// Result of tracing a ray through a sequence of surfaces.
52#[derive(Debug, Clone)]
53pub struct RayTraceResult {
54    /// Angle of incidence at each surface (radians).
55    pub incidence_angles: Vec<f64>,
56    /// Angle of refraction at each surface (radians).
57    pub refraction_angles: Vec<f64>,
58    /// Final transmitted angle (radians).
59    pub final_angle: f64,
60}
61
62// ---------------------------------------------------------------------------
63// Fresnel result
64// ---------------------------------------------------------------------------
65
66/// Reflectance and transmittance from the Fresnel equations.
67#[derive(Debug, Clone)]
68pub struct FresnelResult {
69    /// Reflectance for s-polarisation (TE).
70    pub rs: f64,
71    /// Reflectance for p-polarisation (TM).
72    pub rp: f64,
73    /// Transmittance for s-polarisation.
74    pub ts: f64,
75    /// Transmittance for p-polarisation.
76    pub tp: f64,
77}
78
79// ---------------------------------------------------------------------------
80// Thin lens result
81// ---------------------------------------------------------------------------
82
83/// Output of the thin lens equation.
84#[derive(Debug, Clone)]
85pub struct ThinLensResult {
86    /// Image distance (positive = real image on far side).
87    pub image_distance: f64,
88    /// Lateral magnification (negative = inverted).
89    pub magnification: f64,
90}
91
92// ---------------------------------------------------------------------------
93// Diffraction grating result
94// ---------------------------------------------------------------------------
95
96/// Result of diffraction grating analysis.
97#[derive(Debug, Clone)]
98pub struct DiffractionResult {
99    /// Maximum diffraction order observable.
100    pub max_order: i32,
101    /// Angles of maxima for each order `(order, angle_rad)`.
102    pub maxima: Vec<(i32, f64)>,
103    /// Resolving power `R = m * N`.
104    pub resolving_power: f64,
105}
106
107// ---------------------------------------------------------------------------
108// Core optics functions
109// ---------------------------------------------------------------------------
110
111/// Compute the refraction angle using Snell's law.
112///
113/// `n1 * sin(theta_i) = n2 * sin(theta_t)`
114///
115/// Returns the transmitted angle in radians.  Returns
116/// `OpticsError::TotalInternalReflection` if `sin(theta_t) > 1`.
117pub fn snells_law(n1: f64, n2: f64, theta_i: f64) -> OpticsResult<f64> {
118    if n1 <= 0.0 || n2 <= 0.0 {
119        return Err(OpticsError::InvalidParameter(
120            "refractive indices must be positive".to_string(),
121        ));
122    }
123    let sin_t = n1 * theta_i.sin() / n2;
124    if sin_t.abs() > 1.0 {
125        return Err(OpticsError::TotalInternalReflection);
126    }
127    Ok(sin_t.asin())
128}
129
130/// Compute Fresnel reflectance and transmittance at an interface.
131///
132/// Returns `FresnelResult` with s- and p-polarisation coefficients.
133pub fn fresnel(n1: f64, n2: f64, theta_i: f64) -> OpticsResult<FresnelResult> {
134    let theta_t = snells_law(n1, n2, theta_i)?;
135
136    let cos_i = theta_i.cos();
137    let cos_t = theta_t.cos();
138
139    // Amplitude reflection coefficients
140    let rs_num = n1 * cos_i - n2 * cos_t;
141    let rs_den = n1 * cos_i + n2 * cos_t;
142    let rp_num = n2 * cos_i - n1 * cos_t;
143    let rp_den = n2 * cos_i + n1 * cos_t;
144
145    let rs = if rs_den.abs() < 1e-15 {
146        0.0
147    } else {
148        (rs_num / rs_den).powi(2)
149    };
150    let rp = if rp_den.abs() < 1e-15 {
151        0.0
152    } else {
153        (rp_num / rp_den).powi(2)
154    };
155
156    Ok(FresnelResult {
157        rs,
158        rp,
159        ts: 1.0 - rs,
160        tp: 1.0 - rp,
161    })
162}
163
164/// Thin lens equation: `1/f = 1/d_o + 1/d_i`.
165///
166/// `focal_length` and `object_distance` are signed per the sign convention
167/// (positive for real objects/converging lenses).
168pub fn thin_lens(focal_length: f64, object_distance: f64) -> OpticsResult<ThinLensResult> {
169    if focal_length.abs() < 1e-15 {
170        return Err(OpticsError::InvalidParameter(
171            "focal length cannot be zero".to_string(),
172        ));
173    }
174    let inv_di = 1.0 / focal_length - 1.0 / object_distance;
175    if inv_di.abs() < 1e-15 {
176        return Err(OpticsError::NoImage);
177    }
178    let image_distance = 1.0 / inv_di;
179    let magnification = -image_distance / object_distance;
180
181    Ok(ThinLensResult {
182        image_distance,
183        magnification,
184    })
185}
186
187/// Trace a ray through a sequence of optical surfaces.
188///
189/// `initial_angle` is the angle of incidence on the first surface (radians).
190pub fn trace_ray(surfaces: &[OpticalSurface], initial_angle: f64) -> OpticsResult<RayTraceResult> {
191    let mut incidence_angles = Vec::with_capacity(surfaces.len());
192    let mut refraction_angles = Vec::with_capacity(surfaces.len());
193
194    let mut current_angle = initial_angle;
195
196    for surface in surfaces {
197        incidence_angles.push(current_angle);
198        let refracted = snells_law(surface.n1, surface.n2, current_angle)?;
199        refraction_angles.push(refracted);
200        current_angle = refracted;
201    }
202
203    Ok(RayTraceResult {
204        incidence_angles,
205        refraction_angles,
206        final_angle: current_angle,
207    })
208}
209
210/// Analyse a diffraction grating.
211///
212/// * `d` – grating spacing in metres.
213/// * `wavelength` – light wavelength in metres.
214/// * `n_slits` – number of slits.
215pub fn diffraction_grating(
216    d: f64,
217    wavelength: f64,
218    n_slits: usize,
219) -> OpticsResult<DiffractionResult> {
220    if d <= 0.0 || wavelength <= 0.0 {
221        return Err(OpticsError::InvalidParameter(
222            "grating spacing and wavelength must be positive".to_string(),
223        ));
224    }
225
226    let max_order = (d / wavelength).floor() as i32;
227    let mut maxima = Vec::new();
228
229    for m in -max_order..=max_order {
230        let sin_theta = (m as f64) * wavelength / d;
231        if sin_theta.abs() <= 1.0 {
232            maxima.push((m, sin_theta.asin()));
233        }
234    }
235
236    let resolving_power = max_order as f64 * n_slits as f64;
237
238    Ok(DiffractionResult {
239        max_order,
240        maxima,
241        resolving_power,
242    })
243}
244
245/// Compute the Brewster angle (angle of incidence at which p-polarised
246/// light is perfectly transmitted).
247///
248/// `theta_B = atan(n2 / n1)`
249pub fn brewster_angle(n1: f64, n2: f64) -> OpticsResult<f64> {
250    if n1 <= 0.0 || n2 <= 0.0 {
251        return Err(OpticsError::InvalidParameter(
252            "refractive indices must be positive".to_string(),
253        ));
254    }
255    Ok((n2 / n1).atan())
256}
257
258/// Compute the critical angle for total internal reflection.
259///
260/// `theta_c = asin(n2 / n1)` where `n1 > n2`.
261pub fn critical_angle(n1: f64, n2: f64) -> OpticsResult<f64> {
262    if n1 <= 0.0 || n2 <= 0.0 {
263        return Err(OpticsError::InvalidParameter(
264            "refractive indices must be positive".to_string(),
265        ));
266    }
267    if n1 <= n2 {
268        return Err(OpticsError::InvalidParameter(
269            "n1 must be greater than n2 for total internal reflection".to_string(),
270        ));
271    }
272    let ratio = n2 / n1;
273    Ok(ratio.asin())
274}
275
276/// Compute the optical path length through a medium.
277///
278/// `OPL = n * d`
279pub fn optical_path_length(refractive_index: f64, physical_length: f64) -> OpticsResult<f64> {
280    if refractive_index <= 0.0 {
281        return Err(OpticsError::InvalidParameter(
282            "refractive index must be positive".to_string(),
283        ));
284    }
285    if physical_length < 0.0 {
286        return Err(OpticsError::InvalidParameter(
287            "physical length cannot be negative".to_string(),
288        ));
289    }
290    Ok(refractive_index * physical_length)
291}
292
293/// Compute the minimum deviation angle of a prism.
294///
295/// At minimum deviation: `n = sin((A + D_min)/2) / sin(A/2)`
296/// Solving: `D_min = 2 * asin(n * sin(A/2)) - A`
297///
298/// * `n` – refractive index of the prism.
299/// * `apex_angle` – apex angle of the prism in radians.
300pub fn prism_minimum_deviation(n: f64, apex_angle: f64) -> OpticsResult<f64> {
301    if n <= 0.0 {
302        return Err(OpticsError::InvalidParameter(
303            "refractive index must be positive".to_string(),
304        ));
305    }
306    if apex_angle <= 0.0 || apex_angle >= PI {
307        return Err(OpticsError::InvalidParameter(
308            "apex angle must be in (0, pi)".to_string(),
309        ));
310    }
311
312    let sin_half = (apex_angle / 2.0).sin();
313    let arg = n * sin_half;
314    if arg.abs() > 1.0 {
315        return Err(OpticsError::TotalInternalReflection);
316    }
317    let d_min = 2.0 * arg.asin() - apex_angle;
318    Ok(d_min)
319}
320
321/// Compute the numerical aperture of a fiber or lens.
322///
323/// `NA = sqrt(n_core^2 - n_cladding^2)`
324pub fn numerical_aperture(n_core: f64, n_cladding: f64) -> OpticsResult<f64> {
325    if n_core <= 0.0 || n_cladding <= 0.0 {
326        return Err(OpticsError::InvalidParameter(
327            "refractive indices must be positive".to_string(),
328        ));
329    }
330    if n_core < n_cladding {
331        return Err(OpticsError::InvalidParameter(
332            "core index must be >= cladding index".to_string(),
333        ));
334    }
335    let na_sq = n_core * n_core - n_cladding * n_cladding;
336    Ok(na_sq.sqrt())
337}
338
339// ---------------------------------------------------------------------------
340// Tests
341// ---------------------------------------------------------------------------
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    const DEG: f64 = PI / 180.0;
348
349    // ── Snell's law ──────────────────────────────────────────────────────
350
351    #[test]
352    fn test_snells_law_normal_incidence() {
353        let theta_t = snells_law(1.0, 1.5, 0.0).expect("should not fail");
354        assert!(theta_t.abs() < 1e-10, "normal incidence → 0 refraction");
355    }
356
357    #[test]
358    fn test_snells_law_air_to_glass() {
359        let theta_t = snells_law(1.0, 1.5, 30.0 * DEG).expect("should work");
360        // sin(30) = 0.5 → sin(theta_t) = 0.5/1.5 ≈ 0.333 → theta_t ≈ 19.47 deg
361        let expected = (1.0_f64 / 3.0).asin();
362        assert!((theta_t - expected).abs() < 1e-10);
363    }
364
365    #[test]
366    fn test_snells_law_total_internal_reflection() {
367        // Glass to air at steep angle
368        let result = snells_law(1.5, 1.0, 50.0 * DEG);
369        assert!(result.is_err());
370    }
371
372    #[test]
373    fn test_snells_law_negative_n() {
374        let result = snells_law(-1.0, 1.5, 0.0);
375        assert!(result.is_err());
376    }
377
378    #[test]
379    fn test_snells_law_symmetry() {
380        // Reversibility: n1→n2 at theta gives theta_t, n2→n1 at theta_t gives theta
381        let theta_i = 30.0 * DEG;
382        let theta_t = snells_law(1.0, 1.5, theta_i).expect("ok");
383        let theta_back = snells_law(1.5, 1.0, theta_t).expect("ok");
384        assert!((theta_back - theta_i).abs() < 1e-10);
385    }
386
387    // ── Fresnel equations ────────────────────────────────────────────────
388
389    #[test]
390    fn test_fresnel_normal_incidence() {
391        let f = fresnel(1.0, 1.5, 0.0).expect("ok");
392        // At normal incidence Rs == Rp
393        assert!((f.rs - f.rp).abs() < 1e-10);
394    }
395
396    #[test]
397    fn test_fresnel_energy_conservation() {
398        let f = fresnel(1.0, 1.5, 30.0 * DEG).expect("ok");
399        assert!((f.rs + f.ts - 1.0).abs() < 1e-10);
400        assert!((f.rp + f.tp - 1.0).abs() < 1e-10);
401    }
402
403    #[test]
404    fn test_fresnel_same_medium() {
405        let f = fresnel(1.5, 1.5, 20.0 * DEG).expect("ok");
406        assert!(f.rs < 1e-10);
407        assert!(f.rp < 1e-10);
408    }
409
410    #[test]
411    fn test_fresnel_total_internal_reflection() {
412        let result = fresnel(1.5, 1.0, 50.0 * DEG);
413        assert!(result.is_err());
414    }
415
416    // ── Thin lens equation ───────────────────────────────────────────────
417
418    #[test]
419    fn test_thin_lens_converging() {
420        let r = thin_lens(10.0, 20.0).expect("ok");
421        // 1/10 - 1/20 = 1/20 → d_i = 20
422        assert!((r.image_distance - 20.0).abs() < 1e-10);
423        // m = -20/20 = -1 (inverted, same size)
424        assert!((r.magnification - (-1.0)).abs() < 1e-10);
425    }
426
427    #[test]
428    fn test_thin_lens_object_at_2f() {
429        let f = 5.0;
430        let d_o = 2.0 * f;
431        let r = thin_lens(f, d_o).expect("ok");
432        assert!((r.image_distance - d_o).abs() < 1e-10);
433    }
434
435    #[test]
436    fn test_thin_lens_zero_focal() {
437        let result = thin_lens(0.0, 10.0);
438        assert!(result.is_err());
439    }
440
441    #[test]
442    fn test_thin_lens_virtual_image() {
443        // Object inside focal length → virtual image (negative d_i)
444        let r = thin_lens(10.0, 5.0).expect("ok");
445        assert!(r.image_distance < 0.0);
446        assert!(r.magnification > 0.0); // upright
447    }
448
449    // ── Ray tracing ──────────────────────────────────────────────────────
450
451    #[test]
452    fn test_trace_ray_single_surface() {
453        let surfaces = vec![OpticalSurface { n1: 1.0, n2: 1.5 }];
454        let result = trace_ray(&surfaces, 30.0 * DEG).expect("ok");
455        assert_eq!(result.incidence_angles.len(), 1);
456        assert_eq!(result.refraction_angles.len(), 1);
457    }
458
459    #[test]
460    fn test_trace_ray_two_surfaces_roundtrip() {
461        // Air → glass → air: final angle should equal initial
462        let surfaces = vec![
463            OpticalSurface { n1: 1.0, n2: 1.5 },
464            OpticalSurface { n1: 1.5, n2: 1.0 },
465        ];
466        let initial = 20.0 * DEG;
467        let result = trace_ray(&surfaces, initial).expect("ok");
468        assert!((result.final_angle - initial).abs() < 1e-10);
469    }
470
471    #[test]
472    fn test_trace_ray_empty() {
473        let result = trace_ray(&[], 0.0).expect("ok");
474        assert!(result.incidence_angles.is_empty());
475        assert_eq!(result.final_angle, 0.0);
476    }
477
478    // ── Diffraction grating ──────────────────────────────────────────────
479
480    #[test]
481    fn test_diffraction_grating_zeroth_order() {
482        let result = diffraction_grating(1e-6, 500e-9, 100).expect("ok");
483        // Zeroth order is always at 0
484        assert!(result
485            .maxima
486            .iter()
487            .any(|(m, a)| *m == 0 && a.abs() < 1e-10));
488    }
489
490    #[test]
491    fn test_diffraction_grating_max_order() {
492        let d = 2e-6;
493        let wl = 500e-9;
494        let result = diffraction_grating(d, wl, 50).expect("ok");
495        // max_order = floor(d/wl) = floor(4.0) = 4
496        assert_eq!(result.max_order, 4);
497    }
498
499    #[test]
500    fn test_diffraction_grating_resolving_power() {
501        let result = diffraction_grating(1e-6, 500e-9, 100).expect("ok");
502        assert!(result.resolving_power > 0.0);
503    }
504
505    #[test]
506    fn test_diffraction_grating_invalid() {
507        assert!(diffraction_grating(0.0, 500e-9, 10).is_err());
508        assert!(diffraction_grating(1e-6, -1.0, 10).is_err());
509    }
510
511    // ── Brewster angle ───────────────────────────────────────────────────
512
513    #[test]
514    fn test_brewster_angle_air_glass() {
515        let theta_b = brewster_angle(1.0, 1.5).expect("ok");
516        let expected = (1.5_f64).atan();
517        assert!((theta_b - expected).abs() < 1e-10);
518    }
519
520    #[test]
521    fn test_brewster_angle_complement_critical() {
522        // For air-glass, Brewster + critical < 90 deg
523        let theta_b = brewster_angle(1.5, 1.0).expect("ok");
524        let theta_c = critical_angle(1.5, 1.0).expect("ok");
525        assert!(theta_b + theta_c < PI / 2.0 + 0.1);
526    }
527
528    #[test]
529    fn test_brewster_angle_invalid() {
530        assert!(brewster_angle(-1.0, 1.5).is_err());
531    }
532
533    // ── Critical angle ───────────────────────────────────────────────────
534
535    #[test]
536    fn test_critical_angle_glass_air() {
537        let theta_c = critical_angle(1.5, 1.0).expect("ok");
538        let expected = (1.0_f64 / 1.5).asin();
539        assert!((theta_c - expected).abs() < 1e-10);
540    }
541
542    #[test]
543    fn test_critical_angle_n1_less_than_n2() {
544        // No TIR possible when going from less dense to denser
545        assert!(critical_angle(1.0, 1.5).is_err());
546    }
547
548    #[test]
549    fn test_critical_angle_equal_indices() {
550        assert!(critical_angle(1.5, 1.5).is_err());
551    }
552
553    // ── Optical path length ──────────────────────────────────────────────
554
555    #[test]
556    fn test_optical_path_length_vacuum() {
557        let opl = optical_path_length(1.0, 10.0).expect("ok");
558        assert!((opl - 10.0).abs() < 1e-10);
559    }
560
561    #[test]
562    fn test_optical_path_length_glass() {
563        let opl = optical_path_length(1.5, 10.0).expect("ok");
564        assert!((opl - 15.0).abs() < 1e-10);
565    }
566
567    #[test]
568    fn test_optical_path_length_zero_length() {
569        let opl = optical_path_length(1.5, 0.0).expect("ok");
570        assert!(opl.abs() < 1e-10);
571    }
572
573    #[test]
574    fn test_optical_path_length_invalid_n() {
575        assert!(optical_path_length(0.0, 10.0).is_err());
576    }
577
578    #[test]
579    fn test_optical_path_length_negative_length() {
580        assert!(optical_path_length(1.5, -1.0).is_err());
581    }
582
583    // ── Prism minimum deviation ──────────────────────────────────────────
584
585    #[test]
586    fn test_prism_min_deviation_equilateral() {
587        // n=1.5, A=60deg → D_min ≈ 2*asin(1.5*sin(30)) - 60 deg
588        let a = 60.0 * DEG;
589        let d = prism_minimum_deviation(1.5, a).expect("ok");
590        // For n=1.5, A=60: D_min ≈ 2*asin(0.75) - pi/3 ≈ 37.18 deg
591        let expected = 2.0 * (1.5 * (a / 2.0).sin()).asin() - a;
592        assert!((d - expected).abs() < 1e-10);
593    }
594
595    #[test]
596    fn test_prism_min_deviation_positive() {
597        let d = prism_minimum_deviation(1.5, 60.0 * DEG).expect("ok");
598        assert!(d > 0.0);
599    }
600
601    #[test]
602    fn test_prism_min_deviation_invalid_n() {
603        assert!(prism_minimum_deviation(0.0, 60.0 * DEG).is_err());
604    }
605
606    #[test]
607    fn test_prism_min_deviation_invalid_angle() {
608        assert!(prism_minimum_deviation(1.5, 0.0).is_err());
609        assert!(prism_minimum_deviation(1.5, PI).is_err());
610    }
611
612    // ── Numerical aperture ───────────────────────────────────────────────
613
614    #[test]
615    fn test_numerical_aperture_fiber() {
616        let na = numerical_aperture(1.5, 1.45).expect("ok");
617        let expected = ((1.5f64).powi(2) - (1.45f64).powi(2)).sqrt();
618        assert!((na - expected).abs() < 1e-10);
619    }
620
621    #[test]
622    fn test_numerical_aperture_same_index() {
623        let na = numerical_aperture(1.5, 1.5).expect("ok");
624        assert!(na.abs() < 1e-10);
625    }
626
627    #[test]
628    fn test_numerical_aperture_invalid() {
629        assert!(numerical_aperture(1.0, 1.5).is_err()); // core < cladding
630        assert!(numerical_aperture(-1.0, 0.5).is_err());
631    }
632
633    // ── OpticsError display ──────────────────────────────────────────────
634
635    #[test]
636    fn test_optics_error_display_tir() {
637        let e = OpticsError::TotalInternalReflection;
638        assert!(format!("{e}").contains("total internal reflection"));
639    }
640
641    #[test]
642    fn test_optics_error_display_invalid() {
643        let e = OpticsError::InvalidParameter("test".to_string());
644        assert!(format!("{e}").contains("test"));
645    }
646
647    #[test]
648    fn test_optics_error_display_no_image() {
649        let e = OpticsError::NoImage;
650        assert!(format!("{e}").contains("no real image"));
651    }
652
653    // ── Struct field access ──────────────────────────────────────────────
654
655    #[test]
656    fn test_ray_trace_result_fields() {
657        let surfaces = vec![OpticalSurface { n1: 1.0, n2: 1.5 }];
658        let r = trace_ray(&surfaces, 10.0 * DEG).expect("ok");
659        assert_eq!(r.incidence_angles.len(), 1);
660        assert_eq!(r.refraction_angles.len(), 1);
661        assert!(r.final_angle < 10.0 * DEG); // bends toward normal
662    }
663
664    #[test]
665    fn test_diffraction_result_maxima_symmetric() {
666        let r = diffraction_grating(1e-6, 500e-9, 10).expect("ok");
667        // For each positive order there should be a matching negative order
668        for &(m, angle) in &r.maxima {
669            if m > 0 {
670                let neg = r.maxima.iter().find(|&&(mm, _)| mm == -m);
671                assert!(neg.is_some());
672                // Angles should be opposite in sign
673                let neg_angle = neg.map(|&(_, a)| a).unwrap_or(0.0);
674                assert!((angle + neg_angle).abs() < 1e-10);
675            }
676        }
677    }
678
679    #[test]
680    fn test_thin_lens_result_fields() {
681        let r = thin_lens(10.0, 30.0).expect("ok");
682        assert!(r.image_distance > 0.0);
683        assert!(r.magnification < 0.0); // inverted real image
684    }
685}