Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:48:56

0001 #include <iostream>
0002 #include <iomanip>
0003 #include <array>
0004 #include <vector>
0005 #include <set>
0006 #include <algorithm>
0007 #include <cstring>
0008 #include <csignal>
0009 #include <cstdlib>
0010 
0011 #include <glm/glm.hpp>
0012 #include <glm/gtx/string_cast.hpp>
0013 #include <glm/gtc/type_ptr.hpp>
0014 
0015 #include "sstr.h"
0016 #include "ssys.h"
0017 #include "sproc.h"
0018 #include "SProf.hh"
0019 
0020 #include "smeta.h"
0021 #include "SSim.hh"
0022 #include "SStr.hh"
0023 
0024 // TODO: migrate to spath.h
0025 #include "SPath.hh"
0026 
0027 #include "s_time.h"
0028 #include "SBitSet.h"
0029 
0030 #include "SEventConfig.hh"
0031 #include "SGeoConfig.hh"
0032 #include "NP.hh"
0033 
0034 #include "SEvt.hh"
0035 #include "SSim.hh"
0036 #include "SLOG.hh"
0037 
0038 #include "scuda.h"
0039 #include "sqat4.h"
0040 #include "sframe.h"
0041 #include "SLabel.h"
0042 #include "SScene.h"
0043 
0044 
0045 #include "OpticksCSG.h"
0046 #include "CSGSolid.h"
0047 
0048 #include "CU.h"
0049 
0050 #include "CSGFoundry.h"
0051 #include "SName.h"
0052 #include "CSGTarget.h"
0053 #include "CSGMaker.h"
0054 #include "CSGImport.h"
0055 #include "CSGCopy.h"
0056 
0057 const unsigned CSGFoundry::IMAX = 50000 ;
0058 
0059 const plog::Severity CSGFoundry::LEVEL = SLOG::EnvLevel("CSGFoundry", "DEBUG" );
0060 const int CSGFoundry::VERBOSE = ssys::getenvint("VERBOSE", 0);
0061 
0062 std::string CSGFoundry::descComp() const
0063 {
0064     std::stringstream ss ;
0065     ss << "CSGFoundry::descComp"
0066        << " sim " << ( sim ? sim->desc() : "" )
0067        ;
0068     std::string s = ss.str();
0069     return s ;
0070 }
0071 
0072 
0073 void CSGFoundry::setPrimBoundary(unsigned primIdx, const char* bname )
0074 {
0075     assert( sim );
0076     int bidx = sim->getBndIndex(bname);
0077     assert( bidx > -1 );
0078     setPrimBoundary(primIdx, bidx);
0079 }
0080 
0081 CSGFoundry* CSGFoundry::INSTANCE = nullptr ;
0082 CSGFoundry* CSGFoundry::Get(){ return INSTANCE ; }  // HMM SGeo base struct already has INSTANCE
0083 
0084 
0085 /**
0086 CSGFoundry::CSGFoundry
0087 ------------------------
0088 
0089 HMM: the dependency between CSGFoundry and SSim is a bit mixed up
0090 because of the two possibilities:
0091 
0092 1. "Import" : create CSGFoundry from SSim/stree using CSGImport
0093 2. "Load"   : load previously created and persisted CSGFoundry + SSim from file system
0094 
0095 sim(SSim) used to be a passive passenger of CSGFoundry but now that CSGFoundry
0096 can be CSGImported from SSim it is no longer so passive.
0097 
0098 **/
0099 
0100 
0101 CSGFoundry::CSGFoundry()
0102     :
0103     d_prim(nullptr),
0104     d_node(nullptr),
0105     d_plan(nullptr),
0106     d_itra(nullptr),
0107     sim(SSim::Get()),
0108     import(new CSGImport(this)),
0109     id(new SName(meshname)),   // SName takes a reference of the meshname vector of strings
0110     target(new CSGTarget(this)),
0111     maker(new CSGMaker(this)),
0112     deepcopy_everynode_transform(true),
0113     last_added_solid(nullptr),
0114     last_added_prim(nullptr),
0115     mtime(s_time::EpochSeconds()),
0116     meta(),
0117     fold(nullptr),
0118     cfbase(nullptr),
0119     geom(nullptr),
0120     loaddir(nullptr),
0121     origin(nullptr),
0122     elv(nullptr),
0123     save_opt(ssys::getenvvar(SAVE_OPT))
0124 {
0125     LOG_IF(fatal, sim == nullptr) << "must SSim::Create before CSGFoundry::CSGFoundry " ;
0126     assert(sim);
0127 
0128     init();
0129     INSTANCE = this ;
0130 }
0131 
0132 /**
0133 CSGFoundry::init
0134 -----------------
0135 
0136 Without sufficient reserved the vectors may reallocate on any push_back invalidating prior pointers.
0137 Yes, but generally indices are used rather than pointers to avoid this kinda issue.
0138 
0139 **/
0140 void CSGFoundry::init()
0141 {
0142     solid.reserve(IMAX);
0143     prim.reserve(IMAX);
0144     node.reserve(IMAX);
0145     plan.reserve(IMAX);
0146     tran.reserve(IMAX);
0147     itra.reserve(IMAX);
0148     inst.reserve(IMAX); // inst added July 2022
0149 
0150     smeta::Collect(meta, "CSGFoundry::init");
0151 }
0152 
0153 
0154 
0155 std::string CSGFoundry::brief() const
0156 {
0157     std::stringstream ss ;
0158     ss << "CSGFoundry::brief " << ( loaddir ? loaddir : "-" ) ;
0159     return ss.str();
0160 }
0161 std::string CSGFoundry::desc() const
0162 {
0163     std::stringstream ss ;
0164     ss << "CSGFoundry "
0165        << " num_total " << getNumSolidTotal()
0166        << " num_solid " << solid.size()
0167        << " num_prim " << prim.size()
0168        << " num_node " << node.size()
0169        << " num_plan " << plan.size()
0170        << " num_tran " << tran.size()
0171        << " num_itra " << itra.size()
0172        << " num_inst " << inst.size()
0173        << " gas " << gas.size()
0174        /*
0175        << " ins " << ins.size()
0176        << " sensor_identifier " << sensor_identifier.size()
0177        << " sensor_index " << sensor_index.size()
0178        */
0179        << " meshname " << meshname.size()
0180        << " mmlabel " << mmlabel.size()
0181        << " mtime " << mtime
0182        << " mtimestamp " << s_time::Format(mtime)
0183        << " sim " << ( sim ? "Y" : "N" )
0184        ;
0185     return ss.str();
0186 }
0187 
0188 
0189 
0190 
0191 
0192 std::string CSGFoundry::descSolid() const
0193 {
0194     unsigned num_total = getNumSolidTotal();
0195     unsigned num_standard = getNumSolid(STANDARD_SOLID);
0196     unsigned num_oneprim  = getNumSolid(ONE_PRIM_SOLID);
0197     unsigned num_onenode  = getNumSolid(ONE_NODE_SOLID);
0198     unsigned num_deepcopy = getNumSolid(DEEP_COPY_SOLID);
0199     unsigned num_kludgebbox = getNumSolid(KLUDGE_BBOX_SOLID);
0200 
0201     std::stringstream ss ;
0202     ss << "CSGFoundry "
0203        << " total solids " << num_total
0204        << " STANDARD " << num_standard
0205        << " ONE_PRIM " << num_oneprim
0206        << " ONE_NODE " << num_onenode
0207        << " DEEP_COPY " << num_deepcopy
0208        << " KLUDGE_BBOX " << num_kludgebbox
0209        ;
0210     return ss.str();
0211 }
0212 
0213 
0214 std::string CSGFoundry::descMeshName() const
0215 {
0216     std::stringstream ss ;
0217 
0218     ss << "CSGFoundry::descMeshName"
0219        << " meshname.size " << meshname.size()
0220        << std::endl ;
0221     for(unsigned i=0 ; i < meshname.size() ; i++)
0222         ss << std::setw(5) << i << " : " << meshname[i] << std::endl ;
0223 
0224     std::string s = ss.str();
0225     return s ;
0226 }
0227 
0228 
0229 unsigned CSGFoundry::getNumMeshes() const
0230 {
0231     return meshname.size() ;
0232 }
0233 unsigned CSGFoundry::getNumMeshName() const
0234 {
0235     return meshname.size() ;
0236 }
0237 
0238 
0239 unsigned CSGFoundry::getNumSolidLabel() const
0240 {
0241     return mmlabel.size() ;
0242 }
0243 
0244 
0245 int CSGFoundry::findSolidWithLabel(const char* q_mml) const
0246 {
0247     return SLabel::FindIdxWithLabel(mmlabel, q_mml) ;
0248 }
0249 
0250 
0251 /**
0252 CSGFoundry::isSolidTrimesh_posthoc_kludge
0253 ------------------------------------------
0254 
0255 NB this was used for post-hoc triangulation of a compound solid
0256 prior to implementation of more flexible forced triangulation at stree.h
0257 level, see :doc:`/notes/issues/flexible_forced_triangulation`
0258 
0259 This is used from CSGOptiX/SBT.cc::
0260 
0261     SBT::createGAS
0262     SBT::_getOffset
0263     SBT::getTotalRec
0264     SBT::descGAS
0265     SBT::createHitgroup
0266 
0267 
0268 The effect is to configure the build of the OptiX geometry
0269 to use triangulated geometry for some compound solids (1:1 with OptiX GAS).
0270 
0271 
0272 Normally returns false indicating to use analytic solid setup,
0273 can arrange to return true for some CSGSolid using envvar
0274 with comma delimited mmlabel indicating to use approximate
0275 triangulated geometry for those solids::
0276 
0277    export OPTICKS_SOLID_TRIMESH=1:sStrutBallhead,1:base_steel
0278 
0279 **/
0280 bool CSGFoundry::isSolidTrimesh_posthoc_kludge(int gas_idx) const
0281 {
0282     const char* ls = SGeoConfig::SolidTrimesh() ;
0283     if(ls == nullptr) return false ;
0284     return SLabel::IsIdxLabelListed( mmlabel, gas_idx, ls, ',' );
0285 }
0286 
0287 /**
0288 CSGFoundry::isSolidTrimesh
0289 ---------------------------
0290 
0291 Returns true when getSolidIntent yields 'T'
0292 
0293 **/
0294 
0295 
0296 bool CSGFoundry::isSolidTrimesh(int gas_idx) const
0297 {
0298     char intent = getSolidIntent(gas_idx);
0299     bool trimesh = intent == 'T' ;
0300     assert( intent == 'R' || intent == 'F' || intent == 'T' || intent == '\0'  );
0301     return trimesh ;
0302 }
0303 
0304 
0305 
0306 
0307 /**
0308 CSGFoundry::CopyNames
0309 -----------------------
0310 
0311 Note that there is no accounting for selections to changing the used
0312 meshnames as the LV are regarded as fixed external things no matter
0313 what selection is applied.
0314 
0315 **/
0316 
0317 void CSGFoundry::CopyNames( CSGFoundry* dst, const CSGFoundry* src ) // static
0318 {
0319     CopyMeshName( dst, src );
0320     dst->mtime = src->mtime ;
0321 }
0322 
0323 void CSGFoundry::CopyMeshName( CSGFoundry* dst, const CSGFoundry* src ) // static
0324 {
0325     assert( dst->meshname.size() == 0);
0326     src->getMeshName(dst->meshname);
0327     assert( src->meshname.size() == dst->meshname.size() );
0328 }
0329 
0330 void CSGFoundry::getMeshName( std::vector<std::string>& mname ) const
0331 {
0332     for(unsigned i=0 ; i < meshname.size() ; i++)
0333     {
0334         const std::string& mn = meshname[i];
0335         mname.push_back(mn);
0336     }
0337 }
0338 
0339 
0340 /**
0341 CSGFoundry::getPrimName
0342 ------------------------
0343 
0344 For each prim use meshIdx to lookup the MeshName, which is the "LV" solid name.
0345 
0346 After CSGFoundry.py:make_primIdx_meshname_dict
0347 see notes/issues/cxs_2d_plotting_labels_suggest_meshname_order_inconsistency.rst
0348 
0349 **/
0350 
0351 void CSGFoundry::getPrimName( std::vector<std::string>& pname ) const
0352 {
0353     unsigned num_prim = prim.size();
0354     for(unsigned i=0 ; i < num_prim ; i++)
0355     {
0356         const CSGPrim& pr = prim[i] ;
0357         unsigned midx = num_prim == 1 ? 0 : pr.meshIdx();  // kludge avoid out-of-range for single prim CSGFoundry
0358 
0359         if(midx == UNDEFINED)
0360         {
0361             pname.push_back("err-midx-undefined");   // avoid FAIL  with CSGMakerTest
0362         }
0363         else
0364         {
0365             const char* mname = getMeshName(midx);
0366             LOG(debug) << " primIdx " << std::setw(4) << i << " midx " << midx << " mname " << mname  ;
0367             pname.push_back(mname);
0368         }
0369     }
0370 }
0371 
0372 const char* CSGFoundry::getMeshName(unsigned midx) const
0373 {
0374     bool in_range = midx < meshname.size() ;
0375 
0376     LOG_IF(fatal, !in_range) << " not in range midx " << midx << " meshname.size()  " << meshname.size()  ;
0377     assert(in_range);
0378 
0379     return meshname[midx].c_str() ;
0380 }
0381 
0382 
0383 
0384 
0385 
0386 
0387 /**
0388 CSGFoundry::findMeshIndex
0389 --------------------------
0390 
0391 SName::findIndex uses "name starts with query string" matching
0392 so names like HamamatsuR12860sMask_virtual0x5f50520
0393 can be matched without the pointer suffix.
0394 
0395 HMM: but there are duplicate prefixes, so this aint a good approach.
0396 It causes -1 for midx::
0397 
0398     CSGFoundry::descELV elv.num_bits 139 num_include 139 num_exclude 0
0399     INCLUDE:139
0400 
0401     p:  0:midx:  0:mn:sTopRock_domeAir
0402     p:  1:midx: -1:mn:sTopRock_dome
0403     p:  2:midx:  2:mn:sDomeRockBox
0404     p:  3:midx:  3:mn:PoolCoversub
0405     p:  4:midx:  4:mn:Upper_LS_tube
0406     p:  5:midx:  5:mn:Upper_Steel_tube
0407 
0408 TODO: instead match against names with 0x suffix removed
0409 
0410 **/
0411 
0412 int CSGFoundry::findMeshIndex(const char* qname) const
0413 {
0414     unsigned count = 0 ;
0415     int max_count = 1 ;
0416     int midx = id->findIndex(qname, count, max_count);
0417     return midx ;
0418 }
0419 
0420 /**
0421 CSGFoundry::getMeshIndexWithName
0422 ----------------------------------
0423 
0424 
0425 **/
0426 
0427 int CSGFoundry::getMeshIndexWithName(const char* qname, bool startswith) const
0428 {
0429     return id->findIndexWithName(qname, startswith) ;
0430 }
0431 
0432 
0433 int CSGFoundry::lookup_mtline(int mtindex) const
0434 {
0435     assert(sim);
0436     return sim->lookup_mtline(mtindex) ;
0437 }
0438 std::string CSGFoundry::desc_mt() const
0439 {
0440     assert(sim);
0441     return sim->desc_mt() ;
0442 }
0443 
0444 
0445 /**
0446 CSGFoundry::getTree : Full analytic CSG geometry info
0447 -------------------------------------------------------
0448 **/
0449 stree* CSGFoundry::getTree() const
0450 {
0451     return sim ? sim->tree : nullptr ;
0452 }
0453 
0454 
0455 /**
0456 CSGFoundry::getScene : Full triangulated geometry info
0457 --------------------------------------------------------
0458 **/
0459 SScene* CSGFoundry::getScene() const
0460 {
0461     return sim ? sim->get_scene() : nullptr ;
0462 }
0463 
0464 void CSGFoundry::setOverrideScene(SScene* _scene)
0465 {
0466     assert( sim);
0467     const_cast<SSim*>(sim)->set_override_scene(_scene);
0468 }
0469 
0470 
0471 const std::string CSGFoundry::descELV2(const SBitSet* elv) const
0472 {
0473     unsigned num_bits = elv->num_bits ;
0474     unsigned num_name = id->getNumName() ;
0475     assert( num_bits == num_name );
0476 
0477     std::stringstream ss ;
0478     ss << "CSGFoundry::descELV2"
0479        << " elv.num_bits " << num_bits
0480        << " id.getNumName " << num_name
0481        << std::endl
0482        ;
0483 
0484     for(int p=0 ; p < 2 ; p++)
0485     {
0486         for(unsigned i=0 ; i < num_bits ; i++)
0487         {
0488             bool is_set = elv->is_set(i);
0489             const char* n = id->getName(i);
0490             if( is_set == bool(p) )
0491             {
0492                 ss << std::setw(3) << i << " "
0493                    << ( is_set ? "Y" : "N" )
0494                    << " [" << ( n ? n : "-" ) << "] "
0495                    << std::endl
0496                    ;
0497             }
0498         }
0499     }
0500     std::string str = ss.str();
0501     return str ;
0502 }
0503 
0504 
0505 
0506 /**
0507 CSGFoundry::descELV
0508 ----------------------
0509 
0510 TODO: move elsewhwre, as it all can be done with SBitSet and SName instances
0511 
0512 **/
0513 
0514 const std::string CSGFoundry::descELV(const SBitSet* elv) const
0515 {
0516     std::vector<unsigned> include_pos ;
0517     std::vector<unsigned> exclude_pos ;
0518     elv->get_pos(include_pos, true );   // bit indices set
0519     elv->get_pos(exclude_pos, false);   // bit indices notset
0520 
0521     unsigned num_include = include_pos.size()  ;
0522     unsigned num_exclude = exclude_pos.size()  ;
0523     unsigned num_bits = elv->num_bits ;
0524     assert( num_bits == num_include + num_exclude );
0525     bool is_all_set = elv->is_all_set();
0526 
0527     std::stringstream ss ;
0528     ss << "CSGFoundry::descELV"
0529        << " elv.num_bits " << num_bits
0530        << " num_include " << num_include
0531        << " num_exclude " << num_exclude
0532        << " is_all_set " << is_all_set
0533        << std::endl
0534        ;
0535 
0536     ss << "INCLUDE:" << num_include << std::endl << std::endl ;
0537     for(unsigned i=0 ; i < num_include ; i++)
0538     {
0539         const unsigned& p = include_pos[i] ;
0540         const char* mn = getMeshName(p) ;
0541         int midx = findMeshIndex(mn);
0542         //assert( int(p) == midx );
0543 
0544         ss
0545             << "p:" << std::setw(3) << p << ":"
0546             << "midx:" << std::setw(3) << midx << ":"
0547             << "mn:" << mn << std::endl
0548             ;
0549     }
0550 
0551     ss << "EXCLUDE:" << exclude_pos.size() << std::endl << std::endl ;
0552     for(unsigned i=0 ; i < exclude_pos.size() ; i++)
0553     {
0554         const unsigned& p = exclude_pos[i] ;
0555         const char* mn = getMeshName(p) ;
0556         int midx = findMeshIndex(mn);
0557         //assert( int(p) == midx );
0558 
0559         ss
0560            << "p:" << std::setw(3) << p << ":"
0561            << "midx:" << std::setw(3) << midx << ":"
0562            << "mn:" << mn << std::endl
0563            ;
0564     }
0565 
0566     std::string str = ss.str();
0567     return str ;
0568 }
0569 
0570 
0571 
0572 void CSGFoundry::addMeshName(const char* name)
0573 {
0574     meshname.push_back(name);
0575 }
0576 
0577 void CSGFoundry::addSolidMMLabel(const char* label)
0578 {
0579     mmlabel.push_back(label);
0580 }
0581 
0582 
0583 const std::string& CSGFoundry::getSolidMMLabel(unsigned gas_idx) const
0584 {
0585     assert( gas_idx < mmlabel.size() );
0586     return mmlabel[gas_idx] ;
0587 }
0588 
0589 
0590 /**
0591 CSGFoundry::Compare
0592 --------------------
0593 
0594 This does very simple byte comparison : looking for equality.
0595 
0596 TODO: find/implement absolute and relative difference comparison
0597 using compare methods specific to the types to handle real comparisons,
0598 such as needed by CSGCopyTest
0599 
0600 **/
0601 
0602 int CSGFoundry::Compare( const CSGFoundry* a, const CSGFoundry* b )
0603 {
0604     int mismatch = 0 ;
0605     mismatch += CompareVec( "solid", a->solid, b->solid );
0606     mismatch += CompareVec( "prim" , a->prim , b->prim );
0607     mismatch += CompareVec( "node" , a->node , b->node );
0608     mismatch += CompareVec( "plan" , a->plan , b->plan );
0609     mismatch += CompareVec( "tran" , a->tran , b->tran );
0610     mismatch += CompareVec( "itra" , a->itra , b->itra );
0611     mismatch += CompareVec( "inst" , a->inst , b->inst );
0612     mismatch += CompareVec( "gas"  , a->gas , b->gas );
0613     LOG_IF(fatal, mismatch != 0 ) << " mismatch FAIL ";
0614     //assert( mismatch == 0 );
0615     mismatch += SSim::Compare( a->sim, b->sim );
0616 
0617     return mismatch ;
0618 }
0619 
0620 /**
0621 CSGFoundry::WIP_CompareStruct
0622 ------------------------------
0623 
0624 The base comparisons are not implemented yet.
0625 
0626 **/
0627 
0628 int CSGFoundry::WIP_CompareStruct( const CSGFoundry* a, const CSGFoundry* b )
0629 {
0630     int mismatch = 0 ;
0631     mismatch += CompareStruct( "solid", a->solid, b->solid );
0632     mismatch += CompareStruct( "prim" , a->prim , b->prim );
0633     mismatch += CompareStruct( "node" , a->node , b->node );
0634     mismatch += CompareFloat4( "plan" , a->plan , b->plan );
0635     mismatch += CompareStruct( "tran" , a->tran , b->tran );
0636     mismatch += CompareStruct( "itra" , a->itra , b->itra );
0637     mismatch += CompareStruct( "inst" , a->inst , b->inst );
0638     mismatch += CompareVec(    "gas"  , a->gas , b->gas );
0639     LOG_IF(fatal, mismatch != 0 ) << " mismatch FAIL ";
0640     //assert( mismatch == 0 );
0641     mismatch += SSim::Compare( a->sim, b->sim );
0642 
0643     return mismatch ;
0644 }
0645 
0646 
0647 
0648 
0649 
0650 std::string CSGFoundry::DescCompare( const CSGFoundry* a, const CSGFoundry* b )
0651 {
0652     std::stringstream ss ;
0653     ss << "CSGFoundry::DescCompare" << std::endl ;
0654     int mismatch = 0 ;
0655     int cv = 0 ;
0656     cv = CompareVec( "solid", a->solid, b->solid ); mismatch += cv ;
0657     ss << "CompareVec.solid " <<  cv << std::endl ;
0658     cv = CompareVec( "prim", a->prim, b->prim );   mismatch += cv ;
0659     ss << "CompareVec.prim " <<  cv << std::endl ;
0660     cv = CompareVec( "node" , a->node , b->node );  mismatch += cv ;
0661     ss << "CompareVec.node " <<  cv << std::endl ;
0662     cv = CompareVec( "plan" , a->plan , b->plan );  mismatch += cv ;
0663     ss << "CompareVec.plan " <<  cv << std::endl ;
0664     cv = CompareVec( "tran" , a->tran , b->tran );  mismatch += cv ;
0665     ss << "CompareVec.tran " <<  cv << std::endl ;
0666     cv = CompareVec( "itra" , a->itra , b->itra );  mismatch += cv ;
0667     ss << "CompareVec.itra " <<  cv << std::endl ;
0668     cv = CompareVec( "inst" , a->inst , b->inst );  mismatch += cv ;
0669     ss << "CompareVec.inst " <<  cv << std::endl ;
0670     cv = CompareVec( "gas" , a->gas , b->gas );  mismatch += cv ;
0671     ss << "CompareVec.gas " <<  cv << std::endl ;
0672     cv = SSim::Compare( a->sim, b->sim ) ;  mismatch += cv ;
0673     ss << "SSim::Compare " <<  cv << std::endl ;
0674     ss << SSim::DescCompare( a->sim, b->sim ) << std::endl ;
0675     ss << " mismatch " << mismatch << std::endl ;
0676     std::string s = ss.str();
0677     return s ;
0678 }
0679 
0680 
0681 /**
0682 CSGFoundry::CompareVec
0683 ------------------------
0684 
0685 Simple comparison looking for equality.
0686 
0687 TODO: adopt svec.h
0688 
0689 **/
0690 
0691 template<typename T>
0692 int CSGFoundry::CompareVec( const char* name, const std::vector<T>& a, const std::vector<T>& b )
0693 {
0694     int mismatch = 0 ;
0695 
0696     bool size_match = a.size() == b.size() ;
0697     LOG_IF(info, !size_match) << name << " size_match FAIL " << a.size() << " vs " << b.size()    ;
0698     if(!size_match) mismatch += 1 ;
0699     if(!size_match) return mismatch ;  // below will likely crash if sizes are different
0700 
0701     int data_match = memcmp( a.data(), b.data(), a.size()*sizeof(T) ) ;
0702     LOG_IF(info, data_match != 0) << name << " sizeof(T) " << sizeof(T) << " data_match FAIL "  ;
0703     if(data_match != 0) mismatch += 1 ;
0704 
0705     int byte_match = CompareBytes( a.data(), b.data(), a.size()*sizeof(T) ) ;
0706     LOG_IF(info, byte_match != 0) << name << " sizeof(T) " << sizeof(T) << " byte_match FAIL " ;
0707     if(byte_match != 0) mismatch += 1 ;
0708 
0709     LOG_IF(fatal, mismatch != 0) << " mismatch FAIL for " << name ;
0710     if( mismatch != 0 ) std::cout
0711          << " mismatch FAIL for " << name
0712          << " a.size " << a.size()
0713          << " b.size " << b.size()
0714          << std::endl
0715          ;
0716     return mismatch ;
0717 }
0718 
0719 
0720 
0721 /**
0722 CSGFoundry::CompareStruct
0723 ----------------------------
0724 
0725 More nuanced comparison to avoid small relative differences
0726 causing being regarded as errors.
0727 
0728 **/
0729 
0730 
0731 template<typename T>
0732 int CSGFoundry::CompareStruct( const char* name, const std::vector<T>& aa, const std::vector<T>& bb )
0733 {
0734     int mismatch = 0 ;
0735 
0736     bool size_match = aa.size() == bb.size() ;
0737     LOG_IF(info, !size_match) << name << " size_match FAIL " << aa.size() << " vs " << bb.size()    ;
0738     if(!size_match) mismatch += 1 ;
0739     if(!size_match) return mismatch ;  // below will likely crash if sizes are different
0740 
0741     int num_struct = aa.size();
0742     int num_diff = 0 ;
0743     for(int i=0 ; i < num_struct ; i++)
0744     {
0745         const T& a = aa[i] ;
0746         const T& b = bb[i] ;
0747         bool is_diff = T::IsDiff( a, b );
0748         if( is_diff ) num_diff += 1 ;
0749     }
0750     if(num_diff != 0 ) mismatch += 1 ;
0751 
0752     LOG_IF(fatal, mismatch != 0) << " mismatch FAIL for " << name ;
0753     if( mismatch != 0 ) std::cout
0754          << " mismatch FAIL for " << name
0755          << " aa.size " << aa.size()
0756          << " bb.size " << bb.size()
0757          << " num_diff " << num_diff
0758          << " mismatch " << mismatch
0759          << std::endl
0760          ;
0761     return mismatch ;
0762 }
0763 
0764 int CSGFoundry::CompareFloat4( const char* name, const std::vector<float4>& aa, const std::vector<float4>& bb ) // static
0765 {
0766     int mismatch = 0 ;
0767     bool size_match = aa.size() == bb.size() ;
0768     LOG_IF(info, !size_match) << name << " size_match FAIL " << aa.size() << " vs " << bb.size()    ;
0769     if(!size_match) mismatch += 1 ;
0770     if(!size_match) return mismatch ;  // below will likely crash if sizes are different
0771 
0772     int num_struct = aa.size();
0773     int num_diff = 0 ;
0774     for(int i=0 ; i < num_struct ; i++)
0775     {
0776         const float4& a = aa[i] ;
0777         const float4& b = bb[i] ;
0778         bool is_diff = Float4_IsDiff( a, b );
0779         if( is_diff ) num_diff += 1 ;
0780     }
0781     if(num_diff != 0 ) mismatch += 1 ;
0782 
0783     LOG_IF(fatal, mismatch != 0) << " mismatch FAIL for " << name ;
0784     if( mismatch != 0 ) std::cout
0785          << " mismatch FAIL for " << name
0786          << " aa.size " << aa.size()
0787          << " bb.size " << bb.size()
0788          << " num_diff " << num_diff
0789          << " mismatch " << mismatch
0790          << std::endl
0791          ;
0792     return mismatch ;
0793 }
0794 
0795 bool CSGFoundry::Float4_IsDiff( const float4& a , const float4& b ) // static
0796 {
0797     return false ;
0798 }
0799 
0800 
0801 int CSGFoundry::CompareBytes(const void* a, const void* b, unsigned num_bytes)
0802 {
0803     const char* ca = (const char*)a ;
0804     const char* cb = (const char*)b ;
0805     int mismatch = 0 ;
0806     for(int i=0 ; i < int(num_bytes) ; i++ ) if( ca[i] != cb[i] ) mismatch += 1 ;
0807     return mismatch ;
0808 }
0809 
0810 
0811 template int CSGFoundry::CompareVec(const char*, const std::vector<CSGSolid>& a, const std::vector<CSGSolid>& b ) ;
0812 template int CSGFoundry::CompareVec(const char*, const std::vector<CSGPrim>& a, const std::vector<CSGPrim>& b ) ;
0813 template int CSGFoundry::CompareVec(const char*, const std::vector<CSGNode>& a, const std::vector<CSGNode>& b ) ;
0814 template int CSGFoundry::CompareVec(const char*, const std::vector<float4>& a, const std::vector<float4>& b ) ;
0815 template int CSGFoundry::CompareVec(const char*, const std::vector<qat4>& a, const std::vector<qat4>& b ) ;
0816 template int CSGFoundry::CompareVec(const char*, const std::vector<unsigned>& a, const std::vector<unsigned>& b ) ;
0817 
0818 
0819 void CSGFoundry::summary(const char* msg ) const
0820 {
0821     LOG(info) << msg << std::endl << descSolids() ;
0822 }
0823 
0824 std::string CSGFoundry::descSolids() const
0825 {
0826     unsigned num_solids = getNumSolid();
0827     std::stringstream ss ;
0828     ss
0829         << "CSGFoundry::descSolids"
0830         << " num_solids " << num_solids
0831         << std::endl
0832         ;
0833 
0834     for(unsigned i=0 ; i < num_solids ; i++)
0835     {
0836         const CSGSolid* so = getSolid(i);
0837         ss << " " << so->desc() << std::endl ;
0838     }
0839     std::string s = ss.str();
0840     return s ;
0841 }
0842 
0843 std::string CSGFoundry::descInstance() const
0844 {
0845     std::vector<int>* idxs = ssys::getenv_vec<int>("IDX", nullptr, ',');
0846 
0847     std::stringstream ss ;
0848     if(idxs == nullptr)
0849     {
0850         ss << " no IDX " << std::endl ;
0851     }
0852     else
0853     {
0854         for(unsigned i=0 ; i < idxs->size() ; i++)
0855         {
0856             int idx = (*idxs)[i] ;
0857             ss << descInstance(idx) ;
0858         }
0859     }
0860     std::string s = ss.str();
0861     return s ;
0862 }
0863 
0864 /**
0865 CSGFoundry::descInstance
0866 ---------------------------
0867 
0868 ::
0869 
0870     c ; IDX=0,10,100 METH=descInstance ./CSGTargetTest.sh remote
0871 
0872 
0873 **/
0874 
0875 std::string CSGFoundry::descInstance(unsigned idx) const
0876 {
0877     std::stringstream ss ;
0878     ss << "CSGFoundry::descInstance"
0879        << " idx " << std::setw(7) << idx
0880        << " inst.size " << std::setw(7) << inst.size()
0881         ;
0882 
0883     if(idx >= inst.size() )
0884     {
0885         ss << " idx OUT OF RANGE " ;
0886     }
0887     else
0888     {
0889         const qat4& q = inst[idx] ;
0890         int ins_idx,  gas_idx, sensor_identifier, sensor_index ;
0891         q.getIdentity(ins_idx,  gas_idx, sensor_identifier, sensor_index );
0892 
0893 
0894         const CSGSolid* so = getSolid(gas_idx);
0895 
0896         ss << " idx " << std::setw(7) << idx
0897            << " ins " << std::setw(5) << ins_idx
0898            << " gas " << std::setw(2) << gas_idx
0899            << " s_ident " << std::setw(7) << sensor_identifier
0900            << " s_index " << std::setw(5) << sensor_index
0901            << " so " << so->desc()
0902            ;
0903     }
0904     ss << std::endl ;
0905     std::string s = ss.str();
0906     return s ;
0907 }
0908 
0909 
0910 std::string CSGFoundry::descInst(unsigned ias_idx_, unsigned long long emm ) const
0911 {
0912     std::stringstream ss ;
0913     for(unsigned i=0 ; i < inst.size() ; i++)
0914     {
0915         const qat4& q = inst[i] ;
0916         int ins_idx,  gas_idx, sensor_identifier, sensor_index ;
0917         q.getIdentity(ins_idx,  gas_idx, sensor_identifier, sensor_index );
0918 
0919         bool gas_enabled = emm == 0ull ? true : ( emm & (0x1ull << gas_idx)) ;
0920         if( gas_enabled )
0921         {
0922             const CSGSolid* so = getSolid(gas_idx);
0923             ss
0924                 << " i " << std::setw(5) << i
0925                 << " ins " << std::setw(5) << ins_idx
0926                 << " gas " << std::setw(2) << gas_idx
0927                 << " s_identifier " << std::setw(7) << sensor_identifier
0928                 << " s_index " << std::setw(5) << sensor_index
0929                 << " so " << so->desc()
0930                 << std::endl
0931                 ;
0932         }
0933     }
0934     std::string s = ss.str();
0935     return s ;
0936 }
0937 
0938 
0939 
0940 
0941 
0942 
0943 
0944 /**
0945 CSGFoundry::iasBB
0946 --------------------
0947 
0948 bbox of the IAS obtained by transforming the center_extent cubes of all instances
0949 hmm: could get a smaller bbox by using the bbox and not the ce of the instances
0950 need to add bb to solid...
0951 
0952 **/
0953 
0954 AABB CSGFoundry::iasBB(unsigned ias_idx_, unsigned long long emm ) const
0955 {
0956     AABB bb = {} ;
0957     std::vector<float3> corners ;
0958     for(unsigned i=0 ; i < inst.size() ; i++)
0959     {
0960         const qat4& q = inst[i] ;
0961 
0962         int ins_idx,  gas_idx, sensor_identifier, sensor_index ;
0963         q.getIdentity(ins_idx,  gas_idx, sensor_identifier, sensor_index );
0964 
0965 
0966         bool gas_enabled = emm == 0ull ? true : ( emm & (0x1ull << gas_idx)) ;
0967         if( gas_enabled )
0968         {
0969             const CSGSolid* so = getSolid(gas_idx);
0970             corners.clear();
0971             AABB::cube_corners(corners, so->center_extent);
0972             q.right_multiply_inplace( corners, 1.f );
0973             for(int i=0 ; i < int(corners.size()) ; i++) bb.include_point(corners[i]) ;
0974         }
0975     }
0976     return bb ;
0977 }
0978 
0979 
0980 
0981 /**
0982 CSGFoundry::getMaxExtent
0983 ---------------------------
0984 
0985 Kinda assumes the solids are all close to origin. This tends to
0986 work for a selection of one prim solids all from the same instance.
0987 
0988 **/
0989 
0990 float CSGFoundry::getMaxExtent(const std::vector<unsigned>& solid_selection) const
0991 {
0992     float mxe = 0.f ;
0993     for(unsigned i=0 ; i < solid_selection.size() ; i++)
0994     {
0995         unsigned gas_idx = solid_selection[i] ;
0996         const CSGSolid* so = getSolid(gas_idx);
0997         float4 ce = so->center_extent ;
0998         if(ce.w > mxe) mxe = ce.w ;
0999         LOG(info) << " gas_idx " << std::setw(3) << gas_idx << " ce " << ce << " mxe " << mxe ;
1000     }
1001     return mxe ;
1002 }
1003 
1004 std::string CSGFoundry::descSolids(const std::vector<unsigned>& solid_selection) const
1005 {
1006     std::stringstream ss ;
1007     ss << "CSGFoundry::descSolids solid_selection " << solid_selection.size() << std::endl ;
1008     for(unsigned i=0 ; i < solid_selection.size() ; i++)
1009     {
1010         unsigned gas_idx = solid_selection[i] ;
1011         const CSGSolid* so = getSolid(gas_idx);
1012         //float4 ce = so->center_extent ;
1013         //ss << " gas_idx " << std::setw(3) << gas_idx << " ce " << ce << std::endl ;
1014         ss << so->desc() << std::endl ;
1015     }
1016     std::string s = ss.str();
1017     return s ;
1018 }
1019 
1020 void CSGFoundry::gasCE(float4& ce, unsigned gas_idx ) const
1021 {
1022     const CSGSolid* so = getSolid(gas_idx);
1023     ce.x = so->center_extent.x ;
1024     ce.y = so->center_extent.y ;
1025     ce.z = so->center_extent.z ;
1026     ce.w = so->center_extent.w ;
1027 }
1028 
1029 void CSGFoundry::gasCE(float4& ce, const std::vector<unsigned>& gas_idxs ) const
1030 {
1031     unsigned middle = gas_idxs.size()/2 ;  // target the middle selected solid : what about even ?
1032     unsigned gas_idx = gas_idxs[middle];
1033 
1034     const CSGSolid* so = getSolid(gas_idx);
1035     ce.x = so->center_extent.x ;
1036     ce.y = so->center_extent.y ;
1037     ce.z = so->center_extent.z ;
1038     ce.w = so->center_extent.w ;
1039 }
1040 
1041 
1042 
1043 
1044 /**
1045 CSGFoundry::iasCE
1046 ------------------
1047 
1048 Finds BB of the IAS and uses that to fill in ce (CenterExtent).
1049 
1050 **/
1051 
1052 
1053 void CSGFoundry::iasCE(float4& ce, unsigned ias_idx_, unsigned long long emm ) const
1054 {
1055     AABB bb = iasBB(ias_idx_, emm);
1056     bb.center_extent(ce) ;
1057 }
1058 
1059 float4 CSGFoundry::iasCE(unsigned ias_idx_, unsigned long long emm ) const
1060 {
1061     float4 ce = make_float4( 0.f, 0.f, 0.f, 0.f );
1062     iasCE(ce, ias_idx_, emm );
1063     return ce ;
1064 }
1065 
1066 
1067 void CSGFoundry::dump() const
1068 {
1069     LOG(info) << "[" ;
1070     dumpPrim();
1071     dumpNode();
1072 
1073     LOG(info) << "]" ;
1074 }
1075 
1076 void CSGFoundry::dumpSolid() const
1077 {
1078     unsigned num_solid = getNumSolid();
1079     for(unsigned solidIdx=0 ; solidIdx < num_solid ; solidIdx++)
1080     {
1081         dumpSolid(solidIdx);
1082     }
1083 }
1084 
1085 void CSGFoundry::dumpSolid(unsigned solidIdx) const
1086 {
1087     const CSGSolid* so = solid.data() + solidIdx ;
1088     int primOffset = so->primOffset ;
1089     int numPrim = so->numPrim  ;
1090 
1091     std::cout
1092         << " solidIdx " << std::setw(3) << solidIdx
1093         << so->desc()
1094         << " primOffset " << std::setw(5) << primOffset
1095         << " numPrim " << std::setw(5) << numPrim
1096         << std::endl
1097         ;
1098 
1099     for(int primIdx=so->primOffset ; primIdx < primOffset + numPrim ; primIdx++)
1100     {
1101         const CSGPrim* pr = prim.data() + primIdx ;
1102         int nodeOffset = pr->nodeOffset() ;
1103         int numNode = pr->numNode() ;
1104 
1105         std::cout
1106             << " primIdx " << std::setw(3) << primIdx << " "
1107             << pr->desc()
1108             << " nodeOffset " << std::setw(4) << nodeOffset
1109             << " numNode " << std::setw(4) << numNode
1110             << std::endl
1111             ;
1112 
1113         for(int nodeIdx=nodeOffset ; nodeIdx < nodeOffset + numNode ; nodeIdx++)
1114         {
1115             const CSGNode* nd = node.data() + nodeIdx ;
1116             std::cout << nd->desc() << std::endl ;
1117         }
1118     }
1119 }
1120 
1121 
1122 int CSGFoundry::findSolidIdx(const char* label) const
1123 {
1124     int idx = -1 ;
1125     if( label == nullptr ) return idx ;
1126     for(unsigned i=0 ; i < solid.size() ; i++)
1127     {
1128         const CSGSolid& so = solid[i];
1129         if(strcmp(so.label, label) == 0) idx = i ;
1130     }
1131     return idx ;
1132 }
1133 
1134 
1135 /**
1136 CSGFoundry::findSolidIdx
1137 --------------------------
1138 
1139 Find multiple idx with labels starting with the provided string, eg "r1", "r2", "r1p" or "r2p"
1140 
1141 This uses SStr:SimpleMatch which implements simple pattern matching with '$'
1142 indicating the terminator forcing exact entire match of what is prior to the '$'
1143 
1144 Q: why the awkward external solid_selection vector ?
1145 
1146 **/
1147 
1148 void CSGFoundry::findSolidIdx(std::vector<unsigned>& solid_idx, const char* label) const
1149 {
1150     if( label == nullptr ) return ;
1151 
1152     std::vector<unsigned>& ss = solid_idx ;
1153 
1154     std::vector<std::string> elem ;
1155     SStr::Split(label, ',', elem );
1156 
1157     for(unsigned i=0 ; i < elem.size() ; i++)
1158     {
1159         const std::string& ele = elem[i] ;
1160         for(unsigned j=0 ; j < solid.size() ; j++)
1161         {
1162             const CSGSolid& so = solid[j];
1163 
1164             bool match = SStr::SimpleMatch(so.label, ele.c_str()) ;
1165             unsigned count = std::count(ss.begin(), ss.end(), j );  // count if j is already collected
1166             if(match && count == 0) ss.push_back(j) ;
1167         }
1168     }
1169 
1170 }
1171 
1172 std::string CSGFoundry::descSolidIdx( const std::vector<unsigned>& solid_idx )
1173 {
1174     std::stringstream ss ;
1175     ss << "(" ;
1176     for(int i=0 ; i < int(solid_idx.size()) ; i++) ss << solid_idx[i] << " " ;
1177     ss << ")" ;
1178     std::string s = ss.str() ;
1179     return s ;
1180 }
1181 
1182 
1183 
1184 
1185 
1186 
1187 
1188 void CSGFoundry::dumpPrim() const
1189 {
1190     std::string s = descPrim();
1191     LOG(info) << s ;
1192 }
1193 
1194 std::string CSGFoundry::descPrim() const
1195 {
1196     std::stringstream ss ;
1197     for(unsigned idx=0 ; idx < solid.size() ; idx++) ss << descPrim(idx);
1198     std::string s = ss.str();
1199     return s ;
1200 }
1201 
1202 std::string CSGFoundry::descPrim(unsigned solidIdx) const
1203 {
1204     const CSGSolid* so = getSolid(solidIdx);
1205     assert(so);
1206 
1207     std::stringstream ss ;
1208     ss << std::endl << so->desc() << std::endl ;
1209 
1210     for(int primIdx=so->primOffset ; primIdx < so->primOffset+so->numPrim ; primIdx++)
1211     {
1212         const CSGPrim* pr = getPrim(primIdx) ;  // note absolute primIdx
1213         assert(pr) ;
1214         ss << "    primIdx " << std::setw(5) << primIdx << " : " << pr->desc() << std::endl ;
1215     }
1216 
1217     std::string s = ss.str();
1218     return s ;
1219 }
1220 
1221 /**
1222 CSGFoundry::detailPrim
1223 ------------------------
1224 
1225 Used from CSGPrimTest
1226 
1227 **/
1228 
1229 std::string CSGFoundry::detailPrim() const
1230 {
1231     std::stringstream ss ;
1232     int numPrim = getNumPrim() ;
1233     assert( int(prim.size()) == numPrim );
1234     for(int primIdx=0 ; primIdx < std::min(10000, numPrim) ; primIdx++) ss << detailPrim(primIdx) << std::endl ;
1235     std::string s = ss.str();
1236     return s ;
1237 }
1238 
1239 /**
1240 CSGFoundry::getPrimBoundary
1241 ----------------------------
1242 
1243 Gets the boundary index of a prim.
1244 Currently this gets the boundary from all CSGNode of the
1245 prim and asserts that they are all the same.
1246 
1247 TODO: a faster version that just gets from the first node
1248 
1249 **/
1250 int CSGFoundry::getPrimBoundary(unsigned primIdx) const
1251 {
1252     const CSGPrim* pr = getPrim(primIdx);
1253     return getPrimBoundary_(pr) ;
1254 }
1255 
1256 int CSGFoundry::getPrimBoundary_( const CSGPrim* pr ) const
1257 {
1258     int nodeOffset = pr->nodeOffset() ;
1259     int numNode = pr->numNode() ;
1260     std::set<unsigned> bnd ;
1261     for(int nodeIdx=nodeOffset ; nodeIdx < nodeOffset + numNode ; nodeIdx++)
1262     {
1263         const CSGNode* nd = getNode(nodeIdx);
1264         bnd.insert(nd->boundary());
1265     }
1266     assert( bnd.size() == 1 );
1267     int boundary = bnd.begin() == bnd.end() ? -1 : *bnd.begin() ;
1268     return boundary ;
1269 }
1270 
1271 
1272 /**
1273 CSGFoundry::setPrimBoundary
1274 ---------------------------------------
1275 
1276 Sets the boundary index for all CSGNode from the *primIdx* CSGPrim.
1277 This is intended for in memory changing of boundaries **within simple test geometries only**.
1278 
1279 It would be unwise to apply this to full geometries and then persist the changed CSGFoundry
1280 as that would be difficult to manage.
1281 
1282 With full geometries the boundaries are set during geometry
1283 translation in for example CSG_GGeo.
1284 
1285 NB intersect identity is a combination of primIdx and instanceIdx so does not need to be set
1286 
1287 **/
1288 
1289 
1290 void CSGFoundry::setPrimBoundary(unsigned primIdx, unsigned boundary )
1291 {
1292     const CSGPrim* pr = getPrim(primIdx);
1293     assert( pr );
1294     for(int nodeIdx=pr->nodeOffset() ; nodeIdx < pr->nodeOffset() + pr->numNode() ; nodeIdx++)
1295     {
1296         CSGNode* nd = getNode_(nodeIdx);
1297         nd->setBoundary(boundary);
1298     }
1299 }
1300 
1301 
1302 
1303 
1304 
1305 std::string CSGFoundry::detailPrim(unsigned primIdx) const
1306 {
1307     const CSGPrim* pr = getPrim(primIdx);
1308     unsigned gasIdx = pr->repeatIdx();
1309     unsigned meshIdx = pr->meshIdx();
1310     unsigned pr_primIdx = pr->primIdx();
1311     const char* meshName = id->getName(meshIdx);
1312 
1313     int numNode = pr->numNode() ;
1314     int nodeOffset = pr->nodeOffset() ;
1315     int boundary = getPrimBoundary(primIdx);
1316     const char* bndName = sim ? sim->getBndName(boundary) : "-bd" ;
1317 
1318     float4 ce = pr->ce();
1319 
1320     std::stringstream ss ;
1321     ss
1322         << std::setw(10) << SStr::Format(" pri:%d", primIdx )
1323         << std::setw(10) << SStr::Format(" lpr:%d", pr_primIdx )
1324         << std::setw(8)  << SStr::Format(" gas:%d", gasIdx )
1325         << std::setw(8)  << SStr::Format(" msh:%d", meshIdx)
1326         << std::setw(8)  << SStr::Format(" bnd:%d", boundary)
1327         << std::setw(8)  << SStr::Format(" nno:%d", numNode )
1328         << std::setw(10)  << SStr::Format(" nod:%d", nodeOffset )
1329         << " ce "
1330         << "(" << std::setw(10) << std::fixed << std::setprecision(2) << ce.x
1331         << "," << std::setw(10) << std::fixed << std::setprecision(2) << ce.y
1332         << "," << std::setw(10) << std::fixed << std::setprecision(2) << ce.z
1333         << "," << std::setw(10) << std::fixed << std::setprecision(2) << ce.w
1334         << ")"
1335         << " meshName " << std::setw(15) << ( meshName ? meshName : "-" )
1336         << " bndName "  << std::setw(15) << ( bndName  ? bndName  : "-" )
1337         ;
1338 
1339     std::string s = ss.str();
1340     return s ;
1341 }
1342 
1343 
1344 
1345 
1346 std::string CSGFoundry::descPrimSpec() const
1347 {
1348     unsigned num_solids = getNumSolid();
1349     std::stringstream ss ;
1350     ss
1351         << "CSGFoundry::descPrimSpec"
1352         << " num_solids " << num_solids
1353         << std::endl
1354         ;
1355 
1356     for(unsigned i=0 ; i < num_solids ; i++) ss << descPrimSpec(i) << std::endl ;
1357 
1358     std::string s = ss.str();
1359     return s ;
1360 }
1361 
1362 std::string CSGFoundry::descPrimSpec(unsigned solidIdx) const
1363 {
1364     unsigned gas_idx = solidIdx ;
1365     SCSGPrimSpec ps = getPrimSpec(gas_idx);
1366     return ps.desc() ;
1367 }
1368 
1369 
1370 
1371 
1372 
1373 
1374 void CSGFoundry::dumpPrim(unsigned solidIdx) const
1375 {
1376     std::string s = descPrim(solidIdx);
1377     LOG(info) << std::endl << s ;
1378 }
1379 
1380 
1381 void CSGFoundry::getNodePlanes(std::vector<float4>& planes, const CSGNode* nd) const
1382 {
1383     unsigned tc = nd->typecode();
1384     bool has_planes = CSG::HasPlanes(tc) ;
1385     if(has_planes)
1386     {
1387         for(unsigned planIdx=nd->planeIdx() ; planIdx < nd->planeIdx() + nd->planeNum() ; planIdx++)
1388         {
1389             const float4* pl = getPlan(planIdx);
1390             planes.push_back(*pl);
1391         }
1392     }
1393 }
1394 
1395 
1396 /**
1397 CSGFoundry::getSolidPrim
1398 ----------------------------
1399 
1400 Use *solidIdx* to get CSGSolid pointer *so* and then use
1401 the *so->primOffset* together with *primIdxRel* to get the CSGPrim pointer.
1402 
1403 **/
1404 
1405 const CSGPrim*  CSGFoundry::getSolidPrim(unsigned solidIdx, unsigned primIdxRel) const
1406 {
1407     const CSGSolid* so = getSolid(solidIdx);
1408     LOG_IF(fatal, so == nullptr) << "Failed to getSolid solidIdx " << solidIdx ;
1409     assert(so);
1410 
1411     unsigned primIdx = so->primOffset + primIdxRel ;
1412     const CSGPrim* pr = getPrim(primIdx);
1413     LOG_IF(fatal, pr == nullptr) << "Failed to getPrim primIdxRel " << primIdxRel ;
1414     assert(pr);
1415 
1416     return pr ;
1417 }
1418 
1419 
1420 
1421 
1422 
1423 
1424 
1425 
1426 void CSGFoundry::dumpNode() const
1427 {
1428     LOG(info) << std::endl << descNode();
1429 }
1430 
1431 void CSGFoundry::dumpNode(unsigned solidIdx) const
1432 {
1433     LOG(info) << std::endl << descNode(solidIdx);
1434 }
1435 
1436 std::string CSGFoundry::descNode() const
1437 {
1438     std::stringstream ss ;
1439     for(unsigned idx=0 ; idx < solid.size() ; idx++) ss << descNode(idx) << std::endl ;
1440     std::string s = ss.str();
1441     return s ;
1442 }
1443 
1444 std::string CSGFoundry::descNode(unsigned solidIdx) const
1445 {
1446     const CSGSolid* so = solid.data() + solidIdx ;
1447     //const CSGPrim* pr0 = prim.data() + so->primOffset ;
1448     //const CSGNode* nd0 = node.data() + pr0->nodeOffset() ;
1449 
1450     std::stringstream ss ;
1451     ss << std::endl << so->desc() << std::endl  ;
1452 
1453     for(int primIdx=so->primOffset ; primIdx < so->primOffset+so->numPrim ; primIdx++)
1454     {
1455         const CSGPrim* pr = prim.data() + primIdx ;
1456         int numNode = pr->numNode() ;
1457         for(int nodeIdx=pr->nodeOffset() ; nodeIdx < pr->nodeOffset()+numNode ; nodeIdx++)
1458         {
1459             const CSGNode* nd = node.data() + nodeIdx ;
1460             ss << "    nodeIdx " << std::setw(5) << nodeIdx << " : " << nd->desc() << std::endl ;
1461         }
1462     }
1463 
1464     std::string s = ss.str();
1465     return s ;
1466 }
1467 
1468 std::string CSGFoundry::descTran(unsigned solidIdx) const
1469 {
1470     const CSGSolid* so = solid.data() + solidIdx ;
1471     //const CSGPrim* pr0 = prim.data() + so->primOffset ;
1472     //const CSGNode* nd0 = node.data() + pr0->nodeOffset() ;
1473 
1474     std::stringstream ss ;
1475     ss << std::endl << so->desc() << std::endl  ;
1476 
1477     for(int primIdx=so->primOffset ; primIdx < so->primOffset+so->numPrim ; primIdx++)
1478     {
1479         const CSGPrim* pr = prim.data() + primIdx ;
1480         int numNode = pr->numNode() ;
1481         for(int nodeIdx=pr->nodeOffset() ; nodeIdx < pr->nodeOffset()+numNode ; nodeIdx++)
1482         {
1483             const CSGNode* nd = node.data() + nodeIdx ;
1484             unsigned tranIdx = nd->gtransformIdx();
1485 
1486             const qat4* tr = tranIdx > 0 ? getTran(tranIdx-1) : nullptr ;
1487             const qat4* it = tranIdx > 0 ? getItra(tranIdx-1) : nullptr ;
1488             ss << "    tranIdx " << std::setw(5) << tranIdx << " : " << ( tr ? tr->desc('t') : "" ) << std::endl ;
1489             ss << "    tranIdx " << std::setw(5) << tranIdx << " : " << ( it ? it->desc('i') : "" ) << std::endl ;
1490         }
1491     }
1492     std::string s = ss.str();
1493     return s ;
1494 }
1495 
1496 
1497 
1498 const CSGNode* CSGFoundry::getSolidPrimNode(unsigned solidIdx, unsigned primIdxRel, unsigned nodeIdxRel) const
1499 {
1500     const CSGPrim* pr = getSolidPrim(solidIdx, primIdxRel);
1501     assert(pr);
1502     unsigned nodeIdx = pr->nodeOffset() + nodeIdxRel ;
1503     const CSGNode* nd = getNode(nodeIdx);
1504     assert(nd);
1505     return nd ;
1506 }
1507 
1508 
1509 
1510 /**
1511 CSGFoundry::getPrimSpec
1512 ----------------------
1513 
1514 Provides the specification to access the AABB and sbtIndexOffset of all CSGPrim
1515 of a CSGSolid.  The specification includes pointers, counts and stride.
1516 
1517 NB PrimAABB is distinct from NodeAABB. Cannot directly use NodeAABB
1518 because the number of nodes for each prim (node tree) varies meaning
1519 that the strides are irregular.
1520 
1521 Prim Selection
1522 ~~~~~~~~~~~~~~~~
1523 
1524 HMM: Prim selection will also require new primOffset for all solids,
1525 so best to implement it by spawning a new CSGFoundry with the selection applied.
1526 Then the CSGFoundry code can stay the same just with different solid and prim
1527 and applying the selection can be focussed into one static method.
1528 
1529 HMM: but its not all of CSGFoundry that needs to have selection
1530 applied its just the solid and prim. Could prune nodes and transforms
1531 too, but probably not worthwhile.
1532 
1533 How to implement ? Kinda like CSG_GGeo translation but starting
1534 from another instance of CSGFoundry.
1535 
1536 Also probably better to do enabledmergedmesh solid selection this
1537 way too rather than smearing ok->isEnabledMergedMesh all over CSGOptiX/SBT
1538 Better for SBT creation not to be mixed up with geometry selection.
1539 
1540 **/
1541 
1542 SCSGPrimSpec CSGFoundry::getPrimSpec(unsigned solidIdx) const
1543 {
1544     SCSGPrimSpec ps = d_prim ? getPrimSpecDevice(solidIdx) : getPrimSpecHost(solidIdx) ;
1545     LOG_IF(info, ps.device == false) << "WARNING using host PrimSpec, upload first " ;
1546     return ps ;
1547 }
1548 SCSGPrimSpec CSGFoundry::getPrimSpecHost(unsigned solidIdx) const
1549 {
1550     const CSGSolid* so = solid.data() + solidIdx ;
1551     SCSGPrimSpec ps = CSGPrim::MakeSpec( prim.data(),  so->primOffset, so->numPrim ); ;
1552     ps.device = false ;
1553     return ps ;
1554 }
1555 SCSGPrimSpec CSGFoundry::getPrimSpecDevice(unsigned solidIdx) const
1556 {
1557     assert( d_prim );
1558     const CSGSolid* so = solid.data() + solidIdx ;  // get the primOffset from CPU side solid
1559     SCSGPrimSpec ps = CSGPrim::MakeSpec( d_prim,  so->primOffset, so->numPrim ); ;
1560     ps.device = true ;
1561     return ps ;
1562 }
1563 
1564 void CSGFoundry::checkPrimSpec(unsigned solidIdx) const
1565 {
1566     SCSGPrimSpec ps = getPrimSpec(solidIdx);
1567     LOG(info) << "[ solidIdx  " << solidIdx ;
1568     ps.downloadDump();
1569     LOG(info) << "] solidIdx " << solidIdx ;
1570 }
1571 
1572 void CSGFoundry::checkPrimSpec() const
1573 {
1574     for(unsigned solidIdx = 0 ; solidIdx < getNumSolid() ; solidIdx++)
1575     {
1576         checkPrimSpec(solidIdx);
1577     }
1578 }
1579 
1580 const char* CSGFoundry::getSolidLabel_(int ridx) const
1581 {
1582     const CSGSolid* so = getSolid(ridx);
1583     assert(so);
1584     return so ? so->label : nullptr ;
1585 }
1586 
1587 
1588 /**
1589 CSGFoundry::getSolidIntent
1590 ---------------------------
1591 
1592 **/
1593 
1594 
1595 char CSGFoundry::getSolidIntent(int ridx) const
1596 {
1597     const CSGSolid* so = getSolid(ridx);
1598     assert(so);
1599     return so ? so->getIntent() : '\0' ;
1600 }
1601 
1602 
1603 std::string CSGFoundry::descSolidIntent() const
1604 {
1605     unsigned num_solid = getNumSolid() ;
1606     std::stringstream ss ;
1607     ss << "CSGFoundry::descSolidIntent num_solid " << num_solid << "\n" ;
1608     for(unsigned i=0 ; i < num_solid ; i++)
1609     {
1610         char slpx = getSolidIntent(i);
1611         const char* sl = getSolidLabel_(i);
1612         const std::string& smml = getSolidMMLabel(i) ;
1613         ss
1614            << " i " << std::setw(4) << i
1615            << " getSolidIntent " << std::setw(2) << slpx
1616            << " getSolidLabel_ " << std::setw(10) << ( sl ? sl : "-" )
1617            << " getSolidMMLabel " <<  smml
1618            << "\n"
1619            ;
1620     }
1621     std::string str = ss.str();
1622     return str ;
1623 }
1624 
1625 
1626 
1627 unsigned CSGFoundry::getNumSolid(int type_) const
1628 {
1629     unsigned count = 0 ;
1630     for(unsigned i=0 ; i < solid.size() ; i++)
1631     {
1632         const CSGSolid* so = getSolid(i);
1633         if(so && so->type == type_ ) count += 1 ;
1634     }
1635     return count ;
1636 }
1637 
1638 
1639 
1640 unsigned CSGFoundry::getNumSolid() const {  return getNumSolid(STANDARD_SOLID); }
1641 unsigned CSGFoundry::getNumSolidTotal() const { return solid.size(); }
1642 
1643 
1644 
1645 unsigned CSGFoundry::getNumPrim() const  { return prim.size();  }
1646 unsigned CSGFoundry::getNumNode() const  { return node.size(); }
1647 unsigned CSGFoundry::getNumPlan() const  { return plan.size(); }
1648 unsigned CSGFoundry::getNumTran() const  { return tran.size(); }
1649 unsigned CSGFoundry::getNumItra() const  { return itra.size(); }
1650 unsigned CSGFoundry::getNumInst() const  { return inst.size(); }
1651 
1652 const CSGSolid*  CSGFoundry::getSolid(unsigned solidIdx) const { return solidIdx < solid.size() ? solid.data() + solidIdx  : nullptr ; }
1653 const CSGPrim*   CSGFoundry::getPrim(unsigned primIdx)   const { return primIdx  < prim.size()  ? prim.data()  + primIdx  : nullptr ; }
1654 const CSGNode*   CSGFoundry::getNode(unsigned nodeIdx)   const { return nodeIdx  < node.size()  ? node.data()  + nodeIdx  : nullptr ; }
1655 CSGNode*         CSGFoundry::getNode_(unsigned nodeIdx)        { return nodeIdx  < node.size()  ? node.data()  + nodeIdx  : nullptr ; }
1656 
1657 const float4*    CSGFoundry::getPlan(unsigned planIdx)   const { return planIdx  < plan.size()  ? plan.data()  + planIdx  : nullptr ; }
1658 const qat4*      CSGFoundry::getTran(unsigned tranIdx)   const { return tranIdx  < tran.size()  ? tran.data()  + tranIdx  : nullptr ; }
1659 const qat4*      CSGFoundry::getItra(unsigned itraIdx)   const { return itraIdx  < itra.size()  ? itra.data()  + itraIdx  : nullptr ; }
1660 const qat4*      CSGFoundry::getInst(unsigned instIdx)   const { return instIdx  < inst.size()  ? inst.data()  + instIdx  : nullptr ; }
1661 
1662 
1663 
1664 
1665 
1666 
1667 const CSGSolid*  CSGFoundry::getSolid_(int solidIdx_) const {
1668     unsigned solidIdx = solidIdx_ < 0 ? unsigned(solid.size() + solidIdx_) : unsigned(solidIdx_)  ;   // -ve counts from end
1669     return getSolid(solidIdx);
1670 }
1671 
1672 const CSGSolid* CSGFoundry::getSolidByName(const char* name) const  // caution stored labels truncated to 4 char
1673 {
1674     unsigned missing = ~0u ;
1675     unsigned idx = missing ;
1676     for(unsigned i=0 ; i < solid.size() ; i++) if(strcmp(solid[i].label, name) == 0) idx = i ;
1677     assert( idx != missing );
1678     return getSolid(idx) ;
1679 }
1680 
1681 /**
1682 CSGFoundry::getSolidIdx
1683 ----------------------
1684 
1685 Without sufficient reserve allocation this is unreliable as pointers go stale on reallocations.
1686 
1687 **/
1688 
1689 unsigned CSGFoundry::getSolidIdx(const CSGSolid* so) const
1690 {
1691     unsigned idx = ~0u ;
1692     for(unsigned i=0 ; i < solid.size() ; i++)
1693     {
1694        const CSGSolid* s = solid.data() + i ;
1695        LOG(info) << " i " << i << " s " << s << " so " << so ;
1696        if(s == so) idx = i ;
1697     }
1698     assert( idx != ~0u );
1699     return idx ;
1700 }
1701 
1702 
1703 
1704 
1705 void CSGFoundry::makeDemoSolids()
1706 {
1707     maker->makeDemoSolids();
1708 }
1709 CSGSolid* CSGFoundry::make(const char* name)
1710 {
1711     return maker->make(name);
1712 }
1713 
1714 
1715 /**
1716 CSGFoundry::importSim
1717 ----------------------
1718 
1719 Instanciatation grabs the (SSim)sim instance
1720 
1721 **/
1722 
1723 
1724 void CSGFoundry::importSim()
1725 {
1726     assert(sim);
1727     import->import();
1728 }
1729 
1730 
1731 
1732 
1733 
1734 
1735 /**
1736 CSGFoundry::addNode_solidLocalNodeIdx
1737 --------------------------------------
1738 
1739 Index that would be assigned to the next added node
1740 within the currently "open" last_added_solid.
1741 Notice this is counting right across prim making it a bit awkward
1742 to access.
1743 
1744 Although awkward in the new workflow it was a rather natural
1745 thing in the old workflow from the historical GGeo/GMergedMesh splits.
1746 Actually more precisely its because of the GParts concatenation
1747 done in the old workflow, where the parts for multiple prim got joined
1748 together fot persistency convenience.
1749 
1750 **/
1751 
1752 int CSGFoundry::addNode_solidLocalNodeIdx() const
1753 {
1754     LOG_IF(fatal, !last_added_solid) << "must addSolid prior to addPrim and addNode " ;
1755     assert( last_added_solid );
1756 
1757     unsigned primOffset = last_added_solid->primOffset ;
1758     const CSGPrim* first_prim_of_last_added_solid = prim.data() + primOffset ;
1759     assert( first_prim_of_last_added_solid ) ;
1760     int nodeOffset = first_prim_of_last_added_solid->nodeOffset() ;
1761 
1762     int globalNodeIdx = node.size();
1763     int solidLocalNodeIdx = globalNodeIdx - nodeOffset ;
1764 
1765     return solidLocalNodeIdx ;
1766 }
1767 
1768 
1769 CSGNode* CSGFoundry::addNode(CSGNode nd)
1770 {
1771     LOG_IF(fatal, !last_added_prim) << "must addPrim prior to addNode" ;
1772     assert( last_added_prim );
1773 
1774     unsigned globalNodeIdx = node.size() ;
1775 
1776     unsigned nodeOffset = last_added_prim->nodeOffset();
1777     unsigned numNode = last_added_prim->numNode();
1778     unsigned localNodeIdx = globalNodeIdx - nodeOffset ;
1779 
1780     bool ok_localNodeIdx = localNodeIdx < numNode ;
1781     LOG_IF(fatal, !ok_localNodeIdx)
1782         << " TOO MANY addNode FOR Prim "
1783         << " localNodeIdx " << localNodeIdx
1784         << " numNode " << numNode
1785         << " globalNodeIdx " << globalNodeIdx
1786         << " (must addNode only up to the declared numNode from the addPrim call) "
1787         ;
1788     assert( ok_localNodeIdx  );
1789 
1790     bool ok_globalNodeIdx = globalNodeIdx < IMAX  ;
1791     LOG_IF(fatal, !ok_globalNodeIdx)
1792         << " FATAL : OUT OF RANGE "
1793         << " globalNodeIdx " << globalNodeIdx
1794         << " IMAX " << IMAX
1795         ;
1796     assert( ok_globalNodeIdx );
1797 
1798 
1799     int solidLocalNodeIdx = addNode_solidLocalNodeIdx() ;  // depends on node.size so must call before below push_back
1800 
1801     node.push_back(nd);
1802     last_added_node = node.data() + globalNodeIdx ;
1803 
1804     last_added_node->setIndex( solidLocalNodeIdx );  // TRY AUTOMATIC CSGNode::index a.nix
1805 
1806     return last_added_node ;
1807 }
1808 
1809 
1810 CSGNode* CSGFoundry::addNode()
1811 {
1812     CSGNode nd = CSGNode::Zero() ;
1813     return addNode(nd);
1814 }
1815 
1816 
1817 
1818 /**
1819 CSGFoundry::addNode
1820 --------------------
1821 
1822 Note that the planeIdx and planeNum of the CSGNode are
1823 rewritten based on the number of planes for this nd
1824 and the number of planes collected already into
1825 the global plan vector.
1826 
1827 Note that when pl and tr are nullptr this does very
1828 little : essentially just occupying the slot in the foundry.
1829 
1830 **/
1831 
1832 CSGNode* CSGFoundry::addNode(CSGNode nd, const std::vector<float4>* pl, const Tran<double>* tr  )
1833 {
1834     CSGNode* n = addNode(nd) ;
1835     unsigned num_planes = pl ? pl->size() : 0 ;
1836     if(num_planes > 0)
1837     {
1838         n->setTypecode(CSG_CONVEXPOLYHEDRON) ;
1839         n->setPlaneIdx(plan.size());
1840         n->setPlaneNum(num_planes);
1841         for(unsigned i=0 ; i < num_planes ; i++) addPlan((*pl)[i]);
1842     }
1843     if(tr)
1844     {
1845         unsigned trIdx = 1u + addTran(tr);  // 1-based idx, 0 meaning None
1846         n->setTransform(trIdx);
1847     }
1848     return n ;
1849 }
1850 
1851 
1852 
1853 
1854 
1855 
1856 
1857 
1858 
1859 /**
1860 CSGFoundry::addNodes
1861 ----------------------
1862 
1863 Pointer to the last added node is returned
1864 
1865 **/
1866 
1867 CSGNode* CSGFoundry::addNodes(const std::vector<CSGNode>& nds )
1868 {
1869     unsigned idx = node.size() ;
1870     for(unsigned i=0 ; i < nds.size() ; i++)
1871     {
1872         const CSGNode& nd = nds[i];
1873         idx = node.size() ;     // number of nodes prior to adding this one
1874         assert( idx < IMAX );
1875         node.push_back(nd);
1876     }
1877     return node.data() + idx ;
1878 }
1879 
1880 CSGNode* CSGFoundry::addNode(AABB& bb, CSGNode nd )
1881 {
1882     CSGNode* n = addNode(nd);
1883     bb.include_aabb( n->AABB() );
1884     return n ;
1885 }
1886 
1887 CSGNode* CSGFoundry::addNodes(AABB& bb, std::vector<CSGNode>& nds, const std::vector<const Tran<double>*>* trs  )
1888 {
1889     if( trs == nullptr ) return addNodes(nds); // HUH: bb not updated
1890 
1891     unsigned num_nd = nds.size() ;
1892     unsigned num_tr = trs ? trs->size() : 0  ;
1893     if( num_tr > 0 ) assert( num_nd == num_tr );
1894 
1895     CSGNode* n = nullptr ;
1896     for(unsigned i=0 ; i < num_nd ; i++)
1897     {
1898         CSGNode& nd = nds[i];
1899         const Tran<double>* tr = trs ? (*trs)[i] : nullptr ;
1900         n = addNode(nd);
1901         if(tr)
1902         {
1903             bool transform_node_aabb = true ;
1904             addNodeTran(n, tr, transform_node_aabb );
1905         }
1906         bb.include_aabb( n->AABB() );
1907     }
1908     return n ;
1909 }
1910 
1911 
1912 
1913 /**
1914 CSGFoundry::addPrimNodes
1915 -------------------------
1916 
1917 Trying to make adding prim nodes more self contained
1918 and less fussy.  HMM: its rather difficult to do this
1919 add addition to CSGSolid/CSGPrim/CSGNode has lots
1920 of checks that its done in the prescribed order.
1921 
1922 **/
1923 
1924 CSGPrim* CSGFoundry::addPrimNodes(AABB& bb, const std::vector<CSGNode>& nds, const std::vector<const Tran<double>*>* trs )
1925 {
1926     unsigned num_nd = nds.size();
1927     unsigned num_tr = trs ? trs->size() : 0 ;
1928     if( num_tr > 0 ) assert( num_nd == num_tr );
1929 
1930     int nodeOffset = -1 ;
1931     CSGPrim* pr = addPrim(num_nd, nodeOffset )  ;
1932 
1933     for(unsigned i=0 ; i < num_nd ; i++)
1934     {
1935         const CSGNode& nd = nds[i] ;
1936         const Tran<double>* tr = trs ? (*trs)[i] : nullptr ;
1937         CSGNode* n = addNode(nd);
1938         if(tr)
1939         {
1940             bool transform_node_aabb = true ;
1941             addNodeTran(n, tr, transform_node_aabb );
1942         }
1943         bb.include_aabb( n->AABB() );  // does this feel the transform ?
1944     }
1945     return pr ;
1946 }
1947 
1948 
1949 
1950 
1951 
1952 
1953 
1954 float4* CSGFoundry::addPlan(const float4& pl )
1955 {
1956     unsigned idx = plan.size();
1957     assert( idx < IMAX );
1958     plan.push_back(pl);
1959     return plan.data() + idx ;
1960 }
1961 
1962 
1963 
1964 /**
1965 CSGFoundry::addTran
1966 ---------------------
1967 
1968 When tr argument is nullptr an identity transform is added.
1969 
1970 **/
1971 
1972 template<typename T>
1973 unsigned CSGFoundry::addTran( const Tran<T>* tr  )
1974 {
1975    return tr == nullptr ? addTran() : addTran_(tr);
1976 }
1977 template unsigned CSGFoundry::addTran<float>(const Tran<float>* ) ;
1978 template unsigned CSGFoundry::addTran<double>(const Tran<double>* ) ;
1979 
1980 
1981 
1982 template<typename T>
1983 unsigned CSGFoundry::addTran_( const Tran<T>* tr  )
1984 {
1985     qat4 t(glm::value_ptr(tr->t));  // narrowing when T=double
1986     qat4 v(glm::value_ptr(tr->v));
1987     unsigned idx = addTran(&t, &v);
1988     return idx ;
1989 }
1990 
1991 template unsigned CSGFoundry::addTran_<float>(const Tran<float>* ) ;
1992 template unsigned CSGFoundry::addTran_<double>(const Tran<double>* ) ;
1993 
1994 unsigned CSGFoundry::addTran( const qat4* tr, const qat4* it )
1995 {
1996     unsigned idx = tran.size();   // size before push_back
1997     assert( tran.size() == itra.size()) ;
1998     tran.push_back(*tr);
1999     itra.push_back(*it);
2000     return idx ;
2001 }
2002 
2003 /**
2004 CSGFoundry::addTran
2005 ----------------------
2006 
2007 Add identity transform to tran and itra arrays and return index.
2008 
2009 **/
2010 unsigned CSGFoundry::addTran()
2011 {
2012     qat4 t ;
2013     t.init();
2014     qat4 v ;
2015     v.init();
2016     unsigned idx = addTran(&t, &v);
2017     return idx ;
2018 }
2019 
2020 /**
2021 CSGFoundry::addTranPlaceholder
2022 -------------------------------
2023 
2024 Adds transforms tran and itra if none have yet been added
2025 
2026 **/
2027 void CSGFoundry::addTranPlaceholder()
2028 {
2029     unsigned idx = tran.size();   // size before push_back
2030     assert( tran.size() == itra.size()) ;
2031     if( idx == 0 )
2032     {
2033         addTran();
2034     }
2035 }
2036 
2037 
2038 
2039 /**
2040 CSGFoundry::addNodeTran
2041 ------------------------
2042 
2043 Adds transform and associates it to the CSGNode
2044 
2045 IS THE BELOW TRUE ?::
2046 
2047     NB CSGNode::setTransform on freestanding CSGNode would
2048     almost certainly be a bug. For the transform association
2049     to be effective have to addTran first.
2050 
2051 **/
2052 
2053 template<typename T>
2054 const qat4* CSGFoundry::addNodeTran( CSGNode* nd, const Tran<T>* tr, bool transform_node_aabb  )
2055 {
2056     unsigned transform_idx = 1 + addTran(tr);      // 1-based idx, 0 meaning None
2057     nd->setTransform(transform_idx);
2058     const qat4* q = getTran(transform_idx-1u) ;   // storage uses 0-based
2059 
2060     if( transform_node_aabb )
2061     {
2062         q->transform_aabb_inplace( nd->AABB() );
2063     }
2064     return q ;
2065 }
2066 
2067 
2068 template const qat4* CSGFoundry::addNodeTran<float>(  CSGNode* nd, const Tran<float>* , bool ) ;
2069 template const qat4* CSGFoundry::addNodeTran<double>( CSGNode* nd, const Tran<double>*, bool ) ;
2070 
2071 
2072 void CSGFoundry::addNodeTran(CSGNode* nd )
2073 {
2074     unsigned transform_idx = 1 + addTran();      // 1-based idx, 0 meaning None
2075     nd->setTransform(transform_idx);
2076 }
2077 
2078 
2079 
2080 
2081 
2082 
2083 
2084 
2085 /**
2086 CSGFoundry::addInstance
2087 ------------------------
2088 
2089 Used from CSGCopy::copy/CSGCopy::copySolidInstances
2090 when copying a loaded CSGFoundry to apply a selection
2091 
2092 stree.h/snode.h uses sensor_identifier -1 to indicate not-a-sensor, but
2093 that is not convenient on GPU due to OptixInstance.instanceId limits.
2094 Hence here make transition by adding 1 and treating 0 as not-a-sensor.
2095 
2096 **/
2097 
2098 void CSGFoundry::addInstance(const float* tr16, int gas_idx, int sensor_identifier, int sensor_index, bool firstcall )
2099 {
2100     int sensor_identifier_u = 0 ;
2101 
2102     if( firstcall )
2103     {
2104         assert( sensor_identifier >= -1 );
2105         sensor_identifier_u = sensor_identifier + 1 ;
2106     }
2107     else
2108     {
2109         assert( sensor_identifier >= 0 );
2110         sensor_identifier_u = sensor_identifier  ;
2111     }
2112     assert( sensor_identifier_u >= 0 );
2113 
2114 
2115     qat4 instance(tr16) ;  // identity matrix if tr16 is nullptr
2116     int ins_idx = int(inst.size()) ;
2117 
2118     instance.setIdentity( ins_idx, gas_idx, sensor_identifier_u, sensor_index );
2119 
2120     LOG(debug)
2121         << " firstcall " << ( firstcall ? "YES" : "NO " )
2122         << " ins_idx " << ins_idx
2123         << " gas_idx " << gas_idx
2124         << " sensor_identifier " << sensor_identifier
2125         << " sensor_identifier_u " << sensor_identifier_u
2126         << " sensor_index " << sensor_index
2127         ;
2128 
2129     inst.push_back( instance );
2130 }
2131 
2132 /**
2133 CSGFoundry::addInstanceVector
2134 ------------------------------
2135 
2136 Canonical stack::
2137 
2138     G4CXOpticks::setGeometry
2139     CSGFoundry::CreateFromSim  (after U4Tree::Create within G4CXOpticks::setGeometry)
2140     CSGFoundry::importSim
2141     CSGImport::import
2142     CSGImport::importInst     (with argument stree::inst_f4 populated by stree::add_inst from snode::sensor_id)
2143     CSGFoundry::addInstanceVector
2144 
2145 stree.h/snode.h uses sensor_identifier -1 to indicate not-a-sensor, but
2146 that is not convenient on GPU due to OptixInstance.instanceId limits.
2147 Hence here make transition by adding 1 and treating 0 as not-a-sensor,
2148 with the sqat4::incrementSensorIdentifier method
2149 
2150 The stree::inst_f4 is formed from the stree globals and factors by stree::add_inst
2151 
2152 **/
2153 
2154 void CSGFoundry::addInstanceVector( const std::vector<glm::tmat4x4<float>>& v_inst_f4 )
2155 {
2156     assert( inst.size() == 0 );
2157     int num_inst = v_inst_f4.size() ;
2158 
2159     for(int i=0 ; i < num_inst ; i++)
2160     {
2161         const glm::tmat4x4<float>& inst_f4 = v_inst_f4[i] ;
2162         const float* tr16 = glm::value_ptr(inst_f4) ;
2163         qat4 instance(tr16) ;
2164         instance.incrementSensorIdentifier() ; // GPU side needs 0 to mean "not-a-sensor"
2165         inst.push_back( instance );
2166     }
2167 }
2168 
2169 
2170 
2171 void CSGFoundry::addInstancePlaceholder()
2172 {
2173     const float* tr16 = nullptr ;
2174     int gas_idx = 0 ;    // from -1 : for single solid tests
2175     int sensor_identifier = -1 ;
2176     int sensor_index = -1 ;
2177     bool firstcall = true ;
2178     // CAUSES : sensor_identifier to be incremented
2179     // as on GPU need to use 0 for not-a-sensor NOT -1
2180     // (OptiX has bitlimits and -1 uses all bits)
2181 
2182     addInstance(tr16, gas_idx, sensor_identifier, sensor_index, firstcall );
2183 }
2184 
2185 /**
2186 CSGFoundry::addPrim_solidLocalPrimIdx
2187 --------------------------------------
2188 
2189 Index that would be assigned to the next added prim
2190 within the currently "open" last_added_solid
2191 
2192 **/
2193 
2194 int CSGFoundry::addPrim_solidLocalPrimIdx() const
2195 {
2196     LOG_IF(fatal, !last_added_solid) << "must addSolid prior to addPrim" ;
2197     assert( last_added_solid );
2198 
2199     unsigned primOffset = last_added_solid->primOffset ;
2200     unsigned globalPrimIdx = prim.size();
2201     unsigned localPrimIdx = globalPrimIdx - primOffset ;
2202     return localPrimIdx ;
2203 }
2204 
2205 
2206 /**
2207 CSGFoundry::addPrim
2208 ---------------------
2209 
2210 Offsets counts for  node, tran and plan are
2211 persisted into the CSGPrim.
2212 Thus must addPrim prior to adding any node,
2213 tran or plan needed for that prim.
2214 
2215 The nodeOffset_ argument default of -1 signifies
2216 to set the nodeOffset of the Prim to the count of
2217 preexisting node size. This is appropriate when are
2218 adding new nodes.
2219 
2220 When reusing preexisting nodes, provide a nodeOffset_ argument > -1
2221 
2222 **/
2223 
2224 CSGPrim* CSGFoundry::addPrim(int num_node, int nodeOffset_ )
2225 {
2226     LOG_IF(fatal, !last_added_solid) << "must addSolid prior to addPrim" ;
2227     assert( last_added_solid );
2228 
2229     unsigned primOffset = last_added_solid->primOffset ;
2230     unsigned numPrim    = last_added_solid->numPrim ;
2231 
2232     unsigned globalPrimIdx = prim.size();
2233     unsigned localPrimIdx = globalPrimIdx - primOffset ;   // index of added prim within the currently "open" solid
2234     int nodeOffset = nodeOffset_ < 0 ? int(node.size()) : nodeOffset_ ;
2235 
2236     bool in_global_range = globalPrimIdx < IMAX ;
2237     bool in_local_range = localPrimIdx < numPrim ;
2238 
2239     LOG_IF(fatal, !in_global_range )
2240         << " TOO MANY addPrim CALLS "
2241         << " globalPrimIdx " << globalPrimIdx
2242         << " IMAX " << IMAX
2243         << " in_global_range " << in_global_range
2244         ;
2245     assert( in_global_range );
2246 
2247     LOG_IF(fatal, !in_local_range)
2248         << " TOO MANY addPrim FOR SOLID "
2249         << " localPrimIdx " << localPrimIdx
2250         << " numPrim " << numPrim
2251         << " globalPrimIdx " << globalPrimIdx
2252         << " in_local_range " << in_local_range
2253         << " (must addPrim only up to to the declared numPrim from the prior addSolid call) "
2254         ;
2255     assert( in_local_range );
2256 
2257 
2258     CSGPrim pr = {} ;
2259 
2260     pr.setNumNode(num_node) ;
2261     pr.setNodeOffset(nodeOffset);
2262     pr.setSbtIndexOffset(localPrimIdx) ;  // NB must be localPrimIdx, globalPrimIdx was a bug
2263     pr.setMeshIdx(-1) ;                // metadata, that may be set by caller
2264 
2265     pr.setTranOffset(tran.size());     // HMM are tranOffset and planOffset used now that use global referencing  ?
2266     pr.setPlanOffset(plan.size());     // but still handy to keep them for debugging
2267 
2268     pr.setGlobalPrimIdx(globalPrimIdx); // recent addition
2269 
2270     // setPrimIdx invoked from caller CSGImport::importPrim
2271 
2272 
2273     prim.push_back(pr);
2274 
2275     last_added_prim = prim.data() + globalPrimIdx ;
2276     last_added_node = nullptr ;
2277 
2278     return last_added_prim  ;
2279 }
2280 
2281 
2282 /**
2283 CSGFoundry::getMeshPrimCopies
2284 ------------------------------
2285 
2286 Collect Prims with the supplied mesh_idx
2287 
2288 **/
2289 void CSGFoundry::getMeshPrimCopies(std::vector<CSGPrim>& select_prim, unsigned mesh_idx ) const
2290 {
2291     CSGPrim::select_prim_mesh(prim, select_prim, mesh_idx);
2292 }
2293 
2294 void CSGFoundry::getMeshPrimPointers(std::vector<const CSGPrim*>& select_prim, unsigned mesh_idx ) const
2295 {
2296     CSGPrim::select_prim_pointers_mesh(prim, select_prim, mesh_idx);
2297 }
2298 
2299 /**
2300 CSGFoundry::getMeshPrim
2301 ------------------------
2302 
2303 Selects prim pointers that match the *midx* mesh index
2304 and then return the ordinal-th one of them.
2305 
2306 midx
2307     mesh index
2308 mord
2309     mesh ordinal
2310 
2311 **/
2312 
2313 const CSGPrim* CSGFoundry::getMeshPrim( unsigned midx, unsigned mord ) const
2314 {
2315     std::vector<const CSGPrim*> select_prim ;
2316     getMeshPrimPointers(select_prim, midx );
2317 
2318     bool mord_in_range = mord < select_prim.size() ;
2319     if(!mord_in_range)
2320     {
2321         LOG(error)  << " midx " << midx << " mord " << mord << " select_prim.size " << select_prim.size() << " mord_in_range " << mord_in_range ;
2322         return nullptr ;
2323     }
2324 
2325     const CSGPrim* pr = select_prim[mord] ;
2326     return pr ;
2327 }
2328 
2329 /**
2330 CSGFoundry::getNumMeshPrim
2331 -----------------------------
2332 
2333 Returns the number of prim with the *mesh_idx* in entire geometry.
2334 Using CSGPrim::count_prim_mesh
2335 
2336 The MOI mesh-ordinal values must always be less than the number of mesh prim.
2337 
2338 **/
2339 unsigned CSGFoundry::getNumMeshPrim(unsigned mesh_idx ) const
2340 {
2341     return CSGPrim::count_prim_mesh(prim, mesh_idx);
2342 }
2343 
2344 
2345 /**
2346 CSGFoundry::getNumSelectedPrimInSolid
2347 --------------------------------------
2348 
2349 Used by CSGCopy::copy
2350 
2351 Iterates over the CSGPrim within the CSGSolid counting the
2352 number selected based on whether the CSGPrim::meshIdx
2353 is within the elv SBitSet.
2354 
2355 
2356 **/
2357 
2358 unsigned CSGFoundry::getNumSelectedPrimInSolid(const CSGSolid* solid, const SBitSet* elv ) const
2359 {
2360     unsigned num_selected_prim = 0 ;
2361     for(int primIdx=solid->primOffset ; primIdx < solid->primOffset+solid->numPrim ; primIdx++)
2362     {
2363         const CSGPrim* pr = getPrim(primIdx);
2364         unsigned meshIdx = pr->meshIdx() ;
2365         bool selected = elv == nullptr ? true : elv->is_set(meshIdx) ;
2366         num_selected_prim += int(selected) ;
2367     }
2368     return num_selected_prim ;
2369 }
2370 
2371 
2372 /**
2373 CSGFoundry::descMeshPrim
2374 --------------------------
2375 
2376 Presents table:
2377 
2378 +----------+------------------+---------------+
2379 | midx     |   numMeshPrim    |   meshName    |
2380 +----------+------------------+---------------+
2381 
2382 
2383 midx
2384    mesh index corresponding to lvIdx
2385 
2386 numMeshPrim
2387    number of prim in entire geometry with this midx
2388 
2389 meshName
2390    name coming from the source geometry
2391 
2392 
2393 Notice that the meshName might not be unique, it is
2394 merely obtained from the source geometry solid name.
2395 In this case meshName addressing is not very useful
2396 and it is necessary to address using the midx.
2397 
2398 **/
2399 
2400 std::string CSGFoundry::descMeshPrim() const
2401 {
2402     std::stringstream ss ;
2403     unsigned numName = id->getNumName();
2404     ss
2405         << "CSGFoundry::descMeshPrim  id.numName " << numName << std::endl
2406         << std::setw(4) << "midx"
2407         << " "
2408         << std::setw(12) << "numMeshPrim"
2409         << " "
2410         << "meshName"
2411         << std::endl
2412         ;
2413 
2414     for(unsigned midx=0 ; midx < numName ; midx++)
2415     {
2416         const char* meshName = id->getName(midx);
2417         unsigned numMeshPrim = getNumMeshPrim(midx);
2418         ss
2419             << std::setw(4) << midx
2420             << " "
2421             << std::setw(12) << numMeshPrim
2422             << " "
2423             << meshName
2424             << std::endl
2425             ;
2426     }
2427     return ss.str();
2428 }
2429 
2430 std::string CSGFoundry::descPrimRange() const
2431 {
2432     int num_solid = solid.size();
2433     std::stringstream ss ;
2434     ss << "[CSGFoundry::descPrimRange" << " num_solid " << std::setw(2) << num_solid << "\n" ;
2435     for(int i=0 ; i < num_solid ; i++ ) ss << descPrimRange(i) ;
2436     ss << "[CSGFoundry::descPrimRange" << " num_solid " << std::setw(2) << num_solid << "\n" ;
2437     std::string str = ss.str() ;
2438     return str ;
2439 }
2440 
2441 
2442 
2443 
2444 std::string CSGFoundry::descPrimRange(int solidIdx) const
2445 {
2446     const CSGSolid& so = solid[solidIdx];
2447     std::string dr = CSGPrim::DescRange(prim.data(), so.primOffset, so.numPrim, &meshname) ;
2448     if(strlen(dr.c_str()) == 0) return "" ;
2449 
2450     std::stringstream ss ;
2451     ss
2452        << "[CSGFoundry::descPrimRange" << " solidIdx " << std::setw(2) << solidIdx
2453        << " so.primOffset " << std::setw(5) << so.primOffset
2454        << " so.numPrim " << std::setw(5) << so.numPrim
2455        << "\n"
2456        << dr
2457        << "\n"
2458        << "]CSGFoundry::descPrimRange" << " solidIdx " << std::setw(2) << solidIdx
2459        << "\n"
2460        ;
2461 
2462     std::string str = ss.str() ;
2463     return str ;
2464 }
2465 
2466 
2467 std::string CSGFoundry::comparePrimRange(int solidIdx, const SMeshGroup* mg) const
2468 {
2469     const CSGSolid& so = solid[solidIdx];
2470     std::stringstream ss ;
2471     ss
2472        << "[CSGFoundry::comparePrimRange" << " solidIdx " << std::setw(2) << solidIdx
2473        << " so.primOffset " << std::setw(5) << so.primOffset
2474        << " so.numPrim " << std::setw(5) << so.numPrim
2475        << " mg " << ( mg ? "YES" : "NO " )
2476        << "\n"
2477        ;
2478 
2479     ss << CSGPrim::DescRange(prim.data(), so.primOffset, so.numPrim, &meshname, mg );
2480 
2481     ss << "]CSGFoundry::comparePrimRange" << " solidIdx " << std::setw(2) << solidIdx
2482        << "\n"
2483        ;
2484 
2485     std::string str = ss.str() ;
2486     return str ;
2487 }
2488 
2489 
2490 
2491 
2492 
2493 
2494 
2495 
2496 
2497 
2498 
2499 
2500 
2501 
2502 
2503 
2504 
2505 
2506 
2507 /**
2508 CSGFoundry::addSolid
2509 ----------------------
2510 
2511 The Prim offset is persisted into the CSGSolid
2512 thus must addSolid prior to adding any prim
2513 for the solid.
2514 
2515 The default primOffset_ argument of -1 signifies are about to
2516 add fresh Prim and need to obtain the primOffset for the added solid
2517 from the number of prim that have been collected previously.
2518 
2519 Using a primOffset_ > -1 indicates that the added solid is reusing
2520 already existing Prim (eg for debugging) and the primOffset should be
2521 set from this argument.
2522 
2523 **/
2524 
2525 CSGSolid* CSGFoundry::addSolid(unsigned numPrim, const char* label, int primOffset_ )
2526 {
2527     unsigned idx = solid.size();
2528 
2529     assert( idx < IMAX );
2530 
2531     int primOffset = primOffset_ < 0 ? prim.size() : primOffset_ ;
2532 
2533     CSGSolid so = CSGSolid::Make( label, numPrim , primOffset );
2534 
2535     solid.push_back(so);
2536 
2537     last_added_solid = solid.data() + idx  ;  // retain last_added_solid for getting the solid local primIdx
2538     last_added_prim = nullptr ;
2539     last_added_node = nullptr ;
2540 
2541     return last_added_solid ;
2542 }
2543 
2544 
2545 
2546 /**
2547 CSGFoundry::addDeepCopySolid
2548 -------------------------------
2549 
2550 Used only from CSG_GGeo_Convert::addDeepCopySolid
2551 
2552 
2553 TODO: will probably want to always add transforms as the point of making
2554 deep copies is to allow making experimental changes to the copies
2555 eg for applying progressive shrink scaling to check whether problems are caused
2556 by bbox being too close to each other
2557 
2558 
2559 
2560 **/
2561 CSGSolid* CSGFoundry::addDeepCopySolid(unsigned solidIdx, const char* label )
2562 {
2563     std::string cso_label = label ? label : CSGSolid::MakeLabel('d', solidIdx) ;
2564 
2565     LOG(info) << " cso_label " << cso_label ;
2566     std::cout << " cso_label " << cso_label << std::endl ;
2567 
2568     const CSGSolid* oso = getSolid(solidIdx);
2569     unsigned numPrim = oso->numPrim ;
2570 
2571     AABB solid_bb = {} ;
2572     CSGSolid* cso = addSolid(numPrim, cso_label.c_str());
2573     cso->type = DEEP_COPY_SOLID ;
2574 
2575     for(int primIdx=oso->primOffset ; primIdx < oso->primOffset+oso->numPrim ; primIdx++)
2576     {
2577         const CSGPrim* opr = prim.data() + primIdx ;
2578 
2579         unsigned numNode = opr->numNode()  ;
2580         int nodeOffset_ = -1 ; // as deep copying, -1 declares that will immediately add numNode new nodes
2581 
2582         AABB prim_bb = {} ;
2583         CSGPrim* cpr = addPrim(numNode, nodeOffset_ );
2584 
2585         cpr->setMeshIdx(opr->meshIdx());    // copy the metadata that makes sense to be copied
2586         cpr->setRepeatIdx(opr->repeatIdx());
2587         cpr->setPrimIdx(opr->primIdx());
2588         cpr->setGlobalPrimIdx(opr->globalPrimIdx());
2589 
2590         for(int nodeIdx=opr->nodeOffset() ; nodeIdx < opr->nodeOffset()+opr->numNode() ; nodeIdx++)
2591         {
2592             const CSGNode* ond = node.data() + nodeIdx ;
2593             unsigned o_tranIdx = ond->gtransformIdx();
2594 
2595             CSGNode cnd = {} ;
2596             CSGNode::Copy(cnd, *ond );   // straight copy reusing the transform reference
2597 
2598             const qat4* tra = nullptr ;
2599             const qat4* itr = nullptr ;
2600             unsigned c_tranIdx = 0u ;
2601 
2602             if( o_tranIdx > 0 )
2603             {
2604                 tra = getTran(o_tranIdx-1u) ;
2605                 itr = getItra(o_tranIdx-1u) ;
2606             }
2607             else if( deepcopy_everynode_transform )
2608             {
2609                 tra = new qat4 ;
2610                 itr = new qat4 ;
2611             }
2612 
2613             if( tra && itr )
2614             {
2615                 c_tranIdx = 1 + addTran(tra, itr);  // add fresh transforms, as this is deep copy
2616                 std::cout
2617                     << " o_tranIdx " << o_tranIdx
2618                     << " c_tranIdx " << c_tranIdx
2619                     << " deepcopy_everynode_transform " << deepcopy_everynode_transform
2620                     << std::endl
2621                     ;
2622                 std::cout << " tra  " << tra->desc('t') << std::endl ;
2623                 std::cout << " itr  " << itr->desc('i') << std::endl ;
2624             }
2625 
2626 
2627             // TODO: fix this in CSGNode
2628             bool c0 = cnd.is_complement();
2629             //cnd.zeroTransformComplement();
2630             //cnd.setComplement(c0) ;
2631             //cnd.setTransform( c_tranIdx );
2632             cnd.setTransformComplement(c_tranIdx, c0);
2633 
2634             unsigned c_tranIdx2 = cnd.gtransformIdx() ;
2635 
2636             bool match = c_tranIdx2 == c_tranIdx ;
2637             if(!match) std::cout << "set/get transform fail c_tranIdx " << c_tranIdx << " c_tranIdx2 " << c_tranIdx2 << std::endl ;
2638             assert(match);
2639 
2640 
2641             cnd.setAABBLocal() ;  // reset to local with no transform applied
2642             if(tra)
2643             {
2644                 tra->transform_aabb_inplace( cnd.AABB() );
2645             }
2646             prim_bb.include_aabb( cnd.AABB() );
2647             addNode(cnd);
2648         }                  // over nodes of the Prim
2649 
2650         cpr->setAABB(prim_bb.data());
2651         solid_bb.include_aabb(prim_bb.data()) ;
2652     }    // over Prim of the Solid
2653 
2654     cso->center_extent = solid_bb.center_extent() ;
2655     return cso ;
2656 }
2657 
2658 
2659 void CSGFoundry::DumpAABB(const char* msg, const float* aabb) // static
2660 {
2661     int w = 4 ;
2662     LOG(info) << msg << " " ;
2663     LOG(info) << " | " ;
2664     for(int l=0 ; l < 3 ; l++) LOG(info) << std::setw(w) << *(aabb+l) << " " ;
2665     LOG(info) << " | " ;
2666     for(int l=0 ; l < 3 ; l++) LOG(info) << std::setw(w) << *(aabb+l+3) << " " ;
2667     LOG(info) << " | " ;
2668     for(int l=0 ; l < 3 ; l++) LOG(info) << std::setw(w) << *(aabb+l+3) - *(aabb+l)  << " " ;
2669     LOG(info) ;
2670 }
2671 
2672 
2673 const char* CSGFoundry::BASE = "$DefaultGeometryDir" ; // incorporates GEOM if defined
2674 const char* CSGFoundry::RELDIR = "CSGFoundry"  ;
2675 
2676 /**
2677 CSGFoundry::getBaseDir
2678 -------------------------
2679 
2680 Returns value of CFBASE envvar if defined, otherwise resolves '$DefaultOutputDir' which
2681 is for example /tmp/$USER/opticks/$GEOM/SProc::ExecutableName
2682 
2683 **/
2684 
2685 const char* CSGFoundry::getBaseDir(bool create) const
2686 {
2687     const char* cfbase_default = SPath::Resolve(BASE, create ? DIRPATH : NOOP );  //
2688     const char* cfbase = ssys::getenvvar("CFBASE", cfbase_default );
2689     return cfbase ? strdup(cfbase) : nullptr ;
2690 }
2691 
2692 void CSGFoundry::save() const
2693 {
2694     const char* cfbase = getBaseDir(true) ;
2695     if( cfbase == nullptr )
2696     {
2697         LOG(fatal) << "cannot save unless CFBASE envvar defined or geom has been set " ;
2698         return ;
2699     }
2700     LOG(LEVEL) << " cfbase " << cfbase ;
2701     save(cfbase, RELDIR );
2702 }
2703 
2704 
2705 
2706 
2707 void CSGFoundry::save(const char* base, const char* rel) const
2708 {
2709     if(rel == nullptr) rel = RELDIR ;
2710     std::stringstream ss ;
2711     ss << base << "/" << rel ;
2712     std::string dir = ss.str();
2713     save_(dir.c_str());
2714 }
2715 
2716 /**
2717 CSGFoundry::saveAlt
2718 -----------------------
2719 
2720 Write geometry to $CFBaseAlt/CSGFoundry currently used as workaround so
2721 that the python view of dynamic prim selection geometry can match
2722 the actual uploaded geometry.
2723 
2724 See notes/issues/primIdx-and-skips.rst
2725 **/
2726 
2727 void CSGFoundry::saveAlt() const
2728 {
2729     const char* _path = "$CFBASE_ALT" ;
2730     const char* path = spath::Resolve(_path);
2731     bool unresolved = spath::LooksUnresolved( path, _path );
2732 
2733     LOG(LEVEL)
2734         << " _path[" << ( _path ? _path : "-" )
2735         << " path["  << (  path ?  path : "-" )
2736         << " unresolved " << ( unresolved ? "YES" : "NO " )
2737         ;
2738 
2739     LOG_IF(fatal, unresolved) << "cannot saveAlt as CFBASE_ALT unresolved" ;
2740     if(unresolved) return ;
2741 
2742     const char* cfbase_alt = path ;
2743     LOG(info)
2744         << " cfbase "     << ( cfbase     ? cfbase : "-" )
2745         << " cfbase_alt " << ( cfbase_alt ? cfbase_alt : "-" )
2746         ;
2747 
2748     save(cfbase_alt, RELDIR);
2749 }
2750 
2751 
2752 /**
2753 CSGFoundry::save_
2754 ------------------
2755 
2756 * TODO : adopt NPFold for doing this
2757 
2758 Have observed that whilst changing geometry this can lead to "mixed" exports
2759 with the contents of CSGFoundry directory containing arrays from multiple exports.
2760 The inconsistency causes crashes.
2761 
2762 TODO: find way to avoid this, by deleting the folder ahead : or asserting on consistent time stamps
2763 on loading
2764 
2765 **/
2766 void CSGFoundry::save_(const char* dir_) const
2767 {
2768     const char* dir = SPath::Resolve(dir_, DIRPATH);
2769     LOG(LEVEL) << dir ;
2770 
2771     if(meshname.size() > 0 && save__("meshname")) NP::WriteNames( dir, "meshname.txt", meshname );
2772 
2773     std::vector<std::string> primname ;
2774     getPrimName(primname);
2775     if(primname.size() > 0 && save__("primname")) NP::WriteNames( dir, "primname.txt", primname );
2776 
2777     if(mmlabel.size() > 0 && save__("mmlabel"))  NP::WriteNames( dir, "mmlabel.txt", mmlabel );
2778     if(hasMeta() && save__("meta"))  U::WriteString( dir, "meta.txt", meta.c_str() );
2779 
2780     if(solid.size() > 0 && save__("solid")) NP::Write(dir, "solid.npy",  (int*)solid.data(),  solid.size(), 3, 4 );
2781     if(prim.size() > 0 && save__("prim"))  NP::Write(dir, "prim.npy",   (float*)prim.data(), prim.size(),   4, 4 );
2782     if(node.size() > 0 && save__("node"))  NP::Write(dir, "node.npy",   (float*)node.data(), node.size(),   4, 4 );
2783     if(plan.size() > 0 && save__("plan")) NP::Write(dir, "plan.npy",   (float*)plan.data(), plan.size(),   1, 4 );
2784     if(tran.size() > 0 && save__("tran")) NP::Write(dir, "tran.npy",   (float*)tran.data(), tran.size(),   4, 4 );
2785     if(itra.size() > 0 && save__("itra")) NP::Write(dir, "itra.npy",   (float*)itra.data(), itra.size(),   4, 4 );
2786     if(inst.size() > 0 && save__("inst")) NP::Write(dir, "inst.npy",   (float*)inst.data(), inst.size(),   4, 4 );
2787 
2788 
2789     if(sim && save__("SSim"))
2790     {
2791         LOG(LEVEL) << " SSim::save " << dir ;
2792         const_cast<SSim*>(sim)->save(dir, SSim::RELDIR );
2793     }
2794     else
2795     {
2796         LOG(LEVEL) << " CANNOT SSim::save AS sim null " ;
2797     }
2798 }
2799 
2800 
2801 bool CSGFoundry::save__(const char* elem) const
2802 {
2803     return save_opt == nullptr ? true : ( strstr(save_opt, elem) != nullptr ) ;
2804 }
2805 
2806 void CSGFoundry::setSaveOpt(const char* save_opt_)
2807 {
2808     save_opt = save_opt_ ? strdup(save_opt_) : nullptr ;
2809 }
2810 const char* CSGFoundry::getSaveOpt() const
2811 {
2812     return save_opt ;
2813 }
2814 
2815 
2816 
2817 template <typename T> void CSGFoundry::setMeta( const char* key, T value ){ NP::SetMeta(meta, key, value ); }
2818 
2819 template void CSGFoundry::setMeta<int>(const char*, int );
2820 template void CSGFoundry::setMeta<unsigned>(const char*, unsigned );
2821 template void CSGFoundry::setMeta<float>(const char*, float );
2822 template void CSGFoundry::setMeta<double>(const char*, double );
2823 template void CSGFoundry::setMeta<std::string>(const char*, std::string );
2824 
2825 template <typename T> T CSGFoundry::getMeta( const char* key, T fallback){ return NP::GetMeta(meta, key, fallback );  }
2826 
2827 template int         CSGFoundry::getMeta<int>(const char*, int );
2828 template unsigned    CSGFoundry::getMeta<unsigned>(const char*, unsigned );
2829 template float       CSGFoundry::getMeta<float>(const char*, float );
2830 template double      CSGFoundry::getMeta<double>(const char*, double );
2831 template std::string CSGFoundry::getMeta<std::string>(const char*, std::string );
2832 
2833 bool CSGFoundry::hasMeta() const {  return meta.empty() == false ; }
2834 
2835 
2836 void CSGFoundry::load()
2837 {
2838     const char* cfbase = getBaseDir(false) ;
2839     if( cfbase == nullptr )
2840     {
2841         LOG(fatal) << "cannot load unless CFBASE envvar defined or geom has been set " ;
2842         return ;
2843     }
2844     load(cfbase, RELDIR );
2845 }
2846 
2847 
2848 
2849 
2850 const char* CSGFoundry::load_FAIL_base_null_NOTES = R"(
2851 CSGFoundry::load_FAIL_base_null_NOTES
2852 ======================================
2853 
2854 You appear to be attempting to load a geometry folder that does not exist.
2855 Perhaps this is due to incorrect envvars or the folder really does not exist.
2856 Scripts that can create geometry folders include:
2857 
2858 * CSG/CSGMakerTest.sh
2859 
2860 CSGMaker saves a CSGFoundry geometry that was authored directly in CSG.
2861 
2862 Attempting to run an executable directly rather than the script
2863 that sets up the environment can cause such errors.
2864 
2865 This error can also happen when attempting to load event+geometry
2866 that was previously written to directories below /tmp
2867 but has subsequently been "cleaned" leaving the directory structure
2868 but with all the directories emptied.
2869 
2870 Relevant envvars : CFBASE and GEOM
2871 
2872 )" ;
2873 const char* CSGFoundry::LoadFailNotes(){ return load_FAIL_base_null_NOTES ; } // static
2874 
2875 /**
2876 CSGFoundry::load
2877 ------------------
2878 
2879 1. requires non-null base_ directory
2880 2. asserts that conventional rel directory of CSGFoundry is used
2881 3. records the resolved cfbase
2882 4. invokes load of "<cfbase>/CSGFoundry" directory
2883 
2884 **/
2885 
2886 
2887 void CSGFoundry::load(const char* base_, const char* rel)
2888 {
2889     LOG_IF(error, base_ == nullptr) << load_FAIL_base_null_NOTES ;
2890     assert(base_);
2891     bool conventional = strcmp( rel, RELDIR) == 0  ;
2892     LOG_IF(fatal, !conventional) << "Convention is for rel to be named [" << RELDIR << "] not: [" << rel << "]"  ;
2893     assert(conventional);
2894 
2895     const char* base = SPath::Resolve(base_, NOOP);
2896     setCFBase(base);
2897 
2898     std::stringstream ss ;
2899     ss << base << "/" << rel ;
2900     std::string dir = ss.str();
2901 
2902     load(dir.c_str());
2903 }
2904 
2905 void CSGFoundry::setCFBase( const char* cfbase_ )
2906 {
2907     cfbase = strdup(cfbase_);
2908 }
2909 const char* CSGFoundry::getCFBase() const
2910 {
2911     return cfbase ;
2912 }
2913 const char* CSGFoundry::getOriginCFBase() const
2914 {
2915     // HMM: maybe just pass the pointer, as keep forgetting about this
2916     // NOPE: that would be wrong need to save into a new cfbase
2917     // for consistency between results and geometry
2918 
2919     LOG(LEVEL) << " CAUTION HOW YOU USE THIS : MISUSE CAN EASILY LEAD TO INCONSISTENCY BETWEEN RESULTS AND GEOMETRY " ;
2920     return origin ? origin->cfbase : cfbase ;
2921 }
2922 
2923 
2924 
2925 std::string CSGFoundry::descBase() const
2926 {
2927     const char* cfb = getCFBase();
2928     const char* ocfb = getOriginCFBase();
2929     std::stringstream ss ;
2930     ss << "[CSGFoundry.descBase \n"
2931        << " CFBase       " << ( cfb ? cfb : "-" ) << "\n"
2932        << " OriginCFBase " << ( ocfb ? ocfb : "-" ) << "\n"
2933        << "]CSGFoundry.descBase \n"
2934        ;
2935     return ss.str();
2936 }
2937 
2938 
2939 
2940 /**
2941 CSGFoundry::load
2942 ------------------
2943 
2944 **/
2945 
2946 
2947 const char* CSGFoundry::LOAD_FAIL_NOTES = R"LITERAL(
2948 CSGFoundry::LOAD_FAIL_NOTES
2949 ==============================
2950 
2951 The CSGFoundry directory does not exist. To create it you probably need to
2952 run one of several CSGFoundry creating scripts. Which one to use depends on
2953 what the geometry is that you want to create. Some of the scripts require
2954 export the GEOM envvar within $HOME/.opticks/GEOM/GEOM.sh to pick between
2955 different geometries.
2956 
2957 CSG/CSGMakerTest.sh
2958     CSG level creation of simple test CSGFoundry
2959 
2960 G4CX/G4CXTest.sh
2961     Creates Geant4 geometry translates and saves into CSGFoundry
2962 
2963 
2964 )LITERAL" ;
2965 
2966 
2967 /**
2968 CSGFoundry::load
2969 -----------------
2970 
2971 TODO: adopt NPFold
2972 
2973 **/
2974 
2975 void CSGFoundry::load( const char* dir_ )
2976 {
2977     const char* dir = spath::Resolve(dir_);
2978     bool readable = spath::is_readable(dir);
2979 
2980     LOG_IF(fatal, !readable )
2981        << " dir-not-readable "
2982        << " dir_ [" << ( dir_ ? dir_ : "-" ) << "]"
2983        << " dir [" << ( dir ? dir : "-" ) << "]"
2984        << "\n"
2985        << LOAD_FAIL_NOTES
2986        ;
2987 
2988     assert(readable) ;
2989     if( !readable ) return ;
2990 
2991     loaddir = strdup(dir) ;
2992     LOG(LEVEL) << "[ loaddir " << loaddir ;
2993 
2994     NP::ReadNames( dir, "meshname.txt", meshname );
2995     NP::ReadNames( dir, "mmlabel.txt", mmlabel );
2996 
2997     const char* meta_str = U::ReadString( dir, "meta.txt" ) ;
2998     if(meta_str)
2999     {
3000        meta = meta_str ;
3001     }
3002     else
3003     {
3004        LOG(warning) << " no meta.txt at " << dir ;
3005     }
3006 
3007     loadArray( solid , dir, "solid.npy" );
3008     loadArray( prim  , dir, "prim.npy" );
3009     loadArray( node  , dir, "node.npy" );
3010     loadArray( tran  , dir, "tran.npy" );
3011     loadArray( itra  , dir, "itra.npy" );
3012     loadArray( inst  , dir, "inst.npy" );
3013     loadArray( plan  , dir, "plan.npy" , true );
3014     // plan.npy loading optional, as only geometries with convexpolyhedrons such as trapezoids, tetrahedrons etc.. have them
3015 
3016 
3017     mtime = MTime(dir, "solid.npy");
3018 
3019     LOG(LEVEL) << "] loaddir " << loaddir ;
3020 }
3021 
3022 
3023 /**
3024 CSGFoundry::loadAux
3025 ----------------------
3026 
3027 Load array from a cfbase relative path
3028 
3029 **/
3030 
3031 NP* CSGFoundry::loadAux(const char* auxrel ) const
3032 {
3033     const char* auxpath = cfbase ? SPath::Join(cfbase, auxrel ) : nullptr ;
3034     NP* aux = auxpath && NP::Exists(auxpath) ? NP::Load(auxpath) : nullptr ;
3035     return aux ;
3036 }
3037 
3038 
3039 /**
3040 CSGFoundry::MTime
3041 -------------------
3042 
3043 Use STime::Format(mtime) to convert the int into a timestamp string
3044 
3045 **/
3046 int CSGFoundry::MTime(const char* dir, const char* fname_) // static
3047 {
3048     const char* fname = fname_ ? fname_ : "solid.npy" ;
3049     const char* path = SPath::Resolve(dir, fname, NOOP);
3050     return SPath::mtime(path);
3051 }
3052 
3053 
3054 
3055 
3056 
3057 void CSGFoundry::setGeom(const char* geom_)  // setGeomName would be clearer
3058 {
3059     geom = geom_ ? strdup(geom_) : nullptr ;
3060 }
3061 void CSGFoundry::setOrigin(const CSGFoundry* origin_)
3062 {
3063     origin = origin_ ;
3064 }
3065 void CSGFoundry::setElv(const SBitSet* elv_)
3066 {
3067     elv = elv_ ;
3068 }
3069 
3070 
3071 
3072 /**
3073 CSGFoundry::descELV
3074 ---------------------
3075 
3076 TODO: short description of the partial geometry, midx and names of the included when an only
3077 or the excluded midx and name when an exclusion geometry. To be included into CSGFoundry
3078 metadata and copied into render metadata for table presentation.
3079 
3080 **/
3081 
3082 const char* CSGFoundry::descELV() const
3083 {
3084     std::string str = SBitSet::Brief(elv, id );
3085     return strdup(str.c_str()) ;
3086 }
3087 
3088 
3089 
3090 
3091 
3092 /**
3093 CSGFoundry::CreateFromSim
3094 ---------------------------
3095 
3096 Instanciation grabs the (SSim)sim instance
3097 
3098 **/
3099 
3100 
3101 CSGFoundry* CSGFoundry::CreateFromSim()
3102 {
3103     assert(SSim::Get() != nullptr);
3104     CSGFoundry* fd = new CSGFoundry ;
3105     fd->importSim();
3106     return fd ;
3107 }
3108 
3109 
3110 
3111 /**
3112 CSGFoundry::Load
3113 -------------------
3114 
3115 This argumentless Load method is special, unlike other methods
3116 it provides dynamic prim selection based on the ELV envvar which uses
3117 CSGFoundry::CopySelect to dynamically create a CSGFoundry based
3118 on the elv SBitSet
3119 
3120 Dynamic prim selection (using ELV) without saving the CSGFoundry of the modified geometry
3121 can be useful for render performance scanning to find geometry bottlenecks
3122 but it is just not appropriate when wishing to run multiple executables over the same geometry
3123 and do detailed analysis of the results. In this situation it is vital to have a more constant
3124 CSGFoundry geometry folder that is read by multiple executables including rendering and
3125 python analysis machinery.
3126 
3127 This is taking 0.48s for full JUNO, thats 27% of single event gxt.sh runtime
3128 
3129 
3130 Q: Where is the SSim handover ? How to apply selection to the SScene ?
3131 
3132 
3133 **/
3134 
3135 bool CSGFoundry::Load_saveAlt = ssys::getenvbool("CSGFoundry_Load_saveAlt") ;
3136 
3137 CSGFoundry* CSGFoundry::Load() // static
3138 {
3139     SProf::Add("CSGFoundry__Load_HEAD");
3140 
3141 
3142 
3143     LOG(LEVEL) << "[ argumentless " ;
3144     CSGFoundry* src = CSGFoundry::Load_() ;
3145     if(src == nullptr) return nullptr ;
3146 
3147     SGeoConfig::GeometrySpecificSetup(src->id);
3148 
3149     // convert ELV envvar with include/exclude names into bitset indicating selected lvid using the list of names
3150     const SBitSet* elv = SGeoConfig::ELV(src->id);
3151 
3152     // use the bitset to partially copy geometry
3153     CSGFoundry* dst = elv ? CSGFoundry::CopySelect(src, elv) : src  ;
3154 
3155 
3156     if(elv)
3157     {
3158         LOG(LEVEL) << " apply ELV selection to triangulated SScene " ;
3159         SScene* src_sc = dst->sim->scene ;
3160         SScene* dst_sc = src_sc->copy(elv);
3161         const_cast<SSim*>(dst->sim)->set_override_scene(dst_sc);
3162     }
3163 
3164 
3165     if( elv != nullptr && Load_saveAlt)
3166     {
3167         LOG(error) << " non-standard dynamic selection CSGFoundry_Load_saveAlt " ;
3168         dst->saveAlt() ;
3169     }
3170 
3171 
3172     //AfterLoadOrCreate();  // TRY TO REPLACE WITH SSim::afterLoadOrCreate
3173 
3174     LOG(LEVEL) << "] argumentless " ;
3175     SProf::Add("CSGFoundry__Load_TAIL");
3176     return dst ;
3177 }
3178 
3179 
3180 
3181 /**
3182 CSGFoundry::CopySelect
3183 -------------------------
3184 
3185 This is used from the argumentless CSGFoundry::Load
3186 
3187 Using CSGCopy::Select creates a partial geometry with some solids
3188 included/excluded according to the elv SBitSet specification, that
3189 is normally configured by ELV envvar.
3190 
3191 The SSim pointer from the loaded src instance,
3192 overriding the empty dst SSim instance.
3193 
3194 Notice that the stree that the SSim contains is not changed by
3195 this CSGFoundry level dynamic geometry selection, so
3196 the stree::get_tree_digest will not change as a result of the
3197 ELV geometry selection. Due to this issue, added stree::make_tree_digest
3198 that is used from SSim::AnnotateFrame which includes the elv
3199 SBitSet info into the dynamically formed digest.
3200 
3201 **/
3202 
3203 CSGFoundry* CSGFoundry::CopySelect(const CSGFoundry* src, const SBitSet* elv )
3204 {
3205     LOG(LEVEL) << "[" ;
3206     assert(elv);
3207     LOG(LEVEL) << elv->desc() << std::endl ;
3208     LOG(LEVEL) << src->descELV(elv) ;
3209     LOG(LEVEL) << src->descELV2(elv) ;
3210 
3211     CSGFoundry* dst = CSGCopy::Select(src, elv );
3212     dst->setOrigin(src);
3213     dst->setElv(elv);
3214     dst->setOverrideSim(src->sim);
3215 
3216     LOG(LEVEL) << "]" ;
3217     return dst ;
3218 }
3219 
3220 
3221 
3222 /**
3223 CSGFoundry::ResolveCFBase
3224 ---------------------------
3225 
3226 When GEOM and "GEOM"_CFBaseFromGEOM are defined that directory is used
3227 The former use of CFBASE envvar with SOpticksResource::CFBase is removed.
3228 
3229 **/
3230 
3231 const char* CSGFoundry::ResolveCFBase()
3232 {
3233     const char* cfbase = spath::CFBaseFromGEOM();
3234     bool readable = spath::is_readable(cfbase, "CSGFoundry" );
3235     LOG_IF(fatal, !readable) << " cfbase/CSGFoundry directory [" << cfbase << "]/CSGFoundry" << " IS NOT READABLE " ;
3236     return readable ? cfbase : nullptr ;
3237 }
3238 
3239 
3240 
3241 
3242 
3243 /**
3244 CSGFoundry::Load_
3245 -------------------
3246 
3247 HMM: this is expecting to load preexisting SSim + CSGFoundry
3248 from filesystem. Its also possible to create CSGFoundry from SSim
3249 using CSGImport functionality using CSGFoundry::CreateFromSim and CSGFoundry::importSim
3250 
3251 HMM: this means do not really need to persist CSGFoundry : however
3252 its very useful for debugging and access to geometry info, so
3253 will continue to do so for now.
3254 
3255 
3256 * PREVIOUSLY SSim WAS GETTING LOADED TWICE ?
3257   with the second SSim::Load at tail of CSGFoundry::load
3258   Try removing that.
3259 
3260 
3261 **/
3262 
3263 CSGFoundry* CSGFoundry::Load_() // static
3264 {
3265     const char* cfbase = ResolveCFBase() ;
3266     if(ssys::getenvbool(_Load_DUMP)) std::cout << "CSGFoundry::Load_[" << cfbase << "]\n" ;
3267 
3268     LOG(LEVEL) << "[ SSim::Load cfbase " << ( cfbase ? cfbase : "-" )  ;
3269     SSim* sim = SSim::Load(cfbase, "CSGFoundry/SSim");
3270     LOG(LEVEL) << "] SSim::Load " ;
3271 
3272     LOG_IF(fatal, sim==nullptr ) << " sim(SSim) required before CSGFoundry::Load " ;
3273     assert(sim);
3274 
3275     CSGFoundry* fd = Load(cfbase, "CSGFoundry");
3276     return fd ;
3277 }
3278 
3279 
3280 
3281 CSGFoundry*  CSGFoundry::Load(const char* base, const char* rel) // static
3282 {
3283     if(base == nullptr) return nullptr ;
3284     CSGFoundry* fd = new CSGFoundry();
3285     fd->load(base, rel);
3286     return fd ;
3287 }
3288 
3289 
3290 void CSGFoundry::setOverrideSim( const SSim* override_sim )
3291 {
3292     sim = override_sim ;
3293 }
3294 const SSim* CSGFoundry::getSim() const
3295 {
3296     return sim ;
3297 }
3298 
3299 
3300 
3301 void CSGFoundry::setFold(const char* fold_)
3302 {
3303     const char* rel = SPath::Basename(fold_);
3304 
3305     bool rel_expect = strcmp( rel, "CSGFoundry" ) == 0 ;
3306     assert( rel_expect );
3307     if(!rel_expect) std::raise(SIGINT);
3308 
3309     fold = strdup(fold_);
3310     cfbase = SPath::Dirname(fold_) ;
3311 }
3312 
3313 const char* CSGFoundry::getFold() const
3314 {
3315     return fold ;
3316 }
3317 
3318 
3319 
3320 
3321 template<typename T>
3322 void CSGFoundry::loadArray( std::vector<T>& vec, const char* dir, const char* name, bool optional )
3323 {
3324     bool exists = NP::Exists(dir, name);
3325     if(optional && !exists ) return ;
3326 
3327     NP* a = NP::Load(dir, name);
3328     if(a == nullptr)
3329     {
3330         LOG(fatal) << "FAIL to load non-optional array  " << dir <<  "/" << name ;
3331         LOG(fatal) << "convert geocache into CSGFoundry model using CSG_GGeo/run.sh " ;
3332         // TODO: the CSGFoundry model should live inside the geocache rather than in tmp to avoid having to redo this frequently
3333         assert(0);
3334     }
3335     else
3336     {
3337         assert( a->shape.size()  == 3 );
3338         unsigned ni = a->shape[0] ;
3339         unsigned nj = a->shape[1] ;
3340         unsigned nk = a->shape[2] ;
3341 
3342         LOG(LEVEL) << " ni " << std::setw(5) << ni << " nj " << std::setw(1) << nj << " nk " << std::setw(1) << nk << " " << name ;
3343 
3344         vec.clear();
3345         vec.resize(ni);
3346         memcpy( vec.data(),  a->bytes(), sizeof(T)*ni );
3347     }
3348 }
3349 
3350 template void CSGFoundry::loadArray( std::vector<CSGSolid>& , const char* , const char*, bool );
3351 template void CSGFoundry::loadArray( std::vector<CSGPrim>& , const char* , const char* , bool );
3352 template void CSGFoundry::loadArray( std::vector<CSGNode>& , const char* , const char* , bool );
3353 template void CSGFoundry::loadArray( std::vector<float4>& , const char* , const char*  , bool );
3354 template void CSGFoundry::loadArray( std::vector<qat4>& , const char* , const char* , bool );
3355 
3356 
3357 /**
3358 CSGFoundry::upload
3359 --------------------
3360 
3361 Canonical invokation from CSGOptiX::Create/CSGOptiX::InitGeo which is done by G4CXOpticks::setGeometry
3362 
3363 Notice that the solid, inst and tran are not uploaded, as they are not needed on GPU.
3364 The reason is that the solid feeds into the GAS, the inst into the IAS and the tran
3365 are not needed because the inverse transforms are all that is needed.
3366 
3367 This currently taking 20s for full JUNO, where total runtime for one event is 24s.
3368 TODO: recall this was optimized down to under 1s, check this.
3369 
3370 As this often needs to be called early from the main, and logging from main is
3371 problematically uncontollable have pragmatically removed most of the logging.
3372 
3373 **/
3374 
3375 void CSGFoundry::upload()
3376 {
3377     inst_find_unique();
3378 
3379     //LOG(LEVEL) << desc() ;
3380 
3381     assert( tran.size() == itra.size() );
3382 
3383 
3384     bool is_uploaded_0 = isUploaded();
3385     LOG_IF(fatal, is_uploaded_0) << "HAVE ALREADY UPLOADED : THIS CANNOT BE DONE MORE THAN ONCE " ;
3386     assert(is_uploaded_0 == false);
3387 
3388     // allocates and copies
3389     d_prim = prim.size() > 0 ? CU::UploadArray<CSGPrim>(prim.data(), prim.size() ) : nullptr ;
3390     d_node = node.size() > 0 ? CU::UploadArray<CSGNode>(node.data(), node.size() ) : nullptr ;
3391     d_plan = plan.size() > 0 ? CU::UploadArray<float4>(plan.data(), plan.size() ) : nullptr ;
3392     d_itra = itra.size() > 0 ? CU::UploadArray<qat4>(itra.data(), itra.size() ) : nullptr ;
3393 
3394     bool is_uploaded_1 = isUploaded();
3395     LOG_IF(fatal, !is_uploaded_1) << "FAILED TO UPLOAD" ;
3396     assert(is_uploaded_1 == true);
3397 
3398 }
3399 
3400 bool CSGFoundry::isUploaded() const
3401 {
3402     return d_prim != nullptr && d_node != nullptr ;
3403 }
3404 
3405 
3406 void CSGFoundry::inst_find_unique()
3407 {
3408     qat4::find_unique_gas( inst, gas );
3409     //qat4::find_unique( inst, ins, gas, sensor_identifier, sensor_index );
3410 }
3411 
3412 unsigned CSGFoundry::getNumUniqueGAS() const
3413 {
3414     return gas.size();
3415 }
3416 unsigned CSGFoundry::getNumUniqueIAS() const
3417 {
3418     return 1 ;
3419 }
3420 
3421 /*
3422 unsigned CSGFoundry::getNumUniqueINS() const
3423 {
3424     return ins.size();
3425 }
3426 */
3427 
3428 
3429 
3430 unsigned CSGFoundry::getNumInstancesIAS(int ias_idx, unsigned long long emm) const
3431 {
3432     return qat4::count_ias(inst, ias_idx, emm );
3433 }
3434 
3435 /**
3436 CSGFoundry::getInstanceTransformsIAS
3437 --------------------------------------
3438 
3439 Canonical usage from SBT::createIAS with all instances being selected.
3440 The index of the *select_inst* corresponds to the iindex.
3441 
3442 The input *inst* vector is populated by CSGFoundry::addInstanceVector
3443 directly from the glm::tmat4x4 transforms from stree
3444 
3445 **/
3446 
3447 
3448 void CSGFoundry::getInstanceTransformsIAS(std::vector<qat4>& select_inst, int ias_idx, unsigned long long emm ) const
3449 {
3450     qat4::select_instances_ias(inst, select_inst, ias_idx, emm ) ;
3451 }
3452 
3453 
3454 unsigned CSGFoundry::getNumInstancesGAS(int gas_idx) const
3455 {
3456     return qat4::count_gas(inst, gas_idx );
3457 }
3458 
3459 void CSGFoundry::getInstanceTransformsGAS(std::vector<qat4>& select_qv, int gas_idx ) const
3460 {
3461     qat4::select_instances_gas(inst, select_qv, gas_idx ) ;
3462 }
3463 
3464 void CSGFoundry::getInstancePointersGAS(std::vector<const qat4*>& select_qi, int gas_idx ) const
3465 {
3466     qat4::select_instance_pointers_gas(inst, select_qi, gas_idx ) ;
3467 }
3468 
3469 /**
3470 CSGFoundry::getInstanceIndex
3471 ------------------------------
3472 
3473 Via searching the inst vector this returns teh absolute instance index of the ordinal-th
3474 instance with the provided gas_idx or -1 if not found.
3475 
3476 Note that this does not help with globals as they are all clumped into instance zero.
3477 
3478 Note that the gas ordinal is confusingly referred to as the iidx in many places.
3479 BUT that iidx is not the global instance index it is the index
3480 within occurences of the gas_idx_ GAS/compositeSolid
3481 
3482 **/
3483 int CSGFoundry::getInstanceIndex(int gas_idx_ , unsigned ordinal) const
3484 {
3485     return qat4::find_instance_gas(inst, gas_idx_, ordinal);
3486 }
3487 
3488 /**
3489 CSGFoundry::getInstance_with_GAS_ordinal  (formerly getInstanceGAS)
3490 --------------------------------------------------------------------
3491 
3492 Instance transform using gas local ordinal (not the global instance index).
3493 
3494 NB the method *CSGFoundry::getInst* provides the instance transform
3495 from the global instance index argument.
3496 
3497 **/
3498 
3499 const qat4* CSGFoundry::getInstance_with_GAS_ordinal(int gas_idx_ , unsigned ordinal) const
3500 {
3501     int index = getInstanceIndex(gas_idx_, ordinal);
3502     return index > -1 ? &inst[index] : nullptr ;
3503 }
3504 
3505 std::string CSGFoundry::descGAS() const
3506 {
3507     std::stringstream ss ;
3508     ss << desc() << std::endl ;
3509     for(unsigned i=0 ; i < gas.size() ; i++)
3510     {
3511         int gas_idx = gas[i];
3512         unsigned num_inst_gas = getNumInstancesGAS(gas_idx);
3513         ss << std::setw(5) << gas_idx << ":" << std::setw(8) << num_inst_gas << std::endl ;
3514     }
3515     std::string s = ss.str();
3516     return s ;
3517 }
3518 
3519 
3520 
3521 
3522 /**
3523 CSGFoundry::parseMOI
3524 -------------------------
3525 
3526 MOI lookups Meshidx-meshOrdinal-Instanceidx, examples of moi strings::
3527 
3528    sWorld:0:0
3529    sWorld:0
3530    sWorld
3531 
3532    0:0:0
3533    0:0
3534    0
3535 
3536 The first colon delimited field can be lv index or solid name.
3537 
3538 **/
3539 void CSGFoundry::parseMOI(int& midx, int& mord, int& iidx, const char* moi) const
3540 {
3541     id->parseMOI(midx, mord, iidx, moi );  // SName::parseMOI
3542 }
3543 const char* CSGFoundry::getName(unsigned midx) const
3544 {
3545     return id->getName(midx);
3546 }
3547 
3548 
3549 
3550 
3551 /**
3552 CSGFoundry::getFrame
3553 ---------------------
3554 
3555 Replacing most of CSGOptiX::setComposition
3556 
3557 **/
3558 
3559 const char* CSGFoundry::getFrame_NOTES = R"(
3560 CSGFoundry::getFrame_NOTES
3561 ===========================
3562 
3563 When CSGFoundry::getFrame fails due to the MOI/FRS string used to target
3564 a volume of the geometry failing to find the targetted volume
3565 it is usually due to the spec not being appropriate for the geometry.
3566 
3567 First thing to check is the configured GEOM envvar using GEOM bash function.
3568 
3569 With simple test geometries the lack of frames can be worked around
3570 using special cased MOI in some cases, for example::
3571 
3572     MOI=EXTENT:200 ~/o/cxr_min.sh
3573 
3574 
3575 When using U4VolumeMaker it is sometimes possible to
3576 debug the bad specification string by rerunning with the below
3577 envvars set in order to dump PV/LV/SO names from the full and sub trees.::
3578 
3579    export U4VolumeMaker_PVG_WriteNames=1
3580    export U4VolumeMaker_PVG_WriteNames_Sub=1
3581 
3582 This writes the names for the full volume tree to eg::
3583 
3584    /tmp/$USER/opticks/U4VolumeMaker_PVG_WriteNames
3585    /tmp/$USER/opticks/U4VolumeMaker_PVG_WriteNames_Sub
3586 
3587 Grab these from remote with::
3588 
3589    u4
3590    ./U4VolumeMaker.sh grab
3591 
3592 
3593 )" ;
3594 
3595 
3596 sframe CSGFoundry::getFrame() const // TODO: MIGRATE TO sfr.h
3597 {
3598     const char* moi_or_iidx = ssys::getenvvar("MOI",sframe::DEFAULT_FRS); // DEFAULT_FRS "-1"
3599     return getFrame(moi_or_iidx);
3600 }
3601 
3602 /**
3603 CSGFoundry::getFrame
3604 --------------------
3605 
3606 The FRAME_TRANSITION starts moving away from sframe by
3607 switching to stree::getFrame to provide sfr which is used to
3608 populate the sframe
3609 
3610 Currently sframe::populate lacks handling of the grid metadata
3611 needed for simtrace. Actually seems that metadata not much used and
3612 anyhow if it was needed could include into the genstep metadata.
3613 
3614 **/
3615 
3616 
3617 sframe CSGFoundry::getFrame(const char* q_spec) const //  TODO: MIGRATE TO sfr.h
3618 {
3619     sframe fr = {} ;
3620 
3621 #ifdef FRAME_TRANSITION
3622     stree* tree = getTree();
3623     assert(tree);
3624     sfr f = tree->get_frame(q_spec);
3625     fr.populate(f);
3626 #else
3627     const char* arg = frs ? frs : sframe::DEFAULT_FRS ;
3628     int rc = getFrame(fr, arg );
3629     LOG_IF(error, rc != 0) << " arg" << arg << std::endl << getFrame_NOTES ;
3630     if(rc != 0) std::raise(SIGINT);
3631 #endif
3632     fr.prepare();  // creates Tran<double>
3633 
3634     return fr ;
3635 }
3636 
3637 
3638 
3639 #ifndef FRAME_TRANSITION
3640 
3641 /**
3642 CSGFoundry::getFrame
3643 ---------------------
3644 
3645 
3646 FRAME_TRANSITION SEEKS TO ELIMINATE THIS
3647 
3648 
3649 
3650 
3651 
3652 frs
3653     frame specification string is regarded to "looks_like_moi" when
3654     it starts with a letter or it contains ":" or it is "-1".
3655     For such strings parseMOI is used to extract midx, mord, oodx
3656 
3657     Otherwise the string is assumed to be inst_idx and iidxg
3658     parsed as an integer
3659 
3660 
3661 Q: is indexing by MOI and inst_idx equivalent ? OR: Can a MOI be converted into inst_idx and vice versa ?
3662 
3663 * NO not for global prims : for example for all the repeated prims in the global geometry
3664   there is only one inst_idx (zero) but there are many possible MOI
3665 
3666 * NO not for most instanced prim, where all the prim within an instance
3667   share the same inst_idx and transform
3668 
3669 * BUT for the outer prim of an instance a correspondence is possible
3670 
3671 TODO : AVOID DUPLICATION BETWEEN THIS AND stree::get_frame
3672 
3673 
3674 looks_like_raw:true
3675     frs contains "," delimiting center_extent CE values eg::
3676 
3677          MOI=0.000,0.000,18935.000,1435.000 FULLSCREEN=0 cxr_min.sh
3678 
3679 
3680 **/
3681 
3682 
3683 
3684 
3685 int CSGFoundry::getFrame(sframe& fr, const char* frs ) const  //  TODO: MIGRATE TO sfr.h
3686 {
3687 
3688     bool VERBOSE = ssys::getenvbool(getFrame_VERBOSE) ;
3689     LOG(LEVEL) << "[" << getFrame_VERBOSE << "] " << VERBOSE ;
3690 
3691     int rc = 0 ;
3692 
3693     bool looks_like_axis = sstr::StartsWith(frs,stree::AXIS_PFX) ;
3694     bool looks_like_raw = strstr(frs,",") ;
3695     bool looks_like_moi = sstr::StartsWithLetterAZaz(frs) || strstr(frs, ":") || strcmp(frs,"-1") == 0 ;
3696 
3697     LOG_IF(info, VERBOSE)
3698         << "[" << getFrame_VERBOSE << "] " << ( VERBOSE ? "YES" : "NO " )
3699         << " frs " << ( frs ? frs : "-" )
3700         << " looks_like_moi " << ( looks_like_moi ? "YES" : "NO " )
3701         << " looks_like_raw " << ( looks_like_raw ? "YES" : "NO " )
3702         ;
3703 
3704     if(looks_like_axis)
3705     {
3706         // ASSERTS WITHOUT THIS FROM TOO MANY ELEM IN PopulateFromRaw
3707         // BUT SUSPECT THE sframe NOT REALLY USED
3708         //
3709         // ABOVE IS NOT TRUE, THE PERSISTED sframe STILL USED FROM CSGOptiX/cxt_min.py for simtrace plotting
3710         // SO STILL MUST DO THINGS IN DUPLICATE
3711         sfr lf = sfr::MakeFromAxis<float>(frs + strlen(stree::AXIS_PFX), ',');
3712         fr.populate(lf);
3713     }
3714     else if(looks_like_raw)
3715     {
3716         rc = sframe::PopulateFromRaw(fr, frs, ',' );
3717     }
3718     else if(looks_like_moi)
3719     {
3720         int midx, mord, gord ;  // mesh-index, mesh-ordinal, gas-ordinal
3721         parseMOI(midx, mord, gord,  frs );
3722 
3723         rc = getFrame(fr, midx, mord, gord);
3724         // NB gas ordinal is not the same as gas index
3725 
3726         LOG_IF(info, VERBOSE)
3727             << "[" << getFrame_VERBOSE << "] " << ( VERBOSE ? "YES" : "NO " )
3728             << " frs " << ( frs ? frs : "-" )
3729             << " looks_like_moi " << ( looks_like_moi ? "YES" : "NO " )
3730             << " midx " << midx
3731             << " mord " << mord
3732             << " gord " << gord
3733             << " rc " << rc
3734             ;
3735 
3736     }
3737     else
3738     {
3739         int inst_idx = SName::ParseIntString(frs, 0) ;
3740         rc = getFrame(fr, inst_idx);
3741 
3742         LOG_IF(info, VERBOSE)
3743             << "[" << getFrame_VERBOSE << "] " << ( VERBOSE ? "YES" : "NO " )
3744             << " frs " << ( frs ? frs : "-" )
3745             << " looks_like_moi " << ( looks_like_moi ? "YES" : "NO " )
3746             << " inst_idx " << inst_idx
3747             << " rc " << rc
3748             ;
3749     }
3750 
3751     fr.set_propagate_epsilon( SEventConfig::PropagateEpsilon() );
3752     fr.frs = strdup(frs);
3753     fr.prepare();  // needed for spawn_lite to work
3754 
3755     LOG_IF(info, VERBOSE)
3756         << "[" << getFrame_VERBOSE << "] " << ( VERBOSE ? "YES" : "NO " )
3757         << "[fr.desc\n"
3758         << fr.desc()
3759         << "]fr.desc\n"
3760         ;
3761 
3762     LOG_IF(error, rc != 0) << "Failed to lookup frame with frs [" << frs << "] looks_like_moi " << looks_like_moi  ;
3763     return rc ;
3764 }
3765 #endif
3766 
3767 
3768 int CSGFoundry::getFrame(sframe& fr, int inst_idx) const
3769 {
3770     return target->getFrame( fr, inst_idx );
3771 }
3772 
3773 /**
3774 CSGFoundry::getFrame
3775 ----------------------
3776 
3777 midx
3778     mesh index (aka lv)
3779 mord
3780     mesh ordinal (picking between multipler occurrences of midx
3781 gord
3782     GAS ordinal [NB this is not the GAS index]
3783 
3784 
3785 NB the GAS index is determined from (midx, mord)
3786 and then gord picks between potentially multiple occurrences
3787 
3788 **/
3789 
3790 int CSGFoundry::getFrame(sframe& fr, int midx, int mord, int gord) const
3791 {
3792     int rc = 0 ;
3793     if( midx == -1 )
3794     {
3795         unsigned ias_idx = 0 ; // only one IAS
3796         unsigned long long emm = 0ull ;   // hmm instance var ?
3797         iasCE(fr.ce, ias_idx, emm);
3798     }
3799     else
3800     {
3801         rc = target->getFrame( fr, midx, mord, gord );
3802     }
3803     return rc ;
3804 }
3805 
3806 
3807 /**
3808 CSGFoundry::getFrameE
3809 -----------------------
3810 
3811 The frame and corresponding transform used can be controlled by several envvars,
3812 see CSGFoundry::getFrameE. Possible envvars include:
3813 
3814 +------------------------------+----------------------------+
3815 | envvar                       | Examples                   |
3816 +==============================+============================+
3817 | INST                         |                            |
3818 +------------------------------+----------------------------+
3819 | MOI                          | Hama:0:1000 NNVT:0:1000    |
3820 +------------------------------+----------------------------+
3821 | OPTICKS_INPUT_PHOTON_FRAME   |                            |
3822 +------------------------------+----------------------------+
3823 
3824 
3825 The sframe::set_ekv records into frame metadata the envvar key and value
3826 that picked the frame.
3827 
3828 
3829 Q: WHY NOT DO THIS AT LOWER LEVEL ?
3830 A: Probably because it needs getFrame and it predates the stree.h reorganization
3831    that made frame access at sysrap level possible.
3832 
3833 
3834 NB this is called both by the below CSGFoundry::AfterLoadOrCreate and by CSGOptiX::initFrame
3835    so that should mean that frame annotation always gets done for running from
3836    persisted or live geometry  (HMM perhaps called twice though)
3837 
3838 **/
3839 
3840 
3841 
3842 sframe CSGFoundry::getFrameE() const
3843 {
3844     bool VERBOSE = ssys::getenvbool(getFrameE_VERBOSE) ;
3845     LOG(LEVEL) << "[" << getFrameE_VERBOSE << "] " << VERBOSE ;
3846 
3847     sframe fr = {} ;
3848 
3849     if(ssys::getenvbool("INST"))
3850     {
3851         int INST = ssys::getenvint("INST", 0);
3852         LOG_IF(info, VERBOSE) << " INST " << INST ;
3853         getFrame(fr, INST ) ;
3854 
3855         fr.set_ekv("INST");
3856     }
3857     else if(ssys::getenvbool("MOI"))
3858     {
3859         const char* MOI = ssys::getenvvar("MOI", nullptr) ;
3860         LOG_IF(info, VERBOSE) << " MOI " << MOI ;
3861         fr = getFrame() ;
3862         fr.set_ekv("MOI");
3863     }
3864     else
3865     {
3866         const char* ipf_ = SEventConfig::InputPhotonFrame();  // OPTICKS_INPUT_PHOTON_FRAME
3867         const char* ipf = ipf_ ? ipf_ : "0" ;
3868         LOG_IF(info, VERBOSE) << " ipf " << ipf ;
3869         fr = getFrame(ipf);
3870 
3871         fr.set_ekv(SEventConfig::kInputPhotonFrame, ipf );
3872     }
3873 
3874 
3875     SSim::AnnotateFrame(fr, elv, "CSGFoundry::getFrameE"  );  // set tree and dynamic digests into the frame
3876 
3877     return fr ;
3878 }
3879 
3880 
3881 
3882 /**
3883 CSGFoundry::AfterLoadOrCreate
3884 -------------------------------
3885 
3886 Called from CSGFoundry::Load
3887 
3888 The idea behind this is to auto connect SEvt with the frame
3889 from the geometry.
3890 
3891 HMM: not called after Create, see CSGOptiX::initFrame
3892 
3893 
3894 Formerly frame mechanics had to be done up here at CSGFoundry level
3895 due to the need for geometry info to form the frame from envvar config.
3896 But after stree.h improvements there is no reason not to do within SSim+stree
3897 
3898 TODO: reposition SEvt prep into SSim::AfterLoadOrCreate and sframe/sfr prep into stree::AfterLoadOrCreate ?
3899 
3900 
3901 
3902 Trying to progress with FRAME_TRANSITION by replacing the old
3903 CSGFoundry::AfterLoadOrCreate with SSim::afterLoadOrCreate
3904 
3905 
3906 **/
3907 
3908 void CSGFoundry::AfterLoadOrCreate() // static
3909 {
3910     assert(0 && "DONT USE THIS : THIS PREP DONE BY SSim::afterLoadOrCreate " );
3911 
3912     CSGFoundry* fd = CSGFoundry::Get();
3913     if(!fd) return ;
3914 
3915 #ifdef WITH_OLD_FRAME
3916     SEvt::CreateOrReuse() ;   // creates 1/2 SEvt depending on OPTICKS_INTEGRATION_MODE
3917 
3918     sframe fr = fd->getFrameE() ;
3919     LOG(LEVEL) << fr ;
3920     SEvt::SetFrame(fr); // now only needs to be done once to transform input photons
3921 #endif
3922 
3923 }
3924 
3925 
3926 
3927 /**
3928 CSGFoundry::getCenterExtent
3929 -------------------------------
3930 
3931 For midx -1 returns ce obtained from the ias bbox,
3932 otherwise uses CSGTarget to lookup the center extent.
3933 
3934 For global geometry which typically means a default gord of 0
3935 there is special handling for gord -1/-2/-3 in CSGTarget::getCenterExtent
3936 
3937 gord -1
3938     uses getLocalCenterExtent
3939 
3940 gord -2
3941     uses SCenterExtentFrame xyzw : ordinary XYZ frame
3942 
3943 gord -3
3944     uses SCenterExtentFrame rtpw : tangential RTP frame
3945 
3946 
3947 NB gord is the gas ordinal index
3948 (it was formerly named iidx which was confusing as this is NOT the global instance index)
3949 
3950 
3951 **/
3952 
3953 int CSGFoundry::getCenterExtent(float4& ce, int midx, int mord, int gord, qat4* m2w, qat4* w2m  ) const
3954 {
3955     int rc = 0 ;
3956     if( midx == -1 )
3957     {
3958         unsigned long long emm = 0ull ;   // hmm instance var ?
3959         iasCE(ce, emm);
3960     }
3961     else
3962     {
3963         rc = target->getFrameComponents(ce, midx, mord, gord, m2w, w2m );
3964     }
3965 
3966     if( rc != 0 )
3967     {
3968         LOG(error) << " non-zero RC from CSGTarget::getCenterExtent " ;
3969     }
3970     return rc ;
3971 }
3972 
3973 
3974 int CSGFoundry::getTransform(qat4& q, int midx, int mord, int gord) const
3975 {
3976     return target->getTransform(q, midx, mord, gord);
3977 }
3978 
3979 
3980 /**
3981 CSGFoundry::kludgeScalePrimBBox
3982 ---------------------------------
3983 
3984 **/
3985 
3986 void CSGFoundry::kludgeScalePrimBBox( const char* label, float dscale )
3987 {
3988     std::vector<unsigned> solidIdx ;
3989     findSolidIdx(solidIdx, label);
3990 
3991     for(int i=0 ; i < int(solidIdx.size()) ; i++)
3992     {
3993         unsigned soIdx = solidIdx[i];
3994         kludgeScalePrimBBox( soIdx, dscale );
3995     }
3996 }
3997 
3998 /**
3999 CSGFoundry::kludgeScalePrimBBox
4000 --------------------------------
4001 
4002 Scaling the AABB of all *CSGPrim* of the *solidIdx*
4003 
4004 **/
4005 
4006 void CSGFoundry::kludgeScalePrimBBox( unsigned solidIdx, float dscale )
4007 {
4008     CSGSolid* so = solid.data() + solidIdx ;
4009     so->type = KLUDGE_BBOX_SOLID ;
4010 
4011     unsigned primOffset = so->primOffset ;
4012     unsigned numPrim = so->numPrim ;
4013 
4014     for(unsigned primIdx=0 ; primIdx < numPrim ; primIdx++)
4015     {
4016         // primIdx                :   0,1,2,3,...,numPrim-1
4017         // numPrim-1 - primIdx    :  numPrim-1, numPrim-1-1, numPrim-1-2, ... , 0
4018         // scale                  :  1+(numPrim-1)*dscale,
4019 
4020         float scale = 1.f + dscale*float(numPrim - 1u - primIdx) ;
4021         LOG(info) << " primIdx " << std::setw(2) << primIdx << " scale " << scale ;
4022         std::cout
4023             << "CSGFoundry::kludgeScalePrimBBox"
4024             << " primIdx " << std::setw(2) << primIdx
4025             << " numPrim " << std::setw(2) << numPrim
4026             << " scale " << scale
4027             << std::endl
4028             ;
4029         CSGPrim* pr = prim.data() + primOffset + primIdx ;  // about to modify, so low level access
4030         pr->scaleAABB_(scale);
4031     }
4032 }
4033 
4034 
4035 
4036