From 15115f460fb7f1428816369a7bb04e326d7064cf Mon Sep 17 00:00:00 2001
From: Chao Peng <cpeng@anl.gov>
Date: Thu, 1 Jul 2021 02:24:59 +0000
Subject: [PATCH] implement Shashlik Calorimeter

---
 athena.xml                         |   6 -
 compact/ci_ecal.xml                |  76 ++++++++++++
 compact/ci_ecal_shashlik.xml       |  76 ++++++++++++
 compact/ecal.xml                   |  84 +------------
 src/GaseousRICH_geo.cpp            |   2 +-
 src/GeometryHelpers.cpp            | 134 ++++++++++++++------
 src/GeometryHelpers.h              |   7 +-
 src/HomogeneousCalorimeter_geo.cpp |  28 ++---
 src/ShashlikCalorimeter.cpp        | 189 +++++++++++++++++++++++++++++
 src/ce_MRICH.cpp                   |   2 +-
 10 files changed, 462 insertions(+), 142 deletions(-)
 create mode 100644 compact/ci_ecal.xml
 create mode 100644 compact/ci_ecal_shashlik.xml
 create mode 100644 src/ShashlikCalorimeter.cpp

diff --git a/athena.xml b/athena.xml
index a4c3f87d..ff84f5a1 100644
--- a/athena.xml
+++ b/athena.xml
@@ -108,8 +108,6 @@
   </display>
 
   <comment> Include the IP components first </comment>
-  <include ref="ip6/forward_ion_beamline.xml"/>
-  <include ref="ip6/beampipe.xml"/>
 
   <detectors>
     <detector id="VertexBarrelSubAssembly_ID" 
@@ -203,10 +201,6 @@
   <include ref="compact/forward_trd.xml"/>
   <include ref="compact/gaseous_rich.xml"/>
 
-  <include ref="ip6/B0_tracker.xml"/>
-  <include ref="ip6/far_forward_offM_tracker.xml"/>
-  <include ref="ip6/far_forward_romanpots.xml"/>
-  <include ref="ip6/far_forward_detectors.xml"/>
 
   <!--
   <include ref="compact/mm_tracker_barrel.xml"/>
