Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:49:46

0001 #pragma once
0002 /**
0003 SScene.h
0004 =========
0005 
0006 Canonical SScene instance is member of SSim
0007 and is instanciated by the SSim ctor.
0008 This SScene instance is sibling of the canonical
0009 stree instance.
0010 
0011 The SScene is populated via a surprisingly high level
0012 call stack::
0013 
0014     G4CXOpticks::setGeometry
0015     SSim::initSceneFromTree
0016     SScene::initFromTree
0017 
0018 
0019 To some extent the SScene acts as a minimal sub-selection of the
0020 full stree.h info needed to do triangulated rendering both
0021 with OptiX ray trace and OpenGL rasterized.
0022 
0023 ::
0024 
0025     ~/o/sysrap/tests/SScene_test.sh
0026 
0027 * OpenGL/CUDA interop-ing the triangle data is possible (but not straight off)
0028 
0029 * TODO: solid selection via ELV envvar eg skipping virtuals so can see PMT shapes
0030 
0031   * some work on that already in SScene::CopySelect using name info within SScene
0032   * an alternative approach would be to combine the initFromTree
0033     with applying the selection as all the identity info needed is
0034     available within the tree
0035 
0036 * WIP: incorporate into standard workflow
0037 
0038   * treat SScene.h as sibling to stree.h within SSim.hh
0039   * invoke the SScene.h creation from stree immediately after stree creation by U4Tree
0040 
0041 **/
0042 
0043 #include "ssys.h"
0044 #include "stree.h"
0045 #include "svec.h"
0046 
0047 #include "SName.h"
0048 #include "SMesh.h"
0049 #include "SMeshGroup.h"
0050 #include "SBitSet.h"
0051 #include "SGeoConfig.hh"
0052 
0053 struct SScene
0054 {
0055     static constexpr const char* __level = "SScene__level" ;
0056 
0057     static constexpr const char* BASE = "$CFBaseFromGEOM/CSGFoundry/SSim" ;
0058     static constexpr const char* RELDIR = "scene" ;
0059 
0060     static constexpr const char* MESHGROUP = "meshgroup" ;
0061     static constexpr const char* MESHMERGE = "meshmerge" ;
0062     static constexpr const char* FRAME = "frame" ;
0063     static constexpr const char* INST_TRAN = "inst_tran.npy" ;
0064     static constexpr const char* INST_COL3 = "inst_col3.npy" ;
0065     static constexpr const char* INST_INFO = "inst_info.npy" ;
0066     static constexpr const char* SONAME = "soname.txt" ;
0067 
0068     int level ;
0069     std::vector<std::string>          soname ;
0070     SName*                            id ;
0071 
0072     std::vector<const SMeshGroup*>    meshgroup ;
0073     std::vector<const SMesh*>         meshmerge ;
0074     std::vector<sfr>                  frame ;
0075 
0076     std::vector<int4>                 inst_info ;  // compound solid level
0077 
0078     std::vector<glm::tmat4x4<float>>  inst_tran ;  // instance level
0079     std::vector<glm::tvec4<int32_t>>  inst_col3 ;
0080 
0081 
0082     static SScene* Load_(const char* dir=BASE);
0083     static SScene* Load(const char* dir=BASE);
0084 
0085     SScene();
0086     void check() const ;
0087 
0088     void initFromTree(const stree* st);
0089 
0090     void initFromTree_soname(const stree* st);
0091     void initFromTree_Remainder(  const stree* st);
0092     void initFromTree_Triangulate(const stree* st);
0093     void initFromTree_Global(const stree* st, char ridx_type, int ridx );
0094 
0095     void initFromTree_Factor(const stree* st);
0096     void initFromTree_Factor_(int ridx, const stree* st);
0097     void initFromTree_Node(SMeshGroup* mg, int ridx, const snode& node, const stree* st);
0098     void initFromTree_Instance (const stree* st);
0099 
0100     const SMeshGroup* getMeshGroup(int idx) const ;
0101     const SMesh*      getMeshMerge(int idx) const ;
0102 
0103 
0104     const SMesh* get_mm(int mmidx) const ;
0105     const float* get_mn(int mmidx) const ;
0106     const float* get_mx(int mmidx) const ;
0107     const float* get_ce(int mmidx) const ;
0108 
0109     bool is_empty() const ;
0110     std::string desc() const ;
0111     std::string descDetail() const ;
0112     std::string descSize() const ;
0113     std::string descInstInfo() const ;
0114     std::string descCol3() const ;
0115     std::string descFrame() const ;
0116     std::string descRange() const ;
0117 
0118     void findSubMesh(std::vector<const SMesh*>& subs, int lvid) const ;
0119     std::string descSubMesh(const std::vector<const SMesh*>& subs) const ;
0120 
0121     static std::string DescCompare(const SScene* a, const SScene* b);
0122 
0123 
0124     NPFold* serialize_meshmerge() const ;
0125     void import_meshmerge(const NPFold* _meshmerge ) ;
0126 
0127     NPFold* serialize_meshgroup() const ;
0128     void import_meshgroup(const NPFold* _meshgroup ) ;
0129 
0130     NPFold* serialize_frame() const ;
0131     void import_frame(const NPFold* _frame ) ;
0132 
0133     NPFold* serialize() const ;
0134     void import_(const NPFold* fold);
0135 
0136     void save(const char* dir) const ;
0137     void load(const char* dir);
0138 
0139     void addFrames(const char* path, const stree* st);
0140     void addFrame( const sfr& _f);
0141 
0142     sfr getFrame(int _idx=-1) const ;
0143 
0144     static SScene* CopySelect( const SScene* src, const SBitSet* elv );
0145     SScene* copy(const SBitSet* elv=nullptr) const ;
0146 
0147     static int Compare(const SScene* a, const SScene* b);
0148 
0149 };
0150 
0151 
0152 
0153 
0154 inline SScene* SScene::Load_(const char* _base)
0155 {
0156     int level = ssys::getenvint(__level, 0);
0157     const char* base = spath::ResolveTopLevel(_base) ;
0158     if(level > 0) std::cout << "[SScene::Load_ _base[" << ( _base ? _base : "-" ) << "]\n" ;
0159     SScene* src = nullptr ;
0160     if(base)
0161     {
0162         src = new SScene ;
0163         src->load(base);
0164         src->check();
0165     }
0166     if(level > 0) std::cout << "]SScene::Load_ base[" << ( base ? base : "-" ) << "]\n" ;
0167     return src ;
0168 }
0169 
0170 
0171 /**
0172 SScene::Load
0173 --------------
0174 
0175 HMM cf CSGFoundry::Load which uses SSim::set_override_scene
0176 
0177 Dependency on SGeoConfig.cc (ie library functionality, not just header only)
0178 is inconvenient for examples/UseShaderSGLFW_SScene. Workaraound is to
0179 use lowerlevel SScene::Load_ without the ELV functionality.
0180 
0181 **/
0182 
0183 inline SScene* SScene::Load(const char* base)
0184 {
0185     SScene* src = Load_( base );
0186     if(src == nullptr) std::cerr
0187         << "SScene::Load_ FAILED "
0188         << " base[" << ( base ? base : "-" ) << "]"
0189         << "\n"
0190         ;
0191     if(src == nullptr) return nullptr ;
0192 
0193     const SBitSet* elv = SGeoConfig::ELV(src->id);
0194     SScene* dst = src->copy(elv);
0195     return dst ;
0196 }
0197 
0198 
0199 inline SScene::SScene()
0200     :
0201     level(ssys::getenvint(__level, 0)),
0202     id(new SName(soname))    // reference to soname vector
0203 {
0204 }
0205 
0206 /**
0207 SScene::check
0208 ---------------
0209 
0210 Checks:
0211 
0212 1. more than 0 frames, these should be added by SScene::addFrames
0213 2. consistency between meshmerge and meshgroup
0214 
0215 **/
0216 
0217 
0218 inline void SScene::check() const
0219 {
0220     int num_frame = frame.size();
0221     bool num_frame_expect =  num_frame > 0  ;
0222 
0223     if(!num_frame_expect) std::cout
0224          << "SScene::check"
0225          << " num_frame " << num_frame
0226          << " num_frame_expect " << ( num_frame_expect ? "YES" : "NO " )
0227          << " (expect more than zero frames) "
0228          << "\n"
0229          ;
0230 
0231     assert( num_frame_expect );
0232     assert( meshmerge.size() == meshgroup.size() );
0233 }
0234 
0235 
0236 /**
0237 SScene::initFromTree
0238 ---------------------
0239 
0240 Note the surprisingly high level call stack,
0241 immediately after stree population by U4Tree::Create::
0242 
0243     G4CXOpticks::setGeometry
0244     SSim::initSceneFromTree
0245     SScene::initFromTree
0246 
0247 Creating::
0248 
0249     (SMeshGroup)meshgroup
0250     (SMesh)meshmerge
0251 
0252 
0253 TODO: adding frames at SScene::initFromTree
0254 seems not the right place for it : would be better to do this from the
0255 main when rendering for simpler changing of framespec. Avoiding the need
0256 to rerun the integration in order to change framespec.
0257 
0258 HMM: could do both adding dynamic framespec from some other path
0259 (like framespec.txt in invoking directory) that get to with SHIFT+NUMBER_KEY ?
0260 
0261 **/
0262 
0263 
0264 inline void SScene::initFromTree(const stree* st)
0265 {
0266     initFromTree_soname(st);
0267     initFromTree_Remainder(st);
0268     initFromTree_Factor(st);
0269     initFromTree_Triangulate(st);
0270 
0271     initFromTree_Instance(st);
0272 
0273     addFrames("$SScene__initFromTree_addFrames", st );
0274 }
0275 
0276 inline void SScene::initFromTree_soname(const stree* st)
0277 {
0278     st->get_meshname(soname);
0279 }
0280 
0281 inline void SScene::initFromTree_Remainder(const stree* st)
0282 {
0283     [[maybe_unused]] int num_rem = st->get_num_remainder();
0284     assert( num_rem == 1 );
0285     int ridx = 0 ;
0286     initFromTree_Global( st, 'R', ridx );
0287 }
0288 inline void SScene::initFromTree_Triangulate(const stree* st)
0289 {
0290     int num_rem = st->get_num_remainder();
0291     int num_fac = st->get_num_factor();
0292     int num_tri = st->get_num_triangulated();
0293 
0294     assert( num_rem == 1 );
0295     assert( num_tri == 1 || num_tri == 0  );
0296 
0297     if(num_tri == 1 )
0298     {
0299         int ridx = num_rem + num_fac + 0 ;
0300         initFromTree_Global( st, 'T', ridx );
0301     }
0302 }
0303 
0304 
0305 /**
0306 SScene::initFromTree_Global
0307 ---------------------------
0308 
0309 
0310 **/
0311 
0312 inline void SScene::initFromTree_Global(const stree* st, char ridx_type, int ridx )
0313 {
0314     assert( ridx_type == 'R' || ridx_type == 'T' );
0315     const std::vector<snode>* _nodes = st->get_node_vector(ridx_type)  ;
0316     assert( _nodes );
0317 
0318     int num_node = _nodes->size() ;
0319 
0320     if(level>0) std::cout
0321         << "[ SScene::initFromTree_Global"
0322         << " level " << level
0323         << " num_node " << num_node
0324         << " ridx_type " << ridx_type
0325         << " ridx " << ridx
0326         << std::endl
0327         ;
0328 
0329     SMeshGroup* mg = new SMeshGroup ;
0330     for(int i=0 ; i < num_node ; i++)
0331     {
0332         const snode& node = (*_nodes)[i];
0333         initFromTree_Node(mg, ridx, node, st);
0334     }
0335     const SMesh* _mesh = SMesh::Concatenate( mg->subs, ridx );
0336     meshmerge.push_back(_mesh);
0337     meshgroup.push_back(mg);
0338 
0339     if(level>0) std::cout
0340         << "] SScene::initFromTree_Global"
0341         << " level " << level
0342         << " num_node " << num_node
0343         << " ridx_type " << ridx_type
0344         << " ridx " << ridx
0345         << std::endl
0346         ;
0347 }
0348 
0349 inline void SScene::initFromTree_Factor(const stree* st)
0350 {
0351     int num_rem = st->get_num_remainder();
0352     assert( num_rem == 1 );
0353 
0354     int num_fac = st->get_num_factor();
0355     for(int i=0 ; i < num_fac ; i++) initFromTree_Factor_(num_rem+i, st);
0356 }
0357 
0358 /**
0359 SScene::initFromTree_Factor_
0360 -----------------------------
0361 
0362 Note that the meshmerge and meshgroup contain the same triangulated
0363 geometry info organized differently
0364 
0365 meshgroup
0366     SMeshGroup instances that maintain separate SMesh for each "Prim"
0367     (used by triangulated OptiX?)
0368 
0369 meshmerge
0370     concatenated SMesh used by OpenGL
0371     HMM: the concatenation could be deferred or redone following
0372     lvid based sub-selection applied to mg->subs
0373 
0374 **/
0375 
0376 inline void SScene::initFromTree_Factor_(int ridx, const stree* st)
0377 {
0378     assert( ridx > 0 );
0379     int q_repeat_index = ridx ;
0380     int q_repeat_ordinal = 0 ;   // just first repeat
0381     std::vector<snode> nodes ;
0382     st->get_repeat_node(nodes, q_repeat_index, q_repeat_ordinal) ;
0383     int num_node = nodes.size();
0384 
0385     if(level>0) std::cout
0386         << "SScene::initFromTree_Factor"
0387         << " level " << level
0388         << " ridx " << ridx
0389         << " num_node " << num_node
0390         << std::endl
0391         ;
0392 
0393     SMeshGroup* mg = new SMeshGroup ;
0394     for(int i=0 ; i < num_node ; i++)
0395     {
0396         const snode& node = nodes[i];
0397         initFromTree_Node(mg, ridx, node, st);
0398     }
0399     const SMesh* _mesh = SMesh::Concatenate( mg->subs, ridx );
0400     meshmerge.push_back(_mesh);
0401     meshgroup.push_back(mg);
0402 }
0403 
0404 
0405 
0406 /**
0407 SScene::initFromTree_Node
0408 ---------------------------
0409 
0410 Note that some snode within sfactor subtrees have
0411 different transforms relative to the outer snode
0412 such as PMT innards. Hence the SMesh vtx
0413 are flattened into the instance frame.
0414 
0415 Recall that instance outer nodes will need no transform,
0416 but for volumes within the subtree some relative
0417 transforms will in general need to be applied.
0418 
0419 Contrast with CSGFoundry which goes further delving into
0420 the transforms of the CSG constituents.
0421 
0422 1. get transform for snode::index from stree
0423 2. import SMesh for snode::lvid from stree, possibly applying the transform
0424 3. collect SMesh into SMeshGroup
0425 
0426 
0427 **/
0428 
0429 inline void SScene::initFromTree_Node(SMeshGroup* mg, int ridx, const snode& node, const stree* st)
0430 {
0431     glm::tmat4x4<double> m2w(1.);
0432     glm::tmat4x4<double> w2m(1.);
0433     bool local = true ;        // WHAT ABOUT FOR REMAINDER NODES ?
0434     bool reverse = false ;     // ?
0435     std::ostream* out = nullptr ;
0436     stree::VTR* t_stack = nullptr ;
0437 
0438     st->get_node_product(m2w, w2m, node.index, local, reverse, out, t_stack );
0439     bool is_identity_m2w = stra<double>::IsIdentity(m2w) ;
0440 
0441     const char* so = st->soname[node.lvid].c_str();
0442     // st->soname is now stripped at collection by stree.h with sstr::StripTail_Unique
0443 
0444     const NPFold* fold = st->mesh->get_subfold(so)  ;
0445     assert(fold);
0446     const SMesh* _mesh = SMesh::Import( fold, &m2w );
0447 
0448     mg->subs.push_back(_mesh);
0449     mg->names.push_back(so);
0450 
0451 
0452     if(level>0) std::cout
0453        << "SScene::initFromTree_Node"
0454        << " node.lvid " << node.lvid
0455        << " st.soname[node.lvid] " << st->soname[node.lvid]
0456        << " _mesh " <<  _mesh->brief()
0457        << " is_identity_m2w " << ( is_identity_m2w ? "YES" : "NO " )
0458        << std::endl
0459        ;
0460 
0461     if(level>0 && !is_identity_m2w) std::cout << _mesh->descTransform() << std::endl ;
0462 }
0463 
0464 inline void SScene::initFromTree_Instance(const stree* st)
0465 {
0466     inst_info = st->inst_info ;
0467     //strid::NarrowClear( inst_tran, st->inst ); // copy and narrow from st->inst into inst_tran
0468 
0469     strid::NarrowDecodeClear(inst_tran, inst_col3, st->inst );
0470 
0471 }
0472 
0473 
0474 
0475 inline const SMeshGroup* SScene::getMeshGroup(int idx) const
0476 {
0477     return idx < int(meshgroup.size()) ? meshgroup[idx] : nullptr ;
0478 }
0479 inline const SMesh*      SScene::getMeshMerge(int idx) const
0480 {
0481     return idx < int(meshmerge.size()) ? meshmerge[idx] : nullptr ;
0482 }
0483 
0484 
0485 
0486 inline const SMesh* SScene::get_mm(int mmidx) const
0487 {
0488     const SMesh* mm = mmidx < int(meshmerge.size()) ? meshmerge[mmidx] : nullptr ;
0489     return mm ;
0490 }
0491 inline const float* SScene::get_mn(int mmidx) const
0492 {
0493     const SMesh* mm = get_mm(mmidx);
0494     return mm ? mm->get_mn() : nullptr ;
0495 }
0496 inline const float* SScene::get_mx(int mmidx) const
0497 {
0498     const SMesh* mm = get_mm(mmidx);
0499     return mm ? mm->get_mx() : nullptr ;
0500 }
0501 inline const float* SScene::get_ce(int mmidx) const
0502 {
0503     const SMesh* mm = get_mm(mmidx);
0504     return mm ? mm->get_ce() : nullptr ;
0505 }
0506 
0507 
0508 inline bool SScene::is_empty() const
0509 {
0510     return meshmerge.size() == 0 && meshgroup.size() == 0 && inst_info.size() == 0 && inst_tran.size() == 0 ;
0511 }
0512 
0513 
0514 inline std::string SScene::desc() const
0515 {
0516     std::stringstream ss ;
0517     ss << "[ SScene::desc \n" ;
0518     ss << " is_empty " << ( is_empty() ? "YES" : "NO " ) << "\n" ;
0519     ss << descSize() ;
0520     ss << descInstInfo() ;
0521     ss << descCol3() ;
0522     //ss << descFrame() ;
0523     ss << "] SScene::desc \n" ;
0524     std::string str = ss.str();
0525     return str ;
0526 }
0527 
0528 inline std::string SScene::descDetail() const
0529 {
0530     std::stringstream ss ;
0531     ss << "[ SScene::detail \n" ;
0532     ss << descRange() ;
0533     ss << "] SScene::detail \n" ;
0534     std::string str = ss.str();
0535     return str ;
0536 }
0537 
0538 
0539 
0540 inline std::string SScene::descSize() const
0541 {
0542     std::stringstream ss ;
0543     ss << "SScene::descSize"
0544        << " meshmerge " << meshmerge.size()
0545        << " meshgroup " << meshgroup.size()
0546        << " inst_info " << inst_info.size()
0547        << " inst_col3 " << inst_col3.size()
0548        << " inst_tran " << inst_tran.size()
0549        << std::endl
0550        ;
0551     std::string str = ss.str();
0552     return str ;
0553 }
0554 
0555 inline std::string SScene::descInstInfo() const
0556 {
0557     std::stringstream ss ;
0558     ss << "[SScene::descInstInfo {ridx, inst_count, inst_offset, 0} " << std::endl ;
0559     int num_inst_info = inst_info.size();
0560     int tot_inst = 0 ;
0561     for(int i=0 ; i < num_inst_info ; i++)
0562     {
0563         const int4& info = inst_info[i] ;
0564         ss
0565            << "{"
0566            << std::setw(3) << info.x
0567            << ","
0568            << std::setw(7) << info.y
0569            << ","
0570            << std::setw(7) << info.z
0571            << ","
0572            << std::setw(3) << info.w
0573            << "}"
0574            << std::endl
0575            ;
0576         tot_inst += info.y ;
0577     }
0578     ss << "]SScene::descInstInfo tot_inst " << tot_inst << std::endl ;
0579     std::string str = ss.str();
0580     return str ;
0581 }
0582 
0583 
0584 
0585 inline std::string SScene::descCol3() const
0586 {
0587     std::stringstream ss ;
0588     ss << "[SScene::descCol3 {ins_idx, gas_idx, sen_id, sen_idx} " << std::endl ;
0589     int num_inst_col3 = inst_col3.size();
0590     int edge = 100 ;
0591 
0592     for(int i=0 ; i < num_inst_col3 ; i++)
0593     {
0594         if( i < edge || i > (num_inst_col3 - edge))
0595         {
0596             const glm::tvec4<int32_t>& col3 = inst_col3[i] ;
0597             ss
0598                << "{"
0599                << std::setw(3) << col3.x
0600                << ","
0601                << std::setw(7) << col3.y
0602                << ","
0603                << std::setw(7) << col3.z
0604                << ","
0605                << std::setw(3) << col3.w
0606                << "}"
0607                << std::endl
0608                ;
0609         }
0610 
0611     }
0612     ss << "]SScene::descCol3 " << std::endl ;
0613     std::string str = ss.str();
0614     return str ;
0615 }
0616 
0617 
0618 
0619 
0620 inline std::string SScene::descFrame() const
0621 {
0622     int num_frame = frame.size();
0623     std::stringstream ss ;
0624     ss << "[SScene::descFrame num_frame " << num_frame << std::endl ;
0625     for(int i=0 ; i < num_frame ; i++)
0626     {
0627         const sfr& f = frame[i];
0628         ss << f.desc() ;
0629     }
0630     ss << "]SScene::descFrame num_frame " << num_frame << std::endl ;
0631     std::string str = ss.str();
0632     return str ;
0633 }
0634 
0635 inline std::string SScene::descRange() const
0636 {
0637     int num_mm = meshmerge.size();
0638     [[maybe_unused]] int num_mg = meshgroup.size();
0639     assert( num_mm == num_mg );
0640     int num = num_mm ;
0641 
0642     std::stringstream ss ;
0643     ss << "[SScene::descRange num " << num << std::endl ;
0644     for(int i=0 ; i < num ; i++)
0645     {
0646         const SMeshGroup* mg = meshgroup[i] ;
0647         const SMesh* mm = meshmerge[i] ;
0648         ss << "mg[" << i << "]\n" << mg->descRange(&soname) << "\n" ;
0649         ss << "mm[" << i << "]\n" << mm->descRange() << "\n" ;
0650     }
0651     ss << "]SScene::descRange num " << num << std::endl ;
0652     std::string str = ss.str();
0653     return str ;
0654 }
0655 
0656 
0657 inline void SScene::findSubMesh(std::vector<const SMesh*>& subs, int lvid) const
0658 {
0659     size_t num_mg = meshgroup.size();
0660     std::cout << "SScene::findSubMesh lvid " << lvid << " num_mg " << num_mg << "\n" ;
0661     for(size_t i=0 ; i < num_mg ; i++)
0662     {
0663         const SMeshGroup* mg = meshgroup[i] ;
0664         mg->findSubMesh(subs, lvid);
0665     }
0666 }
0667 
0668 inline std::string SScene::descSubMesh(const std::vector<const SMesh*>& subs) const
0669 {
0670     return SMeshGroup::DescSubMesh(subs, &soname);
0671 }
0672 
0673 
0674 
0675 
0676 inline std::string SScene::DescCompare(const SScene* a, const SScene* b) // static
0677 {
0678     std::stringstream ss ;
0679     ss << "A.descSize " << a->descSize() << "\n" ;
0680     ss << "B.descSize " << b->descSize() << "\n" ;
0681 
0682     ss << "A.descInstInfo " << a->descInstInfo() << "\n" ;
0683     ss << "B.descInstInfo " << b->descInstInfo() << "\n" ;
0684 
0685     std::string str = ss.str();
0686     return str ;
0687 }
0688 
0689 
0690 
0691 inline NPFold* SScene::serialize_meshmerge() const
0692 {
0693     NPFold* _meshmerge = new NPFold ;
0694     int num_meshmerge = meshmerge.size();
0695     for(int i=0 ; i < num_meshmerge ; i++)
0696     {
0697         const SMesh* m = meshmerge[i] ;
0698         _meshmerge->add_subfold( m->name, m->serialize() );
0699     }
0700     return _meshmerge ;
0701 }
0702 
0703 inline NPFold* SScene::serialize_meshgroup() const
0704 {
0705     NPFold* _meshgroup = new NPFold ;
0706     int num_meshgroup = meshgroup.size();
0707     for(int i=0 ; i < num_meshgroup ; i++)
0708     {
0709         const SMeshGroup* mg = meshgroup[i] ;
0710         const char* name = SMesh::FormName(i);
0711         _meshgroup->add_subfold( name, mg->serialize() );
0712     }
0713     return _meshgroup ;
0714 }
0715 
0716 
0717 
0718 
0719 
0720 
0721 inline void SScene::import_meshmerge(const NPFold* _meshmerge )
0722 {
0723     int num_meshmerge = _meshmerge ? _meshmerge->get_num_subfold() : 0 ;
0724     if(level>0) std::cout << "[SScene::import_meshmerge  num_meshmerge " << num_meshmerge << "\n" ;
0725     for(int i=0 ; i < num_meshmerge ; i++)
0726     {
0727         const NPFold* sub = _meshmerge->get_subfold(i);
0728         const SMesh* m = SMesh::Import(sub) ;
0729         meshmerge.push_back(m);
0730     }
0731     if(level>0) std::cout << "]SScene::import_meshmerge  num_meshmerge " << num_meshmerge << "\n" ;
0732 }
0733 
0734 inline void SScene::import_meshgroup(const NPFold* _meshgroup )
0735 {
0736     int num_meshgroup = _meshgroup ? _meshgroup->get_num_subfold() : 0 ;
0737     if(level>0) std::cout << "[SScene::import_meshgroup  num_meshgroup " << num_meshgroup << "\n" ;
0738     for(int i=0 ; i < num_meshgroup ; i++)
0739     {
0740         const NPFold* sub = _meshgroup->get_subfold(i);
0741         const SMeshGroup* mg = SMeshGroup::Import(sub) ;
0742         meshgroup.push_back(mg);
0743     }
0744     if(level>0) std::cout << "]SScene::import_meshgroup  num_meshgroup " << num_meshgroup << "\n" ;
0745 }
0746 
0747 
0748 inline NPFold* SScene::serialize_frame() const
0749 {
0750     NPFold* _frame = new NPFold ;
0751     int num_frame = frame.size();
0752     for(int i=0 ; i < num_frame ; i++)
0753     {
0754         const sfr& f  = frame[i] ;
0755         std::string key = f.get_key() ;
0756         _frame->add( key.c_str(), f.serialize() );
0757     }
0758     return _frame ;
0759 }
0760 
0761 inline void SScene::import_frame(const NPFold* _frame )
0762 {
0763     int num_frame = _frame ? _frame->num_items() : 0 ;
0764     for(int i=0 ; i < num_frame ; i++)
0765     {
0766         const NP* a = _frame->get_array(i);
0767         sfr f = sfr::Import(a) ;
0768         frame.push_back(f);
0769     }
0770 }
0771 
0772 
0773 
0774 inline NPFold* SScene::serialize() const
0775 {
0776     NPFold* _meshmerge = serialize_meshmerge() ;
0777     NPFold* _meshgroup = serialize_meshgroup() ;
0778     NPFold* _frame     = serialize_frame() ;
0779     NP* _inst_tran = NPX::ArrayFromVec<float, glm::tmat4x4<float>>( inst_tran, 4, 4) ;
0780     NP* _inst_info = NPX::ArrayFromVec<int,int4>( inst_info, 4 ) ;
0781     NP* _inst_col3 = NPX::ArrayFromVec<int,glm::tvec4<int32_t>>( inst_col3, 4 ) ;
0782     NP* _soname = NPX::Holder(soname) ;
0783 
0784     NPFold* fold = new NPFold ;
0785     fold->add_subfold( MESHMERGE, _meshmerge );
0786     fold->add_subfold( MESHGROUP, _meshgroup );
0787     fold->add_subfold( FRAME,     _frame );
0788     fold->add( INST_INFO, _inst_info );
0789     fold->add( INST_TRAN, _inst_tran );
0790     fold->add( INST_COL3, _inst_col3 );
0791     fold->add( SONAME, _soname );
0792 
0793     return fold ;
0794 }
0795 inline void SScene::import_(const NPFold* fold)
0796 {
0797     if(level>0) std::cout << "[SScene::import \n" ;
0798     if(fold == nullptr) std::cerr << "SScene::import called with NULL fold argument\n" ;
0799     if(fold == nullptr) return ;
0800 
0801     const NPFold* _meshmerge = fold->get_subfold(MESHMERGE );
0802     const NPFold* _meshgroup = fold->get_subfold(MESHGROUP );
0803     const NPFold* _frame     = fold->get_subfold(FRAME );
0804     import_meshmerge( _meshmerge );
0805     import_meshgroup( _meshgroup );
0806     import_frame(     _frame );
0807 
0808     const NP* _inst_info = fold->get(INST_INFO);
0809     const NP* _inst_tran = fold->get(INST_TRAN);
0810     const NP* _inst_col3 = fold->get(INST_COL3);
0811     const NP* _soname = fold->get(SONAME) ;
0812 
0813     stree::ImportArray<glm::tmat4x4<float>, float>( inst_tran, _inst_tran, INST_TRAN );
0814     stree::ImportArray<int4, int>(                  inst_info, _inst_info, INST_INFO );
0815     stree::ImportArray<glm::tvec4<int32_t>, int>(   inst_col3, _inst_col3, INST_COL3 );
0816     stree::ImportNames( soname, _soname , SONAME);
0817 
0818     if(level>0) std::cout << "]SScene::import \n" ;
0819 }
0820 
0821 inline void SScene::save(const char* dir) const
0822 {
0823     NPFold* fold = serialize();
0824     fold->save(dir, RELDIR);
0825 }
0826 inline void SScene::load(const char* dir)
0827 {
0828     if(level>0) std::cout << "SScene::load dir " << ( dir ? dir : "-" ) << "\n" ;
0829     NPFold* fold = NPFold::Load(dir, RELDIR);
0830     import_(fold);
0831 }
0832 
0833 
0834 /**
0835 SScene::addFrames
0836 ------------------
0837 
0838 Canonically called from SScene::initFromTree with path
0839 argument from envvar::
0840 
0841     addFrames("$SScene__initFromTree_addFrames", st );
0842 
0843 Which is set for example from::
0844 
0845     ~/o/sysrap/tests/SScene_test.sh
0846 
0847 Actions:
0848 
0849 1. read framespec string from path file
0850 2. parse the string splitting into trimmed lines
0851 3. for each line get sfr with stree::get_frame add to frame vector
0852 4. add last frame f0 fabricated from the ce of the first global mergedmesh
0853 
0854 For integrated running a sensible place to configure this
0855 is next to the setting of the output geometry directory::
0856 
0857     export G4CXOpticks__SaveGeometry_DIR=$HOME/.opticks/GEOM/$GEOM
0858     export SScene__initFromTree_addFrames=$HOME/.opticks/GEOM/${GEOM}_framespec.txt
0859 
0860 The file needs to contain frame specification triplet lines, comments starting # are ignored::
0861 
0862     Hama:0:0
0863     NNVT:0:0
0864     Hama:0:1000
0865     NNVT:0:1000
0866     sDeadWater:0:-1
0867     GZ1.A06_07_FlangeI_Web_FlangeII:15:-1
0868     GZ1.B06_07_FlangeI_Web_FlangeII:15:-1
0869     solidXJfixture:0:-1
0870     solidXJfixture:20:-1
0871     solidXJfixture:40:-1
0872     solidXJfixture:55:-1
0873     solidXJanchor:0:-1
0874     solidXJanchor:20:-1
0875     solidXJanchor:40:-1
0876     solidXJanchor:55:-1
0877     solidSJCLSanchor:0:-1
0878     solidSJFixture:0:-1
0879     solidSJReceiver:0:-1
0880     solidSJReceiverFastern:0:-1
0881     sSurftube_0V1_0:0:-1
0882     sSurftube_38V1_0:0:-1
0883     sSurftube_38V1_1:0:-1
0884     solidXJfixture:27:-1     ## near bottom of CD
0885 
0886 These framespec lines are used to provide navigation bookmarks in the visualization GUI
0887 which are accessed by number keys 0-9 with and without modifier keys ALT, SHIFT, ALT+SHIFT.
0888 So it only makes sense to have up to 40 lines in the file. See SGLFW::HELP for details.
0889 The framespec lines are converted into sfr.h instances by stree::get_frame.
0890 
0891 
0892 
0893 TODO: move addFrames addFrame getFrame to SGLM as its not triangulated related
0894 
0895 
0896 **/
0897 
0898 inline void SScene::addFrames(const char* path, const stree* st)
0899 {
0900     std::string framespec ;
0901     bool framespec_exists = spath::Read( framespec, path );
0902 
0903     if(framespec_exists)
0904     {
0905         std::vector<std::string> lines ;
0906         sstr::SplitTrimSuppress(framespec.c_str(), '\n', lines) ;
0907 
0908         int num_line = lines.size();
0909         for(int i=0 ; i < num_line ; i++)
0910         {
0911             const std::string& line = lines[i];
0912             std::string _spec = sstr::StripComment(line);
0913             const char* spec = _spec.c_str();
0914             if(spec == nullptr) continue ;
0915             if(strlen(spec) == 0) continue ;
0916 
0917             bool has_frame = st->has_frame(spec);
0918             if(!has_frame)
0919             {
0920                 std::cout
0921                     << "SScene::addFrames FAIL to find frame "
0922                     << " spec [" << ( spec ? spec : "-" ) << "]\n"
0923                     << " line [" << line << "]\n"
0924                     ;
0925                 continue ;
0926             }
0927 
0928             sfr f = st->get_frame(spec);
0929             addFrame(f);
0930         }
0931     }
0932     else
0933     {
0934         std::cout << "SScene::addFrames framespec path [" << ( path ? path : "-" ) << "] does not exist \n" ;
0935     }
0936 
0937     // last frame that ensures always at least one
0938     const float* _ce = get_ce(0) ;
0939     sfr f0 = sfr::MakeFromCE(_ce) ;
0940     f0.set_name("MakeFromCE0");
0941     addFrame(f0);
0942 
0943 
0944 
0945     /**
0946     // this is wrong place to do this, need to do from main
0947     // because want to easily change target
0948 
0949     const char* MOI = ssys::getenvvar("MOI", nullptr);
0950     if(MOI)
0951     {
0952         sfr fm = st->get_frame(MOI);
0953         addFrame(fm);
0954     }
0955     **/
0956 
0957 
0958 }
0959 
0960 
0961 inline void SScene::addFrame( const sfr& _f)
0962 {
0963    sfr f = _f ;
0964    f.set_idx( frame.size() );
0965    frame.push_back(f);
0966 }
0967 
0968 
0969 /**
0970 SScene::getFrame
0971 ----------------
0972 
0973 Returns the *_idx* frame
0974 
0975 For argument _idx beyond available frames returns the last frame
0976 
0977 **/
0978 
0979 inline sfr SScene::getFrame(int _idx) const
0980 {
0981     int num_frame = frame.size();
0982     bool num_frame_expect =  num_frame > 0  ;
0983     int idx = ( _idx > -1 && _idx < num_frame ) ? _idx : num_frame-1  ;
0984 
0985     if(!num_frame_expect) std::cout
0986          << "SScene::getFrame"
0987          << " num_frame " << num_frame
0988          << " num_frame_expect " << ( num_frame_expect ? "YES" : "NO " )
0989          << " _idx " << _idx
0990          << " idx " << idx
0991          << "\n"
0992          ;
0993 
0994     assert( num_frame_expect );
0995 
0996     const sfr& f = frame[idx] ;
0997 
0998 
0999     assert( f.get_idx() == idx );
1000     return f ;
1001 }
1002 
1003 
1004 /**
1005 SScene::CopySelect
1006 -------------------
1007 
1008 Q: How/where are SScene::inst_info SScene::inst_tran used ? Need to know, to devise how to apply selection.
1009 
1010 A: SOPTIX_Scene.h SOPTIX_Scene::init_Instances is a pure triangulated rendering example
1011 
1012    * inst_tran
1013      inst_info {ridx, inst_count, inst_offset, 0}
1014      inst_col3 {ins_idx, gas_idx, sen_id, sen_idx}
1015 
1016      are intimately tied together, as inst_count and inst_offset
1017      from inst_info provide index references into inst_tran
1018 
1019    * recall that each instance refers to a compound solid of multiple lvid via gas_idx
1020 
1021 
1022 WIP : use inst_col3 info to apply lvid selection to the inst_tran and inst_col3
1023 HMM : lvid is available for the d_mg->subs SMesh.h instances, the SMeshGroup::copy
1024 applies ELV selection with the outcome that some of the SMeshGroup will be nullified
1025 and some will be greatly reduced in size : this means that the old gas_idx will
1026 no longer be valid... need mapping between them like CSGCopy does
1027 and need something like CSGCopy::copySolidInstances to populate d_inst_tran
1028 
1029 **/
1030 
1031 
1032 inline SScene* SScene::CopySelect( const SScene* src, const SBitSet* elv ) // static
1033 {
1034     SScene* dst = new SScene ;
1035     dst->soname = src->soname ; // despite selection copy all names
1036     int s_num_mg = src->meshgroup.size() ;
1037 
1038     int* solidMap = new int[s_num_mg];
1039 
1040 
1041     for(int i=0 ; i < s_num_mg ; i++)
1042     {
1043         int s_SolidIdx = i ;
1044         solidMap[i] = -1 ;
1045 
1046         const SMeshGroup* s_mg = src->meshgroup[i] ;
1047         SMeshGroup* d_mg = s_mg->copy(elv) ;
1048         if( d_mg == nullptr ) continue ;   // null when no subs are ELV selected
1049 
1050         int d_SolidIdx = dst->meshgroup.size() ; // index before adding (0-based)
1051         solidMap[s_SolidIdx] = d_SolidIdx ;
1052 
1053         dst->meshgroup.push_back(d_mg);
1054 
1055         //int ridx = s_SolidIdx ;  // first gen assumption
1056         int ridx = d_SolidIdx ;  // first gen assumption
1057 
1058         const SMesh* d_mesh = SMesh::Concatenate( d_mg->subs, ridx );
1059         dst->meshmerge.push_back(d_mesh);
1060     }
1061 
1062 
1063     int d_num_mg = dst->meshgroup.size() ;
1064 
1065     if(src->level>0) std::cout << "SScene::CopySelect d_num_mg " << d_num_mg << "\n" ;
1066 
1067     // pre-alloc makes it simpler to increment instance by instance
1068     dst->inst_info.resize(d_num_mg);
1069     for(int i=0 ; i < d_num_mg ; i++)
1070     {
1071         int d_ridx = i ;
1072         dst->inst_info[d_ridx].x = d_ridx ;
1073         dst->inst_info[d_ridx].y = 0 ;
1074         dst->inst_info[d_ridx].z = 0 ;
1075         dst->inst_info[d_ridx].w = 0 ;
1076     }
1077 
1078 
1079     dst->frame = src->frame ;
1080 
1081     [[maybe_unused]] int s_inst_info_num = src->inst_info.size() ;
1082     [[maybe_unused]] int s_inst_tran_num = src->inst_tran.size() ;
1083     [[maybe_unused]] int s_inst_col3_num = src->inst_col3.size() ;
1084     assert( s_inst_info_num == s_num_mg );
1085     assert( s_inst_tran_num == s_inst_col3_num );
1086 
1087     std::vector<int4>&  d_inst_info = dst->inst_info ;
1088     std::vector<glm::tvec4<int32_t>>&  d_inst_col3 = dst->inst_col3 ;
1089     std::vector<glm::tmat4x4<float>>&  d_inst_tran = dst->inst_tran ;
1090 
1091 
1092     // equivalent to CSGCopy::copySolidInstances
1093     for(int i=0 ; i < s_inst_col3_num ; i++)
1094     {
1095         const glm::tmat4x4<float>& _s_inst_tran = src->inst_tran[i] ;       // instance level
1096         const glm::tvec4<int32_t>& _s_inst_col3 = src->inst_col3[i] ;
1097 
1098         [[maybe_unused]] int32_t s_inst_idx = _s_inst_col3.x ;
1099         int32_t s_gas_idx = _s_inst_col3.y ;
1100 
1101         assert( s_inst_idx == i );
1102         assert( s_gas_idx < s_num_mg );
1103 
1104         int d_gas_idx = solidMap[s_gas_idx];
1105         assert( d_gas_idx <  d_num_mg );
1106 
1107         bool live_instance = d_gas_idx > -1 ;
1108 
1109         if(live_instance)
1110         {
1111             glm::tmat4x4<float> _d_inst_tran = _s_inst_tran ;
1112 
1113             int d_inst_offset = d_inst_tran.size();  // offset before push_back for 0-based
1114             d_inst_tran.push_back(_d_inst_tran);
1115             d_inst_col3.push_back( { d_inst_offset, d_gas_idx, _s_inst_col3.z, _s_inst_col3.w } );
1116         }
1117     }
1118 
1119     int d_inst_col3_num = d_inst_col3.size();
1120     for(int i=0 ; i < d_inst_col3_num ; i++)
1121     {
1122         int& d_gas_idx = d_inst_col3[i].y ;
1123         int d_ridx = d_gas_idx ;
1124         d_inst_info[d_ridx].y += 1 ;
1125     }
1126 
1127 
1128     // offsets needs to be cumulative sums of prior inst counts, so do separately for sanity
1129     int offset = 0 ;
1130     for(int i=0 ; i < d_num_mg ; i++)
1131     {
1132         int d_ridx = i ;
1133         d_inst_info[d_ridx].z = offset ;
1134         offset += d_inst_info[d_ridx].y ;
1135     }
1136 
1137     return dst ;
1138 }
1139 
1140 
1141 inline SScene* SScene::copy(const SBitSet* elv) const
1142 {
1143     return CopySelect(this, elv);
1144 }
1145 
1146 /**
1147 SScene::Compare
1148 ---------------
1149 
1150 Observed that this comparison does not notice
1151 exclusions of global LV, see tests/SSceneLoadTest.sh
1152 
1153 **/
1154 
1155 
1156 inline int SScene::Compare(const SScene* a, const SScene* b) // static
1157 {
1158     bool dump = false ;
1159     std::stringstream ss ;
1160     std::ostream* out = dump ? &ss : nullptr ;
1161 
1162     int mismatch = 0 ;
1163     mismatch += svec<int4>::Compare( "inst_info", a->inst_info, b->inst_info, out );
1164     mismatch += svec<glm::tmat4x4<float>>::Compare( "inst_tran", a->inst_tran, b->inst_tran, out );
1165     mismatch += svec<glm::tvec4<int32_t>>::Compare( "inst_col3", a->inst_col3, b->inst_col3, out );
1166     //mismatch += svec<sfr>::Compare( "frame", a->frame, b->frame, out );   // std::string name 4 bytes is bytewise discrepant
1167 
1168     if(dump) std::cout << "SScene::Compare mismatch "  << mismatch << "\n" ;
1169     if(out) std::cout << ss.str() << "\n" ;
1170 
1171     return mismatch ;
1172 }
1173 
1174 
1175 
1176