From 4cfa29334a94b85f0598bbc4e8d84830e1116fcf Mon Sep 17 00:00:00 2001
From: "y.zobus" <y.zobus@gsi.de>
Date: Wed, 16 Oct 2024 10:50:23 +0200
Subject: [PATCH] feat: :sparkles: added different colors to bounces in hitmap
 plots

---
 opossum/examples/ghost_focus.rs          |   2 +-
 opossum/src/ray.rs                       |   2 +-
 opossum/src/reporting/analysis_report.rs |   2 +-
 opossum/src/surface/hit_map.rs           | 100 ++++++++++++++---------
 opossum/src/surface/optical_surface.rs   |   4 +-
 5 files changed, 66 insertions(+), 44 deletions(-)

diff --git a/opossum/examples/ghost_focus.rs b/opossum/examples/ghost_focus.rs
index dd856e5f..9313760d 100644
--- a/opossum/examples/ghost_focus.rs
+++ b/opossum/examples/ghost_focus.rs
@@ -45,7 +45,7 @@ fn main() -> OpmResult<()> {
     .with_tilt(degree!(0.0, 5.0, 0.0))?;
     wedge.set_coating(&PortType::Input, "front", &CoatingType::Fresnel)?;
     wedge.set_coating(&PortType::Input, "front", &CoatingType::Fresnel)?;
-    let i_w=scenery.add_node(wedge)?;
+    let i_w = scenery.add_node(wedge)?;
 
     let i_sd2 = scenery.add_node(SpotDiagram::default())?;
     scenery.connect_nodes(i_src, "out1", i_sd, "in1", millimeter!(20.0))?;
diff --git a/opossum/src/ray.rs b/opossum/src/ray.rs
index 15b53509..8e29c9a1 100644
--- a/opossum/src/ray.rs
+++ b/opossum/src/ray.rs
@@ -448,7 +448,7 @@ impl Ray {
                 self.refractive_index = n2;
                 self.number_of_refractions += 1;
                 // save on hit map of surface
-                os.add_to_hit_map((intersection_point, input_energy));
+                os.add_to_hit_map((intersection_point, input_energy), self.number_of_bounces);
 
                 Ok(Some(reflected_ray))
             } else {
diff --git a/opossum/src/reporting/analysis_report.rs b/opossum/src/reporting/analysis_report.rs
index d2c77d36..107d8c56 100644
--- a/opossum/src/reporting/analysis_report.rs
+++ b/opossum/src/reporting/analysis_report.rs
@@ -90,7 +90,7 @@ impl AnalysisReport {
     ///
     /// This information is used i.e. in the [`HtmlReport`].
     pub fn set_analysis_type(&mut self, analysis_type: &str) {
-        self.analysis_type = analysis_type.to_owned();
+        analysis_type.clone_into(&mut self.analysis_type);
     }
 }
 
diff --git a/opossum/src/surface/hit_map.rs b/opossum/src/surface/hit_map.rs
index b889f555..6aa5af55 100644
--- a/opossum/src/surface/hit_map.rs
+++ b/opossum/src/surface/hit_map.rs
@@ -1,12 +1,10 @@
 //! Data structure for storing intersection points (and energies) of [`Rays`](crate::rays::Rays) hitting an
 //! [`OpticalSurface`](crate::surface::OpticalSurface).
+use colorous::Color;
 use nalgebra::{DVector, MatrixXx2, Point2, Point3};
 use plotters::style::RGBAColor;
 use serde::{Deserialize, Serialize};
-use uom::si::{
-    f64::{Energy, Length},
-    length::meter,
-};
+use uom::si::f64::{Energy, Length};
 
 use crate::{
     error::OpmResult,
@@ -22,20 +20,24 @@ use crate::{
 /// [`OpticalSurface`](crate::surface::OpticalSurface).
 #[derive(Default, Debug, Clone, Serialize, Deserialize)]
 pub struct HitMap {
-    hit_map: Vec<(Point3<Length>, Energy)>,
+    hit_map: Vec<Vec<(Point3<Length>, Energy)>>,
 }
 impl HitMap {
     /// Returns a reference to the hit map of this [`HitMap`].
     ///
     /// This function returns a vector of intersection points (with energies) of [`Rays`](crate::rays::Rays) that hit the surface.
     #[must_use]
-    pub fn hit_map(&self) -> &[(Point3<Length>, Energy)] {
+    pub fn hit_map(&self) -> &[Vec<(Point3<Length>, Energy)>] {
         &self.hit_map
     }
     /// Add intersection point (with energy) to this [`HitMap`].
     ///
-    pub fn add_point(&mut self, hit_point: (Point3<Length>, Energy)) {
-        self.hit_map.push(hit_point);
+    pub fn add_point(&mut self, hit_point: (Point3<Length>, Energy), bounce: usize) {
+        if self.hit_map.len() <= bounce {
+            self.hit_map.push(vec![hit_point]);
+        } else {
+            self.hit_map[bounce].push(hit_point);
+        }
     }
     /// Reset this [`HitMap`].
     ///
@@ -61,22 +63,25 @@ impl Plottable for HitMap {
         _legend: bool,
     ) -> OpmResult<Option<Vec<PlotSeries>>> {
         //ray plot series
+        let mut plt_series = Vec::<PlotSeries>::with_capacity(self.hit_map.len());
+        let mut xy_positions = Vec::<Vec<Point2<Length>>>::with_capacity(self.hit_map.len());
         let mut x_max = f64::NEG_INFINITY;
         let mut y_max = f64::NEG_INFINITY;
+        let mut x_min = f64::INFINITY;
+        let mut y_min = f64::INFINITY;
+
+        for hit_map in &self.hit_map {
+            let mut xy_pos = Vec::<Point2<Length>>::with_capacity(hit_map.len());
+            for p in hit_map {
+                xy_pos.push(Point2::new(p.0.x, p.0.y));
 
-        let xy_pos: Vec<Point2<Length>> = self
-            .hit_map
-            .iter()
-            .map(|p| Point2::new(p.0.x, p.0.y))
-            .collect();
-        x_max = xy_pos
-            .iter()
-            .map(|p| p.x.get::<meter>())
-            .fold(x_max, |arg0, x| if x.abs() > arg0 { x.abs() } else { arg0 });
-        y_max = xy_pos
-            .iter()
-            .map(|p| p.y.get::<meter>())
-            .fold(y_max, |arg0, y| if y.abs() > arg0 { y.abs() } else { arg0 });
+                x_max = x_max.max(p.0.x.value);
+                y_max = y_max.max(p.0.y.value);
+                x_min = x_min.min(p.0.x.value);
+                y_min = y_min.min(p.0.y.value);
+            }
+            xy_positions.push(xy_pos);
+        }
         let x_exponent = get_exponent_for_base_unit_in_e3_steps(x_max);
         let y_exponent = get_exponent_for_base_unit_in_e3_steps(y_max);
         let y_prefix = get_prefix_for_base_unit(y_max);
@@ -85,29 +90,46 @@ impl Plottable for HitMap {
         plt_type.set_plot_param(&PlotArgs::XLabel(format!("x position ({y_prefix}m)")))?;
         plt_type.set_plot_param(&PlotArgs::YLabel(format!("y position ({x_prefix}m)")))?;
 
-        let mut plt_series = Vec::<PlotSeries>::with_capacity(1);
-        let x_vals = xy_pos
-            .iter()
-            .map(|p| get_unit_value_as_length_with_format_by_exponent(p.x, x_exponent))
-            .collect::<Vec<f64>>();
-        let y_vals = xy_pos
-            .iter()
-            .map(|p| get_unit_value_as_length_with_format_by_exponent(p.y, y_exponent))
-            .collect::<Vec<f64>>();
+        for (i, xy_pos) in xy_positions.iter().enumerate() {
+            let x_vals = xy_pos
+                .iter()
+                .map(|p| get_unit_value_as_length_with_format_by_exponent(p.x, x_exponent))
+                .collect::<Vec<f64>>();
+            let y_vals = xy_pos
+                .iter()
+                .map(|p| get_unit_value_as_length_with_format_by_exponent(p.y, y_exponent))
+                .collect::<Vec<f64>>();
+
+            let data = PlotData::Dim2 {
+                xy_data: MatrixXx2::from_columns(&[
+                    DVector::from_vec(x_vals),
+                    DVector::from_vec(y_vals),
+                ]),
+            };
 
-        let data = PlotData::Dim2 {
-            xy_data: MatrixXx2::from_columns(&[
-                DVector::from_vec(x_vals),
-                DVector::from_vec(y_vals),
-            ]),
-        };
+            let gradient = colorous::TURBO;
+            let c = if self.hit_map.len() == 1 {
+                Color { r: 255, g: 0, b: 0 }
+            } else if self.hit_map.len() > 10 {
+                gradient.eval_rational(i, self.hit_map.len())
+            } else {
+                colorous::CATEGORY10[i]
+            };
+            let label = format!("Bounce: {i}");
+            plt_series.push(PlotSeries::new(
+                &data,
+                RGBAColor(c.r, c.g, c.b, 1.),
+                Some(label),
+            ));
+        }
 
-        plt_series.push(PlotSeries::new(&data, RGBAColor(255, 0, 0, 1.), None));
         x_max *= f64::powi(10., -x_exponent);
         y_max *= f64::powi(10., -y_exponent);
+        x_min *= f64::powi(10., -x_exponent);
+        y_min *= f64::powi(10., -y_exponent);
 
-        let x_limits = AxLims::create_useful_axlims(-x_max * 1.1, x_max * 1.1);
-        let y_limits = AxLims::create_useful_axlims(-y_max * 1.1, y_max * 1.1);
+        let x_limits = AxLims::create_useful_axlims(x_min * 1.1, x_max * 1.1);
+        let y_limits = AxLims::create_useful_axlims(y_min * 1.1, y_max * 1.1);
 
         plt_type.set_plot_param(&PlotArgs::XLim(x_limits))?;
         plt_type.set_plot_param(&PlotArgs::YLim(y_limits))?;
diff --git a/opossum/src/surface/optical_surface.rs b/opossum/src/surface/optical_surface.rs
index 8798b1d0..9cf78e72 100644
--- a/opossum/src/surface/optical_surface.rs
+++ b/opossum/src/surface/optical_surface.rs
@@ -93,8 +93,8 @@ impl OpticalSurface {
     }
     /// Add intersection point (with energy) to hit map.
     ///
-    pub fn add_to_hit_map(&mut self, hit_point: (Point3<Length>, Energy)) {
-        self.hit_map.add_point(hit_point);
+    pub fn add_to_hit_map(&mut self, hit_point: (Point3<Length>, Energy), bounce: usize) {
+        self.hit_map.add_point(hit_point, bounce);
     }
     /// Reset hit map of this [`OpticalSurface`].
     pub fn reset_hit_map(&mut self) {
-- 
GitLab