diff --git a/compact/ci_ecal.xml b/compact/ci_ecal.xml
new file mode 100644
index 00000000..5ea301e7
--- /dev/null
+++ b/compact/ci_ecal.xml
@@ -0,0 +1,76 @@
+<lccdd>
+  <define>
+    <constant name="EcalEndcapP_rmax" value="Solenoid_rmax "/>
+  </define>
+
+
+  <limits>
+  </limits>
+
+  <regions>
+  </regions>
+
+  <!-- Common Generic visualization attributes -->
+  <comment>Common Generic visualization attributes</comment>
+  <display>
+  </display>
+
+  <detectors>
+
+    <comment>
+      ------------------------------------------
+      Forward (Positive Z) Endcap EM Calorimeter
+      ------------------------------------------
+      A layered EM calorimeter with tungsten and silicon (or scintillator) strips
+    </comment>
+    <detector id="ECalEndcapP_ID" 
+      name="EcalEndcapP" 
+      reflect="false" 
+      type="refdet_PolyhedraEndcapCalorimeter2" 
+      readout="EcalEndcapPHits" 
+      vis="EcalEndcapVis" 
+      calorimeterType="EM_ENDCAP" >
+      <position x="0" y="0" z="-0"/>
+      <dimensions 
+        numsides="CaloSides" 
+        zmin="EcalEndcapP_zmin" 
+        rmin="EcalEndcapP_rmin" 
+        rmax="EcalEndcapP_rmax " />
+      <layer repeat="EcalEndcapPLayer1_NRepeat">
+        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+        <slice material="Copper" thickness="EcalCopperThickness"/>
+        <slice material="Kapton" thickness="EcalKaptonThickness"/>
+        <slice material="Air" thickness="EcalAir1Thickness"/>
+      </layer>
+      <layer repeat="EcalEndcapPLayer2_NRepeat">
+        <slice material="TungstenDens24" thickness="EcalThinTungstenThickness"/>
+        <slice material="Air" thickness="EcalAir2Thickness"/>
+        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+        <slice material="Copper" thickness="EcalCopperThickness"/>
+        <slice material="Kapton" thickness="EcalKaptonThickness"/>
+        <slice material="Air" thickness="EcalAir1Thickness"/>
+      </layer>
+      <layer repeat="EcalEndcapPLayer3_NRepeat">
+        <slice material="TungstenDens24" thickness="EcalThickTungstenThickness"/>
+        <slice material="Air" thickness="EcalAir2Thickness"/>
+        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+        <slice material="Copper" thickness="EcalCopperThickness"/>
+        <slice material="Kapton" thickness="EcalKaptonThickness"/>
+        <slice material="Air" thickness="EcalAir1Thickness"/>
+      </layer>
+    </detector>
+  </detectors>
+
+  <!--  Definition of the readout segmentation/definition  -->
+  <readouts>
+    <readout name="EcalEndcapPHits">
+      <segmentation type="CartesianGridXY" grid_size_x="3.5 * mm" grid_size_y="3.5 * mm"/>
+      <id>system:8,barrel:3,module:4,layer:6,slice:5,x:32:-16,y:-16</id>
+    </readout>
+  </readouts>
+
+  <plugins>
+  </plugins>
+
+
+</lccdd>
diff --git a/compact/ci_ecal_shashlik.xml b/compact/ci_ecal_shashlik.xml
new file mode 100644
index 00000000..171c59a4
--- /dev/null
+++ b/compact/ci_ecal_shashlik.xml
@@ -0,0 +1,76 @@
+<lccdd>
+  <define>
+    <constant name="EcalEndcapP_rmax" value="Solenoid_rmax "/>
+  </define>
+
+
+  <limits>
+  </limits>
+
+  <regions>
+  </regions>
+
+  <!-- Common Generic visualization attributes -->
+  <comment>Common Generic visualization attributes</comment>
+  <display>
+  </display>
+
+  <detectors>
+
+    <comment>
+      ------------------------------------------
+      Forward (Positive Z) Endcap EM Calorimeter
+      ------------------------------------------
+      An EM calorimeter with shashlik hexagon modules
+    </comment>
+    <detector id="ECalEndcapP_ID" 
+      name="EcalEndcapP" 
+      type="ShashlikCalorimeter" 
+      readout="EcalEndcapPHits">
+      <position x="0" y="0" z="EcalEndcapP_zmin"/>
+      <placements>
+        <disk rmin="EcalEndcapP_rmin" rmax = "EcalEndcapP_rmax" sector="1">
+          <wrapper thickness="2*mm" material="Epoxy" vis="WhiteVis"/>
+          <module shape="square" side_length="50*mm" vis="EcalEndcapVis">
+            <layer repeat="EcalEndcapPLayer1_NRepeat" vis="EcalEndcapVis">
+              <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+              <slice material="Copper" thickness="EcalCopperThickness"/>
+              <slice material="Kapton" thickness="EcalKaptonThickness"/>
+              <slice material="Air" thickness="EcalAir1Thickness"/>
+            </layer>
+            <layer repeat="EcalEndcapPLayer2_NRepeat">
+              <slice material="TungstenDens24" thickness="EcalThinTungstenThickness"/>
+              <slice material="Air" thickness="EcalAir2Thickness"/>
+              <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+              <slice material="Copper" thickness="EcalCopperThickness"/>
+              <slice material="Kapton" thickness="EcalKaptonThickness"/>
+              <slice material="Air" thickness="EcalAir1Thickness"/>
+            </layer>
+            <layer repeat="EcalEndcapPLayer3_NRepeat">
+              <slice material="TungstenDens24" thickness="EcalThickTungstenThickness"/>
+              <slice material="Air" thickness="EcalAir2Thickness"/>
+              <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
+              <slice material="Copper" thickness="EcalCopperThickness"/>
+              <slice material="Kapton" thickness="EcalKaptonThickness"/>
+              <slice material="Air" thickness="EcalAir1Thickness"/>
+            </layer>
+          </module>
+        </disk>
+      </placements>
+    </detector>
+  </detectors>
+
+  <!--  Definition of the readout segmentation/definition  -->
+  <readouts>
+    <readout name="EcalEndcapPHits">
+      <segmentation type="NoSegmentation"/>
+      <id>system:8,sector:4,module:24,layer:8,slice:8</id>
+    </readout>
+  </readouts>
+
+  <plugins>
+  </plugins>
+
+
+</lccdd>
+
diff --git a/compact/ecal.xml b/compact/ecal.xml
index 4e19bb16..60704e6c 100644
--- a/compact/ecal.xml
+++ b/compact/ecal.xml
@@ -1,87 +1,15 @@
 <lccdd>
 
-  <define>
-    <constant name="EcalEndcapP_rmax" value="Solenoid_rmax "/>
-  </define>
-
-
-  <limits>
-  </limits>
-
-  <regions>
-  </regions>
-
-  <!-- Common Generic visualization attributes -->
-  <comment>Common Generic visualization attributes</comment>
-  <display>
-  </display>
-
+  <comment>barrel ecal</comment>
   <!-- <include ref="ecal_barrel.xml"/> -->
   <include ref="ecal_barrel_hybrid.xml"/>
-  
+
+  <comment>electron endcap ecal</comment>
   <!--<include ref="ce_ecal.xml"/>-->
   <include ref="ce_ecal_crystal_glass.xml"/>
