Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-17 08:35:34

0001 #include <VecGeom/surfaces/BrepHelper.h>
0002 #include <VecGeom/surfaces/base/CpuTypes.h>
0003 #include <VecGeom/management/Logger.h>
0004 
0005 namespace vgbrep {
0006 
0007 template <typename Real_i>
0008 struct SideIteratorSlices {
0009   using VecInt_t = std::vector<int>;
0010   std::vector<const VecInt_t *> fSlices; ///< Division slices to be iterated
0011   int fNslices{0};                       ///< Number of slices to iterate
0012   int fNsurf{0};                         ///< Number of surfaces on the side
0013   int fCrtSlice{0};                      ///< Current slice
0014   int fCrt{0};                           ///< Current index in slice
0015   bool fDone{true};                      ///< Iteration is done
0016 
0017   SideIteratorSlices(const Side &side, Vector3D<Real_i> const &amin, Vector3D<Real_i> const &amax,
0018                      CPUsurfData<vecgeom::Precision> const &cpudata)
0019   {
0020     if (side.fDivision < 0) {
0021       // If no division on the side, initiate loop mode
0022       fNsurf = side.fNsurf;
0023       fDone  = false;
0024       return;
0025     }
0026     auto const &div = cpudata.fSideDivisions[side.fDivision];
0027     int u1, u2, v1, v2;
0028     auto nslices = div.GetNslices(amin, amax, u1, u2, v1, v2);
0029     if (nslices == 0) return;
0030     fDone = false;
0031     for (auto indU = u1; indU <= u2; ++indU) {
0032       for (auto indV = v1; indV <= v2; ++indV) {
0033         auto slice = div.GetSlice(indU, indV);
0034         if (slice->size()) fSlices.push_back(slice);
0035       }
0036     }
0037     fNslices = fSlices.size();
0038     if (fNslices == 0) fDone = true;
0039   }
0040 
0041   VECCORE_ATT_HOST_DEVICE
0042   VECGEOM_FORCE_INLINE
0043   bool Done() const { return fDone; }
0044 
0045   VECCORE_ATT_HOST_DEVICE VECGEOM_FORCE_INLINE int operator()()
0046   {
0047     if (fNsurf > 0) return fCrt; // loop mode
0048     return (*fSlices[fCrtSlice])[fCrt];
0049   }
0050 
0051   VECCORE_ATT_HOST_DEVICE
0052   VECGEOM_FORCE_INLINE
0053   SideIteratorSlices &operator++()
0054   {
0055     if (fNsurf > 0) {
0056       // loop mode
0057       if (++fCrt > (fNsurf - 1)) fDone = true;
0058       return *this;
0059     }
0060     auto const &slice = *fSlices[fCrtSlice];
0061     fCrt              = (fCrt + 1) % slice.size();
0062     if (fCrt == 0) fCrtSlice++;
0063     if (fCrtSlice == fNslices) fDone = true;
0064     return *this;
0065   }
0066 };
0067 
0068 template <typename Real_t>
0069 BrepHelper<Real_t>::BrepHelper()
0070     : fSurfData(&SurfData<Real_t>::Instance()), fCPUdata(CPUsurfData<vecgeom::Precision>::Instance())
0071 {
0072   static_assert(std::is_same<CPUsurfData_t, CPUsurfData<vecgeom::Precision>>::value,
0073                 "CPUsurfData_t must be CPUsurfData<vecgeom::Precision>");
0074   static_assert(std::is_same<Real_t, double>::value || std::is_same<Real_t, float>::value,
0075                 "Real_t must be either double or float");
0076 }
0077 
0078 template <typename Real_t>
0079 BrepHelper<Real_t> &BrepHelper<Real_t>::Instance()
0080 {
0081   static BrepHelper<Real_t> instance;
0082   return instance;
0083 }
0084 
0085 template <typename Real_t>
0086 BrepHelper<Real_t>::~BrepHelper()
0087 {
0088   if (fSurfData) ClearData();
0089 }
0090 
0091 template <typename Real_t>
0092 void BrepHelper<Real_t>::ClearData()
0093 {
0094   // Dispose of surface data and shrink the container
0095   fCPUdata.Clear();
0096   // delete fSurfData;
0097   fSurfData->Clear();
0098   fSurfData = nullptr;
0099 }
0100 
0101 template <typename Real_t>
0102 void BrepHelper<Real_t>::SortSides(int common_id)
0103 {
0104   // lambda to detect surfaces on a side that have identical frame
0105   auto sortFrames = [&](Side &side) {
0106     if (!side.fNsurf) return;
0107     std::sort(side.fSurfaces, side.fSurfaces + side.fNsurf,
0108               [&](int i, int j) { return fCPUdata.fFramedSurf[i] < fCPUdata.fFramedSurf[j]; });
0109   };
0110 
0111   // lambda to find all parent frames on one side
0112   auto findParentFramedSurf = [&](Side &side) {
0113     if (!side.fNsurf) return;
0114     int top_parent  = side.fNsurf - 1;
0115     int num_parents = 0;
0116     // if there is a parent, it can only be at the last position after sorting
0117     for (auto parent_ind = top_parent; parent_ind >= 0; --parent_ind) {
0118       auto &parent_frame = fCPUdata.fFramedSurf[side.fSurfaces[parent_ind]];
0119       if (parent_frame.fParent < 0) num_parents++;
0120       // Non-embedding frames may be several on the side
0121       auto parent_navind = parent_frame.fState;
0122       // re-parent frames before if they have descendent states
0123       for (int i = 0; i < parent_ind; ++i) {
0124         auto &child_frame = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0125         auto navind       = child_frame.fState;
0126         if (vecgeom::NavigationState::IsDescendentImpl(navind, parent_navind)) {
0127           // Check if the frame is embedded in ANY of the parents
0128           if (!child_frame.fEmbedded) {
0129             auto intersect_type = fCPUdata.CheckFrames(parent_frame, child_frame);
0130             child_frame.fEmbedded =
0131                 (intersect_type == FrameIntersect::kEmbedding) || (intersect_type == FrameIntersect::kEqual);
0132           }
0133           child_frame.fParent = parent_ind;
0134         }
0135       }
0136     }
0137 
0138     // rerun loop to check if non-embedded children have embedding parents
0139     for (auto parent_ind = top_parent; parent_ind >= 0; --parent_ind) {
0140       auto &parent_frame = fCPUdata.fFramedSurf[side.fSurfaces[parent_ind]];
0141       // Non-embedding frames may be several on the side
0142       auto parent_navind = parent_frame.fState;
0143       for (int i = 0; i < parent_ind; ++i) {
0144         auto &child_frame = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0145         auto navind       = child_frame.fState;
0146         if (vecgeom::NavigationState::IsDescendentImpl(navind, parent_navind)) {
0147           // Check if the frame is embedded in ANY of the parents
0148           if (!child_frame.fEmbedded) {
0149 
0150             // if there are multiple parents on the same surface (e.g., on a polycone) we should not print the warning
0151             // of non-embedded surfaces having embedded parents since the parents are in fact embedding, but we would
0152             // need to check the daughters against the union of their parents. since parents are next to each other, we
0153             // just check if the previous or next surface to the parent has the same state as the checked surface to see
0154             // if there are multiple parents Note that this might suppress real overlaps for all surfaces with multiple
0155             // parents
0156             bool multi_parent = false;
0157             if (parent_ind != top_parent)
0158               multi_parent = fCPUdata.fFramedSurf[side.fSurfaces[parent_ind + 1]].fState == parent_navind;
0159             if (parent_ind != 0)
0160               multi_parent |= fCPUdata.fFramedSurf[side.fSurfaces[parent_ind - 1]].fState == parent_navind;
0161             if (multi_parent) continue;
0162             if (parent_frame.fEmbedding && child_frame.fLogicId == 0) {
0163               VECGEOM_LOG(warning) << "Non-embedded frame " << i << " having embedding parent " << parent_ind
0164                                    << " on CS " << common_id;
0165               if (fVerbose > 0) {
0166                 PrintFramedSurface(child_frame);
0167                 PrintFramedSurface(parent_frame);
0168                 // Debugging only:
0169                 fCPUdata.CheckFrames(parent_frame, child_frame);
0170               }
0171             }
0172           }
0173         }
0174       }
0175     }
0176     side.fNumParents = num_parents;
0177     VECGEOM_ASSERT(num_parents > 0);
0178   };
0179 
0180   sortFrames(fCPUdata.fCommonSurfaces[common_id].fLeftSide);
0181   sortFrames(fCPUdata.fCommonSurfaces[common_id].fRightSide);
0182   findParentFramedSurf(fCPUdata.fCommonSurfaces[common_id].fLeftSide);
0183   findParentFramedSurf(fCPUdata.fCommonSurfaces[common_id].fRightSide);
0184 }
0185 
0186 template <typename Real_t>
0187 void BrepHelper<Real_t>::ComputeDefaultStates(int common_id)
0188 {
0189   using vecgeom::NavigationState;
0190   // Computes the default states for each side of a common surface
0191   Side &left  = fCPUdata.fCommonSurfaces[common_id].fLeftSide;
0192   Side &right = fCPUdata.fCommonSurfaces[common_id].fRightSide;
0193   VECGEOM_ASSERT(left.fNsurf > 0 || right.fNsurf > 0);
0194 
0195   NavIndex_t default_ind = 0;
0196 
0197   // A lambda that finds the deepest common ancestor between 2 states
0198   auto getCommonState = [&](NavIndex_t const &s1, NavIndex_t const &s2) {
0199     NavIndex_t a1(s1), a2(s2);
0200     // Bring both states at the same level
0201     while (NavigationState::GetLevelImpl(a1) > NavigationState::GetLevelImpl(a2))
0202       NavigationState::PopImpl(a1);
0203     while (NavigationState::GetLevelImpl(a2) > NavigationState::GetLevelImpl(a1))
0204       NavigationState::PopImpl(a2);
0205 
0206     // Pop until we reach the same state
0207     while (a1 != a2) {
0208       NavigationState::PopImpl(a1);
0209       NavigationState::PopImpl(a2);
0210     }
0211     return a1;
0212   };
0213 
0214   int minlevel = 10000; // this is a big-enough number as level
0215   for (int isurf = 0; isurf < left.fNsurf; ++isurf) {
0216     auto navind = fCPUdata.fFramedSurf[left.fSurfaces[isurf]].fState;
0217     minlevel    = std::min(minlevel, (int)NavigationState::GetLevelImpl(navind));
0218   }
0219   for (int isurf = 0; isurf < right.fNsurf; ++isurf) {
0220     auto navind = fCPUdata.fFramedSurf[right.fSurfaces[isurf]].fState;
0221     minlevel    = std::min(minlevel, (int)NavigationState::GetLevelImpl(navind));
0222   }
0223 
0224   // initialize the default state
0225   if (left.fNsurf > 0)
0226     default_ind = fCPUdata.fFramedSurf[left.fSurfaces[0]].fState;
0227   else if (right.fNsurf > 0)
0228     default_ind = fCPUdata.fFramedSurf[right.fSurfaces[0]].fState;
0229 
0230   for (int isurf = 0; isurf < left.fNsurf; ++isurf)
0231     default_ind = getCommonState(default_ind, fCPUdata.fFramedSurf[left.fSurfaces[isurf]].fState);
0232   for (int isurf = 0; isurf < right.fNsurf; ++isurf)
0233     default_ind = getCommonState(default_ind, fCPUdata.fFramedSurf[right.fSurfaces[isurf]].fState);
0234 
0235   if (NavigationState::GetLevelImpl(default_ind) == minlevel) NavigationState::PopImpl(default_ind);
0236   fCPUdata.fCommonSurfaces[common_id].fDefaultState = default_ind;
0237 }
0238 
0239 template <typename Real_t>
0240 WindowMask<double> BrepHelper<Real_t>::GetPlanarFrameExtent(
0241     FramedSurface<Real_t, TransformationMP<Real_t>> const &framed_surf, TransformationMP<Real_t> const &trans)
0242 {
0243   // This is a helper-lambda that updates the extent based on corner coordinates
0244   auto updateExtent = [](WindowMask<double> &e, Vector3D<double> const &pt) {
0245     e.rangeU[0] = std::min(e.rangeU[0], pt[0]);
0246     e.rangeU[1] = std::max(e.rangeU[1], pt[0]);
0247     e.rangeV[0] = std::min(e.rangeV[0], pt[1]);
0248     e.rangeV[1] = std::max(e.rangeV[1], pt[1]);
0249   };
0250   // Setting initial mask for an extent.
0251   WindowMask<double> ext{vecgeom::kInfLength, -vecgeom::kInfLength, vecgeom::kInfLength, -vecgeom::kInfLength};
0252   FrameType frame_type = framed_surf.fFrame.type;
0253   Vector3D<double> local;
0254 
0255   WindowMask<double> extentL;
0256   // Calculating the limits
0257   switch (frame_type) {
0258   case FrameType::kWindow: {
0259     auto const &maskLocal = fCPUdata.fWindowMasks[framed_surf.fFrame.id];
0260     maskLocal.GetExtent(extentL);
0261     break;
0262   }
0263   case FrameType::kRing: {
0264     auto const &maskLocal = fCPUdata.fRingMasks[framed_surf.fFrame.id];
0265     maskLocal.GetExtent(extentL);
0266     break;
0267   }
0268   case FrameType::kQuadrilateral: {
0269     WindowMask_t extLocal;
0270     auto const &quad = fCPUdata.fQuadMasks[framed_surf.fFrame.id];
0271     quad.GetExtent(extentL);
0272     break;
0273   }
0274   case FrameType::kTriangle: {
0275     TriangleMask_t extLocal;
0276     auto const &maskLocal = fCPUdata.fTriangleMasks[framed_surf.fFrame.id];
0277     maskLocal.GetExtent(extentL);
0278     break;
0279   }
0280   default:
0281     VECGEOM_VALIDATE(0, << "Not implemented");
0282   }
0283 
0284   // This part updates extent
0285   local = trans.InverseTransform(Vector3D<double>{extentL.rangeU[0], extentL.rangeV[0], 0});
0286   updateExtent(ext, local);
0287   local = trans.InverseTransform(Vector3D<double>{extentL.rangeU[0], extentL.rangeV[1], 0});
0288   updateExtent(ext, local);
0289   local = trans.InverseTransform(Vector3D<double>{extentL.rangeU[1], extentL.rangeV[1], 0});
0290   updateExtent(ext, local);
0291   local = trans.InverseTransform(Vector3D<double>{extentL.rangeU[1], extentL.rangeV[0], 0});
0292   updateExtent(ext, local);
0293   return ext;
0294 }
0295 
0296 template <typename Real_t>
0297 WindowMask<double> BrepHelper<Real_t>::ComputePlaneExtent(const Side &side)
0298 {
0299   // This is a helper-lambda that updates extents
0300   // for all sides of common plane surfaces
0301   auto updatePlaneExtent = [](WindowMask<double> &e, WindowMask<double> const &elocal) {
0302     e.rangeU[0] = std::min(e.rangeU[0], elocal.rangeU[0]);
0303     e.rangeU[1] = std::max(e.rangeU[1], elocal.rangeU[1]);
0304     e.rangeV[0] = std::min(e.rangeV[0], elocal.rangeV[0]);
0305     e.rangeV[1] = std::max(e.rangeV[1], elocal.rangeV[1]);
0306   };
0307 
0308   // Setting initial mask for an extent.
0309   WindowMask<double> ext{vecgeom::kInfLength, -vecgeom::kInfLength, vecgeom::kInfLength, -vecgeom::kInfLength};
0310 
0311   // loop through all extents on a side:
0312   for (int i = 0; i < side.fNsurf; ++i) {
0313     // Compute extent for the frame
0314     auto &framed_surf = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0315     auto extentL      = GetPlanarFrameExtent(framed_surf, framed_surf.fTrans);
0316     // This part updates extent
0317     updatePlaneExtent(ext, extentL);
0318   }
0319 
0320   return ext;
0321 }
0322 
0323 template <typename Real_t>
0324 ZPhiMask<double> BrepHelper<Real_t>::ComputeCylinderExtent(const Side &side)
0325 {
0326   auto const &sideext = fCPUdata.GetZPhiMask(fCPUdata.fFramedSurf[side.fSurfaces[0]].fFrame.id);
0327   auto sideext_local  = sideext.InverseTransform(fCPUdata.fFramedSurf[side.fSurfaces[0]].fTrans);
0328 
0329   // loop over remaining frames on the side
0330   for (int i = 1; i < side.fNsurf; ++i) {
0331     // convert extent of current frame to local coordinates
0332     auto &framed_surf = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0333     // Transform the ZPhi mask to the local system
0334     auto const &extLocal = fCPUdata.GetZPhiMask(framed_surf.fFrame.id);
0335     auto extFrame        = extLocal.InverseTransform(framed_surf.fTrans);
0336     // Combine with current extent
0337     bool success = sideext_local.CombineWith(extFrame);
0338     if (!success) {
0339       VECGEOM_LOG(critical) << "ComputeCylinderExtent error";
0340     }
0341   }
0342 
0343   return sideext_local;
0344 }
0345 
0346 template <typename Real_t>
0347 int BrepHelper<Real_t>::ComputeCylinderDivision(Side &side, ZPhiMask<double> extent_full)
0348 {
0349   // return -1;
0350   SideDivisionCPU divisionZ(AxisType::kZ, extent_full.rangeZ[0], extent_full.rangeZ[1], side.fNsurf);
0351   auto updateRangeZ = [](double z, double &zmin, double &zmax) {
0352     zmin = std::min(zmin, z);
0353     zmax = std::max(zmax, z);
0354   };
0355   // loop through all extents on a side:
0356   for (int i = 0; i < side.fNsurf; ++i) {
0357     auto &framed_surf = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0358     VECGEOM_ASSERT(framed_surf.fFrame.type == FrameType::kZPhi);
0359 
0360     Vector3D<double> local;
0361     double zmin{vecgeom::InfinityLength<Real_t>()}, zmax{-vecgeom::InfinityLength<Real_t>()};
0362     auto const &maskLocal = fCPUdata.fZPhiMasks[framed_surf.fFrame.id];
0363     local                 = framed_surf.fTrans.InverseTransform(Vector3D<double>{0, 0, maskLocal.rangeZ[0]});
0364     updateRangeZ(local[2], zmin, zmax);
0365     local = framed_surf.fTrans.InverseTransform(Vector3D<double>{0, 0, maskLocal.rangeZ[1]});
0366     updateRangeZ(local[2], zmin, zmax);
0367     divisionZ.AddCandidate(i, zmin, zmax);
0368   }
0369   if (divisionZ.Efficiency() > 0.01) {
0370     // divisionZ.Print();
0371     side.fDivision = fCPUdata.fSideDivisions.size();
0372     fCPUdata.fSideDivisions.push_back(divisionZ);
0373   }
0374   return side.fDivision;
0375 }
0376 
0377 template <typename Real_t>
0378 void BrepHelper<Real_t>::CountTraversals(const CommonSurface<Real_t> &surf, int &nfound, int &ntotal) const
0379 {
0380   auto count_per_side = [&](Side const &side) {
0381     ntotal += side.fNsurf;
0382     for (int i = 0; i < side.fNsurf; ++i) {
0383       auto const &frame = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0384       if (frame.fTraversal > -2) nfound++;
0385     }
0386   };
0387   count_per_side(surf.fLeftSide);
0388   count_per_side(surf.fRightSide);
0389 }
0390 
0391 template <typename Real_t>
0392 void BrepHelper<Real_t>::ComputeTraversalForFrame(int common_id, bool left, int iframe)
0393 {
0394   using Vector3D         = Vector3D<vecgeom::Precision>;
0395   auto &surf             = fCPUdata.fCommonSurfaces[common_id];
0396   Side const &side       = left ? surf.fLeftSide : surf.fRightSide;
0397   auto &thisside_frame   = fCPUdata.fFramedSurf[side.fSurfaces[iframe]];
0398   Side const &other_side = left ? surf.fRightSide : surf.fLeftSide;
0399   auto setTraversal      = [](FramedSurface<double, TransformationMP<double>> &frame, int iframe) {
0400     // if not set just set it
0401     if (frame.fTraversal == -2) {
0402       frame.fTraversal = iframe;
0403     } else {
0404       // otherwise set it to the minimum frame index (deepest history)
0405       if (iframe < frame.fTraversal) frame.fTraversal = iframe;
0406     }
0407   };
0408 
0409   // Compute extent of the frame in its local reference frame
0410 
0411   // Loop frames on the other_side and find the candidates
0412   bool has_div = other_side.fDivision >= 0;
0413   Vector3D amin, amax;
0414   if (has_div) {
0415     if (surf.fType == SurfaceType::kPlanar) {
0416       if (fCPUdata.fSideDivisions[other_side.fDivision].fAxis == AxisType::kR) {
0417         auto const &ringMask = fCPUdata.fRingMasks[thisside_frame.fFrame.id];
0418         amin[0]              = ringMask.rangeR[0];
0419         amax[0]              = ringMask.rangeR[1];
0420       } else {
0421         // Calculate the aligned bounding box of the frame
0422         auto extentL = GetPlanarFrameExtent(thisside_frame, thisside_frame.fTrans);
0423         amin.Set(extentL.rangeU[0], extentL.rangeV[0], 0.);
0424         amax.Set(extentL.rangeU[1], extentL.rangeV[1], 0.);
0425       }
0426     } else if (surf.fType == SurfaceType::kCylindrical || surf.fType == SurfaceType::kConical) {
0427       Vector3D local;
0428       amin.Set(0., 0., vecgeom::InfinityLength<Real_t>());
0429       amax.Set(0., 0., -vecgeom::InfinityLength<Real_t>());
0430       auto const &maskLocal = fCPUdata.fZPhiMasks[thisside_frame.fFrame.id];
0431       local                 = thisside_frame.fTrans.InverseTransform(Vector3D{0, 0, maskLocal.rangeZ[0]});
0432       amin[2]               = std::min(amin[2], local[2]);
0433       amax[2]               = std::max(amax[2], local[2]);
0434       local                 = thisside_frame.fTrans.InverseTransform(Vector3D{0, 0, maskLocal.rangeZ[1]});
0435       amin[2]               = std::min(amin[2], local[2]);
0436       amax[2]               = std::max(amax[2], local[2]);
0437     }
0438   }
0439 
0440   bool disjoint = true;
0441   std::set<int> candidates;
0442   // check with the frames on the other side
0443   for (SideIteratorSlices it(other_side, amin, amax, fCPUdata); !it.Done(); ++it) {
0444     auto j = it();
0445     if (!candidates.insert(j).second) continue;
0446     auto &otherside_frame = fCPUdata.fFramedSurf[other_side.fSurfaces[j]];
0447 
0448     // frame on the other side already marked "disjoint", don't check
0449     if (otherside_frame.fTraversal == -1) continue;
0450 
0451     // check intersection type
0452     auto intersect_type = fCPUdata.CheckFrames(thisside_frame, otherside_frame);
0453     if (intersect_type == FrameIntersect::kNoIntersect) continue;
0454 
0455     // Other than kNoIntersect is not disjoint
0456     disjoint = false;
0457     if (intersect_type == FrameIntersect::kIntersect) {
0458       // If frames intersect, we cannot have direct traversals on either of them
0459       thisside_frame.fTraversal  = -3;
0460       otherside_frame.fTraversal = -3;
0461       break;
0462     }
0463     if (intersect_type == FrameIntersect::kEmbedding) {
0464       // crossing from otherside_frame always to thisside_frame
0465       if (thisside_frame.fLogicId)
0466         otherside_frame.fTraversal = -3;
0467       else
0468         setTraversal(otherside_frame, iframe);
0469       // thisside_frame is overlapping
0470       thisside_frame.fTraversal = -3;
0471       break;
0472     }
0473     if (intersect_type == FrameIntersect::kEqual) {
0474       // frames transition from one to another
0475       if (otherside_frame.fLogicId)
0476         thisside_frame.fTraversal = -3;
0477       else
0478         setTraversal(thisside_frame, j);
0479 
0480       if (thisside_frame.fLogicId)
0481         otherside_frame.fTraversal = -3;
0482       else
0483         setTraversal(otherside_frame, iframe);
0484     }
0485     if (intersect_type == FrameIntersect::kEmbedded) {
0486       // crossing from thisside_frame always to otherside_frame
0487       if (otherside_frame.fLogicId)
0488         thisside_frame.fTraversal = -3;
0489       else
0490         setTraversal(thisside_frame, j);
0491       otherside_frame.fTraversal = -3;
0492     }
0493   }
0494   if (disjoint) thisside_frame.fTraversal = -1;
0495 }
0496 
0497 template <typename Real_t>
0498 void BrepHelper<Real_t>::ComputeTraversalFrames(int common_id, bool left)
0499 {
0500   auto &surf             = fCPUdata.fCommonSurfaces[common_id];
0501   Side const &side       = left ? surf.fLeftSide : surf.fRightSide;
0502   Side const &other_side = left ? surf.fRightSide : surf.fLeftSide;
0503   if (other_side.fNsurf == 0) {
0504     // nothing on the other side, mark all transitions as non-entering
0505     for (int i = 0; i < side.fNsurf; ++i)
0506       fCPUdata.fFramedSurf[side.fSurfaces[i]].fTraversal = -1;
0507     return;
0508   }
0509 
0510   // Loop frames on the side and find the candidates on the other side
0511   for (int i = 0; i < side.fNsurf; ++i) {
0512     auto &thisside_frame = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0513     // skip logical frames, and checked frames which are already marked disjoint or overlapping
0514     if (thisside_frame.fTraversal == -3 || thisside_frame.fTraversal == -1) continue;
0515     // check with the frames on the other side
0516     ComputeTraversalForFrame(common_id, left, i);
0517   }
0518 }
0519 
0520 template <typename Real_t>
0521 int BrepHelper<Real_t>::ComputePlaneDivision(Side &side, WindowMask<double> extent_full)
0522 {
0523   // Special case if all frames are rings placed with id transformation
0524   bool all_rings_id = true; // all frames are rings with id transformation
0525   double ring_max   = -vecgeom::kInfLength;
0526   double ring_min   = vecgeom::kInfLength;
0527   for (int i = 0; i < side.fNsurf; ++i) {
0528     auto &framed_surf    = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0529     FrameType frame_type = framed_surf.fFrame.type;
0530     if (frame_type == FrameType::kRing && !framed_surf.fTrans.HasTranslation()) {
0531       auto const &maskRing = fCPUdata.fRingMasks[framed_surf.fFrame.id];
0532       ring_min             = std::min(maskRing.rangeR[0], ring_min);
0533       ring_max             = std::max(maskRing.rangeR[1], ring_max);
0534     } else {
0535       all_rings_id = false;
0536       break;
0537     }
0538   }
0539 
0540   // Create all supported division types. We could increase the number of slices potentially.
0541   SideDivisionCPU divisionX(AxisType::kX, extent_full.rangeU[0], extent_full.rangeU[1], side.fNsurf);
0542   SideDivisionCPU divisionY(AxisType::kY, extent_full.rangeV[0], extent_full.rangeV[1], side.fNsurf);
0543   SideDivisionCPU divisionR(AxisType::kR, ring_min, ring_max, side.fNsurf);
0544   SideDivisionCPU divisionXY(AxisType::kXY, extent_full.rangeU[0], extent_full.rangeU[1], extent_full.rangeV[0],
0545                              extent_full.rangeV[1], side.fNsurf);
0546   // loop through all extents on a side:
0547   for (int i = 0; i < side.fNsurf; ++i) {
0548     auto &framed_surf = fCPUdata.fFramedSurf[side.fSurfaces[i]];
0549     if (all_rings_id) {
0550       auto const &extentRing = fCPUdata.fRingMasks[framed_surf.fFrame.id];
0551       divisionR.AddCandidate(i, extentRing.rangeR[0], extentRing.rangeR[1]);
0552     }
0553     auto ext = GetPlanarFrameExtent(framed_surf, framed_surf.fTrans);
0554     divisionX.AddCandidate(i, ext.rangeU[0], ext.rangeU[1]);
0555     divisionY.AddCandidate(i, ext.rangeV[0], ext.rangeV[1]);
0556     if (side.fNsurf > 3) divisionXY.AddCandidate(i, ext.rangeU[0], ext.rangeU[1], ext.rangeV[0], ext.rangeV[1]);
0557   }
0558 
0559   auto bestEfficiency      = divisionX.Efficiency();
0560   SideDivisionCPU *bestDiv = &divisionX;
0561   auto efficiency          = divisionY.Efficiency();
0562   if (efficiency > bestEfficiency) {
0563     bestDiv        = &divisionY;
0564     bestEfficiency = efficiency;
0565   }
0566   efficiency = divisionXY.Efficiency();
0567   if (side.fNsurf >= 4 && efficiency > bestEfficiency) {
0568     bestDiv        = &divisionXY;
0569     bestEfficiency = efficiency;
0570   }
0571   if (all_rings_id) {
0572     efficiency = divisionR.Efficiency();
0573     if (efficiency > bestEfficiency) {
0574       bestDiv        = &divisionR;
0575       bestEfficiency = efficiency;
0576     }
0577   }
0578   if (bestEfficiency > 0.01) {
0579     // bestDiv->Print();
0580     side.fDivision = fCPUdata.fSideDivisions.size();
0581     fCPUdata.fSideDivisions.push_back(*bestDiv);
0582   }
0583   return side.fDivision;
0584 }
0585 
0586 template <typename Real_t>
0587 void BrepHelper<Real_t>::ComputeSideDivisions()
0588 {
0589   // Lambda for computing the division helper of a single side
0590   auto computeSingleSideDivision = [&](SurfaceType type, Side &side) {
0591     switch (type) {
0592     case SurfaceType::kPlanar: {
0593       auto extent_plane = ComputePlaneExtent(side);
0594       return ComputePlaneDivision(side, extent_plane);
0595     }
0596     case SurfaceType::kCylindrical:
0597     case SurfaceType::kConical: {
0598       auto extent_cyl = ComputeCylinderExtent(side);
0599       return ComputeCylinderDivision(side, extent_cyl);
0600     }
0601     default:
0602       return -1;
0603     }
0604   };
0605 
0606   // Compute division helpers for all sides having more than one frame on all surfaces
0607   int nfound{0}, ntotal{0};
0608   int nfoundall{0}, ntotalall{0};
0609   for (size_t common_id = 1; common_id < fCPUdata.fCommonSurfaces.size(); ++common_id) {
0610     auto &surf = fCPUdata.fCommonSurfaces[common_id];
0611     if (surf.fLeftSide.fNsurf > 1) {
0612       computeSingleSideDivision(surf.fType, surf.fLeftSide);
0613     }
0614     if (fCPUdata.fCommonSurfaces[common_id].fRightSide.fNsurf > 1) {
0615       computeSingleSideDivision(surf.fType, surf.fRightSide);
0616     }
0617     // In case the surface is NOT a scene surface, check for unique traversal frames
0618     if (surf.IsSceneSurface()) continue;
0619     ComputeTraversalFrames(common_id, true);
0620     ComputeTraversalFrames(common_id, false);
0621     CountTraversals(surf, nfoundall, ntotalall);
0622     if (surf.fLeftSide.fNsurf > 0 && surf.fRightSide.fNsurf > 0) {
0623       CountTraversals(surf, nfound, ntotal);
0624       //printf("surface %ld: %d/%d traversals\n", common_id, nfound, ntotal);
0625     }
0626   }
0627   VECGEOM_LOG(debug) << "traversals_all: " << nfoundall << " / " << ntotalall << " ["
0628                      << 100. * static_cast<double>(nfoundall) / ntotalall << " %]";
0629 
0630   // Copy division helpers to surface data
0631   fSurfData->fNsideDivisions = fCPUdata.fSideDivisions.size();
0632   size_t size_slices         = 0;
0633   size_t size_slice_cand     = 0;
0634   for (auto const &div : fCPUdata.fSideDivisions) {
0635     size_slices += div.fNslices;
0636     size_slice_cand += div.GetNcandidates();
0637   }
0638   fSurfData->fNslices          = size_slices;
0639   fSurfData->fNsliceCandidates = size_slice_cand;
0640   fSurfData->fSideDivisions    = new SideDivision<Real_t>[fSurfData->fNsideDivisions];
0641   fSurfData->fSlices           = new SliceCand[size_slices];
0642   fSurfData->fSliceCandidates  = new int[size_slice_cand];
0643 
0644   SliceCand *slices     = fSurfData->fSlices;
0645   int *slice_candidates = fSurfData->fSliceCandidates;
0646   for (auto i = 0; i < fSurfData->fNsideDivisions; ++i) {
0647     fCPUdata.fSideDivisions[i].CopyTo(fSurfData->fSideDivisions[i], slices, slice_candidates);
0648     slices += fCPUdata.fSideDivisions[i].fSlices.size();
0649     slice_candidates += fCPUdata.fSideDivisions[i].GetNcandidates();
0650   }
0651 }
0652 
0653 template <typename Real_t>
0654 bool BrepHelper<Real_t>::Convert()
0655 {
0656   bool success = CreateLocalSurfaces();
0657   if (!success) return false;
0658   success = CreateCommonSurfacesScenes();
0659   return success;
0660 }
0661 
0662 template <typename Real_t>
0663 void BrepHelper<Real_t>::ConvertTransformations(int idsurf)
0664 {
0665   auto &surf = fCPUdata.fCommonSurfaces[idsurf];
0666   // Adopt the transformation of the first surface on left for the common surface
0667   surf.fTrans = fCPUdata.fFramedSurf[surf.fLeftSide.fSurfaces[0]].fTrans;
0668   TransformationMP<vecgeom::Precision> identity;
0669 
0670   // Set transformation of first surface on left to identity
0671   fCPUdata.fFramedSurf[surf.fLeftSide.fSurfaces[0]].fTrans = identity;
0672 
0673   // Set flip status of common surface based on first framed surface after sorting
0674   fCPUdata.fCommonSurfaces[idsurf].fFlipped = fCPUdata.fFramedSurf[surf.fLeftSide.fSurfaces[0]].fLogicId < 0 ? 1 : 0;
0675 
0676   TransformationMP<vecgeom::Precision> tsurfinv = surf.fTrans.Inverse();
0677 
0678   // Skip first surface on left side
0679   for (int i = 1; i < surf.fLeftSide.fNsurf; ++i) {
0680     int idglob       = surf.fLeftSide.fSurfaces[i];
0681     auto &framedsurf = fCPUdata.fFramedSurf[idglob];
0682     framedsurf.fTrans *= tsurfinv;
0683     framedsurf.fTrans.SetProperties();
0684   }
0685 
0686   // Convert right-side surfaces
0687   for (int i = 0; i < surf.fRightSide.fNsurf; ++i) {
0688     int idglob       = surf.fRightSide.fSurfaces[i];
0689     auto &framedsurf = fCPUdata.fFramedSurf[idglob];
0690     framedsurf.fTrans *= tsurfinv;
0691     framedsurf.fTrans.SetProperties();
0692   }
0693 }
0694 
0695 template <typename Real_t>
0696 void BrepHelper<Real_t>::CreateCandidateLists()
0697 {
0698   constexpr char kLside = 0x01;
0699   constexpr char kRside = 0x02;
0700   // Lambda adding the surface id as exiting candidate to all states from a side
0701   auto addSurfToSideStates = [&](int isurf, char iside) {
0702     auto const &surf = fCPUdata.fCommonSurfaces[isurf];
0703     Side const &side = (iside == kLside) ? surf.fLeftSide : surf.fRightSide;
0704     auto scene_id    = surf.GetSceneId();
0705     for (int i = 0; i < side.fNsurf; ++i) {
0706       int idglob             = side.fSurfaces[i];
0707       auto const &framedsurf = fCPUdata.fFramedSurf[idglob];
0708       vecgeom::NavigationState state(framedsurf.fState);
0709       auto state_id = state.GetId();
0710       auto surf_ind = framedsurf.fSurfIndex;
0711 
0712       auto &candidatesExiting = fCPUdata.GetCandidatesExiting(scene_id, state_id);
0713       auto &frameIndExiting   = fCPUdata.GetFrameIndExiting(scene_id, state_id);
0714       auto &sidesExiting      = fCPUdata.GetSidesExiting(scene_id, state_id);
0715       // We need to store the surface candidate and the frame index for the slot matching the local surface index
0716       candidatesExiting[surf_ind] = isurf;
0717       frameIndExiting[surf_ind]   = i;
0718       // Exiting frames may exist on both sides
0719       sidesExiting[surf_ind] |= iside;
0720       if (fVerbose > 0) {
0721         auto msg = VECGEOM_LOG(debug);
0722         msg << "  added to exiting of state on scene " << scene_id << ":";
0723         state.PrintTop();
0724         int j = 0;
0725         msg << "candExiting:   ";
0726         for (auto candidate : candidatesExiting) {
0727           msg << "  " << j++ << ": " << candidate;
0728         }
0729       }
0730     }
0731   };
0732 
0733   // Lambda adding the surface id as entering candidate to all parent states from a side
0734   auto addSurfToSideParents = [&](int isurf, char iside) {
0735     auto const &surf = fCPUdata.fCommonSurfaces[isurf];
0736     Side const &side = (iside == kLside) ? surf.fLeftSide : surf.fRightSide;
0737     for (int i = 0; i < side.fNsurf; ++i) {
0738       int idglob       = side.fSurfaces[i];
0739       auto &framedsurf = fCPUdata.fFramedSurf[idglob];
0740       // Skip parent frames, just make sure their parent state matches the default state
0741       NavIndex_t parent_state = 0;
0742       framedsurf.GetParentState(parent_state);
0743       if (framedsurf.fParent < 0) {
0744         VECGEOM_ASSERT(parent_state == surf.fDefaultState);
0745         continue;
0746       }
0747 
0748       // If parent is a boolean, it could be a virtual frame
0749       auto const &parent_framedsurf = fCPUdata.fFramedSurf[side.fSurfaces[framedsurf.fParent]];
0750       if (parent_framedsurf.fLogicId) framedsurf.fVirtualParent = true;
0751 
0752       // If the frame is embedded in the parent frame, skip the frame because it will always be entered through the
0753       // parent exception: if the parent is a virtual surface in a boolean, the embedded frame can only be entered
0754       // directly (not through the parent) and must be added to the entering candidates
0755       if (framedsurf.fEmbedded && !framedsurf.fVirtualParent) continue;
0756       // The frame is not embedded in the parent, so add the surface as candidate to the parent state
0757       // VECGEOM_ASSERT(parent_state != surf.fDefaultState);
0758       vecgeom::NavigationState state(parent_state);
0759       auto state_id            = state.GetId();
0760       auto &candidatesEntering = fCPUdata.GetCandidatesEntering(surf.GetSceneId(), state_id);
0761       auto &frameIndEntering   = fCPUdata.GetFrameIndEntering(surf.GetSceneId(), state_id);
0762       auto &sidesEntering      = fCPUdata.GetSidesEntering(surf.GetSceneId(), state_id);
0763       // the surface may already be a candidate for the parent state
0764       if (candidatesEntering.size() && std::abs(candidatesEntering.back()) == isurf) {
0765         candidatesEntering.back() = -isurf;
0766         sidesEntering.back() |= iside;
0767       } else {
0768         candidatesEntering.push_back(-isurf);
0769         frameIndEntering.push_back(i);
0770         sidesEntering.push_back(iside);
0771       }
0772 
0773       if (fVerbose > 0) {
0774         VECGEOM_LOG(debug) << "  added " << candidatesEntering.back()
0775                            << " to entering of non-embedding parent state on scene " << surf.GetSceneId() << ":";
0776         vecgeom::NavigationState::PrintTopImpl(parent_state);
0777       }
0778     }
0779   };
0780 
0781   // Lambda for adding the surface to the entering candidates of a state
0782   auto addSurfToDefaultEntering = [&](int isurf, NavIndex_t state) {
0783     auto const &surf = fCPUdata.fCommonSurfaces[std::abs(isurf)];
0784     if (fVerbose > 0) printf("===== CS %d:\n", isurf);
0785     //  Add surface as entering candidate for the provided state
0786     int state_id             = vecgeom::NavigationState::GetIdImpl(state);
0787     auto &candidatesEntering = fCPUdata.GetCandidatesEntering(surf.GetSceneId(), state_id);
0788     auto &frameIndEntering   = fCPUdata.GetFrameIndEntering(surf.GetSceneId(), state_id);
0789     auto &sidesEntering      = fCPUdata.GetSidesEntering(surf.GetSceneId(), state_id);
0790     // Check which sides contain surfaces of daughters
0791     char sides = 0;
0792     if (surf.fLeftSide.fNsurf) sides |= kLside;
0793     if (surf.fRightSide.fNsurf) sides |= kRside;
0794     candidatesEntering.push_back(isurf);
0795     frameIndEntering.push_back(-1); // means this state is the default for isurf
0796     sidesEntering.push_back(sides);
0797     if (fVerbose > 0) {
0798       printf("  added %d to entering of def state on scene %d: ", candidatesEntering.back(), surf.GetSceneId());
0799       vecgeom::NavigationState::PrintTopImpl(state);
0800     }
0801   };
0802 
0803   // loop over all common surfaces and add their index in the appropriate list
0804   for (int isurf = 1; isurf < int(fCPUdata.fCommonSurfaces.size()); ++isurf) {
0805     auto const &surf        = fCPUdata.fCommonSurfaces[isurf];
0806     auto const &topSurfLeft = fCPUdata.fFramedSurf[surf.fLeftSide.GetTopSurfaceIndex()];
0807     // The surface is an entering candidate for the default state, if the default state
0808     // is different than the top surface state
0809     if (topSurfLeft.fState != surf.fDefaultState) addSurfToDefaultEntering(isurf, surf.fDefaultState);
0810     // Check if the surface is an entering candidate for any of the parent states on sides
0811     addSurfToSideParents(isurf, kLside);
0812     addSurfToSideParents(isurf, kRside);
0813 
0814     // Add surface to side states as exiting candidate
0815     addSurfToSideStates(isurf, kLside);
0816     addSurfToSideStates(isurf, kRside);
0817   }
0818 }
0819 
0820 template <typename Real_t>
0821 int BrepHelper<Real_t>::CreateCommonSurface(int idglob, int volId, int scene_id, int &iframe, char &iside)
0822 {
0823   constexpr char kLside = 0x01;
0824   constexpr char kRside = 0x02;
0825   bool flip{false}, flip_bool{false};
0826   auto approxEqual = [&](int idglob1, int idglob2) {
0827     flip                                                            = false;
0828     flip_bool                                                       = false;
0829     FramedSurface<Precision, TransformationMP<Precision>> const &s1 = fCPUdata.fFramedSurf[idglob1];
0830     FramedSurface<Precision, TransformationMP<Precision>> const &s2 = fCPUdata.fFramedSurf[idglob2];
0831     // Surfaces may be in future "compatible" even if they are not the same, for now enforce equality
0832     if (s1.fSurface.type != s2.fSurface.type) return false;
0833 
0834     // Skip Arb4 for now
0835     static bool warning_printed = false;
0836     if (s1.fSurface.type == SurfaceType::kArb4 || s2.fSurface.type == SurfaceType::kArb4) {
0837       if (!warning_printed) {
0838         VECGEOM_LOG(warning) << "CreateCommonSurface: case " << to_cstring(s1.fSurface.type) << " not implemented";
0839         warning_printed = true;
0840       }
0841       return false;
0842     }
0843 
0844     // Check if the surfaces may be flipped because of Boolean negation
0845     if ((s1.fLogicId == 0) || (s2.fLogicId == 0)) {
0846       flip_bool = (s1.fLogicId < 0) || (s2.fLogicId < 0);
0847     } else {
0848       flip_bool = (s1.fLogicId < 0) != (s2.fLogicId < 0);
0849     }
0850 
0851     // Check if the 2 surfaces are parallel
0852     vecgeom::Transformation3DMP<vecgeom::Precision> const &t1 = s1.fTrans;
0853     vecgeom::Transformation3DMP<vecgeom::Precision> const &t2 = s2.fTrans;
0854     // Check if the rotations are matching. The z axis inverse-transformed
0855     // with the two rotations should end up as aligned vectors. This is
0856     // true for planes (Z is the normal) but also for tubes/cones where
0857     // Z is the axis of symmetry
0858     vecgeom::Vector3D<vecgeom::Precision> const zaxis(0, 0, 1);
0859     auto z1 = t1.InverseTransformDirection(zaxis);
0860     auto z2 = t2.InverseTransformDirection(zaxis);
0861     if (!ApproxEqualVector(z1.Cross(z2), {0, 0, 0})) return false;
0862     // Calculate normalized connection vector between the two transformations
0863     // Use double precision explicitly
0864     vecgeom::Vector3D<vecgeom::Precision> tdiff = t1.Translation() - t2.Translation();
0865     bool same_tr                                = ApproxEqualVector(tdiff, {0, 0, 0});
0866     vecgeom::Vector3D<vecgeom::Precision> ldir;
0867     switch (s1.fSurface.type) {
0868     case SurfaceType::kPlanar:
0869       flip = z1.Dot(z2) < 0;
0870       if (same_tr) break;
0871       // For planes to match, the connecting vector must be along the planes
0872       tdiff.Normalize();
0873       t1.TransformDirection(tdiff, ldir);
0874       if (std::abs(ldir[2]) > vecgeom::kTolerance) return false;
0875       break;
0876     case SurfaceType::kCylindrical:
0877       if (std::abs(s1.fSurface.Radius() - s2.fSurface.Radius()) > vecgeom::kTolerance) return false;
0878       // Check if the cylynders are flipped with respect to each other
0879       flip = (s1.fSurface.IsFlipped()) ^ (s2.fSurface.IsFlipped());
0880       if (same_tr) break;
0881       tdiff.Normalize();
0882       t1.TransformDirection(tdiff, ldir);
0883       // For connected cylinders, the connecting vector must be along the Z axis
0884       if (!ApproxEqualVector(ldir, {0, 0, ldir[2]})) return false;
0885       break;
0886     case SurfaceType::kConical:
0887       if (std::abs(s1.fSurface.RadiusZ(-Abs(t1.Translation()[2])) - s2.fSurface.RadiusZ(-Abs(t2.Translation()[2]))) >
0888           vecgeom::kTolerance) {
0889         return false;
0890       }
0891       if (std::abs(s1.fSurface.fSlope - s2.fSurface.fSlope) > vecgeom::kTolerance) {
0892         return false;
0893       }
0894       flip = s1.fSurface.IsFlipped() ^ s2.fSurface.IsFlipped();
0895       if (same_tr) break;
0896       tdiff.Normalize();
0897       t1.TransformDirection(tdiff, ldir);
0898       // For connected cones, the connecting vector must be along the Z axis
0899       if (!ApproxEqualVector(ldir, {0, 0, ldir[2]})) return false;
0900       break;
0901     case SurfaceType::kSpherical:
0902     case SurfaceType::kTorus:
0903     case SurfaceType::kArb4:
0904     default:
0905       if (!warning_printed) {
0906         VECGEOM_LOG(warning) << "CreateCommonSurface: case " << to_cstring(s1.fSurface.type) << " not implemented";
0907         warning_printed = true;
0908       }
0909       return false;
0910     };
0911     return true;
0912   };
0913 
0914   auto surfHash = [&](int idglobal, double tolerance = 100 * vecgeom::kTolerance) {
0915     // Compute hash for the surface rotation and translation
0916     auto const &surf                                             = fCPUdata.fFramedSurf[idglobal];
0917     vecgeom::Transformation3DMP<vecgeom::Precision> const &trans = surf.fTrans;
0918 
0919     // get normal vector of surface
0920     vecgeom::Vector3D<vecgeom::Precision> normal;
0921     vecgeom::Vector3D<vecgeom::Precision> scaled_norm_vector;
0922     const vecgeom::Vector3D<vecgeom::Precision> lnorm(0, 0, 1);
0923     trans.InverseTransformDirection(lnorm, normal);
0924 
0925     long hash = 0;
0926     // helper function to generate hash from integer numbers
0927     auto hash_combine = [](long seed, const long value) {
0928       return seed ^ (std::hash<long>{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
0929     };
0930 
0931     switch (surf.fSurface.type) {
0932     case SurfaceType::kPlanar:
0933       // use normal vector scaled by the distance to the origin for hashing
0934       scaled_norm_vector = trans.Translation().Dot(normal) * normal;
0935       for (int i = 0; i < 3; i++) {
0936         // use tolerance to generate int with the desired precision from a real number for hashing
0937         hash = hash_combine(hash, static_cast<long>(std::round(scaled_norm_vector[i] / tolerance)));
0938       }
0939       break;
0940     case SurfaceType::kCylindrical:
0941       // use radius and normal for hashing
0942       hash = hash_combine(hash, static_cast<long>(std::round(surf.fSurface.Radius() / tolerance)));
0943 
0944       for (int i = 0; i < 3; i++) {
0945         hash = hash_combine(hash, static_cast<long>(std::round(normal[i] / tolerance)));
0946       }
0947       break;
0948     case SurfaceType::kConical:
0949       // use radius at origin, slope, and normal for hashing
0950       hash = hash_combine(
0951           hash, static_cast<long>(std::round(surf.fSurface.RadiusZ(-Abs(trans.Translation()[2])) / tolerance)));
0952       hash = hash_combine(hash, static_cast<long>(std::round(surf.fSurface.fSlope / tolerance)));
0953       for (int i = 0; i < 3; i++) {
0954         hash = hash_combine(hash, static_cast<long>(std::round(normal[i] / tolerance)));
0955       }
0956       break;
0957     case SurfaceType::kSpherical:
0958     case SurfaceType::kTorus:
0959     case SurfaceType::kArb4:
0960     default:
0961       hash = 0;
0962       break;
0963     };
0964 
0965     return hash;
0966   };
0967 
0968   auto const &surf   = fCPUdata.fFramedSurf[idglob];
0969   bool is_scene_surf = (scene_id > 0) && (surf.fState == 0);
0970   auto hash          = surfHash(idglob, 1000 * vecgeom::kTolerance);
0971   // Get the compatible surfaces
0972   auto range          = fCPUdata.fSurfHash[scene_id].equal_range(hash);
0973   bool found_dup_surf = false;
0974   int id              = -1;
0975   // check duplicates only for valid hashes
0976   if (hash != 0) {
0977     for (auto it = range.first; it != range.second; ++it) {
0978       const auto &other_id = fCPUdata.fCommonSurfaces[it->second].fLeftSide.fSurfaces[0];
0979 
0980       if (approxEqual(other_id, idglob)) {
0981         flip ^= flip_bool;
0982         // Do not allow surfaces of the same volume on different sides of the same common surface, otherwise the
0983         // surface will be missed when coming from the entering side. if (flip && othersurf.VolumeId() == volId)
0984         // continue;
0985         found_dup_surf = true;
0986         id             = it->second;
0987         auto &crt_side = flip ? fCPUdata.fCommonSurfaces[id].fRightSide : fCPUdata.fCommonSurfaces[id].fLeftSide;
0988         // The common surface is compatible only if the parent state for the current framed surface
0989         // has a frame on the same side or it is already the default state.
0990         NavIndex_t parent_state_index = fCPUdata.fFramedSurf[idglob].fState;
0991         vecgeom::NavigationState::PopImpl(parent_state_index);
0992         NavIndex_t default_state = fCPUdata.fCommonSurfaces[id].fDefaultState;
0993         // if the default state is different (note that it can also be different if both are 0 but it is a scene) do
0994         // thorough check. If the common surface is a top scene surface (fSceneId < 0), don't do the state check if
0995         // the to-be-checked surface is also a top scene surface (is_scene_surf), because those need to be put on the
0996         // same common surface
0997         if ((default_state != parent_state_index) || (default_state == 0 && parent_state_index == 0 &&
0998                                                       fCPUdata.fCommonSurfaces[id].fSceneId < 0 && !is_scene_surf)) {
0999           // To be compatible, a surface of the parent state MUST exist on the same side
1000           bool has_parent = false;
1001           for (auto isurf = 0; isurf < crt_side.fNsurf; ++isurf) {
1002             has_parent = fCPUdata.fFramedSurf[crt_side.fSurfaces[isurf]].fState == parent_state_index;
1003             if (has_parent) break;
1004           }
1005           if (!has_parent) {
1006             found_dup_surf = false;
1007             continue;
1008           }
1009         }
1010         // Add the global surface to the appropriate side
1011         iframe = crt_side.AddSurface(idglob);
1012         iside  = flip ? kRside : kLside;
1013         break;
1014       }
1015     }
1016   }
1017 
1018   if (!found_dup_surf) {
1019     // Construct a new common surface from the current placed global surface
1020     // Set the common state to be the parent of the idglob surface state
1021     id = fCPUdata.fCommonSurfaces.size();
1022     fCPUdata.fCommonSurfaces.push_back({fCPUdata.fFramedSurf[idglob].fSurface.type, idglob, surf.fLogicId < 0});
1023     iside             = kLside;
1024     iframe            = 0;
1025     auto parent_state = fCPUdata.fFramedSurf[idglob].fState;
1026     vecgeom::NavigationState::PopImpl(parent_state);
1027     fCPUdata.fCommonSurfaces[id].fSceneId      = is_scene_surf ? -scene_id : scene_id;
1028     fCPUdata.fCommonSurfaces[id].fDefaultState = parent_state;
1029     // insert the common surface in the scene and in the scene multimap
1030     fCPUdata.fSurfHash[scene_id].insert(std::make_pair(hash, id));
1031   }
1032 
1033   return id;
1034 }
1035 
1036 template <typename Real_t>
1037 bool BrepHelper<Real_t>::CreateCommonSurfacesScenes()
1038 {
1039   using Real_b          = typename SurfData<Real_t>::Real_b;
1040   constexpr char kLside = 0x01;
1041   constexpr char kRside = 0x02;
1042   int nphysical         = 0;
1043   int maxscene          = -1;
1044   int numscenes         = 0;
1045   vecgeom::NavigationState state;
1046   int ntot = vecgeom::GeoManager::Instance().GetRegisteredVolumesCount();
1047   std::vector<bool> visited(ntot, false);
1048   auto &numStates = fCPUdata.fSceneTouchables;
1049 
1050   // recursive geometry visitor lambda counting the number of states per scene
1051   typedef std::function<void(vecgeom::VPlacedVolume const *)> funcCountStates_t;
1052   funcCountStates_t countStatesPerScene = [&](vecgeom::VPlacedVolume const *pvol) {
1053     state.Push(pvol);
1054     const auto vol          = pvol->GetLogicalVolume();
1055     auto ivol               = vol->id();
1056     auto daughters          = vol->GetDaughters();
1057     int nd                  = daughters.size();
1058     unsigned short scene_id = 0, newscene_id = 0;
1059     bool is_scene = state.GetSceneId(scene_id, newscene_id);
1060     if (scene_id > maxscene) {
1061       int ntoinsert = scene_id - maxscene + 1;
1062       numStates.insert(numStates.end(), ntoinsert, 0);
1063       maxscene  = scene_id;
1064       numscenes = maxscene + 1;
1065     }
1066     numStates[scene_id]++;
1067     bool do_daughters = is_scene ? (!visited[ivol]) : true;
1068     if (do_daughters) {
1069       for (int id = 0; id < nd; ++id) {
1070         countStatesPerScene(daughters[id]);
1071       }
1072     }
1073     visited[ivol] = is_scene;
1074     state.Pop();
1075   };
1076 
1077   auto allocateExitingCandidates = [&](unsigned short scene_id, int state_id, size_t nsurf) {
1078     auto &candidatesExiting = fCPUdata.GetCandidatesExiting(scene_id, state_id);
1079     auto &frameIndExiting   = fCPUdata.GetFrameIndExiting(scene_id, state_id);
1080     auto &sidesExiting      = fCPUdata.GetSidesExiting(scene_id, state_id);
1081     if (nsurf >= candidatesExiting.size()) {
1082       candidatesExiting.insert(candidatesExiting.end(), nsurf - candidatesExiting.size(), 0);
1083       frameIndExiting.insert(frameIndExiting.end(), nsurf - frameIndExiting.size(), 0);
1084       sidesExiting.insert(sidesExiting.end(), nsurf - sidesExiting.size(), 0);
1085     }
1086   };
1087 
1088   // recursive geometry visitor lambda creating the common surfaces for the current placed volume
1089   typedef std::function<void(vecgeom::VPlacedVolume const *)> func_t;
1090   func_t createCommonSurfaces = [&](vecgeom::VPlacedVolume const *pvol) {
1091     // TODO: In this funcstion, store the product of pvol.trans * surface.trans
1092     state.Push(pvol);
1093     const auto vol          = pvol->GetLogicalVolume();
1094     auto ivol               = vol->id();
1095     auto daughters          = vol->GetDaughters();
1096     int nd                  = daughters.size();
1097     NavIndex_t nav_ind      = state.GetNavIndex();
1098     unsigned short scene_id = 0, newscene_id = 0;
1099     bool is_scene   = state.GetSceneId(scene_id, newscene_id);
1100     auto state_id   = state.GetId();
1101     auto &nperscene = fCPUdata.fSceneTouchables;
1102     nphysical++;
1103     nperscene[scene_id]++;
1104     TransformationMP<vecgeom::Precision> trans;
1105     // only consider the surface transformation in its scene
1106     state.TopInSceneMatrix(trans);
1107     VolumeShellCPU const &shell = fCPUdata.fShells[ivol];
1108     // Allocate exit candidates arrays
1109     auto nsurf_local = shell.fSurfaces.size();
1110     allocateExitingCandidates(scene_id, state_id, nsurf_local);
1111     if (is_scene && !visited[ivol]) allocateExitingCandidates(newscene_id, 0, nsurf_local);
1112 
1113     for (int lsurf_id : shell.fSurfaces) {
1114       FramedSurface<Precision, TransformationMP<Precision>> &lsurf = fCPUdata.fLocalSurfaces[lsurf_id];
1115 
1116       // Ignore 'inside' helper surfaces having no frame
1117       if (lsurf.fFrame.type == FrameType::kNoFrame) continue;
1118       TransformationMP<vecgeom::Precision> surftrans = lsurf.fTrans;
1119       surftrans *= trans;
1120 
1121       // Create the surface in the current scene using the local navigation index in the scene
1122       int id_surf = fCPUdata.fFramedSurf.size();
1123       fCPUdata.fFramedSurf.push_back({lsurf.fSurface, lsurf.fFrame, surftrans, nav_ind, lsurf.fNeverCheck});
1124       auto &framed_surf      = fCPUdata.fFramedSurf[id_surf];
1125       framed_surf.fLogicId   = lsurf.fLogicId;
1126       framed_surf.fSurfIndex = lsurf.fSurfIndex;
1127       framed_surf.fEmbedding = (lsurf.fLogicId == 0) ? lsurf.fEmbedding : false;
1128       VECGEOM_ASSERT(lsurf.fSurfIndex < nsurf_local);
1129 
1130       char iside = 0;
1131       int iframe = 0;
1132       auto isurf = CreateCommonSurface(id_surf, ivol, scene_id, iframe, iside);
1133 
1134       if (fVerbose > 0) {
1135         std::cout << "scene " << scene_id << ": framed surface " << id_surf << " on CS " << isurf << std::endl;
1136         state.PrintTop();
1137         std::cout << "  " << surftrans << "\n";
1138       }
1139 
1140       if (is_scene) {
1141         // Create the surface once also in the new scene if the shell belongs to one
1142         if (!visited[ivol]) {
1143           // Make a new top framed surface in the new scene
1144           int id_surf_scene = fCPUdata.fFramedSurf.size();
1145           // Store the local transformation of the scene volume surface
1146           fCPUdata.fFramedSurf.push_back(
1147               {lsurf.fSurface, lsurf.fFrame, lsurf.fTrans, 0 /*top in scene*/, lsurf.fNeverCheck});
1148           // Watchout: evil bug: cannot use the framed_surf reference after this point, because after
1149           // inserting a new frame the array may be re-allocated internally by the vector
1150           fCPUdata.fFramedSurf[id_surf_scene].fLogicId   = lsurf.fLogicId;
1151           fCPUdata.fFramedSurf[id_surf_scene].fSurfIndex = lsurf.fSurfIndex;
1152           fCPUdata.fFramedSurf[id_surf_scene].fEmbedding = (lsurf.fLogicId == 0) ? lsurf.fEmbedding : false;
1153           auto isurf_scene = CreateCommonSurface(id_surf_scene, ivol, newscene_id, iframe, iside);
1154 
1155           // This assert was to ensure that the first surface of a new volume must be on the left side
1156           // For booleans, this is not strictly true, so we remove this as a consequence of !1075
1157           // constexpr char kLside                  = 0x01;
1158           // VECGEOM_ASSERT(iside == kLside);
1159 
1160           // Add the CS pointer to the frame in the parent scene. So if a track enters the frame it is relocated in
1161           // this frame, it checks the info on the scene CS
1162           fCPUdata.fFramedSurf[id_surf].fSceneCS = isurf_scene;
1163           // Frames on sides are sorted after, so store the global surface index for now
1164           // After sorting we change this index the the index of the frame on the side
1165           fCPUdata.fFramedSurf[id_surf].fSceneCSind = (iside == kLside) ? id_surf_scene : -id_surf_scene;
1166           fCPUdata.fSceneShells[ivol].fSurfaces[framed_surf.fSurfIndex] = id_surf;
1167           if (fVerbose > 0) {
1168             VECGEOM_LOG(info) << "scene " << newscene_id << ": top framed surface " << id_surf_scene << " on CS "
1169                               << isurf_scene;
1170             VECGEOM_LOG(info) << "  linked to CS " << isurf << " surf_index=" << lsurf.fSurfIndex;
1171           }
1172         } else {
1173           // We need to assign the scene CS pointer to the framed surface
1174           int id_surf_first       = fCPUdata.fSceneShells[ivol].fSurfaces[framed_surf.fSurfIndex];
1175           auto &framed_surf_first = fCPUdata.fFramedSurf[id_surf_first];
1176           framed_surf.fSceneCS    = framed_surf_first.fSceneCS;
1177           framed_surf.fSceneCSind = framed_surf_first.fSceneCSind;
1178         }
1179       }
1180     }
1181 
1182     bool do_daughters = is_scene ? (!visited[ivol]) : true;
1183     if (do_daughters) {
1184       for (int id = 0; id < nd; ++id) {
1185         createCommonSurfaces(daughters[id]);
1186       }
1187     }
1188     visited[ivol] = is_scene;
1189     state.Pop();
1190   };
1191 
1192   auto checkExitingCandidates = [&](unsigned short scene_id, int state_id, size_t nsurf) {
1193     auto &candidatesExiting = fCPUdata.GetCandidatesExiting(scene_id, state_id);
1194     int ivol                = state.GetLogicalId();
1195     auto &shell             = fCPUdata.fShells[ivol];
1196     for (size_t isurf = 0; isurf < nsurf; ++isurf) {
1197       auto surf = fCPUdata.fLocalSurfaces[shell.fSurfaces[isurf]];
1198       if ((surf.fFrame.type == FrameType::kNoFrame && candidatesExiting[isurf] > 0) ||
1199           (surf.fFrame.type != FrameType::kNoFrame && candidatesExiting[isurf] == 0)) {
1200         std::cout << "=== Wrong candidates for state:\n";
1201         state.Print();
1202         printf("   candExiting:   ");
1203         int j = 0;
1204         for (auto candidate : candidatesExiting)
1205           printf("  %d: %d ", j++, candidate);
1206         printf("\n");
1207         return false;
1208       }
1209     }
1210     return true;
1211   };
1212 
1213   // recursive geometry visitor lambda validating exiting candidates
1214   typedef std::function<void(vecgeom::VPlacedVolume const *)> funcValidate_t;
1215   funcValidate_t validateExitingCandidates = [&](vecgeom::VPlacedVolume const *pvol) {
1216     state.Push(pvol);
1217     const auto vol          = pvol->GetLogicalVolume();
1218     auto ivol               = vol->id();
1219     auto daughters          = vol->GetDaughters();
1220     int nd                  = daughters.size();
1221     unsigned short scene_id = 0, newscene_id = 0;
1222     bool is_scene               = state.GetSceneId(scene_id, newscene_id);
1223     auto state_id               = state.GetId();
1224     VolumeShellCPU const &shell = fCPUdata.fShells[ivol];
1225     auto nsurf_local            = shell.fSurfaces.size();
1226 
1227     if (!checkExitingCandidates(scene_id, state_id, nsurf_local)) return false;
1228     if (is_scene && !visited[ivol]) {
1229       if (!checkExitingCandidates(newscene_id, 0, nsurf_local)) return false;
1230     }
1231 
1232     bool do_daughters = is_scene ? (!visited[ivol]) : true;
1233     if (do_daughters) {
1234       for (int id = 0; id < nd; ++id) {
1235         validateExitingCandidates(daughters[id]);
1236       }
1237     }
1238     visited[ivol] = is_scene;
1239     state.Pop();
1240     return true;
1241   };
1242 
1243   // add a dummy common surface since index 0 is not allowed for correctly handling sides
1244   fCPUdata.fCommonSurfaces.push_back({});
1245 
1246   auto world = vecgeom::GeoManager::Instance().GetWorld();
1247   // Count touchables per scene since this is not stored in the navigation table
1248   countStatesPerScene(world);
1249   // Pre-allocate data in scene arrays
1250   // fCPUdata.fSceneTouchables already filled
1251   fCPUdata.fSceneStartIndex.insert(fCPUdata.fSceneStartIndex.end(), numscenes, 0);
1252   fCPUdata.fSurfHash.insert(fCPUdata.fSurfHash.end(), numscenes, {});
1253   int startindex = 0;
1254   for (int scene_id = 0; scene_id < numscenes; ++scene_id) {
1255     // Reserve a place also for the outside state in each scene
1256     int nt                              = ++fCPUdata.fSceneTouchables[scene_id];
1257     fCPUdata.fSceneStartIndex[scene_id] = startindex;
1258     startindex += nt;
1259     fCPUdata.fCandidatesEntering.insert(fCPUdata.fCandidatesEntering.end(), nt, {});
1260     fCPUdata.fCandidatesExiting.insert(fCPUdata.fCandidatesExiting.end(), nt, {});
1261     fCPUdata.fFrameIndEntering.insert(fCPUdata.fFrameIndEntering.end(), nt, {});
1262     fCPUdata.fFrameIndExiting.insert(fCPUdata.fFrameIndExiting.end(), nt, {});
1263     fCPUdata.fSidesEntering.insert(fCPUdata.fSidesEntering.end(), nt, {});
1264     fCPUdata.fSidesExiting.insert(fCPUdata.fSidesExiting.end(), nt, {});
1265   }
1266 
1267   // before we construct the common surfaces from the local surfaces, we can mark convex boolean surfaces such that
1268   // they are treated as non-boolean surfaces. this requires the bounding boxes of the BVH. Init the information
1269   // needed to contruct and use the BVH
1270   InitBVHData();
1271 
1272   vecgeom::ABBoxManager<Real_b>::Instance().InitABBoxesForSurfaces(fCPUdata);
1273   // Commented out Boolean convexity checking
1274   // FindConvexBooleanSurfaces();
1275   vecgeom::ABBoxManager<Real_b>::Instance().InitABBoxesForSurfaces(fCPUdata, /*crop=*/true);
1276 
1277   std::fill(visited.begin(), visited.end(), false);
1278   createCommonSurfaces(world);
1279 
1280   for (size_t isurf = 1; isurf < fCPUdata.fCommonSurfaces.size(); ++isurf) {
1281     // Compute the default states in case no frame on the surface is hit
1282     ComputeDefaultStates(isurf);
1283     // Sort placed surfaces on sides by geometry depth (bigger depth comes first)
1284     SortSides(isurf);
1285     // Convert transformations of placed surfaces in the local frame of the common surface
1286     ConvertTransformations(isurf);
1287   }
1288 
1289   // Adjust scene frame index pointers
1290   // Lambda adjusting fSceneCSind
1291   auto adjustFrameIndex = [&](int isurf, char iside, int iglob) {
1292     auto const &surf = fCPUdata.fCommonSurfaces[isurf];
1293     Side const &side = (iside == kLside) ? surf.fLeftSide : surf.fRightSide;
1294     for (int i = 0; i < side.fNsurf; ++i) {
1295       if (side.fSurfaces[i] == std::abs(iglob)) return (iglob > 0) ? i + 1 : -(i + 1);
1296     }
1297     return 0;
1298   };
1299 
1300   for (size_t iframe = 0; iframe < fCPUdata.fFramedSurf.size(); ++iframe) {
1301     auto &framed_surf = fCPUdata.fFramedSurf[iframe];
1302     if (framed_surf.fSceneCS > 0) {
1303       auto iglob = framed_surf.fSceneCSind;
1304       auto index = adjustFrameIndex(framed_surf.fSceneCS, kLside, iglob);
1305       if (index == 0) index = adjustFrameIndex(framed_surf.fSceneCS, kRside, iglob);
1306       if (index != 0) framed_surf.fSceneCSind = index;
1307     }
1308   }
1309 
1310   // Create the full surface candidate list for each navigation state
1311   CreateCandidateLists();
1312 
1313   // Validate exiting candidates
1314   std::fill(visited.begin(), visited.end(), false);
1315   state.Clear();
1316   validateExitingCandidates(world);
1317 
1318   // Compute side divisions for all sides of common surfaces
1319   ComputeSideDivisions();
1320 
1321   // Now update the surface data structure used for navigation
1322   UpdateSurfData();
1323 
1324   ////////////////////////// BVH /////////////////////////////
1325 
1326   // Surf bounding boxes are already initialized as they were needed for convexity check of boolean surfaces
1327 
1328   // At this point, the AABBs for solids, templated by vecgeom::Precision have already been
1329   // initialized. In case vecgeom::Precision and Real_t are different, we need to also initialize
1330   // them for ABBoxManager<Real_t>
1331   vecgeom::ABBoxManager<Real_b>::Instance().InitABBoxesForCompleteGeometry();
1332 
1333   // Then initialize the BVH for each Logical Volume
1334   std::vector<vecgeom::LogicalVolume const *> lvols;
1335   vecgeom::GeoManager::Instance().GetAllLogicalVolumes(lvols);
1336 
1337   // Allocate space for the BVHs
1338   using Real_b    = typename SurfData<Real_t>::Real_b;
1339   fSurfData->fBVH = new vecgeom::BVH<Real_b>[fCPUdata.fShells.size()];
1340   fSurfData->fBVHSolids =
1341       new vecgeom::BVH<Real_b>[fCPUdata.fShells.size()]; // Auxiliary BVH for location, built from solid AABBs
1342 
1343   for (auto logical_volume : lvols) {
1344     // Get the index allocated for this shell's BVH
1345     int bvh_index = fCPUdata.fShells[logical_volume->id()].fBVH;
1346     int nSolidBoxes{0};
1347     int nSurfBoxes{0};
1348     Vector3D<Real_b> *surfBoxes{nullptr};
1349     Vector3D<Real_b> *solidBoxes{nullptr};
1350     surfBoxes = vecgeom::ABBoxManager<Real_b>::Instance().GetSurfaceABBoxes(logical_volume->id(), nSurfBoxes, fCPUdata);
1351     solidBoxes = vecgeom::ABBoxManager<Real_b>::Instance().GetABBoxes(logical_volume, nSolidBoxes);
1352 
1353     // Note: we construct the BVH directly in the surface data and do not copy it from the CPU data. Still, we use the
1354     // CPUdata to get the indices and data needed for initialization. Destroy the empty BVH object
1355     fSurfData->fBVH[bvh_index].Clear();
1356     // Construct the correct BVH from the logical volume
1357     // bvh::InitBVH<Real_t>(logical_volume->id(), fSurfData->fBVH[bvh_index], *fSurfData);
1358     bvh::InitBVH<Real_t>(logical_volume->id(), fSurfData->fBVH[bvh_index], fCPUdata, surfBoxes, nSurfBoxes);
1359     // Construct the solids BVH
1360     if (logical_volume->GetDaughters().size() > 0) {
1361       // For solids, we can only construct a BVH if there are daughter volumes
1362       // Otherwise the instance remains uninitialized
1363       bvh::InitBVH<Real_t>(logical_volume->id(), fSurfData->fBVHSolids[bvh_index], fCPUdata, solidBoxes, nSolidBoxes);
1364     }
1365   }
1366 
1367   ////////////////////////////////////////////////////////////
1368   if (fVerbose > 0) {
1369     for (size_t isurf = 1; isurf < fCPUdata.fCommonSurfaces.size(); ++isurf)
1370       PrintCommonSurface(isurf);
1371   }
1372 
1373   if (fVerbose > 1) {
1374     PrintCandidateLists();
1375     std::cout << "Visited " << nphysical << " physical volumes, created " << fCPUdata.fCommonSurfaces.size() - 1
1376               << " common surfaces\n";
1377   }
1378 
1379   return true;
1380 }
1381 
1382 template <typename Real_t>
1383 bool BrepHelper<Real_t>::CreateLocalSurfaces()
1384 {
1385   // add identity first in the list of local transformations
1386   TransformationMP<vecgeom::Precision> identity;
1387   //  Iterate logical volumes and create local surfaces
1388   std::vector<vecgeom::LogicalVolume *> volumes;
1389   auto n_registered_volumes = vecgeom::GeoManager::Instance().GetRegisteredVolumesCount();
1390   vecgeom::GeoManager::Instance().GetAllLogicalVolumes(volumes);
1391 
1392   SetNvolumes(n_registered_volumes);
1393   // TODO: Implement a VUnplacedVolume::CreateSurfaces interface for surface creation
1394   // create a placeholder for surface data
1395   for (auto volume : volumes) {
1396     vecgeom::VUnplacedVolume const *solid = volume->GetUnplacedVolume();
1397     bool result                           = conv::CreateSolidSurfaces<vecgeom::Precision>(solid, volume->id());
1398     if (!result) {
1399       VECGEOM_LOG(error) << "Could not convert volume " << volume->id() << ": " << volume->GetName();
1400     }
1401     // Finalize logic expression
1402     if (!fCPUdata.fShells[volume->id()].fSimplified) {
1403       auto &crtlogic = fCPUdata.fShells[volume->id()].fLogic;
1404       vgbrep::logichelper::LogicExpressionConstruct lc(crtlogic);
1405       lc.Simplify(crtlogic);
1406       vgbrep::logichelper::insert_jumps(crtlogic);
1407       fCPUdata.fShells[volume->id()].fSimplified = true;
1408     }
1409   }
1410 
1411   if (fVerbose > 0) {
1412     for (auto volume : volumes) {
1413       auto const &shell = fCPUdata.fShells[volume->id()];
1414       printf("shell %d for volume %s:\n", volume->id(), volume->GetName());
1415       logichelper::print_logic(shell.fLogic);
1416       for (int lsurf_id : shell.fSurfaces) {
1417         auto const &lsurf = fCPUdata.fLocalSurfaces[lsurf_id];
1418         printf(" local surf %d (logic_id=%d): ", lsurf_id, lsurf.fLogicId);
1419         lsurf.fTrans.Print();
1420         printf("\n");
1421       }
1422     }
1423   }
1424 
1425   return true;
1426 }
1427 
1428 template <typename Real_t>
1429 void BrepHelper<Real_t>::DumpBVH(uint ivol)
1430 {
1431   auto lvol = vecgeom::GeoManager::Instance().GetLogicalVolume(ivol);
1432   VECGEOM_ASSERT(lvol->id() == ivol);
1433   int bvh_index = fSurfData->fShells[ivol].fBVH;
1434   auto fname    = std::string(lvol->GetName()) + ".bin";
1435   bvh::DumpBVH(fSurfData->fBVH[bvh_index], fname.c_str());
1436 }
1437 
1438 template <typename Real_t>
1439 bool BrepHelper<Real_t>::EqualFrames(Side const &side, int i1, int i2)
1440 {
1441   using Vector3D = vecgeom::Vector3D<vecgeom::Precision>;
1442 
1443   auto const &s1 = fCPUdata.fFramedSurf[side.fSurfaces[i1]];
1444   auto const &s2 = fCPUdata.fFramedSurf[side.fSurfaces[i2]];
1445   if (s1.fFrame.type != s2.fFrame.type) return false;
1446   // Get displacement vector between the 2 frame centers and check if it has null length
1447   vecgeom::Transformation3DMP<vecgeom::Precision> const &t1 = s1.fTrans;
1448   vecgeom::Transformation3DMP<vecgeom::Precision> const &t2 = s2.fTrans;
1449   Vector3D tdiff                                            = t1.Translation() - t2.Translation();
1450   // TODO: Check if this has to always hold with the new mask types!!
1451   if (!ApproxEqualVector(tdiff, {0, 0, 0})) return false;
1452 
1453   // Different treatment of different frame types
1454   switch (s1.fFrame.type) {
1455   case FrameType::kRangeZ:
1456     break;
1457   case FrameType::kRing: {
1458     auto mask1 = fCPUdata.fRingMasks[s1.fFrame.id];
1459     auto mask2 = fCPUdata.fRingMasks[s2.fFrame.id];
1460 
1461     // Inner radius must be the same
1462     if (!ApproxEqual(mask1.rangeR[0], mask2.rangeR[0]) || !ApproxEqual(mask1.rangeR[1], mask2.rangeR[1]) ||
1463         mask1.isFullCirc != mask2.isFullCirc)
1464       return false;
1465     if (mask1.isFullCirc) return true;
1466     // Unit-vectors used for checking sphi
1467     auto phimin1 = t1.InverseTransformDirection(Vector3D{mask1.vecSPhi[0], mask1.vecSPhi[1], 0});
1468     auto phimin2 = t2.InverseTransformDirection(Vector3D{mask2.vecSPhi[0], mask2.vecSPhi[1], 0});
1469     // Rmax vectors used for checking dphi and outer radius
1470     auto phimax1 = t1.InverseTransformDirection(Vector3D{mask1.vecEPhi[0], mask1.vecEPhi[1], 0});
1471     auto phimax2 = t2.InverseTransformDirection(Vector3D{mask2.vecEPhi[0], mask2.vecEPhi[1], 0});
1472 
1473     if (ApproxEqualVector(phimin1, phimin2) && ApproxEqualVector(phimax1, phimax2)) return true;
1474     break;
1475   }
1476   case FrameType::kZPhi: {
1477     auto mask1 = fCPUdata.fZPhiMasks[s1.fFrame.id];
1478     auto mask2 = fCPUdata.fZPhiMasks[s2.fFrame.id];
1479 
1480     // They are on the same side, so there is no flipping,
1481     // and z extents must be equal
1482     if (!ApproxEqual(mask1.rangeZ[0], mask2.rangeZ[0]) || !ApproxEqual(mask1.rangeZ[1], mask2.rangeZ[1])) return false;
1483     if (mask1.isFullCirc) return true;
1484     // Unit-vectors used for checking sphi
1485     auto phimin1 = t1.InverseTransformDirection(Vector3D{mask1.vecSPhi[0], mask1.vecSPhi[1], 0});
1486     auto phimin2 = t2.InverseTransformDirection(Vector3D{mask2.vecSPhi[0], mask2.vecSPhi[1], 0});
1487     // Rmax vectors used for checking dphi and outer radius
1488     auto phimax1 = t1.InverseTransformDirection(Vector3D{mask1.vecEPhi[0], mask1.vecEPhi[1], 0});
1489     auto phimax2 = t2.InverseTransformDirection(Vector3D{mask2.vecEPhi[0], mask2.vecEPhi[1], 0});
1490 
1491     if (ApproxEqualVector(phimin1, phimin2) && ApproxEqualVector(phimax1, phimax2)) return true;
1492     break;
1493   }
1494   case FrameType::kRangeSph:
1495     // if (ApproxEqualVector(v1, v2)) return true; //Must be changed.
1496     break;
1497   case FrameType::kWindow: {
1498     auto frameData1 = fCPUdata.fWindowMasks[s1.fFrame.id];
1499     auto frameData2 = fCPUdata.fWindowMasks[s2.fFrame.id];
1500 
1501     // Vertices
1502     Vector3D v11 = t1.InverseTransformDirection(Vector3D{frameData1.rangeU[0], frameData1.rangeV[0], 0}); // 1 down left
1503     Vector3D v12 = t1.InverseTransformDirection(Vector3D{frameData1.rangeU[1], frameData1.rangeV[1], 0}); // 1 up right
1504     Vector3D v21 = t2.InverseTransformDirection(Vector3D{frameData2.rangeU[0], frameData2.rangeV[0], 0}); // 2 down left
1505     Vector3D v22 = t2.InverseTransformDirection(Vector3D{frameData2.rangeU[1], frameData2.rangeV[1], 0}); // 2 up right
1506 
1507     // Diagonals
1508     auto diag1 = v12 - v11;
1509     auto diag2 = v22 - v21;
1510 
1511     return ApproxEqualVector(diag1.Abs(), diag2.Abs());
1512   }
1513   case FrameType::kTriangle:
1514     // to be implemented
1515     break;
1516   case FrameType::kQuadrilateral: {
1517     // to be implemented
1518     break;
1519   }
1520   default:
1521     break;
1522   };
1523   return false;
1524 }
1525 
1526 template <typename Real_t>
1527 void BrepHelper<Real_t>::InitBVHData()
1528 {
1529   std::vector<vecgeom::LogicalVolume const *> lvols;
1530   vecgeom::GeoManager::Instance().GetAllLogicalVolumes(lvols);
1531 
1532   TransformationMP<vecgeom::Precision> identity;
1533   VECGEOM_ASSERT(fCPUdata.fPVolTrans.size() == 0);
1534   fCPUdata.fPVolTrans.push_back(identity);
1535 
1536   for (auto lvol : lvols) {
1537     // Get the shell of the LV
1538     auto &rootShell = fCPUdata.fShells[lvol->id()];
1539 
1540     // Assign a BVH slot
1541     rootShell.fBVH = lvol->id();
1542 
1543     // Initialize array of exiting surfaces which have a frame
1544     for (uint i = 0; i < rootShell.fSurfaces.size(); i++) {
1545       if (fCPUdata.fLocalSurfaces[rootShell.fSurfaces[i]].fFrame.type != FrameType::kNoFrame)
1546         rootShell.fExitingSurfaces.push_back(i);
1547     }
1548 
1549     for (const auto &pvol : lvol->GetDaughters()) {
1550       // Add the PVol ID to the root shell
1551       rootShell.fDaughterPvolIds.push_back(pvol->id());
1552 
1553       // Get the shell
1554       const auto &shell = fCPUdata.fShells[pvol->GetLogicalVolume()->id()];
1555 
1556       // get transformation to placed volume
1557       int vol_trans_id = fCPUdata.fPVolTrans.size();
1558       fCPUdata.fPVolTrans.push_back(vecgeom::Transformation3DMP<vecgeom::Precision>(*pvol->GetTransformation()));
1559 
1560       // Add the PVol transformation to the root shell
1561       rootShell.fDaughterPvolTrans.push_back(vol_trans_id);
1562 
1563       // Iterate over the local surfaces which have a frame in this shell
1564       for (uint i = 0; i < shell.fSurfaces.size(); i++) {
1565         if (fCPUdata.fLocalSurfaces[shell.fSurfaces[i]].fFrame.type != FrameType::kNoFrame) {
1566           // Add the index of the surface to the list of entering surfaces for the LV
1567           rootShell.fEnteringSurfaces.push_back(shell.fSurfaces[i]);
1568           // Add the id of the Pvol to which this surface belongs
1569           rootShell.fEnteringSurfacesPvol.push_back(pvol->id());
1570           // store transformation to placed volume and id to logical volume
1571           rootShell.fEnteringSurfacesPvolTrans.push_back(vol_trans_id);
1572           rootShell.fEnteringSurfacesLvolIds.push_back(pvol->GetLogicalVolume()->id());
1573         }
1574       }
1575     }
1576   }
1577 }
1578 
1579 template <typename Real_t>
1580 void BrepHelper<Real_t>::FindConvexBooleanSurfaces()
1581 {
1582   using Real_b = typename SurfData<Real_t>::Real_b;
1583 
1584   std::vector<vecgeom::LogicalVolume const *> lvols;
1585   vecgeom::GeoManager::Instance().GetAllLogicalVolumes(lvols);
1586 
1587   long nsurf_bool      = 0;
1588   long nsurf_bool_conv = 0;
1589 
1590   for (auto lvol : lvols) {
1591     // Get the shell of the root LV
1592     auto &rootShell = fCPUdata.fShells[lvol->id()];
1593 
1594     // std::cout << " CHECKING SHELL WITH ID " << lvol->id() << std::endl;
1595     // for accessing the AABBs we need the BVH AABB precision Real_b
1596     auto boxes = vecgeom::ABBoxManager<Real_b>::Instance().fVolToSurfaceABBoxesMap[lvol->id()];
1597 
1598     // loop over all exiting surfaces of the shell to check each surfaces for convexity
1599     for (auto idsurf = 0u; idsurf < rootShell.fExitingSurfaces.size(); idsurf++) {
1600 
1601       bool surf_convex = true;
1602       bool inner_surf  = false;
1603       // Get the surface
1604       auto exiting_ind = rootShell.fExitingSurfaces[idsurf];
1605       auto &localSurface =
1606           fCPUdata.fLocalSurfaces[rootShell.fSurfaces[exiting_ind]]; // NOT const because we potentially change the
1607                                                                      // logic id!
1608 
1609       if (localSurface.fLogicId == 0) continue; // skip non-boolean surfaces
1610       nsurf_bool++;
1611       if (localSurface.fLogicId < 0) continue; // flipped surfaces cannot be made non-boolean at this point
1612 
1613       if (localSurface.fSkipConvexity) continue;
1614       switch (localSurface.fSurface.type) {
1615       case SurfaceType::kCylindrical:
1616       case SurfaceType::kSpherical:
1617         if (localSurface.fSurface.IsFlipped()) inner_surf = true;
1618         break;
1619       case SurfaceType::kConical:
1620         if (localSurface.fSurface.IsFlipped()) inner_surf = true;
1621         break;
1622       case SurfaceType::kTorus:
1623         if (fCPUdata.fTorusData[localSurface.fSurface.id].IsFlipped()) inner_surf = true;
1624         break;
1625       default:
1626         break;
1627       }
1628       // Local transformation of this surface
1629       auto const &surfaceTransform    = localSurface.fTrans;
1630       auto const inv_surfaceTransform = surfaceTransform.Inverse();
1631 
1632       // printf("\n\nStart checking convexity of surface %i with transformation \n", idsurf);
1633       // surfaceTransform.Print();
1634       // printf("\n");
1635       for (auto other_idsurf = 0u; other_idsurf < rootShell.fExitingSurfaces.size(); other_idsurf++) {
1636 
1637         if (other_idsurf == idsurf) continue;
1638 
1639         auto other_exiting_ind        = rootShell.fExitingSurfaces[other_idsurf];
1640         auto const other_localSurface = fCPUdata.fLocalSurfaces[rootShell.fSurfaces[other_exiting_ind]];
1641 
1642         // skip flipped surfaces if the checked surface is also flipped, because the bounding box can be fully inside,
1643         // while the surface is still outside, leading to false convex surfaces
1644         switch (other_localSurface.fSurface.type) {
1645         case SurfaceType::kCylindrical:
1646         case SurfaceType::kSpherical:
1647           if ((other_localSurface.fSurface.IsFlipped()) && inner_surf) surf_convex = false;
1648           break;
1649         case SurfaceType::kConical:
1650           if (other_localSurface.fSurface.IsFlipped() && inner_surf) surf_convex = false;
1651           break;
1652         case SurfaceType::kTorus:
1653           if (fCPUdata.fTorusData[other_localSurface.fSurface.id].IsFlipped() && inner_surf) surf_convex = false;
1654           break;
1655         default:
1656           break;
1657         }
1658 
1659         bool flip = localSurface.fLogicId < 0;
1660         flip ^= other_localSurface.fLogicId < 0;
1661         Real_t tol = 10000 * vecgeom::kToleranceStrict<Real_t>;
1662         // Real_t tol = other_localSurface.fLogicId < 0 ? 10000 * vecgeom::kToleranceStrict<Real_t>
1663         //                                              : 10000 * vecgeom::kToleranceStrict<Real_t>;
1664         // auto const &other_surfaceTransform = fCPUdata.fLocalTrans[other_localSurface.fTrans];
1665         // printf("checking other surface %i with transformation \n", other_idsurf);
1666         // other_surfaceTransform.Print();
1667         // printf("\n");
1668 
1669         // get other bounding box
1670         auto const other_lowert = boxes[2 * other_idsurf];
1671         auto const other_uppert = boxes[2 * other_idsurf + 1];
1672         Vector3D<double> other_lower(other_lowert[0], other_lowert[1], other_lowert[2]);
1673         Vector3D<double> other_upper(other_uppert[0], other_uppert[1], other_uppert[2]);
1674         // printf("BB: lower other %f %f %f\n", other_lower.x(), other_lower.y(), other_lower.z());
1675         // printf("BB: upper other %f %f %f\n", other_upper.x(), other_upper.y(), other_upper.z());
1676 
1677         // transform bounding into frame of the surface to check against
1678         vecgeom::ABBoxManager<double>::Instance().TransformBoundingBox(other_lower, other_upper, inv_surfaceTransform);
1679         // define the 8 corner points of the bounding box
1680         Vector3D<Real_t> corner1(other_lower.x(), other_lower.y(), other_lower.z());
1681         Vector3D<Real_t> corner2(other_upper.x(), other_lower.y(), other_lower.z());
1682         Vector3D<Real_t> corner3(other_lower.x(), other_upper.y(), other_lower.z());
1683         Vector3D<Real_t> corner4(other_upper.x(), other_upper.y(), other_lower.z());
1684         Vector3D<Real_t> corner5(other_lower.x(), other_lower.y(), other_upper.z());
1685         Vector3D<Real_t> corner6(other_upper.x(), other_lower.y(), other_upper.z());
1686         Vector3D<Real_t> corner7(other_lower.x(), other_upper.y(), other_upper.z());
1687         Vector3D<Real_t> corner8(other_upper.x(), other_upper.y(), other_upper.z());
1688         // check for inside
1689         bool inside_c1 = localSurface.fSurface.Inside(corner1, fCPUdata, flip, tol);
1690         bool inside_c2 = localSurface.fSurface.Inside(corner2, fCPUdata, flip, tol);
1691         bool inside_c3 = localSurface.fSurface.Inside(corner3, fCPUdata, flip, tol);
1692         bool inside_c4 = localSurface.fSurface.Inside(corner4, fCPUdata, flip, tol);
1693         bool inside_c5 = localSurface.fSurface.Inside(corner5, fCPUdata, flip, tol);
1694         bool inside_c6 = localSurface.fSurface.Inside(corner6, fCPUdata, flip, tol);
1695         bool inside_c7 = localSurface.fSurface.Inside(corner7, fCPUdata, flip, tol);
1696         bool inside_c8 = localSurface.fSurface.Inside(corner8, fCPUdata, flip, tol);
1697 
1698         // printf(" Is inside ? 8 corners: %i %i %i %i %i %i %i %i\n", inside_c1, inside_c2, inside_c3, inside_c4, inside_c5, inside_c6, inside_c7, inside_c8);
1699         // printf(" z value ? 2 values: lower point in z %.15f upper point in z %.15f\n", other_lower.z(), other_upper.z());
1700 
1701         surf_convex &=
1702             inside_c1 && inside_c2 && inside_c3 && inside_c4 && inside_c5 && inside_c6 && inside_c7 && inside_c8;
1703       } // inner loop over each surface that is checked against
1704       if (surf_convex) {
1705         // printf("Found Convex surface, setting logicId to 0");
1706         localSurface.fLogicId = 0;
1707         nsurf_bool_conv++;
1708       } else {
1709         // printf("Surface concave, need to do full boolean check");
1710       }
1711     } // loop over initial surfaces
1712   }
1713   if (fVerbose > 0)
1714     printf("\n Boolean Surface Convexity Check:\n convex boolean surfaces: %li total number of boolean surfaces %li "
1715            "ratio: %f\n",
1716            nsurf_bool_conv, nsurf_bool, double(nsurf_bool_conv) / nsurf_bool);
1717 }
1718 
1719 template <typename Real_t>
1720 void BrepHelper<Real_t>::PrintCandidates(vecgeom::NavigationState const &state)
1721 {
1722   printf("\nCandidate surfaces per state: ");
1723   state.Print();
1724   unsigned short scene_id = 0, newscene_id = 0;
1725   bool is_scene = state.GetSceneId(scene_id, newscene_id);
1726   printf("is_scene=%d  scene_id=%d  newscene_id=%d\n", int(is_scene), scene_id, newscene_id);
1727   auto &cand          = fSurfData->GetCandidates(scene_id, state.GetId());
1728   auto &cand_newscene = fSurfData->GetCandidates(newscene_id, 0);
1729   int ncand           = is_scene ? cand.fNExiting + cand_newscene.fNcand - cand_newscene.fNExiting : cand.fNcand;
1730   printf(" %d candidates: exiting={", ncand);
1731   for (int i = 0; i < cand.fNExiting; ++i)
1732     printf("%d (side %d, ind %d) ", cand.fCandidates[i], int(cand.fSides[i]), cand.fFrameInd[i]);
1733   printf("} entering={");
1734   if (is_scene) {
1735     // Entering scene candidates are the entering newscene candidates
1736     for (int i = cand_newscene.fNExiting; i < cand_newscene.fNcand; ++i)
1737       printf("%d (side %d, ind %d) ", cand_newscene.fCandidates[i], int(cand_newscene.fSides[i]),
1738              cand_newscene.fFrameInd[i]);
1739   } else {
1740     for (int i = cand.fNExiting; i < cand.fNcand; ++i)
1741       printf("%d (side %d, ind %d) ", cand.fCandidates[i], int(cand.fSides[i]), cand.fFrameInd[i]);
1742   }
1743   printf("}\n");
1744 }
1745 
1746 template <typename Real_t>
1747 void BrepHelper<Real_t>::PrintCandidateLists()
1748 {
1749   vecgeom::NavigationState state;
1750 
1751   // recursive geometry visitor lambda printing the candidates lists
1752   // We have no direct access from a state (contiguous) id to the actual state index
1753   typedef std::function<void(vecgeom::VPlacedVolume const *)> func_t;
1754   func_t printCandidates = [&](vecgeom::VPlacedVolume const *pvol) {
1755     state.Push(pvol);
1756     const auto vol = pvol->GetLogicalVolume();
1757     auto daughters = vol->GetDaughters();
1758     int nd         = daughters.size();
1759     PrintCandidates(state);
1760 
1761     // do daughters
1762     for (int id = 0; id < nd; ++id) {
1763       printCandidates(daughters[id]);
1764     }
1765     state.Pop();
1766   };
1767 
1768   PrintCandidates(state); // outside state
1769   printCandidates(vecgeom::GeoManager::Instance().GetWorld());
1770 }
1771 
1772 template <typename Real_t>
1773 template <typename Real_i, typename Transformation_t>
1774 void BrepHelper<Real_t>::PrintFramedSurface(FramedSurface<Real_i, Transformation_t> const &surf)
1775 {
1776   // get frame data
1777   std::stringstream framedata;
1778   framedata << "FS: " << to_cstring(surf.fFrame.type);
1779   switch (surf.fFrame.type) {
1780   case FrameType::kRing: {
1781     auto const &data = fCPUdata.fRingMasks[surf.fFrame.id];
1782     framedata << " { rangeR{" << data.rangeR[0] << ", " << data.rangeR[1] << "}, isFullCirc{"
1783               << (data.isFullCirc ? "true" : "false") << "}, vecSPhi{" << data.vecSPhi << "}, vecEPhi{" << data.vecEPhi
1784               << "}}";
1785   } break;
1786   case FrameType::kZPhi: {
1787     auto const &data = fCPUdata.fZPhiMasks[surf.fFrame.id];
1788     framedata << " { rangeZ{" << data.rangeZ[0] << ", " << data.rangeZ[1] << "}, alpha{"
1789               << vecCore::math::ACos(1. / data.invcalf) * vecgeom::kRadToDeg << "}, vecSPhi{" << data.vecSPhi
1790               << "}, vecEPhi{" << data.vecEPhi << "}, isFullCirc{" << (data.isFullCirc ? "true" : "false") << "}}";
1791 
1792   } break;
1793   case FrameType::kWindow: {
1794     auto const &data = fCPUdata.fWindowMasks[surf.fFrame.id];
1795     framedata << " { rangeU{" << data.rangeU[0] << ", " << data.rangeU[1] << "}, rangeV{" << data.rangeV[0] << ", "
1796               << data.rangeV[1] << "}}";
1797   } break;
1798   // TODO: Support these
1799   case FrameType::kTriangle: {
1800     auto const &data = fCPUdata.fTriangleMasks[surf.fFrame.id];
1801     framedata << " { p{ 0:{" << data.p_[0] << "}, 1:{" << data.p_[1] << "}, 2:{" << data.p_[2] << "}, n0:{"
1802               << data.n_[0] << "}, n1:{" << data.n_[1] << "}, n2:{" << data.n_[2] << "}}";
1803   } break;
1804   case FrameType::kQuadrilateral: {
1805     auto const &data = fCPUdata.fQuadMasks[surf.fFrame.id];
1806     framedata << " { p{ 0:{" << data.p_[0] << "}, 1:{" << data.p_[1] << "}, 2:{" << data.p_[2] << "}, 3:{" << data.p_[3]
1807               << "}, n0:{" << data.n_[0] << "}, n1:{" << data.n_[1] << "}, n2:{" << data.n_[2] << "}, n3:{"
1808               << data.n_[3] << "}}";
1809   } break;
1810   case FrameType::kNoFrame:
1811   case FrameType::kRangeZ:
1812     // return (local[2] > vecgeom::MakeMinusTolerant<true>(u[0]) &&
1813     //        local[2] < vecgeom::MakePlusTolerant<true>(u[1]));
1814   case FrameType::kRangeSph:
1815     // return (rsq > vecgeom::MakeMinusTolerantSquare<true>(u[0]) &&
1816     //        rsq < vecgeom::MakePlusTolerantSquare<true>(u[1]));
1817     break;
1818   default:
1819     // unhandled
1820     framedata << " { no such frame type }";
1821   };
1822 
1823   framedata << "    fTrans{" << surf.fTrans << "} fParent{" << surf.fParent << "} fTraversal{" << surf.fTraversal
1824             << "} fLogicId{" << surf.fLogicId << "} fNeverCheck{" << surf.fNeverCheck << "} ";
1825   if (surf.fSceneCS) framedata << "fSceneCS{" << surf.fSceneCS << "} fSceneCSind{" << surf.fSceneCSind << "} ";
1826   framedata << "fEmbedding{" << to_cstring(surf.fEmbedding) << "} ";
1827   framedata << "fEmbedded{" << to_cstring(surf.fEmbedded) << "} ";
1828   framedata << "fVirtualParent{" << to_cstring(surf.fVirtualParent) << "} ";
1829   if (surf.fFrame.type != FrameType::kNoFrame) framedata << "fSurfIndex{" << surf.fSurfIndex << "} ";
1830   std::cout << framedata.str() << "\n    fState: ";
1831   surf.PrintState();
1832 }
1833 
1834 template <typename Real_t>
1835 void BrepHelper<Real_t>::PrintCommonSurface(int common_id)
1836 {
1837   if (common_id >= fSurfData->fNcommonSurf) {
1838     VECGEOM_LOG(error) << "You are trying to print a non-existent common surface " << common_id;
1839     return;
1840   }
1841   auto round0 = [](Real_t x) { return (std::abs(x) < vecgeom::kToleranceStrict<Real_t>) ? Real_t(0) : x; };
1842   vecgeom::Vector3D<Real_t> normal;
1843   const vecgeom::Vector3D<Real_t> lnorm(0, 0, 1);
1844   auto const &surf = fSurfData->fCommonSurfaces[common_id];
1845   surf.fTrans.InverseTransformDirection(lnorm, normal);
1846   vecgeom::NavigationState default_state(surf.fDefaultState);
1847   int scene_id = surf.GetSceneId();
1848   printf("\n== common surface %d: type: %s, scene %d, default state: ", common_id, to_cstring(surf.fType), scene_id);
1849   default_state.Print();
1850   printf(" transformation: ");
1851   surf.fTrans.Print();
1852   switch (surf.fType) {
1853   case SurfaceType::kPlanar: {
1854     printf("\n   \x1B[34mleft:\x1B[0m %d surfaces, num_parents=%d, normal: (%g, %g, "
1855            "%g)\n",
1856            surf.fLeftSide.fNsurf, surf.fLeftSide.fNumParents, round0(normal[0]), round0(normal[1]), round0(normal[2]));
1857     break;
1858   }
1859   case SurfaceType::kCylindrical: {
1860     printf("\n   \x1B[34mleft\x1B[0m: %d surfaces, num_parents=%d, {radius{%g}}\n", surf.fLeftSide.fNsurf,
1861            surf.fLeftSide.fNumParents, fSurfData->fFramedSurf[surf.fLeftSide.fSurfaces[0]].fSurface.fRadius);
1862     break;
1863   }
1864   case SurfaceType::kConical: {
1865     printf("\n   \x1B[34mleft\x1B[0m: %d surfaces, num_parents=%d, {radius{%g}, slope{%g}}\n", surf.fLeftSide.fNsurf,
1866            surf.fLeftSide.fNumParents, fSurfData->fFramedSurf[surf.fLeftSide.fSurfaces[0]].fSurface.fRadius,
1867            fSurfData->fFramedSurf[surf.fLeftSide.fSurfaces[0]].fSurface.fSlope);
1868     break;
1869   }
1870   case SurfaceType::kElliptical:
1871   case SurfaceType::kSpherical:
1872   case SurfaceType::kTorus:
1873   case SurfaceType::kArb4:
1874   default:
1875     VECGEOM_LOG(error) << "Surface type " << to_cstring(surf.fType) << " not implemented";
1876   }
1877   for (int i = 0; i < surf.fLeftSide.fNsurf; ++i) {
1878     int idglob         = surf.fLeftSide.fSurfaces[i];
1879     auto const &placed = fSurfData->fFramedSurf[idglob];
1880     printf("%d: iframe=%d ", i, idglob);
1881     PrintFramedSurface(placed);
1882   }
1883   if (surf.fRightSide.fNsurf > 0) {
1884     switch (surf.fType) {
1885     case SurfaceType::kPlanar: {
1886       printf("   \x1B[31mright:\x1B[0m %d surfaces, num_parents=%d, normal: (%g, "
1887              "%g, %g)\n",
1888              surf.fRightSide.fNsurf, surf.fRightSide.fNumParents, round0(-normal[0]), round0(-normal[1]),
1889              round0(-normal[2]));
1890       break;
1891     }
1892     case SurfaceType::kCylindrical: {
1893       printf("   \x1B[31mright:\x1B[0m %d surfaces, num_parents=%d}\n", surf.fRightSide.fNsurf,
1894              surf.fRightSide.fNumParents);
1895       break;
1896     }
1897     case SurfaceType::kConical: {
1898       printf("\n   \x1B[31mright\x1B[0m: %d surfaces, num_parents=%d\n", surf.fRightSide.fNsurf,
1899              surf.fRightSide.fNumParents);
1900       break;
1901     }
1902     case SurfaceType::kElliptical:
1903     case SurfaceType::kSpherical:
1904     case SurfaceType::kTorus:
1905     case SurfaceType::kArb4:
1906     default:
1907       VECGEOM_LOG(error) << "Surface type " << to_cstring(surf.fType) << " not implemented";
1908     }
1909   } else {
1910     printf("   \x1B[31mright:\x1B[0m 0 surfaces\n");
1911   }
1912 
1913   for (int i = 0; i < surf.fRightSide.fNsurf; ++i) {
1914     int idglob         = surf.fRightSide.fSurfaces[i];
1915     auto const &placed = fSurfData->fFramedSurf[idglob];
1916     printf("%d: iframe=%d ", i, idglob);
1917     PrintFramedSurface(placed);
1918   }
1919 }
1920 
1921 template <typename Real_t>
1922 void BrepHelper<Real_t>::PrintSurfData()
1923 {
1924   constexpr int megabyte = 1024 * 1024;
1925   float total = 0, size = 0;
1926   auto msg = VECGEOM_LOG(info);
1927   size_t ndivX{0}, ndivY{0}, ndivZ{0}, ndivR{0}, ndivXY{0};
1928   for (auto const &div : fCPUdata.fSideDivisions) {
1929     ndivX += div.fAxis == AxisType::kX;
1930     ndivY += div.fAxis == AxisType::kY;
1931     ndivZ += div.fAxis == AxisType::kZ;
1932     ndivR += div.fAxis == AxisType::kR;
1933     ndivXY += div.fAxis == AxisType::kXY;
1934   }
1935 
1936   msg << "___________________________________________________________________________________\n";
1937   msg << " Surface model info:  " << vecgeom::GeoManager::Instance().GetTotalNodeCount() + 1 << " touchables, "
1938       << fSurfData->fNscenes << " scenes\n";
1939   size = float(fSurfData->fNshells * sizeof(VolumeShell) + fSurfData->fNlocalSurf * sizeof(int)) / megabyte;
1940   total += size;
1941   msg << "    volume shells          = " << fSurfData->fNshells << " [" << size << " MB]\n";
1942   size =
1943       float(fSurfData->fNEnteringSurfaces * 4 * sizeof(int) + fSurfData->fNEnteringSurfaces * sizeof(int)) / megabyte;
1944   total += size;
1945   msg << "    Entering surface list  = " << fSurfData->fNEnteringSurfaces << " [" << size << " MB]\n";
1946   size = float(fSurfData->fNExitingSurfaces * 4 * sizeof(int) + fSurfData->fNExitingSurfaces * sizeof(int)) / megabyte;
1947   total += size;
1948   msg << "    Exiting surface list   = " << fSurfData->fNExitingSurfaces << " [" << size << " MB]\n";
1949   size = float(fSurfData->fNvolTrans * sizeof(Transformation)) / megabyte;
1950   total += size;
1951   msg << "    volume transformations = " << fSurfData->fNvolTrans << " [" << size << " MB]\n";
1952   size = float(fSurfData->fNlocalSurf * sizeof(FramedSurface<Real_t, TransformationMP<Real_t>>)) / megabyte;
1953   total += size;
1954   msg << "    local surfaces         = " << fSurfData->fNlocalSurf << " [" << size << " MB]\n";
1955   size = float(fSurfData->fNglobalSurf * sizeof(FramedSurface<Real_t, TransformationMP<Real_t>>)) / megabyte;
1956   total += size;
1957   msg << "    global surfaces        = " << fSurfData->fNglobalSurf << " [" << size << " MB]\n";
1958   size = float(fSurfData->fNcommonSurf * sizeof(CommonSurface<Real_t>) + fSurfData->fNsides * sizeof(int)) / megabyte;
1959   total += size;
1960   msg << "    common surfaces        = " << fSurfData->fNcommonSurf << " [" << size << " MB]\n";
1961   size = float(fSurfData->fNsideDivisions * sizeof(SideDivision_t) + fSurfData->fNslices * sizeof(SliceCand) +
1962                fSurfData->fNsliceCandidates * sizeof(int)) /
1963          megabyte;
1964   total += size;
1965   msg << "    side divisions         = " << fSurfData->fNsideDivisions << " [" << size << " MB]\n";
1966   msg << "      planar { X=" << ndivX << "  Y=" << ndivY << "  kR=" << ndivR << " XY=" << ndivXY
1967       << " } cylindrical { Z=" << ndivZ << " }\n";
1968   size = float(fSurfData->fSizeCandList * sizeof(int)) / megabyte;
1969   total += size;
1970   msg << "    candidates             = " << fSurfData->fSizeCandList << " [" << size << " MB]\n";
1971   size = float(fSurfData->fNwindows * sizeof(WindowMask_t)) / megabyte;
1972   total += size;
1973   msg << "    window masks           = " << fSurfData->fNwindows << " [" << size << " MB]\n";
1974   size = float(fSurfData->fNrings * sizeof(RingMask_t)) / megabyte;
1975   total += size;
1976   msg << "    ring masks             = " << fSurfData->fNrings << " [" << size << " MB]\n";
1977   size = float(fSurfData->fNzphis * sizeof(ZPhiMask_t)) / megabyte;
1978   total += size;
1979   msg << "    Z/phi masks            = " << fSurfData->fNzphis << " [" << size << " MB]\n";
1980   size = float(fSurfData->fNtriangs * sizeof(TriangleMask_t)) / megabyte;
1981   total += size;
1982   msg << "    triangle masks         = " << fSurfData->fNtriangs << " [" << size << " MB]\n";
1983   size = float(fSurfData->fNquads * sizeof(QuadMask_t)) / megabyte;
1984   total += size;
1985   msg << "    quad masks             = " << fSurfData->fNquads << " [" << size << " MB]\n";
1986   size = float(fSurfData->fNshells * sizeof(int)) / megabyte;
1987   // Add the internal allocation for each BVH
1988   for (int i = 0; i < fSurfData->fNshells; i++) {
1989     size += float(fSurfData->fBVH[fSurfData->fShells[i].fBVH].GetAllocatedSize()) / megabyte;
1990   }
1991   total += size;
1992   msg << "    BVHs                   = " << fSurfData->fNshells << " [" << size << " MB]\n";
1993 
1994   msg << " Total: " << total << "[MB]\n";
1995   msg << "___________________________________________________________________________________\n";
1996 }
1997 
1998 template <typename Real_t>
1999 void BrepHelper<Real_t>::SetNvolumes(int nvolumes)
2000 {
2001   if (fCPUdata.fShells.size() > 0) {
2002     VECGEOM_LOG(warning) << "BrepHelper::SetNvolumes already called for this instance";
2003     return;
2004   }
2005   fCPUdata.fShells.resize(nvolumes);
2006   fCPUdata.fSceneShells.resize(nvolumes);
2007 }
2008 
2009 template <typename Real_t>
2010 void BrepHelper<Real_t>::UpdateMaskData()
2011 {
2012   fSurfData->fNwindows = fCPUdata.fWindowMasks.size();
2013   delete[] fSurfData->fWindowMasks;
2014   fSurfData->fWindowMasks = new WindowMask_t[fCPUdata.fWindowMasks.size()];
2015   for (size_t i = 0; i < fCPUdata.fWindowMasks.size(); ++i)
2016     fSurfData->fWindowMasks[i] = fCPUdata.fWindowMasks[i];
2017 
2018   fSurfData->fNrings = fCPUdata.fRingMasks.size();
2019   delete[] fSurfData->fRingMasks;
2020   fSurfData->fRingMasks = new RingMask_t[fCPUdata.fRingMasks.size()];
2021   for (size_t i = 0; i < fCPUdata.fRingMasks.size(); ++i)
2022     fSurfData->fRingMasks[i] = fCPUdata.fRingMasks[i];
2023 
2024   fSurfData->fNzphis = fCPUdata.fZPhiMasks.size();
2025   delete[] fSurfData->fZPhiMasks;
2026   fSurfData->fZPhiMasks = new ZPhiMask_t[fCPUdata.fZPhiMasks.size()];
2027   for (size_t i = 0; i < fCPUdata.fZPhiMasks.size(); ++i)
2028     fSurfData->fZPhiMasks[i] = fCPUdata.fZPhiMasks[i];
2029 
2030   fSurfData->fNquads = fCPUdata.fQuadMasks.size();
2031   delete[] fSurfData->fQuadMasks;
2032   fSurfData->fQuadMasks = new QuadMask_t[fCPUdata.fQuadMasks.size()];
2033   for (size_t i = 0; i < fCPUdata.fQuadMasks.size(); ++i)
2034     fSurfData->fQuadMasks[i] = fCPUdata.fQuadMasks[i];
2035 
2036   fSurfData->fNtriangs      = fCPUdata.fTriangleMasks.size();
2037   fSurfData->fTriangleMasks = new TriangleMask_t[fCPUdata.fTriangleMasks.size()];
2038   for (size_t i = 0; i < fCPUdata.fTriangleMasks.size(); ++i)
2039     fSurfData->fTriangleMasks[i] = fCPUdata.fTriangleMasks[i];
2040 }
2041 
2042 template <typename Real_t>
2043 void BrepHelper<Real_t>::UpdateSurfData()
2044 {
2045   // Create and copy surface data
2046 
2047   // Volume transformations (used for placed volumes)
2048   fSurfData->fNvolTrans = fCPUdata.fPVolTrans.size();
2049   fSurfData->fPVolTrans = new TransformationMP<Real_t>[fCPUdata.fPVolTrans.size()];
2050   for (size_t i = 0; i < fCPUdata.fPVolTrans.size(); ++i)
2051     fSurfData->fPVolTrans[i] = fCPUdata.fPVolTrans[i];
2052 
2053   // Local surfaces (per volume)
2054   auto numLocalSurf      = fCPUdata.fLocalSurfaces.size();
2055   fSurfData->fNlocalSurf = numLocalSurf;
2056   fSurfData->fLocalSurf  = new FramedSurface<Real_t, TransformationMP<Real_t>>[numLocalSurf];
2057   for (size_t i = 0; i < numLocalSurf; ++i)
2058     fSurfData->fLocalSurf[i] = fCPUdata.fLocalSurfaces[i];
2059 
2060   // Global surfaces (used on common surfaces)
2061   auto numGlobalSurf      = fCPUdata.fFramedSurf.size();
2062   fSurfData->fNglobalSurf = numGlobalSurf;
2063   fSurfData->fFramedSurf  = new FramedSurface<Real_t, vecgeom::Transformation2DMP<Real_t>>[numGlobalSurf];
2064   for (size_t i = 0; i < numGlobalSurf; ++i)
2065     fSurfData->fFramedSurf[i] = FramedSurface<Real_t, vecgeom::Transformation2DMP<Real_t>>(fCPUdata.fFramedSurf[i]);
2066 
2067   // Unplaced surface data
2068   fSurfData->fNellip    = fCPUdata.fEllipData.size();
2069   fSurfData->fEllipData = new EllipData_t[fCPUdata.fEllipData.size()];
2070   for (size_t i = 0; i < fCPUdata.fEllipData.size(); ++i)
2071     fSurfData->fEllipData[i] = fCPUdata.fEllipData[i];
2072 
2073   fSurfData->fNtorus    = fCPUdata.fTorusData.size();
2074   fSurfData->fTorusData = new TorusData_t[fCPUdata.fTorusData.size()];
2075   for (size_t i = 0; i < fCPUdata.fTorusData.size(); ++i)
2076     fSurfData->fTorusData[i] = fCPUdata.fTorusData[i];
2077 
2078   fSurfData->fNarb4    = fCPUdata.fArb4Data.size();
2079   fSurfData->fArb4Data = new Arb4Data_t[fCPUdata.fArb4Data.size()];
2080   for (size_t i = 0; i < fCPUdata.fArb4Data.size(); ++i)
2081     fSurfData->fArb4Data[i] = fCPUdata.fArb4Data[i];
2082 
2083   // Create Masks
2084   UpdateMaskData();
2085 
2086   // Copy common surfaces
2087   size_t size_sides = 0;
2088   for (auto const &surf : fCPUdata.fCommonSurfaces)
2089     size_sides += surf.fLeftSide.fNsurf + surf.fRightSide.fNsurf;
2090   fSurfData->fNsides         = size_sides;
2091   fSurfData->fSides          = new int[size_sides];
2092   int *current_side          = fSurfData->fSides;
2093   fSurfData->fNcommonSurf    = fCPUdata.fCommonSurfaces.size();
2094   fSurfData->fCommonSurfaces = new CommonSurface<Real_t>[fSurfData->fNcommonSurf];
2095   for (size_t i = 0; i < fCPUdata.fCommonSurfaces.size(); ++i) {
2096     // Raw copy of surface (wrong pointers in sides)
2097     fSurfData->fCommonSurfaces[i] = fCPUdata.fCommonSurfaces[i];
2098     // Copy left sides content in buffer
2099     for (auto isurf = 0; isurf < fCPUdata.fCommonSurfaces[i].fLeftSide.fNsurf; ++isurf)
2100       current_side[isurf] = fCPUdata.fCommonSurfaces[i].fLeftSide.fSurfaces[isurf];
2101     // Make left sides arrays point to the buffer
2102     fSurfData->fCommonSurfaces[i].fLeftSide.fSurfaces = current_side;
2103     current_side += fCPUdata.fCommonSurfaces[i].fLeftSide.fNsurf;
2104     // Copy right sides content in buffer
2105     for (auto isurf = 0; isurf < fCPUdata.fCommonSurfaces[i].fRightSide.fNsurf; ++isurf)
2106       current_side[isurf] = fCPUdata.fCommonSurfaces[i].fRightSide.fSurfaces[isurf];
2107     // Make right sides arrays point to the buffer
2108     fSurfData->fCommonSurfaces[i].fRightSide.fSurfaces = current_side;
2109     current_side += fCPUdata.fCommonSurfaces[i].fRightSide.fNsurf;
2110     // Copy parent surface indices
2111     fSurfData->fCommonSurfaces[i].fLeftSide.fNumParents  = fCPUdata.fCommonSurfaces[i].fLeftSide.fNumParents;
2112     fSurfData->fCommonSurfaces[i].fRightSide.fNumParents = fCPUdata.fCommonSurfaces[i].fRightSide.fNumParents;
2113   }
2114 
2115   // Copy scenes
2116   auto nscenes        = fCPUdata.fSceneStartIndex.size();
2117   fSurfData->fNscenes = nscenes;
2118   // Copy start indices and sizes per scene
2119   fSurfData->fSceneStartIndex = new int[nscenes];
2120   fSurfData->fSceneTouchables = new int[nscenes];
2121   for (size_t scene_id = 0; scene_id < nscenes; ++scene_id) {
2122     fSurfData->fSceneStartIndex[scene_id] = fCPUdata.fSceneStartIndex[scene_id];
2123     fSurfData->fSceneTouchables[scene_id] = fCPUdata.fSceneTouchables[scene_id];
2124   }
2125 
2126   // Copy candidates lists, frame indices, and sides
2127   // There is one list of candidates per state
2128   size_t num_states   = fCPUdata.fCandidatesEntering.size();
2129   fSurfData->fNStates = num_states;
2130 
2131   auto size_candidates = 0;
2132   for (auto const &list : fCPUdata.fCandidatesExiting) {
2133     size_candidates += 2 * list.size() + list.size() * sizeof(char) / sizeof(int) + 1;
2134   }
2135   for (auto const &list : fCPUdata.fCandidatesEntering) {
2136     size_candidates += 2 * list.size() + list.size() * sizeof(char) / sizeof(int) + 1;
2137   }
2138   // We may need one additional integer per state
2139   size_candidates += num_states;
2140 
2141   // Stores candidate, frame indices, and sides
2142   fSurfData->fSizeCandList = size_candidates;
2143   fSurfData->fCandList     = new int[size_candidates];
2144   int *current_candidates  = fSurfData->fCandList;
2145 
2146   fSurfData->fCandidates = new Candidates[num_states];
2147 
2148   for (size_t i = 0; i < num_states; ++i) {
2149     // Copy Candidate surface, and Frame indices after
2150     // For each state the memory will contain:
2151     // EnteringSurfaces - ExitingSurfaces - EnteringFrameIdx - ExitingFrameIdx
2152 
2153     size_t offset = 0;
2154 
2155     // Copy Exiting Candidates
2156     auto ncandExiting = fCPUdata.fCandidatesExiting[i].size();
2157     for (size_t icand = 0; icand < ncandExiting; icand++) {
2158       current_candidates[icand] = (fCPUdata.fCandidatesExiting[i])[icand];
2159     }
2160 
2161     offset += ncandExiting;
2162 
2163     // Copy Entering Candidates
2164     auto ncandEntering = fCPUdata.fCandidatesEntering[i].size();
2165     for (size_t icand = 0; icand < ncandEntering; icand++) {
2166       current_candidates[icand + offset] = (fCPUdata.fCandidatesEntering[i])[icand];
2167     }
2168 
2169     offset += ncandEntering;
2170 
2171     // Copy Exiting Frame Indices
2172     for (size_t icand = 0; icand < ncandExiting; icand++) {
2173       current_candidates[icand + offset] = (fCPUdata.fFrameIndExiting[i])[icand];
2174     }
2175 
2176     offset += ncandExiting;
2177 
2178     // Copy Entering Frame Indices
2179     for (size_t icand = 0; icand < ncandEntering; icand++) {
2180       current_candidates[icand + offset] = (fCPUdata.fFrameIndEntering[i])[icand];
2181     }
2182 
2183     offset += ncandEntering;
2184 
2185     // Copy exiting sides
2186     char *current_sides = reinterpret_cast<char *>(current_candidates + offset);
2187     for (size_t icand = 0; icand < ncandExiting; icand++) {
2188       current_sides[icand] = (fCPUdata.fSidesExiting[i])[icand];
2189     }
2190 
2191     // Copy entering sides
2192     current_sides += ncandExiting;
2193     for (size_t icand = 0; icand < ncandEntering; icand++) {
2194       current_sides[icand] = (fCPUdata.fSidesEntering[i])[icand];
2195     }
2196 
2197     // compute number of integers represented bu the sides (stored as chars)
2198     auto ncand  = ncandEntering + ncandExiting;
2199     int add_one = ((ncand * sizeof(char)) % sizeof(int)) > 0 ? 1 : 0;
2200     offset += ncand * sizeof(char) / sizeof(int) + add_one;
2201 
2202     // Store the necessary information in the Candidates Struct
2203     fSurfData->fCandidates[i].fNcand    = ncand;
2204     fSurfData->fCandidates[i].fNExiting = ncandExiting;
2205 
2206     fSurfData->fCandidates[i].fCandidates = current_candidates;
2207     // Move the pointer to the start of the Frame index list
2208     fSurfData->fCandidates[i].fFrameInd = current_candidates + ncand;
2209     // Move the pointer to the start of the sides list
2210     fSurfData->fCandidates[i].fSides = reinterpret_cast<char *>(current_candidates + 2 * ncand);
2211     // Move the pointer to the start of the next Candidate index list
2212     current_candidates += offset;
2213   }
2214 
2215   // Copy volume shells
2216   auto numShells      = fCPUdata.fShells.size();
2217   fSurfData->fNshells = numShells;
2218   fSurfData->fShells  = new VolumeShell[numShells];
2219 
2220   fSurfData->fSurfShellList = new int[numLocalSurf];
2221   int *current_surf         = fSurfData->fSurfShellList;
2222 
2223   size_t sizeLogic = 0;
2224   for (size_t i = 0; i < numShells; ++i) {
2225     sizeLogic += fCPUdata.fShells[i].fLogic.size();
2226   }
2227   fSurfData->fLogicList    = new logic_int[sizeLogic];
2228   fSurfData->fNlogic       = sizeLogic;
2229   logic_int *current_logic = fSurfData->fLogicList;
2230 
2231   // Compute the sum of entering surfaces for all Lvols
2232   for (const auto &shell : fCPUdata.fShells) {
2233     fSurfData->fNExitingSurfaces += shell.fExitingSurfaces.size();
2234     fSurfData->fNEnteringSurfaces += shell.fEnteringSurfaces.size();
2235   }
2236   fSurfData->fNPlacedVolumes = vecgeom::GeoManager::Instance().GetPlacedVolumesCount();
2237 
2238   fSurfData->fShellExitingSurfaceList           = new int[fSurfData->fNExitingSurfaces];
2239   fSurfData->fShellEnteringSurfaceList          = new int[fSurfData->fNEnteringSurfaces];
2240   fSurfData->fShellEnteringSurfacePvolList      = new int[fSurfData->fNEnteringSurfaces];
2241   fSurfData->fShellEnteringSurfacePvolTransList = new int[fSurfData->fNEnteringSurfaces];
2242   fSurfData->fShellEnteringSurfaceLvolIdList    = new int[fSurfData->fNEnteringSurfaces];
2243   fSurfData->fShellDaughterPvolIdList           = new int[fSurfData->fNPlacedVolumes];
2244   fSurfData->fShellDaughterPvolTransList        = new int[fSurfData->fNPlacedVolumes];
2245 
2246   int *currentExitingSurface                = fSurfData->fShellExitingSurfaceList;
2247   int *currentEnteringSurface               = fSurfData->fShellEnteringSurfaceList;
2248   int *currentEnteringSurfacePvol           = fSurfData->fShellEnteringSurfacePvolList;
2249   int *currentShellEnteringSurfacePvolTrans = fSurfData->fShellEnteringSurfacePvolTransList;
2250   int *currentShellEnteringSurfaceLvolId    = fSurfData->fShellEnteringSurfaceLvolIdList;
2251   int *currentShellDaughterPvolId           = fSurfData->fShellDaughterPvolIdList;
2252   int *currentShellDaughterPvolTrans        = fSurfData->fShellDaughterPvolTransList;
2253 
2254   for (size_t i = 0; i < numShells; ++i) {
2255     auto const &surfaces         = fCPUdata.fShells[i].fSurfaces;
2256     auto nsurf                   = surfaces.size();
2257     fSurfData->fShells[i].fNsurf = nsurf;
2258     for (size_t isurf = 0; isurf < nsurf; isurf++)
2259       current_surf[isurf] = surfaces[isurf];
2260     fSurfData->fShells[i].fSurfaces = current_surf;
2261     current_surf += nsurf;
2262     auto nlogic                        = fCPUdata.fShells[i].fLogic.size();
2263     fSurfData->fShells[i].fLogic.size_ = nlogic;
2264     fSurfData->fShells[i].fLogic.data_ = current_logic;
2265     int iitem                          = 0;
2266     for (auto item : fCPUdata.fShells[i].fLogic)
2267       fSurfData->fShells[i].fLogic.data_[iitem++] = item;
2268     current_logic += nlogic;
2269 
2270     auto const &exitingSurfaces           = fCPUdata.fShells[i].fExitingSurfaces;
2271     auto const &enteringSurfaces          = fCPUdata.fShells[i].fEnteringSurfaces;
2272     auto const &enteringSurfacesPvol      = fCPUdata.fShells[i].fEnteringSurfacesPvol;
2273     auto const &enteringSurfacesPvolTrans = fCPUdata.fShells[i].fEnteringSurfacesPvolTrans;
2274     auto const &enteringSurfacesLvolIds   = fCPUdata.fShells[i].fEnteringSurfacesLvolIds;
2275     auto const &daughterPvolIds           = fCPUdata.fShells[i].fDaughterPvolIds;
2276     auto const &daughterPvolTrans         = fCPUdata.fShells[i].fDaughterPvolTrans;
2277     auto nExitingSurf                     = exitingSurfaces.size();
2278     auto nEnteringSurf                    = enteringSurfaces.size();
2279     auto nDaughterPvol                    = daughterPvolIds.size();
2280     // Init number of exiting/entering surfaces in shell
2281     fSurfData->fShells[i].fNExitingSurfaces  = nExitingSurf;
2282     fSurfData->fShells[i].fNEnteringSurfaces = nEnteringSurf;
2283     fSurfData->fShells[i].fNDaughterPvols    = nDaughterPvol;
2284     // Copy indices corresponding to this shell to surfData
2285     for (size_t isurf = 0; isurf < nExitingSurf; isurf++)
2286       currentExitingSurface[isurf] = exitingSurfaces[isurf];
2287     for (size_t isurf = 0; isurf < nEnteringSurf; isurf++) {
2288       currentEnteringSurface[isurf]               = enteringSurfaces[isurf];
2289       currentEnteringSurfacePvol[isurf]           = enteringSurfacesPvol[isurf];
2290       currentShellEnteringSurfacePvolTrans[isurf] = enteringSurfacesPvolTrans[isurf];
2291       currentShellEnteringSurfaceLvolId[isurf]    = enteringSurfacesLvolIds[isurf];
2292     }
2293     // Copy PV global IDs
2294     for (size_t ivol = 0; ivol < nDaughterPvol; ivol++) {
2295       currentShellDaughterPvolId[ivol]    = daughterPvolIds[ivol];
2296       currentShellDaughterPvolTrans[ivol] = daughterPvolTrans[ivol];
2297     }
2298 
2299     // Set the members of this shell to point to the newly copied data
2300     fSurfData->fShells[i].fExitingSurfaces           = currentExitingSurface;
2301     fSurfData->fShells[i].fEnteringSurfaces          = currentEnteringSurface;
2302     fSurfData->fShells[i].fEnteringSurfacesPvol      = currentEnteringSurfacePvol;
2303     fSurfData->fShells[i].fEnteringSurfacesPvolTrans = currentShellEnteringSurfacePvolTrans;
2304     fSurfData->fShells[i].fEnteringSurfacesLvolIds   = currentShellEnteringSurfaceLvolId;
2305     fSurfData->fShells[i].fDaughterPvolIds           = currentShellDaughterPvolId;
2306     fSurfData->fShells[i].fDaughterPvolTrans         = currentShellDaughterPvolTrans;
2307     // Move the pointers in surfData for the next shell
2308     currentExitingSurface += nExitingSurf;
2309     currentEnteringSurface += nEnteringSurf;
2310     currentEnteringSurfacePvol += nEnteringSurf;
2311     currentShellEnteringSurfacePvolTrans += nEnteringSurf;
2312     currentShellEnteringSurfaceLvolId += nEnteringSurf;
2313     currentShellDaughterPvolId += nDaughterPvol;
2314     currentShellDaughterPvolTrans += nDaughterPvol;
2315 
2316     fSurfData->fShells[i].fBVH = fCPUdata.fShells[i].fBVH;
2317   }
2318 }
2319 
2320 // Template instantiations for float and double, allowing to precompile these types
2321 template class BrepHelper<double>;
2322 template class BrepHelper<float>;
2323 
2324 } // namespace vgbrep