Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-12-17 09:22:11

0001 #include "PHG4TpcDirectLaser.h"
0002 
0003 #include <phparameter/PHParameterInterface.h>  // for PHParameterInterface
0004 
0005 #include <g4main/PHG4HitContainer.h>
0006 #include <g4main/PHG4HitDefs.h>  // for get_volume_id
0007 #include <g4main/PHG4Hitv1.h>
0008 #include <g4main/PHG4Particlev3.h>
0009 #include <g4main/PHG4TruthInfoContainer.h>
0010 #include <g4main/PHG4VtxPointv1.h>
0011 
0012 #include <fun4all/Fun4AllReturnCodes.h>
0013 #include <fun4all/SubsysReco.h>  // for SubsysReco
0014 
0015 #include <phool/PHCompositeNode.h>
0016 #include <phool/PHIODataNode.h>
0017 #include <phool/PHNode.h>
0018 #include <phool/PHNodeIterator.h>
0019 #include <phool/PHObject.h>  // for PHObject
0020 #include <phool/getClass.h>
0021 #include <phool/phool.h>  // for PHWHERE
0022 
0023 #include <g4detectors/PHG4TpcGeom.h>
0024 #include <g4detectors/PHG4TpcGeomContainer.h>
0025 
0026 #include <trackbase_historic/SvtxTrackMap.h>
0027 #include <trackbase_historic/SvtxTrackMap_v2.h>
0028 #include <trackbase_historic/SvtxTrack_v2.h>
0029 
0030 #include <TFile.h>
0031 #include <TNtuple.h>
0032 #include <TVector3.h>  // for TVector3, operator*
0033 
0034 #include <gsl/gsl_const_mksa.h>  // for the speed of light
0035 
0036 #include <cassert>
0037 #include <iostream>  // for operator<<, basic_os...
0038 #include <optional>
0039 
0040 namespace
0041 {
0042   using PHG4Particle_t = PHG4Particlev3;
0043   using PHG4VtxPoint_t = PHG4VtxPointv1;
0044   using PHG4Hit_t = PHG4Hitv1;
0045 
0046   // utility
0047   template <class T>
0048   constexpr T square(const T& x)
0049   {
0050     return x * x;
0051   }
0052 
0053   // unique detector id for all direct lasers
0054   const int detId = PHG4HitDefs::get_volume_id("PHG4TpcDirectLaser");
0055 
0056   ///@name units
0057   //@{
0058   constexpr double cm = 1.0;
0059   //@}
0060 
0061   /// speed of light, in cm per ns
0062   constexpr double speed_of_light = GSL_CONST_MKSA_SPEED_OF_LIGHT * 1e-7;
0063 
0064   /// length of generated G4Hits along laser track
0065   constexpr double maxHitLength = 1. * cm;
0066 
0067   // inner and outer radii of field cages/TPC
0068   constexpr double begin_CM = 20. * cm;
0069   constexpr double end_CM = 78. * cm;
0070 
0071   //_____________________________________________________________
0072   std::optional<TVector3> central_membrane_intersection(const TVector3& start, const TVector3& direction, double halfwidth_CM)
0073   {
0074     const double end = start.z() > 0 ? halfwidth_CM : -halfwidth_CM;
0075     const double dist = end - start.z();
0076 
0077     // if line is vertical, it will never intercept the endcap
0078     if (direction.z() == 0)
0079     {
0080       return std::nullopt;
0081     }
0082 
0083     // check that distance and direction have the same sign
0084     if (dist * direction.z() < 0)
0085     {
0086       return std::nullopt;
0087     }
0088 
0089     const double direction_scale = dist / direction.z();
0090     return start + direction * direction_scale;
0091   }
0092 
0093   //_____________________________________________________________
0094   std::optional<TVector3> endcap_intersection(const TVector3& start, const TVector3& direction, double halflength_tpc)
0095   {
0096     const double end = start.z() > 0 ? halflength_tpc : -halflength_tpc;
0097     const double dist = end - start.z();
0098 
0099     // if line is vertical, it will never intercept the endcap
0100     if (direction.z() == 0)
0101     {
0102       return std::nullopt;
0103     }
0104 
0105     // check that distance and direction have the same sign
0106     if (dist * direction.z() < 0)
0107     {
0108       return std::nullopt;
0109     }
0110 
0111     const double direction_scale = dist / direction.z();
0112     return start + direction * direction_scale;
0113   }
0114 
0115   //_____________________________________________________________
0116   std::optional<TVector3> cylinder_line_intersection(const TVector3& s, const TVector3& v, double radius)
0117   {
0118     const double R2 = square(radius);
0119 
0120     // Generalized Parameters for collision with cylinder of radius R:
0121     // from quadratic formula solutions of when a vector intersects a circle:
0122     const double a = square(v.x()) + square(v.y());
0123     const double b = 2 * (v.x() * s.x() + v.y() * s.y());
0124     const double c = square(s.x()) + square(s.y()) - R2;
0125 
0126     const double rootterm = square(b) - 4 * a * c;
0127 
0128     /*
0129      * if a==0 then we are parallel and will have no solutions.
0130      * if the rootterm is negative, we will have no real roots,
0131      * we are outside the cylinder and pointing skew to the cylinder such that we never cross.
0132      */
0133     if (rootterm < 0 || a == 0)
0134     {
0135       return std::nullopt;
0136     }
0137 
0138     // Find the (up to) two points where we collide with the cylinder:
0139     const double sqrtterm = std::sqrt(rootterm);
0140     const double t1 = (-b + sqrtterm) / (2 * a);
0141     const double t2 = (-b - sqrtterm) / (2 * a);
0142 
0143     /*
0144      * if either of the t's are nonzero, we have a collision
0145      * the collision closest to the start (hence with the smallest t that is greater than zero) is the one that happens.
0146      */
0147     const double& min_t = (t2 < t1 && t2 > 0) ? t2 : t1;
0148     return s + v * min_t;
0149   }
0150 
0151   //_____________________________________________________________
0152   std::optional<TVector3> field_cage_intersection(const TVector3& start, const TVector3& direction)
0153   {
0154     auto ofc_strike = cylinder_line_intersection(start, direction, end_CM);
0155     auto ifc_strike = cylinder_line_intersection(start, direction, begin_CM);
0156 
0157     // if either of the two intersection is invalid, return the other
0158     if (!ifc_strike)
0159     {
0160       return ofc_strike;
0161     }
0162     if (!ofc_strike)
0163     {
0164       return ifc_strike;
0165     }
0166 
0167     // both intersection are valid, calculate signed distance to start z
0168     const auto ifc_dist = (ifc_strike->Z() - start.Z()) / direction.Z();
0169     const auto ofc_dist = (ofc_strike->Z() - start.Z()) / direction.Z();
0170 
0171     if (ifc_dist < 0)
0172     {
0173       return (ofc_dist > 0) ? ofc_strike : std::nullopt;
0174     }
0175     if (ofc_dist < 0)
0176     {
0177       return ifc_strike;
0178     }
0179 
0180     return (ifc_dist < ofc_dist) ? ifc_strike : ofc_strike;
0181   }
0182 
0183   /// TVector3 stream
0184   inline std::ostream& operator<<(std::ostream& out, const TVector3& vector)
0185   {
0186     out << "( " << vector.x() << ", " << vector.y() << ", " << vector.z() << ")";
0187     return out;
0188   }
0189 
0190 }  // namespace
0191 
0192 //_____________________________________________________________
0193 PHG4TpcDirectLaser::PHG4TpcDirectLaser(const std::string& name)
0194   : SubsysReco(name)
0195   , PHParameterInterface(name)
0196 {
0197   InitializeParameters();
0198 }
0199 
0200 //_____________________________________________________________
0201 int PHG4TpcDirectLaser::InitRun(PHCompositeNode* topNode)
0202 {
0203   // g4 truth info
0204   m_g4truthinfo = findNode::getClass<PHG4TruthInfoContainer>(topNode, "G4TruthInfo");
0205   if (!m_g4truthinfo)
0206   {
0207     std::cout << "Fun4AllDstPileupMerger::load_nodes - creating node G4TruthInfo" << std::endl;
0208 
0209     PHNodeIterator iter(topNode);
0210     auto* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0211     if (!dstNode)
0212     {
0213       std::cout << PHWHERE << "DST Node missing, aborting." << std::endl;
0214       return Fun4AllReturnCodes::ABORTRUN;
0215     }
0216 
0217     m_g4truthinfo = new PHG4TruthInfoContainer();
0218     dstNode->addNode(new PHIODataNode<PHObject>(m_g4truthinfo, "G4TruthInfo", "PHObject"));
0219   }
0220 
0221   // load and check G4Hit node
0222   hitnodename = "G4HIT_" + detector;
0223   auto* g4hit = findNode::getClass<PHG4HitContainer>(topNode, hitnodename);
0224   if (!g4hit)
0225   {
0226     std::cout << Name() << " Could not locate G4HIT node " << hitnodename << std::endl;
0227     return Fun4AllReturnCodes::ABORTRUN;
0228   }
0229 
0230   // find or create track map
0231   /* it is used to store laser parameters on a per event basis */
0232   m_track_map = findNode::getClass<SvtxTrackMap>(topNode, m_track_map_name);
0233   if (!m_track_map)
0234   {
0235     // find DST node and check
0236     PHNodeIterator iter(topNode);
0237     auto* dstNode = static_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0238     if (!dstNode)
0239     {
0240       std::cout << PHWHERE << "DST Node missing, aborting." << std::endl;
0241       return Fun4AllReturnCodes::ABORTRUN;
0242     }
0243 
0244     // find or create SVTX node
0245     iter = PHNodeIterator(dstNode);
0246     auto* node = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "SVTX"));
0247     if (!node)
0248     {
0249       dstNode->addNode(node = new PHCompositeNode("SVTX"));
0250     }
0251 
0252     // add track node
0253     m_track_map = new SvtxTrackMap_v2;
0254     node->addNode(new PHIODataNode<PHObject>(m_track_map, m_track_map_name, "PHObject"));
0255   }
0256 
0257   // setup parameters
0258   UpdateParametersWithMacro();
0259   electrons_per_cm = get_int_param("electrons_per_cm");
0260   electrons_per_gev = get_double_param("electrons_per_gev");
0261 
0262     PHG4TpcGeomContainer *GeomContainer = findNode::getClass<PHG4TpcGeomContainer>(topNode, "TPCGEOMCONTAINER");
0263    
0264   PHG4TpcGeom *layergeom = GeomContainer->GetLayerCellGeom(20);  // z geometry is the same for all layers
0265   double maxdriftlength = layergeom->get_max_driftlength();
0266   halfwidth_CM = layergeom->get_CM_halfwidth();
0267   halflength_tpc = maxdriftlength + halfwidth_CM;
0268   
0269   // setup lasers
0270   SetupLasers();
0271 
0272   // print configuration
0273   if (m_steppingpattern == true)
0274   {
0275     std::cout << "PHG4TpcDirectLaser::InitRun - m_steppingpattern: " << m_steppingpattern << std::endl;
0276     std::cout << "PHG4TpcDirectLaser::InitRun - nTotalSteps: " << nTotalSteps << std::endl;
0277   }
0278   else
0279   {
0280     std::cout << "PHG4TpcDirectLaser::InitRun - m_autoAdvanceDirectLaser: " << m_autoAdvanceDirectLaser << std::endl;
0281     std::cout << "PHG4TpcDirectLaser::InitRun - phi steps: " << nPhiSteps << " min: " << minPhi << " max: " << maxPhi << std::endl;
0282     std::cout << "PHG4TpcDirectLaser::InitRun - theta steps: " << nThetaSteps << " min: " << minTheta << " max: " << maxTheta << std::endl;
0283     std::cout << "PHG4TpcDirectLaser::InitRun - nTotalSteps: " << nTotalSteps << std::endl;
0284   }
0285   std::cout << "PHG4TpcDirectLaser::InitRun - electrons_per_cm: " << electrons_per_cm << std::endl;
0286   std::cout << "PHG4TpcDirectLaser::InitRun - electrons_per_gev " << electrons_per_gev << std::endl;
0287 
0288   // TFile * infile1 = TFile::Open("theta_phi_laser.root");
0289 
0290   std::string LASER_ANGLES_ROOTFILE = std::string(getenv("CALIBRATIONROOT")) + "/TPC/DirectLaser/theta_phi_laser.root";
0291   TFile* infile1 = TFile::Open(LASER_ANGLES_ROOTFILE.c_str());
0292 
0293   pattern = (TNtuple*) infile1->Get("angles");
0294   pattern->SetBranchAddress("#theta", &theta_p);
0295   pattern->SetBranchAddress("#phi", &phi_p);
0296 
0297   return Fun4AllReturnCodes::EVENT_OK;
0298 }
0299 
0300 //_____________________________________________________________
0301 int PHG4TpcDirectLaser::process_event(PHCompositeNode* topNode)
0302 {
0303   // g4 input event
0304   m_g4truthinfo = findNode::getClass<PHG4TruthInfoContainer>(topNode, "G4TruthInfo");
0305   assert(m_g4truthinfo);
0306 
0307   // load g4hit container
0308   m_g4hitcontainer = findNode::getClass<PHG4HitContainer>(topNode, hitnodename);
0309   assert(m_g4hitcontainer);
0310 
0311   // load track map
0312   m_track_map = findNode::getClass<SvtxTrackMap>(topNode, m_track_map_name);
0313   assert(m_track_map);
0314 
0315   if (m_autoAdvanceDirectLaser || m_steppingpattern)
0316   {
0317     AimToNextPatternStep();
0318   }
0319   //_________________________________________________
0320   else
0321   {
0322     // use arbitrary direction
0323     AimToThetaPhi(arbitrary_theta, arbitrary_phi);
0324   }
0325 
0326   return Fun4AllReturnCodes::EVENT_OK;
0327 }
0328 
0329 //_____________________________________________________________
0330 void PHG4TpcDirectLaser::SetDefaultParameters()
0331 {
0332   // same gas parameters as in PHG4TpcElectronDrift::SetDefaultParameters
0333 
0334   // Data on gasses @20 C and 760 Torr from the following source:
0335   // http://www.slac.stanford.edu/pubs/icfa/summer98/paper3/paper3.pdf
0336   // diffusion and drift velocity for 400kV for NeCF4 50/50 from calculations:
0337   // http://skipper.physics.sunysb.edu/~prakhar/tpc/HTML_Gases/split.html
0338   static constexpr double Ne_dEdx = 1.56;    // keV/cm
0339   static constexpr double CF4_dEdx = 7.00;   // keV/cm
0340   static constexpr double Ne_NTotal = 43;    // Number/cm
0341   static constexpr double CF4_NTotal = 100;  // Number/cm
0342   static constexpr double Tpc_NTot = 0.5 * Ne_NTotal + 0.5 * CF4_NTotal;
0343   static constexpr double Tpc_dEdx = 0.5 * Ne_dEdx + 0.5 * CF4_dEdx;
0344   static constexpr double Tpc_ElectronsPerKeV = Tpc_NTot / Tpc_dEdx;
0345 
0346   // number of electrons per deposited GeV in TPC gas
0347   set_default_double_param("electrons_per_gev", Tpc_ElectronsPerKeV * 1e6);
0348 
0349   //  set_default_double_param("tpc_half_length", 102.325);
0350   //  set_default_double_param("CM_halfwidth", 0.28);
0351 
0352   // number of electrons deposited by laser per cm
0353   set_default_int_param("electrons_per_cm", 72);
0354 }
0355 
0356 //_____________________________________________________________
0357 void PHG4TpcDirectLaser::SetPhiStepping(int n, double min, double max)
0358 {
0359   if (n < 0 || max < min)
0360   {
0361     std::cout << PHWHERE << " - invalid" << std::endl;
0362     return;
0363   }
0364   nPhiSteps = n;
0365   minPhi = min;
0366   maxPhi = max;
0367   nTotalSteps = nThetaSteps * nPhiSteps;
0368   return;
0369 }
0370 //_____________________________________________________________
0371 void PHG4TpcDirectLaser::SetThetaStepping(int n, double min, double max)
0372 {
0373   if (n < 0 || max < min)
0374   {
0375     std::cout << PHWHERE << " - invalid" << std::endl;
0376     return;
0377   }
0378   nThetaSteps = n;
0379   minTheta = min;
0380   maxTheta = max;
0381   nTotalSteps = nThetaSteps * nPhiSteps;
0382 
0383   return;
0384 }
0385 
0386 //_____________________________________________________________
0387 void PHG4TpcDirectLaser::SetFileStepping(int n)
0388 {
0389   if (n < 0 || n > 13802)  // 13802 = hard coded number of tuple entries
0390   {
0391     std::cout << PHWHERE << " - invalid" << std::endl;
0392     return;
0393   }
0394   nTotalSteps = n;
0395 
0396   return;
0397 }
0398 
0399 //_____________________________________________________________
0400 
0401 void PHG4TpcDirectLaser::SetupLasers()
0402 {
0403   // clear previous lasers
0404   m_lasers.clear();
0405 
0406   // position of first laser at positive z
0407   const TVector3 position_base(60 * cm, 0., halflength_tpc);
0408 
0409   // add lasers
0410   for (int i = 0; i < 8; ++i)
0411   {
0412     Laser laser;
0413 
0414     // set laser direction
0415     /*
0416      * first four lasers are on positive z readout plane, and shoot towards negative z
0417      * next four lasers are on negative z readout plane and shoot towards positive z
0418      */
0419     laser.m_position = position_base;
0420     if (i < 4)
0421     {
0422       laser.m_position.SetZ(position_base.z());
0423       laser.m_direction = -1;
0424       laser.m_phi = M_PI / 2 * i - (15 * M_PI / 180);  // additional offset of 15 deg.
0425     }
0426     else
0427     {
0428       laser.m_position.SetZ(-position_base.z());
0429       laser.m_direction = 1;
0430       laser.m_phi = M_PI / 2 * i + (15 * M_PI / 180);  // additional offset of 15 deg.
0431     }
0432 
0433     // rotate around z
0434     laser.m_position.RotateZ(laser.m_phi);
0435 
0436     // append
0437     m_lasers.push_back(laser);  // All lasers
0438     //  if(i==0) m_lasers.push_back(laser);//Only laser 1
0439     //  if(i==3) m_lasers.push_back(laser);// Laser 4
0440     // if(i<4) m_lasers.push_back(laser);//Lasers 1, 2, 3, 4
0441   }
0442 }
0443 
0444 //_____________________________________________________________
0445 void PHG4TpcDirectLaser::AimToNextPatternStep()
0446 {
0447   if (nTotalSteps >= 1)
0448   {
0449     if (m_steppingpattern)
0450     {
0451       AimToPatternStep_File(currentPatternStep);
0452       ++currentPatternStep;
0453     }
0454     else
0455     {
0456       AimToPatternStep(currentPatternStep);
0457       ++currentPatternStep;
0458     }
0459   }
0460 }
0461 
0462 //_____________________________________________________________
0463 void PHG4TpcDirectLaser::AimToThetaPhi(double theta, double phi)
0464 {
0465   if (Verbosity())
0466   {
0467     std::cout << "PHG4TpcDirectLaser::AimToThetaPhi - theta: " << theta << " phi: " << phi << std::endl;
0468   }
0469 
0470   // all lasers
0471   for (const auto& laser : m_lasers)
0472   {
0473     AppendLaserTrack(theta, phi, laser);
0474   }
0475 }
0476 
0477 //_____________________________________________________________
0478 void PHG4TpcDirectLaser::AimToPatternStep(int n)
0479 {
0480   // trim against overflows
0481   n = n % nTotalSteps;
0482 
0483   if (Verbosity())
0484   {
0485     std::cout << "PHG4TpcDirectLaser::AimToPatternStep - step: " << n << "/" << nTotalSteps << std::endl;
0486   }
0487 
0488   // store as current pattern
0489   currentPatternStep = n;
0490 
0491   // calculate theta
0492   const int thetaStep = n / nPhiSteps;
0493   const double theta = minTheta + thetaStep * (maxTheta - minTheta) / nThetaSteps;
0494 
0495   // calculate phi
0496   const int phiStep = n % nPhiSteps;
0497   const double phi = minPhi + phiStep * (maxPhi - minPhi) / nPhiSteps;
0498 
0499   // generate laser tracks
0500   AimToThetaPhi(theta, phi);
0501 
0502   return;
0503 }
0504 
0505 //_____________________________________________________________
0506 
0507 void PHG4TpcDirectLaser::AimToPatternStep_File(int n)
0508 {
0509   // trim against overflows
0510   n = n % nTotalSteps;
0511 
0512   if (Verbosity())
0513   {
0514     std::cout << "PHG4TpcDirectLaser::AimToPatternStep_File - step: " << n << "/" << nTotalSteps << std::endl;
0515   }
0516 
0517   // store as current pattern
0518   currentPatternStep = n;
0519 
0520   pattern->GetEntry(n);
0521 
0522   // calculate theta
0523   std::cout << "From file, current entry = " << n << " Theta: " << theta_p << " Phi: " << phi_p << std::endl;
0524 
0525   const double theta = theta_p * M_PI / 180.;
0526 
0527   // calculate phi
0528   const double phi = phi_p * M_PI / 180.;
0529 
0530   // generate laser tracks
0531   AimToThetaPhi(theta, phi);
0532 
0533   return;
0534 }
0535 
0536 //_____________________________________________________________
0537 
0538 void PHG4TpcDirectLaser::AppendLaserTrack(double theta, double phi, const PHG4TpcDirectLaser::Laser& laser)
0539 {
0540   if (!m_g4hitcontainer)
0541   {
0542     std::cout << PHWHERE << "invalid g4hit container. aborting" << std::endl;
0543     return;
0544   }
0545 
0546   // store laser position
0547   const auto& pos = laser.m_position;
0548 
0549   // define track direction
0550   const auto& direction = laser.m_direction;
0551   TVector3 dir(0, 0, direction);
0552 
0553   // adjust direction
0554   dir.RotateY(theta * direction);
0555 
0556   if (laser.m_direction == -1)
0557   {
0558     dir.RotateZ(phi);  // if +z facing -z
0559   }
0560   else
0561   {
0562     dir.RotateZ(-phi);  // if -z facting +z
0563   }
0564 
0565   // also rotate by laser azimuth
0566   dir.RotateZ(laser.m_phi);
0567 
0568   // print
0569   if (Verbosity())
0570   {
0571     std::cout << "PHG4TpcDirectLaser::AppendLaserTrack - position: " << pos << " direction: " << dir << std::endl;
0572   }
0573 
0574   // dummy momentum
0575   static constexpr double total_momentum = 1;
0576 
0577   // mc track id
0578   int trackid = -1;
0579 
0580   // create truth vertex and particle
0581   if (m_g4truthinfo)
0582   {
0583     // add vertex
0584     const auto vtxid = m_g4truthinfo->maxvtxindex() + 1;
0585     auto* const vertex = new PHG4VtxPoint_t(pos.x(), pos.y(), pos.z(), 0, vtxid);
0586     m_g4truthinfo->AddVertex(vtxid, vertex);
0587 
0588     // increment track id
0589     trackid = m_g4truthinfo->maxtrkindex() + 1;
0590 
0591     // create new g4particle
0592     auto* particle = new PHG4Particle_t();
0593     particle->set_track_id(trackid);
0594     particle->set_vtx_id(vtxid);
0595     particle->set_parent_id(0);
0596     particle->set_primary_id(trackid);
0597     particle->set_px(total_momentum * dir.x());
0598     particle->set_py(total_momentum * dir.y());
0599     particle->set_pz(total_momentum * dir.z());
0600 
0601     m_g4truthinfo->AddParticle(trackid, particle);
0602   }
0603 
0604   // store in SvtxTrack map
0605   if (m_track_map)
0606   {
0607     SvtxTrack_v2 track;
0608     track.set_x(pos.x());
0609     track.set_y(pos.y());
0610     track.set_z(pos.z());
0611 
0612     // total momentum is irrelevant. What matters is the direction
0613     track.set_px(total_momentum * dir.x());
0614     track.set_py(total_momentum * dir.y());
0615     track.set_pz(total_momentum * dir.z());
0616 
0617     // insert in map
0618     m_track_map->insert(&track);
0619 
0620     if (Verbosity())
0621     {
0622       std::cout << "PHG4TpcDirectLaser::AppendLaserTrack - position: " << pos << " direction: " << dir << std::endl;
0623     }
0624   }
0625 
0626   // find collision point
0627   /*
0628    * intersection to either central membrane or endcaps
0629    * if the position along beam and laser direction have the same sign, it will intercept the endcap
0630    * otherwise will intercept the central membrane
0631    */
0632   const auto plane_strike = (pos.z() * dir.z() > 0) ? endcap_intersection(pos, dir,halflength_tpc) : central_membrane_intersection(pos, dir, halfwidth_CM);
0633 
0634   // field cage intersection
0635   const auto fc_strike = field_cage_intersection(pos, dir);
0636 
0637   // if none of the strikes is valid, there is no valid information found.
0638   if (!(plane_strike || fc_strike))
0639   {
0640     return;
0641   }
0642 
0643   // decide relevant end of laser
0644   /* chose field cage intersection if valid, and if either plane intersection is invalid or happens on a larger z along the laser direction) */
0645   const TVector3& strike = (fc_strike && (!plane_strike || fc_strike->z() / dir.z() < plane_strike->z() / dir.z())) ? *fc_strike : *plane_strike;
0646 
0647   // find length
0648   TVector3 delta = (strike - pos);
0649   double fullLength = delta.Mag();
0650   int nHitSteps = fullLength / maxHitLength + 1;
0651 
0652   TVector3 start = pos;
0653   TVector3 end = start;
0654   TVector3 step = dir * (maxHitLength / (dir.Mag()));
0655   double stepLength = 0;
0656 
0657   if (Verbosity())
0658   {
0659     std::cout << "PHG4TpcDirectLaser::AppendLaserTrack -"
0660               << " fullLength: " << fullLength
0661               << " nHitSteps: " << nHitSteps
0662               << std::endl;
0663   }
0664 
0665   for (int i = 0; i < nHitSteps; i++)
0666   {
0667     start = end;  // new starting point is the previous ending point.
0668     if (i + 1 == nHitSteps)
0669     {
0670       // last step is the remainder size
0671       end = strike;
0672       delta = start - end;
0673       stepLength = delta.Mag();
0674     }
0675     else
0676     {
0677       // all other steps are uniform length
0678       end = start + step;
0679       stepLength = step.Mag();
0680     }
0681 
0682     // from phg4tpcsteppingaction.cc
0683     auto* hit = new PHG4Hit_t;
0684     hit->set_trkid(trackid);
0685     hit->set_layer(99);
0686 
0687     // here we set the entrance values in cm
0688     hit->set_x(0, start.X() / cm);
0689     hit->set_y(0, start.Y() / cm);
0690     hit->set_z(0, start.Z() / cm);
0691     hit->set_t(0, (start - pos).Mag() / speed_of_light);
0692 
0693     hit->set_x(1, end.X() / cm);
0694     hit->set_y(1, end.Y() / cm);
0695     hit->set_z(1, end.Z() / cm);
0696     hit->set_t(1, (end - pos).Mag() / speed_of_light);
0697 
0698     // momentum
0699     hit->set_px(0, dir.X());  // GeV
0700     hit->set_py(0, dir.Y());
0701     hit->set_pz(0, dir.Z());
0702 
0703     hit->set_px(1, dir.X());
0704     hit->set_py(1, dir.Y());
0705     hit->set_pz(1, dir.Z());
0706 
0707     const double totalE = electrons_per_cm * stepLength / electrons_per_gev;
0708 
0709     hit->set_eion(totalE);
0710     hit->set_edep(totalE);
0711     m_g4hitcontainer->AddHit(detId, hit);
0712   }
0713 
0714   return;
0715 }