-  <detectors>
-
-    <comment>
-      ------------------------------------------
-      Forward (Positive Z) Endcap EM Calorimeter
-      ------------------------------------------
-      A layered EM calorimeter with tungsten and silicon (or scintillator) strips
-    </comment>
-    <detector id="ECalEndcapP_ID" 
-      name="EcalEndcapP" 
-      reflect="false" 
-      type="refdet_PolyhedraEndcapCalorimeter2" 
-      readout="EcalEndcapPHits" 
-      vis="EcalEndcapVis" 
-      calorimeterType="EM_ENDCAP" >
-      <position x="0" y="0" z="-0"/>
-      <dimensions 
-        numsides="CaloSides" 
-        zmin="EcalEndcapP_zmin" 
-        rmin="EcalEndcapP_rmin" 
-        rmax="EcalEndcapP_rmax " />
-      <layer repeat="EcalEndcapPLayer1_NRepeat">
-        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
-        <slice material="Copper" thickness="EcalCopperThickness"/>
-        <slice material="Kapton" thickness="EcalKaptonThickness"/>
-        <slice material="Air" thickness="EcalAir1Thickness"/>
-      </layer>
-      <layer repeat="EcalEndcapPLayer2_NRepeat">
-        <slice material="TungstenDens24" thickness="EcalThinTungstenThickness"/>
-        <slice material="Air" thickness="EcalAir2Thickness"/>
-        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
-        <slice material="Copper" thickness="EcalCopperThickness"/>
-        <slice material="Kapton" thickness="EcalKaptonThickness"/>
-        <slice material="Air" thickness="EcalAir1Thickness"/>
-      </layer>
-      <layer repeat="EcalEndcapPLayer3_NRepeat">
-        <slice material="TungstenDens24" thickness="EcalThickTungstenThickness"/>
-        <slice material="Air" thickness="EcalAir2Thickness"/>
-        <slice material="Silicon" thickness="EcalSiliconThickness" sensitive="yes" limits="cal_limits"/>
-        <slice material="Copper" thickness="EcalCopperThickness"/>
-        <slice material="Kapton" thickness="EcalKaptonThickness"/>
-        <slice material="Air" thickness="EcalAir1Thickness"/>
-      </layer>
-    </detector>
-  </detectors>
-
-  <!--  Definition of the readout segmentation/definition  -->
-  <readouts>
-    <!--  
-    <readout name="PlaneTrackerHits">
-      <segmentation type="CartesianGridXY" grid_size_x="20.0*mm" grid_size_y="20.0*mm" />
-      <id>system:5,module:4,x:32:-16,y:-16</id>
-    </readout>
-    -->
-    <readout name="EcalEndcapPHits">
-      <segmentation type="CartesianGridXY" grid_size_x="3.5 * mm" grid_size_y="3.5 * mm"/>
-      <id>system:8,barrel:3,module:4,layer:6,slice:5,x:32:-16,y:-16</id>
-    </readout>
-  </readouts>
 
-  <plugins>
-  </plugins>
+  <comment>hadron endcap ecal</comment>
+  <include ref="ci_ecal.xml"/>
+  <!--<include ref="ci_ecal_shashlik.xml"/>-->
 
 </lccdd>
diff --git a/src/GaseousRICH_geo.cpp b/src/GaseousRICH_geo.cpp
index 38a66b8d..ab26484b 100644
--- a/src/GaseousRICH_geo.cpp
+++ b/src/GaseousRICH_geo.cpp
@@ -192,7 +192,7 @@ void build_sensors(Detector &desc, Volume &env, xml::Component plm, const Positi
         double rotx = dd4hep::getAttrOrDefault<double>(sec, _Unicode(rotx), 0.);
 
         // fill sensors to the piece
-        auto points = ref::utils::fillRectangles({0., 0.}, sx + gap, sy + gap, rmin - gap, rmax + gap, -phiw/2., phiw/2.);
+        auto points = athena::geo::fillRectangles({0., 0.}, sx + gap, sy + gap, rmin - gap, rmax + gap, -phiw/2., phiw/2.);
         int imod = 1;
         for (auto &p : points) {
             // transofrms are in a reversed order
diff --git a/src/GeometryHelpers.cpp b/src/GeometryHelpers.cpp
index c37d30bd..725aa893 100644
--- a/src/GeometryHelpers.cpp
+++ b/src/GeometryHelpers.cpp
@@ -1,12 +1,23 @@
 #include "GeometryHelpers.h"
 
 // some utility functions that can be shared
-namespace ref::utils {
+namespace athena::geo {
 
 typedef ROOT::Math::XYPoint Point;
 
+// check if a 2d point is already in the container
+bool already_placed(const Point &p, const std::vector<Point> &vec, double xs = 1.0, double ys = 1.0, double tol = 1e-6)
+{
+    for (auto &pt : vec) {
+        if ((std::abs(pt.x() - p.x())/xs < tol) && std::abs(pt.y() - p.y())/ys < tol) {
+            return true;
+        }
+    }
+    return false;
+}
+
 // check if a square in a ring
-inline bool in_ring(const Point &pt, double sx, double sy, double rmin, double rmax, double phmin, double phmax)
+inline bool rec_in_ring(const Point &pt, double sx, double sy, double rmin, double rmax, double phmin, double phmax)
 {
     if (pt.r() > rmax || pt.r() < rmin) {
         return false;
@@ -27,33 +38,29 @@ inline bool in_ring(const Point &pt, double sx, double sy, double rmin, double r
     return true;
 }
 
-// check if a square is overlapped with the others
-inline bool overlap(const Point &pt, double sx, double sy, const std::vector<Point> &pts)
-{
-    for (auto &p : pts) {
-        auto pn = p - pt;
-        if ((std::abs(pn.x()) < (1. - 1e-6)*sx) && (std::abs(pn.y()) < (1. - 1e-6)*sy)) {
-            return true;
-        }
-    }
-    return false;
-}
-
 // a helper function to recursively fill square in a ring
-void add_rectangle(Point p, std::vector<Point> &res, double sx, double sy, double rmin, double rmax, double phmin, double phmax)
+void add_rectangle(Point p, std::vector<Point> &res, double sx, double sy,
+                   double rmin, double rmax, double phmin, double phmax, int max_depth = 20, int depth = 0)
 {
-    // outside of the ring or overlapping
-    if (!in_ring(p, sx, sy, rmin, rmax, phmin, phmax) || overlap(p, sx, sy, res)) {
+    // std::cout << depth << "/" << max_depth << std::endl;
+    // exceeds the maximum depth in searching or already placed
+    if ((depth > max_depth) || (already_placed(p, res, sx, sy))) {
         return;
     }
 
-    res.emplace_back(p);
+    bool in_ring = rec_in_ring(p, sx, sy, rmin, rmax, phmin, phmax);
+    if (in_ring) {
+        res.emplace_back(p);
+    }
 
-    // check adjacent squares
-    add_rectangle(Point(p.x() + sx, p.y()), res, sx, sy, rmin, rmax, phmin, phmax);
-    add_rectangle(Point(p.x() - sx, p.y()), res, sx, sy, rmin, rmax, phmin, phmax);
-    add_rectangle(Point(p.x(), p.y() + sy), res, sx, sy, rmin, rmax, phmin, phmax);
-    add_rectangle(Point(p.x(), p.y() - sy), res, sx, sy, rmin, rmax, phmin, phmax);
+    // continue search for a good placement or if no placement found yet
+    if (in_ring || res.empty()) {
+        // check adjacent squares
+        add_rectangle(Point(p.x() + sx, p.y()), res, sx, sy, rmin, rmax, phmin, phmax, max_depth, depth + 1);
+        add_rectangle(Point(p.x() - sx, p.y()), res, sx, sy, rmin, rmax, phmin, phmax, max_depth, depth + 1);
+        add_rectangle(Point(p.x(), p.y() + sy), res, sx, sy, rmin, rmax, phmin, phmax, max_depth, depth + 1);
+        add_rectangle(Point(p.x(), p.y() - sy), res, sx, sy, rmin, rmax, phmin, phmax, max_depth, depth + 1);
+    }
 }
 
 // fill squares
@@ -68,23 +75,76 @@ std::vector<Point> fillRectangles(Point ref, double sx, double sy, double rmin,
     // move to center
     ref = ref - Point(int(ref.x()/sx)*sx, int(ref.y()/sy)*sy);
 
-    auto find_seed = [] (const Point &ref, int n, double sx, double sy, double rmin, double rmax, double phmin, double phmax) {
-        for (int ix = -n; ix < n; ++ix) {
-            for (int iy = -n; iy < n; ++iy) {
-                Point pt(ref.x() + ix*sx, ref.y() + iy*sy);
-                if (in_ring(pt, sx, sy, rmin, rmax, phmin, phmax)) {
-                    return pt;
-                }
-            }
+    std::vector<Point> res;
+    add_rectangle(ref, res, sx, sy, rmin, rmax, phmin, phmax, (int(rmax/sx) + 1)*(int(rmax/sy) + 1)*2);
+    return res;
+}
+
+// check if a regular polygon is inside a ring
+bool poly_in_ring(const Point &p, int nsides, double lside, double rmin, double rmax, double phmin, double phmax)
+{
+    // outer radius is contained
+    if ((p.r() + lside <= rmax) && (p.r() - lside >= rmin)) {
+        return true;
+    }
+
+    // inner radius is not contained
+    double rin = std::cos(M_PI/nsides)*lside;
+    if ((p.r() + rin > rmax) || (p.r() - rin < rmin)) {
+        return false;
+    }
+
+    // in between, check every corner
+    for (int i = 0; i < nsides; ++i) {
+        double phi = (i + 0.5)*2.*M_PI/static_cast<double>(nsides);
+        Point p2(p.x() + 2.*lside*std::sin(phi), p.y() + 2.*lside*std::cos(phi));
+        if ((p2.r() > rmax) || (p2.r() < rmin)) {
+            return false;
         }
-        return ref;
-    };
+    }
+    return true;
+}
+
+// recursively fill square (nside=4) or hexagon (nside=6) in a ring, other polygons won't work
+void add_poly(Point p, std::vector<Point> &res, int nsides, double lside,
+              double rmin, double rmax, double phmin, double phmax, int max_depth = 20, int depth = 0)
+{
+    // std::cout << depth << "/" << max_depth << std::endl;
+    // exceeds the maximum depth in searching or already placed
+    if ((depth > max_depth) || (already_placed(p, res, lside, lside))) {
+        return;
+    }
+
+    bool in_ring = poly_in_ring(p, nsides, lside, rmin, rmax, phmin, phmax);
+    if (in_ring) {
+        res.emplace_back(p);
+    }
+
+    // recursively add neigbors, continue if it was a good placement or no placement found yet
+    if (in_ring || res.empty()) {
+        double d = 2.*std::cos(M_PI/static_cast<double>(nsides))*lside;
+        for (int i = 0; i < nsides; ++i) {
+            double phi = i*2.*M_PI/static_cast<double>(nsides);
+            add_poly(Point(p.x() + 2.*lside*std::sin(phi), p.y() + 2.*lside*std::cos(phi)), res, nsides, lside,
+                     rmin, rmax, phmin, phmax, max_depth, depth + 1);
+        }
+    }
+}
+
+std::vector<Point> fillHexagons(Point ref, double lside, double rmin, double rmax, double phmin, double phmax)
+{
+    // convert (0, 2pi) to (-pi, pi)
+    if (phmax > M_PI) {
+        phmin -= M_PI;
+        phmax -= M_PI;
+    }
+    // start with a seed and find one in the ring
+    // move to center
+    ref = ref - Point(int(ref.x()/lside)*lside, int(ref.y()/lside)*lside);
 
     std::vector<Point> res;
-    ref = find_seed(ref, int(rmax/sx) + 2, sx, sy, rmin, rmax, phmin, phmax);
-    add_rectangle(ref, res, sx, sy, rmin, rmax, phmin, phmax);
+    add_poly(ref, res, 6, lside, rmin, rmax, phmin, phmax, std::pow(int(rmax/lside) + 1, 2)*2);
     return res;
 }
 
-
-} // ref::utils
+} // athena::geo
diff --git a/src/GeometryHelpers.h b/src/GeometryHelpers.h
index 028c474c..5ec55025 100644
--- a/src/GeometryHelpers.h
+++ b/src/GeometryHelpers.h
@@ -3,7 +3,7 @@
 #include "Math/Point2D.h"
 
 // some utility functions that can be shared
-namespace ref::utils {
+namespace athena::geo {
 
 typedef ROOT::Math::XYPoint Point;
 
@@ -17,4 +17,7 @@ inline std::vector<Point> fillSquares(Point ref, double size, double rmin, doubl
     return fillRectangles(ref, size, size, rmin, rmax, phmin, phmax);
 }
 
-} // ref::utils
+std::vector<Point> fillHexagons(Point ref, double lside, double rmin, double rmax,
+                                double phmin = -M_PI, double phmax = M_PI);
+
+} // athena::geo
diff --git a/src/HomogeneousCalorimeter_geo.cpp b/src/HomogeneousCalorimeter_geo.cpp
index a365801d..ec0b3a62 100644
--- a/src/HomogeneousCalorimeter_geo.cpp
+++ b/src/HomogeneousCalorimeter_geo.cpp
@@ -16,10 +16,10 @@
 #include <XML/Helper.h>
 #include <iostream>
 #include <algorithm>
+#include <tuple>
 #include <math.h>
 
 using namespace dd4hep;
-using namespace dd4hep::detail;
 
 /** \addtogroup calorimeters Calorimeters
  */
@@ -170,13 +170,12 @@ static Ref_t create_detector(Detector& desc, xml::Handle_t handle, SensitiveDete
 }
 
 // helper function to build module with or w/o wrapper
-Volume build_module(Detector &desc, xml::Collection_t &plm, SensitiveDetector &sens, Position &dim)
+std::tuple<Volume, Position> build_module(Detector &desc, xml::Collection_t &plm, SensitiveDetector &sens)
 {
     auto mod = plm.child(_Unicode(module));
     auto sx = mod.attr<double>(_Unicode(sizex));
     auto sy = mod.attr<double>(_Unicode(sizey));
     auto sz = mod.attr<double>(_Unicode(sizez));
-    dim = Position{sx, sy, sz};
     Box modShape(sx/2., sy/2., sz/2.);
     auto modMat = desc.material(mod.attr<std::string>(_Unicode(material)));
     Volume modVol("module_vol", modShape, modMat);
@@ -185,29 +184,27 @@ Volume build_module(Detector &desc, xml::Collection_t &plm, SensitiveDetector &s
 
     // no wrapper
     if (!plm.hasChild(_Unicode(wrapper))) {
-        return modVol;
+        return std::make_tuple(modVol, Position{sx, sy, sz});
     // build wrapper
     } else {
         auto wrp = plm.child(_Unicode(wrapper));
         auto thickness = wrp.attr<double>(_Unicode(thickness));
-        if (thickness == 0.) {
-            return modVol;
+        if (thickness < 1e-12*mm) {
+            return std::make_tuple(modVol, Position{sx, sy, sz});
         }
         auto wrpMat = desc.material(wrp.attr<std::string>(_Unicode(material)));
         Box wrpShape((sx + thickness)/2., (sy + thickness)/2., sz/2.);
         Volume wrpVol("wrapper_vol", wrpShape, wrpMat);
         wrpVol.placeVolume(modVol, Position(0., 0., 0.));
         wrpVol.setVisAttributes(desc.visAttributes(wrp.attr<std::string>(_Unicode(vis))));
-        dim = Position{sx + thickness, sy + thickness, sz};
-        return wrpVol;
+        return std::make_tuple(wrpVol, Position{sx + thickness, sy + thickness, sz});
     }
 }
 
 // place modules, id must be provided
 static void add_individuals(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int sid)
 {
-    Position modSize;
-    auto modVol = build_module(desc, plm, sens, modSize);
+    auto [modVol, modSize] = build_module(desc, plm, sens);
     int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
 
     for (xml::Collection_t pl(plm, _Unicode(placement)); pl; ++pl) {
@@ -228,8 +225,7 @@ static void add_individuals(Detector& desc, Assembly &env, xml::Collection_t &pl
 // place array of modules
 static void add_array(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int sid)
 {
-    Position modSize;
-    auto modVol = build_module(desc, plm, sens, modSize);
+    auto [modVol, modSize] = build_module(desc, plm, sens);
     int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
     int id_begin = dd4hep::getAttrOrDefault<int>(plm, _Unicode(id_begin), 1);
     int nrow = plm.attr<int>(_Unicode(nrow));
@@ -266,8 +262,7 @@ static void add_array(Detector& desc, Assembly &env, xml::Collection_t &plm, Sen
 // place disk of modules
 static void add_disk(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int sid)
 {
-    Position modSize;
-    auto modVol = build_module(desc, plm, sens, modSize);
+    auto [modVol, modSize] = build_module(desc, plm, sens);
     int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
     int id_begin = dd4hep::getAttrOrDefault<int>(plm, _Unicode(id_begin), 1);
     double rmin = plm.attr<double>(_Unicode(rmin));
@@ -275,7 +270,7 @@ static void add_disk(Detector& desc, Assembly &env, xml::Collection_t &plm, Sens
     double phimin = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimin), 0.);
     double phimax = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimax), 2.*M_PI);
 
-    auto points = ref::utils::fillRectangles({0., 0.}, modSize.x(), modSize.y(), rmin, rmax, phimin, phimax);
+    auto points = athena::geo::fillRectangles({0., 0.}, modSize.x(), modSize.y(), rmin, rmax, phimin, phimax);
     // placement to mother
     auto pos = get_xml_xyz(plm, _Unicode(position));
     auto rot = get_xml_xyz(plm, _Unicode(rotation));
@@ -291,8 +286,7 @@ static void add_disk(Detector& desc, Assembly &env, xml::Collection_t &plm, Sens
 // place lines of modules (anchor point is the 0th module of this line)
 static void add_lines(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int sid)
 {
-    Position modSize;
-    auto modVol = build_module(desc, plm, sens, modSize);
+    auto [modVol, modSize] = build_module(desc, plm, sens);
     int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
     int id_begin = dd4hep::getAttrOrDefault<int>(plm, _Unicode(id_begin), 1);
     bool mirrorx = dd4hep::getAttrOrDefault<bool>(plm, _Unicode(mirrorx), false);
diff --git a/src/ShashlikCalorimeter.cpp b/src/ShashlikCalorimeter.cpp
new file mode 100644
index 00000000..5ce04be8
--- /dev/null
+++ b/src/ShashlikCalorimeter.cpp
@@ -0,0 +1,189 @@
+//==========================================================================
+//  Implementation for shashlik calorimeter modules
+//  it supports disk placements with (rmin, rmax), and (phimin, phimax)
+//--------------------------------------------------------------------------
+//  Author: Chao Peng (ANL)
+//  Date: 06/22/2021
+//==========================================================================
+
+#include "GeometryHelpers.h"
+#include "DD4hep/DetFactoryHelper.h"
+#include <XML/Layering.h>
+#include <XML/Helper.h>
+#include <iostream>
+#include <algorithm>
+#include <tuple>
+#include <math.h>
+
+using namespace dd4hep;
+
+static void add_disk_shashlik(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int id);
+
+// helper function to get x, y, z if defined in a xml component
+template<class XmlComp>
+Position get_xml_xyz(XmlComp &comp, dd4hep::xml::Strng_t name)
+{
+    Position pos(0., 0., 0.);
+    if (comp.hasChild(name)) {
+        auto child = comp.child(name);
+        pos.SetX(dd4hep::getAttrOrDefault<double>(child, _Unicode(x), 0.));
+        pos.SetY(dd4hep::getAttrOrDefault<double>(child, _Unicode(y), 0.));
+        pos.SetZ(dd4hep::getAttrOrDefault<double>(child, _Unicode(z), 0.));
+    }
+    return pos;
+}
+
+static Ref_t create_detector(Detector& desc, xml::Handle_t handle, SensitiveDetector sens)
+{
+    static const std::string func = "ShashlikCalorimeter";
+    xml::DetElement detElem = handle;
+    std::string detName = detElem.nameStr();
+    int detID = detElem.id();
+    DetElement det(detName, detID);
+    sens.setType("calorimeter");
+    // envelope
+    Assembly assembly(detName);
+
+    // module placement
+    xml::Component plm = detElem.child(_Unicode(placements));
+    int sector = 1;
+    for (xml::Collection_t mod(plm, _Unicode(disk)); mod; ++mod) {
+        add_disk_shashlik(desc, assembly, mod, sens, sector++);
+    }
+
+    // detector position and rotation
+    auto pos = get_xml_xyz(detElem, _Unicode(position));
+    auto rot = get_xml_xyz(detElem, _Unicode(rotation));
+    Volume motherVol = desc.pickMotherVolume(det);
+    Transform3D tr = Translation3D(pos.x(), pos.y(), pos.z()) * RotationZYX(rot.z(), rot.y(), rot.x());
+    PlacedVolume envPV = motherVol.placeVolume(assembly, tr);
+    envPV.addPhysVolID("system", detID);
+    det.setPlacement(envPV);
+    return det;
+}
+
+// helper function to build module with or w/o wrapper
+std::tuple<Volume, int, double, double> build_shashlik(Detector &desc, xml::Collection_t &plm, SensitiveDetector &sens)
+{
+    auto mod = plm.child(_Unicode(module));
+    // a modular volume
+    std::string shape = dd4hep::getAttrOrDefault(mod, _Unicode(shape), "square");
+    std::transform(shape.begin(), shape.end(), shape.begin(), [](char c) { return std::tolower(c); });
+    int nsides = 4;
+    if (shape == "hexagon") {
+        nsides = 6;
+    } else if (shape != "square") {
+        std::cerr << "ShashlikCalorimeter Error: Unsupported shape of module " << shape
+                  << ". Please choose from (square, hexagon). Proceed with square shape." << std::endl;
+    }
+    double slen = mod.attr<double>(_Unicode(side_length));
+    double rmax = slen/2./std::sin(M_PI/nsides);
+    Layering layering(mod);
+    auto len = layering.totalThickness();
+
+    // wrapper info
+    PolyhedraRegular mpoly(nsides, 0., rmax, len);
+    Volume mvol("shashlik_module_vol", mpoly, desc.air());
+    mvol.setVisAttributes(desc.visAttributes(dd4hep::getAttrOrDefault(mod, _Unicode(vis), "GreenVis")));
+
+    double gap = 0.;
+    Volume wvol("shashlik_wrapper_vol");
+    if (plm.hasChild(_Unicode(wrapper))) {
+        auto wrap = plm.child(_Unicode(wrapper));
+        gap = wrap.attr<double>(_Unicode(thickness));
+        if (gap > 1e-6*mm) {
+            wvol.setSolid(PolyhedraRegular(nsides, 0., rmax + gap, len));
+            wvol.setMaterial(desc.material(wrap.attr<std::string>(_Unicode(material))));
+            wvol.setVisAttributes(desc.visAttributes(dd4hep::getAttrOrDefault(wrap, _Unicode(vis), "WhiteVis")));
+            wvol.placeVolume(mvol, Position{0., 0., 0.});
+        }
+    }
+
+    // layer start point
+    double lz  = -len/2.;
+    int lnum = 1;
+    // Loop over the sets of layer elements in the detector.
+    for (xml_coll_t li(mod, _U(layer)); li; ++li) {
+        int repeat = li.attr<int>(_Unicode(repeat));
+        // Loop over number of repeats for this layer.
+        for (int j = 0; j < repeat; j++) {
+            std::string lname = Form("layer%d", lnum);
+            double lthick = layering.layer(lnum - 1)->thickness();  // Layer's thickness.
+            PolyhedraRegular lpoly(nsides, 0., rmax, lthick);
+            Volume lvol(lname, lpoly, desc.air());
+
+            // Loop over the sublayers or slices for this layer.
+            int snum = 1;
+            double sz = -lthick/2.;
+            for (xml_coll_t si(li, _U(slice)); si; ++si) {
+                std::string sname = Form("slice%d", snum);
+                double sthick = si.attr<double>(_Unicode(thickness));
+                PolyhedraRegular spoly(nsides, 0., rmax, sthick);
+                Volume svol(sname, spoly, desc.material(si.attr<std::string>(_Unicode(material))));
+
+                std::string issens = dd4hep::getAttrOrDefault(si, _Unicode(sensitive), "no");
+                std::transform(issens.begin(), issens.end(), issens.begin(), [](char c) { return std::tolower(c); });
+                if ((issens == "yes") || (issens == "y") || (issens == "true")) {
+                    svol.setSensitiveDetector(sens);
+                }
+                svol.setAttributes(desc,
+                        dd4hep::getAttrOrDefault(si, _Unicode(region), ""),
+                        dd4hep::getAttrOrDefault(si, _Unicode(limits), ""),
+                        dd4hep::getAttrOrDefault(si, _Unicode(vis), "InvisibleNoDaughters"));
+
+                // Slice placement.
+                auto slicePV = lvol.placeVolume(svol, Position(0, 0, sz + sthick/2.));
+                slicePV.addPhysVolID("slice", snum++);
+                // Increment Z position of slice.
+                sz += sthick;
+            }
+
+            // Set region, limitset, and vis of layer.
+            lvol.setAttributes(desc,
+                    dd4hep::getAttrOrDefault(li, _Unicode(region), ""),
+                    dd4hep::getAttrOrDefault(li, _Unicode(limits), ""),
+                    dd4hep::getAttrOrDefault(li, _Unicode(vis), "InvisibleNoDaughters"));
+
+            auto layerPV = mvol.placeVolume(lvol, Position(0, 0, lz + lthick/2));
+            layerPV.addPhysVolID("layer", lnum++);
+            // Increment to next layer Z position.
+            lz += lthick;
+        }
+    }
+
+    if (gap > 1e-6*mm) {
+        return std::make_tuple(wvol, nsides, 2.*std::sin(M_PI/nsides)*(rmax + gap), len);
+    } else {
+        return std::make_tuple(mvol, nsides, slen, len);
+    }
+}
+
+// place disk of modules
+static void add_disk_shashlik(Detector& desc, Assembly &env, xml::Collection_t &plm, SensitiveDetector &sens, int sid)
+{
+    auto [mvol, nsides, sidelen, len]  = build_shashlik(desc, plm, sens);
+    int sector_id = dd4hep::getAttrOrDefault<int>(plm, _Unicode(sector), sid);
+    int id_begin = dd4hep::getAttrOrDefault<int>(plm, _Unicode(id_begin), 1);
+    double rmin = plm.attr<double>(_Unicode(rmin));
+    double rmax = plm.attr<double>(_Unicode(rmax));
+    double phimin = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimin), 0.);
+    double phimax = dd4hep::getAttrOrDefault<double>(plm, _Unicode(phimax), 2.*M_PI);
+
+    auto points = (nsides == 6) ? athena::geo::fillHexagons({0., 0.}, sidelen, rmin, rmax, phimin, phimax)
+                                : athena::geo::fillSquares({0., 0.}, sidelen*1.414, rmin, rmax, phimin, phimax);
+    // placement to mother
+    auto pos = get_xml_xyz(plm, _Unicode(position));
+    auto rot = get_xml_xyz(plm, _Unicode(rotation));
+    int mid = 0;
+    for (auto &p : points) {
+        Transform3D tr = RotationZYX(rot.z(), rot.y(), rot.x())
+                       * Translation3D(pos.x() + p.x(), pos.y() + p.y(), pos.z() + len/2.)
+                       * RotationZ((nsides == 4) ? 45*degree : 0);
+        auto modPV = env.placeVolume(mvol, tr);
+        modPV.addPhysVolID("sector", sector_id).addPhysVolID("module", id_begin + mid++);
+    }
+}
+
+
+DECLARE_DETELEMENT(ShashlikCalorimeter, create_detector)
+
diff --git a/src/ce_MRICH.cpp b/src/ce_MRICH.cpp
index ccc2a2b3..5920ddc3 100644
--- a/src/ce_MRICH.cpp
+++ b/src/ce_MRICH.cpp
@@ -82,7 +82,7 @@ void addModules(Volume &mother, xml::DetElement &detElem, Detector &desc, Sensit
     modVol.setSensitiveDetector(sens);
 
     // place modules in the sectors (disk)
-    auto points = ref::utils::fillSquares({0., 0.}, mWidth + mGap, rmin - mGap, rmax + mGap);
+    auto points = athena::geo::fillSquares({0., 0.}, mWidth + mGap, rmin - mGap, rmax + mGap);
 
     // determine module direction, always facing z = 0
     double roty = dims.z() > 0. ? M_PI/2. : -M_PI/2.;
-- 
GitLab